FailedToRead пре 2 година
комит
f334444734
100 измењених фајлова са 5449 додато и 0 уклоњено
  1. 15 0
      .gitignore
  2. 3 0
      .idea/.gitignore
  3. 1 0
      .idea/.name
  4. 6 0
      .idea/compiler.xml
  5. 22 0
      .idea/gradle.xml
  6. 10 0
      .idea/misc.xml
  7. 6 0
      .idea/vcs.xml
  8. 1 0
      app-base/.gitignore
  9. 50 0
      app-base/build.gradle
  10. 21 0
      app-base/proguard-rules.pro
  11. 26 0
      app-base/src/androidTest/java/com/efunbox/base/ExampleInstrumentedTest.java
  12. 19 0
      app-base/src/main/AndroidManifest.xml
  13. 10 0
      app-base/src/main/AndroidManifestDebug.xml
  14. 9 0
      app-base/src/main/java/com/efunbox/base/BaseMvp.java
  15. 216 0
      app-base/src/main/java/com/efunbox/base/BaseMvpActivity.java
  16. 58 0
      app-base/src/main/java/com/efunbox/base/BaseMvpFragment.java
  17. 50 0
      app-base/src/main/java/com/efunbox/base/BasePresenter.java
  18. 4 0
      app-base/src/main/java/com/efunbox/base/Model.java
  19. 57 0
      app-base/src/main/java/com/efunbox/base/ModuleMediator.java
  20. 123 0
      app-base/src/main/java/com/efunbox/base/MyApplication.java
  21. 29 0
      app-base/src/main/java/com/efunbox/base/Presenter.java
  22. 15 0
      app-base/src/main/java/com/efunbox/base/View.java
  23. 94 0
      app-base/src/main/java/com/efunbox/base/activity/CrashDialogActivity.java
  24. 13 0
      app-base/src/main/java/com/efunbox/base/annotation/BindView.java
  25. 40 0
      app-base/src/main/java/com/efunbox/base/bean/HttpResultBean.java
  26. 50 0
      app-base/src/main/java/com/efunbox/base/bean/HttpResultDataListBean.java
  27. 23 0
      app-base/src/main/java/com/efunbox/base/bean/WeChatBean.java
  28. 30 0
      app-base/src/main/java/com/efunbox/base/callback/OkHttpCallback.java
  29. 81 0
      app-base/src/main/java/com/efunbox/base/crash/ActivityKillerV15_V20.java
  30. 77 0
      app-base/src/main/java/com/efunbox/base/crash/ActivityKillerV21_V23.java
  31. 82 0
      app-base/src/main/java/com/efunbox/base/crash/ActivityKillerV24_V25.java
  32. 81 0
      app-base/src/main/java/com/efunbox/base/crash/ActivityKillerV26.java
  33. 89 0
      app-base/src/main/java/com/efunbox/base/crash/ActivityKillerV28.java
  34. 46 0
      app-base/src/main/java/com/efunbox/base/crash/ActivityLifecycleCallbacksAdapter.java
  35. 10 0
      app-base/src/main/java/com/efunbox/base/crash/ClientTransaction.java
  36. 240 0
      app-base/src/main/java/com/efunbox/base/crash/Cockroach.java
  37. 98 0
      app-base/src/main/java/com/efunbox/base/crash/CrashLog.java
  38. 44 0
      app-base/src/main/java/com/efunbox/base/crash/DebugSafeModeTipActivity.java
  39. 75 0
      app-base/src/main/java/com/efunbox/base/crash/DebugSafeModeUI.java
  40. 59 0
      app-base/src/main/java/com/efunbox/base/crash/ExceptionHandler.java
  41. 17 0
      app-base/src/main/java/com/efunbox/base/crash/IActivityKiller.java
  42. 59 0
      app-base/src/main/java/com/efunbox/base/crash/LinearTopSmoothScroller.java
  43. 125 0
      app-base/src/main/java/com/efunbox/base/cusview/CusToast.java
  44. 16 0
      app-base/src/main/java/com/efunbox/base/debug/MyApplication.java
  45. 245 0
      app-base/src/main/java/com/efunbox/base/fragment/CrashLogFragment.java
  46. 6 0
      app-base/src/main/java/com/efunbox/base/model/CrashActivityModel.java
  47. 6 0
      app-base/src/main/java/com/efunbox/base/model/impl/CrashActivityModelImpl.java
  48. 12 0
      app-base/src/main/java/com/efunbox/base/presenter/CrashActivityPresenter.java
  49. 41 0
      app-base/src/main/java/com/efunbox/base/util/BaseConsts.java
  50. 140 0
      app-base/src/main/java/com/efunbox/base/util/DataCleanManager.java
  51. 265 0
      app-base/src/main/java/com/efunbox/base/util/DeviceUtil.java
  52. 57 0
      app-base/src/main/java/com/efunbox/base/util/DeviceUuidFactory.java
  53. 206 0
      app-base/src/main/java/com/efunbox/base/util/EfunboxUtil.java
  54. 368 0
      app-base/src/main/java/com/efunbox/base/util/GlideUtils.java
  55. 65 0
      app-base/src/main/java/com/efunbox/base/util/HttpInterceptor.java
  56. 22 0
      app-base/src/main/java/com/efunbox/base/util/Logger.java
  57. 191 0
      app-base/src/main/java/com/efunbox/base/util/MMKVUtil.java
  58. 108 0
      app-base/src/main/java/com/efunbox/base/util/OkHttpClient.java
  59. 148 0
      app-base/src/main/java/com/efunbox/base/util/QRCodeUtil.java
  60. 155 0
      app-base/src/main/java/com/efunbox/base/util/SizeUtils.java
  61. 190 0
      app-base/src/main/java/com/efunbox/base/util/TimeUtil.java
  62. 6 0
      app-base/src/main/java/com/efunbox/base/view/CrashActivityView.java
  63. 30 0
      app-base/src/main/res/drawable-v24/ic_launcher_foreground.xml
  64. 8 0
      app-base/src/main/res/drawable/activity_crash_divider_btn.xml
  65. BIN
      app-base/src/main/res/drawable/activity_crash_icon.png
  66. 170 0
      app-base/src/main/res/drawable/ic_launcher_background.xml
  67. 6 0
      app-base/src/main/res/drawable/list_divider_horizontal.xml
  68. BIN
      app-base/src/main/res/drawable/placeholder.jpg
  69. 19 0
      app-base/src/main/res/drawable/theme_stroke_selector.xml
  70. BIN
      app-base/src/main/res/drawable/toast_bg.9.png
  71. 79 0
      app-base/src/main/res/layout/activity_crash_dialog.xml
  72. 66 0
      app-base/src/main/res/layout/activity_safe_mode_warning.xml
  73. 21 0
      app-base/src/main/res/layout/fragment_crash_log.xml
  74. 60 0
      app-base/src/main/res/layout/item_crash_log.xml
  75. 19 0
      app-base/src/main/res/layout/theme_stroke_selector.xml
  76. 37 0
      app-base/src/main/res/layout/toast.xml
  77. 5 0
      app-base/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  78. 5 0
      app-base/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  79. BIN
      app-base/src/main/res/mipmap-hdpi/ic_launcher.webp
  80. BIN
      app-base/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  81. BIN
      app-base/src/main/res/mipmap-mdpi/ic_launcher.webp
  82. BIN
      app-base/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  83. BIN
      app-base/src/main/res/mipmap-xhdpi/ic_launcher.webp
  84. BIN
      app-base/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  85. BIN
      app-base/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  86. BIN
      app-base/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  87. BIN
      app-base/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  88. BIN
      app-base/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  89. 16 0
      app-base/src/main/res/values-night/themes.xml
  90. 290 0
      app-base/src/main/res/values/colors.xml
  91. 7 0
      app-base/src/main/res/values/strings.xml
  92. 24 0
      app-base/src/main/res/values/themes.xml
  93. 17 0
      app-base/src/test/java/com/efunbox/base/ExampleUnitTest.java
  94. 1 0
      app-pay/.gitignore
  95. 20 0
      app-pay/build.gradle
  96. 21 0
      app-pay/proguard-rules.pro
  97. 26 0
      app-pay/src/main/AndroidManifest.xml
  98. 49 0
      app-pay/src/main/AndroidManifestDebug.xml
  99. 22 0
      app-pay/src/main/java/com/efunbox/pay/MyApplication.java
  100. 0 0
      app-pay/src/main/java/com/efunbox/pay/PayActivity.java

+ 15 - 0
.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
+local.properties

+ 3 - 0
.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 1 - 0
.idea/.name

@@ -0,0 +1 @@
+efunboxModel

+ 6 - 0
.idea/compiler.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="11" />
+  </component>
+</project>

+ 22 - 0
.idea/gradle.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+            <option value="$PROJECT_DIR$/app-base" />
+            <option value="$PROJECT_DIR$/app-pay" />
+            <option value="$PROJECT_DIR$/app-video" />
+          </set>
+        </option>
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 10 - 0
.idea/misc.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 1 - 0
app-base/.gitignore

@@ -0,0 +1 @@
+/build

+ 50 - 0
app-base/build.gradle

@@ -0,0 +1,50 @@
+plugins.apply('com.android.library')
+
+android {
+    compileSdk 32
+
+    defaultConfig {
+        minSdk 21
+        targetSdk 32
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+
+dependencies {
+    api userLibs.get('appcompat')
+    api userLibs.get('material')
+    api userLibs.get('constraintlayout')
+    api userLibs.get('mmkv')
+    api userLibs.get('okhttp3') {
+        exclude(module: 'support-annotations')
+        exclude(module: 'gson')
+    }
+    api userLibs.get('glide')
+    api userLibs.get('glide_compiler')
+    api userLibs.get('glide_transformations')
+    api userLibs.get('freereflection')
+    api userLibs.get('orhanbutlogger')
+    //全局共用一个点击时间
+    implementation 'cc.taylorzhang:single-click:1.1.0'
+    //zxing
+    implementation 'com.google.zxing:android-core:3.3.0'
+    implementation 'com.google.zxing:core:3.3.2'
+
+    testImplementation testLibs.get('junit')
+    androidTestImplementation testLibs.get('junitAndroid')
+    androidTestImplementation testLibs.get('espresso')
+
+}

+ 21 - 0
app-base/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

+ 26 - 0
app-base/src/androidTest/java/com/efunbox/base/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.efunbox.base;
+
+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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("com.candy.base", appContext.getPackageName());
+    }
+}

+ 19 - 0
app-base/src/main/AndroidManifest.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.efunbox.base">
+
+    <application android:allowBackup="true">
+
+        <activity
+            android:name=".activity.CrashDialogActivity"
+            android:exported="true"
+            android:theme="@style/FullScreen"></activity>
+
+        <activity
+            android:name=".crash.DebugSafeModeTipActivity"
+            android:exported="true"
+            android:theme="@style/FullScreen"></activity>
+
+    </application>
+
+</manifest>

+ 10 - 0
app-base/src/main/AndroidManifestDebug.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.efunbox.base">
+
+    <application
+        android:name="com.efunbox.base.debug.MyApplication"
+        android:allowBackup="true"
+       >
+    </application>
+</manifest>

+ 9 - 0
app-base/src/main/java/com/efunbox/base/BaseMvp.java

@@ -0,0 +1,9 @@
+package com.efunbox.base;
+
+public interface BaseMvp<M extends Model, V extends View, P extends BasePresenter> {
+    M createModel();
+
+    V createView();
+
+    P createPresenter();
+}

+ 216 - 0
app-base/src/main/java/com/efunbox/base/BaseMvpActivity.java

@@ -0,0 +1,216 @@
+package com.efunbox.base;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.efunbox.base.annotation.BindView;
+import com.efunbox.base.util.BaseConsts;
+import com.efunbox.base.util.EfunboxUtil;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import cc.taylorzhang.singleclick.SingleClickUtil;
+
+public abstract class BaseMvpActivity<M extends Model, V extends View, P extends BasePresenter> extends AppCompatActivity implements BaseMvp<M, V, P> {
+
+
+    protected P mPresenter;
+    protected BaseMvpActivity mActivity;
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        EfunboxUtil.initDesignSize(this);
+        if (this.getLayoutId() != 0) {
+            setContentView(this.getLayoutId());
+        }
+        if (this.getLayoutView() != null) {
+            setContentView(this.getLayoutView());
+        }
+
+        mActivity = this;
+        MyApplication.mActivityList.add(mActivity);
+        bindViews(mActivity);
+        //创建Presenter
+        mPresenter = createPresenter();
+        if (mPresenter != null) {
+            //将Model层注册到Presenter中
+            mPresenter.registerModel(createModel());
+            //将View层注册到Presenter中
+            mPresenter.registerView(createView());
+        }
+        initView();
+        initViewListener();
+
+    }
+
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        MyApplication.mActivityList.remove(mActivity);
+    }
+
+    /**
+     * 设置布局
+     *
+     * @return
+     */
+    public abstract int getLayoutId();
+
+
+    /**
+     * 设置布局view
+     */
+    public abstract android.view.View getLayoutView();
+
+
+    /**
+     * 设置view
+     */
+    public abstract void initView();
+
+
+    /**
+     * 设置view的listener
+     */
+    public abstract void initViewListener();
+
+
+    protected void startActivity(String className, Bundle bundle) {
+        try {
+            Class clazz = Class.forName(className);
+            Intent intent = new Intent(createPackageContext(getPackageName(), Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY), clazz);
+            if (bundle != null) {
+                intent.putExtra("params_bundle", bundle);
+            }
+            startActivity(intent);
+        } catch (ClassNotFoundException exception) {
+            exception.printStackTrace();
+            Toast.makeText(this, "找不到:" + exception.getMessage(), Toast.LENGTH_SHORT).show();
+        } catch (PackageManager.NameNotFoundException exception) {
+            exception.printStackTrace();
+            Toast.makeText(this, "NameNotFoundException:" + exception.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+    }
+
+
+    public ViewGroup getRootView() {
+
+        return findViewById(android.R.id.content);
+    }
+
+
+    protected void addUiClickListener(android.view.View view, android.view.View.OnClickListener listener) {
+        //isShareSingleClick为true,表示共用时间.为false表示单独计时
+        SingleClickUtil.onSingleClick(view, BaseConsts.VIEW_CLICK_TIME, BaseConsts.VIEW_CLICK_ALL, listener);
+    }
+
+
+    protected void startActivity(String className) {
+        try {
+            Class clazz = Class.forName(className);
+            Intent intent = new Intent(createPackageContext(getPackageName(), Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY), clazz);
+            startActivity(intent);
+        } catch (ClassNotFoundException exception) {
+            exception.printStackTrace();
+            Toast.makeText(this, "找不到:" + exception.getMessage(), Toast.LENGTH_SHORT).show();
+        } catch (PackageManager.NameNotFoundException exception) {
+            exception.printStackTrace();
+            Toast.makeText(this, "NameNotFoundException:" + exception.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+    }
+
+
+    protected void startActivity(String className, int requestCode, Bundle bundle) {
+        try {
+            Class clazz = Class.forName(className);
+            Intent intent = new Intent(createPackageContext(getPackageName(), Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY), clazz);
+            if (bundle != null) {
+                intent.putExtra("params_bundle", bundle);
+            }
+            startActivityForResult(intent, requestCode);
+        } catch (ClassNotFoundException exception) {
+            exception.printStackTrace();
+            Toast.makeText(this, "找不到:" + exception.getMessage(), Toast.LENGTH_SHORT).show();
+        } catch (PackageManager.NameNotFoundException exception) {
+            exception.printStackTrace();
+            Toast.makeText(this, "NameNotFoundException:" + exception.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    protected void startActivity(String className, int requestCode) {
+        try {
+            Class clazz = Class.forName(className);
+            Intent intent = new Intent(createPackageContext(getPackageName(), Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY), clazz);
+            startActivityForResult(intent, requestCode);
+        } catch (ClassNotFoundException exception) {
+            exception.printStackTrace();
+            Toast.makeText(this, "找不到:" + exception.getMessage(), Toast.LENGTH_SHORT).show();
+        } catch (PackageManager.NameNotFoundException exception) {
+            exception.printStackTrace();
+            Toast.makeText(this, "NameNotFoundException:" + exception.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+    }
+
+
+    public static void bindViews(Activity activity) {
+        Class<? extends Activity> activityClass = activity.getClass();//获取activity的class
+        Field[] fields = activityClass.getDeclaredFields();//获取activity的字段
+        //遍历所有的字段
+        for (Field field : fields) {
+            //获取该字段的注解
+            BindView bindView = field.getAnnotation(BindView.class);
+            //!=null 说明该字段有注解并且是指定的注解
+            if (bindView != null) {
+                //获取到注解总传入的数值value
+                int viewId = bindView.id();
+                if (viewId != -1) {
+                    try {
+                        //获取到activity中findViewById的方法
+                        Method findViewByIdMethod = activityClass.getMethod("findViewById", int.class);
+                        try {
+                            //执行findViewById方法
+                            Object resView = findViewByIdMethod.invoke(activity, viewId);
+                            //允许通过反射访问私有变量
+                            field.setAccessible(true);
+                            //把字段的值设置该view的实例
+                            field.set(activity, resView);
+                        } catch (IllegalAccessException e) {
+                            e.printStackTrace();
+                        } catch (InvocationTargetException e) {
+                            e.printStackTrace();
+                        }
+                    } catch (NoSuchMethodException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+
+    public Bundle getBundle() {
+        Bundle params = getIntent().getBundleExtra("params_bundle");
+        if (params != null) {
+            return params;
+        }
+        return null;
+
+    }
+
+
+}

+ 58 - 0
app-base/src/main/java/com/efunbox/base/BaseMvpFragment.java

@@ -0,0 +1,58 @@
+package com.efunbox.base;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.efunbox.base.util.EfunboxUtil;
+
+
+public abstract class BaseMvpFragment<M extends Model, V extends View, P extends BasePresenter> extends Fragment implements BaseMvp<M, V, P> {
+    protected P mPresenter;
+    protected android.view.View mRootView;
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        mPresenter = createPresenter();
+        if (mPresenter != null) {
+            mPresenter.registerModel(createModel());
+            mPresenter.registerView(createView());
+        }
+    }
+
+
+    @Override
+    public android.view.View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
+        EfunboxUtil.initDesignSize(getActivity());
+        android.view.View view = inflater.inflate(this.getLayoutId(), container, false);
+        mRootView = view;
+        initView(view);
+        return view;
+    }
+
+
+    protected abstract int getLayoutId();
+
+    /**
+     * 初始化视图
+     *
+     * @param view
+     */
+    protected abstract void initView(android.view.View view);
+
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        if (mPresenter != null) {
+            mPresenter.destroy();
+        }
+    }
+
+}

+ 50 - 0
app-base/src/main/java/com/efunbox/base/BasePresenter.java

@@ -0,0 +1,50 @@
+package com.efunbox.base;
+
+import com.efunbox.base.util.BaseConsts;
+
+import java.lang.ref.WeakReference;
+
+import cc.taylorzhang.singleclick.SingleClickUtil;
+
+public abstract class BasePresenter<M extends Model, V extends View> implements Presenter<M, V> {
+    /**
+     * 使用弱引用来防止内存泄漏
+     */
+    private WeakReference<V> wrf;
+    protected M mModel;
+
+    @Override
+    public void registerModel(M model) {
+        this.mModel = model;
+    }
+
+    @Override
+    public void registerView(V view) {
+        wrf = new WeakReference<V>(view);
+    }
+
+    @Override
+    public V getView() {
+        return wrf == null ? null : wrf.get();
+    }
+
+    /**
+     * 在Activity或Fragment卸载时调用View结束的方法
+     */
+    @Override
+    public void destroy() {
+        if (wrf != null) {
+            wrf.clear();
+            wrf = null;
+        }
+        onViewDestroy();
+    }
+
+    protected abstract void onViewDestroy();
+
+
+    protected void addUiClickListener(android.view.View view, android.view.View.OnClickListener listener) {
+        //isShareSingleClick为true,表示共用时间.为false表示单独计时
+        SingleClickUtil.onSingleClick(view, BaseConsts.VIEW_CLICK_TIME, BaseConsts.VIEW_CLICK_ALL, listener);
+    }
+}

+ 4 - 0
app-base/src/main/java/com/efunbox/base/Model.java

@@ -0,0 +1,4 @@
+package com.efunbox.base;
+
+public interface Model {
+}

+ 57 - 0
app-base/src/main/java/com/efunbox/base/ModuleMediator.java

@@ -0,0 +1,57 @@
+package com.efunbox.base;
+
+import android.app.Application;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * 跳转管理
+ */
+public class ModuleMediator {
+
+
+    /**
+     * 播放页面类
+     **/
+    public static final String ACTIVITY_VIDEO_CLASS = "com.efunbox.video.VideoActivity";
+    /**
+     * TV播放页面类
+     **/
+    public static final String ACTIVITY_VIDEO_TV_CLASS = "com.efunbox.video.VideoTVActivity";
+    /**
+     * 支付页面类
+     **/
+    public static final String ACTIVITY_PAY_CLASS = "com.efunbox.pay.PayActivity";
+
+
+    private static final String APP_BASE_CLASS = "com.efunbox.base.MyApplication";
+    private static final String APP_VIDEO_CLASS = "com.efunbox.video.MyApplication";
+    private static final String APP_PAY_CLASS = "com.efunbox.pay.MyApplication";
+    private static String[] modelApplications = {APP_BASE_CLASS, APP_VIDEO_CLASS, APP_PAY_CLASS};
+
+    public static void initModule(Application application) {
+        for (int i = 0; i < modelApplications.length; i++) {
+            try {
+                Class cls = Class.forName(modelApplications[i]);
+                Method method = cls.getMethod("initModule", Application.class);
+                method.invoke(cls.newInstance(), application);
+            } catch (ClassNotFoundException | NoSuchMethodException exception) {
+                exception.printStackTrace();
+            } catch (InvocationTargetException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            } catch (InstantiationException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+
+    public interface ModuleInitial {
+        void initModule(Application application);
+    }
+
+}

+ 123 - 0
app-base/src/main/java/com/efunbox/base/MyApplication.java

@@ -0,0 +1,123 @@
+package com.efunbox.base;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.RequiresApi;
+
+import com.efunbox.base.activity.CrashDialogActivity;
+import com.efunbox.base.crash.Cockroach;
+import com.efunbox.base.crash.CrashLog;
+import com.efunbox.base.crash.DebugSafeModeTipActivity;
+import com.efunbox.base.crash.DebugSafeModeUI;
+import com.efunbox.base.crash.ExceptionHandler;
+import com.efunbox.base.util.BaseConsts;
+import com.efunbox.base.util.DeviceUuidFactory;
+import com.efunbox.base.util.EfunboxUtil;
+import com.efunbox.base.util.GlideUtils;
+import com.efunbox.base.util.OkHttpClient;
+import com.orhanobut.logger.Logger;
+import com.tencent.mmkv.MMKV;
+
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MyApplication implements ModuleMediator.ModuleInitial {
+
+    public static List<Activity> mActivityList = new ArrayList<>();
+
+
+    @Override
+    public void initModule(Application application) {
+        try {
+            Logger.e("com.efunbox.base.MyApplication");
+            BaseConsts.setAppPackageName(application.getPackageName());
+            //初始化异常
+            if (!BaseConsts.isDebug()) {
+                Logger.e("Consts.isDebug:" + BaseConsts.isDebug());
+                //不在测试环境下,初始化异常
+                initException(application);
+            }
+            //初始化mmkv
+            MMKV.initialize(application);
+            //初始化OKhttp3
+            OkHttpClient.initOkHttpUtil(application);
+            //修改xml问题
+            XmlPullParserFactory.newInstance().setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+
+            //清除图片缓存
+            new Thread() {
+                @Override
+                public void run() {
+                    super.run();
+                    GlideUtils.removeAll(application);
+                }
+            }.start();
+        } catch (XmlPullParserException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+
+    private void initException(Application application) {
+
+
+        final Thread.UncaughtExceptionHandler sysExcepHandler = Thread.getDefaultUncaughtExceptionHandler();
+        DebugSafeModeUI.init(application);
+        Cockroach.install(application, new ExceptionHandler() {
+            @Override
+            protected void onUncaughtExceptionHappened(Thread thread, Throwable throwable) {
+                CrashLog.saveCrashLog(application, throwable);
+                new Handler(Looper.getMainLooper()).post(new Runnable() {
+                    @Override
+                    public void run() {
+                        Logger.e("Cockroach.install:onUncaughtExceptionHappened");
+                    }
+                });
+            }
+
+            @Override
+            protected void onBandageExceptionHappened(Throwable throwable) {
+                throwable.printStackTrace();//打印警告级别log,该throwable可能是最开始的bug导致的,无需关心
+            }
+
+            @Override
+            protected void onEnterSafeMode() {
+//                int tips = R.string.safe_mode_tips;
+//                Toast.makeText(Consts.getmApplicAtion(), getResources().getString(tips), Toast.LENGTH_LONG).show();
+//                DebugSafeModeUI.showSafeModeUI();
+
+                if (BaseConsts.isDebug()) {
+                    Intent intent = new Intent(application, DebugSafeModeTipActivity.class);
+                    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+                    application.startActivity(intent);
+                } else {
+                    Intent intent = new Intent(application, CrashDialogActivity.class);
+                    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+                    application.startActivity(intent);
+                }
+            }
+
+            @RequiresApi(api = Build.VERSION_CODES.N)
+            @Override
+            protected void onMayBeBlackScreen(Throwable e) {
+                Thread thread = Looper.getMainLooper().getThread();
+                e.printStackTrace();
+                //黑屏时建议直接杀死app
+                sysExcepHandler.uncaughtException(thread, new RuntimeException("black screen"));
+                EfunboxUtil.killAppProcess(application, mActivityList);
+            }
+
+        });
+
+    }
+
+}

+ 29 - 0
app-base/src/main/java/com/efunbox/base/Presenter.java

@@ -0,0 +1,29 @@
+package com.efunbox.base;
+
+public interface Presenter<M extends Model, V extends View> {
+    /**
+     * 注册Model层
+     *
+     * @param model
+     */
+    void registerModel(M model);
+
+    /**
+     * 注册View层
+     *
+     * @param view
+     */
+    void registerView(V view);
+
+    /**
+     * 获取View
+     *
+     * @return
+     */
+    V getView();
+
+    /**
+     * 销毁动作(如Activity、Fragment的卸载)
+     */
+    void destroy();
+}

+ 15 - 0
app-base/src/main/java/com/efunbox/base/View.java

@@ -0,0 +1,15 @@
+package com.efunbox.base;
+
+import android.os.Bundle;
+import android.view.ViewGroup;
+
+
+public interface View {
+    BaseMvpActivity getBaseActivity();
+
+
+    ViewGroup getRootView();
+
+    Bundle getBundle();
+
+}

+ 94 - 0
app-base/src/main/java/com/efunbox/base/activity/CrashDialogActivity.java

@@ -0,0 +1,94 @@
+package com.efunbox.base.activity;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.efunbox.base.BaseMvpActivity;
+import com.efunbox.base.MyApplication;
+import com.efunbox.base.R;
+import com.efunbox.base.annotation.BindView;
+import com.efunbox.base.model.CrashActivityModel;
+import com.efunbox.base.model.impl.CrashActivityModelImpl;
+import com.efunbox.base.presenter.CrashActivityPresenter;
+import com.efunbox.base.util.BaseConsts;
+import com.efunbox.base.util.EfunboxUtil;
+import com.efunbox.base.view.CrashActivityView;
+import com.orhanobut.logger.Logger;
+
+public class CrashDialogActivity extends BaseMvpActivity<CrashActivityModel, CrashActivityView, CrashActivityPresenter> implements CrashActivityView {
+
+
+    FrameLayout mRestart;
+    FrameLayout mExitApp;
+
+    @Override
+    public int getLayoutId() {
+        return R.layout.activity_crash_dialog;
+    }
+
+    @Override
+    public View getLayoutView() {
+        return null;
+    }
+
+
+    @Override
+    public void initView() {
+        mActivity = this;
+        mRestart = findViewById(R.id.activity_crash_dialog_restart);
+        mExitApp = findViewById(R.id.activity_crash_dialog_exitapp);
+
+
+    }
+
+    @Override
+    public void initViewListener() {
+        addUiClickListener(mRestart, o -> {
+            //重启APP
+            Intent intent = getPackageManager().getLaunchIntentForPackage(BaseConsts.getAppPackageName());
+            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            startActivity(intent);
+        });
+        addUiClickListener(mExitApp, o -> {
+            EfunboxUtil.killAppProcess(mActivity, MyApplication.mActivityList);
+        });
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) {
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+
+    @Override
+    public BaseMvpActivity getBaseActivity() {
+        return (BaseMvpActivity) mActivity;
+    }
+
+    @Override
+    public ViewGroup getRootView() {
+        return findViewById(android.R.id.content);
+    }
+
+    @Override
+    public CrashActivityModel createModel() {
+        return new CrashActivityModelImpl();
+    }
+
+    @Override
+    public CrashActivityView createView() {
+        return this;
+    }
+
+    @Override
+    public CrashActivityPresenter createPresenter() {
+        return new CrashActivityPresenter();
+    }
+}

+ 13 - 0
app-base/src/main/java/com/efunbox/base/annotation/BindView.java

@@ -0,0 +1,13 @@
+package com.efunbox.base.annotation;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BindView {
+    int id() default -1;
+}

+ 40 - 0
app-base/src/main/java/com/efunbox/base/bean/HttpResultBean.java

@@ -0,0 +1,40 @@
+package com.efunbox.base.bean;
+
+public class HttpResultBean<T> {
+    private String code;
+
+    private String count;
+
+    private T data;
+
+    private String message;
+
+    private boolean success;
+
+
+    public String getCode() {
+        return this.code;
+    }
+
+
+    public String getCount() {
+        return this.count;
+    }
+
+
+    public T getData() {
+        return this.data;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return this.message;
+    }
+
+    public boolean getSuccess() {
+        return this.success;
+    }
+}

+ 50 - 0
app-base/src/main/java/com/efunbox/base/bean/HttpResultDataListBean.java

@@ -0,0 +1,50 @@
+package com.efunbox.base.bean;
+
+import java.util.List;
+
+public class HttpResultDataListBean<T> {
+
+    private boolean hasNext;
+    private boolean hasPrevious;
+    private List<T> list;
+    private String pageNo;
+    private String pageSize;
+    private String start;
+    private String totalNo;
+    private String totalSize;
+
+    public boolean getHasNext() {
+        return hasNext;
+    }
+
+    public boolean getHasPrevious() {
+        return hasPrevious;
+    }
+
+
+    public List<T> getList() {
+        return list;
+    }
+
+    public String getPageNo() {
+        return pageNo;
+    }
+
+
+    public String getPageSize() {
+        return pageSize;
+    }
+
+    public String getStart() {
+        return start;
+    }
+
+
+    public String getTotalNo() {
+        return totalNo;
+    }
+
+    public String getTotalSize() {
+        return totalSize;
+    }
+}

+ 23 - 0
app-base/src/main/java/com/efunbox/base/bean/WeChatBean.java

@@ -0,0 +1,23 @@
+package com.efunbox.base.bean;
+
+public class WeChatBean {
+    public String WX_APPID = "";
+    public String WX_SECRET = "";
+
+
+    public String getAPP_ID() {
+        return WX_APPID;
+    }
+
+    public void setAPP_ID(String APP_ID) {
+        this.WX_APPID = APP_ID;
+    }
+
+    public String getWX_SECRET() {
+        return WX_SECRET;
+    }
+
+    public void setWX_SECRET(String WX_SECRET) {
+        this.WX_SECRET = WX_SECRET;
+    }
+}

+ 30 - 0
app-base/src/main/java/com/efunbox/base/callback/OkHttpCallback.java

@@ -0,0 +1,30 @@
+package com.efunbox.base.callback;
+
+import com.okhttplib.HttpInfo;
+import com.okhttplib.callback.BaseCallback;
+import com.okhttplib.callback.Callback;
+
+import java.io.IOException;
+
+public class OkHttpCallback implements Callback {
+    Callback mCallBack;
+
+    public OkHttpCallback(BaseCallback callback) {
+        mCallBack = (Callback) callback;
+    }
+
+
+    @Override
+    public void onSuccess(HttpInfo info) throws IOException {
+        if (mCallBack != null) {
+            mCallBack.onSuccess(info);
+        }
+    }
+
+    @Override
+    public void onFailure(HttpInfo info) throws IOException {
+        if (mCallBack != null) {
+            mCallBack.onFailure(info);
+        }
+    }
+}

+ 81 - 0
app-base/src/main/java/com/efunbox/base/crash/ActivityKillerV15_V20.java

@@ -0,0 +1,81 @@
+package com.efunbox.base.crash;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Message;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * Created by wanjian on 2018/5/24.
+ * <p>
+ */
+
+public class ActivityKillerV15_V20 implements IActivityKiller {
+
+
+    @Override
+    public void finishLaunchActivity(Message message) {
+        try {
+            Object activityClientRecord = message.obj;
+
+            Field tokenField = activityClientRecord.getClass().getDeclaredField("token");
+
+            tokenField.setAccessible(true);
+            IBinder binder = (IBinder) tokenField.get(activityClientRecord);
+            finish(binder);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @Override
+    public void finishResumeActivity(Message message) {
+
+        try {
+            finish((IBinder) message.obj);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @Override
+    public void finishPauseActivity(Message message) {
+
+        try {
+            finish((IBinder) message.obj);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void finishStopActivity(Message message) {
+        try {
+            finish((IBinder) message.obj);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    private void finish(IBinder binder) throws Exception {
+
+        Class activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
+
+        Method getDefaultMethod = activityManagerNativeClass.getDeclaredMethod("getDefault");
+
+        Object activityManager = getDefaultMethod.invoke(null);
+
+
+        Method finishActivityMethod = activityManager.getClass().getDeclaredMethod("finishActivity", IBinder.class, int.class, Intent.class);
+        finishActivityMethod.invoke(activityManager, binder, Activity.RESULT_CANCELED, null);
+
+    }
+
+}

+ 77 - 0
app-base/src/main/java/com/efunbox/base/crash/ActivityKillerV21_V23.java

@@ -0,0 +1,77 @@
+package com.efunbox.base.crash;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Message;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+
+public class ActivityKillerV21_V23 implements IActivityKiller {
+
+
+    @Override
+    public void finishLaunchActivity(Message message) {
+        try {
+            Object activityClientRecord = message.obj;
+
+            Field tokenField = activityClientRecord.getClass().getDeclaredField("token");
+
+            tokenField.setAccessible(true);
+            IBinder binder = (IBinder) tokenField.get(activityClientRecord);
+            finish(binder);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @Override
+    public void finishResumeActivity(Message message) {
+
+        try {
+            finish((IBinder) message.obj);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @Override
+    public void finishPauseActivity(Message message) {
+
+        try {
+            finish((IBinder) message.obj);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void finishStopActivity(Message message) {
+        try {
+            finish((IBinder) message.obj);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    private void finish(IBinder binder) throws Exception {
+
+        Class activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
+
+        Method getDefaultMethod = activityManagerNativeClass.getDeclaredMethod("getDefault");
+
+        Object activityManager = getDefaultMethod.invoke(null);
+
+
+        Method finishActivityMethod = activityManager.getClass().getDeclaredMethod("finishActivity", IBinder.class, int.class, Intent.class, boolean.class);
+        finishActivityMethod.invoke(activityManager, binder, Activity.RESULT_CANCELED, null, false);
+
+    }
+
+}

+ 82 - 0
app-base/src/main/java/com/efunbox/base/crash/ActivityKillerV24_V25.java

@@ -0,0 +1,82 @@
+package com.efunbox.base.crash;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Message;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+
+public class ActivityKillerV24_V25 implements IActivityKiller {
+
+
+    @Override
+    public void finishLaunchActivity(Message message) {
+        try {
+            Object activityClientRecord = message.obj;
+
+            Field tokenField = activityClientRecord.getClass().getDeclaredField("token");
+
+            tokenField.setAccessible(true);
+            IBinder binder = (IBinder) tokenField.get(activityClientRecord);
+            finish(binder);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @Override
+    public void finishResumeActivity(Message message) {
+
+        finishSomeArgs(message);
+    }
+
+
+    @Override
+    public void finishPauseActivity(Message message) {
+
+        finishSomeArgs(message);
+    }
+
+    @Override
+    public void finishStopActivity(Message message) {
+        finishSomeArgs(message);
+    }
+
+
+    private void finishSomeArgs(Message message) {
+        try {
+            Object someArgs = message.obj;
+            Field arg1Field = someArgs.getClass().getDeclaredField("arg1");
+            arg1Field.setAccessible(true);
+            IBinder binder = (IBinder) arg1Field.get(someArgs);
+            finish(binder);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+    }
+
+    private void finish(IBinder binder) throws Exception {
+
+        /*
+         ActivityManagerNative.getDefault()
+                 .finishActivity(r.token, Activity.RESULT_CANCELED, null, Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
+         */
+        Class activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
+
+        Method getDefaultMethod = activityManagerNativeClass.getDeclaredMethod("getDefault");
+
+        Object activityManager = getDefaultMethod.invoke(null);
+
+
+        Method finishActivityMethod = activityManager.getClass().getDeclaredMethod("finishActivity", IBinder.class, int.class, Intent.class, int.class);
+        int DONT_FINISH_TASK_WITH_ACTIVITY = 0;
+        finishActivityMethod.invoke(activityManager, binder, Activity.RESULT_CANCELED, null, DONT_FINISH_TASK_WITH_ACTIVITY);
+
+    }
+
+}

+ 81 - 0
app-base/src/main/java/com/efunbox/base/crash/ActivityKillerV26.java

@@ -0,0 +1,81 @@
+package com.efunbox.base.crash;
+
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Message;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * Created by wanjian on 2018/5/24.
+ * <p>
+ * <p>
+ * handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,msg.arg2, false);
+ * ActivityManager.getService().finishActivity(mToken, resultCode, resultData, finishTask)
+ */
+
+public class ActivityKillerV26 implements IActivityKiller {
+
+
+    @Override
+    public void finishLaunchActivity(Message message) {
+        try {
+            Object activityClientRecord = message.obj;
+
+            Field tokenField = activityClientRecord.getClass().getDeclaredField("token");
+
+            tokenField.setAccessible(true);
+            IBinder binder = (IBinder) tokenField.get(activityClientRecord);
+            finish(binder);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @Override
+    public void finishResumeActivity(Message message) {
+
+        finishSomeArgs(message);
+    }
+
+
+    @Override
+    public void finishPauseActivity(Message message) {
+
+        finishSomeArgs(message);
+    }
+
+    @Override
+    public void finishStopActivity(Message message) {
+        finishSomeArgs(message);
+    }
+
+
+    private void finishSomeArgs(Message message) {
+        try {
+            Object someArgs = message.obj;
+            Field arg1Field = someArgs.getClass().getDeclaredField("arg1");
+            arg1Field.setAccessible(true);
+            IBinder binder = (IBinder) arg1Field.get(someArgs);
+            finish(binder);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+    }
+
+    private void finish(IBinder binder) throws Exception {
+        Method getServiceMethod = ActivityManager.class.getDeclaredMethod("getService");
+        Object activityManager = getServiceMethod.invoke(null);
+
+        Method finishActivityMethod = activityManager.getClass().getDeclaredMethod("finishActivity", IBinder.class, int.class, Intent.class, int.class);
+        finishActivityMethod.setAccessible(true);
+        int DONT_FINISH_TASK_WITH_ACTIVITY = 0;
+        finishActivityMethod.invoke(activityManager, binder, Activity.RESULT_CANCELED, null, DONT_FINISH_TASK_WITH_ACTIVITY);
+
+    }
+}

+ 89 - 0
app-base/src/main/java/com/efunbox/base/crash/ActivityKillerV28.java

@@ -0,0 +1,89 @@
+package com.efunbox.base.crash;
+
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Message;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+
+public class ActivityKillerV28 implements IActivityKiller {
+
+
+    @Override
+    public void finishLaunchActivity(Message message) {
+
+        try {
+            tryFinish1(message);
+            return;
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+
+        try {
+            tryFinish2(message);
+            return;
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+
+        try {
+            tryFinish3(message);
+            return;
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+
+    }
+
+    private void tryFinish1(Message message) throws Throwable {
+        ClientTransaction clientTransaction = (ClientTransaction) message.obj;
+        IBinder binder = clientTransaction.getActivityToken();
+        finish(binder);
+    }
+
+    private void tryFinish3(Message message) throws Throwable {
+        Object clientTransaction = message.obj;
+        Field mActivityTokenField = clientTransaction.getClass().getDeclaredField("mActivityToken");
+        IBinder binder = (IBinder) mActivityTokenField.get(clientTransaction);
+        finish(binder);
+    }
+
+    private void tryFinish2(Message message) throws Throwable {
+        Object clientTransaction = message.obj;
+        Method getActivityTokenMethod = clientTransaction.getClass().getDeclaredMethod("getActivityToken");
+        IBinder binder = (IBinder) getActivityTokenMethod.invoke(clientTransaction);
+        finish(binder);
+    }
+
+
+    @Override
+    public void finishResumeActivity(Message message) {
+
+    }
+
+
+    @Override
+    public void finishPauseActivity(Message message) {
+
+    }
+
+    @Override
+    public void finishStopActivity(Message message) {
+    }
+
+    private void finish(IBinder binder) throws Exception {
+        Method getServiceMethod = ActivityManager.class.getDeclaredMethod("getService");
+        Object activityManager = getServiceMethod.invoke(null);
+
+        Method finishActivityMethod = activityManager.getClass().getDeclaredMethod("finishActivity", IBinder.class, int.class, Intent.class, int.class);
+        finishActivityMethod.setAccessible(true);
+        int DONT_FINISH_TASK_WITH_ACTIVITY = 0;
+        finishActivityMethod.invoke(activityManager, binder, Activity.RESULT_CANCELED, null, DONT_FINISH_TASK_WITH_ACTIVITY);
+
+    }
+}

+ 46 - 0
app-base/src/main/java/com/efunbox/base/crash/ActivityLifecycleCallbacksAdapter.java

@@ -0,0 +1,46 @@
+package com.efunbox.base.crash;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+
+/**
+ * Created by wanjian on 2018/5/21.
+ */
+
+public class ActivityLifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks {
+    @Override
+    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+
+    }
+
+    @Override
+    public void onActivityStarted(Activity activity) {
+
+    }
+
+    @Override
+    public void onActivityResumed(Activity activity) {
+
+    }
+
+    @Override
+    public void onActivityPaused(Activity activity) {
+
+    }
+
+    @Override
+    public void onActivityStopped(Activity activity) {
+
+    }
+
+    @Override
+    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+
+    }
+
+    @Override
+    public void onActivityDestroyed(Activity activity) {
+
+    }
+}

+ 10 - 0
app-base/src/main/java/com/efunbox/base/crash/ClientTransaction.java

@@ -0,0 +1,10 @@
+package com.efunbox.base.crash;
+
+import android.os.IBinder;
+
+public class ClientTransaction {
+
+    public IBinder getActivityToken() {
+        return null;
+    }
+}

+ 240 - 0
app-base/src/main/java/com/efunbox/base/crash/Cockroach.java

@@ -0,0 +1,240 @@
+package com.efunbox.base.crash;
+
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import java.lang.reflect.Field;
+
+import me.weishu.reflection.Reflection;
+
+
+/**
+ * Created by wanjian on 2017/2/14.
+ */
+
+public final class Cockroach {
+
+    private static IActivityKiller sActivityKiller;
+    private static ExceptionHandler sExceptionHandler;
+    private static boolean sInstalled = false;//标记位,避免重复安装卸载
+    private static boolean sIsSafeMode;
+
+    private Cockroach() {
+    }
+
+    public static void install(Context ctx, ExceptionHandler exceptionHandler) {
+        if (sInstalled) {
+            return;
+        }
+        try {
+            //解除 android P 反射限制
+            Reflection.unseal(ctx);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+        sInstalled = true;
+        sExceptionHandler = exceptionHandler;
+
+        initActivityKiller();
+
+        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+            @Override
+            public void uncaughtException(Thread t, Throwable e) {
+                if (sExceptionHandler != null) {
+                    sExceptionHandler.uncaughtExceptionHappened(t, e);
+                }
+                if (t == Looper.getMainLooper().getThread()) {
+                    isChoreographerException(e);
+                    safeMode();
+                }
+            }
+        });
+
+    }
+
+    /**
+     * 替换ActivityThread.mH.mCallback,实现拦截Activity生命周期,直接忽略生命周期的异常的话会导致黑屏,目前
+     * 会调用ActivityManager的finishActivity结束掉生命周期抛出异常的Activity
+     */
+    private static void initActivityKiller() {
+        //各版本android的ActivityManager获取方式,finishActivity的参数,token(binder对象)的获取不一样
+        if (Build.VERSION.SDK_INT >= 28) {
+            sActivityKiller = new ActivityKillerV28();
+        } else if (Build.VERSION.SDK_INT >= 26) {
+            sActivityKiller = new ActivityKillerV26();
+        } else if (Build.VERSION.SDK_INT == 25 || Build.VERSION.SDK_INT == 24) {
+            sActivityKiller = new ActivityKillerV24_V25();
+        } else if (Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT <= 23) {
+            sActivityKiller = new ActivityKillerV21_V23();
+        } else if (Build.VERSION.SDK_INT >= 15 && Build.VERSION.SDK_INT <= 20) {
+            sActivityKiller = new ActivityKillerV15_V20();
+        } else if (Build.VERSION.SDK_INT < 15) {
+            sActivityKiller = new ActivityKillerV15_V20();
+        }
+
+        try {
+            hookmH();
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static void hookmH() throws Exception {
+
+        final int LAUNCH_ACTIVITY = 100;
+        final int PAUSE_ACTIVITY = 101;
+        final int PAUSE_ACTIVITY_FINISHING = 102;
+        final int STOP_ACTIVITY_HIDE = 104;
+        final int RESUME_ACTIVITY = 107;
+        final int DESTROY_ACTIVITY = 109;
+        final int NEW_INTENT = 112;
+        final int RELAUNCH_ACTIVITY = 126;
+        Class activityThreadClass = Class.forName("android.app.ActivityThread");
+        Object activityThread = activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null);
+
+        Field mhField = activityThreadClass.getDeclaredField("mH");
+        mhField.setAccessible(true);
+        final Handler mhHandler = (Handler) mhField.get(activityThread);
+        Field callbackField = Handler.class.getDeclaredField("mCallback");
+        callbackField.setAccessible(true);
+        callbackField.set(mhHandler, new Handler.Callback() {
+            @Override
+            public boolean handleMessage(Message msg) {
+                if (Build.VERSION.SDK_INT >= 28) {//android P 生命周期全部走这
+                    final int EXECUTE_TRANSACTION = 159;
+                    if (msg.what == EXECUTE_TRANSACTION) {
+                        try {
+                            mhHandler.handleMessage(msg);
+                        } catch (Throwable throwable) {
+                            sActivityKiller.finishLaunchActivity(msg);
+                            notifyException(throwable);
+                        }
+                        return true;
+                    }
+                    return false;
+                }
+                switch (msg.what) {
+                    case LAUNCH_ACTIVITY:// startActivity--> activity.attach  activity.onCreate  r.activity!=null  activity.onStart  activity.onResume
+                        try {
+                            mhHandler.handleMessage(msg);
+                        } catch (Throwable throwable) {
+                            sActivityKiller.finishLaunchActivity(msg);
+                            notifyException(throwable);
+                        }
+                        return true;
+                    case RESUME_ACTIVITY://回到activity onRestart onStart onResume
+                        try {
+                            mhHandler.handleMessage(msg);
+                        } catch (Throwable throwable) {
+                            sActivityKiller.finishResumeActivity(msg);
+                            notifyException(throwable);
+                        }
+                        return true;
+                    case PAUSE_ACTIVITY_FINISHING://按返回键 onPause
+                        try {
+                            mhHandler.handleMessage(msg);
+                        } catch (Throwable throwable) {
+                            sActivityKiller.finishPauseActivity(msg);
+                            notifyException(throwable);
+                        }
+                        return true;
+                    case PAUSE_ACTIVITY://开启新页面时,旧页面执行 activity.onPause
+                        try {
+                            mhHandler.handleMessage(msg);
+                        } catch (Throwable throwable) {
+                            sActivityKiller.finishPauseActivity(msg);
+                            notifyException(throwable);
+                        }
+                        return true;
+                    case STOP_ACTIVITY_HIDE://开启新页面时,旧页面执行 activity.onStop
+                        try {
+                            mhHandler.handleMessage(msg);
+                        } catch (Throwable throwable) {
+                            sActivityKiller.finishStopActivity(msg);
+                            notifyException(throwable);
+                        }
+                        return true;
+                    case DESTROY_ACTIVITY:// 关闭activity onStop  onDestroy
+                        try {
+                            mhHandler.handleMessage(msg);
+                        } catch (Throwable throwable) {
+                            notifyException(throwable);
+                        }
+                        return true;
+                }
+                return false;
+            }
+        });
+    }
+
+
+    private static void notifyException(Throwable throwable) {
+        if (sExceptionHandler == null) {
+            return;
+        }
+        if (isSafeMode()) {
+            sExceptionHandler.bandageExceptionHappened(throwable);
+        } else {
+            sExceptionHandler.uncaughtExceptionHappened(Looper.getMainLooper().getThread(), throwable);
+            safeMode();
+        }
+    }
+
+    public static boolean isSafeMode() {
+        return sIsSafeMode;
+    }
+
+    private static void safeMode() {
+        sIsSafeMode = true;
+        if (sExceptionHandler != null) {
+            sExceptionHandler.enterSafeMode();
+        }
+        while (true) {
+            try {
+                Looper.loop();
+            } catch (Throwable e) {
+                isChoreographerException(e);
+                if (sExceptionHandler != null) {
+                    sExceptionHandler.bandageExceptionHappened(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * view measure layout draw时抛出异常会导致Choreographer挂掉
+     * <p>
+     * 建议直接杀死app。以后的版本会只关闭黑屏的Activity
+     *
+     * @param e
+     */
+    private static void isChoreographerException(Throwable e) {
+        if (e == null || sExceptionHandler == null) {
+            return;
+        }
+        StackTraceElement[] elements = e.getStackTrace();
+        if (elements == null) {
+            return;
+        }
+
+        for (int i = elements.length - 1; i > -1; i--) {
+            if (elements.length - i > 20) {
+                return;
+            }
+            StackTraceElement element = elements[i];
+            if ("android.view.Choreographer".equals(element.getClassName())
+                    && "Choreographer.java".equals(element.getFileName())
+                    && "doFrame".equals(element.getMethodName())) {
+                sExceptionHandler.mayBeBlackScreen(e);
+                return;
+            }
+
+        }
+    }
+
+
+}

+ 98 - 0
app-base/src/main/java/com/efunbox/base/crash/CrashLog.java

@@ -0,0 +1,98 @@
+package com.efunbox.base.crash;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+import com.orhanobut.logger.Logger;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.Field;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ *
+ */
+public class CrashLog {
+    public static final String TAG = "CrashLog";
+
+    public static void saveCrashLog(Context context, Throwable throwable) {
+        Map<String, String> map = collectDeviceInfo(context);
+        saveCrashInfo2File(context, throwable, map);
+    }
+
+
+    private static Map<String, String> collectDeviceInfo(Context ctx) {
+        Map<String, String> infos = new TreeMap<>();
+        try {
+
+            infos.put("systemVersion", Build.VERSION.RELEASE);
+            PackageManager pm = ctx.getPackageManager();
+            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
+            if (pi != null) {
+                String versionName = pi.versionName == null ? "null" : pi.versionName;
+                String versionCode = pi.versionCode + "";
+                infos.put("versionName", versionName);
+                infos.put("versionCode", versionCode);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+        Field[] fields = Build.class.getDeclaredFields();
+        for (Field field : fields) {
+            try {
+                field.setAccessible(true);
+                infos.put(field.getName(), field.get(null).toString());
+            } catch (Exception e) {
+            }
+        }
+        return infos;
+    }
+
+    private static void saveCrashInfo2File(Context context, Throwable ex, Map<String, String> infos) {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : infos.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            sb.append(key).append("=").append(value).append("\n");
+        }
+
+        Writer writer = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(writer);
+        ex.printStackTrace(printWriter);
+        Throwable cause = ex.getCause();
+        while (cause != null) {
+            cause.printStackTrace(printWriter);
+            cause = cause.getCause();
+        }
+        printWriter.close();
+        String result = writer.toString();
+        sb.append(result);
+
+        try {
+            long timestamp = System.currentTimeMillis();
+            String time = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
+            String fileName = "crash-" + time + "-" + timestamp + ".log";
+            String cachePath = crashLogDir(context);
+            Logger.e("efunboxReader-LogPath:" + cachePath);
+            File dir = new File(cachePath);
+            dir.mkdirs();
+            FileOutputStream fos = new FileOutputStream(cachePath + fileName);
+            fos.write(sb.toString().getBytes());
+            fos.close();
+        } catch (Exception e) {
+        }
+    }
+
+    public static String crashLogDir(Context context) {
+        return context.getCacheDir().getPath() + File.separator + "crash" + File
+                .separator;
+    }
+}

+ 44 - 0
app-base/src/main/java/com/efunbox/base/crash/DebugSafeModeTipActivity.java

@@ -0,0 +1,44 @@
+package com.efunbox.base.crash;
+
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+
+import com.efunbox.base.R;
+import com.efunbox.base.fragment.CrashLogFragment;
+
+
+/**
+ *
+ */
+
+public class DebugSafeModeTipActivity extends AppCompatActivity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_safe_mode_warning);
+
+        findViewById(R.id.log).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Fragment fragment = getSupportFragmentManager().findFragmentByTag(CrashLogFragment.class.getName());
+                if (fragment == null) {
+                    fragment = new CrashLogFragment();
+                }
+                getSupportFragmentManager()
+                        .beginTransaction()
+                        .replace(R.id.container, fragment, CrashLogFragment.class.getName())
+                        .commit();
+            }
+        });
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return true;
+    }
+}

+ 75 - 0
app-base/src/main/java/com/efunbox/base/crash/DebugSafeModeUI.java

@@ -0,0 +1,75 @@
+package com.efunbox.base.crash;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by wanjian on 2018/5/21.
+ * 进入安全模式后给所有act添加一个渐变的绿色顶栏
+ */
+
+public class DebugSafeModeUI {
+
+    private static int barHeight;
+    private static List<WeakReference<Activity>> sActivitysWRef = new ArrayList<>();
+
+    public static void init(Application application) {
+        application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksAdapter() {
+            @Override
+            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+                super.onActivityCreated(activity, savedInstanceState);
+                sActivitysWRef.add(new WeakReference<>(activity));
+                if (Cockroach.isSafeMode()) {
+                    //进入安全模式后给新创建的act添加渐变绿色顶栏
+                    enterSafeMode(activity);
+                }
+            }
+
+            @Override
+            public void onActivityDestroyed(Activity activity) {
+                super.onActivityDestroyed(activity);
+                for (WeakReference<Activity> reference : sActivitysWRef) {
+                    Activity act = reference.get();
+                    if (act == activity) {
+                        sActivitysWRef.remove(reference);
+                        return;
+                    }
+                }
+            }
+        });
+        barHeight = (int) (application.getResources().getDisplayMetrics().density * 50);
+    }
+
+    /**
+     * 进入安全模式后给当前已存在的act添加渐变绿色顶栏
+     */
+    public static void showSafeModeUI() {
+        for (WeakReference<Activity> reference : sActivitysWRef) {
+            Activity activity = reference.get();
+            if (activity == null || activity.isFinishing()) {
+                continue;
+            }
+            enterSafeMode(activity);
+        }
+    }
+
+    public static void enterSafeMode(Activity activity) {
+        try {
+            View view = new View(activity);
+            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, barHeight);
+            view.setLayoutParams(params);
+//            view.setBackgroundResource(R.drawable.);
+            ((ViewGroup) activity.getWindow().getDecorView()).addView(view);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+    }
+}

+ 59 - 0
app-base/src/main/java/com/efunbox/base/crash/ExceptionHandler.java

@@ -0,0 +1,59 @@
+package com.efunbox.base.crash;
+
+
+public abstract class ExceptionHandler {
+    final void uncaughtExceptionHappened(Thread thread, Throwable throwable) {
+        try {//捕获监听中异常,防止使用方代码抛出异常时导致的反复调用
+            onUncaughtExceptionHappened(thread, throwable);
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+
+
+    final void bandageExceptionHappened(Throwable throwable) {
+        try {//捕获监听中异常,防止使用方代码抛出异常时导致的反复调用
+            onBandageExceptionHappened(throwable);
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+    }
+
+    final void enterSafeMode() {
+        try {
+            onEnterSafeMode();
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+    }
+
+    final void mayBeBlackScreen(Throwable e) {
+        try {
+            onMayBeBlackScreen(e);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+    }
+
+    /**
+     * 子线程抛出异常时始终调用该方法。主线程只有第一次抛出异常时才会调用该方法,该方法中到的throwable都会上报到bugly。以后主线程的异常只调用 {@link #onBandageExceptionHappened(Throwable)}
+     *
+     * @param thread
+     * @param throwable
+     */
+    protected abstract void onUncaughtExceptionHappened(Thread thread, Throwable throwable);
+
+    /**
+     * 当原本导致app崩溃的主线程异常发生后,主线程再次抛出导致app崩溃异常时会调用该方法。(自己try catch住的异常不会导致app崩溃)
+     * (该方法中到的throwable不会上报到bugly,也无需上报到bugly,因为本次异常可能是由于第一次主线程异常时app没有崩溃掉才发生的,只要修复了bug就不会发生该异常了)
+     *
+     * @param throwable 主线程的异常
+     */
+    protected abstract void onBandageExceptionHappened(Throwable throwable);
+
+    protected abstract void onEnterSafeMode();
+
+    protected void onMayBeBlackScreen(Throwable e) {
+
+    }
+}

+ 17 - 0
app-base/src/main/java/com/efunbox/base/crash/IActivityKiller.java

@@ -0,0 +1,17 @@
+package com.efunbox.base.crash;
+
+
+import android.os.Message;
+
+public interface IActivityKiller {
+
+    void finishLaunchActivity(Message message);
+
+    void finishResumeActivity(Message message);
+
+    void finishPauseActivity(Message message);
+
+    void finishStopActivity(Message message);
+
+
+}

+ 59 - 0
app-base/src/main/java/com/efunbox/base/crash/LinearTopSmoothScroller.java

@@ -0,0 +1,59 @@
+package com.efunbox.base.crash;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+
+import androidx.recyclerview.widget.LinearSmoothScroller;
+
+public class LinearTopSmoothScroller extends LinearSmoothScroller {
+
+    /**
+     * MILLISECONDS_PER_INCH 值越大滚动越慢
+     */
+    private float MILLISECONDS_PER_INCH = 0.03f;
+    private final Context context;
+
+    /**
+     * @param context  context
+     * @param needFast 是否需要快速滑动
+     */
+    public LinearTopSmoothScroller(Context context, boolean needFast) {
+        super(context);
+        this.context = context;
+        if (needFast) {
+            setScrollFast();
+        } else {
+            setScrollSlowly();
+        }
+    }
+
+
+    @Override
+    protected int getHorizontalSnapPreference() {
+        return SNAP_TO_START;
+    }
+
+    @Override
+    protected int getVerticalSnapPreference() {
+        return SNAP_TO_START;
+    }
+
+    @Override
+    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+        //return super.calculateSpeedPerPixel(displayMetrics);
+        setScrollSlowly();
+        return MILLISECONDS_PER_INCH / displayMetrics.density;
+    }
+
+    public void setScrollSlowly() {
+        //建议不同分辨率设备上的滑动速度相同
+        //0.3f可以根据不同自己的需求进行更改
+        MILLISECONDS_PER_INCH = context.getResources().getDisplayMetrics().density * 0.3f;
+    }
+
+    public void setScrollFast() {
+        MILLISECONDS_PER_INCH = context.getResources().getDisplayMetrics().density * 0.03f;
+    }
+
+
+}

+ 125 - 0
app-base/src/main/java/com/efunbox/base/cusview/CusToast.java

@@ -0,0 +1,125 @@
+package com.efunbox.base.cusview;
+
+
+import android.content.Context;
+import android.os.Build;
+import android.os.CountDownTimer;
+import android.os.Handler;
+import android.os.Message;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.efunbox.base.R;
+import com.efunbox.base.util.EfunboxUtil;
+import com.efunbox.base.util.Logger;
+
+public class CusToast extends Toast {
+    private boolean canceled = true;
+    private Toast toast;
+    private TimeCount time;
+    private TextView toast_content;
+    private static CusToast instance;
+
+
+    public static CusToast getInstance(Context context) {
+        if (instance == null) {
+            instance = new CusToast(context);
+        }
+
+        return instance;
+    }
+
+
+    public CusToast(Context context) {
+        super(context);
+        EfunboxUtil.initDesignSize(context);
+        View layout = LayoutInflater.from(context).inflate(R.layout.toast, null, false);
+        toast_content = (TextView) layout.findViewById(R.id.tvToast);
+        if (toast == null) {
+            toast = new Toast(context);
+        }
+        toast.setGravity(Gravity.CENTER, 0, 0);
+        toast.setDuration(Toast.LENGTH_LONG);
+        toast.setView(layout);
+    }
+
+
+    /**
+     * @param text     要显示的内容
+     * @param duration 显示的时间长
+     *                 根据LENGTH_MAX进行判断
+     *                 如果不匹配,进行系统显示
+     *                 如果匹配,永久显示,直到调用hide()
+     */
+    public void show(String text, int duration) {
+        if (time != null) {
+            time.cancel();
+            time = null;
+        }
+        time = new TimeCount(duration, 1000);//1000是消失渐变时间
+        toast_content.setText(text);
+        time.start();
+        canceled = false;
+        showUntilCancel();
+    }
+
+    /**
+     * 隐藏Toast
+     */
+    public void hide() {
+        if (toast != null) {
+            toast.cancel();
+        }
+        canceled = true;
+    }
+
+    private void showUntilCancel() {
+        if (handler != null) {
+            handler.removeMessages(1);
+        }
+        if (canceled) {
+            return;
+        }
+        toast.show();
+        handler.sendEmptyMessageDelayed(1, 3000);
+    }
+
+    Handler handler = new Handler(new Handler.Callback() {
+        @Override
+        public boolean handleMessage(@NonNull Message message) {
+            switch (message.what) {
+                case 1:
+                    showUntilCancel();
+                    break;
+                default:
+                    throw new IllegalStateException("Unexpected value: " + message.what);
+            }
+            return false;
+        }
+    });
+
+    /**
+     * 计时器
+     */
+    class TimeCount extends CountDownTimer {
+        public TimeCount(long millisInFuture, long countDownInterval) {
+            super(millisInFuture, countDownInterval); // 总时长,计时的时间间隔
+        }
+
+        @Override
+        public void onFinish() { // 计时完毕时触发
+            hide();
+        }
+
+        @Override
+        public void onTick(long millisUntilFinished) { // 计时过程显示
+        }
+
+    }
+}

+ 16 - 0
app-base/src/main/java/com/efunbox/base/debug/MyApplication.java

@@ -0,0 +1,16 @@
+package com.efunbox.base.debug;
+
+import android.app.Application;
+
+import com.efunbox.base.util.Logger;
+
+
+public class MyApplication extends Application {
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Logger.init(this);
+        new com.efunbox.base.MyApplication().initModule(this);
+    }
+}

+ 245 - 0
app-base/src/main/java/com/efunbox/base/fragment/CrashLogFragment.java

@@ -0,0 +1,245 @@
+package com.efunbox.base.fragment;
+
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.efunbox.base.R;
+import com.efunbox.base.crash.CrashLog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+
+public class CrashLogFragment extends Fragment {
+
+    RecyclerView recyclerView;
+    Handler fileReadHandler;
+    Handler uiHandler = new Handler();
+    LogAdapter adapter = new LogAdapter();
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.fragment_crash_log, container, false);
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        recyclerView = view.findViewById(R.id.recyclerview);
+        recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), RecyclerView.VERTICAL, false));
+        recyclerView.setAdapter(adapter);
+
+        DividerItemDecoration decoration = new DividerItemDecoration(getContext(), RecyclerView.VERTICAL);
+        decoration.setDrawable(getResources().getDrawable(R.drawable.list_divider_horizontal));
+        recyclerView.addItemDecoration(decoration);
+
+        HandlerThread thread = new HandlerThread("crash_log_read") {
+            @Override
+            protected void onLooperPrepared() {
+                super.onLooperPrepared();
+                fileReadHandler = new Handler(getLooper());
+                readFileList();
+            }
+        };
+        thread.start();
+
+
+    }
+
+    private void readFileList() {
+        fileReadHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                String dir = CrashLog.crashLogDir(getContext());
+                if (dir == null) {
+                    return;
+                }
+                File file = new File(dir);
+                List<File> fs = Arrays.asList(file.listFiles());
+
+                Collections.sort(fs, new Comparator<File>() {
+                    @Override
+                    public int compare(File o1, File o2) {
+                        return (int) (o2.lastModified() - o1.lastModified());
+                    }
+                });
+
+                final List<Log> logs = new ArrayList<>();
+                for (File f : fs) {
+                    logs.add(new Log(f, f.getName(), null));
+                }
+                setFileList(logs);
+            }
+        });
+    }
+
+    private void setFileList(final List<Log> logs) {
+
+        uiHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                adapter.setFileList(logs);
+            }
+        });
+    }
+
+    private void readFileContent(final File file) {
+
+        fileReadHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    String content = read(file);
+                    update(file, content);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+
+            }
+        });
+    }
+
+    private void update(final File file, final String content) {
+        if (content == null) {
+            return;
+        }
+
+        uiHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                for (Log log : adapter.logs) {
+                    if (log.file == file) {
+                        log.content = content;
+                        adapter.notifyDataSetChanged();
+                        return;
+                    }
+
+                }
+
+            }
+        });
+    }
+
+    private String read(File file) throws Exception {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
+        String line;
+
+        StringBuilder builder = new StringBuilder((int) file.length());
+        while ((line = reader.readLine()) != null) {
+            builder.append(line);
+            builder.append("\n");
+        }
+        return builder.toString();
+
+    }
+
+    class LogAdapter extends RecyclerView.Adapter<LogAdapter.LogVH> {
+
+        private List<Log> logs;
+
+        @NonNull
+        @Override
+        public LogVH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+            return new LogVH();
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull LogVH holder, int position) {
+            holder.title.setTag(position);
+            Log log = getData(position);
+            holder.copy.setTag(log.content);
+            holder.title.setText(log.title);
+            if (log.content == null) {
+                holder.content.setVisibility(View.GONE);
+                holder.copy.setVisibility(View.INVISIBLE);
+            } else {
+                holder.content.setText(log.content);
+                holder.content.setVisibility(View.VISIBLE);
+                holder.copy.setVisibility(View.VISIBLE);
+            }
+        }
+
+        protected Log getData(int position) {
+            return logs.get(position);
+        }
+
+        @Override
+        public int getItemCount() {
+            return logs == null ? 0 : logs.size();
+        }
+
+        public void setFileList(List<Log> fs) {
+            this.logs = fs;
+            notifyDataSetChanged();
+        }
+
+        class LogVH extends RecyclerView.ViewHolder {
+
+            TextView title;
+            TextView content;
+            TextView copy;
+
+            public LogVH() {
+                super(LayoutInflater.from(getContext()).inflate(R.layout.item_crash_log, recyclerView, false));
+                title = itemView.findViewById(R.id.title);
+                content = itemView.findViewById(R.id.content);
+                copy = itemView.findViewById(R.id.copy);
+                title.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        int position = ((int) v.getTag());
+                        if (logs.get(position).content == null) {
+                            readFileContent(logs.get(position).file);
+                        }
+                    }
+                });
+                copy.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        String log = ((String) v.getTag());
+                        ClipboardManager cmb = (ClipboardManager) v.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+                        cmb.setText(log);
+//                        Toast.makeText(v.getContext(), "已经复制到粘贴板", Toast.LENGTH_SHORT).show();
+                    }
+                });
+            }
+        }
+
+
+    }
+
+    class Log {
+        File file;
+        String title;
+        String content;
+
+        public Log(File file, String title, String content) {
+            this.file = file;
+            this.title = title;
+            this.content = content;
+        }
+    }
+}
+

+ 6 - 0
app-base/src/main/java/com/efunbox/base/model/CrashActivityModel.java

@@ -0,0 +1,6 @@
+package com.efunbox.base.model;
+
+import com.efunbox.base.Model;
+
+public interface CrashActivityModel extends Model {
+}

+ 6 - 0
app-base/src/main/java/com/efunbox/base/model/impl/CrashActivityModelImpl.java

@@ -0,0 +1,6 @@
+package com.efunbox.base.model.impl;
+
+import com.efunbox.base.model.CrashActivityModel;
+
+public class CrashActivityModelImpl implements CrashActivityModel {
+}

+ 12 - 0
app-base/src/main/java/com/efunbox/base/presenter/CrashActivityPresenter.java

@@ -0,0 +1,12 @@
+package com.efunbox.base.presenter;
+
+import com.efunbox.base.BasePresenter;
+import com.efunbox.base.model.CrashActivityModel;
+import com.efunbox.base.view.CrashActivityView;
+
+public class CrashActivityPresenter extends BasePresenter<CrashActivityModel, CrashActivityView> {
+    @Override
+    protected void onViewDestroy() {
+
+    }
+}

+ 41 - 0
app-base/src/main/java/com/efunbox/base/util/BaseConsts.java

@@ -0,0 +1,41 @@
+package com.efunbox.base.util;
+
+import com.efunbox.base.bean.WeChatBean;
+
+public class BaseConsts {
+
+    //是否是debug环境
+    private static boolean isDebug = false;
+
+    //view点击间隔时间
+    public static final int VIEW_CLICK_TIME = 1000;
+
+    //view共用间隔时间
+    public static final boolean VIEW_CLICK_ALL = true;
+
+    //APP的包名
+    private static String APP_PACKAGE_NAME = "";
+
+
+    //微信支付实体类
+    public static WeChatBean WECHAT_INFO;
+
+
+    public static boolean isDebug() {
+        return isDebug;
+    }
+
+    public static void setDebug(boolean debug) {
+        isDebug = debug;
+    }
+
+
+    public static String getAppPackageName() {
+        return APP_PACKAGE_NAME;
+    }
+
+    public static void setAppPackageName(String appPackageName) {
+        APP_PACKAGE_NAME = appPackageName;
+    }
+
+}

+ 140 - 0
app-base/src/main/java/com/efunbox/base/util/DataCleanManager.java

@@ -0,0 +1,140 @@
+package com.efunbox.base.util;
+
+
+import android.content.Context;
+import android.os.Environment;
+
+import java.io.File;
+
+/**
+ * 本应用数据清除管理器
+ */
+public class DataCleanManager {
+    /**
+     * 清除本应用内部缓存(/data/data/com.xxx.xxx/cache)
+     *
+     * @param context
+     */
+    public static void cleanInternalCache(Context context) {
+        deleteFilesByDirectory(context.getCacheDir());
+    }
+
+    /**
+     * 清除本应用所有数据库(/data/data/com.xxx.xxx/databases)
+     *
+     * @param context
+     */
+    public static void cleanDatabases(Context context) {
+        deleteFilesByDirectory(new File("/data/data/"
+                + context.getPackageName() + "/databases"));
+    }
+
+    /**
+     * 清除本应用SharedPreference(/data/data/com.xxx.xxx/shared_prefs)
+     *
+     * @param context
+     */
+    public static void cleanSharedPreference(Context context) {
+        deleteFilesByDirectory(new File("/data/data/"
+                + context.getPackageName() + "/shared_prefs"));
+    }
+
+    /**
+     * 按名字清除本应用数据库
+     *
+     * @param context
+     * @param dbName
+     */
+    public static void cleanDatabaseByName(Context context, String dbName) {
+        context.deleteDatabase(dbName);
+    }
+
+    /**
+     * 清除/data/data/com.xxx.xxx/files下的内容
+     *
+     * @param context
+     */
+    public static void cleanFiles(Context context) {
+        deleteFilesByDirectory(context.getFilesDir());
+    }
+
+    /**
+     * 清除外部cache下的内容(/mnt/sdcard/android/data/com.xxx.xxx/cache)
+     *
+     * @param context
+     */
+    public static void cleanExternalCache(Context context) {
+        if (Environment.getExternalStorageState().equals(
+                Environment.MEDIA_MOUNTED)) {
+            deleteFilesByDirectory(context.getExternalCacheDir());
+        }
+    }
+
+    /**
+     * 清除自定义路径下的文件,使用需小心,请不要误删。而且只支持目录下的文件删除
+     *
+     * @param filePath
+     */
+    public static void cleanCustomCache(String filePath) {
+        deleteFilesByDirectory(new File(filePath));
+    }
+
+    /**
+     * 清除本应用所有的数据
+     *
+     * @param context
+     * @param filepath
+     */
+    public static void cleanApplicationData(Context context, String... filepath) {
+        cleanInternalCache(context);
+        cleanExternalCache(context);
+        cleanDatabases(context);
+        cleanSharedPreference(context);
+        cleanFiles(context);
+        for (String filePath : filepath) {
+            cleanCustomCache(filePath);
+        }
+    }
+
+    /**
+     * 删除方法 这里只会删除某个文件夹下的文件,如果传入的directory是个文件,将不做处理
+     *
+     * @param directory
+     */
+    public static void deleteFilesByDirectory(File directory) {
+        if (directory != null && directory.exists() && directory.isDirectory()) {
+            for (File item : directory.listFiles()) {
+                item.delete();
+            }
+        }
+    }
+
+    /**
+     * 删除文件夹下指定时间以前的所有文件
+     *
+     * @param folderPath 文件夹路径
+     * @param msec       指定时间(毫秒),传 <=0 表示删除所有的文件
+     */
+    public static void delFileBeforeTime(String folderPath, long msec) {
+        File folder = new File(folderPath);
+        if (folder.exists() && folder.isDirectory()) {
+            File[] files = folder.listFiles();
+            if (files != null && files.length > 0) {
+                for (File file : files) {
+                    if (file.isFile() && (msec <= 0 || file.lastModified() < msec)) {
+                        file.delete();
+//                        Logger.e("delete file :" + file.getAbsolutePath());
+                    } else if (file.isDirectory()) {
+                        delFileBeforeTime(file.getAbsolutePath(), msec);
+                        //这里如果文件夹已经为空,直接删除文件夹
+                        File[] folderFiles = file.listFiles();
+                        if (folderFiles == null || folderFiles.length == 0) {
+                            file.delete();
+//                            Logger.e("delete folder :" + file.getAbsolutePath());
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 265 - 0
app-base/src/main/java/com/efunbox/base/util/DeviceUtil.java

@@ -0,0 +1,265 @@
+package com.efunbox.base.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.util.DisplayMetrics;
+
+import com.orhanobut.logger.Logger;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.security.MessageDigest;
+import java.util.Enumeration;
+import java.util.Locale;
+
+public class DeviceUtil {
+
+
+    private static DisplayMetrics metric = new DisplayMetrics();
+
+    /**
+     * @return 序列号 SerialNumber
+     */
+    public static String SerialNumber() {
+
+        return Build.SERIAL;
+    }
+
+
+    public static String getSerialNumber() {
+        String serial = "获取失败";
+        try {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // 9.0 +
+                serial = Build.getSerial();
+            } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) { // 8.0 +
+                serial = Build.SERIAL;
+            } else { // 8.0 -
+                Class<?> c = Class.forName("android.os.SystemProperties");
+                Method get = c.getMethod("get", String.class);
+                serial = (String) get.invoke(c, "ro.serialno");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            Logger.e("获取设备序列号失败");
+        }
+        return serial;
+    }
+
+
+    public static int getWidth(Activity context) {
+        context.getWindowManager().getDefaultDisplay().getMetrics(metric);
+        return metric.widthPixels; // 屏幕宽度(像素)
+    }
+
+    public static int getHeight(Activity context) {
+        context.getWindowManager().getDefaultDisplay().getMetrics(metric);
+        return metric.heightPixels; // 屏幕高度(像素)
+    }
+
+    public static float getDensity(Activity context) {
+        context.getWindowManager().getDefaultDisplay().getMetrics(metric);
+        return metric.density; // 屏幕密度(0.75 / 1.0 / 1.5)
+    }
+
+    public static int getDensityDpi(Activity context) {
+        context.getWindowManager().getDefaultDisplay().getMetrics(metric);
+        return metric.densityDpi; // // 屏幕密度DPI(120 / 160 / 240)
+    }
+
+    // md5加密
+    public String md5Encode(String inStr) {
+        MessageDigest md5 = null;
+        try {
+            md5 = MessageDigest.getInstance("MD5");
+        } catch (Exception e) {
+            System.out.println(e.toString());
+            e.printStackTrace();
+            return "";
+        }
+
+        byte[] byteArray = null;
+        try {
+            byteArray = inStr.getBytes("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        byte[] md5Bytes = md5.digest(byteArray);
+        StringBuffer hexValue = new StringBuffer();
+        for (int i = 0; i < md5Bytes.length; i++) {
+            int val = ((int) md5Bytes[i]) & 0xff;
+            if (val < 16) {
+                hexValue.append("0");
+            }
+            hexValue.append(Integer.toHexString(val));
+        }
+        return hexValue.toString();
+    }
+
+    /**
+     * @return VersionCode
+     * @author zhangmengjie
+     */
+    public static String getVersionCode(Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        PackageInfo packageInfo;
+        String versionCode = "";
+        try {
+            packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
+            versionCode = packageInfo.versionCode + "";
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return versionCode;
+    }
+
+    /**
+     * @return VersionName
+     * @author zhangmengjie
+     */
+    public static String getVersionName(Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        PackageInfo packageInfo;
+        String versionName = "";
+        try {
+            packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
+            versionName = packageInfo.versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return versionName;
+    }
+
+    /**
+     * 获取当前手机系统语言。
+     *
+     * @return 返回当前系统语言。例如:当前设置的是“中文-中国”,则返回“zh-CN”
+     */
+    public String getSystemLanguage() {
+        return Locale.getDefault().getLanguage();
+    }
+
+    /**
+     * 获取当前系统上的语言列表(Locale列表)
+     *
+     * @return 语言列表
+     */
+    public Locale[] getSystemLanguageList() {
+        return Locale.getAvailableLocales();
+    }
+
+    /**
+     * 获取当前手机系统版本号
+     *
+     * @return 系统版本号
+     */
+    public static String getSystemVersion() {
+        return Build.VERSION.RELEASE;
+    }
+
+    /**
+     * 获取手机型号
+     *
+     * @return 手机型号
+     */
+    public static String getSystemModel() {
+        return Build.MODEL;
+    }
+
+    /**
+     * 获取手机厂商
+     *
+     * @return 手机厂商
+     */
+    public static String getDeviceBrand() {
+        return Build.BRAND;
+    }
+
+//    /**
+//     * 获取手机IMEI(需要“android.permission.READ_PHONE_STATE”权限)
+//     *
+//     * @return 手机IMEI
+//     */
+//    public String getIMEI(Context ctx) {
+//        TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Activity.TELEPHONY_SERVICE);
+//        if (tm != null) {
+//            return tm.getDeviceId();
+//        }
+//        return null;
+//    }
+
+
+    /**
+     * 获取mac地址
+     */
+    // 有线ip得mac
+    public static String getLocalMacAddressFromIp(Context context) {
+        String mac_s = "";
+        String macAddress = "";
+        try {
+            byte[] mac;
+            NetworkInterface ne = NetworkInterface.getByInetAddress(InetAddress
+                    .getByName(getLocalIpAddress()));
+            mac = ne.getHardwareAddress();
+            mac_s = bytes2hex02(mac);
+
+            macAddress = mac_s.substring(0, 2) + ":" + mac_s.substring(2, 4) + ":"
+                    + mac_s.substring(4, 6) + ":" + mac_s.substring(6, 8) + ":"
+                    + mac_s.substring(8, 10) + ":" + mac_s.substring(10, 12);
+            return macAddress;
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return macAddress;
+
+    }
+
+    /**
+     * 二进制转十六进制
+     *
+     * @param
+     * @return
+     */
+    private static String bytes2hex02(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        String tmp = null;
+        for (byte b : bytes) {
+            // 将每个字节与0xFF进行与运算,然后转化为10进制,然后借助于Integer再转化为16进制
+            tmp = Integer.toHexString(0xFF & b);
+            if (tmp.length() == 1)// 每个字节8为,转为16进制标志,2个16进制位
+            {
+                tmp = "0" + tmp;
+            }
+            sb.append(tmp);
+        }
+
+        return sb.toString();
+
+    }
+
+    // 获得ip地址
+    public static String getLocalIpAddress() {
+        try {
+            for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
+                NetworkInterface intf = (NetworkInterface) en.nextElement();
+                for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
+                    InetAddress inetAddress = (InetAddress) enumIpAddr.nextElement();
+                    if (!inetAddress.isLoopbackAddress()) {
+                        return inetAddress.getHostAddress().toString();
+                    }
+                }
+            }
+        } catch (SocketException ex) {
+            ex.printStackTrace();
+        }
+        return null;
+    }
+}

+ 57 - 0
app-base/src/main/java/com/efunbox/base/util/DeviceUuidFactory.java

@@ -0,0 +1,57 @@
+package com.efunbox.base.util;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+
+import com.orhanobut.logger.Logger;
+
+import java.io.UnsupportedEncodingException;
+import java.util.UUID;
+
+public class DeviceUuidFactory {
+
+    protected static UUID uuid;
+
+    public DeviceUuidFactory(Context context) {
+        if (uuid == null) {
+            synchronized (DeviceUuidFactory.class) {
+                if (uuid == null) {
+                    Logger.e("调用了DeviceUuidFactory:" + context.getClass().getName());
+                    try {
+                        final String androidId = Settings.Secure.getString(
+                                context.getContentResolver(), Settings.Secure.ANDROID_ID);
+                        if (!"9774d56d682e549c".equals(androidId)) {
+                            uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));
+                        } else {
+                            @SuppressLint("MissingPermission") final String deviceId = ((TelephonyManager) context
+                                    .getSystemService(Context.TELEPHONY_SERVICE))
+                                    .getDeviceId();
+                            uuid = deviceId != null ? UUID.nameUUIDFromBytes(deviceId
+                                    .getBytes("utf8")) : UUID.randomUUID();
+                            try {
+                            } catch (Exception e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    } catch (UnsupportedEncodingException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+//                SPutil.setPrefString(context, UUID_SPKEY, uuid.toString());
+            }
+        }
+    }
+
+
+    /**
+     * @return UUID
+     */
+    public static String getUuid() {
+        if (uuid == null || uuid.equals("") || uuid.equals("null")) {
+            return null;
+        }
+        return uuid.toString();
+    }
+}

+ 206 - 0
app-base/src/main/java/com/efunbox/base/util/EfunboxUtil.java

@@ -0,0 +1,206 @@
+package com.efunbox.base.util;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+
+import com.efunbox.base.MyApplication;
+
+import java.text.DecimalFormat;
+import java.util.List;
+
+public class EfunboxUtil {
+
+    /**
+     * 得到宽高缩放比
+     */
+    public static void initDesignSize(Context context) {
+        final DisplayMetrics appDisplayMetrics = context.getResources().getDisplayMetrics();
+        int designWidth = 1280;
+        int designHeight = 720;
+        float ds = 1.0f * designWidth / designHeight;
+        float ts = 1.0f * appDisplayMetrics.widthPixels / appDisplayMetrics.heightPixels;
+        if (ds >= ts) {
+            int targetWidth = appDisplayMetrics.widthPixels;
+            float designScale = 1.0f * targetWidth / designWidth;
+            setDisplay(context, designScale);
+        } else {
+            int targetHeight = appDisplayMetrics.heightPixels;
+            float designScale = 1.0f * targetHeight / designHeight;
+            setDisplay(context, designScale);
+        }
+    }
+
+    /**
+     * 设置dpi缩放比
+     */
+    public static void setDisplay(Context context, float designScale) {
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        displayMetrics.density = designScale;
+        displayMetrics.scaledDensity = displayMetrics.density;
+        displayMetrics.xdpi = displayMetrics.density * 160;
+        displayMetrics.ydpi = displayMetrics.xdpi;
+        displayMetrics.densityDpi = Float.valueOf(displayMetrics.xdpi).intValue();
+    }
+
+
+    /**
+     * 判断字符串是否符合手机号码格式
+     * 移动号段: 134,135,136,137,138,139,147,150,151,152,157,158,159,170,178,182,183,184,187,188
+     * 联通号段: 130,131,132,145,155,156,170,171,175,176,185,186
+     * 电信号段: 133,149,153,170,173,177,180,181,189
+     *
+     * @param mobileNums
+     * @return 待检测的字符串
+     */
+    public static boolean isMobileNO(String mobileNums) {
+        String telRegex = "^((13[0-9])|(15[^4])|(18[0-9])|(17[0-8])|(147,145))\\d{8}$";
+        if (TextUtils.isEmpty(mobileNums)) {
+            return false;
+        } else {
+            return mobileNums.matches(telRegex);
+        }
+    }
+
+
+    /**
+     * 根据中文年纪选择换成后台枚举
+     */
+    public static String getEnumByCNGrade(String cnGrade) {
+//                PRESCHOOL("学前"),
+//                PRIMARY_FIRST_GRADE("小学一年级"),
+//                PRIMARY_SECOND_GRADE("小学二年级"),
+//                PRIMARY_THREE_GRADE("小学三年级"),
+//                PRIMARY_SENIOR_GRADE("小学四年级");
+        if (TextUtils.isEmpty(cnGrade)) {
+            return "undefinde";
+        }
+        switch (cnGrade) {
+            case "一年级":
+                return "PRIMARY_FIRST_GRADE";
+            case "二年级":
+                return "PRIMARY_SECOND_GRADE";
+            case "三年级":
+                return "PRIMARY_THREE_GRADE";
+            case "四年级":
+                return "PRIMARY_SENIOR_GRADE";
+            case "学前":
+                return "PRESCHOOL";
+        }
+        return "undefinde";
+    }
+
+    public static String getCNGradeByEnum(String gradeEnum) {
+        if (TextUtils.isEmpty(gradeEnum)) {
+            return "undefinde";
+        }
+        switch (gradeEnum) {
+            case "PRIMARY_FIRST_GRADE":
+                return "一年级";
+            case "PRIMARY_SECOND_GRADE":
+                return "二年级";
+            case "PRIMARY_THREE_GRADE":
+                return "三年级";
+            case "PRIMARY_SENIOR_GRADE":
+                return "四年级";
+            case "PRESCHOOL":
+                return "学前";
+        }
+        return "undefinde";
+    }
+
+    public static int getIndexGradeByEnum(String gradeEnum) {
+        if (TextUtils.isEmpty(gradeEnum)) {
+            return -1;
+        }
+        switch (gradeEnum) {
+            case "PRIMARY_FIRST_GRADE":
+                return 0;
+            case "PRIMARY_SECOND_GRADE":
+                return 1;
+            case "PRIMARY_THREE_GRADE":
+                return 2;
+            case "PRIMARY_SENIOR_GRADE":
+                return 3;
+            case "PRESCHOOL":
+                return 4;
+        }
+        return -1;
+    }
+
+
+    public static String checkNum10000(String num) {
+        if (TextUtils.isEmpty(num)) {
+            return "0";
+        }
+        if (Float.valueOf(num) > 10000f) {
+            float a = Float.parseFloat(num) / 10000;
+            DecimalFormat decimalFormat = new DecimalFormat("#.#");
+            return String.valueOf(decimalFormat.format(a)) + "万";
+        }
+        return num;
+    }
+
+    /**
+     * 手机号用****号隐藏中间数字
+     *
+     * @param phone
+     * @return
+     */
+    public static String setPhoneMiddle(String phone) {
+        String phone_s = phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
+        return phone_s;
+    }
+
+
+    /**
+     * 邮箱用****号隐藏前面的字母
+     *
+     * @return
+     */
+    public static String setEmailMiddle(String email) {
+        String emails = email.replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4");
+        return emails;
+    }
+
+
+    /**
+     * 关闭应用
+     */
+    public static void killAppProcess(Context context, List<Activity> list) {
+        for (int i = 0; i < list.size(); i++) {
+            list.get(i).finish();
+        }
+        //注意:不能先杀掉主进程,否则逻辑代码无法继续执行,需先杀掉相关进程最后杀掉主进程
+        ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RunningAppProcessInfo> mList = mActivityManager.getRunningAppProcesses();
+        if (mList != null) {
+            for (ActivityManager.RunningAppProcessInfo runningAppProcessInfo : mList) {
+                if (runningAppProcessInfo.pid != android.os.Process.myPid()) {
+                    android.os.Process.killProcess(runningAppProcessInfo.pid);
+                }
+            }
+            android.os.Process.killProcess(android.os.Process.myPid());
+        }
+//        System.exit(0);
+    }
+
+
+    /**
+     * long转换成时间
+     * */
+    /**
+     * long转换成String时间
+     */
+    public static String getTime(int mss) {
+        int hours = (mss / (1000 * 60 * 60));
+        int minutes = (mss - hours * (1000 * 60 * 60)) / (1000 * 60);
+        int m = hours * 60 + minutes;
+        int seconds = (mss - hours * (1000 * 60 * 60) - minutes * (1000 * 60)) / 1000;
+        return String.format("%02d:%02d", m, seconds);
+    }
+
+
+}

+ 368 - 0
app-base/src/main/java/com/efunbox/base/util/GlideUtils.java

@@ -0,0 +1,368 @@
+package com.efunbox.base.util;
+
+import static com.bumptech.glide.load.DecodeFormat.PREFER_RGB_565;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.widget.ImageView;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.MultiTransformation;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.load.engine.GlideException;
+import com.bumptech.glide.load.resource.bitmap.CenterCrop;
+import com.bumptech.glide.request.RequestListener;
+import com.bumptech.glide.request.RequestOptions;
+import com.bumptech.glide.request.target.Target;
+import com.efunbox.base.R;
+
+import java.io.File;
+
+import jp.wasabeef.glide.transformations.BlurTransformation;
+import jp.wasabeef.glide.transformations.CropCircleWithBorderTransformation;
+import jp.wasabeef.glide.transformations.GrayscaleTransformation;
+import jp.wasabeef.glide.transformations.RoundedCornersTransformation;
+
+/**
+ * Glide工具类
+ */
+public class GlideUtils {
+    /*** 占位图 */
+    public static int placeholderImage = R.drawable.placeholder;
+    /*** 错误图 */
+    public static int errorImage = R.drawable.placeholder;
+
+    /**
+     * 加载图片(默认)
+     *
+     * @param context   上下文
+     * @param url       链接
+     * @param imageView ImageView
+     */
+    public static void loadImage(Context context, String url, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .placeholder(placeholderImage) //占位图
+                .error(errorImage);            //错误图
+        Glide.with(context).load(url).apply(options).into(imageView);
+
+    }
+
+
+    /**
+     * 加载图片(默认)
+     *
+     * @param context   上下文
+     * @param resid     资源id
+     * @param imageView ImageView
+     */
+    public static void loadImage(Context context, int resid, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .placeholder(placeholderImage) //占位图
+                .error(errorImage);            //错误图
+        Glide.with(context).load(resid).apply(options).into(imageView);
+
+    }
+
+    /**
+     * 指定图片大小;使用override()方法指定了一个图片的尺寸。
+     * Glide现在只会将图片加载成width*height像素的尺寸,而不会管你的ImageView的大小是多少了。
+     * 如果你想加载一张图片的原始尺寸的话,可以使用Target.SIZE_ORIGINAL关键字----override(Target.SIZE_ORIGINAL)
+     *
+     * @param context   上下文
+     * @param url       链接
+     * @param imageView ImageView
+     * @param width     图片宽度
+     * @param height    图片高度
+     */
+    public static void loadImageSize(Context context, String url, ImageView imageView, int width, int height) {
+        RequestOptions options = new RequestOptions()
+                .placeholder(placeholderImage) //占位图
+                .error(errorImage)             //错误图
+                .override(width, height)
+                .priority(Priority.HIGH);
+        Glide.with(context).load(url).apply(options).into(imageView);
+
+    }
+
+    /**
+     * 禁用内存缓存功能
+     * diskCacheStrategy()方法基本上就是Glide硬盘缓存功能的一切,它可以接收五种参数:
+     * <p>
+     * DiskCacheStrategy.NONE: 表示不缓存任何内容。
+     * DiskCacheStrategy.DATA: 表示只缓存原始图片。
+     * DiskCacheStrategy.RESOURCE: 表示只缓存转换过后的图片。
+     * DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
+     * DiskCacheStrategy.AUTOMATIC: 表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)。
+     *
+     * @param context   上下文
+     * @param url       链接
+     * @param imageView ImageView
+     */
+
+    public static void loadImageSizeKipMemoryCache(Context context, String url, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .placeholder(placeholderImage) //占位图
+                .error(errorImage)             //错误图
+                .format(PREFER_RGB_565)
+                .skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC);          //禁用掉Glide的内存缓存功能
+        Glide.with(context).load(url).apply(options).into(imageView);
+
+    }
+
+
+    public static void loadImageSizeKipMemoryCache(Context context, int id, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .placeholder(placeholderImage) //占位图
+                .error(errorImage)             //错误图
+                .format(PREFER_RGB_565)
+                .skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC);          //禁用掉Glide的内存缓存功能
+        Glide.with(context).load(id).apply(options).into(imageView);
+
+    }
+
+
+    /**
+     * 预先加载图片
+     * 在使用图片之前,预先把图片加载到缓存,调用了预加载之后,我们以后想再去加载这张图片就会非常快了,
+     * 因为Glide会直接从缓存当中去读取图片并显示出来
+     *
+     * @param context 上下文
+     * @param url     链接
+     */
+    public static void preloadImage(Context context, String url) {
+        Glide.with(context).load(url).preload();
+    }
+
+    /**
+     * 加载圆形图片
+     *
+     * @param context   上下文
+     * @param url       链接
+     * @param imageView ImageView
+     */
+    public static void loadCircleImage(Context context, String url, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .centerCrop()
+                .circleCrop()//设置圆形
+                .placeholder(placeholderImage) //占位图
+                .error(errorImage)             //错误图
+                .format(PREFER_RGB_565)
+                .priority(Priority.HIGH);
+        Glide.with(context).load(url).apply(options).into(imageView);
+    }
+
+    /**
+     * 加载圆形图片
+     *
+     * @param context   上下文
+     * @param resid     资源id
+     * @param imageView ImageView
+     */
+    public static void loadCircleImage(Context context, int resid, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .centerCrop()
+                .circleCrop()//设置圆形
+                .placeholder(placeholderImage) //占位图
+                .error(errorImage)             //错误图
+                .format(PREFER_RGB_565)
+                .priority(Priority.HIGH);
+
+        Glide.with(context).load(resid).apply(options).into(imageView);
+    }
+
+    /**
+     * 加载圆形带边框图片
+     *
+     * @param context     上下文
+     * @param url         链接
+     * @param imageView   ImageView
+     * @param borderSize  边框宽度 px
+     * @param borderColor 边框颜色
+     */
+    public static void loadCircleWithBorderImage(Context context, String url, ImageView imageView,
+                                                 float borderSize, @ColorInt int borderColor) {
+        RequestOptions options = RequestOptions.bitmapTransform(
+                        new MultiTransformation<>(
+                                new CenterCrop(),
+                                new CropCircleWithBorderTransformation(SizeUtils.px2dp(context, borderSize), borderColor)
+                        ))
+                .placeholder(placeholderImage) //占位图
+                .format(PREFER_RGB_565)
+                .error(errorImage);            //错误图
+        Glide.with(context).load(url).apply(options).into(imageView);
+    }
+
+    /**
+     * 加载圆角图片
+     *
+     * @param context   上下文
+     * @param url       链接
+     * @param imageView ImageView
+     * @param radius    圆角 px
+     */
+    public static void loadRoundCircleImage(Context context, String url, ImageView imageView,
+                                            float radius) {
+        RequestOptions options = RequestOptions.bitmapTransform(
+                        new MultiTransformation<>(
+                                new CenterCrop(),
+                                new RoundedCornersTransformation((int) radius, 0,
+                                        RoundedCornersTransformation.CornerType.ALL)
+                        ))
+                .placeholder(placeholderImage) //占位图
+                .format(PREFER_RGB_565)
+                .error(errorImage);            //错误图
+        Glide.with(context).load(url).apply(options).into(imageView);
+
+    }
+
+
+    /**
+     * 加载圆角图片-指定任意部分圆角(图片上、下、左、右四个角度任意定义)
+     *
+     * @param context   上下文
+     * @param url       链接
+     * @param imageView ImageView
+     * @param radius    圆角 px
+     * @param type      圆角位置
+     */
+    public static void loadRoundCircleImage(Context context, String url, ImageView imageView,
+                                            float radius, RoundedCornersTransformation.CornerType type) {
+        RequestOptions options = RequestOptions.bitmapTransform(
+                        new MultiTransformation<>(
+                                new CenterCrop(),
+                                new RoundedCornersTransformation(SizeUtils.px2dp(context, radius), 0, type)
+                        ))
+                .placeholder(placeholderImage) //占位图
+                .format(PREFER_RGB_565)
+                .error(errorImage);            //错误图
+        Glide.with(context).load(url).apply(options).into(imageView);
+    }
+
+    /**
+     * 加载模糊图片(自定义透明度)
+     *
+     * @param context   上下文
+     * @param url       链接
+     * @param imageView ImageView
+     * @param blur      模糊度,一般1-100够了,越大越模糊
+     */
+    public static void loadBlurImage(Context context, String url, ImageView imageView, int blur) {
+        RequestOptions options = RequestOptions.bitmapTransform(
+                        new MultiTransformation<>(
+                                new CenterCrop(),
+                                new BlurTransformation(blur)
+                        ))
+                .placeholder(placeholderImage) //占位图
+                .format(PREFER_RGB_565)
+                .error(errorImage);            //错误图
+        Glide.with(context).load(url).apply(options).into(imageView);
+    }
+
+    /**
+     * 加载模糊图片(自定义透明度)
+     *
+     * @param context   上下文
+     * @param url       链接
+     * @param imageView ImageView
+     * @param blur      模糊度,一般1-100够了,越大越模糊
+     * @param sampling  取样
+     */
+    public static void loadBlurImage(Context context, String url, ImageView imageView, int blur, int sampling) {
+        RequestOptions options = RequestOptions.bitmapTransform(
+                        new MultiTransformation<>(
+                                new CenterCrop(),
+                                new BlurTransformation(blur, sampling)
+                        ))
+                .placeholder(placeholderImage) //占位图
+                .format(PREFER_RGB_565)
+                .error(errorImage);            //错误图
+        Glide.with(context).load(url).apply(options).into(imageView);
+    }
+
+    /**
+     * 加载灰度(黑白)图片(自定义透明度)
+     *
+     * @param context   上下文
+     * @param url       链接
+     * @param imageView ImageView
+     */
+    public static void loadBlackImage(Context context, String url, ImageView imageView) {
+        RequestOptions options = RequestOptions.bitmapTransform(
+                        new MultiTransformation<>(
+                                new CenterCrop(),
+                                new GrayscaleTransformation()
+                        ))
+                .placeholder(placeholderImage) //占位图
+                .format(PREFER_RGB_565)
+                .error(errorImage);            //错误图
+        Glide.with(context).load(url).apply(options).into(imageView);
+    }
+
+    /**
+     * Glide.with(this).asGif()    //强制指定加载动态图片
+     * 如果加载的图片不是gif,则asGif()会报错, 当然,asGif()不写也是可以正常加载的。
+     * 加入了一个asBitmap()方法,这个方法的意思就是说这里只允许加载静态图片,不需要Glide去帮我们自动进行图片格式的判断了。
+     * 如果你传入的还是一张GIF图的话,Glide会展示这张GIF图的第一帧,而不会去播放它。
+     *
+     * @param context   上下文
+     * @param url       链接
+     * @param imageView ImageView
+     */
+    private void loadGif(Context context, String url, ImageView imageView) {
+        RequestOptions options = new RequestOptions()
+                .placeholder(placeholderImage) //占位图
+                .error(errorImage);            //错误图
+        Glide.with(context)
+                .load(url)
+                .apply(options)
+                .listener(new RequestListener<Drawable>() {
+                    @Override
+                    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+                        return false;
+                    }
+
+                    @Override
+                    public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+                        return false;
+                    }
+                })
+                .into(imageView);
+
+    }
+
+    /**
+     * 下载图片
+     * 在RequestListener的onResourceReady方法里面获取下载File图片
+     * new RequestListener<File>() {
+     * *@Override
+     * public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
+     * return false;
+     * }
+     * <p>
+     * *@Override
+     * public boolean onResourceReady(File resource, Object model, Target<File> target, DataSource dataSource, boolean isFirstResource) {
+     * //resource即为下载取得的图片File
+     * return false;
+     * }
+     * }
+     *
+     * @param context         上下文
+     * @param url             下载链接
+     * @param requestListener 下载监听
+     */
+    public static void downloadImage(final Context context, final String url, RequestListener<File> requestListener) {
+        Glide.with(context)
+                .downloadOnly()
+                .load(url)
+                .addListener(requestListener).preload();
+    }
+
+    public static void removeAll(Context context) {
+        Glide.get(context).clearDiskCache();
+    }
+}

+ 65 - 0
app-base/src/main/java/com/efunbox/base/util/HttpInterceptor.java

@@ -0,0 +1,65 @@
+package com.efunbox.base.util;
+
+import com.okhttplib.HttpInfo;
+import com.okhttplib.interceptor.ExceptionInterceptor;
+import com.okhttplib.interceptor.ResultInterceptor;
+
+/**
+ * Http拦截器
+ * 1、请求结果统一预处理拦截器
+ * 2、请求链路异常信息拦截器
+ */
+public class HttpInterceptor {
+
+    /**
+     * 请求结果统一预处理拦截器
+     * 该拦截器会对所有网络请求返回结果进行预处理并修改
+     */
+    public static com.okhttplib.interceptor.ResultInterceptor ResultInterceptor = new ResultInterceptor() {
+        @Override
+        public HttpInfo intercept(HttpInfo info) throws Exception {
+            //请求结果预处理:可以进行GSon过滤与解析
+            return info;
+        }
+    };
+
+    /**
+     * 请求链路异常信息拦截器
+     * 该拦截器会发送网络请求时链路异常信息进行拦截处理
+     */
+    public static com.okhttplib.interceptor.ExceptionInterceptor ExceptionInterceptor = new ExceptionInterceptor() {
+        @Override
+        public HttpInfo intercept(HttpInfo info) throws Exception {
+            //弹不出toast
+//            Toast.makeText(Consts.getmApplicAtion(), "Code:" + info.getRetCode() + "--Detail:" + info.getRetDetail(), Toast.LENGTH_LONG).show();
+            switch (info.getRetCode()) {
+                case HttpInfo.NonNetwork:
+//                    Toast.makeText(Consts.getmApplicAtion(), "网络中断", Toast.LENGTH_SHORT).show();
+                    info.setRetDetail("网络中断");
+                    break;
+                case HttpInfo.CheckURL:
+                    info.setRetDetail("网络地址错误[" + info.getNetCode() + "]");
+                    break;
+                case HttpInfo.ProtocolException:
+                    info.setRetDetail("协议类型错误[" + info.getNetCode() + "]");
+                    break;
+                case HttpInfo.CheckNet:
+//                    Toast.makeText(Consts.getmApplicAtion(), "请检查网络连接是否正常", Toast.LENGTH_SHORT).show();
+                    info.setRetDetail(info.getUrl() + "请检查网络连接是否正常[" + info.getNetCode() + "]");
+                    break;
+                case HttpInfo.ConnectionTimeOut:
+//                    CusToast.getInstance(Consts.getmApplicAtion()).show("手机号不正确", 2000);
+                    info.setRetDetail("连接超时");
+                    break;
+                case HttpInfo.WriteAndReadTimeOut:
+                    info.setRetDetail("读写超时");
+                    break;
+                case HttpInfo.ConnectionInterruption:
+                    info.setRetDetail("连接中断");
+                    break;
+            }
+            return info;
+        }
+    };
+}
+

+ 22 - 0
app-base/src/main/java/com/efunbox/base/util/Logger.java

@@ -0,0 +1,22 @@
+package com.efunbox.base.util;
+
+import android.app.Application;
+
+import com.orhanobut.logger.AndroidLogAdapter;
+import com.orhanobut.logger.PrettyFormatStrategy;
+
+public class Logger {
+
+    public static void init(Application application) {
+        com.orhanobut.logger.Logger.addLogAdapter(new AndroidLogAdapter(PrettyFormatStrategy.newBuilder()
+                .showThreadInfo(false)
+                .methodCount(3)
+//                .methodOffset(0)
+                .tag(application.getPackageName())
+                .build()));
+    }
+
+    public static void e(String message) {
+        com.orhanobut.logger.Logger.e(message);
+    }
+}

+ 191 - 0
app-base/src/main/java/com/efunbox/base/util/MMKVUtil.java

@@ -0,0 +1,191 @@
+package com.efunbox.base.util;
+
+
+import android.os.Parcelable;
+
+import com.tencent.mmkv.MMKV;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class MMKVUtil {
+
+    private MMKV mkv;
+
+    private MMKVUtil() {
+        mkv = MMKV.defaultMMKV();
+    }
+
+    public static MMKVUtil getInstance() {
+        return SingletonHolder.sInstance;
+    }
+
+    //静态内部类
+    private static class SingletonHolder {
+        private static final MMKVUtil sInstance = new MMKVUtil();
+    }
+
+    /**
+     * 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法
+     *
+     * @param key
+     * @param object
+     */
+    public void encode(String key, Object object) {
+        if (object instanceof String) {
+            mkv.encode(key, (String) object);
+        } else if (object instanceof Integer) {
+            mkv.encode(key, (Integer) object);
+        } else if (object instanceof Boolean) {
+            mkv.encode(key, (Boolean) object);
+        } else if (object instanceof Float) {
+            mkv.encode(key, (Float) object);
+        } else if (object instanceof Long) {
+            mkv.encode(key, (Long) object);
+        } else if (object instanceof Double) {
+            mkv.encode(key, (Double) object);
+        } else if (object instanceof byte[]) {
+            mkv.encode(key, (byte[]) object);
+        } else {
+            mkv.encode(key, object.toString());
+        }
+    }
+
+    public void encodeSet(String key, Set<String> sets) {
+        mkv.encode(key, sets);
+    }
+
+    public void encodeParcelable(String key, Parcelable obj) {
+        mkv.encode(key, obj);
+    }
+
+    /**
+     * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
+     *
+     * @param key
+     * @param defaultObject
+     * @return
+     */
+    public Object decode(String key, Object defaultObject) {
+        if (defaultObject instanceof String) {
+            return mkv.decodeString(key, (String) defaultObject);
+        } else if (defaultObject instanceof Integer) {
+            return mkv.decodeInt(key, (Integer) defaultObject);
+        } else if (defaultObject instanceof Boolean) {
+            return mkv.decodeBool(key, (Boolean) defaultObject);
+        } else if (defaultObject instanceof Float) {
+            return mkv.decodeFloat(key, (Float) defaultObject);
+        } else if (defaultObject instanceof Long) {
+            return mkv.decodeLong(key, (Long) defaultObject);
+        } else if (defaultObject instanceof Double) {
+            return mkv.decodeDouble(key, (Double) defaultObject);
+        } else if (defaultObject instanceof byte[]) {
+            return mkv.decodeBytes(key, (byte[]) defaultObject);
+        }
+        return defaultObject;
+    }
+
+    /**
+     * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
+     */
+    public Integer decodeInt(String key) {
+        return mkv.decodeInt(key, 0);
+    }
+
+    public Double decodeDouble(String key) {
+        return mkv.decodeDouble(key, 0.00);
+    }
+
+    public Long decodeLong(String key) {
+        return mkv.decodeLong(key, 0L);
+    }
+
+    public Boolean decodeBoolean(String key) {
+        return mkv.decodeBool(key, false);
+    }
+
+    public Float decodeFloat(String key) {
+        return mkv.decodeFloat(key, 0F);
+    }
+
+    public byte[] decodeBytes(String key) {
+        return mkv.decodeBytes(key);
+    }
+
+    public String decodeString(String key) {
+        return mkv.decodeString(key, "");
+    }
+
+    public Set<String> decodeStringSet(String key) {
+        return mkv.decodeStringSet(key, Collections.<String>emptySet());
+    }
+
+    public Parcelable decodeParcelable(String key, Class clz) {
+        return mkv.decodeParcelable(key, clz);
+    }
+
+    /**
+     * 移除某个key对
+     *
+     * @param key
+     */
+    public void removeKey(String key) {
+        mkv.removeValueForKey(key);
+    }
+
+    /**
+     * 移除多个key对
+     *
+     * @param key
+     */
+    public void removeKeys(String[] key) {
+        mkv.removeValuesForKeys(key);
+    }
+
+    /**
+     * 获取全部key对
+     */
+    public String[] getAllKeys() {
+        return mkv.allKeys();
+    }
+
+    /**
+     * 含有某个key
+     *
+     * @param key
+     * @return
+     */
+    public boolean hasKey(String key) {
+        return mkv.containsKey(key);
+    }
+
+    /**
+     * 含有某个key
+     *
+     * @param key
+     * @return
+     */
+    public boolean have(String key) {
+        return mkv.contains(key);
+    }
+
+    /**
+     * 清除所有key
+     */
+    public void clearAll() {
+        mkv.clearMemoryCache();
+        mkv.clearAll();
+        mkv.clear();
+    }
+
+
+    /**
+     * 获取操作对象
+     *
+     * @return
+     */
+    public MMKV getMkv() {
+        return mkv;
+    }
+
+}

+ 108 - 0
app-base/src/main/java/com/efunbox/base/util/OkHttpClient.java

@@ -0,0 +1,108 @@
+package com.efunbox.base.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Environment;
+
+import androidx.annotation.Nullable;
+
+import com.efunbox.base.callback.OkHttpCallback;
+import com.okhttplib.HttpInfo;
+import com.okhttplib.OkHttpUtil;
+import com.okhttplib.annotation.CacheType;
+import com.okhttplib.annotation.Encoding;
+import com.okhttplib.callback.BaseCallback;
+import com.okhttplib.cookie.PersistentCookieJar;
+import com.okhttplib.cookie.cache.SetCookieCache;
+import com.okhttplib.cookie.persistence.SharedPrefsCookiePersistor;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class OkHttpClient {
+    //初始化OKHTTP
+    private static String downloadFileDir = Environment.getExternalStorageDirectory().getPath() + "/okHttp_download/";
+    private static String cacheDir = Environment.getExternalStorageDirectory().getPath() + "/okHttp_cache";
+    private static Map<String, String> mHeaders;
+
+    public static void initOkHttpUtil(Context context) {
+        OkHttpUtil.init(context)
+                .setConnectTimeout(10)//连接超时时间
+                .setWriteTimeout(300)//写超时时间
+                .setReadTimeout(300)//读超时时间
+                .setMaxCacheSize(10 * 1024 * 1024)//缓存空间大小
+                .setCacheType(CacheType.FORCE_NETWORK)//缓存类型
+                .setHttpLogTAG("HttpLog")//设置请求日志标识
+                .setIsGzip(false)//Gzip压缩,需要服务端支持
+                .setShowHttpLog(true)//显示请求日志
+                .setShowLifecycleLog(false)//显示Activity销毁日志
+                .setRetryOnConnectionFailure(false)//失败后不自动重连
+                .setCachedDir(new File(cacheDir))//设置缓存目录
+                .setDownloadFileDir(downloadFileDir)//文件下载保存目录
+                .setResponseEncoding(Encoding.UTF_8)//设置全局的服务器响应编码
+                .setRequestEncoding(Encoding.UTF_8)//设置全局的请求参数编码
+//                    .setHttpsCertificate("12306.cer")//设置全局Https证书
+                .addResultInterceptor(HttpInterceptor.ResultInterceptor)//请求结果拦截器
+                .addExceptionInterceptor(HttpInterceptor.ExceptionInterceptor)//请求链路异常拦截器
+                .setCookieJar(new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context)))//持久化cookie
+                .build();
+        refHeader();
+    }
+
+    private static void refHeader() {
+        if (mHeaders == null) {
+            mHeaders = new HashMap<>();
+        }
+        mHeaders.clear();
+//        mHeaders.put("uid", Consts.getUID());
+//        mHeaders.put("channelCode", Consts.getUmengChannel());
+    }
+
+
+    public static void doGetAsync(Activity activity, HttpInfo.Builder info, BaseCallback callback) {
+        if (!info.build().getUrl().contains("posts/list")
+                && !info.build().getUrl().contains("/userRead/check")
+                && !info.build().getUrl().contains("/order/info/")) {
+        }
+        refHeader();
+        OkHttpUtil.getDefault(activity).doGetAsync(info.addHeads(mHeaders).build(), new OkHttpCallback(callback));
+    }
+
+    public static void doGetAsync(HttpInfo.Builder info, BaseCallback callback) {
+        if (!info.build().getUrl().contains("posts/list")
+                && !info.build().getUrl().contains("/userRead/check")
+                && !info.build().getUrl().contains("/order/info/")) {
+        }
+        refHeader();
+        OkHttpUtil.getDefault().doGetAsync(info.addHeads(mHeaders).build(), new OkHttpCallback(callback));
+    }
+
+
+    public static void doPostAsync(@Nullable Activity activity, HttpInfo.Builder info, BaseCallback callback) {
+        refHeader();
+        OkHttpUtil.getDefault(activity).doPostAsync(info.addHeads(mHeaders).build(), new OkHttpCallback(callback));
+
+    }
+
+    public static void doPostAsync(HttpInfo.Builder info, BaseCallback callback) {
+        refHeader();
+        OkHttpUtil.getDefault().doPostAsync(info.addHeads(mHeaders).build(), new OkHttpCallback(callback));
+    }
+
+    public static void doPutAsync(Activity activity, HttpInfo.Builder info, BaseCallback callback) {
+        OkHttpUtil.getDefault(activity).doPutAsync(info.addHeads(mHeaders).build(), new OkHttpCallback(callback));
+    }
+
+    public static void doDeleteAsync(Activity activity, HttpInfo.Builder info, BaseCallback callback) {
+        OkHttpUtil.getDefault(activity).doDeleteAsync(info.addHeads(mHeaders).build(), new OkHttpCallback(callback));
+    }
+
+    public static void doUploadFile(Activity activity, HttpInfo.Builder info) {
+        refHeader();
+        OkHttpUtil.getDefault(activity).doUploadFileAsync(info.addHeads(mHeaders).build());
+    }
+
+}
+
+

+ 148 - 0
app-base/src/main/java/com/efunbox/base/util/QRCodeUtil.java

@@ -0,0 +1,148 @@
+package com.efunbox.base.util;
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import java.util.Hashtable;
+
+public class QRCodeUtil {
+    private static int IMAGE_HALFWIDTH = 50;//宽度值,影响中间图片大小
+
+    /**
+     * 生成二维码,默认大小为500*500
+     *
+     * @return bitmap
+     */
+    public static Bitmap createQRCode() {
+        return createQRCode();
+    }
+
+    /**
+     * 生成二维码,默认大小为500*500
+     *
+     * @param text 需要生成二维码的文字、网址等
+     * @return bitmap
+     */
+    public static Bitmap createQRCode(String text) {
+        return createQRCode(text, 500);
+    }
+
+    /**
+     * 生成二维码
+     *
+     * @param text 需要生成二维码的文字、网址等
+     * @param size 需要生成二维码的大小()
+     * @return bitmap
+     */
+    public static Bitmap createQRCode(String text, int size) {
+        try {
+            Hashtable<EncodeHintType, String> hints = new Hashtable<>();
+            hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
+            hints.put(EncodeHintType.MARGIN, "1");
+            BitMatrix bitMatrix = new QRCodeWriter().encode(text,
+                    BarcodeFormat.QR_CODE, size, size, hints);
+            int[] pixels = new int[size * size];
+            for (int y = 0; y < size; y++) {
+                for (int x = 0; x < size; x++) {
+                    if (bitMatrix.get(x, y)) {
+                        pixels[y * size + x] = 0xff000000;
+                    } else {
+                        pixels[y * size + x] = 0xffffffff;
+                    }
+
+                }
+            }
+            Bitmap bitmap = Bitmap.createBitmap(size, size,
+                    Bitmap.Config.ARGB_8888);
+            bitmap.setPixels(pixels, 0, size, 0, 0, size, size);
+            return bitmap;
+        } catch (WriterException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 生成带logo的二维码,默认二维码的大小为500,logo为二维码的1/5
+     *
+     * @param text    需要生成二维码的文字、网址等
+     * @param mBitmap logo文件
+     * @return bitmap
+     */
+    public static Bitmap createQRCodeWithLogo(String text, Bitmap mBitmap) {
+        return createQRCodeWithLogo(text, 500, mBitmap);
+    }
+
+    /**
+     * 生成带logo的二维码,logo默认为二维码的1/5
+     *
+     * @param text    需要生成二维码的文字、网址等
+     * @param size    需要生成二维码的大小()
+     * @param mBitmap logo文件
+     * @return bitmap
+     */
+    public static Bitmap createQRCodeWithLogo(String text, int size, Bitmap mBitmap) {
+        try {
+            IMAGE_HALFWIDTH = size / 10;
+            Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
+            hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
+            /*
+             * 设置容错级别,默认为ErrorCorrectionLevel.L
+             * 因为中间加入logo所以建议你把容错级别调至H,否则可能会出现识别不了
+             */
+            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
+            BitMatrix bitMatrix = new QRCodeWriter().encode(text,
+                    BarcodeFormat.QR_CODE, size, size, hints);
+
+            int width = bitMatrix.getWidth();//矩阵高度
+            int height = bitMatrix.getHeight();//矩阵宽度
+            int halfW = width / 2;
+            int halfH = height / 2;
+
+            Matrix m = new Matrix();
+            float sx = (float) 2 * IMAGE_HALFWIDTH / mBitmap.getWidth();
+            float sy = (float) 2 * IMAGE_HALFWIDTH
+                    / mBitmap.getHeight();
+            m.setScale(sx, sy);
+            //设置缩放信息
+            //将logo图片按martix设置的信息缩放
+            mBitmap = Bitmap.createBitmap(mBitmap, 0, 0,
+                    mBitmap.getWidth(), mBitmap.getHeight(), m, false);
+
+            int[] pixels = new int[size * size];
+            for (int y = 0; y < size; y++) {
+                for (int x = 0; x < size; x++) {
+                    if (x > halfW - IMAGE_HALFWIDTH && x < halfW + IMAGE_HALFWIDTH
+                            && y > halfH - IMAGE_HALFWIDTH
+                            && y < halfH + IMAGE_HALFWIDTH) {
+                        //该位置用于存放图片信息
+                        //记录图片每个像素信息
+                        pixels[y * width + x] = mBitmap.getPixel(x - halfW
+                                + IMAGE_HALFWIDTH, y - halfH + IMAGE_HALFWIDTH);
+                    } else {
+                        if (bitMatrix.get(x, y)) {
+                            pixels[y * size + x] = 0xff000000;
+                        } else {
+                            pixels[y * size + x] = 0xffffffff;
+                        }
+                    }
+                }
+            }
+            Bitmap bitmap = Bitmap.createBitmap(size, size,
+                    Bitmap.Config.ARGB_8888);
+            bitmap.setPixels(pixels, 0, size, 0, 0, size, size);
+            return bitmap;
+        } catch (WriterException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+}

+ 155 - 0
app-base/src/main/java/com/efunbox/base/util/SizeUtils.java

@@ -0,0 +1,155 @@
+package com.efunbox.base.util;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
+
+public class SizeUtils {
+
+    private SizeUtils() {
+        throw new UnsupportedOperationException("u can't instantiate me...");
+    }
+
+    /**
+     * dp转px
+     *
+     * @param context 上下文
+     * @param dpValue dp值
+     * @return px值
+     */
+    public static int dp2px(Context context, float dpValue) {
+        final float scale = context.getResources().getDisplayMetrics().density;
+        return (int) (dpValue * scale + 0.5f);
+    }
+
+    /**
+     * px转dp
+     *
+     * @param context 上下文
+     * @param pxValue px值
+     * @return dp值
+     */
+    public static int px2dp(Context context, float pxValue) {
+        final float scale = context.getResources().getDisplayMetrics().density;
+        return (int) (pxValue / scale + 0.5f);
+    }
+
+    /**
+     * sp转px
+     *
+     * @param context 上下文
+     * @param spValue sp值
+     * @return px值
+     */
+    public static int sp2px(Context context, float spValue) {
+        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+        return (int) (spValue * fontScale + 0.5f);
+    }
+
+    /**
+     * px转sp
+     *
+     * @param context 上下文
+     * @param pxValue px值
+     * @return sp值
+     */
+    public static int px2sp(Context context, float pxValue) {
+        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+        return (int) (pxValue / fontScale + 0.5f);
+    }
+
+    /**
+     * 各种单位转换
+     * <p>该方法存在于TypedValue</p>
+     *
+     * @param unit    单位
+     * @param value   值
+     * @param metrics DisplayMetrics
+     * @return 转换结果
+     */
+    public static float applyDimension(int unit, float value, DisplayMetrics metrics) {
+        switch (unit) {
+            case TypedValue.COMPLEX_UNIT_PX:
+                return value;
+            case TypedValue.COMPLEX_UNIT_DIP:
+                return value * metrics.density;
+            case TypedValue.COMPLEX_UNIT_SP:
+                return value * metrics.scaledDensity;
+            case TypedValue.COMPLEX_UNIT_PT:
+                return value * metrics.xdpi * (1.0f / 72);
+            case TypedValue.COMPLEX_UNIT_IN:
+                return value * metrics.xdpi;
+            case TypedValue.COMPLEX_UNIT_MM:
+                return value * metrics.xdpi * (1.0f / 25.4f);
+        }
+        return 0;
+    }
+
+    /**
+     * 在onCreate()即可强行获取View的尺寸
+     * <p>需回调onGetSizeListener接口,在onGetSize中获取view宽高</p>
+     * <p>用法示例如下所示</p>
+     * <pre>
+     * SizeUtils.forceGetViewSize(view, new SizeUtils.onGetSizeListener() {
+     *     Override
+     *     public void onGetSize(View view) {
+     *         view.getWidth();
+     *     }
+     * });
+     * </pre>
+     *
+     * @param view     视图
+     * @param listener 监听器
+     */
+    public static void forceGetViewSize(final View view, final onGetSizeListener listener) {
+        view.post(new Runnable() {
+            @Override
+            public void run() {
+                if (listener != null) {
+                    listener.onGetSize(view);
+                }
+            }
+        });
+    }
+
+    /**
+     * 获取到View尺寸的监听
+     */
+    public interface onGetSizeListener {
+        void onGetSize(View view);
+    }
+
+    public static void setListener(onGetSizeListener listener) {
+        mListener = listener;
+    }
+
+    private static onGetSizeListener mListener;
+
+    /**
+     * ListView中提前测量View尺寸,如headerView
+     * <p>用的时候去掉注释拷贝到ListView中即可</p>
+     * <p>参照以下注释代码</p>
+     *
+     * @param view 视图
+     */
+    public static void measureViewInLV(View view) {
+        /*
+        ViewGroup.LayoutParams p = view.getLayoutParams();
+        if (p == null) {
+            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+        int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);
+        int height;
+        int tempHeight = p.height;
+        if (tempHeight > 0) {
+            height = MeasureSpec.makeMeasureSpec(tempHeight,
+                    MeasureSpec.EXACTLY);
+        } else {
+            height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        }
+        view.measure(width, height);
+        */
+    }
+}

+ 190 - 0
app-base/src/main/java/com/efunbox/base/util/TimeUtil.java

@@ -0,0 +1,190 @@
+package com.efunbox.base.util;
+
+import android.text.format.Time;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class TimeUtil {
+
+    /**
+     * 日期字符串转换Date实体
+     */
+    public static Date parseServerTime(String serverTime, String format) {
+        if (format == null || format.isEmpty()) {
+            format = "yyyy-MM-dd HH:mm:ss";
+        }
+        SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.CHINESE);
+        sdf.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
+        Date date = null;
+        try {
+            date = sdf.parse(serverTime);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return date;
+    }
+
+    /**
+     * 秒数转换成时分秒
+     */
+    public static String convertSecToTimeString(long lSeconds) {
+        long nHour = lSeconds / 3600;
+        long nMin = lSeconds % 3600;
+        long nSec = nMin % 60;
+        nMin = nMin / 60;
+
+        return String.format("%02d小时%02d分钟%02d秒", nHour, nMin, nSec);
+    }
+
+    /**
+     * Date对象获取时间字符串
+     */
+    public static String getDateStr(Date date, String format) {
+        if (format == null || format.isEmpty()) {
+            format = "yyyy-MM-dd HH:mm:ss";
+        }
+        SimpleDateFormat formatter = new SimpleDateFormat(format);
+        return formatter.format(date);
+    }
+
+
+    /**
+     * 时间戳转换日期格式字符串
+     */
+    public static String timeStamp2Date(long time, String format) {
+        if (format == null || format.isEmpty()) {
+            format = "yyyy-MM-dd HH:mm:ss";
+        }
+        SimpleDateFormat sdf = new SimpleDateFormat(format);
+        return sdf.format(new Date(time));
+    }
+
+
+    /**
+     * 日期格式字符串转换时间戳
+     */
+    public static String date2TimeStamp(String date, String format) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat(format);
+            return String.valueOf(sdf.parse(date).getTime() / 1000);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+
+    /**
+     * 获取某个日期前后N天的日期
+     *
+     * @param beginDate
+     * @param distanceDay 前后几天 如获取前7天日期则传-7即可;如果后7天则传7
+     * @param format      日期格式,默认"yyyy-MM-dd"
+     * @return
+     */
+    public static String getOldDateByDay(Date beginDate, int distanceDay, String format) {
+        if (format == null || format.isEmpty()) {
+            format = "yyyy-MM-dd";
+        }
+        SimpleDateFormat dft = new SimpleDateFormat(format);
+        Calendar date = Calendar.getInstance();
+        date.setTime(beginDate);
+        date.set(Calendar.DATE, date.get(Calendar.DATE) + distanceDay);
+        Date endDate = null;
+        try {
+            endDate = dft.parse(dft.format(date.getTime()));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return dft.format(endDate);
+    }
+
+    /**
+     * 获取前后几个月的日期
+     *
+     * @param beginDate
+     * @param distanceMonth
+     * @param format
+     * @return
+     */
+    public static String getOldDateByMonth(Date beginDate, int distanceMonth, String format) {
+        if (format == null || format.isEmpty()) {
+            format = "yyyy-MM-dd";
+        }
+        SimpleDateFormat dft = new SimpleDateFormat(format);
+        Calendar date = Calendar.getInstance();
+        date.setTime(beginDate);
+        date.set(Calendar.MONTH, date.get(Calendar.MONTH) + distanceMonth);
+        Date endDate = null;
+        try {
+            endDate = dft.parse(dft.format(date.getTime()));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return dft.format(endDate);
+    }
+
+
+    /**
+     * 判断一个时间是否在某个时间范围内
+     */
+    public static boolean isCurrentInTimeScope(int deadlineHour, int deadlineMin) {
+        boolean result;
+        // 1000 * 60 * 60 * 24
+        final long aDayInMillis = 86400000;
+        final long currentTimeMillis = System.currentTimeMillis();
+        //截止时间
+        Time deadlineTime = new Time();
+        deadlineTime.set(currentTimeMillis);
+        deadlineTime.hour = deadlineHour;
+        deadlineTime.minute = deadlineMin;
+        //当前时间
+        Time startTime = new Time();
+        startTime.set(currentTimeMillis);
+        //当前时间推后20分钟
+        Date d = new Date(currentTimeMillis);
+        long myTime = (d.getTime() / 1000) + 20 * 60;
+        d.setTime(myTime * 1000);
+        Time endTime = new Time();
+        endTime.set(myTime);
+        if (!startTime.before(endTime)) {
+            // 跨天的特殊情况(比如22:00-8:00)
+            startTime.set(startTime.toMillis(true) - aDayInMillis);
+            result = !deadlineTime.before(startTime) && !deadlineTime.after(endTime);
+            // startTime <= deadlineTime <=endTime
+            Time startTimeInThisDay = new Time();
+            startTimeInThisDay.set(startTime.toMillis(true) + aDayInMillis);
+            if (!deadlineTime.before(startTimeInThisDay)) {
+                result = true;
+            }
+        } else {
+            // 普通情况(比如 8:00 - 14:00)
+            result = !deadlineTime.before(startTime) && !deadlineTime.after(endTime);
+            // startTime <= deadlineTime <=endTime
+        }
+        return result;
+    }
+
+
+    /**
+     * 秒数转化为日期
+     */
+    public static String getDateFromSeconds(String seconds) {
+        if (seconds == null)
+            return " ";
+        else {
+            Date date = new Date();
+            try {
+                date.setTime(Long.parseLong(seconds) * 1000);
+            } catch (NumberFormatException nfe) {
+
+            }
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            return sdf.format(date);
+        }
+    }
+}

+ 6 - 0
app-base/src/main/java/com/efunbox/base/view/CrashActivityView.java

@@ -0,0 +1,6 @@
+package com.efunbox.base.view;
+
+import com.efunbox.base.View;
+
+public interface CrashActivityView extends View {
+}

Разлика између датотеке није приказан због своје велике величине
+ 30 - 0
app-base/src/main/res/drawable-v24/ic_launcher_foreground.xml


+ 8 - 0
app-base/src/main/res/drawable/activity_crash_divider_btn.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shape="rectangle">
+    <size
+            android:width="20dp"
+            android:height="20dp" />
+    <solid android:color="@android:color/transparent" />
+</shape>

BIN
app-base/src/main/res/drawable/activity_crash_icon.png


+ 170 - 0
app-base/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 6 - 0
app-base/src/main/res/drawable/list_divider_horizontal.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/white" />
+    <size android:height="1dp" />
+</shape>

BIN
app-base/src/main/res/drawable/placeholder.jpg


+ 19 - 0
app-base/src/main/res/drawable/theme_stroke_selector.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+
+    <item android:color="@color/colorPrimary" android:state_pressed="true">
+        <shape>
+            <stroke android:width="2dp" android:color="@color/colorAccent" />
+            <solid android:color="@color/white" />
+            <corners android:radius="2dp" />
+        </shape>
+    </item>
+
+    <item android:color="@color/white">
+        <shape>
+            <stroke android:width="2dp" android:color="@color/colorAccent" />
+            <corners android:radius="2dp" />
+        </shape>
+    </item>
+</selector>

BIN
app-base/src/main/res/drawable/toast_bg.9.png


+ 79 - 0
app-base/src/main/res/layout/activity_crash_dialog.xml

@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#70C1FF">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:orientation="vertical"
+        android:showDividers="middle">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/activity_crash_icon"></ImageView>
+
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="60dp"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:text="应用崩溃了~"
+            android:textColor="@color/white"
+            android:textSize="40dp"></TextView>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="10dp"
+            android:divider="@drawable/activity_crash_divider_btn"
+            android:gravity="center"
+            android:orientation="horizontal"
+            android:showDividers="middle">
+
+            <FrameLayout
+                android:id="@+id/activity_crash_dialog_restart"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@color/blue"
+                android:padding="8dp">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_gravity="center"
+                    android:gravity="center"
+                    android:text="给个机会"
+                    android:textColor="@color/white"
+                    android:textSize="30dp"></TextView>
+            </FrameLayout>
+
+            <FrameLayout
+                android:id="@+id/activity_crash_dialog_exitapp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@color/cadetblue"
+                android:padding="8dp">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_gravity="center"
+                    android:gravity="center"
+                    android:text="狠心退出"
+                    android:textColor="@color/white"
+                    android:textSize="30dp"></TextView>
+            </FrameLayout>
+        </LinearLayout>
+    </LinearLayout>
+
+</FrameLayout>

+ 66 - 0
app-base/src/main/res/layout/activity_safe_mode_warning.xml

@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#000"
+        android:gravity="center"
+        android:orientation="vertical"
+        tools:context=".support.DebugSafeModeTipActivity">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="#ec0000"
+            android:padding="10dp"
+            android:text="WARNING"
+            android:textColor="#fff"
+            android:textSize="30dp"
+            android:textStyle="bold" />
+
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="20dp"
+            android:gravity="center"
+            android:padding="15dp"
+            android:text="@string/debug_safe_mode"
+            android:textColor="#fff"
+            android:textSize="18dp"
+            android:textStyle="bold" />
+
+        <TextView
+            android:id="@+id/log"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="20dp"
+            android:background="@drawable/theme_stroke_selector"
+            android:clickable="true"
+            android:gravity="center"
+            android:padding="15dp"
+            android:text="查看历史崩溃记录"
+            android:textColor="@drawable/theme_stroke_selector"
+            android:textSize="18dp"
+            android:textStyle="bold" />
+
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="20dp"
+            android:gravity="center"
+            android:padding="12dp"
+            android:text="开发阶段建议弹出该Activity,以免遗漏bug。线上版本就不要展示该页面了。当看到该提示时就说明APP已经崩溃了,开发者可以手动杀进程,不要再让APP继续运行了"
+            android:textColor="#fff"
+            android:textSize="18dp"
+            android:textStyle="bold" />
+
+
+    </LinearLayout>
+</FrameLayout>

+ 21 - 0
app-base/src/main/res/layout/fragment_crash_log.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#000"
+        android:orientation="vertical">
+
+    <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:padding="10dp"
+            android:text="崩溃历史记录"
+            android:textColor="@color/white"
+            android:textSize="20dp" />
+
+    <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/recyclerview"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"></androidx.recyclerview.widget.RecyclerView>
+</LinearLayout>

+ 60 - 0
app-base/src/main/res/layout/item_crash_log.xml

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="#000"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:padding="10dp">
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+
+            android:textColor="@color/white"
+            android:textSize="18dp"
+            tools:text="标题" />
+
+        <TextView
+            android:id="@+id/copy"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@drawable/theme_stroke_selector"
+            android:padding="8dp"
+            android:text="复制"
+            android:textColor="@color/white"
+            android:textSize="14dp"
+            android:visibility="invisible" />
+
+    </LinearLayout>
+
+
+    <HorizontalScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="5dp"
+        android:background="@drawable/theme_stroke_selector">
+
+        <EditText
+            android:id="@+id/content"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@null"
+            android:editable="false"
+            android:inputType="none"
+            android:padding="10dp"
+            android:paddingLeft="20dp"
+            android:textColor="@color/white"
+            android:textIsSelectable="true"
+            android:textSize="13dp"
+            android:textStyle="bold"
+            tools:text="内容。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。" />
+    </HorizontalScrollView>
+</LinearLayout>

+ 19 - 0
app-base/src/main/res/layout/theme_stroke_selector.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+
+    <item android:color="@color/colorPrimary" android:state_pressed="true">
+        <shape>
+            <stroke android:width="2dp" android:color="@color/colorAccent" />
+            <solid android:color="@color/white" />
+            <corners android:radius="2dp" />
+        </shape>
+    </item>
+
+    <item android:color="@color/white">
+        <shape>
+            <stroke android:width="2dp" android:color="@color/colorAccent" />
+            <corners android:radius="2dp" />
+        </shape>
+    </item>
+</selector>

+ 37 - 0
app-base/src/main/res/layout/toast.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:background="@color/transparent"
+    android:gravity="center">
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:background="@color/transparent"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:background="@drawable/toast_bg" />
+
+        <TextView
+            android:id="@+id/tvToast"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:background="@color/transparent"
+            android:gravity="center"
+            android:lineSpacingMultiplier="1.5"
+            android:maxWidth="600dp"
+            android:padding="30dp"
+            android:text="2222222222222222222222222222222222222\n2\n2\n2\n22"
+            android:textColor="#AB722D"
+            android:textSize="27dp" />
+    </FrameLayout>
+</LinearLayout>

+ 5 - 0
app-base/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app-base/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
app-base/src/main/res/mipmap-hdpi/ic_launcher.webp


BIN
app-base/src/main/res/mipmap-hdpi/ic_launcher_round.webp


BIN
app-base/src/main/res/mipmap-mdpi/ic_launcher.webp


BIN
app-base/src/main/res/mipmap-mdpi/ic_launcher_round.webp


BIN
app-base/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
app-base/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


BIN
app-base/src/main/res/mipmap-xxhdpi/ic_launcher.webp


BIN
app-base/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp


BIN
app-base/src/main/res/mipmap-xxxhdpi/ic_launcher.webp


BIN
app-base/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp


+ 16 - 0
app-base/src/main/res/values-night/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.TestModelApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 290 - 0
app-base/src/main/res/values/colors.xml

@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="transparent">#00000000</color><!--透明色 -->
+
+    <color name="white">#FFFFFF</color><!--白色 -->
+
+    <color name="ivory">#FFFFF0</color><!--象牙色 -->
+
+    <color name="lightyellow">#FFFFE0</color><!--亮黄色 -->
+
+    <color name="yellow">#FFFF00</color><!--黄色 -->
+
+    <color name="snow">#FFFAFA</color><!--雪白色 -->
+
+    <color name="floralwhite">#FFFAF0</color><!--花白色 -->
+
+    <color name="lemonchiffon">#FFFACD</color><!--柠檬绸色 -->
+
+    <color name="cornsilk">#FFF8DC</color><!--米绸色 -->
+
+    <color name="seashell">#FFF5EE</color><!--海贝色 -->
+
+    <color name="lavenderblush">#FFF0F5</color><!--淡紫红 -->
+
+    <color name="papayawhip">#FFEFD5</color><!--番木色 -->
+
+    <color name="blanchedalmond">#FFEBCD</color><!--白杏色 -->
+
+    <color name="mistyrose">#FFE4E1</color><!--浅玫瑰色 -->
+
+    <color name="bisque">#FFE4C4</color><!--桔黄色 -->
+
+    <color name="moccasin">#FFE4B5</color><!--鹿皮色 -->
+
+    <color name="navajowhite">#FFDEAD</color><!--纳瓦白 -->
+
+    <color name="peachpuff">#FFDAB9</color><!--桃色 -->
+
+    <color name="gold">#FFD700</color><!--金色 -->
+
+    <color name="pink">#FFC0CB</color><!--粉红色 -->
+
+    <color name="lightpink">#FFB6C1</color><!--亮粉红色 -->
+
+    <color name="orange">#FFA500</color><!--橙色 -->
+
+    <color name="lightsalmon">#FFA07A</color><!--亮肉色 -->
+
+    <color name="darkorange">#FF8C00</color><!--暗桔黄色 -->
+
+    <color name="coral">#FF7F50</color><!--珊瑚色 -->
+
+    <color name="hotpink">#FF69B4</color><!--热粉红色 -->
+
+    <color name="tomato">#FF6347</color><!--西红柿色 -->
+
+    <color name="orangered">#FF4500</color><!--红橙色 -->
+
+    <color name="deeppink">#FF1493</color><!--深粉红色 -->
+
+    <color name="fuchsia">#FF00FF</color><!--紫红色 -->
+       
+    <color name="red">#FF0000</color><!--红色 -->
+
+    <color name="oldlace">#FDF5E6</color><!--老花色 -->
+
+    <color name="lightgoldenrodyellow">#FAFAD2</color><!--亮金黄色 -->
+
+    <color name="linen">#FAF0E6</color><!--亚麻色 -->
+
+    <color name="antiquewhite">#FAEBD7</color><!--古董白 -->
+
+    <color name="salmon">#FA8072</color><!--鲜肉色 -->
+
+    <color name="ghostwhite">#F8F8FF</color><!--幽灵白 -->
+
+    <color name="mintcream">#F5FFFA</color><!--薄荷色 -->
+
+    <color name="whitesmoke">#F5F5F5</color><!--烟白色 -->
+
+    <color name="beige">#F5F5DC</color><!--米色 -->
+
+    <color name="wheat">#F5DEB3</color><!--浅黄色 -->
+
+    <color name="sandybrown">#F4A460</color><!--沙褐色 -->
+
+    <color name="azure">#F0FFFF</color><!--天蓝色 -->
+
+    <color name="honeydew">#F0FFF0</color><!--蜜色 -->
+
+    <color name="aliceblue">#F0F8FF</color><!--艾利斯兰 -->
+
+    <color name="khaki">#F0E68C</color><!--黄褐色 -->
+
+    <color name="lightcoral">#F08080</color><!--亮珊瑚色 -->
+
+    <color name="palegoldenrod">#EEE8AA</color><!--苍麒麟色 -->
+
+    <color name="violet">#EE82EE</color><!--紫罗兰色 -->
+
+    <color name="darksalmon">#E9967A</color><!--暗肉色 -->
+
+    <color name="lavender">#E6E6FA</color><!--淡紫色 -->
+
+    <color name="lightcyan">#E0FFFF</color><!--亮青色 -->
+
+    <color name="burlywood">#DEB887</color><!--实木色 -->
+
+    <color name="plum">#DDA0DD</color><!--洋李色 -->
+
+    <color name="gainsboro">#DCDCDC</color><!--淡灰色 -->
+
+    <color name="crimson">#DC143C</color><!--暗深红色 -->
+
+    <color name="palevioletred">#DB7093</color><!--苍紫罗兰色 -->
+
+    <color name="goldenrod">#DAA520</color><!--金麒麟色 -->
+
+    <color name="orchid">#DA70D6</color><!--淡紫色 -->
+
+    <color name="thistle">#D8BFD8</color><!--蓟色 -->
+
+    <color name="lightgray">#D3D3D3</color><!--亮灰色 -->
+
+    <color name="tan">#D2B48C</color><!--茶色 -->
+
+    <color name="chocolate">#D2691E</color><!--巧可力色 -->
+
+    <color name="peru">#CD853F</color><!--秘鲁色 -->
+
+    <color name="indianred">#CD5C5C</color><!--印第安红 -->
+
+    <color name="mediumvioletred">#C71585</color><!--中紫罗兰色 -->
+
+    <color name="silver">#C0C0C0</color><!--银色 -->
+
+    <color name="darkkhaki">#BDB76B</color><!--暗黄褐色 -->
+
+    <color name="rosybrown">#BC8F8F</color><!--褐玫瑰红 -->
+
+    <color name="mediumorchid">#BA55D3</color><!--中粉紫色 -->
+
+    <color name="darkgoldenrod">#B8860B</color><!--暗金黄色 -->
+
+    <color name="firebrick">#B22222</color><!--火砖色 -->
+
+    <color name="powderblue">#B0E0E6</color><!--粉蓝色 -->
+
+    <color name="lightsteelblue">#B0C4DE</color><!--亮钢兰色 -->
+
+    <color name="paleturquoise">#AFEEEE</color><!--苍宝石绿 -->
+
+    <color name="greenyellow">#ADFF2F</color><!--黄绿色 -->
+
+    <color name="lightblue">#ADD8E6</color><!--亮蓝色 -->
+
+    <color name="darkgray">#A9A9A9</color><!--暗灰色 -->
+
+    <color name="brown">#A52A2A</color><!--褐色 -->
+
+    <color name="sienna">#A0522D</color><!--赭色 -->
+
+    <color name="darkorchid">#9932CC</color><!--暗紫色 -->
+
+    <color name="palegreen">#98FB98</color><!--苍绿色 -->
+
+    <color name="darkviolet">#9400D3</color><!--暗紫罗兰色 -->
+
+    <color name="mediumpurple">#9370DB</color><!--中紫色 -->
+
+    <color name="lightgreen">#90EE90</color><!--亮绿色 -->
+
+    <color name="darkseagreen">#8FBC8F</color><!--暗海兰色 -->
+
+    <color name="saddlebrown">#8B4513</color><!--重褐色 -->
+
+    <color name="darkmagenta">#8B008B</color><!--暗洋红 -->
+
+    <color name="darkred">#8B0000</color><!--暗红色 -->
+
+    <color name="blueviolet">#8A2BE2</color><!--紫罗兰蓝色 -->
+
+    <color name="lightskyblue">#87CEFA</color><!--亮天蓝色 -->
+
+    <color name="skyblue">#87CEEB</color><!--天蓝色 -->
+
+    <color name="gray">#808080</color><!--灰色 -->
+
+    <color name="olive">#808000</color><!--橄榄色 -->
+
+    <color name="purple">#800080</color><!--紫色 -->
+
+    <color name="maroon">#800000</color><!--粟色 -->
+
+    <color name="aquamarine">#7FFFD4</color><!--碧绿色 -->
+
+    <color name="chartreuse">#7FFF00</color><!--黄绿色 -->
+
+    <color name="lawngreen">#7CFC00</color><!--草绿色 -->
+
+    <color name="mediumslateblue">#7B68EE</color><!--中暗蓝色 -->
+
+    <color name="lightslategray">#778899</color><!--亮蓝灰 -->
+
+    <color name="slategray">#708090</color><!--灰石色 -->
+
+    <color name="olivedrab">#6B8E23</color><!--深绿褐色 -->
+
+    <color name="slateblue">#6A5ACD</color><!--石蓝色 -->
+
+    <color name="dimgray">#696969</color><!--暗灰色 -->
+
+    <color name="mediumaquamarine">#66CDAA</color><!--中绿色 -->
+
+    <color name="cornflowerblue">#6495ED</color><!--菊兰色 -->
+
+    <color name="cadetblue">#5F9EA0</color><!--军兰色 -->
+
+    <color name="darkolivegreen">#556B2F</color><!--暗橄榄绿 -->
+
+    <color name="indigo">#4B0082</color><!--靛青色 -->
+
+    <color name="mediumturquoise">#48D1CC</color><!--中绿宝石 -->
+
+    <color name="darkslateblue">#483D8B</color><!--暗灰蓝色 -->
+
+    <color name="steelblue">#4682B4</color><!--钢兰色 -->
+
+    <color name="royalblue">#4169E1</color><!--皇家蓝 -->
+
+    <color name="turquoise">#40E0D0</color><!--青绿色 -->
+
+    <color name="mediumseagreen">#3CB371</color><!--中海蓝 -->
+
+    <color name="limegreen">#32CD32</color><!--橙绿色 -->
+
+    <color name="darkslategray">#2F4F4F</color><!--暗瓦灰色 -->
+
+    <color name="seagreen">#2E8B57</color><!--海绿色 -->
+
+    <color name="forestgreen">#228B22</color><!--森林绿 -->
+
+    <color name="lightseagreen">#20B2AA</color><!--亮海蓝色 -->
+
+    <color name="dodgerblue">#1E90FF</color><!--闪兰色 -->
+
+    <color name="midnightblue">#191970</color><!--中灰兰色 -->
+
+    <color name="aqua">#00FFFF</color><!--浅绿色 -->
+
+    <color name="cyan">#00FFFF</color><!--青色 -->
+
+    <color name="springgreen">#00FF7F</color><!--春绿色 -->
+
+    <color name="lime">#00FF00</color><!--酸橙色 -->
+
+    <color name="mediumspringgreen">#00FA9A</color><!--中春绿色 -->
+
+    <color name="darkturquoise">#00CED1</color><!--暗宝石绿 -->
+
+    <color name="deepskyblue">#00BFFF</color><!--深天蓝色 -->
+
+    <color name="darkcyan">#008B8B</color><!--暗青色 -->
+
+    <color name="teal">#008080</color><!--水鸭色 -->
+
+    <color name="green">#008000</color><!--绿色 -->
+
+    <color name="darkgreen">#006400</color><!--暗绿色 -->
+
+    <color name="blue">#0000FF</color><!--蓝色 -->
+
+    <color name="mediumblue">#0000CD</color><!--中兰色 -->
+    <color name="darkblue">#00008B</color><!--暗蓝色 -->
+    <color name="navy">#000080</color><!--海军色 -->
+    <color name="black">#000000</color><!--黑色 -->
+    <color name="grassgreen">#99cc33</color><!--草绿色 -->
+    <color name="gray_cc">#cccccc</color><!--灰色cc -->
+    <color name="gray_8f">#8f8f8f</color><!--灰色8f -->
+    <color name="translucent_background">#90000000</color><!--半透明 -->
+    <color name="colorPrimary">#008577</color>
+    <color name="colorPrimaryDark">#00574B</color>
+    <color name="colorAccent">#D81B60</color>
+    <color name="fragment_recommend_user_color">#333333</color>
+</resources>

+ 7 - 0
app-base/src/main/res/values/strings.xml

@@ -0,0 +1,7 @@
+<resources>
+    <string name="app_name">app-base</string>
+    <string name="debug_safe_mode">APP已经崩溃了,请告知开发者!</string>
+    <string name="safe_mode_tips">已经进入安全模式</string>
+    <string name="safe_mode_excep_tips">捕获到导致崩溃的异常</string>
+    <string name="safe_mode_black_screen_tips">黑屏了</string>
+</resources>

+ 24 - 0
app-base/src/main/res/values/themes.xml

@@ -0,0 +1,24 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.TestModelApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+
+
+    <style name="FullScreen" parent="@style/Theme.AppCompat.Light">
+        <item name="windowNoTitle">true</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowActionBar">false</item>
+
+    </style>
+</resources>

+ 17 - 0
app-base/src/test/java/com/efunbox/base/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.efunbox.base;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 1 - 0
app-pay/.gitignore

@@ -0,0 +1 @@
+/build

+ 20 - 0
app-pay/build.gradle

@@ -0,0 +1,20 @@
+apply from: '../app.gradle'
+
+android {
+
+    defaultConfig {
+        resourcePrefix 'pay_'
+
+        if (!isRelease) {
+            applicationId "com.efunbox.pay"
+            versionCode 1
+            versionName "1.0"
+        }
+    }
+
+}
+
+dependencies {
+
+
+}

+ 21 - 0
app-pay/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

+ 26 - 0
app-pay/src/main/AndroidManifest.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.efunbox.pay">
+
+    <application android:allowBackup="true">
+        <activity
+            android:name="com.efunbox.pay.PayActivity"
+            android:exported="true"
+            android:launchMode="singleTask"
+            android:maxAspectRatio="2.1"
+            android:resizeableActivity="false"
+            android:screenOrientation="landscape"
+            android:splitMotionEvents="false"
+            android:theme="@style/FullScreen"></activity>
+        <activity
+            android:name="com.efunbox.pay.wxapi.WXPayEntryActivity"
+            android:exported="true"
+            android:launchMode="singleTask"
+            android:maxAspectRatio="2.1"
+            android:resizeableActivity="false"
+            android:screenOrientation="landscape"
+            android:splitMotionEvents="false"
+            android:theme="@style/FullScreen"></activity>
+    </application>
+
+</manifest>

+ 49 - 0
app-pay/src/main/AndroidManifestDebug.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.efunbox.pay">
+
+    <application
+        android:name="com.efunbox.pay.debug.MyApplication"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.TestModelApp">
+
+
+        <activity
+            android:name="com.efunbox.pay.PayActivity"
+            android:configChanges="screenLayout|screenSize|keyboardHidden|keyboard|orientation"
+            android:exported="true"
+            android:launchMode="singleTask"
+            android:maxAspectRatio="2.1"
+            android:resizeableActivity="false"
+            android:screenOrientation="landscape"
+            android:splitMotionEvents="false"
+            android:theme="@style/FullScreen"
+            android:windowSoftInputMode="adjustNothing|stateHidden">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+
+
+        </activity>
+        <activity
+            android:name="com.efunbox.pay.wxapi.WXPayEntryActivity"
+            android:configChanges="screenLayout|screenSize|keyboardHidden|keyboard|orientation"
+            android:exported="true"
+            android:launchMode="singleTask"
+            android:maxAspectRatio="2.1"
+            android:resizeableActivity="false"
+            android:screenOrientation="landscape"
+            android:splitMotionEvents="false"
+            android:theme="@style/FullScreen"
+            android:windowSoftInputMode="adjustNothing|stateHidden">
+
+        </activity>
+    </application>
+
+</manifest>

+ 22 - 0
app-pay/src/main/java/com/efunbox/pay/MyApplication.java

@@ -0,0 +1,22 @@
+package com.efunbox.pay;
+
+import android.app.Application;
+
+import com.efunbox.base.ModuleMediator;
+import com.efunbox.base.cusview.CusToast;
+import com.efunbox.base.util.BaseConsts;
+import com.efunbox.base.util.Logger;
+import com.efunbox.pay.util.PayConsts;
+import com.tencent.mm.opensdk.openapi.WXAPIFactory;
+
+public class MyApplication implements ModuleMediator.ModuleInitial {
+
+    @Override
+    public void initModule(Application application) {
+        if (BaseConsts.WECHAT_INFO == null) {
+            CusToast.getInstance(application).show("未设置BaseConsts.WECHAT_INFO", 1000);
+        } else {
+            PayConsts.mWeChatAPI = WXAPIFactory.createWXAPI(application, BaseConsts.WECHAT_INFO.getAPP_ID());
+        }
+    }
+}

+ 0 - 0
app-pay/src/main/java/com/efunbox/pay/PayActivity.java


Неке датотеке нису приказане због велике количине промена