Code is the best teacher
Summary 1.
Github
The official documentation
- MMKV is a key-value component based on MMAP memory mapping
- High performance, strong stability (the underlying serialization/deserialization using Protobuf implementation)
- Support encryption
- Multi-process sharing is supported
- Support anonymous memory, memory suspended files, high security
- High efficiency
- SharedPreferences direct migration is supported
- Supported types: Boolean, int, long, float, double, byte[], String, Set, Parcelable
2. Contrast & Principle
Data source: Official test data of Tencent
2.1 Single process Performance
It can be seen that the performance of MMKV is faster than that of SP and SQLite
(The test machine was Huawei Mate 20 Pro 128G and Android 10. The operation was repeated 1K times for each group, and the time unit was ms.)
2.2 Multi-process Performance
Can see, multiple processes, MMKV far beyond MultiProcessSharedPreferences & SQLite & SQLite. MMKV in Android multi-process key – value storage component is the default choice
(The test machine was Huawei Mate 20 Pro 128G and Android 10. The operation was repeated 1K times for each group, and the time unit was ms.)
2.3 the principle
- Memory to prepare
The MMAP memory mapping file provides a memory block that can be written at any time. App only writes data into it, and the operating system is responsible for writing the memory back to the file. There is no need to worry about data loss caused by crash.
- Data organization
With respect to data serialization, we use protobuf protocol, and PB has a good performance in performance and space occupancy.
- To optimize
Given that the primary usage scenario is frequent write updates, we need the ability to incrementally update. We consider appending the incremental KV object to the end of memory after serialization.
- Space growth
Using Append for incremental updates brings up a new problem, which is that the file size can grow out of control if you append constantly. We need to find a compromise between performance and space.
Please move to MMKV principle for more information
3. Simple use
1. Build. Gradle for module
Dependencies {implementation 'com. Tencent: MMKV -static: 1.2.2 '}Copy the code
2. Initialize the MMKV in the Application of app. You can also customize the customized path to initialize the MMKV
public void onCreate() { super.onCreate(); // String rootDir = mmkv.initialize (this); }Copy the code
3. Simple use: Access data
MMKV kv = MMKV.defaultMMKV();
kv.encode("bool", true);
boolean bValue = kv.decodeBool("bool");
kv.encode("int", Integer.MIN_VALUE);
int iValue = kv.decodeInt("int");
kv.encode("string", "Hello from mmkv");
String str = kv.decodeString("string");
Copy the code
4. Advanced usage
Likewise, lead packages
Dependencies {implementation 'com. Tencent: MMKV -static: 1.2.2 '}Copy the code
4.1. Initialization (logging, multi-process, encryption, grouped storage)
Initialize the MMKV in the Application of app. You can also customize the customized path to initialize the MMKV. See the tool class encapsulated in Demo
public void onCreate() { super.onCreate(); Mmkvhelper.init (this); String dir = getFilesDir().getabsolutePath () + "/mmkv_2"; String rootDir = MMKVHelper.init(dir); }Copy the code
MMKVHelper encapsulates classes. Method, covering encryption, multi-process usage scenarios
public static String ENCRPT_KEY = BuildConfig.LIBRARY_PACKAGE_NAME; Public static String ENCRPT_KEY_MULTI_PROGRESS = buildconfig.library_package_name; Key2 public static String init(Application Context) {return init(context,"",true,ENCRPT_KEY); } public static String init(String mmkvFilePath) { return init(null,mmkvFilePath ,true ,ENCRPT_KEY); } public static String init(Application context, String path ,boolean openLog ,String encryptKey) { String rootDir ; if (TextUtils.isEmpty(path)){ rootDir = MMKV.initialize(context); }else { rootDir = MMKV.initialize(path); } if (openLog) { Log.d("MMKV root dir:", rootDir); MMKV.setLogLevel(MMKVLogLevel.LevelInfo); SetLogLevel (mmkvlogLevel.levelNone);}else {// Log should not be turned off unless there is very strong evidence that mmkv.setLogLevel (mmkvloglevel.levelNone) is slowing down the App; } if (! TextUtils.isEmpty(encryptKey)){ ENCRPT_KEY = encryptKey; ENCRPT_KEY_MULTI_PROGRESS = encryptKey; } return rootDir; }Copy the code
4.2. Add, delete, modify and check
MMKV is simple to use, the default configuration is single process, no encryption, packaging MMKVHelper advanced use, open and convenient Settings, see Demo packaging class code for the complete code.
example:
4.2.1 Storage (Add, Change) :
public static boolean put(String key, String value) { return MMKV.defaultMMKV(MMKV.SINGLE_PROCESS_MODE , ENCRPT_KEY).encode(key, value); } public static boolean put(String key, int value) { return MMKV.defaultMMKV(MMKV.SINGLE_PROCESS_MODE, ENCRPT_KEY).encode(key, value); } // Same for other types...... public static Boolean put2Group(String GroupId, String key, Object value) { return put2Group(GroupId, key, value, false); } public static Boolean put2Group(String GroupId, String key, Object value, Boolean multiProgress) {MMKV MMKV; If (multiProgress) {// If the business needs multi-process access, Mmkv.multi_process_mode MMKV = mmkv.mmkvwithid (GroupId, mmkv.multi_process_mode, ENCRPT_KEY_MULTI_PROGRESS); } else {// Default single process MMKV = mmkv.mmkvWithID (GroupId, mmkv.single_process_mode, ENCRPT_KEY); } boolean flag = false; if (value instanceof Boolean) { flag = mmkv.encode(key, (Boolean) value); } if (value instanceof Integer) { flag = mmkv.encode(key, (int) value); } if (value instanceof Float) { flag = mmkv.encode(key, (Float) value); } if (value instanceof Double) { flag = mmkv.encode(key, (Double) value); } if (value instanceof Long) { flag = mmkv.encode(key, (Long) value); } if (value instanceof String) { flag = mmkv.encode(key, (String) value); } if (value instanceof Parcelable) { flag = mmkv.encode(key, (Parcelable) value); } return flag; }Copy the code
4.2.2 delete
//delete simple
public static boolean delete(String deleteItemKey) {
MMKV.defaultMMKV(MMKV.SINGLE_PROCESS_MODE, ENCRPT_KEY).remove(deleteItemKey);
return MMKV.defaultMMKV(MMKV.SINGLE_PROCESS_MODE, ENCRPT_KEY).contains(deleteItemKey);
}
public static boolean delete(String groupId, String deleteItemKey) {
return delete(groupId, deleteItemKey, false);
}
public static boolean delete(String groupId, String deleteItemKey, boolean multiProgress) {
int mode = multiProgress ? MMKV.MULTI_PROCESS_MODE : MMKV.SINGLE_PROCESS_MODE;
MMKV mmkv;
if (mode == MMKV.MULTI_PROCESS_MODE) {
mmkv = MMKV.mmkvWithID(groupId, MMKV.MULTI_PROCESS_MODE, ENCRPT_KEY_MULTI_PROGRESS);
} else {
mmkv = MMKV.mmkvWithID(groupId, mode, ENCRPT_KEY);
}
mmkv.remove(deleteItemKey);
return mmkv.contains(deleteItemKey);
}
Copy the code
4.2.3 Obtaining (Query)
Basic acquisition, multi-process acquisition, group acquisition
public static String get(String key, String defValue) { return MMKV.defaultMMKV(MMKV.SINGLE_PROCESS_MODE , ENCRPT_KEY).decodeString(key, defValue); } public static int get(String key, int defValue) { return MMKV.defaultMMKV(MMKV.SINGLE_PROCESS_MODE, ENCRPT_KEY).decodeInt(key, defValue); } public static <T> T getByGroup(String GroupId, String key, Object defValue) { return (T) getByGroup(GroupId, key, defValue, false); } public static Object getByGroup(String GroupId, String key, Object defValue, boolean multiProgress) { MMKV mmkv; If (multiProgress) {// If the business needs multi-process access, Mmkv.multi_process_mode MMKV = mmkv.mmkvwithid (GroupId, mmkv.multi_process_mode, ENCRPT_KEY_MULTI_PROGRESS); } else {// Default single process MMKV = mmkv.mmkvWithID (GroupId, mmkv.single_process_mode, ENCRPT_KEY); } if (defValue instanceof Boolean) { return mmkv.decodeBool(key, (Boolean) defValue); } if (defValue instanceof Integer) { return mmkv.decodeInt(key, (int) defValue); } if (defValue instanceof Float) { return mmkv.decodeFloat(key, (Float) defValue); } if (defValue instanceof Double) { return mmkv.decodeDouble(key, (Double) defValue); } if (defValue instanceof Long) { return mmkv.decodeLong(key, (Long) defValue); } if (defValue instanceof String) { return mmkv.decodeString(key, (String) defValue); } if (defValue instanceof Parcelable) { return mmkv.decodeParcelable(key, (Class<Parcelable>) defValue); } return null; }Copy the code
5. Other Settings
SP 5.1 migration
SharedPreferences migration MMKV provides importFromSharedPreferences () function, can be more convenient to transfer data.
MMKV also additional implementation of SharedPreferences, SharedPreferences.Editor these two interfaces, in the migration of only two or three lines of code, other CRUD operation code need not change.
private void testImportSharedPreferences() { //SharedPreferences preferences = getSharedPreferences("myData", MODE_PRIVATE); MMKV preferences = MMKV.mmkvWithID("myData"); {SharedPreferences old_man = getSharedPreferences("myData", MODE_PRIVATE); preferences.importFromSharedPreferences(old_man); old_man.edit().clear().commit(); SharedPreferences.Editor = preferences. Edit (); editor.putBoolean("bool", true); editor.putInt("int", Integer.MIN_VALUE); editor.putLong("long", Long.MAX_VALUE); Editor. PutFloat (" float ", to 3.14 f); editor.putString("string", "hello, imported"); HashSet<String> set = new HashSet<String>(); set.add("W"); set.add("e"); set.add("C"); set.add("h"); set.add("a"); set.add("t"); editor.putStringSet("string-set", set); // editor.mit (); }Copy the code
5.2 log
MMKV prints logs to Logcat by default, which makes it difficult to locate and resolve online problems. You can receive and forward MMKV logs when the App starts. To implement the MMKVHandler interface, add code like the following:
@Override public boolean wantLogRedirecting() { return true; } @Override public void mmkvLog(MMKVLogLevel level, String file, int line, String func, String message) { String log = "<" + file + ":" + line + "::" + func + "> " + message; switch (level) { case LevelDebug: //Log.d("redirect logging MMKV", log); break; case LevelInfo: //Log.i("redirect logging MMKV", log); break; case LevelWarning: //Log.w("redirect logging MMKV", log); break; case LevelError: //Log.e("redirect logging MMKV", log); break; case LevelNone: //Log.e("redirect logging MMKV", log); break; }}Copy the code
The log component is recommended to use Xlog, also originated from the wechat team.
Disabling logs (not recommended) :
Logs should not be turned off unless there is very strong evidence that MMKV’s logs are slowing down the App. Without logs, users will not be able to follow up in case of future problems.
MMKV.setLogLevel(MMKVLogLevel.LevelNone); // Disable loggingCopy the code
5.3 the encryption
MMKV stores all key-values in plaintext by default and relies on the Sandbox mechanism of the Android system to ensure file encryption. If you are worried about information leakage, you can choose to encrypt MMKV.
String cryptKey = "My-Encrypt-Key"; MMKV kv = MMKV.mmkvWithID("MyID", MMKV.SINGLE_PROCESS_MODE, cryptKey); You can change the key, change an encrypted MMKV to plaintext, or vice versa. final String mmapID = "testAES_reKey1"; // an unencrypted MMKV instance MMKV kv = MMKV.mmkvWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, null); // change from unencrypted to encrypted kv.reKey("Key_seq_1"); // change encryption key kv.reKey("Key_seq_2"); // change from encrypted to unencrypted kv.reKey(null);Copy the code
5.4 Customizing a Root Directory
$(FilesDir)/ MMKV/by default. You can customize the root directory at App startup:
String dir = getFilesDir().getAbsolutePath() + "/mmkv_2"; String rootDir = MMKV.initialize(dir); Log.i("MMKV", "mmkv root: " + rootDir); MMKV even supports custom directories for a file: String relativePath = getFilesDir().getabsolutePath () + "/mmkv_3"; MMKV kv = MMKV.mmkvWithID("testCustomDir", relativePath);Copy the code
Note: The official recommendation is to store the MMKV file in your App’s private path, not in external storage (i.e. SD card).
5.5 Customizing library Loaders
Some Android devices (API Level 19) may fail to install/update APK, causing libmmkv.so not to be found. Then I will meet Java. Lang. UnsatisfiedLinkError crash. There is an open source library called ReLinker that addresses this problem. You can use it to load MMKV:
String dir = getFilesDir().getAbsolutePath() + "/mmkv"; MMKV.initialize(dir, new MMKV.LibLoader() { @Override public void loadLibrary(String libName) { ReLinker.loadLibrary(MyApplication.this, libName); }});Copy the code
5.6 Native Buffer
When fetching a String or byte[] from MMKV, there will be a memory copy from native to JVM. If this value is immediately passed to another Native library (JNI), there will be another memory copy from the JVM to Native. When this value is large, the whole process can be very wasteful. The Native Buffer is designed to solve this problem. NativeBuffer is a memory Buffer created by Native, encapsulated in Java as the type of NativeBuffer, which can be transparently transmitted to another Native library for access and processing. The entire process avoids the waste of copying memory to and back from the JVM. Sample code:
int sizeNeeded = kv.getValueActualSize("bytes"); NativeBuffer nativeBuffer = MMKV.createNativeBuffer(sizeNeeded); if (nativeBuffer ! = null) { int size = kv.writeValueToNativeBuffer("bytes", nativeBuffer); Log.i("MMKV", "size Needed = " + sizeNeeded + " written size = " + size); // pass nativeBuffer to another native library // ... // destroy when you're done MMKV.destroyNativeBuffer(nativeBuffer); }Copy the code
5.7 Data Recovery
If the CRC fails or the file length is incorrect, the MMKV discards all data by default. You can make THE MMKV recover the data. Note that the repair rate is not guaranteed, and strange key-values can be fixed. Also implement the MMKVHandler interface, add the following code:
@Override
public MMKVRecoverStrategic onMMKVCRCCheckFail(String mmapID) {
return MMKVRecoverStrategic.OnErrorRecover;
}
@Override
public MMKVRecoverStrategic onMMKVFileLengthError(String mmapID) {
return MMKVRecoverStrategic.OnErrorRecover;
}
Copy the code
5.8 Multi-process and implementation
Multi-process design and implementation