MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今在微信上使用,其性能和稳定性经过了时间的验证。近期也已移植到 Android / macOS / Win32 / POSIX 平台,一并开源。
在微信客户端的日常运营中,时不时就会爆发特殊文字引起系统的 crash,参考文章,文章里面设计的技术方案是在关键代码前后进行计数器的加减,通过检查计数器的异常,来发现引起闪退的异常文字。在会话列表、会话界面等有大量 cell 的地方,希望新加的计时器不会影响滑动性能;另外这些计数器还要永久存储下来——因为闪退随时可能发生。这就需要一个性能非常高的通用 key-value 存储组件,我们考察了 SharedPreferences、NSUserDefaults、SQLite 等常见组件,发现都没能满足如此苛刻的性能要求。考虑到这个防 crash 方案最主要的诉求还是实时写入,而 mmap 内存映射文件刚好满足这种需求,我们尝试通过它来实现一套 key-value 组件。
更详细的设计原理参考 MMKV 原理。
MMKV的出现其实是为了解决SharedPreferences的一些问题,微信团队希望以此来代替SharedPreferences,目前在Android中,对于经常使用的快速本地化存储,大部分人往往会选择SharedPreferences来作为存储方式, 作为Android库中自带的存储方式,SharePreferences在使用方式上还是很便捷的,但是也往往存在以下的一些问题。
1、通过 getSharedPreferences 可以获取 SP 实例,从首次初始化到读到数据会存在延迟,因为读文件的操作阻塞调用的线程直到文件读取完毕,如果在主线程调用,可能会对 UI 流畅度造成影响。(线程阻塞)
2、虽然支持设置 MODE_MULTI_PROCESS 标志位,但是跨进程共享 SP 存在很多问题,所以不建议使用该模式。(文件跨进程共享)
3、将数据写入文件需要将数据拷贝两次,再写入到文件中,如果数据量过大,也会有很大的性能损耗。(二次写入)
支持以下 Java 语言基础类型:
boolean、int、long、float、double、byte[],String、Set,任何实现了Parcelable的类型,对象存储方式是,转化成json串,通过字符串存储,使用的时候在取出来反序列化.
implementation(libs.mmkv)
[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]
@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)
}
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();
}
}
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")
}
}
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
}
}
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
}
}
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 {
// 权限被拒绝,处理权限请求失败的情况
}
}
}
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 {
// 权限被拒绝,处理权限请求失败的情况
}
}
}
}
<?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>
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()
}
}
}
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()
}
}
}
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;
}
}
以上就是今天的内容使用Kotlin封装基于MMKV的工具类,目前基本的数据存储没啥问题,但是跨进程在Android13中有点问题,两个app的存储目录不一样导致数据储存成功,但是跨进程时只能读取到本app目录下的数据,还没想好解决方法,Android9中及以下是可以正常使用的,因为11及以上内外部存储发生了变化,官网在1.3.1版本中更新了关于Android13的适配,他们给出的方案把两个app的mmkv文件存储写入到同一个公共的目录,后面有时间在尝试一下,如有解决的小伙伴们说一下.
https://gitee.com/jackning_admin/mmkvutils-demo