MMKV is an efficient, small, easy-to-use mobile key-value storage framework used in the WeChat application.
—
Advanced functionality including key expiration, native buffer operations, Anonymous Shared Memory support, performance optimizations, and specialized use cases for high-performance applications.
Automatic key expiration functionality for time-based data management.
/**
* Enable auto key expiration. This is an upgrade operation, the file format will change.
* And the file won't be accessed correctly by older version (v1.2.16) of MMKV.
* NOTICE: enableCompareBeforeSet will be invalid when Expiration is on.
* @param expireDurationInSecond The expire duration for all keys, ExpireNever (0) means no default duration
* @return true if successfully enabled, false otherwise
*/
public boolean enableAutoKeyExpire(int expireDurationInSecond);
/**
* Disable auto key expiration. This is a downgrade operation.
* @return true if successfully disabled, false otherwise
*/
public boolean disableAutoKeyExpire();Usage Example:
MMKV cacheKv = MMKV.mmkvWithID("cache_data");
// Enable expiration with default 1-hour expiration for all keys
boolean enabled = cacheKv.enableAutoKeyExpire(MMKV.ExpireInHour);
if (enabled) {
Log.d("MMKV", "Auto-expiration enabled with 1-hour default");
// Store data with default expiration
cacheKv.encode("api_response", responseData);
// Store with custom expiration (overrides default)
cacheKv.encode("temp_token", token, MMKV.ExpireInMinute);
// Store without expiration (overrides default)
cacheKv.encode("permanent_config", config, MMKV.ExpireNever);
} else {
Log.e("MMKV", "Failed to enable auto-expiration");
}
// Disable expiration when no longer needed
boolean disabled = cacheKv.disableAutoKeyExpire();
if (disabled) {
Log.d("MMKV", "Auto-expiration disabled");
}
// Check expiration status through key counts
long totalKeys = cacheKv.count();
long activeKeys = cacheKv.countNonExpiredKeys();
Log.d("MMKV", String.format("Keys: %d total, %d active, %d expired",
totalKeys, activeKeys, totalKeys - activeKeys));Advanced performance features for high-throughput applications.
/**
* Enable data compare before set, for better performance.
* If data for key seldom changes, use it.
* When encryption or expiration is on, compare-before-set will be invalid.
*/
public void enableCompareBeforeSet();
/**
* Disable data compare before set.
* Disabled by default.
*/
public void disableCompareBeforeSet();Usage Example:
MMKV configKv = MMKV.mmkvWithID("app_config");
// Enable compare-before-set for configuration data that rarely changes
try {
configKv.enableCompareBeforeSet();
Log.d("MMKV", "Compare-before-set optimization enabled");
// These operations will be optimized if data hasn't changed
configKv.encode("theme", "dark");
configKv.encode("language", "en");
configKv.encode("notifications", true);
// Subsequent identical writes will be faster
configKv.encode("theme", "dark"); // Optimized - no actual write
configKv.encode("theme", "light"); // Full write - data changed
} catch (RuntimeException e) {
Log.w("MMKV", "Compare-before-set not available: " + e.getMessage());
// Happens when encryption or expiration is enabled
}
// Performance benchmark example
private void benchmarkCompareBeforeSet() {
MMKV testKv = MMKV.mmkvWithID("benchmark");
// Test without optimization
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
testKv.encode("test_key", "same_value");
}
long timeWithoutOptimization = System.currentTimeMillis() - startTime;
// Test with optimization
testKv.enableCompareBeforeSet();
startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
testKv.encode("test_key", "same_value");
}
long timeWithOptimization = System.currentTimeMillis() - startTime;
Log.d("MMKV", String.format("Without optimization: %dms, With optimization: %dms",
timeWithoutOptimization, timeWithOptimization));
}Direct native memory operations for high-performance scenarios and JNI integration.
/**
* Create a native buffer, whose underlying memory can be directly transferred to another JNI method.
* Avoiding unnecessary JNI boxing and unboxing. Must be manually destroyed to avoid memory leak.
* @param size The size of the underlying memory
* @return NativeBuffer instance, or null if allocation failed
*/
public static NativeBuffer createNativeBuffer(int size);
/**
* Destroy the native buffer. Must be manually called to avoid memory leak.
* @param buffer The buffer to destroy
*/
public static void destroyNativeBuffer(NativeBuffer buffer);
/**
* Write the value of the key to the native buffer.
* @param key The key to read
* @param buffer The native buffer to write to
* @return The size written, or -1 on error
*/
public int writeValueToNativeBuffer(String key, NativeBuffer buffer);Usage Example:
MMKV kv = MMKV.defaultMMKV();
kv.encode("large_data", new byte[8192]); // Store 8KB data
// Create native buffer for direct memory access
NativeBuffer buffer = MMKV.createNativeBuffer(10240); // 10KB buffer
if (buffer != null) {
try {
// Write MMKV value directly to native buffer
int bytesWritten = kv.writeValueToNativeBuffer("large_data", buffer);
if (bytesWritten > 0) {
Log.d("MMKV", "Wrote " + bytesWritten + " bytes to native buffer");
// Buffer can now be passed to native JNI methods without copying
// processNativeData(buffer.pointer, buffer.size);
} else {
Log.e("MMKV", "Failed to write to native buffer");
}
} finally {
// Always destroy buffer to prevent memory leaks
MMKV.destroyNativeBuffer(buffer);
}
} else {
Log.e("MMKV", "Failed to create native buffer");
}
// Example JNI integration
public class NativeDataProcessor {
static {
System.loadLibrary("native_processor");
}
// Native method that works directly with MMKV native buffer
private native int processData(long pointer, int size);
public void processMMKVData(MMKV kv, String key) {
// Estimate buffer size
int valueSize = kv.getValueSize(key);
if (valueSize <= 0) return;
NativeBuffer buffer = MMKV.createNativeBuffer(valueSize + 1024); // Extra space
if (buffer != null) {
try {
int bytesWritten = kv.writeValueToNativeBuffer(key, buffer);
if (bytesWritten > 0) {
// Process data directly in native code
int result = processData(buffer.pointer, bytesWritten);
Log.d("MMKV", "Native processing result: " + result);
}
} finally {
MMKV.destroyNativeBuffer(buffer);
}
}
}
}Advanced inter-process communication using Anonymous Shared Memory for temporary data sharing.
/**
* Create an MMKV instance based on Anonymous Shared Memory, not synced to any disk files.
* Anonymous Shared Memory on Android can't grow dynamically, must set appropriate size on creation.
* @param context The context of Android App
* @param mmapID The unique ID of the MMKV instance
* @param size The maximum size of the underlying Anonymous Shared Memory
* @param mode The process mode of the MMKV instance
* @param cryptKey The encryption key (no more than 16 bytes, nullable)
* @return MMKV instance
* @throws RuntimeException if there's a runtime error
*/
public static MMKV mmkvWithAshmemID(Context context, String mmapID, int size, int mode, String cryptKey);
/**
* Get an ashmem MMKV instance that has been initiated by another process.
* @param mmapID The unique ID of the MMKV instance
* @param fd The file descriptor of the ashmem of the MMKV file
* @param metaFD The file descriptor of the ashmem of the MMKV crc file
* @param cryptKey The encryption key (no more than 16 bytes, nullable)
* @return MMKV instance
* @throws RuntimeException if there's a runtime error
*/
public static MMKV mmkvWithAshmemFD(String mmapID, int fd, int metaFD, String cryptKey);
/**
* Get the file descriptor of the ashmem of the MMKV file.
* @return The file descriptor
*/
public int ashmemFD();
/**
* Get the file descriptor of the ashmem of the MMKV crc file.
* @return The file descriptor
*/
public int ashmemMetaFD();Usage Example:
// Create ashmem instance for high-performance IPC
MMKV ashmemKv = MMKV.mmkvWithAshmemID(
this,
"real_time_data",
2 * 1024 * 1024, // 2MB fixed size
MMKV.MULTI_PROCESS_MODE,
null
);
// Use for real-time data sharing between processes
ashmemKv.encode("sensor_data", sensorReadings);
ashmemKv.encode("location", currentLocation);
ashmemKv.encode("timestamp", System.currentTimeMillis());
// Get file descriptors for IPC
int dataFD = ashmemKv.ashmemFD();
int metaFD = ashmemKv.ashmemMetaFD();
Log.d("MMKV", String.format("Ashmem FDs - data: %d, meta: %d", dataFD, metaFD));
// Create ParcelableMMKV for Binder IPC
ParcelableMMKV parcelable = new ParcelableMMKV(ashmemKv);
// Example: High-frequency data sharing
public class RealTimeDataManager {
private MMKV realTimeKv;
private Handler updateHandler;
public void startRealTimeUpdates() {
realTimeKv = MMKV.mmkvWithAshmemID(
getApplicationContext(),
"real_time_stream",
1024 * 1024, // 1MB for high-frequency updates
MMKV.MULTI_PROCESS_MODE,
null
);
updateHandler = new Handler();
updateHandler.post(updateRunnable);
}
private Runnable updateRunnable = new Runnable() {
@Override
public void run() {
// High-frequency updates (every 100ms)
realTimeKv.encode("frame_data", getCurrentFrameData());
realTimeKv.encode("timestamp", System.currentTimeMillis());
updateHandler.postDelayed(this, 100);
}
};
}Advanced namespace functionality for organizing MMKV instances with custom root directories.
/**
* Create a NameSpace with custom root directory.
* @param dir The customize root directory of a NameSpace
* @return A NameSpace with custom root dir
* @throws RuntimeException if there's a runtime error
*/
public static NameSpace nameSpace(String dir);
/**
* Get the default NameSpace (identical with the original MMKV with the global root dir).
* @return Default NameSpace
* @throws RuntimeException if there's a runtime error
*/
public static NameSpace defaultNameSpace();Usage Example:
// Create custom namespaces for different data categories
File userDataDir = new File(getFilesDir(), "user_data");
File cacheDataDir = new File(getCacheDir(), "mmkv_cache");
File secureDataDir = new File(getFilesDir(), "secure");
NameSpace userNamespace = MMKV.nameSpace(userDataDir.getAbsolutePath());
NameSpace cacheNamespace = MMKV.nameSpace(cacheDataDir.getAbsolutePath());
NameSpace secureNamespace = MMKV.nameSpace(secureDataDir.getAbsolutePath());
// Create instances within namespaces
MMKV userSettings = userNamespace.mmkvWithID("settings");
MMKV userPrefs = userNamespace.mmkvWithID("preferences");
MMKV imageCache = cacheNamespace.mmkvWithID("images");
MMKV apiCache = cacheNamespace.mmkvWithID("api_responses");
MMKV credentials = secureNamespace.mmkvWithID("auth", MMKV.SINGLE_PROCESS_MODE, "secret_key");
// Namespace-specific operations
boolean backupSuccess = userNamespace.backupOneToDirectory("settings", getBackupDir());
boolean validFile = cacheNamespace.isFileValid("images");
// Different namespaces can have instances with same ID
MMKV userConfig = userNamespace.mmkvWithID("config"); // /user_data/config
MMKV appConfig = MMKV.defaultNameSpace().mmkvWithID("config"); // /mmkv/configAdvanced error handling with custom recovery strategies.
/**
* Register a handler for MMKV log redirecting, and error handling.
* @param handler The callback handler
*/
public static void registerHandler(MMKVHandler handler);
/**
* Unregister the handler for MMKV.
*/
public static void unregisterHandler();Usage Example:
// Custom error handler with recovery strategies
MMKVHandler errorHandler = new MMKVHandler() {
@Override
public MMKVRecoverStrategic onMMKVCRCCheckFail(String mmapID) {
Log.w("MMKV", "CRC check failed for: " + mmapID);
// Different recovery strategies based on data type
if (mmapID.startsWith("cache_")) {
// Cache data can be discarded
return MMKVRecoverStrategic.OnErrorDiscard;
} else if (mmapID.equals("user_data")) {
// Critical user data should attempt recovery
return MMKVRecoverStrategic.OnErrorRecover;
} else {
// Default strategy
return MMKVRecoverStrategic.OnErrorRecover;
}
}
@Override
public MMKVRecoverStrategic onMMKVFileLengthError(String mmapID) {
Log.w("MMKV", "File length error for: " + mmapID);
// Log error for analytics
crashlytics.recordException(new Exception("MMKV file length error: " + mmapID));
// Attempt recovery for all file length errors
return MMKVRecoverStrategic.OnErrorRecover;
}
@Override
public boolean wantLogRedirecting() {
// Redirect logs in debug builds only
return BuildConfig.DEBUG;
}
@Override
public void mmkvLog(MMKVLogLevel level, String file, int line, String function, String message) {
String logTag = "MMKV-" + level.name();
switch (level) {
case LevelDebug:
Log.d(logTag, String.format("%s:%d %s() - %s", file, line, function, message));
break;
case LevelInfo:
Log.i(logTag, message);
break;
case LevelWarning:
Log.w(logTag, message);
break;
case LevelError:
Log.e(logTag, message);
// Report errors to crash reporting
crashlytics.log(message);
break;
case LevelNone:
break;
}
}
};
// Register error handler during app initialization
MMKV.registerHandler(errorHandler);
// Unregister when no longer needed
@Override
protected void onDestroy() {
super.onDestroy();
MMKV.unregisterHandler();
}Get system information and perform diagnostics on MMKV instances.
/**
* Get the device's memory page size.
* @return The device's memory page size in bytes
*/
public static int pageSize();
/**
* Get the version of MMKV.
* @return The version string of MMKV
*/
public static String version();
/**
* Notify MMKV that App is about to exit. It's totally fine not calling it at all.
*/
public static void onExit();Usage Example:
// System diagnostics and information
public class MMKVDiagnostics {
public void logSystemInfo() {
Log.d("MMKV", "MMKV Version: " + MMKV.version());
Log.d("MMKV", "System Page Size: " + MMKV.pageSize() + " bytes");
Log.d("MMKV", "Root Directory: " + MMKV.getRootDir());
}
public void performHealthCheck() {
String[] criticalInstances = {"user_data", "app_settings", "cache"};
for (String instanceId : criticalInstances) {
boolean exists = MMKV.checkExist(instanceId);
boolean valid = exists && MMKV.isFileValid(instanceId);
if (exists && !valid) {
Log.e("MMKV", "Corrupted instance detected: " + instanceId);
handleCorruption(instanceId);
}
if (exists && valid) {
MMKV kv = MMKV.mmkvWithID(instanceId);
long totalSize = kv.totalSize();
long actualSize = kv.actualSize();
long keyCount = kv.count();
Log.d("MMKV", String.format(
"Instance %s: %d keys, %d KB used, %d KB total (%.1f%% utilization)",
instanceId, keyCount, actualSize / 1024, totalSize / 1024,
100.0 * actualSize / totalSize
));
}
}
}
// App lifecycle integration
@Override
protected void onDestroy() {
super.onDestroy();
// Optional cleanup notification
MMKV.onExit();
}
}public final class NativeBuffer {
public long pointer; // Native memory pointer
public int size; // Buffer size in bytes
public NativeBuffer(long ptr, int length);
}
public final class NameSpace {
public String getRootDir();
public MMKV mmkvWithID(String mmapID);
public MMKV mmkvWithID(String mmapID, int mode);
public MMKV mmkvWithID(String mmapID, int mode, long expectedCapacity);
public MMKV mmkvWithID(String mmapID, int mode, String cryptKey);
public MMKV mmkvWithID(String mmapID, int mode, String cryptKey, long expectedCapacity);
public boolean backupOneToDirectory(String mmapID, String dstDir);
public boolean restoreOneMMKVFromDirectory(String mmapID, String srcDir);
public boolean isFileValid(String mmapID);
public boolean removeStorage(String mmapID);
public boolean checkExist(String mmapID);
}
public interface MMKVHandler {
MMKVRecoverStrategic onMMKVCRCCheckFail(String mmapID);
MMKVRecoverStrategic onMMKVFileLengthError(String mmapID);
boolean wantLogRedirecting();
void mmkvLog(MMKVLogLevel level, String file, int line, String function, String message);
}
public enum MMKVRecoverStrategic {
OnErrorDiscard, // Discard corrupted data (default)
OnErrorRecover // Attempt to recover corrupted data
}// Expiration constants (in seconds)
public static final int ExpireNever = 0; // Never expire
public static final int ExpireInMinute = 60; // 1 minute
public static final int ExpireInHour = 60 * 60; // 1 hour
public static final int ExpireInDay = 24 * 60 * 60; // 1 day
public static final int ExpireInMonth = 30 * 24 * 60 * 60; // 30 days
public static final int ExpireInYear = 365 * 30 * 24 * 60 * 60; // 365 daysInstall with Tessl CLI
npx tessl i tessl/maven-com-tencent--mmkv-shared