commit 66d4a00ba86dfe906eedbe175f113a4be6af3986
Author: Fanhuitong <981964879@qq.com>
Date: Thu Mar 28 10:39:39 2024 +0800
init
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b1123a0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+/.idea/
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..f5bb2e0
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,194 @@
+apply plugin: 'com.android.application'
+
+static def appName() {
+ return "AndroidAppTemplate"
+}
+
+static def releaseTime() {
+ return new Date().format("yyyyMMdd_HHmmss", TimeZone.getDefault())
+}
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+
+ defaultConfig {
+ applicationId "com.ttstd.template"
+ minSdkVersion 26
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ multiDexEnabled true
+
+ ndk {
+ //选择要添加的对应 cpu 类型的 .so 库。
+ abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', "x86"
+ // 还可以添加 'x86', 'x86_64', 'mips', 'mips64'
+ }
+
+ javaCompileOptions {
+ annotationProcessorOptions {
+ includeCompileClasspath true
+ }
+ }
+
+ lintOptions {
+ checkReleaseBuilds false
+ // Or, if you prefer, you can continue to check for errors in release builds,
+ // but continue the build even when errors are found:
+ abortOnError false
+ }
+
+ dexOptions {
+ jumboMode true
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ viewBinding{
+ enabled = true
+ }
+
+ dataBinding {
+ enabled true
+ }
+
+ }
+
+ //签名
+ signingConfigs {
+ tuixin {// 签名文件
+ storeFile file("keystore/tuixin.jks")
+ storePassword "123456"
+ keyAlias "universal"
+ keyPassword "123456"
+ v2SigningEnabled false
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+
+ debug {
+ buildConfigField "String", "platform", '"tuixin"'
+ versionNameSuffix "-debug"
+ debuggable true
+ //Zipalign优化
+ zipAlignEnabled true
+ minifyEnabled false
+ signingConfig signingConfigs.tuixin
+ applicationVariants.all { variant ->
+ variant.outputs.each { output ->
+ if (outputFile != null) {
+ def fileName = "${appName()}-${variant.versionCode}-V${variant.versionName}-${releaseTime()}-${buildType.name}.apk"
+ output.outputFileName = fileName
+ }
+ }
+ }
+ }
+
+ release {
+ buildConfigField "String", "platform", '"tuixin"'
+ //Zipalign优化
+ zipAlignEnabled true
+ //混淆
+ minifyEnabled false
+ //前一部分代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明,后一个文件是自己的定义混淆文件
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ //签名
+ signingConfig signingConfigs.tuixin
+// 将release版本的包名重命名,加上版本及日期
+ applicationVariants.all { variant ->
+ variant.outputs.each { output ->
+ def outputFile = ""
+ if (outputFile != null) {
+ def fileName = "${appName()}-${variant.versionCode}-V${variant.versionName}-${releaseTime()}-${buildType.name}.apk"
+ output.outputFileName = new File(outputFile, fileName)
+ }
+ }
+ }
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'androidx.appcompat:appcompat:1.3.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+ implementation 'androidx.recyclerview:recyclerview:1.2.1'
+
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+
+ debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
+
+ //BindView
+ implementation 'com.jakewharton:butterknife:10.2.3'
+// If you are using Kotlin, replace annotationProcessor with kapt.
+// annotationProcessor rootProject.ext.dependencies["butterknife-compiler"]
+ annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
+
+ //okhttp
+ implementation 'com.squareup.okhttp3:okhttp:4.9.3'
+ implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
+ //Retrofit
+ implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+ implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
+ // gson converter
+ implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
+ // 标准转换器,去掉 Retrofit以Mutipart上传参数时,String参数会多一对双引号
+ implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
+ //RxJava
+ implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
+ implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
+ //生命周期管理
+ implementation 'com.trello.rxlifecycle4:rxlifecycle:4.0.2'
+ implementation 'com.trello.rxlifecycle4:rxlifecycle-android:4.0.2'
+ implementation 'com.trello.rxlifecycle4:rxlifecycle-components:4.0.2'
+ implementation 'com.trello.rxlifecycle4:rxlifecycle-components-preference:4.0.2'
+ implementation 'com.trello.rxlifecycle4:rxlifecycle-android-lifecycle:4.0.2'
+ //rxbinding
+ implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0'
+ //Google
+ implementation 'com.google.code.gson:gson:2.10.1'
+ implementation 'com.google.zxing:core:3.5.0'
+ implementation 'com.google.android.material:material:1.4.0'
+ //图片加载框架
+ implementation 'com.github.bumptech.glide:glide:4.13.2'
+ annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2'
+ //磁盘缓存
+ implementation 'com.jakewharton:disklrucache:2.0.2'
+ //Aria
+ implementation 'com.arialyy.aria:core:3.8.15'
+ annotationProcessor 'com.arialyy.aria:compiler:3.8.15'
+ //MMKV
+ implementation 'com.tencent:mmkv-static:1.2.14'
+ //bugly
+ implementation 'com.tencent.bugly:crashreport:4.1.9.2'
+ implementation 'com.iqiyi.xcrash:xcrash-android-lib:3.0.0'
+ //工具类
+ implementation 'com.blankj:utilcodex:1.31.0'
+ //autosize会改变第三方view的大小
+ //https://github.com/JessYanCoding/AndroidAutoSize
+ implementation 'com.github.JessYanCoding:AndroidAutoSize:1.2.1'
+ implementation 'com.flyco.tablayout:FlycoTabLayout_Lib:2.1.2@aar'
+ implementation 'com.github.chrisbanes:PhotoView:2.0.0'
+ //沉浸状态栏
+ implementation 'com.gitee.zackratos:UltimateBarX:0.8.0'
+// //验证码输入
+// implementation 'com.jacktuotuo.customview:verificationcodeview:1.0.5'
+ //动态权限框架
+// implementation 'com.hjq:xxpermissions:6.0'
+}
diff --git a/app/keystore/tuixin.jks b/app/keystore/tuixin.jks
new file mode 100644
index 0000000..d7ecbad
Binary files /dev/null and b/app/keystore/tuixin.jks differ
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/androidTest/java/com/ttstd/template/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/ttstd/template/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..743420d
--- /dev/null
+++ b/app/src/androidTest/java/com/ttstd/template/ExampleInstrumentedTest.java
@@ -0,0 +1,27 @@
+package com.ttstd.template;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ assertEquals("com.ttstd.template", appContext.getPackageName());
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1a9b385
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/template/activity/main/MainActivity.java b/app/src/main/java/com/ttstd/template/activity/main/MainActivity.java
new file mode 100644
index 0000000..aa1f984
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/activity/main/MainActivity.java
@@ -0,0 +1,25 @@
+package com.ttstd.template.activity.main;
+
+import com.ttstd.template.R;
+import com.ttstd.template.base.BaseActivity;
+
+public class MainActivity extends BaseActivity {
+
+
+ @Override
+ public int getLayoutId() {
+ return R.layout.activity_main;
+ }
+
+ @Override
+ public void initView() {
+
+ }
+
+ @Override
+ public void initData() {
+
+ }
+
+
+}
diff --git a/app/src/main/java/com/ttstd/template/activity/template/TemplateActivity.java b/app/src/main/java/com/ttstd/template/activity/template/TemplateActivity.java
new file mode 100644
index 0000000..6b86678
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/activity/template/TemplateActivity.java
@@ -0,0 +1,46 @@
+package com.ttstd.template.activity.template;
+
+import android.content.res.Configuration;
+
+import androidx.annotation.NonNull;
+
+import com.ttstd.template.R;
+import com.ttstd.template.base.BaseLightActivity;
+
+import butterknife.ButterKnife;
+
+public class TemplateActivity extends BaseLightActivity implements TemplateContact.TemplateView {
+
+ private static final String TAG = TemplateActivity.class.getSimpleName();
+
+ private TemplatePresenter mPresenter;
+
+ @Override
+ public int getLayoutId() {
+ return R.layout.activity_video;
+ }
+
+ @Override
+ public void initView() {
+ ButterKnife.bind(this);
+ mPresenter = new TemplatePresenter(this);
+ mPresenter.setLifecycle(lifecycleSubject);
+ mPresenter.attachView(this);
+ }
+
+ @Override
+ public void initData() {
+
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ Configuration config = getResources().getConfiguration();
+ }
+}
diff --git a/app/src/main/java/com/ttstd/template/activity/template/TemplateContact.java b/app/src/main/java/com/ttstd/template/activity/template/TemplateContact.java
new file mode 100644
index 0000000..14e64e3
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/activity/template/TemplateContact.java
@@ -0,0 +1,14 @@
+package com.ttstd.template.activity.template;
+
+import com.ttstd.template.base.BasePresenter;
+import com.ttstd.template.base.BaseView;
+
+public class TemplateContact {
+ interface Presenter extends BasePresenter {
+
+ }
+
+ public interface TemplateView extends BaseView {
+
+ }
+}
diff --git a/app/src/main/java/com/ttstd/template/activity/template/TemplatePresenter.java b/app/src/main/java/com/ttstd/template/activity/template/TemplatePresenter.java
new file mode 100644
index 0000000..96c294c
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/activity/template/TemplatePresenter.java
@@ -0,0 +1,44 @@
+package com.ttstd.template.activity.template;
+
+import android.content.Context;
+
+import com.trello.rxlifecycle4.android.ActivityEvent;
+import com.ttstd.template.disklrucache.CacheHelper;
+
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+
+public class TemplatePresenter implements TemplateContact.Presenter {
+ private static final String TAG = TemplatePresenter.class.getSimpleName();
+
+ private TemplateContact.TemplateView mView;
+ private Context mContext;
+ private CacheHelper mCacheHelper;
+
+ public TemplatePresenter(Context context) {
+ this.mContext = context;
+ this.mCacheHelper = new CacheHelper(context);
+ }
+
+ private BehaviorSubject lifecycle;
+
+ public void setLifecycle(BehaviorSubject lifecycle) {
+ this.lifecycle = lifecycle;
+ }
+
+ public BehaviorSubject getLifecycle() {
+ return lifecycle;
+ }
+
+ @Override
+ public void attachView(TemplateContact.TemplateView view) {
+ this.mView = view;
+ }
+
+ @Override
+ public void detachView() {
+ this.mView = null;
+ }
+
+
+}
diff --git a/app/src/main/java/com/ttstd/template/base/BaseActivity.java b/app/src/main/java/com/ttstd/template/base/BaseActivity.java
new file mode 100644
index 0000000..cde9933
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/base/BaseActivity.java
@@ -0,0 +1,134 @@
+package com.ttstd.template.base;
+
+import android.app.ActivityManager;
+import android.os.Build;
+import android.os.Bundle;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.CheckResult;
+import androidx.annotation.ContentView;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.trello.rxlifecycle4.LifecycleProvider;
+import com.trello.rxlifecycle4.LifecycleTransformer;
+import com.trello.rxlifecycle4.RxLifecycle;
+import com.trello.rxlifecycle4.android.ActivityEvent;
+import com.trello.rxlifecycle4.android.RxLifecycleAndroid;
+import com.ttstd.template.R;
+import com.zackratos.ultimatebarx.ultimatebarx.java.UltimateBarX;
+
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+
+public abstract class BaseActivity extends AppCompatActivity implements LifecycleProvider {
+ public final BehaviorSubject lifecycleSubject = BehaviorSubject.create();
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+// StatusBarUtil.init(this);
+ UltimateBarX.statusBar(this)
+ .transparent()
+ .colorRes(R.color.colorPrimaryDark)
+ .light(true)
+ .apply();
+ UltimateBarX.navigationBar(this)
+ .transparent()
+ .colorRes(R.color.colorPrimaryDark)
+ .light(true)
+ .apply();
+ setContentView(this.getLayoutId());
+ initView();
+ initData();
+ //最近任务和应用图标不一样
+ if (Build.VERSION.SDK_INT > 27) {
+ ActivityManager.TaskDescription description = new ActivityManager.TaskDescription(getString(R.string.app_name), R.mipmap.ic_launcher, getColor(R.color.colorPrimary));
+ this.setTaskDescription(description);
+ }
+ }
+
+ /**
+ * 设置布局
+ */
+ public abstract int getLayoutId();
+
+ /**
+ * 初始化视图
+ */
+ public abstract void initView();
+
+
+ /**
+ * 初始化数据
+ */
+ public abstract void initData();
+
+ public BaseActivity() {
+ super();
+ }
+
+ @ContentView
+ public BaseActivity(@LayoutRes int contentLayoutId) {
+ super(contentLayoutId);
+ }
+
+ @Override
+ @NonNull
+ @CheckResult
+ public final Observable lifecycle() {
+ return lifecycleSubject.hide();
+ }
+
+ @Override
+ @NonNull
+ @CheckResult
+ public final LifecycleTransformer bindUntilEvent(@NonNull ActivityEvent event) {
+ return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
+ }
+
+ @Override
+ @NonNull
+ @CheckResult
+ public final LifecycleTransformer bindToLifecycle() {
+ return RxLifecycleAndroid.bindActivity(lifecycleSubject);
+ }
+
+ @Override
+ @CallSuper
+ protected void onStart() {
+ super.onStart();
+ lifecycleSubject.onNext(ActivityEvent.START);
+ }
+
+ @Override
+ @CallSuper
+ protected void onResume() {
+ super.onResume();
+ lifecycleSubject.onNext(ActivityEvent.RESUME);
+ }
+
+ @Override
+ @CallSuper
+ protected void onPause() {
+ lifecycleSubject.onNext(ActivityEvent.PAUSE);
+ super.onPause();
+ }
+
+ @Override
+ @CallSuper
+ protected void onStop() {
+ lifecycleSubject.onNext(ActivityEvent.STOP);
+ super.onStop();
+ }
+
+ @Override
+ @CallSuper
+ protected void onDestroy() {
+ lifecycleSubject.onNext(ActivityEvent.DESTROY);
+ super.onDestroy();
+ }
+}
diff --git a/app/src/main/java/com/ttstd/template/base/BaseApplication.java b/app/src/main/java/com/ttstd/template/base/BaseApplication.java
new file mode 100644
index 0000000..6d0e04b
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/base/BaseApplication.java
@@ -0,0 +1,78 @@
+package com.ttstd.template.base;
+
+import android.app.Application;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.tencent.mmkv.MMKV;
+import com.ttstd.template.BuildConfig;
+import com.ttstd.template.network.NetInterfaceManager;
+import com.ttstd.template.utils.SystemUtils;
+
+public class BaseApplication extends Application {
+ private static final String TAG = BaseApplication.class.getSimpleName();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ //非主进程不初始化
+ if (SystemUtils.isMainProcessName(this, android.os.Process.myPid())) {
+
+ }
+ init();
+ }
+
+ private void init() {
+ if (!BuildConfig.DEBUG) {
+ catchException();
+ }
+ String rootDir = MMKV.initialize(this);
+ Log.i(TAG, "mmkv root: " + rootDir);
+
+
+// CrashReport.initCrashReport(getApplicationContext(), "df75262f6a", false);
+// CrashReport.setDeviceId(this, Utils.getSerial());
+ xcrash.XCrash.init(this);
+
+ NetInterfaceManager.init(this);
+ }
+
+ private void catchException() {
+ Thread.setDefaultUncaughtExceptionHandler(
+ new Thread.UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ Log.e("捕获异常子线程:", Thread.currentThread().getName() +
+ "在:" + e.getStackTrace()[0].getClassName());
+ }
+ }
+ );
+ //下面是新增方法!
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ Looper.loop(); //会先执行这个方法,然后在执行下面的异常捕获方法!
+ } catch (Exception e) {
+ Log.e("捕获异常主线程:", Thread.currentThread().getName() +
+ "在:" + e.getStackTrace()[0].getClassName());
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ }
+
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ }
+}
diff --git a/app/src/main/java/com/ttstd/template/base/BaseFragment.java b/app/src/main/java/com/ttstd/template/base/BaseFragment.java
new file mode 100644
index 0000000..629b500
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/base/BaseFragment.java
@@ -0,0 +1,156 @@
+package com.ttstd.template.base;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.CheckResult;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.trello.rxlifecycle4.LifecycleProvider;
+import com.trello.rxlifecycle4.LifecycleTransformer;
+import com.trello.rxlifecycle4.RxLifecycle;
+import com.trello.rxlifecycle4.android.FragmentEvent;
+import com.trello.rxlifecycle4.android.RxLifecycleAndroid;
+
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+public abstract class BaseFragment extends Fragment implements LifecycleProvider {
+ public final BehaviorSubject lifecycleSubject = BehaviorSubject.create();
+
+ protected boolean isViewInitiated;
+ protected boolean isVisibleToUser;
+ protected boolean isDataInitiated;
+
+ @Override
+ @NonNull
+ @CheckResult
+ public final Observable lifecycle() {
+ return lifecycleSubject.hide();
+ }
+
+ @Override
+ @NonNull
+ @CheckResult
+ public final LifecycleTransformer bindUntilEvent(@NonNull FragmentEvent event) {
+ return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
+ }
+
+ @Override
+ @NonNull
+ @CheckResult
+ public final LifecycleTransformer bindToLifecycle() {
+ return RxLifecycleAndroid.bindFragment(lifecycleSubject);
+ }
+
+ @Override
+ @CallSuper
+ public void onAttach(android.app.Activity activity) {
+ super.onAttach(activity);
+ lifecycleSubject.onNext(FragmentEvent.ATTACH);
+ }
+
+ @Override
+ @CallSuper
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ lifecycleSubject.onNext(FragmentEvent.CREATE);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ return super.onCreateView(inflater, container, savedInstanceState);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ isViewInitiated = true;
+ prepareFetchData();
+ }
+
+ @Override
+ public void setUserVisibleHint(boolean isVisibleToUser) {
+ super.setUserVisibleHint(isVisibleToUser);
+ this.isVisibleToUser = isVisibleToUser;
+ prepareFetchData();
+ }
+
+ public abstract void fetchData();
+
+ public boolean prepareFetchData() {
+ return prepareFetchData(false);
+ }
+
+ public boolean prepareFetchData(boolean forceUpdate) {
+ if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
+ fetchData();
+ //注释掉保证每次都更新数据
+ isDataInitiated = true;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ @CallSuper
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ lifecycleSubject.onNext(FragmentEvent.CREATE_VIEW);
+ }
+
+ @Override
+ @CallSuper
+ public void onStart() {
+ super.onStart();
+ lifecycleSubject.onNext(FragmentEvent.START);
+ }
+
+ @Override
+ @CallSuper
+ public void onResume() {
+ super.onResume();
+ lifecycleSubject.onNext(FragmentEvent.RESUME);
+ }
+
+ @Override
+ @CallSuper
+ public void onPause() {
+ lifecycleSubject.onNext(FragmentEvent.PAUSE);
+ super.onPause();
+ }
+
+ @Override
+ @CallSuper
+ public void onStop() {
+ lifecycleSubject.onNext(FragmentEvent.STOP);
+ super.onStop();
+ }
+
+ @Override
+ @CallSuper
+ public void onDestroyView() {
+ lifecycleSubject.onNext(FragmentEvent.DESTROY_VIEW);
+ super.onDestroyView();
+ }
+
+ @Override
+ @CallSuper
+ public void onDestroy() {
+ lifecycleSubject.onNext(FragmentEvent.DESTROY);
+ super.onDestroy();
+ }
+
+ @Override
+ @CallSuper
+ public void onDetach() {
+ lifecycleSubject.onNext(FragmentEvent.DETACH);
+ super.onDetach();
+ }
+}
diff --git a/app/src/main/java/com/ttstd/template/base/BaseLightActivity.java b/app/src/main/java/com/ttstd/template/base/BaseLightActivity.java
new file mode 100644
index 0000000..6ec1beb
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/base/BaseLightActivity.java
@@ -0,0 +1,129 @@
+package com.ttstd.template.base;
+
+import android.os.Bundle;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.CheckResult;
+import androidx.annotation.ContentView;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.trello.rxlifecycle4.LifecycleProvider;
+import com.trello.rxlifecycle4.LifecycleTransformer;
+import com.trello.rxlifecycle4.RxLifecycle;
+import com.trello.rxlifecycle4.android.ActivityEvent;
+import com.trello.rxlifecycle4.android.RxLifecycleAndroid;
+import com.ttstd.template.R;
+import com.zackratos.ultimatebarx.ultimatebarx.java.UltimateBarX;
+
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+
+public abstract class BaseLightActivity extends AppCompatActivity implements LifecycleProvider {
+ public final BehaviorSubject lifecycleSubject = BehaviorSubject.create();
+
+ public BaseLightActivity() {
+ super();
+ }
+
+ @ContentView
+ public BaseLightActivity(@LayoutRes int contentLayoutId) {
+ super(contentLayoutId);
+ }
+
+ @Override
+ @NonNull
+ @CheckResult
+ public final Observable lifecycle() {
+ return lifecycleSubject.hide();
+ }
+
+ @Override
+ @NonNull
+ @CheckResult
+ public final LifecycleTransformer bindUntilEvent(@NonNull ActivityEvent event) {
+ return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
+ }
+
+ @Override
+ @NonNull
+ @CheckResult
+ public final LifecycleTransformer bindToLifecycle() {
+ return RxLifecycleAndroid.bindActivity(lifecycleSubject);
+ }
+
+ @Override
+ @CallSuper
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ lifecycleSubject.onNext(ActivityEvent.CREATE);
+// StatusBarUtil.init(this);
+ UltimateBarX.statusBar(this)
+ .transparent()
+ .colorRes(R.color.colorPrimaryDark)
+ .light(true)
+ .apply();
+ UltimateBarX.navigationBar(this)
+ .transparent()
+ .colorRes(R.color.colorPrimaryDark)
+ .light(true)
+ .apply();
+ setContentView(this.getLayoutId());
+ initView();
+ initData();
+ }
+
+ /**
+ * 设置布局
+ */
+ public abstract int getLayoutId();
+
+ /**
+ * 初始化视图
+ */
+ public abstract void initView();
+
+
+ /**
+ * 初始化数据
+ */
+ public abstract void initData();
+
+ @Override
+ @CallSuper
+ protected void onStart() {
+ super.onStart();
+ lifecycleSubject.onNext(ActivityEvent.START);
+ }
+
+ @Override
+ @CallSuper
+ protected void onResume() {
+ super.onResume();
+ lifecycleSubject.onNext(ActivityEvent.RESUME);
+ }
+
+ @Override
+ @CallSuper
+ protected void onPause() {
+ lifecycleSubject.onNext(ActivityEvent.PAUSE);
+ super.onPause();
+ }
+
+ @Override
+ @CallSuper
+ protected void onStop() {
+ lifecycleSubject.onNext(ActivityEvent.STOP);
+ super.onStop();
+ }
+
+ @Override
+ @CallSuper
+ protected void onDestroy() {
+ lifecycleSubject.onNext(ActivityEvent.DESTROY);
+ super.onDestroy();
+ }
+}
diff --git a/app/src/main/java/com/ttstd/template/base/BasePresenter.java b/app/src/main/java/com/ttstd/template/base/BasePresenter.java
new file mode 100644
index 0000000..8e81b2a
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/base/BasePresenter.java
@@ -0,0 +1,7 @@
+package com.ttstd.template.base;
+
+public interface BasePresenter {
+ void attachView(V view);
+
+ void detachView();
+}
diff --git a/app/src/main/java/com/ttstd/template/base/BaseView.java b/app/src/main/java/com/ttstd/template/base/BaseView.java
new file mode 100644
index 0000000..75bcf3f
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/base/BaseView.java
@@ -0,0 +1,4 @@
+package com.ttstd.template.base;
+
+public interface BaseView {
+}
diff --git a/app/src/main/java/com/ttstd/template/bean/BaseResponse.java b/app/src/main/java/com/ttstd/template/bean/BaseResponse.java
new file mode 100644
index 0000000..82bb9f9
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/bean/BaseResponse.java
@@ -0,0 +1,24 @@
+package com.ttstd.template.bean;
+
+import androidx.annotation.NonNull;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParser;
+
+import java.io.Serializable;
+
+
+public class BaseResponse implements Serializable {
+
+ private static final long serialVersionUID = 5468533687801294972L;
+
+ public int code;
+ public String msg;
+ public T data;
+
+ @NonNull
+ @Override
+ public String toString() {
+ return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/template/comm/CommonConfig.java b/app/src/main/java/com/ttstd/template/comm/CommonConfig.java
new file mode 100644
index 0000000..b7ef7e8
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/comm/CommonConfig.java
@@ -0,0 +1,5 @@
+package com.ttstd.template.comm;
+
+public class CommonConfig {
+ public static final String MMKV_ID = "InterProcessKV";
+}
diff --git a/app/src/main/java/com/ttstd/template/disklrucache/CacheHelper.java b/app/src/main/java/com/ttstd/template/disklrucache/CacheHelper.java
new file mode 100644
index 0000000..4daf039
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/disklrucache/CacheHelper.java
@@ -0,0 +1,474 @@
+package com.ttstd.template.disklrucache;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.util.Log;
+
+import com.jakewharton.disklrucache.DiskLruCache;
+import com.tencent.mmkv.MMKV;
+import com.ttstd.template.comm.CommonConfig;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Serializable;
+
+/**
+ * 磁盘缓存帮助类
+ */
+public class CacheHelper {
+ private static final String TAG = "DiskLruCacheHelper";
+
+ private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+
+ private static final String DIR_NAME = "diskCache";
+ private static final int MAX_COUNT = 1024 * 1024 * 1024;
+ private static final int DEFAULT_APP_VERSION = 1;
+
+ private DiskLruCache mDiskLruCache;
+
+ public CacheHelper(Context context) {
+ mDiskLruCache = generateCache(context, DIR_NAME, MAX_COUNT);
+ }
+
+ public CacheHelper(Context context, String dirName) {
+ mDiskLruCache = generateCache(context, dirName, MAX_COUNT);
+ }
+
+ public CacheHelper(Context context, String dirName, int maxCount) {
+ mDiskLruCache = generateCache(context, dirName, maxCount);
+ }
+
+ //custom cache dir
+ public CacheHelper(File dir) {
+ mDiskLruCache = generateCache(null, dir, MAX_COUNT);
+ }
+
+ public CacheHelper(Context context, File dir) {
+ mDiskLruCache = generateCache(context, dir, MAX_COUNT);
+ }
+
+ public CacheHelper(Context context, File dir, int maxCount) {
+ mDiskLruCache = generateCache(context, dir, maxCount);
+ }
+
+ private DiskLruCache generateCache(Context context, File dir, int maxCount) {
+ if (!dir.exists() || !dir.isDirectory()) {
+ throw new IllegalArgumentException(
+ dir + " is not a directory or does not exists. ");
+ }
+
+ int appVersion = context == null ? DEFAULT_APP_VERSION : Utils.getAppVersion(context);
+
+ DiskLruCache diskLruCache = null;
+ try {
+ diskLruCache = DiskLruCache.open(
+ dir,
+ appVersion,
+ DEFAULT_APP_VERSION,
+ maxCount);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return diskLruCache;
+ }
+
+ private DiskLruCache generateCache(Context context, String dirName, int maxCount) {
+ DiskLruCache diskLruCache = null;
+ try {
+ diskLruCache = DiskLruCache.open(
+ getDiskCacheDir(context, dirName),
+ Utils.getAppVersion(context),
+ DEFAULT_APP_VERSION,
+ maxCount);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return diskLruCache;
+ }
+
+ // =======================================
+ // ============== String 数据 读写 =============
+ // =======================================
+
+ public void put(String key, String value) {
+ Log.e(TAG, "put: key = " + key + " value = " + value);
+ mMMKV.encode(key + "_time", System.currentTimeMillis());
+ mMMKV.encode(key + "_mmkv", value);
+
+ DiskLruCache.Editor edit = null;
+ BufferedWriter bw = null;
+ try {
+ edit = editor(key);
+ if (edit == null) {
+ return;
+ }
+ OutputStream os = edit.newOutputStream(0);
+ bw = new BufferedWriter(new OutputStreamWriter(os));
+ bw.write(value);
+ edit.commit();//write CLEAN
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.e(TAG, "put: " + e.getMessage());
+ try {
+ //s
+ edit.abort();//write REMOVE
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ Log.e(TAG, "put: " + e1.getMessage());
+ }
+ } finally {
+ try {
+ if (bw != null) {
+ bw.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.e(TAG, "put: " + e.getMessage());
+ }
+ }
+ }
+
+ public String getAsString(String key) {
+ Log.e(TAG, "getAsString: " + key);
+ InputStream inputStream = null;
+ try {
+ //write READ
+ inputStream = get(key);
+ if (inputStream == null) {
+ return mMMKV.decodeString(key + "_mmkv", null);
+ }
+ StringBuilder sb = new StringBuilder();
+ int len = 0;
+ byte[] buf = new byte[128];
+ while ((len = inputStream.read(buf)) != -1) {
+ sb.append(new String(buf, 0, len));
+ }
+ return sb.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.e(TAG, "getAsString: " + e.getMessage());
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ Log.e(TAG, "getAsString: " + e1.getMessage());
+ }
+ }
+ }
+ return null;
+ }
+
+
+ public void put(String key, JSONObject jsonObject) {
+ put(key, jsonObject.toString());
+ }
+
+ public JSONObject getAsJson(String key) {
+ String val = getAsString(key);
+ try {
+ if (val != null) {
+ return new JSONObject(val);
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ // =======================================
+ // ============ JSONArray 数据 读写 =============
+ // =======================================
+
+ public void put(String key, JSONArray jsonArray) {
+ put(key, jsonArray.toString());
+ }
+
+ public JSONArray getAsJSONArray(String key) {
+ String JSONString = getAsString(key);
+ try {
+ JSONArray obj = new JSONArray(JSONString);
+ return obj;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ // =======================================
+ // ============== byte 数据 读写 =============
+ // =======================================
+
+ /**
+ * 保存 byte数据 到 缓存中
+ *
+ * @param key 保存的key
+ * @param value 保存的数据
+ */
+ public void put(String key, byte[] value) {
+ OutputStream out = null;
+ DiskLruCache.Editor editor = null;
+ try {
+ editor = editor(key);
+ if (editor == null) {
+ return;
+ }
+ out = editor.newOutputStream(0);
+ out.write(value);
+ out.flush();
+ editor.commit();//write CLEAN
+ } catch (Exception e) {
+ e.printStackTrace();
+ try {
+ editor.abort();//write REMOVE
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+
+ public byte[] getAsBytes(String key) {
+ byte[] res = null;
+ InputStream is = get(key);
+ if (is == null) {
+ return null;
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ byte[] buf = new byte[256];
+ int len = 0;
+ while ((len = is.read(buf)) != -1) {
+ baos.write(buf, 0, len);
+ }
+ res = baos.toByteArray();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return res;
+ }
+
+
+ // =======================================
+ // ============== 序列化 数据 读写 =============
+ // =======================================
+ public void put(String key, Serializable value) {
+ DiskLruCache.Editor editor = editor(key);
+ ObjectOutputStream oos = null;
+ if (editor == null) {
+ return;
+ }
+ try {
+ OutputStream os = editor.newOutputStream(0);
+ oos = new ObjectOutputStream(os);
+ oos.writeObject(value);
+ oos.flush();
+ editor.commit();
+ } catch (IOException e) {
+ e.printStackTrace();
+ try {
+ editor.abort();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ } finally {
+ try {
+ if (oos != null) {
+ oos.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public T getAsSerializable(String key) {
+ T t = null;
+ InputStream is = get(key);
+ ObjectInputStream ois = null;
+ if (is == null) {
+ return null;
+ }
+ try {
+ ois = new ObjectInputStream(is);
+ t = (T) ois.readObject();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if (ois != null) {
+ ois.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return t;
+ }
+
+ // =======================================
+ // ============== bitmap 数据 读写 =============
+ // =======================================
+ public void put(String key, Bitmap bitmap) {
+ put(key, Utils.bitmap2Bytes(bitmap));
+ }
+
+ public Bitmap getAsBitmap(String key) {
+ byte[] bytes = getAsBytes(key);
+ if (bytes == null) {
+ return null;
+ }
+ return Utils.bytes2Bitmap(bytes);
+ }
+
+ // =======================================
+ // ============= drawable 数据 读写 =============
+ // =======================================
+ public void put(String key, Drawable value) {
+ put(key, Utils.drawable2Bitmap(value));
+ }
+
+ public Drawable getAsDrawable(String key) {
+ byte[] bytes = getAsBytes(key);
+ if (bytes == null) {
+ return null;
+ }
+ return Utils.bitmap2Drawable(Utils.bytes2Bitmap(bytes));
+ }
+
+ // =======================================
+ // ============= other methods =============
+ // =======================================
+ public boolean remove(String key) {
+ try {
+ key = Utils.hashKeyForDisk(key);
+ return mDiskLruCache.remove(key);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ public void close() throws IOException {
+ mDiskLruCache.close();
+ }
+
+ public void delete() throws IOException {
+ mDiskLruCache.delete();
+ mMMKV.clearAll();
+ }
+
+ public void flush() throws IOException {
+ mDiskLruCache.flush();
+ }
+
+ public boolean isClosed() {
+ return mDiskLruCache.isClosed();
+ }
+
+ public long size() {
+ return mDiskLruCache.size();
+ }
+
+ public void setMaxSize(long maxSize) {
+ mDiskLruCache.setMaxSize(maxSize);
+ }
+
+ public File getDirectory() {
+ return mDiskLruCache.getDirectory();
+ }
+
+ public long getMaxSize() {
+ return mDiskLruCache.getMaxSize();
+ }
+
+
+ // =======================================
+ // ===遇到文件比较大的,可以直接通过流读写 =====
+ // =======================================
+ //basic editor
+ public DiskLruCache.Editor editor(String key) {
+ try {
+ key = Utils.hashKeyForDisk(key);
+ //wirte DIRTY
+ DiskLruCache.Editor edit = mDiskLruCache.edit(key);
+ //edit maybe null :the entry is editing
+ if (edit == null) {
+ Log.w(TAG, "the entry spcified key:" + key + " is editing by other . ");
+ }
+ return edit;
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.e(TAG, "editor: " + e.getMessage());
+ }
+
+ return null;
+ }
+
+
+ //basic get
+ public InputStream get(String key) {
+ try {
+ DiskLruCache.Snapshot snapshot = mDiskLruCache.get(Utils.hashKeyForDisk(key));
+ if (snapshot == null) //not find entry , or entry.readable = false
+ {
+ Log.e(TAG, "not find entry , or entry.readable = false");
+ return null;
+ }
+ //write READ
+ return snapshot.getInputStream(0);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ }
+
+
+ // =======================================
+ // ============== 序列化 数据 读写 =============
+ // =======================================
+
+ private File getDiskCacheDir(Context context, String uniqueName) {
+ String cachePath;
+ if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
+ || !Environment.isExternalStorageRemovable()) {
+ if (context.getExternalCacheDir() != null) {
+ cachePath = context.getExternalCacheDir().getPath();
+ } else if (context.getExternalFilesDir("cache") != null) {
+ cachePath = context.getExternalFilesDir("cache").getPath();
+ } else {
+ cachePath = context.getCacheDir().getPath();
+ }
+ } else {
+ cachePath = context.getCacheDir().getPath();
+ }
+ return new File(cachePath + File.separator + uniqueName);
+ }
+
+}
diff --git a/app/src/main/java/com/ttstd/template/disklrucache/DiskLruCacheHelper.java b/app/src/main/java/com/ttstd/template/disklrucache/DiskLruCacheHelper.java
new file mode 100644
index 0000000..bcbfd94
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/disklrucache/DiskLruCacheHelper.java
@@ -0,0 +1,427 @@
+package com.ttstd.template.disklrucache;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.util.Log;
+
+import com.jakewharton.disklrucache.DiskLruCache;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Serializable;
+
+/**
+ * 磁盘缓存帮助类
+ */
+public class DiskLruCacheHelper {
+ private static final String DIR_NAME = "diskCache";
+ private static final int MAX_COUNT = 5 * 1024 * 1024;
+ private static final int DEFAULT_APP_VERSION = 1;
+
+ private static final String TAG = "DiskLruCacheHelper";
+
+ private DiskLruCache mDiskLruCache;
+
+ public DiskLruCacheHelper(Context context) throws IOException {
+ mDiskLruCache = generateCache(context, DIR_NAME, MAX_COUNT);
+ }
+
+ public DiskLruCacheHelper(Context context, String dirName) throws IOException {
+ mDiskLruCache = generateCache(context, dirName, MAX_COUNT);
+ }
+
+ public DiskLruCacheHelper(Context context, String dirName, int maxCount) throws IOException {
+ mDiskLruCache = generateCache(context, dirName, maxCount);
+ }
+
+ //custom cache dir
+ public DiskLruCacheHelper(File dir) throws IOException {
+ mDiskLruCache = generateCache(null, dir, MAX_COUNT);
+ }
+
+ public DiskLruCacheHelper(Context context, File dir) throws IOException {
+ mDiskLruCache = generateCache(context, dir, MAX_COUNT);
+ }
+
+ public DiskLruCacheHelper(Context context, File dir, int maxCount) throws IOException {
+ mDiskLruCache = generateCache(context, dir, maxCount);
+ }
+
+ private DiskLruCache generateCache(Context context, File dir, int maxCount) throws IOException {
+ if (!dir.exists() || !dir.isDirectory()) {
+ throw new IllegalArgumentException(
+ dir + " is not a directory or does not exists. ");
+ }
+
+ int appVersion = context == null ? DEFAULT_APP_VERSION : Utils.getAppVersion(context);
+
+ DiskLruCache diskLruCache = DiskLruCache.open(
+ dir,
+ appVersion,
+ DEFAULT_APP_VERSION,
+ maxCount);
+
+ return diskLruCache;
+ }
+
+ private DiskLruCache generateCache(Context context, String dirName, int maxCount) throws IOException {
+ DiskLruCache diskLruCache = DiskLruCache.open(
+ getDiskCacheDir(context, dirName),
+ Utils.getAppVersion(context),
+ DEFAULT_APP_VERSION,
+ maxCount);
+ return diskLruCache;
+ }
+ // =======================================
+ // ============== String 数据 读写 =============
+ // =======================================
+
+ public void put(String key, String value) {
+ DiskLruCache.Editor edit = null;
+ BufferedWriter bw = null;
+ try {
+ edit = editor(key);
+ if (edit == null) return;
+ OutputStream os = edit.newOutputStream(0);
+ bw = new BufferedWriter(new OutputStreamWriter(os));
+ bw.write(value);
+ edit.commit();//write CLEAN
+ } catch (IOException e) {
+ e.printStackTrace();
+ try {
+ //s
+ edit.abort();//write REMOVE
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ } finally {
+ try {
+ if (bw != null)
+ bw.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public String getAsString(String key) {
+ InputStream inputStream = null;
+ try {
+ //write READ
+ inputStream = get(key);
+ if (inputStream == null) return null;
+ StringBuilder sb = new StringBuilder();
+ int len = 0;
+ byte[] buf = new byte[128];
+ while ((len = inputStream.read(buf)) != -1) {
+ sb.append(new String(buf, 0, len));
+ }
+ return sb.toString();
+
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ if (inputStream != null)
+ try {
+ inputStream.close();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+
+ public void put(String key, JSONObject jsonObject) {
+ put(key, jsonObject.toString());
+ }
+
+ public JSONObject getAsJson(String key) {
+ String val = getAsString(key);
+ try {
+ if (val != null)
+ return new JSONObject(val);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ // =======================================
+ // ============ JSONArray 数据 读写 =============
+ // =======================================
+
+ public void put(String key, JSONArray jsonArray) {
+ put(key, jsonArray.toString());
+ }
+
+ public JSONArray getAsJSONArray(String key) {
+ String JSONString = getAsString(key);
+ try {
+ JSONArray obj = new JSONArray(JSONString);
+ return obj;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ // =======================================
+ // ============== byte 数据 读写 =============
+ // =======================================
+
+ /**
+ * 保存 byte数据 到 缓存中
+ *
+ * @param key 保存的key
+ * @param value 保存的数据
+ */
+ public void put(String key, byte[] value) {
+ OutputStream out = null;
+ DiskLruCache.Editor editor = null;
+ try {
+ editor = editor(key);
+ if (editor == null) {
+ return;
+ }
+ out = editor.newOutputStream(0);
+ out.write(value);
+ out.flush();
+ editor.commit();//write CLEAN
+ } catch (Exception e) {
+ e.printStackTrace();
+ try {
+ editor.abort();//write REMOVE
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+
+ public byte[] getAsBytes(String key) {
+ byte[] res = null;
+ InputStream is = get(key);
+ if (is == null) return null;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ byte[] buf = new byte[256];
+ int len = 0;
+ while ((len = is.read(buf)) != -1) {
+ baos.write(buf, 0, len);
+ }
+ res = baos.toByteArray();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return res;
+ }
+
+
+ // =======================================
+ // ============== 序列化 数据 读写 =============
+ // =======================================
+ public void put(String key, Serializable value) {
+ DiskLruCache.Editor editor = editor(key);
+ ObjectOutputStream oos = null;
+ if (editor == null) return;
+ try {
+ OutputStream os = editor.newOutputStream(0);
+ oos = new ObjectOutputStream(os);
+ oos.writeObject(value);
+ oos.flush();
+ editor.commit();
+ } catch (IOException e) {
+ e.printStackTrace();
+ try {
+ editor.abort();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ } finally {
+ try {
+ if (oos != null)
+ oos.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public T getAsSerializable(String key) {
+ T t = null;
+ InputStream is = get(key);
+ ObjectInputStream ois = null;
+ if (is == null) return null;
+ try {
+ ois = new ObjectInputStream(is);
+ t = (T) ois.readObject();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if (ois != null)
+ ois.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return t;
+ }
+
+ // =======================================
+ // ============== bitmap 数据 读写 =============
+ // =======================================
+ public void put(String key, Bitmap bitmap) {
+ put(key, Utils.bitmap2Bytes(bitmap));
+ }
+
+ public Bitmap getAsBitmap(String key) {
+ byte[] bytes = getAsBytes(key);
+ if (bytes == null) return null;
+ return Utils.bytes2Bitmap(bytes);
+ }
+
+ // =======================================
+ // ============= drawable 数据 读写 =============
+ // =======================================
+ public void put(String key, Drawable value) {
+ put(key, Utils.drawable2Bitmap(value));
+ }
+
+ public Drawable getAsDrawable(String key) {
+ byte[] bytes = getAsBytes(key);
+ if (bytes == null) {
+ return null;
+ }
+ return Utils.bitmap2Drawable(Utils.bytes2Bitmap(bytes));
+ }
+
+ // =======================================
+ // ============= other methods =============
+ // =======================================
+ public boolean remove(String key) {
+ try {
+ key = Utils.hashKeyForDisk(key);
+ return mDiskLruCache.remove(key);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ public void close() throws IOException {
+ mDiskLruCache.close();
+ }
+
+ public void delete() throws IOException {
+ mDiskLruCache.delete();
+ }
+
+ public void flush() throws IOException {
+ mDiskLruCache.flush();
+ }
+
+ public boolean isClosed() {
+ return mDiskLruCache.isClosed();
+ }
+
+ public long size() {
+ return mDiskLruCache.size();
+ }
+
+ public void setMaxSize(long maxSize) {
+ mDiskLruCache.setMaxSize(maxSize);
+ }
+
+ public File getDirectory() {
+ return mDiskLruCache.getDirectory();
+ }
+
+ public long getMaxSize() {
+ return mDiskLruCache.getMaxSize();
+ }
+
+
+ // =======================================
+ // ===遇到文件比较大的,可以直接通过流读写 =====
+ // =======================================
+ //basic editor
+ public DiskLruCache.Editor editor(String key) {
+ try {
+ key = Utils.hashKeyForDisk(key);
+ //wirte DIRTY
+ DiskLruCache.Editor edit = mDiskLruCache.edit(key);
+ //edit maybe null :the entry is editing
+ if (edit == null) {
+ Log.w(TAG, "the entry spcified key:" + key + " is editing by other . ");
+ }
+ return edit;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+
+ //basic get
+ public InputStream get(String key) {
+ try {
+ DiskLruCache.Snapshot snapshot = mDiskLruCache.get(Utils.hashKeyForDisk(key));
+ if (snapshot == null) //not find entry , or entry.readable = false
+ {
+ Log.e(TAG, "not find entry , or entry.readable = false");
+ return null;
+ }
+ //write READ
+ return snapshot.getInputStream(0);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ }
+
+
+ // =======================================
+ // ============== 序列化 数据 读写 =============
+ // =======================================
+
+ private File getDiskCacheDir(Context context, String uniqueName) {
+ String cachePath;
+ if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
+ || !Environment.isExternalStorageRemovable()) {
+ cachePath = context.getExternalCacheDir().getPath();
+ } else {
+ cachePath = context.getCacheDir().getPath();
+ }
+ return new File(cachePath + File.separator + uniqueName);
+ }
+
+}
diff --git a/app/src/main/java/com/ttstd/template/disklrucache/Utils.java b/app/src/main/java/com/ttstd/template/disklrucache/Utils.java
new file mode 100644
index 0000000..7c0e980
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/disklrucache/Utils.java
@@ -0,0 +1,101 @@
+package com.ttstd.template.disklrucache;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import java.io.ByteArrayOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class Utils {
+ public static int getAppVersion(Context context) {
+ try {
+ PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
+ return info.versionCode;
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return 1;
+ }
+
+
+ public static String hashKeyForDisk(String key) {
+ String cacheKey;
+ try {
+ final MessageDigest mDigest = MessageDigest.getInstance("MD5");
+ mDigest.update(key.getBytes());
+ cacheKey = bytesToHexString(mDigest.digest());
+ } catch (NoSuchAlgorithmException e) {
+ cacheKey = String.valueOf(key.hashCode());
+ }
+ return cacheKey;
+ }
+
+ public static String bytesToHexString(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < bytes.length; i++) {
+ String hex = Integer.toHexString(0xFF & bytes[i]);
+ if (hex.length() == 1) {
+ sb.append('0');
+ }
+ sb.append(hex);
+ }
+ return sb.toString();
+ }
+
+ public static byte[] bitmap2Bytes(Bitmap bm) {
+ if (bm == null) {
+ return null;
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
+ return baos.toByteArray();
+ }
+
+ public static Bitmap bytes2Bitmap(byte[] bytes) {
+ return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+ }
+
+
+ /**
+ * Drawable → Bitmap
+ */
+ public static Bitmap drawable2Bitmap(Drawable drawable) {
+ if (drawable == null) {
+ return null;
+ }
+ // 取 drawable 的长宽
+ int w = drawable.getIntrinsicWidth();
+ int h = drawable.getIntrinsicHeight();
+ // 取 drawable 的颜色格式
+ Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+ // 建立对应 bitmap
+ Bitmap bitmap = Bitmap.createBitmap(w, h, config);
+ // 建立对应 bitmap 的画布
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, w, h);
+ // 把 drawable 内容画到画布中
+ drawable.draw(canvas);
+ return bitmap;
+ }
+
+ /*
+ * Bitmap → Drawable
+ */
+ @SuppressWarnings("deprecation")
+ public static Drawable bitmap2Drawable(Bitmap bm) {
+ if (bm == null) {
+ return null;
+ }
+ BitmapDrawable bd = new BitmapDrawable(bm);
+ bd.setTargetDensity(bm.getDensity());
+ return new BitmapDrawable(bm);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/template/fragment/TemplateFragment.java b/app/src/main/java/com/ttstd/template/fragment/TemplateFragment.java
new file mode 100644
index 0000000..c120f50
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/fragment/TemplateFragment.java
@@ -0,0 +1,96 @@
+package com.ttstd.template.fragment;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.fragment.app.Fragment;
+
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.ttstd.template.R;
+import com.ttstd.template.base.BaseFragment;
+
+import butterknife.ButterKnife;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link TemplateFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class TemplateFragment extends BaseFragment {
+ private static final String TAG = TemplateFragment.class.getSimpleName();
+
+ private View rootView;// 设置为全局的
+ private Activity mContext;
+
+ // TODO: Rename parameter arguments, choose names that match
+ // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
+ private static final String ARG_PARAM1 = "param1";
+ private static final String ARG_PARAM2 = "param2";
+
+ // TODO: Rename and change types of parameters
+ private String mParam1;
+ private String mParam2;
+
+ public TemplateFragment() {
+ // Required empty public constructor
+ }
+
+ /**
+ * Use this factory method to create a new instance of
+ * this fragment using the provided parameters.
+ *
+ * @param param1 Parameter 1.
+ * @param param2 Parameter 2.
+ * @return A new instance of fragment TemplateFragment.
+ */
+ // TODO: Rename and change types and number of parameters
+ public static TemplateFragment newInstance(String param1, String param2) {
+ TemplateFragment fragment = new TemplateFragment();
+ Bundle args = new Bundle();
+ args.putString(ARG_PARAM1, param1);
+ args.putString(ARG_PARAM2, param2);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getArguments() != null) {
+ mParam1 = getArguments().getString(ARG_PARAM1);
+ mParam2 = getArguments().getString(ARG_PARAM2);
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ Log.e(TAG, "onCreateView: ");
+ if (null != rootView) {
+ ViewGroup parent = (ViewGroup) rootView.getParent();
+ if (null != parent) {
+ parent.removeView(rootView);
+ }
+ } else { // 如ongoing果rootView为空 ,就实例化该视图
+ rootView = inflater.inflate(R.layout.fragment_health, container, false);
+ mContext = (Activity) rootView.getContext();
+ ButterKnife.bind(this, rootView);
+ initView();
+ }
+ return rootView;
+ }
+
+ @Override
+ public void fetchData() {
+
+ }
+
+ private void initView() {
+
+ }
+}
diff --git a/app/src/main/java/com/ttstd/template/network/MD5Util.java b/app/src/main/java/com/ttstd/template/network/MD5Util.java
new file mode 100644
index 0000000..09dda45
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/network/MD5Util.java
@@ -0,0 +1,112 @@
+package com.ttstd.template.network;
+
+import android.annotation.SuppressLint;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class MD5Util {
+
+ public static String packetMD5(String str) {
+ MessageDigest messageDigest = null;
+ try {
+ messageDigest = MessageDigest.getInstance("MD5");
+
+ messageDigest.reset();
+
+ messageDigest.update(str.getBytes("UTF-8"));
+ } catch (NoSuchAlgorithmException e) {
+ System.out.println("NoSuchAlgorithmException caught!");
+ System.exit(-1);
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+
+ byte[] byteArray = messageDigest.digest();
+
+ StringBuffer md5StrBuff = new StringBuffer();
+
+ for (int i = 0; i < byteArray.length; i++) {
+ if (Integer.toHexString(0xFF & byteArray[i]).length() == 1)
+ md5StrBuff.append("0").append(
+ Integer.toHexString(0xFF & byteArray[i]));
+ else
+ md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
+ }
+
+ return md5StrBuff.toString();
+ }
+
+ @SuppressLint("DefaultLocale")
+ public static String getUpperMD5Str(String str) {
+ MessageDigest messageDigest = null;
+
+ try {
+ messageDigest = MessageDigest.getInstance("MD5");
+
+ messageDigest.reset();
+
+ messageDigest.update(str.getBytes("UTF-8"));
+ } catch (NoSuchAlgorithmException e) {
+ System.out.println("NoSuchAlgorithmException caught!");
+ System.exit(-1);
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+
+ byte[] byteArray = messageDigest.digest();
+
+ StringBuffer md5StrBuff = new StringBuffer();
+
+ for (int i = 0; i < byteArray.length; i++) {
+ if (Integer.toHexString(0xFF & byteArray[i]).length() == 1)
+ md5StrBuff.append("0").append(
+ Integer.toHexString(0xFF & byteArray[i]));
+ else
+ md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
+ }
+
+ return md5StrBuff.toString().toUpperCase();
+ }
+
+
+ /**
+ * 获取16位的MD5 值得
+ *
+ * @param str
+ * @return
+ */
+ @SuppressLint("DefaultLocale")
+ public static String getUpperMD5Str16(String str) {
+ MessageDigest messageDigest = null;
+
+ try {
+ messageDigest = MessageDigest.getInstance("MD5");
+
+ messageDigest.reset();
+
+ messageDigest.update(str.getBytes("UTF-8"));
+ } catch (NoSuchAlgorithmException e) {
+ System.out.println("NoSuchAlgorithmException caught!");
+ System.exit(-1);
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+
+ byte[] byteArray = messageDigest.digest();
+
+ StringBuffer md5StrBuff = new StringBuffer();
+
+ for (int i = 0; i < byteArray.length; i++) {
+ if (Integer.toHexString(0xFF & byteArray[i]).length() == 1)
+ md5StrBuff.append("0").append(
+ Integer.toHexString(0xFF & byteArray[i]));
+ else
+ md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
+ }
+
+ return md5StrBuff.toString().toUpperCase().substring(8, 24);
+ }
+
+}
diff --git a/app/src/main/java/com/ttstd/template/network/NetInterfaceManager.java b/app/src/main/java/com/ttstd/template/network/NetInterfaceManager.java
new file mode 100644
index 0000000..812fbfc
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/network/NetInterfaceManager.java
@@ -0,0 +1,158 @@
+package com.ttstd.template.network;
+
+import android.annotation.SuppressLint;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Environment;
+
+import com.tencent.mmkv.MMKV;
+import com.ttstd.template.bean.BaseResponse;
+import com.ttstd.template.comm.CommonConfig;
+import com.ttstd.template.network.interceptor.RepeatRequestInterceptor;
+
+import java.io.File;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import io.reactivex.rxjava3.disposables.Disposable;
+import okhttp3.Cache;
+import okhttp3.OkHttpClient;
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class NetInterfaceManager {
+ private static final String TAG = NetInterfaceManager.class.getSimpleName();
+
+ @SuppressLint("StaticFieldLeak")
+ private static NetInterfaceManager INSTANCE;
+ private Context mContext;
+ private ContentResolver crv;
+ private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+ private Retrofit mRetrofit;
+ private OkHttpClient okHttpClient;
+// private CacheHelper mCacheHelper;
+
+ private final ConcurrentHashMap requestIdsMap = new ConcurrentHashMap<>();
+
+ //超时时间
+ public static final int timeOut = 30;
+ // 缓存文件最大限制大小20M
+ private static final long cacheSize = 1024 * 1024 * 64;
+
+ private NetInterfaceManager(Context context) {
+ if (context == null) {
+ throw new RuntimeException("Context is NULL");
+ }
+ this.mContext = context;
+ this.crv = mContext.getContentResolver();
+// this.mCacheHelper = new CacheHelper(mContext);
+
+ if (null == mRetrofit) {
+ if (okHttpClient == null) {
+ //如果无法生存缓存文件目录,检测权限使用已经加上,检测手机是否把文件读写权限禁止了
+ OkHttpClient.Builder builder = new OkHttpClient.Builder();
+ builder.connectTimeout(timeOut, TimeUnit.SECONDS); // 设置连接超时时间
+ builder.writeTimeout(timeOut, TimeUnit.SECONDS);// 设置写入超时时间
+ builder.readTimeout(timeOut, TimeUnit.SECONDS);// 设置读取数据超时时间
+ builder.retryOnConnectionFailure(true);// 设置进行连接失败重试
+ builder.addInterceptor(new RepeatRequestInterceptor());
+
+ // 设置缓存文件路径
+ String cacheDirectory = getCacheDir() + "/OkHttpCache";
+ Cache cache = new Cache(new File(cacheDirectory), cacheSize);
+ builder.cache(cache);// 设置缓存
+ okHttpClient = builder.build();
+ }
+
+ mRetrofit = new Retrofit.Builder()
+ .client(okHttpClient)
+ .baseUrl(UrlAddress.ROOT_URL)
+ .addConverterFactory(GsonConverterFactory.create())
+ .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
+ .build();
+ }
+ }
+
+ private String getCacheDir() {
+ String cachePath;
+ if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
+ || !Environment.isExternalStorageRemovable()) {
+ if (mContext.getExternalCacheDir() != null) {
+ cachePath = mContext.getExternalCacheDir().getPath();
+ } else if (mContext.getExternalFilesDir("cache") != null) {
+ cachePath = mContext.getExternalFilesDir("cache").getPath();
+ } else {
+ cachePath = mContext.getCacheDir().getPath();
+ }
+ } else {
+ cachePath = mContext.getCacheDir().getPath();
+ }
+ return cachePath;
+ }
+
+ public static void init(Context context) {
+ if (context == null) {
+ throw new RuntimeException("context is NULL");
+ }
+ if (INSTANCE == null) {
+ INSTANCE = new NetInterfaceManager(context);
+ }
+ }
+
+ public static NetInterfaceManager getInstance() {
+ if (INSTANCE == null) {
+ throw new IllegalStateException("You must be init NetworkManager first");
+ }
+ return INSTANCE;
+ }
+
+ public OkHttpClient getOkHttpClient() {
+ return okHttpClient;
+ }
+
+ /*
+ *
+ * Observable
+ *
+ * */
+
+
+
+ /*
+ *
+ * API
+ *
+ * */
+
+
+
+ /*
+ *
+ * execution
+ *
+ * */
+
+ public interface ObserverCallback {
+ void onSubscribe(Disposable d);
+
+ void onNext(BaseResponse response);
+
+ void onError(Throwable e);
+
+ void onComplete();
+ }
+
+ public interface onCompleteCallback {
+ void onComplete();
+ }
+
+ public interface onNextCallback {
+ void onNext(Object o);
+
+ void onError(Object o);
+
+ void onComplete();
+ }
+
+}
diff --git a/app/src/main/java/com/ttstd/template/network/RetryCallback.java b/app/src/main/java/com/ttstd/template/network/RetryCallback.java
new file mode 100644
index 0000000..48be199
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/network/RetryCallback.java
@@ -0,0 +1,92 @@
+package com.ttstd.template.network;
+
+import android.util.Log;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public abstract class RetryCallback implements Callback {
+
+ private static final String TAG = RetryCallback.class.getSimpleName();
+
+ private int mRetryCount;
+ private long mRetryInterval;
+
+ private int mCurrentRetryCount;
+
+ private boolean isExecuting;
+
+ private Call mCall;
+
+ private Timer timer = new Timer();
+
+ public RetryCallback(Call call, int retryCount, long retryInterval) {
+ isExecuting = true;
+ mCall = call;
+ mRetryCount = retryCount;
+ mRetryInterval = retryInterval;
+ }
+
+ @Override
+ public final void onResponse(@NonNull Call call, @NonNull Response response) {
+ Log.e(TAG, "onResponse");
+ isExecuting = false;
+ if (!response.isSuccessful() && mCurrentRetryCount < mRetryCount) {
+ mCurrentRetryCount++;
+ retryRequest(call);
+ } else {
+ onRequestResponse(call, response);
+ }
+ }
+
+ @Override
+ public final void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ Log.e(TAG, "onFailure");
+ isExecuting = false;
+ if (mCurrentRetryCount < mRetryCount) {
+ mCurrentRetryCount++;
+ Log.e(TAG, "onFailure: " + "RetryCount: " + mCurrentRetryCount);
+ Log.e(TAG, "onFailure: " + "RetryResponse: " + call.request());
+ retryRequest(call);
+ } else {
+ onRequestFail(call, t);
+ }
+ }
+
+ private void retryRequest(final Call call) {
+ Log.e(TAG, "retryRequest");
+ onStartRetry();
+ TimerTask timerTask = new TimerTask() {
+ @Override
+ public void run() {
+ synchronized (RetryCallback.this) {
+ mCall = call.clone();
+ mCall.enqueue(RetryCallback.this);
+ isExecuting = true;
+ }
+ }
+ };
+ timer.schedule(timerTask, mRetryInterval);
+ }
+
+ public void cancelCall() {
+ synchronized (this) {
+ if (!isExecuting) {
+ timer.cancel();
+ } else {
+ mCall.cancel();
+ }
+ }
+ }
+
+ public abstract void onRequestResponse(Call call, Response response);
+
+ public abstract void onRequestFail(Call call, Throwable t);
+
+ public abstract void onStartRetry();
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/template/network/UrlAddress.java b/app/src/main/java/com/ttstd/template/network/UrlAddress.java
new file mode 100644
index 0000000..1089c26
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/network/UrlAddress.java
@@ -0,0 +1,80 @@
+package com.ttstd.template.network;
+
+public class UrlAddress {
+ /*主页接口*/
+ public static final String ROOT_URL = "https://led.zuoyepad.com/android/";
+
+ /*设备激活*/
+ public static final String SN_ACTIVATION = "sn/snActivation";
+ /*获取设备是否激活*/
+ public static final String GET_SN_IS_ACTIVATION = "sn/getSnIsActivation";
+ /*获取设备激活二维码链接*/
+ public static final String ACTIVATION_QRCODE = "pay/getActivationQrcode";
+
+ /*获取文件*/
+ public static final String GET_FILES = "file/getFiles";
+ /*获取操作指南*/
+ public static final String GET_OPERATION_GUIDE = "file/getFiles";
+
+
+ /*上传屏幕截图*/
+ public final static String UPLOAD_SCREEN_SNAPSHOT = "sn/uploadScreenshot";
+ /*浏览器网址管控*/
+ public final static String SET_BROWSER_URL = "control/getBrowser";
+ /*浏览器书签管控*/
+ public final static String SET_BROWSER_LABEL = "control/getLabel";
+ /*上传控制面版截图*/
+ public static final String UPLOAD_CONTROL_SCREENSHOT = "sn/uploadControlScreenshot";
+
+
+ /*设备信息接口*/
+ public static final String SNINFO = "sn/getSnInfo";
+ /*获取用户头像和信息*/
+ public static final String GET_USER_AVATAR_INFO = "sn/getUserAvatarInfo";
+ /*获取设备类型*/
+ public static final String GET_SN_TYPE = "sn/getSnType";
+ /*获取正在运行的app*/
+ public static final String RUN_NEW_APP = "app/runNewApp";
+ /*获取所有应用*/
+ public final static String GET_ALL_PACKAGE = "app/queryAllApp";
+ /*绑定设备消息*/
+ public final static String BIND_DEVICES = "sn/bindSn";
+ /*获取系统设置*/
+ public final static String GET_SETTINGS = "control/getSetting";
+ /*获取强制下载*/
+ public final static String GET_FORCE_INSTALL = "app/getForceDownload";
+ /*发送卸载或者安装信息*/
+ public final static String SEND_INSTALLEDORREMOVED = "app/addAppInstall";
+ /*发送设备基本信息*/
+ public final static String UPDATE_SNINFO = "sn/updateAdminSn";
+ /*根据包名获取更新*/
+ public final static String GET_NEWESTAPPUPDATE = "app/newestAppUpdate";
+ /*获取禁用包名*/
+ public static final String GET_APP_ICON = "getAppIcon";
+ /*获取灰度更新*/
+ public static final String GET_TEST_APP_INFO = "app/getTestAppInfo";
+ /*获取wifi*/
+ public static final String GET_WIFI_ALIAS_PW = "getWifi";
+
+ /*获取屏幕管控*/
+ public final static String GET_SCREEN_LOCK = "sn/getScreenshot";
+ /*获取锁屏密码*/
+ public static final String LOCK_SCREEN_PWD = "sn/getLockScreenPwd";
+ /*解除锁屏*/
+ public static final String UPDATE_LOCK_SCREEN = "sn/updateLockScreen";
+
+ /*发送绑定验证码*/
+ public static final String SEND_BIND_VER_CODE = "Sn/sendBindVerCode";
+ /*手动绑定设备*/
+ public static final String EQUIPMENT_BIND = "Sn/equipmentBind";
+
+ /*上传应用图标*/
+ public static final String UPLOAD_APP_IMG = "collectData/uploadAppImg";
+ /*获取应用库是否有图标*/
+ public static final String GET_IS_APP_IMG = "collectData/getIsAppImg";
+
+
+ /*通过ip获取信息*/
+ public static final String PCONLINE_WHOIS = "http://whois.pconline.com.cn/";
+ public static final String WHOIS = "ipJson.jsp";
+}
diff --git a/app/src/main/java/com/ttstd/template/network/interceptor/RepeatRequestInterceptor.java b/app/src/main/java/com/ttstd/template/network/interceptor/RepeatRequestInterceptor.java
new file mode 100644
index 0000000..0c9f3ab
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/network/interceptor/RepeatRequestInterceptor.java
@@ -0,0 +1,107 @@
+package com.ttstd.template.network.interceptor;
+
+import android.util.Log;
+
+import com.ttstd.template.BuildConfig;
+import com.ttstd.template.network.MD5Util;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+import okhttp3.Interceptor;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okio.Buffer;
+
+/**
+ * v1.0 2022-07-15 16:16:52
+ */
+public class RepeatRequestInterceptor implements Interceptor {
+ private static final String TAG = RepeatRequestInterceptor.class.getSimpleName();
+
+ private final ConcurrentHashMap requestIdsMap = new ConcurrentHashMap<>();
+ public static final String REPEAT_REQUEST_PROTOCOL = "OKHTTP_REPEAT_REQUEST_PROTOCOL";
+
+ @NotNull
+ @Override
+ public Response intercept(@NotNull Chain chain) throws IOException {
+ Request request = chain.request();
+ Response response = chain.proceed(request);
+ ResponseBody responseBody = response.body();
+
+ //会消费请求,导致请求多次
+ String content = responseBody.string();
+// Response copy = response.newBuilder().body(responseBody).build();
+ ResponseBody copy = ResponseBody.create(responseBody.contentType(), content);
+ if (BuildConfig.DEBUG) {
+ Log.e(TAG, "请求体返回:| Response: " + request.url() + "\t body: " + content);
+ }
+ //相同的请求
+ String requestKey = MD5Util.getUpperMD5Str(request.method() + request.url().toString() + requestBodyToString(request.body()));
+ long time = System.currentTimeMillis();//请求时间
+ try {
+ if (requestIdsMap.size() > 0 && requestIdsMap.containsKey(requestKey)) {
+ log("重复请求:", requestKey, request);
+ //下面这行写了不会抛出onerror
+// chain.call().cancel();
+ return new Response.Builder()
+ .protocol(Protocol.get(REPEAT_REQUEST_PROTOCOL))
+ .request(request) //multi thread
+ .build();
+ }
+ requestIdsMap.put(requestKey, time);
+ log("注册请求:", requestKey, request);
+// RepeatRequestInterceptor.Builder builder = request.newBuilder();
+// builder.addHeader("header", jsonObject.toString());
+ return response.newBuilder().body(copy).build();
+ } catch (IOException e) {
+ Log.e(TAG, "intercept: " + e.getMessage());
+ throw e;
+ } finally {
+ if (requestIdsMap.containsKey(requestKey) && requestIdsMap.containsValue(time)) {//请求任务完成删除map中的数据
+ requestIdsMap.remove(requestKey);
+ log("移除请求:", requestKey, request);
+ }
+ }
+ }
+
+ private void log(String action, String requestKey, Request request) {
+ if (BuildConfig.DEBUG) {
+ Log.e("REPEAT-REQUEST", action + requestKey + " Method @" + request.method() + " --- " + " URL = " + request.url().encodedPath() + "\t" + bodyToString(request));
+ } else {
+ Log.e("REPEAT-REQUEST", action + requestKey + " Method @" + request.method());
+ }
+ }
+
+ private static String bodyToString(final Request request) {
+ try {
+ final Request copy = request.newBuilder().build();
+ final Buffer buffer = new Buffer();
+ copy.body().writeTo(buffer);
+ if (buffer.size() > 4096) {
+ return "-too long";
+ }
+ return buffer.readUtf8();
+ } catch (Exception e) {
+ return "-";
+ }
+ }
+
+ private static String requestBodyToString(RequestBody body) {
+ try {
+ final Buffer buffer = new Buffer();
+ body.writeTo(buffer);
+ if (buffer.size() > 4096) {
+ return "-too long";
+ }
+ return buffer.readUtf8();
+ } catch (Exception e) {
+ return "-";
+ }
+ }
+}
diff --git a/app/src/main/java/com/ttstd/template/utils/SystemUtils.java b/app/src/main/java/com/ttstd/template/utils/SystemUtils.java
new file mode 100644
index 0000000..752813e
--- /dev/null
+++ b/app/src/main/java/com/ttstd/template/utils/SystemUtils.java
@@ -0,0 +1,24 @@
+package com.ttstd.template.utils;
+
+import android.app.ActivityManager;
+import android.content.Context;
+
+import java.util.List;
+
+public class SystemUtils {
+
+ public static boolean isMainProcessName(Context cxt, int pid) {
+ String packageName = cxt.getPackageName();
+ ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
+ List runningApps = am.getRunningAppProcesses();
+ if (runningApps == null) {
+ return false;
+ }
+ for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
+ if (procInfo.pid == pid) {
+ return procInfo.processName.equals(packageName);
+ }
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..b0d0301
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_health.xml b/app/src/main/res/layout/fragment_health.xml
new file mode 100644
index 0000000..c18b330
--- /dev/null
+++ b/app/src/main/res/layout/fragment_health.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a571e60
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..61da551
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c41dd28
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..db5080a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..6dba46d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..da31a87
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..15ac681
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b216f2d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f25a419
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e96783c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..fa03541
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,248 @@
+
+
+ #6200EE
+ #3700B3
+ #03DAC5
+
+
+ #00000000
+ #FFE2C59B
+ #FFFFFFF0
+ #FFFFFFE0
+ #FFFFFF00
+ #FFFFFAFA
+ #FFFFFAF0
+ #FFFFFACD
+ #FFFFF8DC
+ #FFFFF5EE
+ #FFFFF0F5
+ #FFFFEFD5
+ #FFFFEBCD
+ #FFFFE4E1
+ #FFFFE4C4
+ #FFFFE4B5
+ #FFFFDEAD
+ #FFFFDAB9
+ #FFFFD700
+ #FFFFC0CB
+ #FFFFB6C1
+ #FFFFA500
+ #FFFFA07A
+ #FFFF8C00
+ #FFFF7F50
+ #FFFF69B4
+ #FFFF6347
+ #FFFF4500
+ #FFFF1493
+ #FFFF00FF
+ #FFFF0000
+ #FFFDF5E6
+ #FFFAFAD2
+ #FFFAF0E6
+ #FFFAEBD7
+ #FFFA8072
+ #FFF8F8FF
+ #FFF5FFFA
+ #FFF5F5F5
+ #FFF5F5DC
+ #FFF5DEB3
+ #FFF4A460
+ #FFF0FFFF
+ #FFF0F8FF
+ #FFF0E68C
+ #FFF08080
+ #FFEEE8AA
+ #FFEE82EE
+ #FFE9967A
+ #FFE6E6FA
+ #FFE0FFFF
+ #FFDEB887
+ #FFDDA0DD
+ #FFDCDCDC
+ #FFDC143C
+ #FFDB7093
+ #FFDAA520
+ #FFDA70D6
+ #FFD8BFD8
+ #FFD3D3D3
+ #FFD2B48C
+ #FFD2691E
+ #FFCD853F
+ #FFCD5C5C
+ #FFC71585
+ #FFC0C0C0
+ #FFBDB76B
+ #FFBC8F8F
+ #FFBA55D3
+ #FFB8860B
+ #FFB22222
+ #FFB0E0E6
+ #FFB0C4DE
+ #FFAFEEEE
+ #FFADFF2F
+ #FFADD8E6
+ #FFA9A9A9
+ #FFA52A2A
+ #FFA0522D
+ #FF9932CC
+ #FF98FB98
+ #FF9400D3
+ #FF9370DB
+ #FF90EE90
+ #FF8FBC8F
+ #FF8B4513
+ #FF8B008B
+ #FF8B0000
+ #FF8A2BE2
+ #FF87CEFA
+ #FF87CEEB
+ #FF808080
+ #FF808000
+ #FF800080
+ #FF800000
+ #FF7FFFD4
+ #FF7FFF00
+ #FF7CFC00
+ #FF7B68EE
+ #FF778899
+ #FF708090
+ #FF6B8E23
+ #FF6A5ACD
+ #FF696969
+ #FF66CDAA
+ #FF6495ED
+ #FF5F9EA0
+ #FF556B2F
+ #FF4B0082
+ #FF48D1CC
+ #FF483D8B
+ #FF4682B4
+ #FF4169E1
+ #FF40E0D0
+ #FF3CB371
+ #FF32CD32
+ #FF2F4F4F
+ #FF2E8B57
+ #FF228B22
+ #FF20B2AA
+ #FF1E90FF
+ #FF191970
+ #FF00FFFF
+ #FF00FF7F
+ #FF00FF00
+ #FF00FA9A
+ #FF00CED1
+ #FF00BFFF
+ #FF008B8B
+ #FF008080
+ #FF008000
+ #FF006400
+ #FF0000FF
+ #FF0000CD
+ #FF00008B
+ #FF000080
+ #FF2B2B2B
+
+
+ #ffffff
+ #ffffe0
+ #fffaf0
+ #fffacd
+ #fff8dc
+ #fff5ee
+ #fff0f5
+ #ffefd5
+ #ffebcd
+ #ffe4e1
+ #ffdead
+ #ffdab9
+ #ffb6c1
+ #ffa07a
+ #ff8c00
+ #ff69b4
+ #ff4500
+ #ff1493
+ #ff00ff
+ #fdf5e6
+ #fafad2
+ #faebd7
+ #f8f8ff
+ #f5fffa
+ #f5f5f5
+ #f4a460
+ #f0fff0
+ #f0f8ff
+ #f08080
+ #eee8aa
+ #e9967a
+ #e0ffff
+ #deb887
+ #dcdcdc
+ #db7093
+ #d3d3d3
+ #d3d3d3
+ #cd5c5c
+ #c71585
+ #bdb76b
+ #bc8f8f
+ #ba55d3
+ #b8860b
+ #b0e0e6
+ #b0c4de
+ #afeeee
+ #adff2f
+ #add8e6
+ #a9a9a9
+ #a9a9a9
+ #9932cc
+ #98fb98
+ #9400d3
+ #9370db
+ #90ee90
+ #8fbc8f
+ #8b4513
+ #8b008b
+ #8b0000
+ #8a2be2
+ #87cefa
+ #87ceeb
+ #808080
+ #7cfc00
+ #7b68ee
+ #778899
+ #778899
+ #708090
+ #708090
+ #6b8e23
+ #6a5acd
+ #696969
+ #696969
+ #66cdaa
+ #6495ed
+ #5f9ea0
+ #556b2f
+ #48d1cc
+ #483d8b
+ #4682b4
+ #4169e1
+ #3cb371
+ #32cd32
+ #2f4f4f
+ #2f4f4f
+ #2e8b57
+ #228b22
+ #20b2aa
+ #1e90ff
+ #191970
+ #00ffff
+ #00ff7f
+ #00fa9a
+ #00ced1
+ #00bfff
+ #008b8b
+ #006400
+ #0000cd
+ #00008b
+ #000000
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ddcbbdc
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+ AndroidAppTemplate
+
+
+ Hello blank fragment
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7ce5214
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..c6e50a7
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/app/src/main/res/xml/network.xml b/app/src/main/res/xml/network.xml
new file mode 100644
index 0000000..dca93c0
--- /dev/null
+++ b/app/src/main/res/xml/network.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/ttstd/template/ExampleUnitTest.java b/app/src/test/java/com/ttstd/template/ExampleUnitTest.java
new file mode 100644
index 0000000..c10a316
--- /dev/null
+++ b/app/src/test/java/com/ttstd/template/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.ttstd.template;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..cff5877
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,35 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+
+ repositories {
+ google()
+ mavenCentral()
+ maven { url "https://jitpack.io" }
+ maven {url 'http://developer.huawei.com/repo/'}
+ maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' }
+ maven { url 'http://maven.aliyun.com/nexus/content/repositories/releases/' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.6.4'
+
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ maven { url "https://jitpack.io" }
+ maven {url 'http://developer.huawei.com/repo/'}
+ maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' }
+ maven { url 'http://maven.aliyun.com/nexus/content/repositories/releases/' }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..199d16e
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e32620a
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Dec 21 10:49:41 CST 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..b34cf45
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name='AndroidAppTemplate'
+include ':app'