Android使用Kotlin封装MMKVUtils

Android使用Kotlin封装MMKVUtils

1.简介:

MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今在微信上使用,其性能和稳定性经过了时间的验证。近期也已移植到 Android / macOS / Win32 / POSIX 平台,一并开源。

2.MMKV 源起

在微信客户端的日常运营中,时不时就会爆发特殊文字引起系统的 crash, 参考文章,文章里面设计的技术方案是在关键代码前后进行计数器的加减,通过检查计数器的异常,来发现引起闪退的异常文字。在会话列表、会话界面等有大量 cell 的地方,希望新加的计时器不会影响滑动性能;另外这些计数器还要永久存储下来------因为闪退随时可能发生。这就需要一个性能非常高的通用 key-value 存储组件,我们考察了 SharedPreferences、NSUserDefaults、SQLite 等常见组件,发现都没能满足如此苛刻的性能要求。考虑到这个防 crash 方案最主要的诉求还是实时写入,而 mmap 内存映射文件刚好满足这种需求,我们尝试通过它来实现一套 key-value 组件。

3.MMKV 原理

  • 内存准备 通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。
  • 数据组织 数据序列化方面我们选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。
  • 写入优化 考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力。我们考虑将增量 kv 对象序列化后,append 到内存末尾。
  • 空间增长 使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。我们需要在性能和空间上做个折中。

更详细的设计原理参考 MMKV 原理

4.MMKV优势:

MMKV的出现其实是为了解决SharedPreferences的一些问题,微信团队希望以此来代替SharedPreferences,目前在Android中,对于经常使用的快速本地化存储,大部分人往往会选择SharedPreferences来作为存储方式, 作为Android库中自带的存储方式,SharePreferences在使用方式上还是很便捷的,但是也往往存在以下的一些问题。

1、通过 getSharedPreferences 可以获取 SP 实例,从首次初始化到读到数据会存在延迟,因为读文件的操作阻塞调用的线程直到文件读取完毕,如果在主线程调用,可能会对 UI 流畅度造成影响。(线程阻塞)

2、虽然支持设置 MODE_MULTI_PROCESS 标志位,但是跨进程共享 SP 存在很多问题,所以不建议使用该模式。(文件跨进程共享)

3、将数据写入文件需要将数据拷贝两次,再写入到文件中,如果数据量过大,也会有很大的性能损耗。(二次写入)

5.MMKV支持的数据类型:

支持以下 Java 语言基础类型:

boolean、int、long、float、double、byte[],String、Set,任何实现了Parcelable的类型,对象存储方式是,转化成json串,通过字符串存储,使用的时候在取出来反序列化.

6.依赖导入:

scss 复制代码
implementation(libs.mmkv)

7.AGP8.1统一依赖配置:

ini 复制代码
[versions]
agp = "8.1.0"
org-jetbrains-kotlin-android = "1.8.0"
core-ktx = "1.10.1"
junit = "4.13.2"
androidx-test-ext-junit = "1.1.5"
espresso-core = "3.5.1"
appcompat = "1.6.1"
material = "1.9.0"
constraintlayout = "2.1.4"
mmkv = "1.3.0"
​
[libraries]
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
mmkv = { group = "com.tencent", name = "mmkv", version.ref = "mmkv" }
​
[plugins]
com-android-application = { id = "com.android.application", version.ref = "agp" }
org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "org-jetbrains-kotlin-android" }
​
[bundles]

8.App.gradle配置:

ini 复制代码
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
    alias(libs.plugins.com.android.application)
    alias(libs.plugins.org.jetbrains.kotlin.android)
}
​
android {
    namespace = "com.example.mmkvdemo"
    compileSdk = 33
​
    defaultConfig {
        applicationId = "com.example.mmkvdemo"
        minSdk = 23
        targetSdk = 33
        versionCode = 1
        versionName = "1.0"
​
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
​
    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}
​
dependencies {
    implementation(libs.core.ktx)
    implementation(libs.appcompat)
    implementation(libs.material)
    implementation(libs.constraintlayout)
    implementation(libs.mmkv)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.test.ext.junit)
    androidTestImplementation(libs.espresso.core)
}

9.使用java封装的工具类:

typescript 复制代码
package com.example.lib_common.utils;
​
import android.content.Context;
import android.os.Environment;
​
import com.tencent.mmkv.MMKV;
​
/**
 * @author: njb
 * @date: 2023/8/9 14:53
 * @desc:
 */
public class MMKVUtils {
    private MMKV mmkv;
    private static volatile MMKVUtils mInstance;
​
    private MMKVUtils() {
​
    }
​
    public void init(Context context) {
        String dir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/mmkv";
        //mmkv初始化
        MMKV.initialize(context, dir);
        mmkv = MMKV.mmkvWithID("MyMMID");
        //开启跨进程通信
        mmkv = MMKV.mmkvWithID("MyMMID", MMKV.MULTI_PROCESS_MODE);
    }
​
    public static MMKVUtils getInstance() {
        if (mInstance == null) {
            synchronized (MMKVUtils.class) {
                if (mInstance == null) {
                    mInstance = new MMKVUtils();
                }
            }
        }
        return mInstance;
    }
​
    public void encode(String key, Object value) {
        if (value instanceof String) {
            mmkv.encode(key, (String) value);
        } else if (value instanceof Integer) {
            mmkv.encode(key, (Integer) value);
        } else if (value instanceof Boolean) {
            mmkv.encode(key, (Boolean) value);
        } else if (value instanceof Long) {
            mmkv.encode(key, (Long) value);
        } else if (value instanceof Float) {
            mmkv.encode(key, (Float) value);
        } else if (value instanceof Double) {
            mmkv.encode(key, (Double) value);
        }
    }
​
​
    public Integer decodeInt(String key) {
        return mmkv.decodeInt(key);
    }
​
    public String decodeString(String key) {
        return mmkv.decodeString(key, "");
    }
​
    public Boolean decodeBoolean(String key) {
        return mmkv.decodeBool(key);
    }
​
    public Long decodeLong(String key) {
        return mmkv.decodeLong(key);
    }
​
    public Float decodeFloat(String key) {
        return mmkv.decodeFloat(key);
    }
​
    public Double decodeDouble(String key) {
        return mmkv.decodeDouble(key);
    }
​
    public void clearAllData(){
        mmkv.clearAll();
    }
}
​

10.使用kotlin封装的工具类:

kotlin 复制代码
package com.example.lib_common.utils
​
import android.content.Context
import com.tencent.mmkv.MMKV
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
​
/**
 * @author: njb
 * @date: 2023/8/9 22:40
 * @desc:
 */
class MMKVUtil private constructor(){
    lateinit var mmKv:MMKV
    companion object {
        const val DATE_FORMAT = "yyyy-MM-dd HH.mm.ss"
        val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { MMKVUtil() }
    }
​
    fun init(context: Context) {
        //第一种使用mmkv默认目录
        //MMKV.initialize(context)
        //第二种使用自定义包名目录
         //MMKV.initialize(context, FileManager.getStorageRootDir() + FileManager.MMKV_DIR)
​
        val mFileDateFormat = SimpleDateFormat(DATE_FORMAT, Locale.US)
        //视频保存路径
        val file =
            File(FileManager.getMMKVPath(), mFileDateFormat.format(Date()) + "/mmkv")
        //第三种使用自定义的系统目录 dcim、download、music其中一个即可
        MMKV.initialize(context,file.absolutePath)
        mmKv = MMKV.mmkvWithID("MyMMKVTestID", MMKV.MULTI_PROCESS_MODE)
        mmKv.encode("bool", true)
    }
​
    fun initTest(context: Context) {
        //第一种使用mmkv默认目录
        //MMKV.initialize(context)
        //第二种使用自定义包名目录
        MMKV.initialize(context, FileManager.getStorageRootDir() + FileManager.MMKV_DIR)
        //第三种使用自定义的系统目录 dcim、download、music其中一个即可
        //MMKV.initialize(context,FileManager.getMMKVPath())
        mmKv = MMKV.mmkvWithID("MyTestID", MMKV.MULTI_PROCESS_MODE)
        mmKv.encode("bool", true)
    }
​
    fun encode(key: String, value: Any) {
        when (value) {
            is String -> mmKv.encode(key, value)
            is Int -> mmKv.encode(key, value)
            is Boolean -> mmKv.encode(key, value)
            is Long -> mmKv.encode(key, value)
            is Float -> mmKv.encode(key, value)
            is Double -> mmKv.encode(key, value)
        }
    }
​
    inline fun <reified T> decode(key: String, defaultValue: T): T = when (T::class) {
        String::class -> mmKv.decodeString(key, defaultValue as String?) as T
        Int::class -> mmKv.decodeInt(key, defaultValue as Int) as T
        Boolean::class -> mmKv.decodeBool(key, defaultValue as Boolean) as T
        Long::class -> mmKv.decodeLong(key, defaultValue as Long) as T
        Float::class -> mmKv.decodeFloat(key, defaultValue as Float) as T
        Double::class -> mmKv.decodeDouble(key, defaultValue as Double) as T
        else -> throw IllegalArgumentException("Unsupported type")
    }
}
​
​

11.初始化:

kotlin 复制代码
private fun performFileOperations() {
    MyApp.mInstance.initMMKV()
    // 执行文件读写操作
    initMMKVData()
}
​
package com.example.mmkvdemo
​
import android.app.Application
import com.example.mmkvdemo.utils.MMKVUtils
​
/**
 * @author: njb
 * @date: 2023/8/9 23:19
 * @desc:
 */
class MyApp :Application(){
    override fun onCreate() {
        super.onCreate()
       // initMMKV()
        mInstance = this
    }
​
    fun initMMKV() {
        MMKVUtils.instance.init(this)
    }
​
    companion object{
        lateinit var mInstance: MyApp
            private set
    }
}

12.简单使用:

kotlin 复制代码
​
   private fun performFileOperations() {
        MyApp.mInstance.initMMKV()
        // 执行文件读写操作
        initMMKVData()
    }
​
    private fun initMMKVData() {
        // 存储数据
        MMKVUtils.instance.encode("key1", "value1")
        MMKVUtils.instance.encode("key2", "456")
    }
    
@SuppressLint("SetTextI18n")
private fun initView() {
    textView.setOnClickListener {
        // 读取数据
        val value1: String = MMKVUtils.instance.decode("key1","")
        val value2: String = MMKVUtils.instance.decode("key2","")
        Log.d(TAG, "====数据为===$value1$value2")
        textView.text = value1 + value2
    }
}

13.适配Android13:

kotlin 复制代码
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // 检查并请求权限
    if (checkPermissions()) {
        // 已经有权限,执行文件读写操作
        performFileOperations()
    } else {
        // 请求权限
        requestPermission()
    }
    initView()
}
​
 private fun checkPermissions(): Boolean {
        if (VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            PERMISSIONS = arrayOf<String>(Manifest.permission.READ_EXTERNAL_STORAGE)
            for (permission in PERMISSIONS) {
                if (ContextCompat.checkSelfPermission(
                        this,
                        permission
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    return false
                }
            }
        } else {
            for (permission in PERMISSIONS) {
                if (ContextCompat.checkSelfPermission(
                        this,
                        permission
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    return false
                }
            }
        }
        return true
    }
​
    private fun requestPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            PERMISSIONS = arrayOf<String>(Manifest.permission.READ_EXTERNAL_STORAGE)
            ActivityCompat.requestPermissions(
                this,
                PERMISSIONS,
                REQUEST_PERMISSION_CODE
            )
        } else {
            ActivityCompat.requestPermissions(
                this,
                PERMISSIONS,
                REQUEST_PERMISSION_CODE
            )
        }
    }
​
    private fun performFileOperations() {
        MyApp.mInstance.initMMKV()
        // 执行文件读写操作
        initMMKVData()
    }
​
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String?>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_PERMISSION_CODE) {
            var allPermissionsGranted = true
            for (result in grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allPermissionsGranted = false
                    break
                }
            }
            if (allPermissionsGranted) {
                // 权限已授予,执行文件读写操作
                performFileOperations()
            } else {
                // 权限被拒绝,处理权限请求失败的情况
            }
        }
    }

14.完整使用代码:

kotlin 复制代码
package com.example.mmkvdemo
​
import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.os.Build
import android.os.Build.VERSION
import android.os.Bundle
import android.util.Log
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.mmkvdemo.utils.MMKVUtils
​
class MainActivity : AppCompatActivity() {
    private val TAG = MainActivity::class.java.name
    private val REQUEST_PERMISSION_CODE = 100
    private var PERMISSIONS: Array<String> = arrayOf(
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE
    )
    private val textView:TextView by lazy { findViewById(R.id.tv_test) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 检查并请求权限
        if (checkPermissions()) {
            // 已经有权限,执行文件读写操作
            performFileOperations()
        } else {
            // 请求权限
            requestPermission()
        }
        initView()
    }
​
    @SuppressLint("SetTextI18n")
    private fun initView() {
        textView.setOnClickListener {
            // 读取数据
            val value1: String = MMKVUtils.instance.decode("key1","")
            val value2: String = MMKVUtils.instance.decode("key2","")
            Log.d(TAG, "====数据为===$value1$value2")
            textView.text = value1 + value2
        }
    }
​
    private fun checkPermissions(): Boolean {
        if (VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            PERMISSIONS = arrayOf<String>(Manifest.permission.READ_EXTERNAL_STORAGE)
            for (permission in PERMISSIONS) {
                if (ContextCompat.checkSelfPermission(
                        this,
                        permission
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    return false
                }
            }
        } else {
            for (permission in PERMISSIONS) {
                if (ContextCompat.checkSelfPermission(
                        this,
                        permission
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    return false
                }
            }
        }
        return true
    }
​
    private fun requestPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            PERMISSIONS = arrayOf<String>(Manifest.permission.READ_EXTERNAL_STORAGE)
            ActivityCompat.requestPermissions(
                this,
                PERMISSIONS,
                REQUEST_PERMISSION_CODE
            )
        } else {
            ActivityCompat.requestPermissions(
                this,
                PERMISSIONS,
                REQUEST_PERMISSION_CODE
            )
        }
    }
​
    private fun performFileOperations() {
        MyApp.mInstance.initMMKV()
        // 执行文件读写操作
        initMMKVData()
    }
​
    private fun initMMKVData() {
        // 存储数据
        MMKVUtils.instance.encode("key1", "value1")
        MMKVUtils.instance.encode("key2", "456")
    }
​
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String?>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_PERMISSION_CODE) {
            var allPermissionsGranted = true
            for (result in grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allPermissionsGranted = false
                    break
                }
            }
            if (allPermissionsGranted) {
                // 权限已授予,执行文件读写操作
                performFileOperations()
            } else {
                // 权限被拒绝,处理权限请求失败的情况
            }
        }
    }
​
}

15.布局代码:

ini 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
​
    <TextView
        android:id="@+id/tv_test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
​
</androidx.constraintlayout.widget.ConstraintLayout>

16.跨进程使用:

16.1 主App存储数据

kotlin 复制代码
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 检查并请求权限
        if (checkPermissions()) {
            // 已经有权限,执行文件读写操作
            performFileOperations()
        } else {
            // 请求权限
            requestPermission()
        }
        initView()
 }
​
private fun performFileOperations() {
        //BaseApplication.Instance.initMMKV()
        // 执行文件读写操作
        initMMKVData()
}
​
private fun initMMKVData() {
        // 存储数据
        MMKVUtils.getInstance().encode("key1", "用户小明")
        MMKVUtils.getInstance().encode("age",  23)
        MMKVUtils.getInstance().encode("sex", "男")
        MMKVUtils.getInstance().encode("address", "北京市朝阳区")
        MMKVUtils.getInstance().encode("birthday", "2020-01-18")
        MMKVUtils.getInstance().encode("account", "188888")
        MMKVUtils.getInstance().encode("identity", false)
        MMKVUtils.getInstance().encode("amount", 888888.88)
}
​
@SuppressLint("SetTextI18n")
private fun initView() {
       textView.setOnClickListener {
            // 读取数据
            val value1: String = MMKVUtils.getInstance().decodeString("key1")
            val age: Int = MMKVUtils.getInstance().decodeInt("age")
            val sex: String = MMKVUtils.getInstance().decodeString("sex")
            val address: String = MMKVUtils.getInstance().decodeString("address")
            val birthday: String = MMKVUtils.getInstance().decodeString("birthday")
            val account: String = MMKVUtils.getInstance().decodeString("account")
            val identity: Boolean = MMKVUtils.getInstance().decodeBoolean("identity")
            val amount: Double = MMKVUtils.getInstance().decodeDouble("amount")
            Log.d(TAG, "====数据为===$value1$age$sex$address$birthday$account$identity$account$amount")
            textView.text = value1
            try {
                ToolUtils.openApp("com.example.testmmkv", this@MainActivity)
               // ToolUtils.openThirdApp("om.example.testmmkv", this@MainActivity)
            }catch (e:Exception){
                e.printStackTrace()
            }
        }
}
​
​

16.2 跨进程TestApp接收数据:

kotlin 复制代码
private fun performFileOperations() {
   // BaseApplication.Instance.initMMKV()
    initData()
}
    private fun initData() {
        val userName = MMKVUtils.getInstance().decodeString("key1")
        val age: Int = MMKVUtils.getInstance().decodeInt("age")
        val sex: String = MMKVUtils.getInstance().decodeString("sex")
        val address: String = MMKVUtils.getInstance().decodeString("address")
        val birthday: String = MMKVUtils.getInstance().decodeString("birthday")
        val account: String = MMKVUtils.getInstance().decodeString("account")
        val identity: Boolean = MMKVUtils.getInstance().decodeBoolean("identity")
        val amount: Double = MMKVUtils.getInstance().decodeDouble("amount")
        textView.text = "用户姓名:$userName"
        tvAddress.text = "用户地址:$address"
        tvAge.text = "用户年龄:$age"
        tvAccount.text = "用户账号:$account"
        tvAmount.text = "用户金额:$amount"
        tvBirthday.text = "用户生日:$birthday"
        tvIdentity.text = "是否党员:$identity"
        tvSex.text = "用户性别:$sex"
        Log.d(TAG, "====跨进程通信测试数据===$userName$age$sex$address$birthday$account$identity$account$amount")
        tvBack.setOnClickListener {
            try {
                MMKVUtils.getInstance().encode("backKey","用户小明回到原来的应用")
                finish()
               // ToolUtils.openApp("com.example.mmkvdemo", this@MainActivity)
                // ToolUtils.openThirdApp("om.example.testmmkv", this@MainActivity)
            }catch (e:Exception){
                e.printStackTrace()
            }
        }
    }

17.日志打印:

18.打开第三方app工具类:

java 复制代码
package com.example.mmkvdemo.utils;
​
import static android.content.Context.ACTIVITY_SERVICE;
​
import android.app.Activity;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
​
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.List;
​
/**
 * @author: njb
 * @date: 2023/8/8 0:03
 * @desc:
 */
public class ToolUtils {
    private static final String TAG = ToolUtils.class.getName();
​
    /**
     * 打开软件
     *
     * @param packageName 包名
     * @param context     上下文对象
     */
    public static void openApp(String packageName, Context context) {
        if (packageName != null) {
            PackageManager packageManager = context.getPackageManager();
            Intent intent = packageManager.getLaunchIntentForPackage(packageName);
            context.startActivity(intent);
        }
    }
​
    public static void openApp(String packageName, Context context, Bundle bundle) {
        Intent intent = new Intent();
        ComponentName comp = new ComponentName("com.tencent.mobileqq", "com.tencent.mobileqq.activity.SplashActivity");
        intent.setComponent(comp);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
​
​
    /**
     * 获取前台程序包名
     */
    public static String getForegroundAppPackageName(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(Activity.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
        ComponentName componentInfo = taskInfo.get(0).topActivity;
        return componentInfo.getPackageName();
    }
​
    /**
     * 根据报名杀死应用
     */
    public static void killApp(Context context, String packageName) {
        try {
            ActivityManager m = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
            Method method = m.getClass().getMethod("forceStopPackage", String.class);
            method.setAccessible(true);
            method.invoke(m, packageName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
​
    /**
     * 杀死第三方应用
     *
     * @param context
     * @param packageName
     */
    public static void killThirdApp(Context context, String packageName) {
        if (packageName != null) {
            killApp(context, packageName);
        }
    }
​
​
​
    /**
     * 获取前台activity名称
     *
     * @param context
     * @return
     */
    public static String getForegroundActivityName(Context context) {
        if (context == null) {
            return "";
        }
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> list = am.getRunningTasks(1);
        if (list != null && list.size() > 0) {
            ComponentName cpn = list.get(0).topActivity;
            return cpn.getClassName();
        }
        return "";
    }
​
​
    /**
     * 判断APP是否安装了
     *
     * @param packageName 包名
     * @return
     */
    public static boolean isAppInstalled(Context context, String packageName) {
        PackageManager packageManager = context.getPackageManager();
        try {
            packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
            return true;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            return false;
        }
    }
​
​
​
    public static void unInstall(Context context, String packageName) {
        if (packageName == null) {
            return;
        }
        Uri uri = Uri.parse("package:" + packageName);
        Intent uninstall = new Intent(Intent.ACTION_DELETE, uri);
        uninstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(uninstall);
    }
​
    /**
     * 静默卸载App
     *
     * @param packageName  包名
     * @return 是否卸载成功
     */
    public static boolean uninstall(String packageName) {
        Process process = null;
        BufferedReader successResult = null;
        BufferedReader errorResult = null;
        StringBuilder successMsg = new StringBuilder();
        StringBuilder errorMsg = new StringBuilder();
        try {
            process = new ProcessBuilder("pm", "uninstall", packageName).start();
            successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
            errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            String s;
            while ((s = successResult.readLine()) != null) {
                successMsg.append(s);
            }
            while ((s = errorResult.readLine()) != null) {
                errorMsg.append(s);
            }
        } catch (Exception e) {
            Log.d("e = " , e.toString());
        } finally {
            try {
                if (successResult != null) {
                    successResult.close();
                }
                if (errorResult != null) {
                    errorResult.close();
                }
            } catch (Exception e) {
                Log.d("Exception : " , e.toString());
            }
            if (process != null) {
                process.destroy();
            }
        }
        //如果含有"success"单词则认为卸载成功
        return successMsg.toString().equalsIgnoreCase("success");
    }
​
    /**
     * 判断应用是否存在
     *
     * @param context     上下文
     * @param packageName 包名
     * @return 是否存在
     */
    private boolean appExist(Context context, String packageName) {
        try {
            List<PackageInfo> packageInfoList = context.getPackageManager().getInstalledPackages(0);
            for (PackageInfo packageInfo : packageInfoList) {
                if (packageInfo.packageName.equalsIgnoreCase(packageName)) {
                    return true;
                }
            }
        } catch (Exception e) {
            Log.d(TAG,e.toString());
        }
        return false;
    }
}

19.实现效果:

20.总结:

以上就是今天的内容使用Kotlin封装基于MMKV的工具类,目前基本的数据存储没啥问题,但是跨进程在Android13中有点问题,两个app的存储目录不一样导致数据储存成功,但是跨进程时只能读取到本app目录下的数据,还没想好解决方法,Android9中及以下是可以正常使用的,因为11及以上内外部存储发生了变化,官网在1.3.1版本中更新了关于Android13的适配,他们给出的方案把两个app的mmkv文件存储写入到同一个公共的目录,后面有时间在尝试一下,如有解决的小伙伴们说一下.

21.项目demo源码:

gitee.com/jackning_ad...

相关推荐
呆萌小新@渊洁1 小时前
后端接收数组,集合类数据
android·java·开发语言
ByteSaid3 小时前
Android 内核开发之—— repo 使用教程
android·git
Tom哈哈3 小时前
Android 系统WIFI AP模式
android
ShawnRacine3 小时前
Android注册广播
android
麦克尔.马6 小时前
一个安卓鸿蒙化工具
android·华为·harmonyos
国通快递驿站6 小时前
理解JVM中的死锁:原因及解决方案
android·java·jvm·spring·诊断
岸芷漫步8 小时前
Android从启动到ActivityThread的流程分析
android
锋风8 小时前
哔哩哔哩直播链接报403解决办法
android
西瓜本瓜@9 小时前
在Android中fragment的生命周期
android·开发语言·android studio·kt
老哥不老11 小时前
MySQL安装教程
android·mysql·adb