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...

相关推荐
阿巴斯甜18 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker19 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952720 小时前
Andorid Google 登录接入文档
android
黄林晴21 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android