• 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.依赖导入:

    implementation(libs.mmkv)
    
    • 1

    在这里插入图片描述

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

    [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]
    
    • 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
    • 26
    • 27

    8.App.gradle配置:

    @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)
    }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

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

    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();
        }
    }
    
    
    • 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
    • 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
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    10.使用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")
        }
    }
    
    
    
    • 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
    • 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
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    11.初始化:

    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
        }
    }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在这里插入图片描述

    12.简单使用:

       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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    13.适配Android13:

    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 {
                    // 权限被拒绝,处理权限请求失败的情况
                }
            }
        }
    
    • 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
    • 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
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    14.完整使用代码:

    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 {
                    // 权限被拒绝,处理权限请求失败的情况
                }
            }
        }
    
    }
    
    • 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
    • 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
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127

    15.布局代码:

    <?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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    16.跨进程使用:

    16.1 主App存储数据

    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()
                }
            }
    }
    
    
    
    • 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
    • 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
    • 52
    • 53
    • 54
    • 55
    • 56

    16.2 跨进程TestApp接收数据:

    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()
                }
            }
        }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    17.日志打印:

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

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

    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;
        }
    }
    
    • 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
    • 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
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203

    19.实现效果:

    在这里插入图片描述
    在这里插入图片描述

    20.总结:

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

    20.项目demo源码:

    https://gitee.com/jackning_admin/mmkvutils-demo

  • 相关阅读:
    类和对象(上)
    DGIOT实战教程——虚拟Modbus TCP接入
    2022/7/27 算力-价格明细
    国外芯片,为什么有中文资料和网页?
    总结:企业开发中,如何利用webService获取第三方公司传递过来的数据。
    用strtok和指针数组构造一个能对字符转进行解析的函数
    【Python程序设计】 工厂模式【07/8】
    探索请求头中的UUID的不同版本:UUID1、UUID3、UUID4和UUID5
    wandb不可缺少的机器学习分析工具
    el-tab 滚动条滚动到指定位置
  • 原文地址:https://blog.csdn.net/u012556114/article/details/132748788