CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-tencent--mmkv-shared

MMKV is an efficient, small, easy-to-use mobile key-value storage framework used in the WeChat application.

Pending
Overview
Eval results
Files

advanced-features.mddocs/

Advanced Features

Advanced functionality including key expiration, native buffer operations, Anonymous Shared Memory support, performance optimizations, and specialized use cases for high-performance applications.

Capabilities

Key Expiration System

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));

Performance Optimizations

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));
}

Native Buffer Operations

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);
            }
        }
    }
}

Anonymous Shared Memory (Ashmem) Support

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);
        }
    };
}

Namespace Management

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/config

Error Handling and Recovery

Advanced 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();
}

System Information and Diagnostics

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();
    }
}

Types

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
}

Constants

// 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 days

Install with Tessl CLI

npx tessl i tessl/maven-com-tencent--mmkv-shared

docs

advanced-features.md

data-management.md

encryption.md

index.md

initialization.md

instance-management.md

multi-process.md

storage-operations.md

tile.json