ソースを参照

v1.0.0开发:工具库开发

#Suyghur 3 年 前
コミット
852133306b
67 ファイル変更5392 行追加34 行削除
  1. 1 1
      build.gradle
  2. 2 1
      demo/build.gradle
  3. BIN
      demo/libs/oaid_sdk_1.0.23.jar
  4. 15 0
      demo/src/main/AndroidManifest.xml
  5. 77 9
      demo/src/main/java/com/yyxx/support/demo/DemoActivity.kt
  6. 5 6
      demo/src/main/java/com/yyxx/support/demo/DemoApplication.kt
  7. 29 0
      demo/src/main/java/com/yyxx/support/demo/FloatView.kt
  8. 64 0
      demo/src/main/java/com/yyxx/support/demo/FloatViewService.kt
  9. 76 0
      demo/src/main/java/com/yyxx/support/demo/FloatViewServiceManager.kt
  10. BIN
      demo/src/main/jniLibs/arm64-v8a/libmmkv.so
  11. BIN
      demo/src/main/jniLibs/arm64-v8a/libsecsdk.so
  12. BIN
      demo/src/main/jniLibs/armeabi-v7a/libmmkv.so
  13. BIN
      demo/src/main/jniLibs/armeabi-v7a/libsecsdk.so
  14. BIN
      demo/src/main/jniLibs/armeabi/libmmkv.so
  15. BIN
      demo/src/main/jniLibs/armeabi/libsecsdk.so
  16. BIN
      demo/src/main/jniLibs/x86/libmmkv.so
  17. BIN
      demo/src/main/jniLibs/x86/libsecsdk.so
  18. BIN
      demo/src/main/jniLibs/x86_64/libmmkv.so
  19. BIN
      demo/src/main/jniLibs/x86_64/libsecsdk.so
  20. BIN
      demo/src/main/res/drawable-xhdpi/cpp.gif
  21. BIN
      demo/src/main/res/drawable-xhdpi/icon.png
  22. BIN
      demo/src/main/res/drawable-xhdpi/test.png
  23. 2 2
      library_support/build.gradle
  24. BIN
      library_support/libs/oaid_sdk_1.0.23.jar
  25. 9 2
      library_support/proguard-rules.pro
  26. 77 0
      library_support/src/main/java/cn/yyxx/support/AndroidBug5497Workaround.java
  27. 25 6
      library_support/src/main/java/cn/yyxx/support/AppUtils.java
  28. 206 0
      library_support/src/main/java/cn/yyxx/support/DensityUtils.java
  29. 156 0
      library_support/src/main/java/cn/yyxx/support/FileUtils.java
  30. 5 1
      library_support/src/main/java/cn/yyxx/support/HostModelUtils.java
  31. 20 0
      library_support/src/main/java/cn/yyxx/support/JsonUtils.java
  32. 186 0
      library_support/src/main/java/cn/yyxx/support/PropertiesUtils.java
  33. 956 0
      library_support/src/main/java/cn/yyxx/support/cache/bitmap/DiskLruCache.java
  34. 196 0
      library_support/src/main/java/cn/yyxx/support/cache/bitmap/StrictLineReader.java
  35. 76 0
      library_support/src/main/java/cn/yyxx/support/cache/bitmap/Util.java
  36. 257 1
      library_support/src/main/java/cn/yyxx/support/device/DeviceInfoUtils.java
  37. 234 0
      library_support/src/main/java/cn/yyxx/support/emulator/EmulatorFiles.java
  38. 130 0
      library_support/src/main/java/cn/yyxx/support/emulator/FindDebugger.java
  39. 247 0
      library_support/src/main/java/cn/yyxx/support/emulator/FindEmulator.java
  40. 15 0
      library_support/src/main/java/cn/yyxx/support/emulator/Property.java
  41. 51 0
      library_support/src/main/java/cn/yyxx/support/emulator/Utilities.java
  42. 91 0
      library_support/src/main/java/cn/yyxx/support/emulator/newfunc/CommandUtils.java
  43. 350 0
      library_support/src/main/java/cn/yyxx/support/emulator/newfunc/EmulatorCheck.java
  44. 133 0
      library_support/src/main/java/cn/yyxx/support/encryption/Base64Utils.java
  45. 102 0
      library_support/src/main/java/cn/yyxx/support/encryption/GzipUtils.java
  46. 149 0
      library_support/src/main/java/cn/yyxx/support/encryption/Md5Utils.java
  47. 35 0
      library_support/src/main/java/cn/yyxx/support/encryption/aes/AesCommon.java
  48. 54 0
      library_support/src/main/java/cn/yyxx/support/encryption/aes/AesDecrypt.java
  49. 54 0
      library_support/src/main/java/cn/yyxx/support/encryption/aes/AesEncrypt.java
  50. 44 0
      library_support/src/main/java/cn/yyxx/support/encryption/aes/AesUtils.java
  51. 525 0
      library_support/src/main/java/cn/yyxx/support/encryption/rsa/RsaUtils.java
  52. 45 0
      library_support/src/main/java/cn/yyxx/support/hawkeye/OwnDebugUtils.java
  53. 22 0
      library_support/src/main/java/cn/yyxx/support/hawkeye/ToastUtils.java
  54. 1 1
      library_support/src/main/java/cn/yyxx/support/msa/MsaDeviceIdsHandler.java
  55. 30 0
      library_support/src/main/java/cn/yyxx/support/scheduler/ScheduledWorker.java
  56. 168 0
      library_support/src/main/java/cn/yyxx/support/ui/DragViewLayout.java
  57. 272 0
      library_support/src/main/java/cn/yyxx/support/ui/GifView.java
  58. 196 0
      library_support/src/main/java/cn/yyxx/support/volley/VolleyBitmapCache.java
  59. 1 1
      library_support/src/main/java/cn/yyxx/support/volley/source/toolbox/BasicNetwork.java
  60. 1 1
      library_support/src/main/java/cn/yyxx/support/volley/source/toolbox/HurlStack.java
  61. 1 1
      library_support/src/main/java/cn/yyxx/support/volley/source/toolbox/JsonRequest.java
  62. 1 1
      library_support/src/main/java/cn/yyxx/support/volley/source/toolbox/StringRequest.java
  63. 0 0
      libs/android-support-v4.jar
  64. BIN
      libs/mmkv-static-1.2.8.jar
  65. BIN
      libs/oaid_sdk_1.0.25.jar
  66. BIN
      v4backup.zip
  67. BIN
      volley.zip

+ 1 - 1
build.gradle

@@ -14,7 +14,7 @@ buildscript {
     // minSdkVersion
     ext.MIN_SDK_VERSION = 16
     // targetSdkVersion
-    ext.TARGET_SDK_VERSION = 30
+    ext.TARGET_SDK_VERSION = 28
 
     repositories {
         google()

+ 2 - 1
demo/build.gradle

@@ -73,5 +73,6 @@ android {
 
 dependencies {
     implementation project(':library_support')
-    implementation files('libs/oaid_sdk_1.0.23.jar')
+    implementation files('../libs/oaid_sdk_1.0.25.jar')
+    implementation files('../libs/mmkv-static-1.2.8.jar')
 }

BIN
demo/libs/oaid_sdk_1.0.23.jar


+ 15 - 0
demo/src/main/AndroidManifest.xml

@@ -3,6 +3,12 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.yyxx.support.demo">
 
+    <!-- 网络权限 -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+
     <!-- msa sdk -->
     <uses-sdk tools:overrideLibrary="com.zui.opendeviceidlibrary" />
     <!-- asus -->
@@ -11,6 +17,12 @@
     <uses-permission android:name="freemme.permission.msa" />
     <!-- msa sdk -->
 
+    <!-- 适配Android 11 上软件包可见性被屏蔽导致无法跳转第三方应用 -->
+    <queries>
+        <package android:name="com.tencent.mm" />
+        <package android:name="com.tencent.mobileqq" />
+    </queries>
+
     <application
         android:name=".DemoApplication"
         android:allowBackup="true"
@@ -29,6 +41,9 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <service
+            android:name=".FloatViewService"
+            android:exported="true" />
     </application>
 
 </manifest>

+ 77 - 9
demo/src/main/java/com/yyxx/support/demo/DemoActivity.kt

@@ -1,15 +1,18 @@
 package com.yyxx.support.demo
 
 import android.app.Activity
+import android.graphics.Bitmap
 import android.os.Bundle
 import android.view.View
-import android.widget.Button
-import android.widget.LinearLayout
-import android.widget.ScrollView
-import android.widget.TextView
+import android.widget.*
+import cn.yyxx.support.AppUtils
 import cn.yyxx.support.device.DeviceInfoUtils
 import cn.yyxx.support.hawkeye.LogUtils
 import cn.yyxx.support.msa.MsaDeviceIdsHandler
+import cn.yyxx.support.volley.VolleySingleton
+import cn.yyxx.support.volley.source.Response
+import cn.yyxx.support.volley.source.toolbox.ImageRequest
+import com.tencent.mmkv.MMKV
 
 /**
  * @author #Suyghur.
@@ -18,13 +21,18 @@ import cn.yyxx.support.msa.MsaDeviceIdsHandler
 class DemoActivity : Activity(), View.OnClickListener {
 
     private val events = mutableListOf(
-            Item(0, "00 获取MSA DeviceIds"),
-            Item(1, "111111"),
-            Item(2, "222222"),
-            Item(3, "333333")
+        Item(0, "00 获取MSA DeviceIds"),
+        Item(1, "是否安装微信"),
+        Item(2, "获取网络图片"),
+        Item(3, "显示浮标"),
+        Item(4, "隐藏浮标"),
+        Item(5, "MMKV测试 encode"),
+        Item(6, "MMKV测试 decode")
     )
 
     private lateinit var textView: TextView
+    private lateinit var imgView: ImageView
+    private lateinit var demoFloatView: FloatView
     private val sb = StringBuilder()
     private var hasReadIds = false
 
@@ -48,13 +56,18 @@ class DemoActivity : Activity(), View.OnClickListener {
                 layout.addView(this)
             }
         }
+//        val gifView = GifView(this)
+//        gifView.setGifResource(ResUtils.getResId(this, "test", "drawable"))
+//        layout.addView(gifView)
+        imgView = ImageView(this)
+        layout.addView(imgView)
         val scrollView = ScrollView(this)
         scrollView.addView(layout)
         setContentView(scrollView)
+        FloatViewServiceManager.getInstance().init(this)
     }
 
     private fun initDeviceInfo() {
-        LogUtils.d("initDeviceInfo")
         sb.append("Android ID : ").append(DeviceInfoUtils.getAndroidDeviceId(this)).append("\n")
         sb.append("手机制造商 : ").append(DeviceInfoUtils.getDeviceManufacturer()).append("\n")
         sb.append("手机品牌 : ").append(DeviceInfoUtils.getDeviceBrand()).append("\n")
@@ -66,6 +79,22 @@ class DemoActivity : Activity(), View.OnClickListener {
         textView.text = sb.toString()
     }
 
+    private fun requestImg() {
+        val url = "https://i.loli.net/2019/09/16/oMtIUKWiavEbFPw.jpg"
+        val request = object : ImageRequest(url,
+            Response.Listener<Bitmap> {
+                imgView.setImageBitmap(it)
+            },
+            0, 0, Bitmap.Config.ARGB_8888,
+            Response.ErrorListener {
+                LogUtils.e("onError")
+            }
+        ) {
+
+        }
+        VolleySingleton.getInstance(this.applicationContext).addToRequestQueue(this.applicationContext, request)
+    }
+
     override fun onClick(v: View?) {
         v?.apply {
             when (tag as Int) {
@@ -78,7 +107,46 @@ class DemoActivity : Activity(), View.OnClickListener {
                         hasReadIds = true
                     }
                 }
+                1 -> {
+                    LogUtils.d("aaaaa : ${AppUtils.isPackageInstalled(this@DemoActivity, "com.tencent.mm")}")
+                }
+                2 -> requestImg()
+                3 -> FloatViewServiceManager.getInstance().attach()
+                4 -> FloatViewServiceManager.getInstance().detach()
+                5 -> {
+                    MMKV.defaultMMKV()!!.encode("test", "yyxx support")
+                    MMKV.defaultMMKV()!!.encode("test1", "yyxx support1")
+                    MMKV.defaultMMKV()!!.encode("test2", "yyxx support2")
+                    MMKV.defaultMMKV()!!.encode("test3", "yyxx support3")
+
+                }
+                6 -> {
+//                    sb.append("MMKV decode : ").append(MMKV.defaultMMKV()!!.decodeString("test"))
+//                    textView.text = sb.toString()
+                    val keys = MMKV.defaultMMKV()!!.allKeys()
+                    keys?.apply {
+                        for (key in this) {
+                            LogUtils.i(key)
+                        }
+                    }
+                }
             }
         }
     }
+
+    override fun onResume() {
+        super.onResume()
+        FloatViewServiceManager.getInstance().attach()
+    }
+
+    override fun onPause() {
+        super.onPause()
+        FloatViewServiceManager.getInstance().detach()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        FloatViewServiceManager.getInstance().release()
+
+    }
 }

+ 5 - 6
demo/src/main/java/com/yyxx/support/demo/DemoApplication.kt

@@ -4,6 +4,7 @@ import android.app.Application
 import android.content.Context
 import cn.yyxx.support.hawkeye.LogUtils
 import cn.yyxx.support.msa.MsaDeviceIdsHandler
+import com.tencent.mmkv.MMKV
 
 /**
  * @author #Suyghur.
@@ -13,17 +14,15 @@ class DemoApplication : Application() {
 
     override fun attachBaseContext(base: Context?) {
         super.attachBaseContext(base)
-        MsaDeviceIdsHandler.initMsaDeviceIds(this) { code, msg, ids ->
+        MsaDeviceIdsHandler.initMsaDeviceIds(this) { code, msg, _ ->
             LogUtils.i("initMsaDeviceIds code : $code , msg : $msg")
-//            if (code == 0) {
-//                LogUtils.d("oaid : ${ids["oaid"]}")
-//                LogUtils.d("vaid : ${ids["vaid"]}")
-//                LogUtils.d("aaid : ${ids["aaid"]}")
-//            }
         }
+
     }
 
     override fun onCreate() {
         super.onCreate()
+        val dir = MMKV.initialize(this)
+        LogUtils.i("mmkv dir : $dir")
     }
 }

+ 29 - 0
demo/src/main/java/com/yyxx/support/demo/FloatView.kt

@@ -0,0 +1,29 @@
+package com.yyxx.support.demo
+
+import android.app.Activity
+import android.content.Context
+import android.view.ViewGroup
+import android.widget.ImageView
+import cn.yyxx.support.ResUtils
+import cn.yyxx.support.hawkeye.LogUtils
+import cn.yyxx.support.ui.DragViewLayout
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/05/12
+ */
+class FloatView(context: Context) : DragViewLayout(context) {
+
+    private var imageView: ImageView
+
+    init {
+        isClickable = true
+        imageView = ImageView(context)
+        imageView.setBackgroundResource(ResUtils.getResId(context, "icon", "drawable"))
+        imageView.setOnClickListener {
+            LogUtils.d("点击了DemoFloatView")
+        }
+        val params = ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+        addView(imageView, params)
+    }
+}

+ 64 - 0
demo/src/main/java/com/yyxx/support/demo/FloatViewService.kt

@@ -0,0 +1,64 @@
+package com.yyxx.support.demo
+
+import android.app.Activity
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.Binder
+import android.os.IBinder
+import cn.yyxx.support.hawkeye.LogUtils
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/05/12
+ */
+class FloatViewService : Service() {
+
+    private lateinit var activity: Activity
+    private var floatView: FloatView? = null
+
+    override fun onCreate() {
+        super.onCreate()
+    }
+
+
+    fun initFloatView(activity:Activity) {
+        this.activity = activity
+        LogUtils.d("init")
+    }
+
+    fun show() {
+        if (floatView == null) {
+            floatView = FloatView(activity)
+        }
+        if (floatView == null) {
+            LogUtils.d("aaaa")
+        }
+        LogUtils.d("show")
+        floatView?.show()
+    }
+
+    fun hide() {
+        floatView?.hide()
+    }
+
+    fun release() {
+        floatView?.release()
+    }
+
+
+    override fun onBind(intent: Intent): IBinder {
+        return FloatViewServiceBinder()
+    }
+
+    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+        return super.onStartCommand(intent, flags, startId)
+    }
+
+    inner class FloatViewServiceBinder : Binder() {
+
+        fun getService(): FloatViewService {
+            return this@FloatViewService
+        }
+    }
+}

+ 76 - 0
demo/src/main/java/com/yyxx/support/demo/FloatViewServiceManager.kt

@@ -0,0 +1,76 @@
+package com.yyxx.support.demo
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import com.yyxx.support.demo.FloatViewService.FloatViewServiceBinder
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/05/12
+ */
+class FloatViewServiceManager {
+
+    private var mService: FloatViewService? = null
+
+    private var mActivity: Activity? = null
+    private var mIntent: Intent? = null
+    private var isBindService = false
+
+    private val serviceConnection = object : ServiceConnection {
+        override fun onServiceDisconnected(name: ComponentName) {
+            TODO("Not yet implemented")
+        }
+
+        override fun onServiceConnected(name: ComponentName?, service: IBinder) {
+            mService = (service as FloatViewServiceBinder).getService()
+            mService?.initFloatView(mActivity!!)
+        }
+
+    }
+
+    fun init(activity: Activity) {
+        this.mActivity = activity
+        if (mService == null) {
+            mIntent = Intent(activity.applicationContext, FloatViewService::class.java)
+            activity.applicationContext.startService(mIntent)
+            activity.applicationContext.bindService(mIntent, serviceConnection, Context.BIND_AUTO_CREATE)
+            isBindService = true
+        }
+    }
+
+    fun attach() {
+        mService?.show()
+    }
+
+    fun detach() {
+        mService?.hide()
+    }
+
+    fun release() {
+        mService?.release()
+        if (isBindService) {
+            mActivity?.apply {
+                applicationContext.unbindService(serviceConnection)
+                applicationContext.stopService(mIntent)
+            }
+
+        }
+        mIntent = null
+        mActivity = null
+    }
+
+    companion object {
+        fun getInstance(): FloatViewServiceManager {
+            return FloatViewServiceManagerHolder.INSTANCE
+        }
+
+        private object FloatViewServiceManagerHolder {
+            val INSTANCE = FloatViewServiceManager()
+        }
+
+    }
+}

BIN
demo/src/main/jniLibs/arm64-v8a/libmmkv.so


BIN
demo/src/main/jniLibs/arm64-v8a/libsecsdk.so


BIN
demo/src/main/jniLibs/armeabi-v7a/libmmkv.so


BIN
demo/src/main/jniLibs/armeabi-v7a/libsecsdk.so


BIN
demo/src/main/jniLibs/armeabi/libmmkv.so


BIN
demo/src/main/jniLibs/armeabi/libsecsdk.so


BIN
demo/src/main/jniLibs/x86/libmmkv.so


BIN
demo/src/main/jniLibs/x86/libsecsdk.so


BIN
demo/src/main/jniLibs/x86_64/libmmkv.so


BIN
demo/src/main/jniLibs/x86_64/libsecsdk.so


BIN
demo/src/main/res/drawable-xhdpi/cpp.gif


BIN
demo/src/main/res/drawable-xhdpi/icon.png


BIN
demo/src/main/res/drawable-xhdpi/test.png


+ 2 - 2
library_support/build.gradle

@@ -41,8 +41,8 @@ android {
 }
 
 dependencies {
-    compileOnly files('libs/android-support-v4.jar')
-    compileOnly files('libs/oaid_sdk_1.0.23.jar')
+    compileOnly files('../libs/oaid_sdk_1.0.25.jar')
+    compileOnly files('../libs/android-support-v4.jar')
 }
 
 apply from: 'buildJar.gradle'

BIN
library_support/libs/oaid_sdk_1.0.23.jar


+ 9 - 2
library_support/proguard-rules.pro

@@ -111,13 +111,20 @@
 
 -keep class **JNI* {*;}
 
--keep class android.support.annotation.GuardedBy{*;}
+-keep class android.support.annotation.**{*;}
+-keep class cn.yyxx.support.AndroidBug5497Workaround{ public <fields>;public <methods>;}
 -keep class cn.yyxx.support.AppUtils{ public <fields>;public <methods>;}
 -keep class cn.yyxx.support.BeanUtils{ public <fields>;public <methods>;}
 -keep class cn.yyxx.support.device.DeviceInfoUtils{ public <fields>;public <methods>;}
+-keep class cn.yyxx.support.DensityUtils{ public <fields>;public <methods>;}
+-keep class cn.yyxx.support.JsonUtils{ public <fields>;public <methods>;}
+-keep class cn.yyxx.support.ui.**{ public <fields>; public <methods>;}
+-keep class cn.yyxx.support.encryption.**{ public <fields>; public <methods>;}
 -keep class cn.yyxx.support.FileUtils{ public <fields>;public <methods>;}
 -keep class cn.yyxx.support.HostModelUtils{ public <fields>;public <methods>;}
+-keep class cn.yyxx.support.PropertiesUtils{ public <fields>;public <methods>;}
 -keep class cn.yyxx.support.ResUtils{ public <fields>;public <methods>;}
+-keep class cn.yyxx.support.scheduler.**{ public <fields>;public <methods>;}
 -keep class cn.yyxx.support.msa.**{ public <fields>; public <methods>;}
 -keep class cn.yyxx.support.hawkeye.**{ public <fields>; public <methods>;}
--keep class cn.yyxx.support.volley.**{  public <fields>; public <methods>;}
+-keep class cn.yyxx.support.volley.**{ *;}

+ 77 - 0
library_support/src/main/java/cn/yyxx/support/AndroidBug5497Workaround.java

@@ -0,0 +1,77 @@
+package cn.yyxx.support;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+
+/**
+ * Author #Suyghur.
+ * Created on 2020/11/12
+ */
+public class AndroidBug5497Workaround {
+
+    public static void assistActivity(Activity activity) {
+        new AndroidBug5497Workaround(activity);
+    }
+
+    private Activity activity;
+    private View mChildOfContent;
+    private int usableHeightPrevious;
+    private FrameLayout.LayoutParams frameLayoutParams;
+
+    private AndroidBug5497Workaround(Activity activity) {
+        this.activity = activity;
+        FrameLayout content = activity.findViewById(android.R.id.content);
+        mChildOfContent = content.getChildAt(0);
+        mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+            public void onGlobalLayout() {
+                possiblyResizeChildOfContent();
+            }
+        });
+        frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
+    }
+
+    private void possiblyResizeChildOfContent() {
+        int usableHeightNow = computeUsableHeight();
+        if (usableHeightNow != usableHeightPrevious) {
+            int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
+
+            //这个判断是为了解决19之前的版本不支持沉浸式状态栏导致布局显示不完全的问题
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+                Rect frame = new Rect();
+                activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
+                int statusBarHeight = frame.top;
+                usableHeightSansKeyboard -= statusBarHeight;
+            }
+            int heightDifference = usableHeightSansKeyboard - usableHeightNow;
+            if (heightDifference > (usableHeightSansKeyboard / 4)) {
+                // keyboard probably just became visible
+                frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
+            } else {
+                // keyboard probably just became hidden
+                frameLayoutParams.height = usableHeightSansKeyboard;
+            }
+            mChildOfContent.requestLayout();
+            usableHeightPrevious = usableHeightNow;
+        }
+    }
+
+    private int computeUsableHeight() {
+        Rect frame = new Rect();
+        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
+        int statusBarHeight = frame.top;
+
+        Rect r = new Rect();
+        mChildOfContent.getWindowVisibleDisplayFrame(r);
+
+        //这个判断是为了解决19之后的版本在弹出软键盘时,键盘和推上去的布局(adjustResize)之间有黑色区域的问题
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            return (r.bottom - r.top) + statusBarHeight;
+        }
+
+        return (r.bottom - r.top);
+    }
+}

+ 25 - 6
library_support/src/main/java/cn/yyxx/support/AppUtils.java

@@ -1,9 +1,14 @@
 package cn.yyxx.support;
 
+import android.app.ActivityManager;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.text.TextUtils;
+import android.widget.TextView;
+
+import java.util.List;
 
 /**
  * @author #Suyghur.
@@ -79,18 +84,32 @@ public class AppUtils {
         return "1";
     }
 
+    public static String getProcessName(Context context) {
+        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
+        if (runningApps == null) {
+            return "";
+        }
+        for (ActivityManager.RunningAppProcessInfo proInfo : runningApps) {
+            if (proInfo.pid == android.os.Process.myPid()) {
+                if (proInfo.processName != null) {
+                    return proInfo.processName;
+                }
+            }
+        }
+        return "";
+    }
+
     /**
      * 判断是否已安装
      */
     public static boolean isPackageInstalled(Context context, String pkgName) {
+        if (TextUtils.isEmpty(pkgName)) {
+            return false;
+        }
         try {
-            if (TextUtils.isEmpty(pkgName)) {
-                return false;
-            } else {
-                context.getPackageManager().getPackageInfo(pkgName.trim(), PackageManager.GET_GIDS);
-            }
+            context.getPackageManager().getPackageInfo(pkgName, PackageManager.GET_GIDS);
         } catch (Exception e) {
-            e.printStackTrace();
             return false;
         }
         return true;

+ 206 - 0
library_support/src/main/java/cn/yyxx/support/DensityUtils.java

@@ -0,0 +1,206 @@
+package cn.yyxx.support;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.WindowManager;
+
+import java.text.DecimalFormat;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class DensityUtils {
+    private DensityUtils() {
+        /* cannot be instantiated */
+        throw new UnsupportedOperationException("cannot be instantiated");
+    }
+
+    /**
+     * 根据手机的分辨率从 dip 的单位 转成为 px(像素)
+     */
+    public static int dip2px(Context context, float dpValue) {
+        final float scale = context.getResources().getDisplayMetrics().density;
+        return (int) (dpValue * scale + 0.5f);
+    }
+
+    /**
+     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
+     */
+    public static int px2dip(Context context, float pxValue) {
+        final float scale = context.getResources().getDisplayMetrics().density;
+        return (int) (pxValue / scale + 0.5f);
+    }
+
+    /**
+     * 将px值转换为sp值,保证文字大小不变
+     */
+    public static int px2sp(Context context, float pxValue) {
+        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+        return (int) (pxValue / fontScale + 0.5f);
+    }
+
+    /**
+     * 将sp值转换为px值,保证文字大小不变
+     */
+    public static int sp2px(Context context, float spValue) {
+        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+        return (int) (spValue * fontScale + 0.5f);
+    }
+
+    public static String convertSize2String(int size) {
+        String result = null;
+        int K = 1024;
+        int M = K * 1024;
+        int G = M * 1024;
+        DecimalFormat fmt = new DecimalFormat("#.##");
+        if (size / K < 1) {
+            result = size + "K";
+        } else if (size / M < 1) {
+            result = fmt.format(size * 1.0 / K) + "M";
+        } else if (size / G < 1) {
+            result = fmt.format(size * 1.0 / M) + "G";
+        } else {
+            result = fmt.format(size * 1.0 * K / G) + "G";
+        }
+        return result;
+    }
+
+    /**
+     * 获取屏幕分辨率,方法体过时
+     *
+     * @param activity
+     * @return
+     */
+    @Deprecated
+    public static int[] getHeightAndWidth(Context activity) {
+        WindowManager wm = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
+        DisplayMetrics metrics = new DisplayMetrics();
+        wm.getDefaultDisplay().getMetrics(metrics);
+        int[] disp = new int[2];
+
+        disp[0] = metrics.widthPixels; // 当前屏幕像素
+        disp[1] = metrics.heightPixels; // 当前屏幕像素
+
+        return disp;
+    }
+
+    /**
+     * 获取屏幕分辨率
+     *
+     * @param activity
+     * @return
+     */
+    public static int[] getHeigthAndWidth(Activity activity) {
+        Display defaultDisplay = activity.getWindowManager().getDefaultDisplay();
+        Point point = new Point();
+        defaultDisplay.getSize(point);
+        int w = point.x;
+        int h = point.y;
+
+        int[] disp = new int[2];
+        disp[0] = w;
+        disp[1] = h;
+        return disp;
+    }
+
+    /**
+     * 获取屏幕分辨率widthPixels
+     *
+     * @param activity
+     * @return
+     */
+    public static int getDisplayWidth(Activity activity) {
+        Display defaultDisplay = activity.getWindowManager().getDefaultDisplay();
+        Point point = new Point();
+        defaultDisplay.getSize(point);
+        int w = point.x;
+        int h = point.y;
+        return w;
+    }
+
+    /**
+     * 获取屏幕分辨率heightPixels
+     *
+     * @param activity
+     * @return
+     */
+    public static int getDisplayHeigth(Activity activity) {
+        Display defaultDisplay = activity.getWindowManager().getDefaultDisplay();
+        Point point = new Point();
+        defaultDisplay.getSize(point);
+        int w = point.x;
+        int h = point.y;
+        return h;
+    }
+
+    /**
+     * 【方法体过时,推荐用 getResolutionByFullScreen】
+     *
+     * @param context
+     * @return
+     */
+    @Deprecated
+    public static String getResolution(Context context) {
+        try {
+            WindowManager wm = (WindowManager) context
+                    .getSystemService(Context.WINDOW_SERVICE);
+            DisplayMetrics metrics = new DisplayMetrics();
+            wm.getDefaultDisplay().getMetrics(metrics);
+            int w = metrics.widthPixels;
+            int h = metrics.heightPixels;
+            return w + "x" + h;
+        } catch (Exception e) {
+            // TODO: handle exception
+        }
+        return "0x0";
+
+    }
+
+    /**
+     * 获取分辨率:应用程序的显示区域,不包系统栏
+     *
+     * @param activity
+     * @return
+     */
+    public static String getResolutionByApp(Activity activity) {
+        try {
+            Display defaultDisplay = activity.getWindowManager().getDefaultDisplay();
+            Point point = new Point();
+            defaultDisplay.getSize(point);
+            int w = point.x;
+            int h = point.y;
+            return w + "x" + h;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "0x0";
+    }
+
+    /**
+     * 获取分辨率:整个屏幕,包系统栏
+     *
+     * @param activity
+     * @return
+     */
+//    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
+    public static String getResolutionByFullScreen(Activity activity) {
+        try {
+            Point outSize = new Point();
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+                activity.getWindowManager().getDefaultDisplay().getRealSize(outSize);
+            }
+            int w = outSize.x;
+            int h = outSize.y;
+
+            return w + "*" + h;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "0*0";
+    }
+}

+ 156 - 0
library_support/src/main/java/cn/yyxx/support/FileUtils.java

@@ -1,13 +1,102 @@
 package cn.yyxx.support;
 
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.AssetManager;
+import android.text.TextUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.Reader;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
 /**
  * @author #Suyghur.
  * Created on 2021/04/23
  */
 public class FileUtils {
+
+    private FileUtils() {
+        /* cannot be instantiated */
+        throw new UnsupportedOperationException("cannot be instantiated");
+    }
+
+    /**
+     * 将字节流转换成文件
+     */
+    public static void saveFile(String filepath, byte[] data) throws IOException {
+        if (data != null) {
+            File file = new File(filepath);
+            if (file.exists()) {
+                file.delete();
+            }
+            FileOutputStream fos = new FileOutputStream(file);
+            fos.write(data, 0, data.length);
+            fos.flush();
+            fos.close();
+        }
+    }
+
+
+    public static InputStream accessFileFromAssets(Context context, String fileName) {
+        InputStream in = null;
+        try {
+            in = context.getAssets().open(fileName);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return in;
+    }
+
+    public static InputStream accessFileFromMetaInf(Context context, String fileName) {
+        ApplicationInfo applicationInfo = context.getApplicationInfo();
+        String sourceDir = applicationInfo.sourceDir;
+        ZipFile zipFile;
+        InputStream in = null;
+        try {
+            zipFile = new ZipFile(sourceDir);
+            Enumeration entries = zipFile.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = (ZipEntry) entries.nextElement();
+                String entryName = entry.getName();
+                if (entryName.startsWith("META-INF/" + fileName)) {
+                    if (entry.getSize() > 0) {
+                        in = zipFile.getInputStream(entry);
+                    }
+                    break;
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return in;
+    }
+
+    public static boolean isExistInAssets(Context context, String fileName, String path) {
+        AssetManager assetManager = context.getAssets();
+        try {
+            String[] fileNames = assetManager.list(path);
+            if (fileNames != null && fileNames.length != 0) {
+                for (String item : fileNames) {
+                    if (fileName.equals(item)) {
+                        return true;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
     public static String loadReaderAsString(Reader reader) throws Exception {
         StringBuilder builder = new StringBuilder();
         char[] buffer = new char[4096];
@@ -25,4 +114,71 @@ public class FileUtils {
         reader.close();
         return text;
     }
+
+    /**
+     * 读取文件
+     */
+    public static String readFile(String filePath) {
+        if (TextUtils.isEmpty(filePath)) {
+            return null;
+        }
+        BufferedReader reader = null;
+        FileInputStream is = null;
+        StringBuffer stringBuffer = new StringBuffer();
+        File file = new File(filePath);
+        if (!file.exists()) {
+            return null;
+        }
+        try {
+            is = new FileInputStream(file);
+            reader = new BufferedReader(new InputStreamReader(is));
+            try {
+                String data;
+                while ((data = reader.readLine()) != null) {
+                    stringBuffer.append(data);
+                }
+            } catch (IOException e) {
+                try {
+                    if (reader != null) {
+                        reader.close();
+                        reader = null;
+                    }
+                } catch (IOException e1) {
+                    e1.printStackTrace();
+                }
+                try {
+                    if (is != null) {
+                        is.close();
+                        is = null;
+                    }
+                } catch (IOException e1) {
+                    e1.printStackTrace();
+                }
+                e.printStackTrace();
+            } finally {
+                try {
+                    if (is != null) {
+                        is.close();
+                        is = null;
+                    }
+                } catch (IOException e1) {
+                    e1.printStackTrace();
+                }
+                try {
+                    if (reader != null) {
+                        reader.close();
+                        reader = null;
+                    }
+                } catch (IOException e1) {
+                    e1.printStackTrace();
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } catch (OutOfMemoryError ex) {
+            ex.printStackTrace();
+        }
+        return stringBuffer.toString().trim();
+    }
+
 }

+ 5 - 1
library_support/src/main/java/cn/yyxx/support/HostModelUtils.java

@@ -3,6 +3,10 @@ package cn.yyxx.support;
 import android.content.Context;
 import android.content.SharedPreferences;
 
+import java.util.logging.Logger;
+
+import cn.yyxx.support.hawkeye.LogUtils;
+
 /**
  * @author #Suyghur.
  * Created on 2021/04/22
@@ -20,7 +24,7 @@ public class HostModelUtils {
     public static void setHostModel(Context context, int ipModel) {
         SharedPreferences sp = context.getSharedPreferences("yyxx_host_model", Context.MODE_PRIVATE);
         SharedPreferences.Editor editor = sp.edit();
-        editor.putInt("host_mode", ipModel);
+        editor.putInt("host_model", ipModel);
         editor.commit();
     }
 

+ 20 - 0
library_support/src/main/java/cn/yyxx/support/JsonUtils.java

@@ -0,0 +1,20 @@
+package cn.yyxx.support;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/30
+ */
+public class JsonUtils {
+
+    private JsonUtils() {
+        throw new UnsupportedOperationException("cannot be instantiated");
+    }
+
+    public static boolean hasJsonKey(JSONObject jsonObject, String key) throws JSONException {
+//        return jsonObject.has(key) && !jsonObject.getString(key).equals("[]")&&!jsonObject.getString(key).equals("{}");
+        return jsonObject.has(key) && !jsonObject.getString(key).equals("[]");
+    }
+}

+ 186 - 0
library_support/src/main/java/cn/yyxx/support/PropertiesUtils.java

@@ -0,0 +1,186 @@
+package cn.yyxx.support;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.text.TextUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import cn.yyxx.support.hawkeye.LogUtils;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/04/23
+ */
+public class PropertiesUtils {
+
+
+    private static Map<String, Properties> propertiesMapCache = null;
+
+    public static Properties getProperties(Context context, String fileName, Location location) {
+        Properties proFile = null;
+        InputStream in = null;
+        try {
+            proFile = new Properties();
+            switch (location) {
+                case ASSETS:
+                    in = FileUtils.accessFileFromAssets(context, fileName);
+                    break;
+                case META_INF:
+                    in = FileUtils.accessFileFromMetaInf(context, fileName);
+                    break;
+                default:
+                    LogUtils.e("get properties failed , file location is error");
+                    break;
+            }
+            if (in != null) {
+                proFile.load(in);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return proFile;
+    }
+
+
+    public static String accessProFromAssets(Context context, String fileName, String key) {
+        Properties properties = getProperties(context, fileName, Location.ASSETS);
+        if (properties != null) {
+            propertiesMapCache.put(fileName, properties);
+            return properties.getProperty(key);
+        } else {
+            return "";
+        }
+    }
+
+    public static String accessProFromMetaInf(Context context, String fileName, String key) {
+        Properties properties = getProperties(context, fileName, Location.META_INF);
+        if (properties != null) {
+            propertiesMapCache.put(fileName, properties);
+            return properties.getProperty(key);
+        } else {
+            return "";
+        }
+    }
+
+    public static String getValue4Properties(Context context, String fileName, String key) {
+        return getValue4Properties(context, fileName, "", key);
+    }
+
+
+    public static String getValue4Properties(Context context, String fileName, String path, String key) {
+        if (propertiesMapCache == null) {
+            propertiesMapCache = new HashMap<>();
+        }
+        String value = null;
+
+//        //拿包ID,优先从META-INF/里获取拿包id
+//        //打包分子包时会创建package_拿包id命名的文件
+//        //SDK优先判断META-INF文件夹内有无package_开头的文件,如果有则直接截取文件后缀作为包的拿包id
+//        //如果META-INF内未包含上述文件,则还是按正常逻辑走
+//        if (key.equals("3KWAN_PackageID")) {
+//            if (mMetaInfPackageId > 0) {
+//                return mMetaInfPackageId + "";
+//            } else if (mMetaInfPackageId == -1) {
+//                LogUtils.d("没有参数值,然后从Assets获取...");
+//            } else {
+//                mMetaInfPackageId = getPackageIdFromMetainf(context);
+//                LogUtils.d("从META-INF获取的packageId = " + mMetaInfPackageId);
+//                if (mMetaInfPackageId == 0) {
+//                    mMetaInfPackageId = -1;
+//                }
+//                if (mMetaInfPackageId > 0) {
+//                    return mMetaInfPackageId + "";
+//                }
+//                LogUtils.d("没有参数值,然后从Assets获取...");
+//            }
+//        }
+
+        if (propertiesMapCache.containsKey(fileName)) {
+            try {
+                value = propertiesMapCache.get(fileName).getProperty(key);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            LogUtils.d("获取缓存数据:" + key + ":" + value);
+            return value;
+        }
+
+
+        if (FileUtils.isExistInAssets(context, fileName, path)) {
+            if (TextUtils.isEmpty(path)) {
+                value = accessProFromAssets(context, fileName, key);
+            } else {
+                value = accessProFromAssets(context, path + "/" + fileName, key);
+            }
+        }
+//        if (FileUtils.isExistInMetaInf(context, fileName)) {
+//            value = accessProFromMetaInf(context, fileName, key);
+//        }
+        return value;
+    }
+
+    /**
+     * 从META-INF/里获取拿包id(package_xxx)xxx是拿包ID
+     */
+    public static int getPackageIdFromMetainf(Context context) {
+        ApplicationInfo appInfo = context.getApplicationInfo();
+        String sourceDir = appInfo.sourceDir;
+        //LogUtils.d("sourceDir = " + sourceDir);
+        ZipFile zipfile = null;
+        int packageId = 0;
+        try {
+            zipfile = new ZipFile(sourceDir);
+            Enumeration<?> entries = zipfile.entries();
+            a:
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = ((ZipEntry) entries.nextElement());
+                String entryName = entry.getName();
+                if (entryName.contains("../")) {
+                    break;
+                }
+                //LogUtils.d("entryName = " + entryName);
+                //取META-INF/package_文件
+                if (entryName.contains("META-INF/package_")) {
+                    // 表示要读取的文件名
+                    // 利用ZipInputStream读取文件
+                    String packageIdStr = entryName.split("_")[1];
+                    //LogUtils.d("packageIdStr = " + packageIdStr);
+                    packageId = Integer.parseInt(packageIdStr);
+                    break a;
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (zipfile != null) {
+                try {
+                    zipfile.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return packageId;
+    }
+
+    public enum Location {
+        ASSETS,
+        META_INF
+    }
+}

+ 956 - 0
library_support/src/main/java/cn/yyxx/support/cache/bitmap/DiskLruCache.java

@@ -0,0 +1,956 @@
+package cn.yyxx.support.cache.bitmap;
+
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import cn.yyxx.support.hawkeye.LogUtils;
+
+/**
+ * A cache that uses a bounded amount of space on a filesystem. Each cache
+ * entry has a string key and a fixed number of values. Each key must match
+ * the regex <strong>[a-z0-9_-]{1,120}</strong>. Values are byte sequences,
+ * accessible as streams or files. Each value must be between {@code 0} and
+ * {@code Integer.MAX_VALUE} bytes in length.
+ *
+ * <p>The cache stores its data in a directory on the filesystem. This
+ * directory must be exclusive to the cache; the cache may delete or overwrite
+ * files from its directory. It is an error for multiple processes to use the
+ * same cache directory at the same time.
+ *
+ * <p>This cache limits the number of bytes that it will store on the
+ * filesystem. When the number of stored bytes exceeds the limit, the cache will
+ * remove entries in the background until the limit is satisfied. The limit is
+ * not strict: the cache may temporarily exceed it while waiting for files to be
+ * deleted. The limit does not include filesystem overhead or the cache
+ * journal so space-sensitive applications should set a conservative limit.
+ *
+ * <p>Clients call {@link #edit} to create or update the values of an entry. An
+ * entry may have only one editor at one time; if a value is not available to be
+ * edited then {@link #edit} will return null.
+ * <ul>
+ * <li>When an entry is being <strong>created</strong> it is necessary to
+ * supply a full set of values; the empty value should be used as a
+ * placeholder if necessary.
+ * <li>When an entry is being <strong>edited</strong>, it is not necessary
+ * to supply data for every value; values default to their previous
+ * value.
+ * </ul>
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
+ * or {@link Editor#abort}. Committing is atomic: a read observes the full set
+ * of values as they were before or after the commit, but never a mix of values.
+ *
+ * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
+ * observe the value at the time that {@link #get} was called. Updates and
+ * removals after the call do not impact ongoing reads.
+ *
+ * <p>This class is tolerant of some I/O errors. If files are missing from the
+ * filesystem, the corresponding entries will be dropped from the cache. If
+ * an error occurs while writing a cache value, the edit will fail silently.
+ * Callers should handle other problems by catching {@code IOException} and
+ * responding appropriately.
+ */
+public final class DiskLruCache implements Closeable {
+    static final String JOURNAL_FILE = "journal";
+    static final String JOURNAL_FILE_TEMP = "journal.tmp";
+    static final String JOURNAL_FILE_BACKUP = "journal.bkp";
+    static final String MAGIC = "libcore.io.DiskLruCache";
+    static final String VERSION_1 = "1";
+    static final long ANY_SEQUENCE_NUMBER = -1;
+    static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}";
+    static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);
+    private static final String CLEAN = "CLEAN";
+    private static final String DIRTY = "DIRTY";
+    private static final String REMOVE = "REMOVE";
+    private static final String READ = "READ";
+
+    /*
+     * This cache uses a journal file named "journal". A typical journal file
+     * looks like this:
+     *     libcore.io.DiskLruCache
+     *     1
+     *     100
+     *     2
+     *
+     *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
+     *     DIRTY 335c4c6028171cfddfbaae1a9c313c52
+     *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
+     *     REMOVE 335c4c6028171cfddfbaae1a9c313c52
+     *     DIRTY 1ab96a171faeeee38496d8b330771a7a
+     *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
+     *     READ 335c4c6028171cfddfbaae1a9c313c52
+     *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
+     *
+     * The first five lines of the journal form its header. They are the
+     * constant string "libcore.io.DiskLruCache", the disk cache's version,
+     * the application's version, the value count, and a blank line.
+     *
+     * Each of the subsequent lines in the file is a record of the state of a
+     * cache entry. Each line contains space-separated values: a state, a key,
+     * and optional state-specific values.
+     *   o DIRTY lines track that an entry is actively being created or updated.
+     *     Every successful DIRTY action should be followed by a CLEAN or REMOVE
+     *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that
+     *     temporary files may need to be deleted.
+     *   o CLEAN lines track a cache entry that has been successfully published
+     *     and may be read. A publish line is followed by the lengths of each of
+     *     its values.
+     *   o READ lines track accesses for LRU.
+     *   o REMOVE lines track entries that have been deleted.
+     *
+     * The journal file is appended to as cache operations occur. The journal may
+     * occasionally be compacted by dropping redundant lines. A temporary file named
+     * "journal.tmp" will be used during compaction; that file should be deleted if
+     * it exists when the cache is opened.
+     */
+
+    private final File directory;
+    private final File journalFile;
+    private final File journalFileTmp;
+    private final File journalFileBackup;
+    private final int appVersion;
+    private long maxSize;
+    private final int valueCount;
+    private long size = 0;
+    private Writer journalWriter;
+    private final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<String, Entry>(0, 0.75f, true);
+    private int redundantOpCount;
+
+    /**
+     * To differentiate between old and current snapshots, each entry is given
+     * a sequence number each time an edit is committed. A snapshot is stale if
+     * its sequence number is not equal to its entry's sequence number.
+     */
+    private long nextSequenceNumber = 0;
+
+    /**
+     * This cache uses a single background thread to evict entries.
+     */
+    final ThreadPoolExecutor executorService =
+            new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+    private final Callable<Void> cleanupCallable = new Callable<Void>() {
+        public Void call() throws Exception {
+            synchronized (DiskLruCache.this) {
+                if (journalWriter == null) {
+                    return null; // Closed.
+                }
+                trimToSize();
+                if (journalRebuildRequired()) {
+                    rebuildJournal();
+                    redundantOpCount = 0;
+                }
+            }
+            return null;
+        }
+    };
+
+    private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
+        this.directory = directory;
+        this.appVersion = appVersion;
+        this.journalFile = new File(directory, JOURNAL_FILE);
+        this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
+        this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
+        this.valueCount = valueCount;
+        this.maxSize = maxSize;
+    }
+
+    /**
+     * Opens the cache in {@code directory}, creating a cache if none exists
+     * there.
+     *
+     * @param directory  a writable directory
+     * @param valueCount the number of values per cache entry. Must be positive.
+     * @param maxSize    the maximum number of bytes this cache should use to store
+     * @throws IOException if reading or writing the cache directory fails
+     */
+    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
+            throws IOException {
+        if (maxSize <= 0) {
+            throw new IllegalArgumentException("maxSize <= 0");
+        }
+        if (valueCount <= 0) {
+            throw new IllegalArgumentException("valueCount <= 0");
+        }
+
+        // If a bkp file exists, use it instead.
+        File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
+        if (backupFile.exists()) {
+            File journalFile = new File(directory, JOURNAL_FILE);
+            // If journal file also exists just delete backup file.
+            if (journalFile.exists()) {
+                backupFile.delete();
+            } else {
+                renameTo(backupFile, journalFile, false);
+            }
+        }
+
+        // Prefer to pick up where we left off.
+        DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+        if (cache.journalFile.exists()) {
+            try {
+                cache.readJournal();
+                cache.processJournal();
+                return cache;
+            } catch (IOException e) {
+                e.printStackTrace();
+                LogUtils.e("DiskLruCache " + directory + " is corrupt: " + e.getMessage() + ", removing");
+                cache.delete();
+            }
+        }
+
+        // Create a new empty cache.
+        directory.mkdirs();
+        cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+        cache.rebuildJournal();
+        return cache;
+    }
+
+    private void readJournal() throws IOException {
+        StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
+        try {
+            String magic = reader.readLine();
+            String version = reader.readLine();
+            String appVersionString = reader.readLine();
+            String valueCountString = reader.readLine();
+            String blank = reader.readLine();
+            if (!MAGIC.equals(magic) || !VERSION_1.equals(version) || !Integer.toString(appVersion).equals(appVersionString)
+                    || !Integer.toString(valueCount).equals(valueCountString) || !"".equals(blank)) {
+                throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
+            }
+
+            int lineCount = 0;
+            while (true) {
+                try {
+                    readJournalLine(reader.readLine());
+                    lineCount++;
+                } catch (EOFException endOfJournal) {
+                    break;
+                }
+            }
+            redundantOpCount = lineCount - lruEntries.size();
+
+            // If we ended on a truncated line, rebuild the journal before appending to it.
+            if (reader.hasUnterminatedLine()) {
+                rebuildJournal();
+            } else {
+                journalWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
+            }
+        } finally {
+            Util.closeQuietly(reader);
+        }
+    }
+
+    private void readJournalLine(String line) throws IOException {
+        int firstSpace = line.indexOf(' ');
+        if (firstSpace == -1) {
+            throw new IOException("unexpected journal line: " + line);
+        }
+
+        int keyBegin = firstSpace + 1;
+        int secondSpace = line.indexOf(' ', keyBegin);
+        final String key;
+        if (secondSpace == -1) {
+            key = line.substring(keyBegin);
+            if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
+                lruEntries.remove(key);
+                return;
+            }
+        } else {
+            key = line.substring(keyBegin, secondSpace);
+        }
+
+        Entry entry = lruEntries.get(key);
+        if (entry == null) {
+            entry = new Entry(key);
+            lruEntries.put(key, entry);
+        }
+
+        if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
+            String[] parts = line.substring(secondSpace + 1).split(" ");
+            entry.readable = true;
+            entry.currentEditor = null;
+            entry.setLengths(parts);
+        } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
+            entry.currentEditor = new Editor(entry);
+        } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
+            // This work was already done by calling lruEntries.get().
+        } else {
+            throw new IOException("unexpected journal line: " + line);
+        }
+    }
+
+    /**
+     * Computes the initial size and collects garbage as a part of opening the
+     * cache. Dirty entries are assumed to be inconsistent and will be deleted.
+     */
+    private void processJournal() throws IOException {
+        deleteIfExists(journalFileTmp);
+        for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
+            Entry entry = i.next();
+            if (entry.currentEditor == null) {
+                for (int t = 0; t < valueCount; t++) {
+                    size += entry.lengths[t];
+                }
+            } else {
+                entry.currentEditor = null;
+                for (int t = 0; t < valueCount; t++) {
+                    deleteIfExists(entry.getCleanFile(t));
+                    deleteIfExists(entry.getDirtyFile(t));
+                }
+                i.remove();
+            }
+        }
+    }
+
+    /**
+     * Creates a new journal that omits redundant information. This replaces the
+     * current journal if it exists.
+     */
+    private synchronized void rebuildJournal() throws IOException {
+        if (journalWriter != null) {
+            journalWriter.close();
+        }
+
+        Writer writer = new BufferedWriter(
+                new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII));
+        try {
+            writer.write(MAGIC);
+            writer.write("\n");
+            writer.write(VERSION_1);
+            writer.write("\n");
+            writer.write(Integer.toString(appVersion));
+            writer.write("\n");
+            writer.write(Integer.toString(valueCount));
+            writer.write("\n");
+            writer.write("\n");
+
+            for (Entry entry : lruEntries.values()) {
+                if (entry.currentEditor != null) {
+                    writer.write(DIRTY + ' ' + entry.key + '\n');
+                } else {
+                    writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            writer.close();
+        }
+
+        if (journalFile.exists()) {
+            renameTo(journalFile, journalFileBackup, true);
+        }
+        renameTo(journalFileTmp, journalFile, false);
+        journalFileBackup.delete();
+
+        journalWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
+    }
+
+    private static void deleteIfExists(File file) throws IOException {
+        if (file.exists() && !file.delete()) {
+            throw new IOException();
+        }
+    }
+
+    private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
+        if (deleteDestination) {
+            deleteIfExists(to);
+        }
+        if (!from.renameTo(to)) {
+            throw new IOException();
+        }
+    }
+
+    /**
+     * Returns a snapshot of the entry named {@code key}, or null if it doesn't
+     * exist is not currently readable. If a value is returned, it is moved to
+     * the head of the LRU queue.
+     */
+    public synchronized Snapshot get(String key) throws IOException {
+        checkNotClosed();
+        validateKey(key);
+        Entry entry = lruEntries.get(key);
+        if (entry == null) {
+            return null;
+        }
+
+        if (!entry.readable) {
+            return null;
+        }
+
+        // Open all streams eagerly to guarantee that we see a single published
+        // snapshot. If we opened streams lazily then the streams could come
+        // from different edits.
+        InputStream[] ins = new InputStream[valueCount];
+        try {
+            for (int i = 0; i < valueCount; i++) {
+                ins[i] = new FileInputStream(entry.getCleanFile(i));
+            }
+        } catch (FileNotFoundException e) {
+            // A file must have been deleted manually!
+            for (int i = 0; i < valueCount; i++) {
+                if (ins[i] != null) {
+                    Util.closeQuietly(ins[i]);
+                } else {
+                    break;
+                }
+            }
+            return null;
+        }
+
+        redundantOpCount++;
+        journalWriter.append(READ + ' ' + key + '\n');
+        if (journalRebuildRequired()) {
+            executorService.submit(cleanupCallable);
+        }
+
+        return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
+    }
+
+    /**
+     * Returns an editor for the entry named {@code key}, or null if another
+     * edit is in progress.
+     */
+    public Editor edit(String key) throws IOException {
+        return edit(key, ANY_SEQUENCE_NUMBER);
+    }
+
+    private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
+        checkNotClosed();
+        validateKey(key);
+        Entry entry = lruEntries.get(key);
+        if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
+                || entry.sequenceNumber != expectedSequenceNumber)) {
+            return null; // Snapshot is stale.
+        }
+        if (entry == null) {
+            entry = new Entry(key);
+            lruEntries.put(key, entry);
+        } else if (entry.currentEditor != null) {
+            return null; // Another edit is in progress.
+        }
+
+        Editor editor = new Editor(entry);
+        entry.currentEditor = editor;
+
+        // Flush the journal before creating files to prevent file leaks.
+        journalWriter.write(DIRTY + ' ' + key + '\n');
+        journalWriter.flush();
+        return editor;
+    }
+
+    /**
+     * Returns the directory where this cache stores its data.
+     */
+    public File getDirectory() {
+        return directory;
+    }
+
+    /**
+     * Returns the maximum number of bytes that this cache should use to store
+     * its data.
+     */
+    public synchronized long getMaxSize() {
+        return maxSize;
+    }
+
+    /**
+     * Changes the maximum number of bytes the cache can store and queues a job
+     * to trim the existing store, if necessary.
+     */
+    public synchronized void setMaxSize(long maxSize) {
+        this.maxSize = maxSize;
+        executorService.submit(cleanupCallable);
+    }
+
+    /**
+     * Returns the number of bytes currently being used to store the values in
+     * this cache. This may be greater than the max size if a background
+     * deletion is pending.
+     */
+    public synchronized long size() {
+        return size;
+    }
+
+    private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
+        Entry entry = editor.entry;
+        if (entry.currentEditor != editor) {
+            throw new IllegalStateException();
+        }
+
+        // If this edit is creating the entry for the first time, every index must have a value.
+        if (success && !entry.readable) {
+            for (int i = 0; i < valueCount; i++) {
+                if (!editor.written[i]) {
+                    editor.abort();
+                    throw new IllegalStateException("Newly created entry didn't create value for index " + i);
+                }
+                if (!entry.getDirtyFile(i).exists()) {
+                    editor.abort();
+                    return;
+                }
+            }
+        }
+
+        for (int i = 0; i < valueCount; i++) {
+            File dirty = entry.getDirtyFile(i);
+            if (success) {
+                if (dirty.exists()) {
+                    File clean = entry.getCleanFile(i);
+                    dirty.renameTo(clean);
+                    long oldLength = entry.lengths[i];
+                    long newLength = clean.length();
+                    entry.lengths[i] = newLength;
+                    size = size - oldLength + newLength;
+                }
+            } else {
+                deleteIfExists(dirty);
+            }
+        }
+
+        redundantOpCount++;
+        entry.currentEditor = null;
+        if (entry.readable | success) {
+            entry.readable = true;
+            journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+            if (success) {
+                entry.sequenceNumber = nextSequenceNumber++;
+            }
+        } else {
+            lruEntries.remove(entry.key);
+            journalWriter.write(REMOVE + ' ' + entry.key + '\n');
+        }
+        journalWriter.flush();
+
+        if (size > maxSize || journalRebuildRequired()) {
+            executorService.submit(cleanupCallable);
+        }
+    }
+
+    /**
+     * We only rebuild the journal when it will halve the size of the journal
+     * and eliminate at least 2000 ops.
+     */
+    private boolean journalRebuildRequired() {
+        final int redundantOpCompactThreshold = 2000;
+        return redundantOpCount >= redundantOpCompactThreshold //
+                && redundantOpCount >= lruEntries.size();
+    }
+
+    /**
+     * Drops the entry for {@code key} if it exists and can be removed. Entries
+     * actively being edited cannot be removed.
+     *
+     * @return true if an entry was removed.
+     */
+    public synchronized boolean remove(String key) throws IOException {
+        checkNotClosed();
+        validateKey(key);
+        Entry entry = lruEntries.get(key);
+        if (entry == null || entry.currentEditor != null) {
+            return false;
+        }
+
+        for (int i = 0; i < valueCount; i++) {
+            File file = entry.getCleanFile(i);
+            if (file.exists() && !file.delete()) {
+                throw new IOException("failed to delete " + file);
+            }
+            size -= entry.lengths[i];
+            entry.lengths[i] = 0;
+        }
+
+        redundantOpCount++;
+        journalWriter.append(REMOVE + ' ' + key + '\n');
+        lruEntries.remove(key);
+
+        if (journalRebuildRequired()) {
+            executorService.submit(cleanupCallable);
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if this cache has been closed.
+     */
+    public synchronized boolean isClosed() {
+        return journalWriter == null;
+    }
+
+    private void checkNotClosed() {
+        if (journalWriter == null) {
+            throw new IllegalStateException("cache is closed");
+        }
+    }
+
+    /**
+     * Force buffered operations to the filesystem.
+     */
+    public synchronized void flush() throws IOException {
+        checkNotClosed();
+        trimToSize();
+        journalWriter.flush();
+    }
+
+    /**
+     * Closes this cache. Stored values will remain on the filesystem.
+     */
+    public synchronized void close() throws IOException {
+        if (journalWriter == null) {
+            return; // Already closed.
+        }
+        for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
+            if (entry.currentEditor != null) {
+                entry.currentEditor.abort();
+            }
+        }
+        trimToSize();
+        journalWriter.close();
+        journalWriter = null;
+    }
+
+    private void trimToSize() throws IOException {
+        while (size > maxSize) {
+            Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
+            remove(toEvict.getKey());
+        }
+    }
+
+    /**
+     * Closes the cache and deletes all of its stored values. This will delete
+     * all files in the cache directory including files that weren't created by
+     * the cache.
+     */
+    public void delete() throws IOException {
+        close();
+        Util.deleteContents(directory);
+    }
+
+    private void validateKey(String key) {
+        Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("keys must match regex "
+                    + STRING_KEY_PATTERN + ": \"" + key + "\"");
+        }
+    }
+
+    private static String inputStreamToString(InputStream in) throws IOException {
+        return Util.readFully(new InputStreamReader(in, Util.UTF_8));
+    }
+
+    /**
+     * A snapshot of the values for an entry.
+     */
+    public final class Snapshot implements Closeable {
+        private final String key;
+        private final long sequenceNumber;
+        private final InputStream[] ins;
+        private final long[] lengths;
+
+        private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) {
+            this.key = key;
+            this.sequenceNumber = sequenceNumber;
+            this.ins = ins;
+            this.lengths = lengths;
+        }
+
+        /**
+         * Returns an editor for this snapshot's entry, or null if either the
+         * entry has changed since this snapshot was created or if another edit
+         * is in progress.
+         */
+        public Editor edit() throws IOException {
+            return DiskLruCache.this.edit(key, sequenceNumber);
+        }
+
+        /**
+         * Returns the unbuffered stream with the value for {@code index}.
+         */
+        public InputStream getInputStream(int index) {
+            return ins[index];
+        }
+
+        /**
+         * Returns the string value for {@code index}.
+         */
+        public String getString(int index) throws IOException {
+            return inputStreamToString(getInputStream(index));
+        }
+
+        /**
+         * Returns the byte length of the value for {@code index}.
+         */
+        public long getLength(int index) {
+            return lengths[index];
+        }
+
+        public void close() {
+            for (InputStream in : ins) {
+                Util.closeQuietly(in);
+            }
+        }
+    }
+
+    private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
+        @Override
+        public void write(int b) throws IOException {
+            // Eat all writes silently. Nom nom.
+        }
+    };
+
+    /**
+     * Edits the values for an entry.
+     */
+    public final class Editor {
+        private final Entry entry;
+        private final boolean[] written;
+        private boolean hasErrors;
+        private boolean committed;
+
+        private Editor(Entry entry) {
+            this.entry = entry;
+            this.written = (entry.readable) ? null : new boolean[valueCount];
+        }
+
+        /**
+         * Returns an unbuffered input stream to read the last committed value,
+         * or null if no value has been committed.
+         */
+        public InputStream newInputStream(int index) throws IOException {
+            synchronized (DiskLruCache.this) {
+                if (entry.currentEditor != this) {
+                    throw new IllegalStateException();
+                }
+                if (!entry.readable) {
+                    return null;
+                }
+                try {
+                    return new FileInputStream(entry.getCleanFile(index));
+                } catch (FileNotFoundException e) {
+                    return null;
+                }
+            }
+        }
+
+        /**
+         * Returns the last committed value as a string, or null if no value
+         * has been committed.
+         */
+        public String getString(int index) throws IOException {
+            InputStream in = newInputStream(index);
+            return in != null ? inputStreamToString(in) : null;
+        }
+
+        /**
+         * Returns a new unbuffered output stream to write the value at
+         * {@code index}. If the underlying output stream encounters errors
+         * when writing to the filesystem, this edit will be aborted when
+         * {@link #commit} is called. The returned output stream does not throw
+         * IOExceptions.
+         */
+        public OutputStream newOutputStream(int index) throws IOException {
+            if (index < 0 || index >= valueCount) {
+                throw new IllegalArgumentException("Expected index " + index + " to "
+                        + "be greater than 0 and less than the maximum value count "
+                        + "of " + valueCount);
+            }
+            synchronized (DiskLruCache.this) {
+                if (entry.currentEditor != this) {
+                    throw new IllegalStateException();
+                }
+                if (!entry.readable) {
+                    written[index] = true;
+                }
+                File dirtyFile = entry.getDirtyFile(index);
+                FileOutputStream outputStream;
+                try {
+                    outputStream = new FileOutputStream(dirtyFile);
+                } catch (FileNotFoundException e) {
+                    // Attempt to recreate the cache directory.
+                    directory.mkdirs();
+                    try {
+                        outputStream = new FileOutputStream(dirtyFile);
+                    } catch (FileNotFoundException e2) {
+                        // We are unable to recover. Silently eat the writes.
+                        return NULL_OUTPUT_STREAM;
+                    }
+                }
+                return new FaultHidingOutputStream(outputStream);
+            }
+        }
+
+        /**
+         * Sets the value at {@code index} to {@code value}.
+         */
+        public void set(int index, String value) throws IOException {
+            Writer writer = null;
+            try {
+                writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8);
+                writer.write(value);
+            } finally {
+                Util.closeQuietly(writer);
+            }
+        }
+
+        /**
+         * Commits this edit so it is visible to readers.  This releases the
+         * edit lock so another edit may be started on the same key.
+         */
+        public void commit() throws IOException {
+            if (hasErrors) {
+                completeEdit(this, false);
+                remove(entry.key); // The previous entry is stale.
+            } else {
+                completeEdit(this, true);
+            }
+            committed = true;
+        }
+
+        /**
+         * Aborts this edit. This releases the edit lock so another edit may be
+         * started on the same key.
+         */
+        public void abort() throws IOException {
+            completeEdit(this, false);
+        }
+
+        public void abortUnlessCommitted() {
+            if (!committed) {
+                try {
+                    abort();
+                } catch (IOException ignored) {
+                }
+            }
+        }
+
+        private class FaultHidingOutputStream extends FilterOutputStream {
+            private FaultHidingOutputStream(OutputStream out) {
+                super(out);
+            }
+
+            @Override
+            public void write(int oneByte) {
+                try {
+                    out.write(oneByte);
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+
+            @Override
+            public void write(byte[] buffer, int offset, int length) {
+                try {
+                    out.write(buffer, offset, length);
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+
+            @Override
+            public void close() {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+
+            @Override
+            public void flush() {
+                try {
+                    out.flush();
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+        }
+    }
+
+    private final class Entry {
+        private final String key;
+
+        /**
+         * Lengths of this entry's files.
+         */
+        private final long[] lengths;
+
+        /**
+         * True if this entry has ever been published.
+         */
+        private boolean readable;
+
+        /**
+         * The ongoing edit or null if this entry is not being edited.
+         */
+        private Editor currentEditor;
+
+        /**
+         * The sequence number of the most recently committed edit to this entry.
+         */
+        private long sequenceNumber;
+
+        private Entry(String key) {
+            this.key = key;
+            this.lengths = new long[valueCount];
+        }
+
+        public String getLengths() throws IOException {
+            StringBuilder result = new StringBuilder();
+            for (long size : lengths) {
+                result.append(' ').append(size);
+            }
+            return result.toString();
+        }
+
+        /**
+         * Set lengths using decimal numbers like "10123".
+         */
+        private void setLengths(String[] strings) throws IOException {
+            if (strings.length != valueCount) {
+                throw invalidLengths(strings);
+            }
+
+            try {
+                for (int i = 0; i < strings.length; i++) {
+                    lengths[i] = Long.parseLong(strings[i]);
+                }
+            } catch (NumberFormatException e) {
+                throw invalidLengths(strings);
+            }
+        }
+
+        private IOException invalidLengths(String[] strings) throws IOException {
+            throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
+        }
+
+        public File getCleanFile(int i) {
+            return new File(directory, key + "." + i);
+        }
+
+        public File getDirtyFile(int i) {
+            return new File(directory, key + "." + i + ".tmp");
+        }
+    }
+}

+ 196 - 0
library_support/src/main/java/cn/yyxx/support/cache/bitmap/StrictLineReader.java

@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.yyxx.support.cache.bitmap;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+/**
+ * Buffers input from an {@link InputStream} for reading lines.
+ *
+ * <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
+ * with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
+ * line at end of input is invalid and will be ignored, the caller may use {@code
+ * hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
+ *
+ * <p>This class is intended for reading input that strictly consists of lines, such as line-based
+ * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
+ * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
+ * end-of-input reporting and a more restrictive definition of a line.
+ *
+ * <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
+ * and 10, respectively, and the representation of no other character contains these values.
+ * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
+ * The default charset is US_ASCII.
+ */
+class StrictLineReader implements Closeable {
+    private static final byte CR = (byte) '\r';
+    private static final byte LF = (byte) '\n';
+
+    private final InputStream in;
+    private final Charset charset;
+
+    /**
+     * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
+     * and the data in the range [pos, end) is buffered for reading. At end of input, if there is
+     * an unterminated line, we set end == -1, otherwise end == pos. If the underlying
+     * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
+     */
+    private byte[] buf;
+    private int pos;
+    private int end;
+
+    /**
+     * Constructs a new {@code LineReader} with the specified charset and the default capacity.
+     *
+     * @param in      the {@code InputStream} to read data from.
+     * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
+     *                supported.
+     * @throws NullPointerException     if {@code in} or {@code charset} is null.
+     * @throws IllegalArgumentException if the specified charset is not supported.
+     */
+    public StrictLineReader(InputStream in, Charset charset) {
+        this(in, 8192, charset);
+    }
+
+    /**
+     * Constructs a new {@code LineReader} with the specified capacity and charset.
+     *
+     * @param in       the {@code InputStream} to read data from.
+     * @param capacity the capacity of the buffer.
+     * @param charset  the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
+     *                 supported.
+     * @throws NullPointerException     if {@code in} or {@code charset} is null.
+     * @throws IllegalArgumentException if {@code capacity} is negative or zero
+     *                                  or the specified charset is not supported.
+     */
+    public StrictLineReader(InputStream in, int capacity, Charset charset) {
+        if (in == null || charset == null) {
+            throw new NullPointerException();
+        }
+        if (capacity < 0) {
+            throw new IllegalArgumentException("capacity <= 0");
+        }
+        if (!(charset.equals(Util.US_ASCII))) {
+            throw new IllegalArgumentException("Unsupported encoding");
+        }
+
+        this.in = in;
+        this.charset = charset;
+        buf = new byte[capacity];
+    }
+
+    /**
+     * Closes the reader by closing the underlying {@code InputStream} and
+     * marking this reader as closed.
+     *
+     * @throws IOException for errors when closing the underlying {@code InputStream}.
+     */
+    public void close() throws IOException {
+        synchronized (in) {
+            if (buf != null) {
+                buf = null;
+                in.close();
+            }
+        }
+    }
+
+    /**
+     * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
+     * this end of line marker is not included in the result.
+     *
+     * @return the next line from the input.
+     * @throws IOException  for underlying {@code InputStream} errors.
+     * @throws EOFException for the end of source stream.
+     */
+    public String readLine() throws IOException {
+        synchronized (in) {
+            if (buf == null) {
+                throw new IOException("LineReader is closed");
+            }
+
+            // Read more data if we are at the end of the buffered data.
+            // Though it's an error to read after an exception, we will let {@code fillBuf()}
+            // throw again if that happens; thus we need to handle end == -1 as well as end == pos.
+            if (pos >= end) {
+                fillBuf();
+            }
+            // Try to find LF in the buffered data and return the line if successful.
+            for (int i = pos; i != end; ++i) {
+                if (buf[i] == LF) {
+                    int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
+                    String res = new String(buf, pos, lineEnd - pos, charset.name());
+                    pos = i + 1;
+                    return res;
+                }
+            }
+
+            // Let's anticipate up to 80 characters on top of those already read.
+            ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
+                @Override
+                public String toString() {
+                    int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
+                    try {
+                        return new String(buf, 0, length, charset.name());
+                    } catch (UnsupportedEncodingException e) {
+                        throw new AssertionError(e); // Since we control the charset this will never happen.
+                    }
+                }
+            };
+
+            while (true) {
+                out.write(buf, pos, end - pos);
+                // Mark unterminated line in case fillBuf throws EOFException or IOException.
+                end = -1;
+                fillBuf();
+                // Try to find LF in the buffered data and return the line if successful.
+                for (int i = pos; i != end; ++i) {
+                    if (buf[i] == LF) {
+                        if (i != pos) {
+                            out.write(buf, pos, i - pos);
+                        }
+                        pos = i + 1;
+                        return out.toString();
+                    }
+                }
+            }
+        }
+    }
+
+    public boolean hasUnterminatedLine() {
+        return end == -1;
+    }
+
+    /**
+     * Reads new input data into the buffer. Call only with pos == end or end == -1,
+     * depending on the desired outcome if the function throws.
+     */
+    private void fillBuf() throws IOException {
+        int result = in.read(buf, 0, buf.length);
+        if (result == -1) {
+            throw new EOFException();
+        }
+        pos = 0;
+        end = result;
+    }
+}
+

+ 76 - 0
library_support/src/main/java/cn/yyxx/support/cache/bitmap/Util.java

@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.yyxx.support.cache.bitmap;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+
+/**
+ * Junk drawer of utility methods.
+ */
+final class Util {
+    static final Charset US_ASCII = Charset.forName("US-ASCII");
+    static final Charset UTF_8 = Charset.forName("UTF-8");
+
+    static String readFully(Reader reader) throws IOException {
+        try {
+            StringWriter writer = new StringWriter();
+            char[] buffer = new char[1024];
+            int count;
+            while ((count = reader.read(buffer)) != -1) {
+                writer.write(buffer, 0, count);
+            }
+            return writer.toString();
+        } finally {
+            reader.close();
+        }
+    }
+
+    /**
+     * Deletes the contents of {@code dir}. Throws an IOException if any file
+     * could not be deleted, or if {@code dir} is not a readable directory.
+     */
+    static void deleteContents(File dir) throws IOException {
+        File[] files = dir.listFiles();
+        if (files == null) {
+            throw new IOException("not a readable directory: " + dir);
+        }
+        for (File file : files) {
+            if (file.isDirectory()) {
+                deleteContents(file);
+            }
+            if (!file.delete()) {
+                throw new IOException("failed to delete file: " + file);
+            }
+        }
+    }
+
+    static void closeQuietly(/*Auto*/Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (RuntimeException rethrown) {
+                throw rethrown;
+            } catch (Exception ignored) {
+            }
+        }
+    }
+}

+ 257 - 1
library_support/src/main/java/cn/yyxx/support/device/DeviceInfoUtils.java

@@ -3,22 +3,32 @@ package cn.yyxx.support.device;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.os.BatteryManager;
 import android.os.Build;
+import android.provider.Settings.Secure;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
 import java.io.BufferedReader;
 import java.io.FileReader;
+import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.LineNumberReader;
 import java.lang.reflect.Method;
 import java.net.NetworkInterface;
 import java.net.SocketException;
+import java.nio.charset.StandardCharsets;
 import java.util.Enumeration;
 
 import cn.yyxx.support.FileUtils;
+import cn.yyxx.support.emulator.EmulatorFiles;
+import cn.yyxx.support.emulator.newfunc.EmulatorCheck;
 import cn.yyxx.support.hawkeye.LogUtils;
 
 /**
@@ -134,6 +144,18 @@ public class DeviceInfoUtils {
         return getTelephoneInfo(context, SIM);
     }
 
+
+    /**
+     * 判断是否包含SIM卡
+     *
+     * @return 状态
+     */
+    public static boolean hasSimCard(Context context) {
+        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        int simState = telephonyManager.getSimState();
+        return simState != TelephonyManager.SIM_STATE_ABSENT && simState != TelephonyManager.SIM_STATE_UNKNOWN;
+    }
+
     /**
      * 获取手机序列号
      */
@@ -157,7 +179,7 @@ public class DeviceInfoUtils {
         if (context == null) {
             return androidId;
         }
-        androidId = android.provider.Settings.Secure.getString(context.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
+        androidId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
         return androidId;
     }
 
@@ -219,6 +241,14 @@ public class DeviceInfoUtils {
         return String.valueOf(mi.availMem / 1024 / 1024);
     }
 
+    /**
+     * 获取系统版本
+     */
+    public static String getDeviceSoftwareVersion() {
+        return Build.VERSION.RELEASE;
+    }
+
+
     /**
      * 获得手机MAC
      *
@@ -337,4 +367,230 @@ public class DeviceInfoUtils {
         }
         return buf.toString();
     }
+
+    /**
+     * 判断网络是否可用
+     *
+     * @param context 上下文
+     */
+    @SuppressLint("MissingPermission")
+    public static boolean isAvailable(Context context) {
+        if (context == null) {
+            return false;
+        }
+        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo info = cm.getActiveNetworkInfo();
+        return info != null && info.isAvailable();
+    }
+
+    /**
+     * 判断网络是否已连接或正在连接
+     *
+     * @param context 上下文
+     */
+    @SuppressLint("MissingPermission")
+    public static boolean isNetworkConnected(Context context) {
+        if (context == null) {
+            return false;
+        }
+        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo info = cm.getActiveNetworkInfo();
+        return info != null && info.isConnectedOrConnecting();
+    }
+
+    /**
+     * 获取运营商CODE
+     *
+     * @param context
+     * @return
+     */
+    public static String getSimOperatorCode(Context context) {
+        return getTelephoneInfo(context, SIM_OPERATOR);
+    }
+
+    /**
+     * 获取运营商名称
+     *
+     * @param context
+     * @return
+     */
+    public static String getSimOperatorName(Context context) {
+        return getTelephoneInfo(context, SIM_OPERATOR_NAME);
+    }
+
+    /**
+     * 获取运营商
+     * 1、移动;2、联通;3、电信;4、其他
+     *
+     * @param context
+     * @return
+     */
+    public static String getSimOperator(Context context) {
+        String code = getSimOperatorCode(context);
+        if (code.length() > 0) {
+            if (code.equals("46000") || code.equals("46002")
+                    || code.equals("46007")) {
+                // 中国移动
+                //LogUtils.d("中国移动");
+                return "1";
+            } else if (code.equals("46001") || code.equals("46006")) {
+                // 中国联通
+                //LogUtils.d("中国联通");
+                return "2";
+            } else if (code.equals("46003") || code.equals("46005")) {
+                // 中国电信
+                //LogUtils.d("中国电信");
+                return "3";
+            } else {
+                //LogUtils.d("无或其他");
+                return "4";
+            }
+        }
+        return "4";
+    }
+
+    @SuppressLint("MissingPermission")
+    public static String getNetworkClass(Context context) {
+        //获取系统的网络服务
+        ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        //如果当前没有网络
+        if (null == connManager)
+            return "none";
+
+        //获取当前网络类型,如果为空,返回无网络
+        NetworkInfo activeNetInfo = connManager.getActiveNetworkInfo();
+        if (activeNetInfo == null || !activeNetInfo.isAvailable()) {
+            return "none";
+        }
+
+        // 判断是不是连接的是不是wifi
+        NetworkInfo wifiInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+        if (null != wifiInfo) {
+            NetworkInfo.State state = wifiInfo.getState();
+            if (null != state)
+                if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
+                    return "wifi";
+                }
+        }
+
+        // 如果不是wifi,则判断当前连接的是运营商的哪种网络2g、3g、4g等
+        NetworkInfo networkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+
+        if (null != networkInfo) {
+            NetworkInfo.State state = networkInfo.getState();
+            String strSubTypeName = networkInfo.getSubtypeName();
+            if (null != state)
+                if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
+                    switch (activeNetInfo.getSubtype()) {
+                        //如果是2g类型
+                        case TelephonyManager.NETWORK_TYPE_GPRS: // 联通2g
+                        case TelephonyManager.NETWORK_TYPE_CDMA: // 电信2g
+                        case TelephonyManager.NETWORK_TYPE_EDGE: // 移动2g
+                        case TelephonyManager.NETWORK_TYPE_1xRTT:
+                        case TelephonyManager.NETWORK_TYPE_IDEN:
+                            return "2G";
+                        //如果是3g类型
+                        case TelephonyManager.NETWORK_TYPE_EVDO_A: // 电信3g
+                        case TelephonyManager.NETWORK_TYPE_UMTS:
+                        case TelephonyManager.NETWORK_TYPE_EVDO_0:
+                        case TelephonyManager.NETWORK_TYPE_HSDPA:
+                        case TelephonyManager.NETWORK_TYPE_HSUPA:
+                        case TelephonyManager.NETWORK_TYPE_HSPA:
+                        case TelephonyManager.NETWORK_TYPE_EVDO_B:
+                        case TelephonyManager.NETWORK_TYPE_EHRPD:
+                        case TelephonyManager.NETWORK_TYPE_HSPAP:
+                            return "3G";
+                        //如果是4g类型
+                        case TelephonyManager.NETWORK_TYPE_LTE:
+                            return "4G";
+                        case TelephonyManager.NETWORK_TYPE_NR:
+                            return "5G";
+                        default:
+                            //中国移动 联通 电信 三种3G制式
+                            if (strSubTypeName.equalsIgnoreCase("TD-SCDMA") || strSubTypeName.equalsIgnoreCase("WCDMA") || strSubTypeName.equalsIgnoreCase("CDMA2000")) {
+                                return "3G";
+                            } else {
+                                return "wifi";
+                            }
+                    }
+                }
+        }
+        return "none";
+    }
+
+    public static boolean isCharged(Context context) {
+        IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+        Intent batteryStatusIntent = context.registerReceiver(null, ifilter);
+        //如果设备正在充电,可以提取当前的充电状态和充电方式(无论是通过 USB 还是交流充电器),如下所示:
+
+        // Are we charging / charged?
+        int status = batteryStatusIntent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+        boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
+                status == BatteryManager.BATTERY_STATUS_FULL;
+
+        // How are we charging?
+        int chargePlug = batteryStatusIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+        boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
+        boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
+
+        if (isCharging) {
+            if (usbCharge) {
+                return false;
+            } else {
+                return acCharge;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 判断是不是模拟器
+     */
+    public static boolean isEmulator(Context context) {
+        // readSysProperty 和 hasEmulatorAdb 会对真机造成误伤
+//        boolean b = readSysProperty();
+//        if (!b) {
+//            if (FindEmulator.hasEmulatorAdb()) {
+//                return true;
+//            }
+//        }
+        boolean b = EmulatorFiles.hasEmulatorFile();
+        if (!b) {
+            return DeviceInfoUtils.isPcKernel();
+        }
+        return b;
+    }
+
+    public static boolean isEmulator2(Context context) {
+        return EmulatorCheck.readSysProperty(context);
+    }
+
+
+    /**
+     * cat /proc/cpuinfo
+     * 从cpuinfo中读取cpu架构,检测CPU是否是PC端
+     */
+    public static boolean isPcKernel() {
+        String str = "";
+        try {
+            Process start = new ProcessBuilder(new String[]{"/system/bin/cat", "/proc/cpuinfo"}).start();
+            StringBuilder stringBuffer = new StringBuilder();
+            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(start.getInputStream(), StandardCharsets.UTF_8));
+            while (true) {
+                String readLine = bufferedReader.readLine();
+                if (readLine == null) {
+                    break;
+                }
+                stringBuffer.append(readLine);
+            }
+            bufferedReader.close();
+            str = stringBuffer.toString().toLowerCase();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return str.contains("intel") || str.contains("amd");
+    }
+
 }

+ 234 - 0
library_support/src/main/java/cn/yyxx/support/emulator/EmulatorFiles.java

@@ -0,0 +1,234 @@
+package cn.yyxx.support.emulator;
+
+import java.io.File;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class EmulatorFiles {
+    // =================== 主要证据 ===================
+    /**
+     * 特殊的模拟器特征文件
+     * 186项文件,查到一个文件就是百分百模拟器,权重最高
+     * <p>
+     * 模拟器内核GoldFish
+     * /data下的特征文件需要权限才可以访问,其他目录下的特征文件不需要
+     * 因为模拟器的话,都是root权限,所以并不需要担心权限问题。
+     * 转载请注明出处:CSDN 胖虎
+     * https://blog.csdn.net/ljphhj/
+     */
+    static final String[] emulatorFiles = {
+
+            // vbox模拟器文件
+            "/data/youwave_id",
+            "/dev/vboxguest",
+            "/dev/vboxuser",
+            "/mnt/prebundledapps/bluestacks.prop.orig",
+            "/mnt/prebundledapps/propfiles/ics.bluestacks.prop.note",
+            "/mnt/prebundledapps/propfiles/ics.bluestacks.prop.s2",
+            "/mnt/prebundledapps/propfiles/ics.bluestacks.prop.s3",
+            "/mnt/sdcard/bstfolder/InputMapper/com.bluestacks.appmart.cfg",
+            "/mnt/sdcard/buildroid-gapps-ics-20120317-signed.tgz",
+            "/mnt/sdcard/windows/InputMapper/com.bluestacks.appmart.cfg",
+            "/proc/irq/9/vboxguest",
+            "/sys/bus/pci/drivers/vboxguest",
+            "/sys/bus/pci/drivers/vboxguest/0000:00:04.0",
+            "/sys/bus/pci/drivers/vboxguest/bind",
+            "/sys/bus/pci/drivers/vboxguest/module",
+            "/sys/bus/pci/drivers/vboxguest/new_id",
+            "/sys/bus/pci/drivers/vboxguest/remove_id",
+            "/sys/bus/pci/drivers/vboxguest/uevent",
+            "/sys/bus/pci/drivers/vboxguest/unbind",
+            "/sys/bus/platform/drivers/qemu_pipe",
+            "/sys/bus/platform/drivers/qemu_trace",
+            "/sys/class/bdi/vboxsf-c",
+            "/sys/class/misc/vboxguest",
+            "/sys/class/misc/vboxuser",
+            "/sys/devices/virtual/bdi/vboxsf-c",
+            "/sys/devices/virtual/misc/vboxguest",
+            "/sys/devices/virtual/misc/vboxguest/dev",
+            "/sys/devices/virtual/misc/vboxguest/power",
+            "/sys/devices/virtual/misc/vboxguest/subsystem",
+            "/sys/devices/virtual/misc/vboxguest/uevent",
+            "/sys/devices/virtual/misc/vboxuser",
+            "/sys/devices/virtual/misc/vboxuser/dev",
+            "/sys/devices/virtual/misc/vboxuser/power",
+            "/sys/devices/virtual/misc/vboxuser/subsystem",
+            "/sys/devices/virtual/misc/vboxuser/uevent",
+            "/sys/module/vboxguest",
+            "/sys/module/vboxguest/coresize",
+            "/sys/module/vboxguest/drivers",
+            "/sys/module/vboxguest/drivers/pci:vboxguest",
+            "/sys/module/vboxguest/holders",
+            "/sys/module/vboxguest/holders/vboxsf",
+            "/sys/module/vboxguest/initsize",
+            "/sys/module/vboxguest/initstate",
+            "/sys/module/vboxguest/notes",
+            "/sys/module/vboxguest/notes/.note.gnu.build-id",
+            "/sys/module/vboxguest/parameters",
+            "/sys/module/vboxguest/parameters/log",
+            "/sys/module/vboxguest/parameters/log_dest",
+            "/sys/module/vboxguest/parameters/log_flags",
+            "/sys/module/vboxguest/refcnt",
+            "/sys/module/vboxguest/sections",
+            "/sys/module/vboxguest/sections/.altinstructions",
+            "/sys/module/vboxguest/sections/.altinstr_replacement",
+            "/sys/module/vboxguest/sections/.bss",
+            "/sys/module/vboxguest/sections/.data",
+            "/sys/module/vboxguest/sections/.devinit.data",
+            "/sys/module/vboxguest/sections/.exit.text",
+            "/sys/module/vboxguest/sections/.fixup",
+            "/sys/module/vboxguest/sections/.gnu.linkonce.this_module",
+            "/sys/module/vboxguest/sections/.init.text",
+            "/sys/module/vboxguest/sections/.note.gnu.build-id",
+            "/sys/module/vboxguest/sections/.rodata",
+            "/sys/module/vboxguest/sections/.rodata.str1.1",
+            "/sys/module/vboxguest/sections/.smp_locks",
+            "/sys/module/vboxguest/sections/.strtab",
+            "/sys/module/vboxguest/sections/.symtab",
+            "/sys/module/vboxguest/sections/.text",
+            "/sys/module/vboxguest/sections/__ex_table",
+            "/sys/module/vboxguest/sections/__ksymtab",
+            "/sys/module/vboxguest/sections/__ksymtab_strings",
+            "/sys/module/vboxguest/sections/__param",
+            "/sys/module/vboxguest/srcversion",
+            "/sys/module/vboxguest/taint",
+            "/sys/module/vboxguest/uevent",
+            "/sys/module/vboxguest/version",
+            "/sys/module/vboxsf",
+            "/sys/module/vboxsf/coresize",
+            "/sys/module/vboxsf/holders",
+            "/sys/module/vboxsf/initsize",
+            "/sys/module/vboxsf/initstate",
+            "/sys/module/vboxsf/notes",
+            "/sys/module/vboxsf/notes/.note.gnu.build-id",
+            "/sys/module/vboxsf/refcnt",
+            "/sys/module/vboxsf/sections",
+            "/sys/module/vboxsf/sections/.bss",
+            "/sys/module/vboxsf/sections/.data",
+            "/sys/module/vboxsf/sections/.exit.text",
+            "/sys/module/vboxsf/sections/.gnu.linkonce.this_module",
+            "/sys/module/vboxsf/sections/.init.text",
+            "/sys/module/vboxsf/sections/.note.gnu.build-id",
+            "/sys/module/vboxsf/sections/.rodata",
+            "/sys/module/vboxsf/sections/.rodata.str1.1",
+            "/sys/module/vboxsf/sections/.smp_locks",
+            "/sys/module/vboxsf/sections/.strtab",
+            "/sys/module/vboxsf/sections/.symtab",
+            "/sys/module/vboxsf/sections/.text",
+            "/sys/module/vboxsf/sections/__bug_table",
+            "/sys/module/vboxsf/sections/__param",
+            "/sys/module/vboxsf/srcversion",
+            "/sys/module/vboxsf/taint",
+            "/sys/module/vboxsf/uevent",
+            "/sys/module/vboxsf/version",
+            "/sys/module/vboxvideo",
+            "/sys/module/vboxvideo/coresize",
+            "/sys/module/vboxvideo/holders",
+            "/sys/module/vboxvideo/initsize",
+            "/sys/module/vboxvideo/initstate",
+            "/sys/module/vboxvideo/notes",
+            "/sys/module/vboxvideo/notes/.note.gnu.build-id",
+            "/sys/module/vboxvideo/refcnt",
+            "/sys/module/vboxvideo/sections",
+            "/sys/module/vboxvideo/sections/.data",
+            "/sys/module/vboxvideo/sections/.exit.text",
+            "/sys/module/vboxvideo/sections/.gnu.linkonce.this_module",
+            "/sys/module/vboxvideo/sections/.init.text",
+            "/sys/module/vboxvideo/sections/.note.gnu.build-id",
+            "/sys/module/vboxvideo/sections/.rodata.str1.1",
+            "/sys/module/vboxvideo/sections/.strtab",
+            "/sys/module/vboxvideo/sections/.symtab",
+            "/sys/module/vboxvideo/sections/.text",
+            "/sys/module/vboxvideo/srcversion",
+            "/sys/module/vboxvideo/taint",
+            "/sys/module/vboxvideo/uevent",
+            "/sys/module/vboxvideo/version",
+            "/system/app/bluestacksHome.apk",
+            "/system/bin/androVM-prop",
+            "/system/bin/androVM-vbox-sf",
+            "/system/bin/androVM_setprop",
+            "/system/bin/get_androVM_host",
+            "/system/bin/mount.vboxsf",
+            "/system/etc/init.androVM.sh",
+            "/system/etc/init.buildroid.sh",
+            "/system/lib/hw/audio.primary.vbox86.so",
+            "/system/lib/hw/camera.vbox86.so",
+            "/system/lib/hw/gps.vbox86.so",
+            "/system/lib/hw/gralloc.vbox86.so",
+            "/system/lib/hw/sensors.vbox86.so",
+            "/system/lib/modules/3.0.8-android-x86+/extra/vboxguest",
+            "/system/lib/modules/3.0.8-android-x86+/extra/vboxguest/vboxguest.ko",
+            "/system/lib/modules/3.0.8-android-x86+/extra/vboxsf",
+            "/system/lib/modules/3.0.8-android-x86+/extra/vboxsf/vboxsf.ko",
+            "/system/lib/vboxguest.ko",
+            "/system/lib/vboxsf.ko",
+            "/system/lib/vboxvideo.ko",
+            "/system/usr/idc/androVM_Virtual_Input.idc",
+            "/system/usr/keylayout/androVM_Virtual_Input.kl",
+
+            "/system/xbin/mount.vboxsf",
+            "/ueventd.android_x86.rc",
+            "/ueventd.vbox86.rc",
+            "/ueventd.goldfish.rc",
+            "/fstab.vbox86",
+            "/init.vbox86.rc",
+            "/init.goldfish.rc",
+
+            // ========针对原生Android模拟器 内核:goldfish===========
+            "/sys/module/goldfish_audio",
+            "/sys/module/goldfish_sync",
+
+            // ========针对蓝叠模拟器===========
+            "/data/app/com.bluestacks.appmart-1.apk",
+            "/data/app/com.bluestacks.BstCommandProcessor-1.apk",
+            "/data/app/com.bluestacks.help-1.apk",
+            "/data/app/com.bluestacks.home-1.apk",
+            "/data/app/com.bluestacks.s2p-1.apk",
+            "/data/app/com.bluestacks.searchapp-1.apk",
+            "/data/bluestacks.prop",
+            "/data/data/com.androVM.vmconfig",
+            "/data/data/com.bluestacks.accelerometerui",
+            "/data/data/com.bluestacks.appfinder",
+            "/data/data/com.bluestacks.appmart",
+            "/data/data/com.bluestacks.appsettings",
+            "/data/data/com.bluestacks.BstCommandProcessor",
+            "/data/data/com.bluestacks.bstfolder",
+            "/data/data/com.bluestacks.help",
+            "/data/data/com.bluestacks.home",
+            "/data/data/com.bluestacks.s2p",
+            "/data/data/com.bluestacks.searchapp",
+            "/data/data/com.bluestacks.settings",
+            "/data/data/com.bluestacks.setup",
+            "/data/data/com.bluestacks.spotlight",
+
+            // ========针对逍遥安卓模拟器===========
+            // 虚拟化网卡和pci,可能存在误判,不可靠
+//            "/sys/module/virtio_net",
+//            "/sys/module/virtio_pci",
+            "/data/data/com.microvirt.download",
+            "/data/data/com.microvirt.guide",
+            "/data/data/com.microvirt.installer",
+            "/data/data/com.microvirt.launcher",
+            "/data/data/com.microvirt.market",
+            "/data/data/com.microvirt.memuime",
+            "/data/data/com.microvirt.tools",
+
+            // ========针对Mumu模拟器===========
+            "/data/data/com.mumu.launcher",
+            "/data/data/com.mumu.store",
+            "/data/data/com.netease.mumu.cloner"
+    };
+
+
+    public static boolean hasEmulatorFile() {
+        for (String pipe : emulatorFiles) {
+            File qemu_socket = new File(pipe);
+            if (qemu_socket.exists()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 130 - 0
library_support/src/main/java/cn/yyxx/support/emulator/FindDebugger.java

@@ -0,0 +1,130 @@
+package cn.yyxx.support.emulator;
+
+import android.os.Debug;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class FindDebugger {
+
+    private static String tracerPid = "TracerPid";
+
+    /**
+     * Believe it or not, there are packers that use this...
+     */
+    public static boolean isBeingDebugged() {
+        return Debug.isDebuggerConnected();
+    }
+
+    /**
+     * This is used by Alibaba to detect someone ptracing the application.
+     * <p>
+     * Easy to circumvent, the usage ITW was a native thread constantly doing this every three seconds - and would cause
+     * the application to crash if it was detected.
+     *
+     * @return
+     * @throws IOException
+     */
+    public static boolean hasTracerPid() throws IOException {
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/self/status")), 1000);
+            String line;
+
+            while ((line = reader.readLine()) != null) {
+                if (line.length() > tracerPid.length()) {
+                    if (line.substring(0, tracerPid.length()).equalsIgnoreCase(tracerPid)) {
+                        if (Integer.decode(line.substring(tracerPid.length() + 1).trim()) > 0) {
+                            return true;
+                        }
+                        break;
+                    }
+                }
+            }
+
+        } catch (Exception exception) {
+            exception.printStackTrace();
+        } finally {
+            reader.close();
+        }
+        return false;
+    }
+
+    /**
+     * This was reversed from a sample someone was submitting to sandboxes for a thesis, can't find paper anymore
+     *
+     * @throws IOException
+     */
+    public static boolean hasAdbInEmulator() throws IOException {
+        boolean adbInEmulator = false;
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/net/tcp")), 1000);
+            String line;
+            // Skip column names
+            reader.readLine();
+
+            ArrayList<tcp> tcpList = new ArrayList<tcp>();
+
+            while ((line = reader.readLine()) != null) {
+                tcpList.add(tcp.create(line.split("\\W+")));
+            }
+
+            //reader.close();
+
+            // Adb is always bounce to 0.0.0.0 - though the port can change
+            // real devices should be != 127.0.0.1
+            int adbPort = -1;
+            for (tcp tcpItem : tcpList) {
+                if (tcpItem.localIp == 0) {
+                    adbPort = tcpItem.localPort;
+                    break;
+                }
+            }
+
+            if (adbPort != -1) {
+                for (tcp tcpItem : tcpList) {
+                    if ((tcpItem.localIp != 0) && (tcpItem.localPort == adbPort)) {
+                        adbInEmulator = true;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (reader != null)
+                reader.close();
+        }
+
+        return adbInEmulator;
+    }
+
+    public static class tcp {
+
+        public int id;
+        public long localIp;
+        public int localPort;
+        public int remoteIp;
+        public int remotePort;
+
+        static tcp create(String[] params) {
+            return new tcp(params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8],
+                    params[9], params[10], params[11], params[12], params[13], params[14]);
+        }
+
+        public tcp(String id, String localIp, String localPort, String remoteIp, String remotePort, String state,
+                   String tx_queue, String rx_queue, String tr, String tm_when, String retrnsmt, String uid,
+                   String timeout, String inode) {
+            this.id = Integer.parseInt(id, 16);
+            this.localIp = Long.parseLong(localIp, 16);
+            this.localPort = Integer.parseInt(localPort, 16);
+        }
+    }
+}

+ 247 - 0
library_support/src/main/java/cn/yyxx/support/emulator/FindEmulator.java

@@ -0,0 +1,247 @@
+package cn.yyxx.support.emulator;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class FindEmulator {
+
+    /**
+     * Default emulator phone numbers + VirusTotal
+     */
+    private static String[] known_numbers = {
+            "15555215554", "15555215556", "15555215558", "15555215560", "15555215562", "15555215564",
+            "15555215566", "15555215568", "15555215570", "15555215572", "15555215574", "15555215576",
+            "15555215578", "15555215580", "15555215582", "15555215584",};
+    /**
+     * Default emulator id
+     */
+    private static String[] known_device_ids = {"000000000000000", "e21833235b6eef10", "012345678912345"};
+    private static String[] known_imsi_ids = {"310260000000000" // Default imsi id
+    };
+    private static String[] known_pipes = {"/dev/socket/qemud", "/dev/qemu_pipe"};
+    private static String[] known_files = {"/system/lib/libc_malloc_debug_qemu.so", "/sys/qemu_trace", "/system/bin/qemu-props"};
+    private static String[] known_geny_files = {"/dev/socket/genyd", "/dev/socket/baseband_genyd"};
+    private static String[] known_qemu_drivers = {"goldfish"};
+    /**
+     * Known props, in the format of [property name, value to seek] if value to seek is null, then it is assumed that
+     * the existence of this property (anything not null) indicates the QEmu environment.
+     */
+    private static Property[] known_props = {new Property("init.svc.qemud", null),
+            new Property("init.svc.qemu-props", null), new Property("qemu.hw.mainkeys", null),
+            new Property("qemu.sf.fake_camera", null), new Property("qemu.sf.lcd_density", null),
+            new Property("ro.bootloader", "unknown"), new Property("ro.bootmode", "unknown"),
+            new Property("ro.hardware", "goldfish"), new Property("ro.kernel.android.qemud", null),
+            new Property("ro.kernel.qemu.gles", null), new Property("ro.kernel.qemu", "1"),
+            new Property("ro.product.device", "generic"), new Property("ro.product.model", "sdk"),
+            new Property("ro.product.name", "sdk"),
+            // Need to double check that an "empty" string ("") returns null
+            new Property("ro.serialno", null)};
+    /**
+     * The "known" props have the potential for false-positiving due to interesting (see: poorly) made Chinese
+     * devices/odd ROMs. Keeping this threshold low will result in better QEmu detection with possible side affects.
+     */
+    private static int MIN_PROPERTIES_THRESHOLD = 0x5;
+
+    static {
+        // This is only valid for arm
+//        System.loadLibrary("anti");
+    }
+
+    /**
+     * Check the existence of known pipes used by the Android QEmu environment.
+     *
+     * @return {@code true} if any pipes where found to exist or {@code false} if not.
+     */
+    public static boolean hasPipes() {
+        for (String pipe : known_pipes) {
+            File qemu_socket = new File(pipe);
+            if (qemu_socket.exists()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Check the existence of known files used by the Android QEmu environment.
+     *
+     * @return {@code true} if any files where found to exist or {@code false} if not.
+     */
+    public static boolean hasQEmuFiles() {
+        for (String pipe : known_files) {
+            File qemu_file = new File(pipe);
+            if (qemu_file.exists()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Check the existence of known files used by the Genymotion environment.
+     *
+     * @return {@code true} if any files where found to exist or {@code false} if not.
+     */
+    public static boolean hasGenyFiles() {
+        for (String file : known_geny_files) {
+            File geny_file = new File(file);
+            if (geny_file.exists()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Reads in the driver file, then checks a list for known QEmu drivers.
+     *
+     * @return {@code true} if any known drivers where found to exist or {@code false} if not.
+     */
+    public static boolean hasQEmuDrivers() {
+        for (File drivers_file : new File[]{new File("/proc/tty/drivers"), new File("/proc/cpuinfo")}) {
+            if (drivers_file.exists() && drivers_file.canRead()) {
+                // We don't care to read much past things since info we care about should be inside here
+                byte[] data = new byte[1024];
+                try {
+                    InputStream is = new FileInputStream(drivers_file);
+                    is.read(data);
+                    is.close();
+                } catch (Exception exception) {
+                    exception.printStackTrace();
+                }
+
+                String driver_data = new String(data);
+                for (String known_qemu_driver : FindEmulator.known_qemu_drivers) {
+                    if (driver_data.indexOf(known_qemu_driver) != -1) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    public static boolean hasKnownPhoneNumber(Context context) {
+        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+
+        String phoneNumber = telephonyManager.getLine1Number();
+
+        for (String number : known_numbers) {
+            if (number.equalsIgnoreCase(phoneNumber)) {
+                return true;
+            }
+
+        }
+        return false;
+    }
+
+    public static boolean hasKnownDeviceId(Context context) {
+        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+
+        String deviceId = telephonyManager.getDeviceId();
+
+        for (String known_deviceId : known_device_ids) {
+            if (known_deviceId.equalsIgnoreCase(deviceId)) {
+                return true;
+            }
+
+        }
+        return false;
+    }
+
+    public static boolean hasKnownImsi(Context context) {
+        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        String imsi = telephonyManager.getSubscriberId();
+
+        for (String known_imsi : known_imsi_ids) {
+            if (known_imsi.equalsIgnoreCase(imsi)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean hasEmulatorBuild(Context context) {
+        String BOARD = android.os.Build.BOARD; // The name of the underlying board, like "unknown".
+        // This appears to occur often on real hardware... that's sad
+        // String BOOTLOADER = android.os.Build.BOOTLOADER; // The system bootloader version number.
+        String BRAND = android.os.Build.BRAND; // The brand (e.g., carrier) the software is customized for, if any.
+        // "generic"
+        String DEVICE = android.os.Build.DEVICE; // The name of the industrial design. "generic"
+        String HARDWARE = android.os.Build.HARDWARE; // The name of the hardware (from the kernel command line or
+        // /proc). "goldfish"
+        String MODEL = android.os.Build.MODEL; // The end-user-visible name for the end product. "sdk"
+        String PRODUCT = android.os.Build.PRODUCT; // The name of the overall product.
+        return (BOARD.compareTo("unknown") == 0) /* || (BOOTLOADER.compareTo("unknown") == 0) */
+                || (BRAND.compareTo("generic") == 0) || (DEVICE.compareTo("generic") == 0)
+                || (MODEL.compareTo("sdk") == 0) || (PRODUCT.compareTo("sdk") == 0)
+                || (HARDWARE.compareTo("goldfish") == 0);
+    }
+
+    public static boolean isOperatorNameAndroid(Context paramContext) {
+        String szOperatorName = ((TelephonyManager) paramContext.getSystemService(Context.TELEPHONY_SERVICE)).getNetworkOperatorName();
+        return szOperatorName.equalsIgnoreCase("android");
+    }
+
+//    public native static int qemuBkpt();
+
+    public static boolean checkQemuBreakpoint() {
+        boolean hit_breakpoint = false;
+
+        // Potentially you may want to see if this is a specific value
+//        int result = qemuBkpt();
+
+//        if (result > 0) {
+//            hit_breakpoint = true;
+//        }
+//
+        return hit_breakpoint;
+    }
+
+    public static boolean hasEmulatorAdb() {
+        try {
+            return FindDebugger.hasAdbInEmulator();
+        } catch (Exception exception) {
+            //exception.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Will query specific system properties to try and fingerprint a QEmu environment. A minimum threshold must be met
+     * in order to prevent false positives.
+     *
+     * @param context A {link Context} object for the Android application.
+     * @return {@code true} if enough properties where found to exist or {@code false} if not.
+     */
+    public boolean hasQEmuProps(Context context) {
+        int found_props = 0;
+
+        for (Property property : known_props) {
+            String property_value = Utilities.getProp(context, property.name);
+            // See if we expected just a non-null
+            if ((property.seek_value == null) && (property_value != null)) {
+                found_props++;
+            }
+            // See if we expected a value to seek
+            if ((property.seek_value != null) && (property_value.contains(property.seek_value))) {
+                found_props++;
+            }
+
+        }
+
+        return found_props >= MIN_PROPERTIES_THRESHOLD;
+    }
+}

+ 15 - 0
library_support/src/main/java/cn/yyxx/support/emulator/Property.java

@@ -0,0 +1,15 @@
+package cn.yyxx.support.emulator;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class Property {
+    public String name;
+    public String seek_value;
+
+    public Property(String name, String seek_value) {
+        this.name = name;
+        this.seek_value = seek_value;
+    }
+}

+ 51 - 0
library_support/src/main/java/cn/yyxx/support/emulator/Utilities.java

@@ -0,0 +1,51 @@
+package cn.yyxx.support.emulator;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class Utilities {
+    /**
+     * Method to reflectively invoke the SystemProperties.get command - which is the equivalent to the adb shell getProp
+     * command.
+     *
+     * @param context  A {@link Context} object used to get the proper ClassLoader (just needs to be Application Context
+     *                 object)
+     * @param property A {@code String} object for the property to retrieve.
+     * @return {@code String} value of the property requested.
+     */
+    public static String getProp(Context context, String property) {
+        try {
+            ClassLoader classLoader = context.getClassLoader();
+            Class<?> systemProperties = classLoader.loadClass("android.os.SystemProperties");
+
+            Method get = systemProperties.getMethod("get", String.class);
+
+            Object[] params = new Object[1];
+            params[0] = new String(property);
+
+            return (String) get.invoke(systemProperties, params);
+        } catch (IllegalArgumentException iAE) {
+            throw iAE;
+        } catch (Exception exception) {
+            throw null;
+        }
+    }
+
+    public static boolean hasPackageNameInstalled(Context context, String packageName) {
+        PackageManager packageManager = context.getPackageManager();
+
+        // In theory, if the package installer does not throw an exception, package exists
+        try {
+            packageManager.getInstallerPackageName(packageName);
+            return true;
+        } catch (IllegalArgumentException exception) {
+            return false;
+        }
+    }
+}

+ 91 - 0
library_support/src/main/java/cn/yyxx/support/emulator/newfunc/CommandUtils.java

@@ -0,0 +1,91 @@
+package cn.yyxx.support.emulator.newfunc;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/04/28
+ */
+public class CommandUtils {
+    private CommandUtils() {
+    }
+
+    public static String getProperty(String propName) {
+        String value = "";
+        Object roSecureObj;
+        try {
+            roSecureObj = Class.forName("android.os.SystemProperties").getMethod("get", String.class).invoke(null, propName);
+            if (roSecureObj != null) {
+                value = (String) roSecureObj;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return value;
+    }
+
+    public static String exec(String command) {
+        BufferedOutputStream bufferedOutputStream = null;
+        BufferedInputStream bufferedInputStream = null;
+        Process process = null;
+        try {
+            process = Runtime.getRuntime().exec("sh");
+            bufferedOutputStream = new BufferedOutputStream(process.getOutputStream());
+
+            bufferedInputStream = new BufferedInputStream(process.getInputStream());
+            bufferedOutputStream.write(command.getBytes());
+            bufferedOutputStream.write('\n');
+            bufferedOutputStream.flush();
+            bufferedOutputStream.close();
+
+            process.waitFor();
+
+            return getStrFromBufferInputSteam(bufferedInputStream);
+        } catch (Exception e) {
+            return "";
+        } finally {
+            if (bufferedOutputStream != null) {
+                try {
+                    bufferedOutputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (bufferedInputStream != null) {
+                try {
+                    bufferedInputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (process != null) {
+                process.destroy();
+            }
+        }
+    }
+
+    private static String getStrFromBufferInputSteam(BufferedInputStream bufferedInputStream) {
+        if (null == bufferedInputStream) {
+            return "";
+        }
+        int bufferSize = 512;
+        byte[] buffer = new byte[bufferSize];
+        StringBuilder result = new StringBuilder();
+        try {
+            while (true) {
+                int read = bufferedInputStream.read(buffer);
+                if (read > 0) {
+                    result.append(new String(buffer, 0, read));
+                }
+                if (read < bufferSize) {
+                    break;
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return result.toString();
+    }
+}

+ 350 - 0
library_support/src/main/java/cn/yyxx/support/emulator/newfunc/EmulatorCheck.java

@@ -0,0 +1,350 @@
+package cn.yyxx.support.emulator.newfunc;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.text.TextUtils;
+
+import static android.content.Context.SENSOR_SERVICE;
+
+
+public class EmulatorCheck {
+
+    //可能是模拟器
+    private static final int RESULT_MAYBE_EMULATOR = 0;
+    //模拟器
+    private static final int RESULT_EMULATOR = 1;
+    //可能是真机
+    private static final int RESULT_UNKNOWN = 2;
+
+    private EmulatorCheck() {
+
+    }
+
+    public static boolean readSysProperty(Context context) {
+
+        int suspectCount = 0;
+
+        //检测硬件名称
+        switch (checkFeaturesByHardware()) {
+            case RESULT_MAYBE_EMULATOR:
+                ++suspectCount;
+                break;
+            case RESULT_EMULATOR:
+                return true;
+        }
+
+        //检测渠道
+        switch (checkFeaturesByFlavor()) {
+            case RESULT_MAYBE_EMULATOR:
+                ++suspectCount;
+                break;
+            case RESULT_EMULATOR:
+                return true;
+        }
+
+        //检测设备型号
+        switch (checkFeaturesByModel()) {
+            case RESULT_MAYBE_EMULATOR:
+                ++suspectCount;
+                break;
+            case RESULT_EMULATOR:
+                return true;
+        }
+
+        //检测硬件制造商
+        switch (checkFeaturesByManufacturer()) {
+            case RESULT_MAYBE_EMULATOR:
+                ++suspectCount;
+                break;
+            case RESULT_EMULATOR:
+                return true;
+        }
+
+        //检测主板名称
+        switch (checkFeaturesByBoard()) {
+            case RESULT_MAYBE_EMULATOR:
+                ++suspectCount;
+                break;
+            case RESULT_EMULATOR:
+                return true;
+        }
+
+        //检测主板平台
+        switch (checkFeaturesByPlatform()) {
+            case RESULT_MAYBE_EMULATOR:
+                ++suspectCount;
+                break;
+            case RESULT_EMULATOR:
+                return true;
+        }
+
+        //检测基带信息
+        switch (checkFeaturesByBaseBand()) {
+            case RESULT_MAYBE_EMULATOR:
+                suspectCount += 2;//模拟器基带信息为null的情况概率相当大
+                break;
+            case RESULT_EMULATOR:
+                return true;
+        }
+
+        //检测传感器数量
+        int sensorNumber = getSensorNumber(context);
+        if (sensorNumber <= 7) ++suspectCount;
+
+        //检测已安装第三方应用数量
+        int userAppNumber = getUserAppNumber();
+        if (userAppNumber <= 5) ++suspectCount;
+
+        //检测是否支持闪光灯
+        boolean supportCameraFlash = supportCameraFlash(context);
+        if (!supportCameraFlash) ++suspectCount;
+        //检测是否支持相机
+        boolean supportCamera = supportCamera(context);
+        if (!supportCamera) ++suspectCount;
+        //检测是否支持蓝牙
+        boolean supportBluetooth = supportBluetooth(context);
+        if (!supportBluetooth) ++suspectCount;
+
+        //检测光线传感器
+        boolean hasLightSensor = hasLightSensor(context);
+        if (!hasLightSensor) {
+            ++suspectCount;
+        }
+
+        //检测进程组信息
+        if (checkFeaturesByCgroup() == RESULT_MAYBE_EMULATOR) {
+            ++suspectCount;
+        }
+        //嫌疑值大于3,认为是模拟器
+        return suspectCount > 3;
+    }
+
+    private static int getUserAppNum(String userApps) {
+        if (TextUtils.isEmpty(userApps)) return 0;
+        String[] result = userApps.split("package:");
+        return result.length;
+    }
+
+    private static String getProperty(String propName) {
+        String property = CommandUtils.getProperty(propName);
+        return TextUtils.isEmpty(property) ? null : property;
+    }
+
+    /**
+     * 特征参数-硬件名称
+     *
+     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
+     */
+    private static int checkFeaturesByHardware() {
+        String hardware = getProperty("ro.hardware");
+        if (TextUtils.isEmpty(hardware))
+            return RESULT_MAYBE_EMULATOR;
+        int result;
+        String tempValue = hardware.toLowerCase();
+        switch (tempValue) {
+            case "ttvm"://天天模拟器
+            case "nox"://夜神模拟器
+            case "cancro"://网易MUMU模拟器
+            case "intel"://逍遥模拟器
+            case "vbox":
+            case "vbox86"://腾讯手游助手
+            case "android_x86"://雷电模拟器
+                result = RESULT_EMULATOR;
+                break;
+            default:
+                result = RESULT_UNKNOWN;
+                break;
+        }
+        return result;
+    }
+
+    /**
+     * 特征参数-渠道
+     *
+     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
+     */
+    private static int checkFeaturesByFlavor() {
+        String flavor = getProperty("ro.build.flavor");
+        if (TextUtils.isEmpty(flavor))
+            return RESULT_MAYBE_EMULATOR;
+        int result;
+        String tempValue = flavor.toLowerCase();
+        if (tempValue.contains("vbox")) {
+            result = RESULT_EMULATOR;
+        } else if (tempValue.contains("sdk_gphone")) {
+            result = RESULT_EMULATOR;
+        } else {
+            result = RESULT_UNKNOWN;
+        }
+        return result;
+    }
+
+    /**
+     * 特征参数-设备型号
+     *
+     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
+     */
+    private static int checkFeaturesByModel() {
+        String model = getProperty("ro.product.model");
+        if (TextUtils.isEmpty(model)) {
+            return RESULT_MAYBE_EMULATOR;
+        }
+        int result;
+        String tempValue = model.toLowerCase();
+        if (tempValue.contains("google_sdk")) {
+            result = RESULT_EMULATOR;
+        } else if (tempValue.contains("emulator")) {
+            result = RESULT_EMULATOR;
+        } else if (tempValue.contains("android sdk built for x86")) {
+            result = RESULT_EMULATOR;
+        } else {
+            result = RESULT_UNKNOWN;
+        }
+        return result;
+    }
+
+    /**
+     * 特征参数-硬件制造商
+     *
+     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
+     */
+    private static int checkFeaturesByManufacturer() {
+        String manufacturer = getProperty("ro.product.manufacturer");
+        if (TextUtils.isEmpty(manufacturer)) {
+            return RESULT_MAYBE_EMULATOR;
+        }
+        int result;
+        String tempValue = manufacturer.toLowerCase();
+        if (tempValue.contains("genymotion")) {
+            result = RESULT_EMULATOR;
+        } else if (tempValue.contains("netease")) {
+            result = RESULT_EMULATOR;//网易MUMU模拟器
+        } else {
+            result = RESULT_UNKNOWN;
+        }
+        return result;
+    }
+
+    /**
+     * 特征参数-主板名称
+     *
+     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
+     */
+    private static int checkFeaturesByBoard() {
+        String board = getProperty("ro.product.board");
+        if (TextUtils.isEmpty(board)) {
+            return RESULT_MAYBE_EMULATOR;
+        }
+        int result;
+        String tempValue = board.toLowerCase();
+        if (tempValue.contains("android")) {
+            result = RESULT_EMULATOR;
+        } else if (tempValue.contains("goldfish")) {
+            result = RESULT_EMULATOR;
+        } else {
+            result = RESULT_UNKNOWN;
+        }
+        return result;
+    }
+
+    /**
+     * 特征参数-主板平台
+     *
+     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
+     */
+    private static int checkFeaturesByPlatform() {
+        String platform = getProperty("ro.board.platform");
+        if (TextUtils.isEmpty(platform)) {
+            return RESULT_MAYBE_EMULATOR;
+        }
+        int result;
+        String tempValue = platform.toLowerCase();
+        if (tempValue.contains("android")) {
+            result = RESULT_EMULATOR;
+        } else {
+            result = RESULT_UNKNOWN;
+        }
+        return result;
+    }
+
+    /**
+     * 特征参数-基带信息
+     *
+     * @return 0表示可能是模拟器,1表示模拟器,2表示可能是真机
+     */
+    private static int checkFeaturesByBaseBand() {
+        String baseBandVersion = getProperty("gsm.version.baseband");
+        if (TextUtils.isEmpty(baseBandVersion)) {
+            return RESULT_MAYBE_EMULATOR;
+        }
+        int result;
+        if (baseBandVersion.contains("1.0.0.0")) {
+            result = RESULT_EMULATOR;
+        } else {
+            result = RESULT_UNKNOWN;
+        }
+        return result;
+    }
+
+    /**
+     * 获取传感器数量
+     */
+    private static int getSensorNumber(Context context) {
+        SensorManager sm = (SensorManager) context.getSystemService(SENSOR_SERVICE);
+        return sm.getSensorList(Sensor.TYPE_ALL).size();
+    }
+
+    /**
+     * 获取已安装第三方应用数量
+     */
+    private static int getUserAppNumber() {
+        String userApps = CommandUtils.exec("pm list package -3");
+        return getUserAppNum(userApps);
+    }
+
+    /**
+     * 是否支持相机
+     */
+    private static boolean supportCamera(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
+    }
+
+    /**
+     * 是否支持闪光灯
+     */
+    private static boolean supportCameraFlash(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+    }
+
+    /**
+     * 是否支持蓝牙
+     */
+    private static boolean supportBluetooth(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
+    }
+
+    /**
+     * 判断是否存在光传感器来判断是否为模拟器
+     * 部分真机也不存在温度和压力传感器。其余传感器模拟器也存在。
+     *
+     * @return false为模拟器
+     */
+    private static boolean hasLightSensor(Context context) {
+        SensorManager sensorManager = (SensorManager) context.getSystemService(SENSOR_SERVICE);
+        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); //光线传感器
+        return null != sensor;
+    }
+
+    /**
+     * 特征参数-进程组信息
+     */
+    private static int checkFeaturesByCgroup() {
+        String filter = CommandUtils.exec("cat /proc/self/cgroup");
+        if (TextUtils.isEmpty(filter)) {
+            return RESULT_MAYBE_EMULATOR;
+        }
+        return RESULT_UNKNOWN;
+    }
+}

+ 133 - 0
library_support/src/main/java/cn/yyxx/support/encryption/Base64Utils.java

@@ -0,0 +1,133 @@
+package cn.yyxx.support.encryption;
+
+import android.text.TextUtils;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class Base64Utils {
+    private Base64Utils() {
+        /* cannot be instantiated */
+        throw new UnsupportedOperationException("cannot be instantiated");
+    }
+
+    private static char[] base64EncodeChars = new char[]
+            {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+                    'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+                    'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
+                    '6', '7', '8', '9', '+', '/'};
+    private static byte[] base64DecodeChars = new byte[]
+            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+                    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53,
+                    54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+                    12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29,
+                    30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1,
+                    -1, -1, -1};
+
+    /**
+     * 加密
+     *
+     * @param data 明文byte数据
+     * @return base64后的数据
+     */
+    public static String encode(byte[] data) {
+        if (data == null || data.length < 1) return "";
+        StringBuilder sb = new StringBuilder();
+        int len = data.length;
+        int i = 0;
+        int b1, b2, b3;
+        while (i < len) {
+            b1 = data[i++] & 0xff;
+            if (i == len) {
+                sb.append(base64EncodeChars[b1 >>> 2]);
+                sb.append(base64EncodeChars[(b1 & 0x3) << 4]);
+                sb.append("==");
+                break;
+            }
+            b2 = data[i++] & 0xff;
+            if (i == len) {
+                sb.append(base64EncodeChars[b1 >>> 2]);
+                sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
+                sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);
+                sb.append("=");
+                break;
+            }
+            b3 = data[i++] & 0xff;
+            sb.append(base64EncodeChars[b1 >>> 2]);
+            sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
+            sb.append(base64EncodeChars[((b2 & 0x0f) << 2) | ((b3 & 0xc0) >>> 6)]);
+            sb.append(base64EncodeChars[b3 & 0x3f]);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 解密
+     *
+     * @param str base64数据
+     * @return 明文的byte数据
+     */
+    public static byte[] decode(String str) {
+        if (TextUtils.isEmpty(str)) return null;
+
+        try {
+            return decodePrivate(str);
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 解密
+     *
+     * @param str base64数据
+     * @return 明文的byte数据
+     */
+    private static byte[] decodePrivate(String str) throws UnsupportedEncodingException {
+        if (TextUtils.isEmpty(str)) return null;
+
+        StringBuilder sb = new StringBuilder();
+        byte[] data = str.getBytes("US-ASCII");
+        int len = data.length;
+        int i = 0;
+        int b1, b2, b3, b4;
+        while (i < len) {
+            do {
+                b1 = base64DecodeChars[data[i++]];
+            } while (i < len && b1 == -1);
+            if (b1 == -1)
+                break;
+            do {
+                b2 = base64DecodeChars[data[i++]];
+            } while (i < len && b2 == -1);
+            if (b2 == -1)
+                break;
+            sb.append((char) ((b1 << 2) | ((b2 & 0x30) >>> 4)));
+
+            do {
+                b3 = data[i++];
+                if (b3 == 61)
+                    return sb.toString().getBytes("iso8859-1");
+                b3 = base64DecodeChars[b3];
+            } while (i < len && b3 == -1);
+            if (b3 == -1)
+                break;
+            sb.append((char) (((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2)));
+
+            do {
+                b4 = data[i++];
+                if (b4 == 61)
+                    return sb.toString().getBytes("iso8859-1");
+                b4 = base64DecodeChars[b4];
+            } while (i < len && b4 == -1);
+            if (b4 == -1)
+                break;
+            sb.append((char) (((b3 & 0x03) << 6) | b4));
+        }
+        return sb.toString().getBytes("iso8859-1");
+    }
+}

+ 102 - 0
library_support/src/main/java/cn/yyxx/support/encryption/GzipUtils.java

@@ -0,0 +1,102 @@
+package cn.yyxx.support.encryption;
+
+import android.text.TextUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * @author #Suyghur,
+ * Created on 2021/1/27
+ */
+public class GzipUtils {
+
+    public static String compress(String str) {
+        if (TextUtils.isEmpty(str)) {
+            return str;
+        }
+
+        ByteArrayOutputStream bos = null;
+        GZIPOutputStream gos = null;
+
+        try {
+            //创建一个新的输出流
+            bos = new ByteArrayOutputStream();
+            //使用默认缓冲区创建新的输出流
+            gos = new GZIPOutputStream(bos);
+            //将字节写入
+            gos.write(str.getBytes("UTF-8"));
+            return bos.toString("ISO-8859-1");
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (gos != null) {
+                try {
+                    gos.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            if (bos != null) {
+                try {
+                    bos.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return str;
+    }
+
+    public static String deCompress(String str) {
+        if (TextUtils.isEmpty(str)) {
+            return str;
+        }
+
+        ByteArrayOutputStream bos = null;
+        ByteArrayInputStream bis = null;
+        GZIPInputStream gis = null;
+        try {
+            bos = new ByteArrayOutputStream();
+            bis = new ByteArrayInputStream(str.getBytes("ISO-8859-1"));
+            gis = new GZIPInputStream(bis);
+            byte[] buffer = new byte[256];
+            int count = 0;
+            while ((count = gis.read(buffer)) > 0) {
+                bos.write(buffer, 0, count);
+            }
+            return bos.toString("UTF-8");
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (gis != null) {
+                try {
+                    gis.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            if (bis != null) {
+                try {
+                    bis.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+
+            if (bos != null) {
+                try {
+                    bos.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return str;
+    }
+}

+ 149 - 0
library_support/src/main/java/cn/yyxx/support/encryption/Md5Utils.java

@@ -0,0 +1,149 @@
+package cn.yyxx.support.encryption;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class Md5Utils {
+
+    private static final String ALGORITHM = "MD5";
+
+    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+    private Md5Utils() {
+        /* cannot be instantiated */
+        throw new UnsupportedOperationException("cannot be instantiated");
+    }
+
+    /**
+     * 加密
+     *
+     * @param str 明文
+     * @return MD5密文
+     */
+    public static String encodeByMD5(String str) {
+        if (str == null) {
+            return null;
+        }
+        try {
+            MessageDigest messageDigest = MessageDigest.getInstance(ALGORITHM);
+            messageDigest.update(str.getBytes());
+            return getFormattedText(messageDigest.digest());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    /**
+     * 获取文件Md5 VALUE(返回31位,有缺陷,建议用getFileMD5)
+     *
+     * @param file 文件
+     * @return 文件的MD5数值
+     */
+    public static String getMd5ByFile(File file) {
+        String value = null;
+        FileInputStream in = null;
+        try {
+            in = new FileInputStream(file);
+            MappedByteBuffer byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
+            MessageDigest md5 = MessageDigest.getInstance("MD5");
+            md5.update(byteBuffer);
+            BigInteger bi = new BigInteger(1, md5.digest());
+            value = bi.toString(16);
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (null != in) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return value;
+    }
+
+
+    /**
+     * 获取文件md5 (返回32位)
+     *
+     * @param file
+     * @return
+     */
+    public static String getFileMD5(File file) {
+        if (!file.exists() || !file.isFile()) {
+            return "";
+        }
+
+        byte[] buffer = new byte[2048];
+        try {
+            MessageDigest digest = MessageDigest.getInstance("MD5");
+            FileInputStream in = new FileInputStream(file);
+            while (true) {
+                int len = in.read(buffer, 0, 2048);
+                if (len != -1) {
+                    digest.update(buffer, 0, len);
+                } else {
+                    break;
+                }
+            }
+            in.close();
+
+            byte[] md5Bytes = digest.digest();
+            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();
+
+            //String hash = new BigInteger(1,digest.digest()).toString(16);
+            //return hash;
+
+        } catch (NoSuchAlgorithmException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            return "";
+        } catch (FileNotFoundException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            return "";
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    /**
+     * Takes the raw bytes from the digest and formats them correct.
+     *
+     * @param bytes the raw bytes from the digest.
+     * @return the formatted bytes.
+     */
+    private static String getFormattedText(byte[] bytes) {
+        int len = bytes.length;
+        StringBuilder buf = new StringBuilder(len * 2);
+        // 把密文转换成十六进制的字符串形式
+        for (int j = 0; j < len; j++) {
+            buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
+            buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
+        }
+        return buf.toString();
+    }
+}

+ 35 - 0
library_support/src/main/java/cn/yyxx/support/encryption/aes/AesCommon.java

@@ -0,0 +1,35 @@
+package cn.yyxx.support.encryption.aes;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class AesCommon {
+
+    protected static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5Padding";
+
+    public static final int AES_IV_LENGTH = 16;
+
+    /**
+     * reverse aesKey's first 16 bytes to generate IV, add number 1 if not enough 16 bytes
+     *
+     * @param aesKey
+     * @return
+     */
+    public static byte[] generateIV(byte[] aesKey) {
+        byte[] ivBytes = new byte[AES_IV_LENGTH];
+
+        int j = 0;
+        int i = aesKey.length - 1;
+        while (i >= 0 && j < AES_IV_LENGTH) {
+            ivBytes[j] = aesKey[i];
+            i--;
+            j++;
+        }
+        while (j < AES_IV_LENGTH) {
+            ivBytes[j] = 1;
+            j++;
+        }
+        return ivBytes;
+    }
+}

+ 54 - 0
library_support/src/main/java/cn/yyxx/support/encryption/aes/AesDecrypt.java

@@ -0,0 +1,54 @@
+package cn.yyxx.support.encryption.aes;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class AesDecrypt extends AesCommon {
+    private final byte[] aesKey;
+    private final byte[] iv;
+    private final Cipher aesCipher;
+
+    public AesDecrypt(byte[] aesKey) {
+        if (aesKey == null || !(aesKey.length == 16 || aesKey.length == 32)) {
+            throw new RuntimeException("aes key should be 16 bytes(128bits) or 32 bytes(256bits)");
+        }
+        this.aesKey = aesKey;
+        this.iv = generateIV(this.aesKey);
+        try {
+            this.aesCipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        } catch (NoSuchPaddingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public byte[] decrypt(byte[] encryptData, int offset, int length)
+            throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+        try {
+            aesCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(this.aesKey, "AES"), new IvParameterSpec(this.iv, 0, this.iv.length));
+            return aesCipher.doFinal(encryptData, offset, length);
+        } catch (InvalidKeyException e) {
+            throw e;
+        } catch (InvalidAlgorithmParameterException e) {
+            throw e;
+        } catch (IllegalBlockSizeException e) {
+            throw e;
+        } catch (BadPaddingException e) {
+            throw e;
+        }
+    }
+
+}

+ 54 - 0
library_support/src/main/java/cn/yyxx/support/encryption/aes/AesEncrypt.java

@@ -0,0 +1,54 @@
+package cn.yyxx.support.encryption.aes;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class AesEncrypt extends AesCommon {
+
+    private final byte[] aesKey;
+    private final byte[] iv;
+    private final Cipher aesCipher;
+
+    public AesEncrypt(byte[] aesKey) {
+        if (aesKey == null || !(aesKey.length == 16 || aesKey.length == 32)) {
+            throw new RuntimeException("aes key should be 16 bytes(128bits) or 32 bytes(256bits)");
+        }
+        this.aesKey = aesKey;
+        this.iv = generateIV(this.aesKey);
+        try {
+            this.aesCipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        } catch (NoSuchPaddingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public byte[] encrypt(byte[] buf, int offset, int length)
+            throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
+        try {
+            aesCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(this.aesKey, "AES"), new IvParameterSpec(this.iv, 0, this.iv.length));
+            return aesCipher.doFinal(buf, offset, length);
+        } catch (InvalidKeyException e) {
+            throw e;
+        } catch (InvalidAlgorithmParameterException e) {
+            throw e;
+        } catch (IllegalBlockSizeException e) {
+            throw e;
+        } catch (BadPaddingException e) {
+            throw e;
+        }
+    }
+}

+ 44 - 0
library_support/src/main/java/cn/yyxx/support/encryption/aes/AesUtils.java

@@ -0,0 +1,44 @@
+package cn.yyxx.support.encryption.aes;
+
+
+import cn.yyxx.support.encryption.Base64Utils;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class AesUtils {
+
+    /**
+     * 加密
+     *
+     * @param aesKey
+     * @param content
+     * @return
+     */
+    public static String encrypt(String aesKey, String content) throws Exception {
+        byte[] keyBytes = aesKey.getBytes("UTF-8");
+        byte[] contentBytes = content.getBytes("UTF-8");
+
+        //AES encrypt
+        AesEncrypt aesEncrypt = new AesEncrypt(keyBytes);
+        byte[] encryptedContent = aesEncrypt.encrypt(contentBytes, 0, contentBytes.length);
+        return Base64Utils.encode(encryptedContent);
+    }
+
+    /**
+     * 解密
+     *
+     * @param aesKey
+     * @param content
+     * @return
+     */
+    public static String decrypt(String aesKey, String content) throws Exception {
+        byte[] keyBytes = aesKey.getBytes("UTF-8");
+        byte[] contentBytes = Base64Utils.decode(content);
+        //AES decrypt
+        AesDecrypt aesDecrypt = new AesDecrypt(keyBytes);
+        byte[] decryptedContent = aesDecrypt.decrypt(contentBytes, 0, contentBytes.length);
+        return new String(decryptedContent);
+    }
+}

+ 525 - 0
library_support/src/main/java/cn/yyxx/support/encryption/rsa/RsaUtils.java

@@ -0,0 +1,525 @@
+package cn.yyxx.support.encryption.rsa;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.crypto.Cipher;
+
+import cn.yyxx.support.encryption.Base64Utils;
+
+/**
+ * @author #Suyghur.
+ * Created on 2020/7/29
+ */
+public class RsaUtils {
+
+    private RsaUtils() {
+        /* cannot be instantiated */
+        throw new UnsupportedOperationException("cannot be instantiated");
+    }
+
+    public static final String RSA = "RSA";
+    /**
+     * 加密填充方式
+     */
+    public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";
+    /**
+     * 秘钥默认长度
+     */
+    public static final int DEFAULT_KEY_SIZE = 2048;
+    /**
+     * 当要加密的内容超过bufferSize,则采用partSplit进行分块加密
+     */
+    public static final byte[] DEFAULT_SPLIT = "#PART#".getBytes();
+    /**
+     * 当前秘钥支持加密的最大字节数
+     */
+    public static final int DEFAULT_BUFFERSIZE = (DEFAULT_KEY_SIZE / 8) - 11;
+
+    /**
+     * 服务器给的(融合公钥)
+     */
+
+//    public static final String RSA_PUBLIC_1024_X509_PEM = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCDlV1JzHzuZD8AwezcbyK+6TsVL00TVJsYB5Jy4lI5TrUf+vn0fsi40MxAwKCdUagjjkQKh7Fv2TgBUlJoJuZiUdXwcCJp+XoBDEHMJreRs2L/1RiutCiighb+FYeCMXIwuIOcdkoRwD7eWOL0C1D7RSI+lrk52k/OaUyK+1/lHQIDAQAB";
+    public static final String RSA_PUBLIC_1024_X509_PEM = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALHEovdQ9uVH9WTLh5PprTUZAJPdAPEHfvlsMLEiAenwecn5jNkKcI7MzDj/T963iLnjDooRcPwfyokgA75Ff2kCAwEAAQ==";
+
+    public static final String RSA_PRIVATE_1024_X509_PEM = "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAscSi91D25Uf1ZMuHk+mtNRkAk90A8Qd++WwwsSIB6fB5yfmM2QpwjszMOP9P3reIueMOihFw/B/KiSADvkV/aQIDAQABAkEAql+2fOfpKUg1JXx3nUiZi8lBp3VqAJfZlv2dETdxtB4AAVr/h+3dAKaA0+GG+a9BFX0ITr7eiXbq0B26+NAGgQIhANWW7339dvB7ssn0G2xmpe6DN4xCMTeETYduX/m5QMAZAiEA1RDYD8iADR3qn2LyeeQGhX5fpPELyNzbR9L0Qs73Y9ECIGog2VdM/jB4Blp6xLWUO5bL9Gno6fOf9bX5jg7TkezhAiBsUBfGTkLqaB7xz7c8R7MZAdlVXESFY+EFbjRGEjipQQIhAIby4FTtMXHnNr0kiPBnQ3HQigH4RaTjWo7BNqGBiN1S";
+
+    /**
+     * 用公钥对字符串进行分段加密
+     *
+     * @param data      要加密的原始数据
+     * @param publicKey 公钥
+     */
+    public static byte[] encryptByPublicKeyForSpilt(byte[] data, byte[] publicKey) throws Exception {
+        int dataLen = data.length;
+        if (dataLen <= DEFAULT_BUFFERSIZE) {
+            return encryptByPublicKey(data, publicKey);
+        }
+        List<Byte> allBytes = new ArrayList<Byte>(2048);
+        int bufIndex = 0;
+        int subDataLoop = 0;
+        byte[] buf = new byte[DEFAULT_BUFFERSIZE];
+        for (int i = 0; i < dataLen; i++) {
+            buf[bufIndex] = data[i];
+            if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {
+                subDataLoop++;
+                if (subDataLoop != 1) {
+                    for (byte b : DEFAULT_SPLIT) {
+                        allBytes.add(b);
+                    }
+                }
+                byte[] encryptBytes = encryptByPublicKey(buf, publicKey);
+                for (byte b : encryptBytes) {
+                    allBytes.add(b);
+                }
+                bufIndex = 0;
+                if (i == dataLen - 1) {
+                    buf = null;
+                } else {
+                    buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];
+                }
+            }
+        }
+        byte[] bytes = new byte[allBytes.size()];
+        {
+            int i = 0;
+            for (Byte b : allBytes) {
+                bytes[i++] = b.byteValue();
+            }
+        }
+        return bytes;
+    }
+
+    /**
+     * 私钥分段加密
+     *
+     * @param data       要加密的原始数据
+     * @param privateKey 秘钥
+     */
+    public static byte[] encryptByPrivateKeyForSpilt(byte[] data, byte[] privateKey) throws Exception {
+        int dataLen = data.length;
+        if (dataLen <= DEFAULT_BUFFERSIZE) {
+            return encryptByPrivateKey(data, privateKey);
+        }
+        List<Byte> allBytes = new ArrayList<Byte>(2048);
+        int bufIndex = 0;
+        int subDataLoop = 0;
+        byte[] buf = new byte[DEFAULT_BUFFERSIZE];
+        for (int i = 0; i < dataLen; i++) {
+            buf[bufIndex] = data[i];
+            if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {
+                subDataLoop++;
+                if (subDataLoop != 1) {
+                    for (byte b : DEFAULT_SPLIT) {
+                        allBytes.add(b);
+                    }
+                }
+                byte[] encryptBytes = encryptByPrivateKey(buf, privateKey);
+                for (byte b : encryptBytes) {
+                    allBytes.add(b);
+                }
+                bufIndex = 0;
+                if (i == dataLen - 1) {
+                    buf = null;
+                } else {
+                    buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];
+                }
+            }
+        }
+        byte[] bytes = new byte[allBytes.size()];
+        {
+            int i = 0;
+            for (Byte b : allBytes) {
+                bytes[i++] = b.byteValue();
+            }
+        }
+        return bytes;
+    }
+
+    /**
+     * 公钥分段解密
+     *
+     * @param encrypted 待解密数据
+     * @param publicKey 密钥
+     */
+    public static byte[] decryptByPublicKeyForSpilt(byte[] encrypted, byte[] publicKey)
+            throws Exception {
+        int splitLen = DEFAULT_SPLIT.length;
+        if (splitLen <= 0) {
+            return decryptByPublicKey(encrypted, publicKey);
+        }
+        int dataLen = encrypted.length;
+        List<Byte> allBytes = new ArrayList<Byte>(1024);
+        int latestStartIndex = 0;
+        for (int i = 0; i < dataLen; i++) {
+            byte bt = encrypted[i];
+            boolean isMatchSplit = false;
+            if (i == dataLen - 1) {
+                // 到data的最后了
+                byte[] part = new byte[dataLen - latestStartIndex];
+                System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
+                byte[] decryptPart = decryptByPublicKey(part, publicKey);
+                for (byte b : decryptPart) {
+                    allBytes.add(b);
+                }
+                latestStartIndex = i + splitLen;
+                i = latestStartIndex - 1;
+            } else if (bt == DEFAULT_SPLIT[0]) {
+                // 这个是以split[0]开头
+                if (splitLen > 1) {
+                    if (i + splitLen < dataLen) {
+                        // 没有超出data的范围
+                        for (int j = 1; j < splitLen; j++) {
+                            if (DEFAULT_SPLIT[j] != encrypted[i + j]) {
+                                break;
+                            }
+                            if (j == splitLen - 1) {
+                                // 验证到split的最后一位,都没有break,则表明已经确认是split段
+                                isMatchSplit = true;
+                            }
+                        }
+                    }
+                } else {
+                    // split只有一位,则已经匹配了
+                    isMatchSplit = true;
+                }
+            }
+            if (isMatchSplit) {
+                byte[] part = new byte[i - latestStartIndex];
+                System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
+                byte[] decryptPart = decryptByPublicKey(part, publicKey);
+                for (byte b : decryptPart) {
+                    allBytes.add(b);
+                }
+                latestStartIndex = i + splitLen;
+                i = latestStartIndex - 1;
+            }
+        }
+        byte[] bytes = new byte[allBytes.size()];
+        {
+            int i = 0;
+            for (Byte b : allBytes) {
+                bytes[i++] = b.byteValue();
+            }
+        }
+        return bytes;
+    }
+
+    /**
+     * 使用私钥分段解密
+     */
+    public static byte[] decryptByPrivateKeyForSpilt(byte[] encrypted, byte[] privateKey)
+            throws Exception {
+        int splitLen = DEFAULT_SPLIT.length;
+        if (splitLen <= 0) {
+            return decryptByPrivateKey(encrypted, privateKey);
+        }
+        int dataLen = encrypted.length;
+        List<Byte> allBytes = new ArrayList<Byte>(1024);
+        int latestStartIndex = 0;
+        for (int i = 0; i < dataLen; i++) {
+            byte bt = encrypted[i];
+            boolean isMatchSplit = false;
+            if (i == dataLen - 1) {
+                // 到data的最后了
+                byte[] part = new byte[dataLen - latestStartIndex];
+                System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
+                byte[] decryptPart = decryptByPrivateKey(part, privateKey);
+                for (byte b : decryptPart) {
+                    allBytes.add(b);
+                }
+                latestStartIndex = i + splitLen;
+                i = latestStartIndex - 1;
+            } else if (bt == DEFAULT_SPLIT[0]) {
+                // 这个是以split[0]开头
+                if (splitLen > 1) {
+                    if (i + splitLen < dataLen) {
+                        // 没有超出data的范围
+                        for (int j = 1; j < splitLen; j++) {
+                            if (DEFAULT_SPLIT[j] != encrypted[i + j]) {
+                                break;
+                            }
+                            if (j == splitLen - 1) {
+                                // 验证到split的最后一位,都没有break,则表明已经确认是split段
+                                isMatchSplit = true;
+                            }
+                        }
+                    }
+                } else {
+                    // split只有一位,则已经匹配了
+                    isMatchSplit = true;
+                }
+            }
+            if (isMatchSplit) {
+                byte[] part = new byte[i - latestStartIndex];
+                System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
+                byte[] decryptPart = decryptByPrivateKey(part, privateKey);
+                for (byte b : decryptPart) {
+                    allBytes.add(b);
+                }
+                latestStartIndex = i + splitLen;
+                i = latestStartIndex - 1;
+            }
+        }
+        byte[] bytes = new byte[allBytes.size()];
+        {
+            int i = 0;
+            for (Byte b : allBytes) {
+                bytes[i++] = b.byteValue();
+            }
+        }
+        return bytes;
+    }
+
+    /**
+     * 用公钥对字符串进行加密
+     *
+     * @param data 原文
+     */
+    public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
+        // 得到公钥
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
+        KeyFactory kf = KeyFactory.getInstance(RSA);
+        PublicKey keyPublic = kf.generatePublic(keySpec);
+        // 加密数据
+        Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
+        cp.init(Cipher.ENCRYPT_MODE, keyPublic);
+        return cp.doFinal(data);
+    }
+
+    /**
+     * 私钥加密
+     *
+     * @param data       待加密数据
+     * @param privateKey 密钥
+     * @return byte[] 加密数据
+     */
+    public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey)
+            throws Exception {
+        // 得到私钥
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
+        KeyFactory kf = KeyFactory.getInstance(RSA);
+        PrivateKey keyPrivate = kf.generatePrivate(keySpec);
+        // 数据加密
+        Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
+        cipher.init(Cipher.ENCRYPT_MODE, keyPrivate);
+        return cipher.doFinal(data);
+    }
+
+
+    /**
+     * 公钥解密
+     *
+     * @param data      待解密数据
+     * @param publicKey 密钥
+     * @return byte[] 解密数据
+     */
+    public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
+        // 得到公钥
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
+        KeyFactory kf = KeyFactory.getInstance(RSA);
+        PublicKey keyPublic = kf.generatePublic(keySpec);
+        // 数据解密
+        Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
+        cipher.init(Cipher.DECRYPT_MODE, keyPublic);
+        return cipher.doFinal(data);
+    }
+
+    /**
+     * 使用私钥进行解密
+     *
+     * @param encrypted
+     * @param privateKey
+     * @return
+     * @throws Exception
+     */
+    public static byte[] decryptByPrivateKey(byte[] encrypted, byte[] privateKey)
+            throws Exception {
+        // 得到私钥
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
+        KeyFactory kf = KeyFactory.getInstance(RSA);
+        PrivateKey keyPrivate = kf.generatePrivate(keySpec);
+
+        // 解密数据
+        Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
+        cp.init(Cipher.DECRYPT_MODE, keyPrivate);
+        byte[] arr = cp.doFinal(encrypted);
+        return arr;
+    }
+
+    /**
+     * 从字符串中加载公钥
+     *
+     * @param publicKeyStr 公钥数据字符串
+     */
+    public static PublicKey loadPublicKey(String publicKeyStr) {
+        if (publicKeyStr == null || publicKeyStr.length() == 0) {
+            return null;
+        }
+        try {
+            byte[] buffer = Base64Utils.decode(publicKeyStr);
+            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
+            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
+            return keyFactory.generatePublic(keySpec);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 从字符串中加载私钥
+     *
+     * @param privateKeyStr 私钥数据字符串
+     */
+    public static PrivateKey loadPrivateKey(String privateKeyStr) {
+        if (privateKeyStr == null || privateKeyStr.length() == 0) {
+            return null;
+        }
+        try {
+            byte[] buffer = Base64Utils.decode(privateKeyStr);
+            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
+            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
+            return keyFactory.generatePrivate(keySpec);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * /**
+     * 从文件输入流中加载公钥
+     *
+     * @param in 公钥输入流
+     */
+    public static PublicKey loadPublicKey(InputStream in) {
+        if (in == null) {
+            return null;
+        }
+        try {
+            return loadPublicKey(readKey(in));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 从文件输入流中加载私钥
+     *
+     * @param in 私钥输入流
+     */
+    public static PrivateKey loadPrivateKey(InputStream in) {
+        if (in == null) {
+            return null;
+        }
+        try {
+            return loadPrivateKey(readKey(in));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 读取密钥信息
+     *
+     * @param in 输入流
+     * @return 秘钥 or 私钥
+     */
+    private static String readKey(InputStream in) {
+        if (in == null) {
+            return null;
+        }
+        try {
+            BufferedReader br = new BufferedReader(new InputStreamReader(in));
+            String readLine = null;
+            StringBuilder sb = new StringBuilder();
+            while ((readLine = br.readLine()) != null) {
+                if (readLine.charAt(0) == '-') {
+                    continue;
+                } else {
+                    sb.append(readLine);
+                    sb.append('\r');
+                }
+            }
+            return sb.toString();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static String encryptByPublicKey(String raw) {
+        return encryptByPublicKey(RSA_PUBLIC_1024_X509_PEM, raw);
+    }
+
+    public static String encryptByPublicKey(String key, String raw) {
+        String enc = "";
+        byte[] keyBytesPublic = Base64Utils.decode(key);
+        try {
+            byte[] data = raw.getBytes();
+            byte[] bytes = encryptByPublicKey(data, keyBytesPublic);
+            enc = Base64Utils.encode(bytes);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return enc;
+    }
+
+
+    public static String decryptByPublicKey(String enc) {
+        return decryptByPublicKey(RSA_PUBLIC_1024_X509_PEM, enc);
+    }
+
+    public static String decryptByPublicKey(String key, String enc) {
+        String raw = "";
+        byte[] keyBytesPublic = Base64Utils.decode(key);
+        try {
+            byte[] bytes = decryptByPublicKey(Base64Utils.decode(enc), keyBytesPublic);
+            raw = new String(bytes, "UTF-8");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return raw;
+    }
+
+    public static String encryptByPrivateKey(String raw, String priKey) {
+        String enc = "";
+        byte[] keyBytesPrivate = Base64Utils.decode(priKey);
+        try {
+            byte[] rawByte = raw.getBytes();
+            byte[] bytes = encryptByPrivateKey(rawByte, keyBytesPrivate);
+            enc = Base64Utils.encode(bytes);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return enc;
+    }
+
+    public static String decryptByPrivateKey(String enc) {
+        return decryptByPrivateKey(enc, RSA_PRIVATE_1024_X509_PEM);
+    }
+
+    public static String decryptByPrivateKey(String enc, String priKey) {
+        String raw = "";
+        byte[] keyBytesPrivate = Base64Utils.decode(priKey);
+        try {
+            byte[] bytes = decryptByPrivateKey(Base64Utils.decode(enc), keyBytesPrivate);
+            raw = new String(bytes, "utf-8");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return raw;
+    }
+}

+ 45 - 0
library_support/src/main/java/cn/yyxx/support/hawkeye/OwnDebugUtils.java

@@ -0,0 +1,45 @@
+package cn.yyxx.support.hawkeye;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import java.io.File;
+
+import cn.yyxx.support.FileUtils;
+import cn.yyxx.support.PropertiesUtils;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/04/23
+ */
+public class OwnDebugUtils {
+
+    private static final String OWN_DEBUG_FILE = "own.txt";
+
+    public static boolean isOwnDebug(Context context, String fileName, String folderName, String key) {
+        //先判断配置文件
+        if (getOwnDebugFromCfg(context, fileName, folderName, key)) {
+            return true;
+        }
+        //配置文件中关闭时再判断私有目录
+        String ownFileName = context.getExternalFilesDir(null).getAbsolutePath() + "/" + OWN_DEBUG_FILE;
+        File file = new File(fileName);
+        if (!file.exists()) {
+            return false;
+        }
+        String data = FileUtils.readFile(ownFileName);
+        if (!TextUtils.isEmpty(data)) {
+            return data.trim().equals("own");
+        } else {
+            return false;
+        }
+
+    }
+
+    public static boolean getOwnDebugFromCfg(Context context, String fileName, String folderName, String key) {
+        String value = PropertiesUtils.getValue4Properties(context, fileName, folderName, key);
+        LogUtils.d("own value : " + value);
+        return !TextUtils.isEmpty(value) && Boolean.parseBoolean(value);
+    }
+
+}

+ 22 - 0
library_support/src/main/java/cn/yyxx/support/hawkeye/ToastUtils.java

@@ -0,0 +1,22 @@
+package cn.yyxx.support.hawkeye;
+
+import android.content.Context;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/04/23
+ */
+public class ToastUtils {
+
+    public static boolean DEBUG = false;
+
+    public static void toastInfo(Context context, String message) {
+        android.widget.Toast.makeText(context, message, android.widget.Toast.LENGTH_SHORT).show();
+    }
+
+    public static void toastDebugInfo(Context context, String msg) {
+        if (DEBUG) {
+            android.widget.Toast.makeText(context, msg, android.widget.Toast.LENGTH_SHORT).show();
+        }
+    }
+}

+ 1 - 1
library_support/src/main/java/cn/yyxx/support/msa/MsaDeviceIdsHandler.java

@@ -18,7 +18,7 @@ import cn.yyxx.support.hawkeye.LogUtils;
  */
 public class MsaDeviceIdsHandler {
 
-    private static final String VERSION = "1.0.23";
+    private static final String VERSION = "1.0.25";
 
     public static String oaid = "";
     public static String vaid = "";

+ 30 - 0
library_support/src/main/java/cn/yyxx/support/scheduler/ScheduledWorker.java

@@ -0,0 +1,30 @@
+package cn.yyxx.support.scheduler;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/05/19
+ */
+public class ScheduledWorker {
+
+    private ScheduledExecutorService mService;
+    private ScheduledFuture<?> mFuture;
+
+    public ScheduledWorker(int poolSize) {
+        this.mService = new ScheduledThreadPoolExecutor(poolSize);
+    }
+
+    public void invokeAtFixedRate(Runnable runnable, long initialDelay, long period, TimeUnit unit) {
+        this.mFuture = mService.scheduleAtFixedRate(runnable, initialDelay, period, unit);
+    }
+
+    public void cancel() {
+        if (mFuture != null && !mFuture.isCancelled()) {
+            mFuture.cancel(false);
+        }
+    }
+}

+ 168 - 0
library_support/src/main/java/cn/yyxx/support/ui/DragViewLayout.java

@@ -0,0 +1,168 @@
+package cn.yyxx.support.ui;
+
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import cn.yyxx.support.hawkeye.LogUtils;
+
+/**
+ * @author #Suyghur.
+ * Created on 2021/05/12
+ */
+public class DragViewLayout extends FrameLayout {
+
+    //view所在位置
+    private int mLastX, mLastY;
+
+    //屏幕宽高
+    private int mScreenWidth, mScreenHeight;
+
+    //view宽高
+    private int mWidth, mHeight;
+
+    //是否在拖拽过程中
+    private boolean isDrag = false;
+
+    //系统最新滑动距离
+    private int mTouchSlop = 0;
+
+    private WindowManager.LayoutParams floatLayoutParams;
+    private WindowManager mWindowManager;
+
+    //手指触摸位置
+    private float xInScreen;
+    private float yInScreen;
+    private float xInView;
+    private float yInView;
+
+    public DragViewLayout(Context context) {
+        super(context);
+        mScreenWidth = context.getResources().getDisplayMetrics().widthPixels;
+        mScreenHeight = context.getResources().getDisplayMetrics().heightPixels;
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+        floatLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
+                PixelFormat.RGBA_8888);
+        floatLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+        floatLayoutParams.x = 0;
+        floatLayoutParams.y = (int) (mScreenHeight * 0.4);
+        mWindowManager.addView(this, floatLayoutParams);
+    }
+
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        int action = event.getAction();
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mLastX = (int) event.getRawX();
+                mLastY = (int) event.getRawY();
+                yInView = event.getY();
+                xInView = event.getX();
+                xInScreen = event.getRawX();
+                yInScreen = event.getRawY();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                int dx = (int) event.getRawX() - mLastX;
+                int dy = (int) event.getRawY() - mLastY;
+                //移动量大于阈值才判定为正在移动
+                if (Math.abs(dx) > mTouchSlop || Math.abs(dy) > mTouchSlop) {
+                    isDrag = true;
+                }
+                xInScreen = event.getRawX();
+                yInScreen = event.getRawY();
+                mLastX = (int) event.getRawX();
+                mLastY = (int) event.getRawY();
+                //拖拽时调用WindowManager updateViewLayout更新悬浮球位置
+                updateFloatPosition(false);
+                break;
+            case MotionEvent.ACTION_UP:
+                if (isDrag) {
+                    //执行贴边
+                    startAnim();
+                    isDrag = false;
+                }
+                break;
+            default:
+                break;
+        }
+        return super.dispatchTouchEvent(event);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        if (mWidth == 0) {
+            //获取浮窗宽高
+            mWidth = getWidth();
+            mHeight = getHeight();
+        }
+    }
+
+    private void updateFloatPosition(boolean isUp) {
+        int x = (int) (xInScreen - xInView);
+        int y = (int) (yInScreen - yInView);
+        if (isUp) {
+            x = isRightFloat() ? mScreenWidth : 0;
+        }
+        if (y > mScreenHeight - mHeight) {
+            y = mScreenHeight - mHeight;
+        }
+        floatLayoutParams.x = x;
+        floatLayoutParams.y = y;
+        //更新位置
+        mWindowManager.updateViewLayout(this, floatLayoutParams);
+    }
+
+    private boolean isRightFloat() {
+        return xInScreen > mScreenWidth / 2;
+    }
+
+    private void startAnim() {
+        ValueAnimator valueAnimator;
+        if (floatLayoutParams.x < mScreenWidth / 2) {
+            valueAnimator = ValueAnimator.ofInt(floatLayoutParams.x, 0);
+        } else {
+            valueAnimator = ValueAnimator.ofInt(floatLayoutParams.x, mScreenWidth - mWidth);
+        }
+        valueAnimator.setDuration(200);
+        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                floatLayoutParams.x = (int) animation.getAnimatedValue();
+                mWindowManager.updateViewLayout(DragViewLayout.this, floatLayoutParams);
+            }
+        });
+        valueAnimator.start();
+    }
+
+    public void show() {
+        setVisibility(View.VISIBLE);
+    }
+
+    public void hide() {
+        setVisibility(View.GONE);
+    }
+
+    public void release() {
+        mWindowManager.removeView(this);
+    }
+}

+ 272 - 0
library_support/src/main/java/cn/yyxx/support/ui/GifView.java

@@ -0,0 +1,272 @@
+package cn.yyxx.support.ui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Movie;
+import android.os.Build;
+import android.view.View;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author #Suyghur,
+ * Created on 2019/07/22
+ */
+public class GifView extends View {
+
+    /**
+     * gif播放时间,默认1s
+     */
+    private static final int DEFAULT_GIF_DURATION = 1000;
+
+    private Movie mMovie;
+    /**
+     * gif开始时间
+     */
+    private long mStartTime;
+    /**
+     * 当前显示的帧
+     */
+    private int mCurrentFrame;
+    private float mMarginLeft;
+    private float mMarginTop;
+    private int mWidth;
+    private int mHeight;
+    private float mScale;
+    private boolean isVisible = true;
+    private volatile boolean isPause = false;
+
+
+    public GifView(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mMovie != null) {
+            int movieWidth = mMovie.width();
+            int movieHeight = mMovie.height();
+            int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
+            float scaleW = (float) movieWidth / (float) maxWidth;
+            mScale = 1f / scaleW;
+            mWidth = maxWidth;
+            mHeight = (int) (movieHeight * mScale);
+            setMeasuredDimension(mWidth, mHeight);
+        } else {
+            setMeasuredDimension(getSuggestedMinimumWidth(), getSuggestedMinimumHeight());
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        mMarginLeft = (getWidth() - mWidth) / 2f;
+        mMarginTop = (getHeight() - mHeight) / 2f;
+        isVisible = getVisibility() == View.VISIBLE;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mMovie != null) {
+            if (!isPause) {
+                updateAnimationTime();
+                drawMovieFrame(canvas);
+                invalidateView();
+            } else {
+                drawMovieFrame(canvas);
+            }
+        }
+    }
+
+    @Override
+    public void onScreenStateChanged(int screenState) {
+        super.onScreenStateChanged(screenState);
+        isVisible = screenState == SCREEN_STATE_ON;
+        invalidateView();
+    }
+
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        isVisible = visibility == View.VISIBLE;
+        invalidateView();
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        isVisible = visibility == View.VISIBLE;
+        invalidateView();
+    }
+
+    /**
+     * 更新当前显示进度
+     */
+    private void updateAnimationTime() {
+        long current = android.os.SystemClock.uptimeMillis();
+        // 如果第一帧,记录起始时间
+        if (mStartTime == 0) {
+            mStartTime = current;
+        }
+        // 取出动画的时长
+        int dur = mMovie.duration();
+        if (dur == 0) {
+            dur = DEFAULT_GIF_DURATION;
+        }
+        // 算出需要显示第几帧
+        mCurrentFrame = (int) ((current - mStartTime) % dur);
+    }
+
+    /**
+     * 绘制图片
+     *
+     * @param canvas 画布
+     */
+    private void drawMovieFrame(Canvas canvas) {
+        // 设置要显示的帧,绘制即可
+        mMovie.setTime(mCurrentFrame);
+        canvas.save();
+        canvas.scale(mScale, mScale);
+        mMovie.draw(canvas, mMarginLeft / mScale, mMarginTop / mScale);
+        canvas.restore();
+    }
+
+    /**
+     * 重绘
+     */
+    private void invalidateView() {
+        if (isVisible) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+                postInvalidateOnAnimation();
+            } else {
+                invalidate();
+            }
+        }
+    }
+
+    /**
+     * 设置gif图资源
+     */
+    public void setGifResource(int giftResId) {
+        byte[] bytes = gif2Bytes(giftResId);
+        mMovie = Movie.decodeByteArray(bytes, 0, bytes.length);
+        requestLayout();
+    }
+
+    public void setGifResource(File file) {
+        byte[] bytes = gif2Bytes(file);
+        mMovie = Movie.decodeByteArray(bytes, 0, bytes.length);
+        requestLayout();
+    }
+
+    /**
+     * 手动设置 Movie对象
+     *
+     * @param movie Movie
+     */
+    public void setMovie(Movie movie) {
+        this.mMovie = movie;
+        requestLayout();
+    }
+
+    /**
+     * 得到Movie对象
+     *
+     * @return Movie
+     */
+    public Movie getMovie() {
+        return mMovie;
+    }
+
+    /**
+     * 设置要显示第几帧动画
+     *
+     * @param frame 帧
+     */
+    public void setMovieTime(int frame) {
+        mCurrentFrame = frame;
+        invalidate();
+    }
+
+    /**
+     * 设置暂停
+     *
+     * @param paused
+     */
+    public void setPaused(boolean paused) {
+        this.isPause = paused;
+        if (!paused) {
+            mStartTime = android.os.SystemClock.uptimeMillis() - mCurrentFrame;
+        }
+        invalidate();
+    }
+
+    /**
+     * 获取当前播放状态
+     *
+     * @return
+     */
+    public boolean getStatus() {
+        return this.isPause;
+    }
+
+    private byte[] gif2Bytes(File file) {
+        byte[] data = null;
+        FileInputStream fis = null;
+        ByteArrayOutputStream baos = null;
+
+        try {
+            fis = new FileInputStream(file);
+            baos = new ByteArrayOutputStream();
+            int len;
+            byte[] buffer = new byte[1024];
+            while ((len = fis.read(buffer)) != -1) {
+                baos.write(buffer, 0, len);
+            }
+
+            data = baos.toByteArray();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (fis != null) {
+                    fis.close();
+                }
+                if (baos != null) {
+                    baos.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+
+        }
+        return data;
+    }
+
+
+    private byte[] gif2Bytes(int resId) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        InputStream is = getResources().openRawResource(resId);
+        byte[] b = new byte[1024];
+        int len;
+        try {
+            while ((len = is.read(b, 0, 1024)) != -1) {
+                baos.write(b, 0, len);
+            }
+            baos.flush();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                is.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return baos.toByteArray();
+    }
+}

+ 196 - 0
library_support/src/main/java/cn/yyxx/support/volley/VolleyBitmapCache.java

@@ -0,0 +1,196 @@
+package cn.yyxx.support.volley;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.text.TextUtils;
+import android.util.LruCache;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import cn.yyxx.support.cache.bitmap.DiskLruCache;
+import cn.yyxx.support.encryption.Md5Utils;
+import cn.yyxx.support.hawkeye.LogUtils;
+import cn.yyxx.support.volley.source.toolbox.ImageLoader;
+
+
+/**
+ * @author #Suyghur,
+ * Created on 2019/09/11
+ * Copyright (c) 2019 3KWan.
+ */
+public class VolleyBitmapCache implements ImageLoader.ImageCache {
+    /**
+     * 内存缓存
+     */
+    private LruCache<String, Bitmap> lruCache;
+    /**
+     * 本地缓存
+     */
+    private static DiskLruCache diskLruCache;
+    /**
+     * 本地缓存文件名
+     */
+    final static String DISK_CACHE_DIR = "qsgame_img";
+    /**
+     * 本地缓存大小
+     */
+    final long DISK_MAX_SIZE = 20 * 1024 * 1024;
+
+    private static VolleyBitmapCache volleyBitmapCache = null;
+
+    /**
+     * 默认格式jpeg
+     */
+    private static Bitmap.CompressFormat mFormat = Bitmap.CompressFormat.JPEG;
+
+    public static VolleyBitmapCache getVolleyBitmapCache(Context context, String diskCachePath, Bitmap.CompressFormat compressFormat) {
+        if (compressFormat != null) {
+            mFormat = compressFormat;
+        }
+        if (volleyBitmapCache == null) {
+            synchronized (Object.class) {
+                if (volleyBitmapCache == null) {
+                    volleyBitmapCache = new VolleyBitmapCache(context, diskCachePath);
+                }
+            }
+        }
+        return volleyBitmapCache;
+    }
+
+    private VolleyBitmapCache(Context context, String diskCachePath) {
+        //获取应用内存
+        final int maxMemory = (int) Runtime.getRuntime().maxMemory();
+        final int cacheSize = maxMemory / 8;
+        //初始化LruCache,设置缓存大小cacheSize
+        this.lruCache = new LruCache<String, Bitmap>(cacheSize) {
+            @Override
+            protected int sizeOf(String key, Bitmap value) {
+                return value.getRowBytes() * value.getHeight();
+            }
+        };
+
+        //本地缓存
+        //如果diskCachePath为空则设置默认的路径:data/data/packageName/cache/qsgame_img
+        String diskLruCachePath;
+        if (TextUtils.isEmpty(diskCachePath)) {
+            diskLruCachePath = context.getCacheDir().getPath() + File.separator + DISK_CACHE_DIR;
+        } else {
+            diskLruCachePath = diskCachePath;
+        }
+        LogUtils.d("cachePath : " + diskLruCachePath);
+        //缓存路径
+        File disLruCacheDir = new File(diskLruCachePath);
+        if (!disLruCacheDir.exists()) {
+            disLruCacheDir.mkdirs();
+        }
+        try {
+            //初始化DiskLruCache,设置最大缓存大小DISK_MAX_SIZE
+            diskLruCache = DiskLruCache.open(disLruCacheDir, 1, 1, DISK_MAX_SIZE);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+    @Override
+    public Bitmap getBitmap(String url) {
+        String key = Md5Utils.encodeByMD5(url);
+        //先从内存中查找
+        Bitmap bitmap = lruCache.get(url);
+        if (bitmap == null) {
+            //加载本地
+            LogUtils.d("getBitmap 加载本地 url : " + url);
+            bitmap = getBitmap4DiskLruCache(key);
+            //加载到内存
+            if (bitmap != null) {
+                LogUtils.d("getBitmap 加载到内存 url : " + url);
+                lruCache.put(url, bitmap);
+            }
+        }
+        return bitmap;
+    }
+
+    @Override
+    public void putBitmap(String url, Bitmap bitmap) {
+        lruCache.put(url, bitmap);
+        String key = Md5Utils.encodeByMD5(url);
+        putBitmap2DiskLruCache(key, bitmap);
+    }
+
+    /**
+     * 缓存到本地
+     *
+     * @param key
+     * @param bitmap
+     */
+    private void putBitmap2DiskLruCache(String key, Bitmap bitmap) {
+        DiskLruCache.Editor editor = null;
+        OutputStream outputStream = null;
+        try {
+            editor = diskLruCache.edit(key);
+            if (editor != null) {
+                outputStream = editor.newOutputStream(0);
+                boolean compress = bitmap.compress(mFormat, 100, outputStream);
+                if (compress) {
+                    LogUtils.d("缓存到本地");
+                    diskLruCache.flush();
+                    editor.commit();
+                } else {
+                    editor.abort();
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (editor != null) {
+                    editor.abort();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * 从本地加载
+     *
+     * @param key
+     * @return
+     */
+    private Bitmap getBitmap4DiskLruCache(String key) {
+        DiskLruCache.Snapshot snapshot = null;
+        Bitmap bitmap = null;
+        InputStream inputStream = null;
+        try {
+            snapshot = diskLruCache.get(key);
+            if (snapshot != null) {
+                inputStream = snapshot.getInputStream(0);
+                if (inputStream != null) {
+                    bitmap = BitmapFactory.decodeStream(inputStream);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (snapshot != null) {
+                snapshot.close();
+            }
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return bitmap;
+    }
+}

+ 1 - 1
library_support/src/main/java/cn/yyxx/support/volley/source/toolbox/BasicNetwork.java

@@ -260,7 +260,7 @@ public class BasicNetwork implements Network {
         request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
     }
 
-    private Map<String, String> getCacheHeaders(Cache.Entry entry) {
+    private Map<String, String> getCacheHeaders(Entry entry) {
         // If there's no cache entry, we're done.
         if (entry == null) {
             return Collections.emptyMap();

+ 1 - 1
library_support/src/main/java/cn/yyxx/support/volley/source/toolbox/HurlStack.java

@@ -152,7 +152,7 @@ public class HurlStack extends BaseHttpStack {
      * @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3">RFC 7230 section 3.3</a>
      */
     private static boolean hasResponseBody(int requestMethod, int responseCode) {
-        return requestMethod != Request.Method.HEAD
+        return requestMethod != Method.HEAD
                 && !(HTTP_CONTINUE <= responseCode && responseCode < HttpURLConnection.HTTP_OK)
                 && responseCode != HttpURLConnection.HTTP_NO_CONTENT
                 && responseCode != HttpURLConnection.HTTP_NOT_MODIFIED;

+ 1 - 1
library_support/src/main/java/cn/yyxx/support/volley/source/toolbox/JsonRequest.java

@@ -84,7 +84,7 @@ public abstract class JsonRequest<T> extends Request<T> {
 
     @Override
     protected void deliverResponse(T response) {
-        Response.Listener<T> listener;
+        Listener<T> listener;
         synchronized (mLock) {
             listener = mListener;
         }

+ 1 - 1
library_support/src/main/java/cn/yyxx/support/volley/source/toolbox/StringRequest.java

@@ -80,7 +80,7 @@ public class StringRequest extends Request<String> {
 
     @Override
     protected void deliverResponse(String response) {
-        Response.Listener<String> listener;
+        Listener<String> listener;
         synchronized (mLock) {
             listener = mListener;
         }

+ 0 - 0
library_support/libs/android-support-v4.jar → libs/android-support-v4.jar


BIN
libs/mmkv-static-1.2.8.jar


BIN
libs/oaid_sdk_1.0.25.jar


BIN
v4backup.zip


BIN
volley.zip