From 9348a93f26be4792c4d057db336558b9fe6ef38d Mon Sep 17 00:00:00 2001 From: guoling Date: Wed, 18 Mar 2026 12:40:05 +0800 Subject: [PATCH] refactor callback system into unified MMKVHandler across all platforms Replace 4 separate static function pointers (LogHandler, ErrorHandler, ContentChangeHandler, ContentLoadedHandler) with a single MMKVHandler abstract class in Core C++. Update all 6 platform wrappers (iOS, Android, OHOS, Python, Golang, Flutter) to use adapter classes bridging to the new unified handler. Add onMMKVContentLoadSuccessfully callback to Android, OHOS, Flutter, Python and Golang where it was previously missing. --- Android/MMKV/mmkv/proguard-rules.pro | 1 + .../MMKV/mmkv/src/main/cpp/flutter-bridge.cpp | 93 ++++++---- .../MMKV/mmkv/src/main/cpp/native-bridge.cpp | 155 ++++++++++------ .../src/main/java/com/tencent/mmkv/MMKV.java | 28 ++- .../mmkv/MMKVContentChangeNotification.java | 2 + .../java/com/tencent/mmkv/MMKVHandler.java | 22 ++- .../com/tencent/mmkvdemo/MyApplication.java | 19 +- Core/CMakeLists.txt | 1 + Core/Core.xcodeproj/project.pbxproj | 6 + Core/MMKV.cpp | 64 ++----- Core/MMKV.h | 24 +-- Core/MMKVHandler.h | 55 ++++++ Core/MMKVLog.cpp | 12 +- Core/MMKVLog.h | 3 +- Core/MMKVLog_Android.cpp | 8 +- Core/MMKVPredef.h | 14 -- .../MMKV/src/main/cpp/flutter-bridge.cpp | 93 ++++++---- .../MMKV/src/main/cpp/native_bridge.cpp | 165 +++++++++++------- .../MMKV/src/main/ets/utils/MMKVHandler.ets | 8 + POSIX/golang/callback.go | 134 +++++++------- POSIX/golang/golang-bridge.cpp | 103 ++++++----- POSIX/golang/golang-bridge.h | 4 +- POSIX/golang/mmkv.go | 21 ++- POSIX/golang/test/main.go | 46 ++--- Python/libmmkv_python.cpp | 140 +++++++-------- flutter/mmkv/lib/mmkv.dart | 12 ++ .../mmkv_ios/darwin/Classes/flutter-bridge.h | 1 + .../mmkv_ios/darwin/Classes/flutter-bridge.mm | 10 ++ flutter/mmkv_linux/linux/flutter-bridge.cpp | 93 ++++++---- .../lib/mmkv_platform_ffi.dart | 5 + .../lib/mmkv_platform_interface.dart | 10 ++ flutter/mmkv_win32/windows/flutter-bridge.cpp | 109 ++++++------ iOS/MMKV/MMKV/libMMKV.mm | 127 +++++++------- 33 files changed, 917 insertions(+), 671 deletions(-) create mode 100644 Core/MMKVHandler.h diff --git a/Android/MMKV/mmkv/proguard-rules.pro b/Android/MMKV/mmkv/proguard-rules.pro index 6fb0ef7b..0cb364d3 100644 --- a/Android/MMKV/mmkv/proguard-rules.pro +++ b/Android/MMKV/mmkv/proguard-rules.pro @@ -6,4 +6,5 @@ private static *** onMMKVFileLengthError(***); private static *** mmkvLogImp(...); private static *** onContentChangedByOuterProcess(***); + private static *** onMMKVContentLoadSuccessfully(***); } diff --git a/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp b/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp index a6e7dc84..8165f1ff 100644 --- a/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp +++ b/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp @@ -41,13 +41,50 @@ extern string g_android_tmpDir; # define MMKV_EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used)) using LogCallback_t = void (*)(uint32_t level, const char *file, int32_t line, const char *funcname, const char *message); -LogCallback_t g_logCallback = nullptr; +using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); +using ContenctChangeCallback_t = void (*)(const char *mmapID); -static void myLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) { - if (g_logCallback) { - g_logCallback(level, file, line, function, message.c_str()); +class FlutterMMKVHandler : public mmkv::MMKVHandler { +public: + LogCallback_t logCallback = nullptr; + ErrorCallback_t errorCallback = nullptr; + ContenctChangeCallback_t contentChangeCallback = nullptr; + ContenctChangeCallback_t contentLoadedCallback = nullptr; + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) override { + if (logCallback) { + logCallback(level, file, line, function, message.c_str()); + } } -} + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVCRCCheckFail); + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVFileLength); + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (contentChangeCallback) { + contentChangeCallback(mmapID.c_str()); + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (contentLoadedCallback) { + contentLoadedCallback(mmapID.c_str()); + } + } +}; + +static FlutterMMKVHandler g_flutterHandler; MMKV_EXPORT void *mmkvInitialize_v2(const char *rootDir, const char *cacheDir, int32_t sdkInt, int32_t logLevel, LogCallback_t callback) { if (!rootDir) { @@ -65,8 +102,8 @@ MMKV_EXPORT void *mmkvInitialize_v2(const char *rootDir, const char *cacheDir, i #endif if (callback) { - g_logCallback = callback; - MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, myLogHandler); + g_flutterHandler.logCallback = callback; + MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, &g_flutterHandler); } else { MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel); } @@ -663,40 +700,24 @@ MMKV_EXPORT bool isReadOnly(void *handle) { return false; } -using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); -static ErrorCallback_t g_errorCallback = nullptr; - -static MMKVRecoverStrategic myErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - if (g_errorCallback) { - return (MMKVRecoverStrategic) g_errorCallback(mmapID.c_str(), errorType); - } - return OnErrorDiscard; -} - MMKV_EXPORT void registerErrorHandler(ErrorCallback_t callback) { - g_errorCallback = callback; - if (callback) { - MMKV::registerErrorHandler(myErrorHandler); - } else { - MMKV::unRegisterErrorHandler(); - } -} - -using ContenctChangeCallback_t = void (*)(const char *mmapID); -static ContenctChangeCallback_t g_contentChanceCallback = nullptr; - -static void myContentChangeHandler(const std::string &mmapID) { - if (g_contentChanceCallback) { - g_contentChanceCallback(mmapID.c_str()); + g_flutterHandler.errorCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.contentChangeCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); } } MMKV_EXPORT void registerContentChangeNotify(ContenctChangeCallback_t callback) { - g_contentChanceCallback = callback; - if (callback) { - MMKV::registerContentChangeHandler(myContentChangeHandler); - } else { - MMKV::unRegisterContentChangeHandler(); + g_flutterHandler.contentChangeCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); + } +} + +MMKV_EXPORT void registerContentLoadedNotify(ContenctChangeCallback_t callback) { + g_flutterHandler.contentLoadedCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentChangeCallback) { + MMKV::registerHandler(&g_flutterHandler); } } diff --git a/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp b/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp index dedfba6e..d2626525 100644 --- a/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp +++ b/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp @@ -40,6 +40,7 @@ static jmethodID g_callbackOnCRCFailID = nullptr; static jmethodID g_callbackOnFileLengthErrorID = nullptr; static jmethodID g_mmkvLogID = nullptr; static jmethodID g_callbackOnContentChange = nullptr; +static jmethodID g_callbackOnContentLoaded = nullptr; static JavaVM *g_currentJVM = nullptr; static int registerNativeMethods(JNIEnv *env, jclass cls); @@ -52,6 +53,79 @@ namespace mmkv { typedef void (*AndroidLogHandler)(MMKVLogLevel level, const char *file, int line, const char *function, const char *message); static AndroidLogHandler g_androidLogHandler = nullptr; static void androidLogWrapper(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message); + + static JNIEnv *getCurrentEnv(); + static jstring string2jstring(JNIEnv *env, const string &str); + +// C++ adapter class that bridges mmkv::MMKVHandler to JNI +class JNIMMKVHandler : public mmkv::MMKVHandler { + bool m_logRedirecting = false; + bool m_hasCallback = false; + bool m_wantsContentChange = false; +public: + void setLogRedirecting(bool logRedirecting) { m_logRedirecting = logRedirecting; } + void setHasCallback(bool hasCallback) { m_hasCallback = hasCallback; } + void setWantsContentChange(bool wantsContentChange) { m_wantsContentChange = wantsContentChange; } + bool isLogRedirecting() const { return m_logRedirecting; } + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) override { + if (m_logRedirecting) { + if (g_androidLogHandler) { + g_androidLogHandler(level, file, line, function, message.c_str()); + } else { + mmkv::mmkvLog(level, file, line, function, message); + } + } + } + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (!m_hasCallback) { + return OnErrorDiscard; + } + auto currentEnv = getCurrentEnv(); + if (currentEnv && g_callbackOnCRCFailID) { + jstring str = string2jstring(currentEnv, mmapID); + auto strategic = currentEnv->CallStaticIntMethod(g_cls, g_callbackOnCRCFailID, str); + currentEnv->DeleteLocalRef(str); + return static_cast(strategic); + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (!m_hasCallback) { + return OnErrorDiscard; + } + auto currentEnv = getCurrentEnv(); + if (currentEnv && g_callbackOnFileLengthErrorID) { + jstring str = string2jstring(currentEnv, mmapID); + auto strategic = currentEnv->CallStaticIntMethod(g_cls, g_callbackOnFileLengthErrorID, str); + currentEnv->DeleteLocalRef(str); + return static_cast(strategic); + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + auto currentEnv = getCurrentEnv(); + if (currentEnv && g_callbackOnContentChange) { + jstring str = string2jstring(currentEnv, mmapID); + currentEnv->CallStaticVoidMethod(g_cls, g_callbackOnContentChange, str); + currentEnv->DeleteLocalRef(str); + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + auto currentEnv = getCurrentEnv(); + if (currentEnv && g_callbackOnContentLoaded) { + jstring str = string2jstring(currentEnv, mmapID); + currentEnv->CallStaticVoidMethod(g_cls, g_callbackOnContentLoaded, str); + currentEnv->DeleteLocalRef(str); + } + } +}; + +static JNIMMKVHandler g_jniHandler; } #define InternalLogError(format, ...) \ @@ -112,6 +186,11 @@ extern "C" JNIEXPORT JNICALL jint JNI_OnLoad(JavaVM *vm, void *reserved) { if (!g_callbackOnContentChange) { InternalLogError("fail to get method id for onContentChangedByOuterProcess()"); } + g_callbackOnContentLoaded = + env->GetStaticMethodID(g_cls, "onMMKVContentLoadSuccessfully", "(Ljava/lang/String;)V"); + if (!g_callbackOnContentLoaded) { + InternalLogError("fail to get method id for onMMKVContentLoadSuccessfully()"); + } // Note: If you use NDK r23 or older, you can get API level by accessing android.os.Build.VERSION.SDK_INT g_android_api = android_get_device_api_level(); @@ -131,32 +210,29 @@ namespace mmkv { static string jstring2string(JNIEnv *env, jstring str); -MMKVRecoverStrategic onMMKVError(const std::string &mmapID, MMKVErrorType errorType); - MMKV_JNI void jniInitialize(JNIEnv *env, jobject obj, jstring rootDir, jstring cacheDir, jint logLevel, jboolean logReDirecting, jboolean hasCallback, jlong nativeLogHandler) { if (!rootDir) { return; } const char *kstr = env->GetStringUTFChars(rootDir, nullptr); if (kstr) { - mmkv::LogHandler logHandler = nullptr; - if (logReDirecting) { - if (nativeLogHandler != 0) { - g_androidLogHandler = (AndroidLogHandler) nativeLogHandler; - logHandler = androidLogWrapper; - } else { - logHandler = mmkvLog; - } + g_jniHandler.setLogRedirecting(logReDirecting == JNI_TRUE); + g_jniHandler.setHasCallback(hasCallback == JNI_TRUE); + if (logReDirecting && nativeLogHandler != 0) { + g_androidLogHandler = (AndroidLogHandler) nativeLogHandler; + } else { + g_androidLogHandler = nullptr; } - MMKV::initializeMMKV(kstr, (MMKVLogLevel) logLevel, logHandler); + mmkv::MMKVHandler *handler = (logReDirecting || hasCallback) ? &g_jniHandler : nullptr; + MMKV::initializeMMKV(kstr, (MMKVLogLevel) logLevel, handler); env->ReleaseStringUTFChars(rootDir, kstr); g_android_tmpDir = jstring2string(env, cacheDir); - if (hasCallback == JNI_TRUE) { - MMKV::registerErrorHandler(onMMKVError); + if (hasCallback == JNI_TRUE || logReDirecting == JNI_TRUE) { + MMKV::registerHandler(&g_jniHandler); } else { - MMKV::unRegisterErrorHandler(); + MMKV::unRegisterHandler(); } } } @@ -227,24 +303,6 @@ static JNIEnv *getCurrentEnv() { return nullptr; } -MMKVRecoverStrategic onMMKVError(const std::string &mmapID, MMKVErrorType errorType) { - jmethodID methodID = nullptr; - if (errorType == MMKVCRCCheckFail) { - methodID = g_callbackOnCRCFailID; - } else if (errorType == MMKVFileLength) { - methodID = g_callbackOnFileLengthErrorID; - } - - auto currentEnv = getCurrentEnv(); - if (currentEnv && methodID) { - jstring str = string2jstring(currentEnv, mmapID); - auto strategic = currentEnv->CallStaticIntMethod(g_cls, methodID, str); - currentEnv->DeleteLocalRef(str); - return static_cast(strategic); - } - return OnErrorDiscard; -} - extern "C" void internalLogWithLevel(MMKVLogLevel level, const char *filename, const char *func, int line, const char *format, ...) { if (level >= g_currentLogLevel) { std::string message; @@ -294,15 +352,6 @@ static void androidLogWrapper(MMKVLogLevel level, const char *file, int line, co g_androidLogHandler(level, file, line, function, message.c_str()); } -static void onContentChangedByOuterProcess(const std::string &mmapID) { - auto currentEnv = getCurrentEnv(); - if (currentEnv && g_callbackOnContentChange) { - jstring str = string2jstring(currentEnv, mmapID); - currentEnv->CallStaticVoidMethod(g_cls, g_callbackOnContentChange, str); - currentEnv->DeleteLocalRef(str); - } -} - MMKV_JNI jlong getMMKVWithID(JNIEnv *env, jobject, jstring mmapID, jint mode, jstring cryptKey, jstring rootPath, jlong expectedCapacity, jboolean aes256, jint enableKeyExpire, jint expiredInSeconds, jboolean enableCompareBeforeSet, jint recover, jint itemSizeLimit) { @@ -944,22 +993,18 @@ MMKV_JNI void setLogLevel(JNIEnv *env, jclass type, jint level) { } MMKV_JNI void setCallbackHandler(JNIEnv *env, jclass type, jboolean logReDirecting, jboolean hasCallback, jlong nativeHandler) { - if (logReDirecting == JNI_TRUE) { - if (nativeHandler != 0) { - g_androidLogHandler = (AndroidLogHandler) nativeHandler; - MMKV::registerLogHandler(androidLogWrapper); - } else { - MMKV::registerLogHandler(mmkvLog); - } + g_jniHandler.setLogRedirecting(logReDirecting == JNI_TRUE); + g_jniHandler.setHasCallback(hasCallback == JNI_TRUE); + if (logReDirecting && nativeHandler != 0) { + g_androidLogHandler = (AndroidLogHandler) nativeHandler; } else { g_androidLogHandler = nullptr; - MMKV::unRegisterLogHandler(); } - if (hasCallback == JNI_TRUE) { - MMKV::registerErrorHandler(onMMKVError); + if (hasCallback == JNI_TRUE || logReDirecting == JNI_TRUE) { + MMKV::registerHandler(&g_jniHandler); } else { - MMKV::unRegisterErrorHandler(); + MMKV::unRegisterHandler(); } } @@ -986,10 +1031,10 @@ MMKV_JNI jint writeValueToNB(JNIEnv *env, jobject instance, jlong handle, jstrin } MMKV_JNI void setWantsContentChangeNotify(JNIEnv *env, jclass type, jboolean notify) { + g_jniHandler.setWantsContentChange(notify == JNI_TRUE); + // ensure handler is registered when content change is wanted if (notify == JNI_TRUE) { - MMKV::registerContentChangeHandler(onContentChangedByOuterProcess); - } else { - MMKV::unRegisterContentChangeHandler(); + MMKV::registerHandler(&g_jniHandler); } } diff --git a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKV.java b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKV.java index 03e1906d..2e6ce14f 100644 --- a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKV.java +++ b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKV.java @@ -209,6 +209,10 @@ public class MMKV implements SharedPreferences, SharedPreferences.Editor { String ret = doInitialize(rootDir, cacheDir, loader, logLevel, gWantLogReDirecting, hasCallback, nativeLogHandler); + if (handler != null && handler.wantContentChangeNotification()) { + setWantsContentChangeNotify(true); + } + // disable process mode in release build // FIXME: Find a better way to getApplicationInfo() without using context. // If any one knows how, you're welcome to make a contribution. @@ -1836,6 +1840,9 @@ public class MMKV implements SharedPreferences, SharedPreferences.Editor { gWantLogReDirecting = gCallbackHandler.wantLogRedirecting(); long nativeLogHandler = gCallbackHandler.getNativeLogHandler(); setCallbackHandler(gWantLogReDirecting, true, nativeLogHandler); + if (gCallbackHandler.wantContentChangeNotification()) { + setWantsContentChangeNotify(true); + } } /** @@ -1846,6 +1853,7 @@ public class MMKV implements SharedPreferences, SharedPreferences.Editor { setCallbackHandler(false, false, 0); gWantLogReDirecting = false; + setWantsContentChangeNotify(gContentChangeNotify != null); } private static int onMMKVCRCCheckFail(String mmapID) { @@ -1912,26 +1920,40 @@ public class MMKV implements SharedPreferences, SharedPreferences.Editor { * For example {@link #checkContentChangedByOuterProcess()}. * * @param notify The notification handler. + * @deprecated Use {@link MMKVHandler#onContentChangedByOuterProcess(String)} instead. */ + @Deprecated public static void registerContentChangeNotify(MMKVContentChangeNotification notify) { gContentChangeNotify = notify; - setWantsContentChangeNotify(gContentChangeNotify != null); + setWantsContentChangeNotify(gContentChangeNotify != null || (gCallbackHandler != null && gCallbackHandler.wantContentChangeNotification())); } /** * Unregister for MMKV inter-process content change notification. + * @deprecated Use {@link MMKVHandler#onContentChangedByOuterProcess(String)} instead. */ + @Deprecated public static void unregisterContentChangeNotify() { gContentChangeNotify = null; - setWantsContentChangeNotify(false); + setWantsContentChangeNotify(gCallbackHandler != null && gCallbackHandler.wantContentChangeNotification()); } private static void onContentChangedByOuterProcess(String mmapID) { - if (gContentChangeNotify != null) { + MMKVHandler handler = gCallbackHandler; + if (handler != null && handler.wantContentChangeNotification()) { + handler.onContentChangedByOuterProcess(mmapID); + } else if (gContentChangeNotify != null) { gContentChangeNotify.onContentChangedByOuterProcess(mmapID); } } + private static void onMMKVContentLoadSuccessfully(String mmapID) { + MMKVHandler handler = gCallbackHandler; + if (handler != null) { + handler.onMMKVContentLoadSuccessfully(mmapID); + } + } + private static native void setWantsContentChangeNotify(boolean needsNotify); /** diff --git a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVContentChangeNotification.java b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVContentChangeNotification.java index f9d62555..af896be7 100644 --- a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVContentChangeNotification.java +++ b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVContentChangeNotification.java @@ -23,7 +23,9 @@ package com.tencent.mmkv; /** * Inter-process content change notification. * Triggered by any method call, such as getXXX() or setXXX() or {@link MMKV#checkContentChangedByOuterProcess()}. + * @deprecated Use {@link MMKVHandler#onContentChangedByOuterProcess(String)} instead. */ +@Deprecated public interface MMKVContentChangeNotification { /** * Inter-process content change notification. diff --git a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVHandler.java b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVHandler.java index 4bc98f2a..13bc5d41 100644 --- a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVHandler.java +++ b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVHandler.java @@ -21,7 +21,7 @@ package com.tencent.mmkv; /** - * Callback handler for MMKV. + * Unified callback handler for MMKV. * Callback is called on the operating thread of the MMKV instance. */ public interface MMKVHandler { @@ -59,5 +59,23 @@ public interface MMKVHandler { * @return a native log handler with signature of void log(int level, const char *file, int line, const char *function, const char *message) * return 0 to indicate no native handler */ - long getNativeLogHandler(); + default long getNativeLogHandler() { return 0; } + + /** + * @return Return true if you want inter-process content change notification. + */ + default boolean wantContentChangeNotification() { return false; } + + /** + * Inter-process content change notification. + * Triggered by any method call, such as getXXX() or setXXX() or {@link MMKV#checkContentChangedByOuterProcess()}. + * @param mmapID The unique ID of the changed MMKV instance. + */ + default void onContentChangedByOuterProcess(String mmapID) {} + + /** + * Called when an MMKV file is loaded successfully. + * @param mmapID The unique ID of the loaded MMKV instance. + */ + default void onMMKVContentLoadSuccessfully(String mmapID) {} } diff --git a/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MyApplication.java b/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MyApplication.java index 470bac45..a23a37d9 100644 --- a/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MyApplication.java +++ b/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MyApplication.java @@ -24,13 +24,12 @@ import android.app.Application; import android.util.Log; import com.getkeepsafe.relinker.ReLinker; import com.tencent.mmkv.MMKV; -import com.tencent.mmkv.MMKVContentChangeNotification; import com.tencent.mmkv.MMKVHandler; import com.tencent.mmkv.MMKVLogLevel; import com.tencent.mmkv.MMKVRecoverStrategic; import com.tencent.mmkv.NameSpace; -public class MyApplication extends Application implements MMKVHandler, MMKVContentChangeNotification { +public class MyApplication extends Application implements MMKVHandler { @Override public void onCreate() { super.onCreate(); @@ -46,7 +45,7 @@ public class MyApplication extends Application implements MMKVHandler, MMKVConte public void loadLibrary(String libName) { ReLinker.loadLibrary(MyApplication.this, libName); } - }, MMKVLogLevel.LevelInfo,this); + }, MMKVLogLevel.LevelInfo, this); Log.i("MMKV", "mmkv root: " + rootDir); // set log level @@ -58,8 +57,8 @@ public class MyApplication extends Application implements MMKVHandler, MMKVConte // register log redirecting & recover handler is moved into MMKV.initialize() // MMKV.registerHandler(this); - // content change notification - MMKV.registerContentChangeNotify(this); + // deprecated: content change notification + // MMKV.registerContentChangeNotify(this); } @Override @@ -112,6 +111,16 @@ public class MyApplication extends Application implements MMKVHandler, MMKVConte Log.i("MMKV", "other process has changed content of : " + mmapID); } + @Override + public boolean wantContentChangeNotification() { + return true; + } + + @Override + public void onMMKVContentLoadSuccessfully(String mmapID) { + Log.i("MMKV", "content load successfully : " + mmapID); + } + private void testNameSpace() { final String nsRoot = getFilesDir().getAbsolutePath() + "/namespace"; final NameSpace ns = MMKV.nameSpace(nsRoot); diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 096ffacd..9d7b9f18 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -193,6 +193,7 @@ copy_files( MMKVPredef.h MMBuffer.h MiniPBCoder.h + MMKVHandler.h ${CMAKE_CURRENT_SOURCE_DIR}/include/MMKV) #message(STATUS "copying headers to ${CMAKE_CURRENT_SOURCE_DIR}/include/MMKV") diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 68489ee3..3728ee89 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + CB0798F02F6A4B3C00B74BB8 /* MMKVHandler.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB0798EF2F6A4A6D00B74BB8 /* MMKVHandler.h */; }; + CB0798F12F6A4B4200B74BB8 /* MMKVHandler.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB0798EF2F6A4A6D00B74BB8 /* MMKVHandler.h */; }; CB0DCC45242B384E009AFE59 /* openssl_md5.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB95640223AB2E9100ACCD39 /* openssl_md5.h */; }; CB0DCC46242B3856009AFE59 /* openssl_opensslconf.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB95640023AB2E9100ACCD39 /* openssl_opensslconf.h */; }; CB0DCC6D242B5786009AFE59 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CBF3450323B4BABA00168AC7 /* libz.tbd */; }; @@ -88,6 +90,7 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + CB0798F02F6A4B3C00B74BB8 /* MMKVHandler.h in CopyFiles */, CBE668A32D5C8746005C184E /* MiniPBCoder.h in CopyFiles */, CB0DCC46242B3856009AFE59 /* openssl_opensslconf.h in CopyFiles */, CB0DCC45242B384E009AFE59 /* openssl_md5.h in CopyFiles */, @@ -107,6 +110,7 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + CB0798F12F6A4B4200B74BB8 /* MMKVHandler.h in CopyFiles */, CBE668A42D5C9467005C184E /* MiniPBCoder.h in CopyFiles */, CBF1906A243D70BA001C82ED /* openssl_opensslconf.h in CopyFiles */, CBF1906B243D70BA001C82ED /* openssl_md5.h in CopyFiles */, @@ -123,6 +127,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + CB0798EF2F6A4A6D00B74BB8 /* MMKVHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMKVHandler.h; sourceTree = ""; }; CB0DCC70242B57E5009AFE59 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/lib/libc++.tbd"; sourceTree = DEVELOPER_DIR; }; CB30A3EC24987C32007171B1 /* CodedInputDataCrypt.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = CodedInputDataCrypt.cpp; sourceTree = ""; }; CB30A3ED24987C32007171B1 /* CodedInputDataCrypt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CodedInputDataCrypt.h; sourceTree = ""; }; @@ -244,6 +249,7 @@ CB7C029924A0F65B008D77E6 /* MMKV_IO.cpp */, CB467F862431D3ED00FD7421 /* MMKV_OSX.h */, CBD723BE23B5C22800D3CDAF /* MMKV_OSX.cpp */, + CB0798EF2F6A4A6D00B74BB8 /* MMKVHandler.h */, CB95641123AB2E9100ACCD39 /* MMKVLog.cpp */, CB9563F423AB2E9100ACCD39 /* MMKVLog.h */, CB9563EC23AB2E9100ACCD39 /* MMKVMetaInfo.hpp */, diff --git a/Core/MMKV.cpp b/Core/MMKV.cpp index 11567f92..fb20d9e9 100755 --- a/Core/MMKV.cpp +++ b/Core/MMKV.cpp @@ -63,7 +63,6 @@ MMKVPath_t g_rootDir; MMKVPath_t g_realRootDir; static ThreadLock *g_namespaceLock; static unordered_map g_realRootMap; -static mmkv::ErrorHandler g_errorHandler; size_t mmkv::DEFAULT_MMAP_SIZE; MMKV_NAMESPACE_BEGIN @@ -210,9 +209,9 @@ static void ensureMinimalInitialize() { ThreadLock::ThreadOnce(&once_control, initialize); } -void MMKV::initializeMMKV(const MMKVPath_t &rootDir, MMKVLogLevel logLevel, mmkv::LogHandler handler) { +void MMKV::initializeMMKV(const MMKVPath_t &rootDir, MMKVLogLevel logLevel, mmkv::MMKVHandler *handler) { g_currentLogLevel = logLevel; - g_logHandler = handler; + g_handler = handler; ensureMinimalInitialize(); @@ -316,18 +315,15 @@ const string &MMKV::mmapID() const { return m_mmapID; } -mmkv::ContentChangeHandler g_contentChangeHandler = nullptr; -mmkv::ContentLoadedHandler g_contentLoadedHandler = nullptr; - void MMKV::notifyContentChanged() { - if (g_contentChangeHandler) { - g_contentChangeHandler(m_mmapID); + if (g_handler) { + g_handler->onContentChangedByOuterProcess(m_mmapID); } } void MMKV::notifyContentLoaded() { - if (g_contentLoadedHandler) { - g_contentLoadedHandler(m_mmapID); + if (g_handler) { + g_handler->onMMKVContentLoadSuccessfully(m_mmapID); } } @@ -336,22 +332,6 @@ void MMKV::checkContentChanged() { checkLoadData(); } -void MMKV::registerContentChangeHandler(mmkv::ContentChangeHandler handler) { - g_contentChangeHandler = handler; -} - -void MMKV::unRegisterContentChangeHandler() { - g_contentChangeHandler = nullptr; -} - -void MMKV::registerContentLoadedHandler(mmkv::ContentChangeHandler handler) { - g_contentLoadedHandler = handler; -} - -void MMKV::unRegisterContentLoadedHandler() { - g_contentLoadedHandler = nullptr; -} - void MMKV::clearMemoryCache(bool keepSpace) { SCOPED_LOCK(m_lock); if (m_needLoadFromFile) { @@ -1603,36 +1583,20 @@ size_t MMKV::restoreAllFromDirectory(const MMKVPath_t &srcDir, const MMKVPath_t // callbacks -void MMKV::registerErrorHandler(ErrorHandler handler) { +void MMKV::registerHandler(mmkv::MMKVHandler *handler) { if (!g_instanceLock) { return; } SCOPED_LOCK(g_instanceLock); - g_errorHandler = handler; + g_handler = handler; } -void MMKV::unRegisterErrorHandler() { +void MMKV::unRegisterHandler() { if (!g_instanceLock) { return; } SCOPED_LOCK(g_instanceLock); - g_errorHandler = nullptr; -} - -void MMKV::registerLogHandler(LogHandler handler) { - if (!g_instanceLock) { - return; - } - SCOPED_LOCK(g_instanceLock); - g_logHandler = handler; -} - -void MMKV::unRegisterLogHandler() { - if (!g_instanceLock) { - return; - } - SCOPED_LOCK(g_instanceLock); - g_logHandler = nullptr; + g_handler = nullptr; } void MMKV::setLogLevel(MMKVLogLevel level) { @@ -1759,15 +1723,15 @@ MMKVPath_t crcPathWithPath(const MMKVPath_t &kvPath) { } MMKVRecoverStrategic onMMKVCRCCheckFail(const string &mmapID) { - if (g_errorHandler) { - return g_errorHandler(mmapID, MMKVErrorType::MMKVCRCCheckFail); + if (g_handler) { + return g_handler->onMMKVCRCCheckFail(mmapID); } return OnErrorDiscard; } MMKVRecoverStrategic onMMKVFileLengthError(const string &mmapID) { - if (g_errorHandler) { - return g_errorHandler(mmapID, MMKVErrorType::MMKVFileLength); + if (g_handler) { + return g_handler->onMMKVFileLengthError(mmapID); } return OnErrorDiscard; } diff --git a/Core/MMKV.h b/Core/MMKV.h index dff82f69..0b2d8d2a 100755 --- a/Core/MMKV.h +++ b/Core/MMKV.h @@ -33,6 +33,7 @@ #endif #include "MiniPBCoder.h" +#include "MMKVHandler.h" #include #include @@ -298,7 +299,7 @@ class MMKV_EXPORT MMKV { public: // call this before getting any MMKV instance - static void initializeMMKV(const MMKVPath_t &rootDir, MMKVLogLevel logLevel = MMKVLogInfo, mmkv::LogHandler handler = nullptr); + static void initializeMMKV(const MMKVPath_t &rootDir, MMKVLogLevel logLevel = MMKVLogInfo, mmkv::MMKVHandler *handler = nullptr); // a generic purpose instance static MMKV *defaultMMKV(MMKVMode mode = MMKV_SINGLE_PROCESS, const std::string *cryptKey = nullptr, bool aes256 = false); @@ -654,29 +655,14 @@ public: // check if content been changed by other process void checkContentChanged(); - // called when content is changed by other process - // doesn't guarantee real-time notification - static void registerContentChangeHandler(mmkv::ContentChangeHandler handler); - static void unRegisterContentChangeHandler(); - - // called when content is loaded successfully - static void registerContentLoadedHandler(mmkv::ContentLoadedHandler handler); - static void unRegisterContentLoadedHandler(); - - // by default MMKV will discard all datas on failure - // return `OnErrorRecover` to recover any data from file - static void registerErrorHandler(mmkv::ErrorHandler handler); - static void unRegisterErrorHandler(); + // register a unified callback handler for MMKV + static void registerHandler(mmkv::MMKVHandler *handler); + static void unRegisterHandler(); // MMKVLogInfo by default // pass MMKVLogNone to disable all logging static void setLogLevel(MMKVLogLevel level); - // by default MMKV will print log to the console - // implement this method to redirect MMKV's log - static void registerLogHandler(mmkv::LogHandler handler); - static void unRegisterLogHandler(); - // detect if the MMKV file is valid or not // Note: Don't use this to check the existence of the instance, the return value is undefined if the file was never created. static bool isFileValid(const std::string &mmapID, const MMKVPath_t *relatePath = nullptr); diff --git a/Core/MMKVHandler.h b/Core/MMKVHandler.h new file mode 100644 index 00000000..2fad03ff --- /dev/null +++ b/Core/MMKVHandler.h @@ -0,0 +1,55 @@ +/* + * Tencent is pleased to support the open source community by making + * MMKV available. + * + * Copyright (C) 2026 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of + * the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MMKVCoreHandler_h +#define MMKVCoreHandler_h + +#include "MMKVPredef.h" + +namespace mmkv { + +// unified callback handler for MMKV +class MMKVHandler { +public: + virtual ~MMKVHandler() = default; + + // by default MMKV will print log using system log + // implement this method to redirect MMKV's log + virtual void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) {} + + // by default MMKV will discard all data on crc32-check failure + // return `OnErrorRecover` to recover any data on the file + virtual MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) { return OnErrorDiscard; } + + // by default MMKV will discard all data on file length mismatch + // return `OnErrorRecover` to recover any data on the file + virtual MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) { return OnErrorDiscard; } + + // called when content is changed by other process + // doesn't guarantee real-time notification + virtual void onContentChangedByOuterProcess(const std::string &mmapID) {} + + // called when an MMKV file is loaded successfully + virtual void onMMKVContentLoadSuccessfully(const std::string &mmapID) {} +}; + +} // namespace mmkv + +#endif /* MMKVHandler_h */ diff --git a/Core/MMKVLog.cpp b/Core/MMKVLog.cpp index 35d2bd95..f2400b87 100755 --- a/Core/MMKVLog.cpp +++ b/Core/MMKVLog.cpp @@ -32,8 +32,6 @@ MMKVLogLevel g_currentLogLevel = MMKVLogDebug; MMKVLogLevel g_currentLogLevel = MMKVLogInfo; #endif -mmkv::LogHandler g_logHandler = nullptr; - #ifndef __FILE_NAME__ const char *_getFileName(const char *path) { const char *ptr = strrchr(path, '/'); @@ -48,6 +46,8 @@ const char *_getFileName(const char *path) { } #endif +mmkv::MMKVHandler *g_handler = nullptr; + MMKV_NAMESPACE_END @@ -84,8 +84,8 @@ void _MMKVLogWithLevel(MMKVLogLevel level, const char *filename, const char *fun NSString *message = [[NSString alloc] initWithFormat:nsFormat arguments:argList]; va_end(argList); - if (g_logHandler) { - g_logHandler(level, filename, line, func, message); + if (g_handler) { + g_handler->mmkvLog(level, filename, line, func, message); } else { NSLog(@"[%s] <%s:%d::%s> %@", MMKVLogLevelDesc(level), filename, line, func, message); } @@ -169,8 +169,8 @@ void _MMKVLogWithLevel(MMKVLogLevel level, const char *filename, const char *fun va_end(args); } - if (g_logHandler) { - g_logHandler(level, filename, line, func, message); + if (g_handler) { + g_handler->mmkvLog(level, filename, line, func, message); } else { #if defined(MMKV_WIN32) PrintUTF8("[%s] <%s:%d::%s> %s\n", MMKVLogLevelDesc(level), filename, line, func, message.c_str()); diff --git a/Core/MMKVLog.h b/Core/MMKVLog.h index 12274622..6f398340 100755 --- a/Core/MMKVLog.h +++ b/Core/MMKVLog.h @@ -23,6 +23,7 @@ #ifdef __cplusplus #include "MMKVPredef.h" +#include "MMKVHandler.h" #include #include @@ -39,7 +40,7 @@ void _MMKVLogWithLevel( MMKV_NAMESPACE_BEGIN extern MMKVLogLevel g_currentLogLevel; -extern mmkv::LogHandler g_logHandler; +extern mmkv::MMKVHandler *g_handler; // enable logging #define ENABLE_MMKV_LOG diff --git a/Core/MMKVLog_Android.cpp b/Core/MMKVLog_Android.cpp index 5b0c7af9..24c474f0 100644 --- a/Core/MMKVLog_Android.cpp +++ b/Core/MMKVLog_Android.cpp @@ -66,8 +66,8 @@ void _MMKVLogWithLevel(MMKVLogLevel level, const char *filename, const char *fun va_end(args); } - if (g_logHandler) { - g_logHandler(level, filename, line, func, message); + if (g_handler) { + g_handler->mmkvLog(level, filename, line, func, message); } else { auto desc = MMKVLogLevelDesc(level); OH_LOG_Print(LOG_APP, desc, 0, APP_NAME, "<%{public}s:%{public}d::%{public}s> %{public}s", @@ -121,8 +121,8 @@ void _MMKVLogWithLevel(MMKVLogLevel level, const char *filename, const char *fun va_end(args); } - if (g_logHandler) { - g_logHandler(level, filename, line, func, message); + if (g_handler) { + g_handler->mmkvLog(level, filename, line, func, message); } else { auto desc = MMKVLogLevelDesc(level); __android_log_print(desc, APP_NAME, "<%s:%d::%s> %s", filename, line, func, message.c_str()); diff --git a/Core/MMKVPredef.h b/Core/MMKVPredef.h index 88bee249..1ba1f116 100755 --- a/Core/MMKVPredef.h +++ b/Core/MMKVPredef.h @@ -169,20 +169,6 @@ MMKV_NAMESPACE_END namespace mmkv { -typedef void (*LogHandler)(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message); - -// by default MMKV will discard all datas on failure -// return `OnErrorRecover` to recover any data from file -typedef MMKVRecoverStrategic (*ErrorHandler)(const std::string &mmapID, MMKVErrorType errorType); - -// called when content is changed by other process -// doesn't guarantee real-time notification -typedef void (*ContentChangeHandler)(const std::string &mmapID); - -// called when content is loaded successfully -typedef void (*ContentLoadedHandler)(const std::string &mmapID); - - extern MMKV_EXPORT size_t DEFAULT_MMAP_SIZE; #define DEFAULT_MMAP_ID "mmkv.default" diff --git a/OpenHarmony/MMKV/src/main/cpp/flutter-bridge.cpp b/OpenHarmony/MMKV/src/main/cpp/flutter-bridge.cpp index bc2cc99d..b78b0ba6 100644 --- a/OpenHarmony/MMKV/src/main/cpp/flutter-bridge.cpp +++ b/OpenHarmony/MMKV/src/main/cpp/flutter-bridge.cpp @@ -38,13 +38,50 @@ extern string g_android_tmpDir; # define MMKV_EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used)) using LogCallback_t = void (*)(uint32_t level, const char *file, int32_t line, const char *funcname, const char *message); -LogCallback_t g_logCallback = nullptr; +using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); +using ContenctChangeCallback_t = void (*)(const char *mmapID); -static void myLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) { - if (g_logCallback) { - g_logCallback(level, file, line, function, message.c_str()); +class FlutterMMKVHandler : public mmkv::MMKVHandler { +public: + LogCallback_t logCallback = nullptr; + ErrorCallback_t errorCallback = nullptr; + ContenctChangeCallback_t contentChangeCallback = nullptr; + ContenctChangeCallback_t contentLoadedCallback = nullptr; + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) override { + if (logCallback) { + logCallback(level, file, line, function, message.c_str()); + } } -} + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVCRCCheckFail); + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVFileLength); + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (contentChangeCallback) { + contentChangeCallback(mmapID.c_str()); + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (contentLoadedCallback) { + contentLoadedCallback(mmapID.c_str()); + } + } +}; + +static FlutterMMKVHandler g_flutterHandler; MMKV_EXPORT void *mmkvInitialize_v2(const char *rootDir, const char *cacheDir, int32_t sdkInt, int32_t logLevel, LogCallback_t callback) { if (!rootDir) { @@ -62,8 +99,8 @@ MMKV_EXPORT void *mmkvInitialize_v2(const char *rootDir, const char *cacheDir, i #endif if (callback) { - g_logCallback = callback; - MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, myLogHandler); + g_flutterHandler.logCallback = callback; + MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, &g_flutterHandler); } else { MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel); } @@ -660,40 +697,24 @@ MMKV_EXPORT bool isReadOnly(void *handle) { return false; } -using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); -static ErrorCallback_t g_errorCallback = nullptr; - -static MMKVRecoverStrategic myErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - if (g_errorCallback) { - return (MMKVRecoverStrategic) g_errorCallback(mmapID.c_str(), errorType); - } - return OnErrorDiscard; -} - MMKV_EXPORT void registerErrorHandler(ErrorCallback_t callback) { - g_errorCallback = callback; - if (callback) { - MMKV::registerErrorHandler(myErrorHandler); - } else { - MMKV::unRegisterErrorHandler(); - } -} - -using ContenctChangeCallback_t = void (*)(const char *mmapID); -static ContenctChangeCallback_t g_contentChanceCallback = nullptr; - -static void myContentChangeHandler(const std::string &mmapID) { - if (g_contentChanceCallback) { - g_contentChanceCallback(mmapID.c_str()); + g_flutterHandler.errorCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.contentChangeCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); } } MMKV_EXPORT void registerContentChangeNotify(ContenctChangeCallback_t callback) { - g_contentChanceCallback = callback; - if (callback) { - MMKV::registerContentChangeHandler(myContentChangeHandler); - } else { - MMKV::unRegisterContentChangeHandler(); + g_flutterHandler.contentChangeCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); + } +} + +MMKV_EXPORT void registerContentLoadedNotify(ContenctChangeCallback_t callback) { + g_flutterHandler.contentLoadedCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentChangeCallback) { + MMKV::registerHandler(&g_flutterHandler); } } diff --git a/OpenHarmony/MMKV/src/main/cpp/native_bridge.cpp b/OpenHarmony/MMKV/src/main/cpp/native_bridge.cpp index c2cfaac2..67432ca3 100644 --- a/OpenHarmony/MMKV/src/main/cpp/native_bridge.cpp +++ b/OpenHarmony/MMKV/src/main/cpp/native_bridge.cpp @@ -354,13 +354,14 @@ static uint64_t NValueToUInt64(napi_env env, napi_value value, bool maybeUndefin struct CallbackInfo { napi_ref handlerRef = nullptr; - napi_ref callbacks[6] = {}; + napi_ref callbacks[7] = {}; napi_ref &wantLogRedirect = callbacks[0]; napi_ref &mmkvLog = callbacks[1]; napi_ref &onMMKVCRCCheckFail = callbacks[2]; napi_ref &onMMKVFileLengthError = callbacks[3]; napi_ref &wantContentChangeNotification = callbacks[4]; napi_ref &onContentChangedByOuterProcess = callbacks[5]; + napi_ref &onMMKVContentLoadSuccessfully = callbacks[6]; }; static const char *g_arrCallbackNames[] = { @@ -369,7 +370,8 @@ static const char *g_arrCallbackNames[] = { "onMMKVCRCCheckFail", "onMMKVFileLengthError", "wantContentChangeNotification", - "onContentChangedByOuterProcess" + "onContentChangedByOuterProcess", + "onMMKVContentLoadSuccessfully" }; static CallbackInfo g_callbackInfo; @@ -431,70 +433,102 @@ static std::tuple initCallbacks(napi_env env, napi_value callb static napi_env g_env = nullptr; -void myLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) { - if (!g_env) { - return; - } - - napi_value handler; - napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); - napi_value callback; - napi_get_reference_value(g_env, g_callbackInfo.mmkvLog, &callback); - - napi_value args[] = { - Int32ToNValue(g_env, level), - StringToNValue(g_env, file), - Int32ToNValue(g_env, line), - StringToNValue(g_env, function), - StringToNValue(g_env, message) - }; - - napi_value result; - napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result); -} - -static MMKVRecoverStrategic myErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - if (!g_env) { - return OnErrorDiscard; - } - - napi_value handler; - napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); - napi_value callback; - if (errorType == MMKVCRCCheckFail) { - napi_get_reference_value(g_env, g_callbackInfo.onMMKVCRCCheckFail, &callback); - } else if (errorType == MMKVFileLength) { - napi_get_reference_value(g_env, g_callbackInfo.onMMKVFileLengthError, &callback); - } else { - return OnErrorDiscard; - } - - napi_value args[] = { StringToNValue(g_env, mmapID) }; - - napi_value result; - if (napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result) == napi_ok) { - if (!IsNValueUndefined(g_env, result)) { - return ( MMKVRecoverStrategic ) NValueToInt32(g_env, result); +// C++ adapter class that bridges mmkv::MMKVHandler to NAPI +class NAPIMMKVHandler : public mmkv::MMKVHandler { +public: + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) override { + if (!g_env || !g_callbackInfo.mmkvLog) { + return; } - } - return OnErrorDiscard; -} + napi_value handler; + napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); + napi_value callback; + napi_get_reference_value(g_env, g_callbackInfo.mmkvLog, &callback); -static void myContentNotificationHandler(const std::string &mmapID) { - if (!g_env) { - return; + napi_value args[] = { + Int32ToNValue(g_env, level), + StringToNValue(g_env, file), + Int32ToNValue(g_env, line), + StringToNValue(g_env, function), + StringToNValue(g_env, message) + }; + + napi_value result; + napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result); } - napi_value handler; - napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); - napi_value callback; - napi_get_reference_value(g_env, g_callbackInfo.onContentChangedByOuterProcess, &callback); + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (!g_env || !g_callbackInfo.onMMKVCRCCheckFail) { + return OnErrorDiscard; + } + napi_value handler; + napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); + napi_value callback; + napi_get_reference_value(g_env, g_callbackInfo.onMMKVCRCCheckFail, &callback); - napi_value args[] = { StringToNValue(g_env, mmapID) }; + napi_value args[] = { StringToNValue(g_env, mmapID) }; - napi_value result; - napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result); -} + napi_value result; + if (napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result) == napi_ok) { + if (!IsNValueUndefined(g_env, result)) { + return ( MMKVRecoverStrategic ) NValueToInt32(g_env, result); + } + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (!g_env || !g_callbackInfo.onMMKVFileLengthError) { + return OnErrorDiscard; + } + napi_value handler; + napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); + napi_value callback; + napi_get_reference_value(g_env, g_callbackInfo.onMMKVFileLengthError, &callback); + + napi_value args[] = { StringToNValue(g_env, mmapID) }; + + napi_value result; + if (napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result) == napi_ok) { + if (!IsNValueUndefined(g_env, result)) { + return ( MMKVRecoverStrategic ) NValueToInt32(g_env, result); + } + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (!g_env || !g_callbackInfo.onContentChangedByOuterProcess) { + return; + } + napi_value handler; + napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); + napi_value callback; + napi_get_reference_value(g_env, g_callbackInfo.onContentChangedByOuterProcess, &callback); + + napi_value args[] = { StringToNValue(g_env, mmapID) }; + + napi_value result; + napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result); + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (!g_env || !g_callbackInfo.onMMKVContentLoadSuccessfully) { + return; + } + napi_value handler; + napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); + napi_value callback; + napi_get_reference_value(g_env, g_callbackInfo.onMMKVContentLoadSuccessfully, &callback); + + napi_value args[] = { StringToNValue(g_env, mmapID) }; + + napi_value result; + napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result); + } +}; + +static NAPIMMKVHandler g_napiHandler; static napi_value initialize(napi_env env, napi_callback_info info) { g_env = env; @@ -510,19 +544,16 @@ static napi_value initialize(napi_env env, napi_callback_info info) { NAPI_CALL(napi_get_value_int32(env, args[2], &logLevel)); auto [hasCallback, wantLogRedirect, wantContentChangeNotification] = initCallbacks(env, args[3]); - auto logHandler = wantLogRedirect ? myLogHandler : nullptr; + mmkv::MMKVHandler *handler = (hasCallback || wantLogRedirect) ? &g_napiHandler : nullptr; MMKVInfo("rootDir: %s, cacheDir: %s, log level:%d, has callback: %d, want log redirect: %d", rootDir.c_str(), cacheDir.c_str(), logLevel, hasCallback, wantLogRedirect); - MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, logHandler); + MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, handler); g_android_tmpDir = cacheDir; - if (hasCallback) { - MMKV::registerErrorHandler(myErrorHandler); - } - if (wantContentChangeNotification) { - MMKV::registerContentChangeHandler(myContentNotificationHandler); + if (hasCallback || wantLogRedirect || wantContentChangeNotification) { + MMKV::registerHandler(&g_napiHandler); } return StringToNValue(env, MMKV::getRootDir()); diff --git a/OpenHarmony/MMKV/src/main/ets/utils/MMKVHandler.ets b/OpenHarmony/MMKV/src/main/ets/utils/MMKVHandler.ets index adabf771..004332af 100644 --- a/OpenHarmony/MMKV/src/main/ets/utils/MMKVHandler.ets +++ b/OpenHarmony/MMKV/src/main/ets/utils/MMKVHandler.ets @@ -108,4 +108,12 @@ export abstract class MMKVHandler { public onContentChangedByOuterProcess(mmapID: string): void { return; } + + /** + * Called when an MMKV file is loaded successfully. + * @param mmapID The unique ID of the loaded MMKV instance. + */ + public onMMKVContentLoadSuccessfully(mmapID: string): void { + return; + } } diff --git a/POSIX/golang/callback.go b/POSIX/golang/callback.go index 1c63c447..6cc47eea 100644 --- a/POSIX/golang/callback.go +++ b/POSIX/golang/callback.go @@ -27,34 +27,6 @@ package mmkv */ import "C" -// the callback type of Logger -type LogHandler func(level int, file string, line int, function string, message string) - -var gLogHandler LogHandler - -// register a log callback -// -// Deprecated: use mmkv.InitializeMMKVWithLogLevelAndHandler(rootDir, logLevel, logHandler) instead. -func RegisterLogHandler(logHandler LogHandler) { - gLogHandler = logHandler - - C.setWantsLogRedirect(C.bool(true)) -} - -// unregister a log callback -func UnRegisterLogHandler() { - gLogHandler = nil - - C.setWantsLogRedirect(C.bool(false)) -} - -//export myLogHandler -func myLogHandler(level int, file string, line int, function string, message string) { - if gLogHandler != nil { - gLogHandler(level, file, line, function, message) - } -} - const ( OnErrorDiscard = iota // When there's an error, MMKV will discard everything by default. OnErrorRecover // When there's an error, MMKV will try to recover as much data as possible. @@ -65,57 +37,91 @@ const ( MMKVFileLength ) -// the callback type of error handler -// error is either MMKVCRCCheckFail or MMKVFileLength -// return OnErrorDiscard (default) or OnErrorRecover -type ErrorHandler func(mmapID string, error int) int +// Handler is the unified callback interface for MMKV. +type Handler interface { + // WantLogRedirect returns true to enable log redirecting. + WantLogRedirect() bool -var gErrorHandler ErrorHandler + // MMKVLog is called for each log message when log redirecting is enabled. + MMKVLog(level int, file string, line int, function string, message string) -// register a error callback -func RegisterErrorHandler(errorHandler ErrorHandler) { - gErrorHandler = errorHandler + // OnMMKVCRCCheckFail is called when a CRC check fails. + // Return OnErrorDiscard (default) or OnErrorRecover. + OnMMKVCRCCheckFail(mmapID string) int - C.setWantsErrorHandle(C.bool(true)) + // OnMMKVFileLengthError is called when a file length mismatch is detected. + // Return OnErrorDiscard (default) or OnErrorRecover. + OnMMKVFileLengthError(mmapID string) int + + // WantContentChangeNotification returns true to enable inter-process content change notifications. + WantContentChangeNotification() bool + + // OnContentChangedByOuterProcess is called when content is changed by another process. + OnContentChangedByOuterProcess(mmapID string) + + // OnMMKVContentLoadSuccessfully is called when an MMKV file is loaded successfully. + OnMMKVContentLoadSuccessfully(mmapID string) } -// unregister a error callback -func UnRegisterErrorHandler() { - gErrorHandler = nil +// DefaultHandler provides default implementations for all Handler methods. +// Embed this in your handler struct to only override the methods you need. +type DefaultHandler struct{} - C.setWantsErrorHandle(C.bool(false)) +func (d DefaultHandler) WantLogRedirect() bool { return false } +func (d DefaultHandler) MMKVLog(level int, file string, line int, function string, message string) {} +func (d DefaultHandler) OnMMKVCRCCheckFail(mmapID string) int { return OnErrorDiscard } +func (d DefaultHandler) OnMMKVFileLengthError(mmapID string) int { return OnErrorDiscard } +func (d DefaultHandler) WantContentChangeNotification() bool { return false } +func (d DefaultHandler) OnContentChangedByOuterProcess(mmapID string) {} +func (d DefaultHandler) OnMMKVContentLoadSuccessfully(mmapID string) {} + +var gHandler Handler + +// RegisterHandler registers a unified callback handler for MMKV. +func RegisterHandler(handler Handler) { + gHandler = handler + + wantLog := handler.WantLogRedirect() + wantContent := handler.WantContentChangeNotification() + C.setWantsHandler(C.bool(true), C.bool(wantLog), C.bool(wantContent)) +} + +// UnRegisterHandler unregisters the callback handler for MMKV. +func UnRegisterHandler() { + gHandler = nil + + C.setWantsHandler(C.bool(false), C.bool(false), C.bool(false)) +} + +//export myLogHandler +func myLogHandler(level int, file string, line int, function string, message string) { + if gHandler != nil { + gHandler.MMKVLog(level, file, line, function, message) + } } //export myErrorHandler func myErrorHandler(mmapID string, error int) int { - if gErrorHandler != nil { - return gErrorHandler(mmapID, error) + if gHandler != nil { + if error == MMKVCRCCheckFail { + return gHandler.OnMMKVCRCCheckFail(mmapID) + } else if error == MMKVFileLength { + return gHandler.OnMMKVFileLengthError(mmapID) + } } return OnErrorDiscard } -// the type of content change handler -type ContentChangeHandler func(mmapID string) - -var gContentChangeHandler ContentChangeHandler - -// register a content change callback -func RegisterContentChangeHandler(contentChangeHandler ContentChangeHandler) { - gContentChangeHandler = contentChangeHandler - - C.setWantsContentChangeHandle(C.bool(true)) -} - -// unregister a content change callback -func UnRegisterContentChangeHandler() { - gContentChangeHandler = nil - - C.setWantsContentChangeHandle(C.bool(false)) -} - //export myContentChangeHandler func myContentChangeHandler(mmapID string) { - if gContentChangeHandler != nil { - gContentChangeHandler(mmapID) + if gHandler != nil { + gHandler.OnContentChangedByOuterProcess(mmapID) + } +} + +//export myContentLoadedHandler +func myContentLoadedHandler(mmapID string) { + if gHandler != nil { + gHandler.OnMMKVContentLoadSuccessfully(mmapID) } } diff --git a/POSIX/golang/golang-bridge.cpp b/POSIX/golang/golang-bridge.cpp index 310c4af4..fe4a7880 100644 --- a/POSIX/golang/golang-bridge.cpp +++ b/POSIX/golang/golang-bridge.cpp @@ -34,13 +34,58 @@ using namespace std; # endif # define MMKV_EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used)) -void cLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message); +extern "C" void myLogHandler(int64_t level, GoStringWrap file, int64_t line, GoStringWrap function, GoStringWrap message); +extern "C" int64_t myErrorHandler(GoStringWrap mmapID, int64_t error); +extern "C" void myContentChangeHandler(GoStringWrap mmapID); +extern "C" void myContentLoadedHandler(GoStringWrap mmapID); + +class GoMMKVHandler : public mmkv::MMKVHandler { +public: + bool m_wantLog = false; + bool m_wantContentChange = false; + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) override { + if (!m_wantLog) { + return; + } + GoStringWrap oFile { file, static_cast(strlen(file)) }; + GoStringWrap oFunction { function, static_cast(strlen(function)) }; + GoStringWrap oMessage { message.data(), static_cast(message.length()) }; + myLogHandler(level, oFile, line, oFunction, oMessage); + } + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; + return static_cast(myErrorHandler(oID, static_cast(MMKVCRCCheckFail))); + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; + return static_cast(myErrorHandler(oID, static_cast(MMKVFileLength))); + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (!m_wantContentChange) { + return; + } + GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; + myContentChangeHandler(oID); + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; + myContentLoadedHandler(oID); + } +}; + +static GoMMKVHandler g_goHandler; MMKV_EXPORT void mmkvInitialize(GoStringWrap rootDir, int32_t logLevel, bool redirect) { if (!rootDir.ptr) { return; } - mmkv::LogHandler handler = redirect ? cLogHandler : nullptr; + g_goHandler.m_wantLog = redirect; + mmkv::MMKVHandler *handler = redirect ? &g_goHandler : nullptr; MMKV::initializeMMKV(string(rootDir.ptr, rootDir.length), (MMKVLogLevel) logLevel, handler); } @@ -654,53 +699,15 @@ MMKV_EXPORT bool disableCompareBeforeSet(void *handle) { return false; } -extern "C" void myLogHandler(int64_t level, GoStringWrap file, int64_t line, GoStringWrap function, GoStringWrap message); - -void cLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) { - GoStringWrap oFile { file, static_cast(strlen(file)) }; - GoStringWrap oFunction { function, static_cast(strlen(function)) }; - GoStringWrap oMessage { message.data(), static_cast(message.length()) }; - - myLogHandler(level, oFile, line, oFunction, oMessage); -} - -void setWantsLogRedirect(bool redirect) { - if (redirect) { - MMKV::registerLogHandler(&cLogHandler); +void setWantsHandler(bool hasHandler, bool wantLog, bool wantContent) { + if (hasHandler) { + g_goHandler.m_wantLog = wantLog; + g_goHandler.m_wantContentChange = wantContent; + MMKV::registerHandler(&g_goHandler); } else { - MMKV::unRegisterLogHandler(); - } -} - -extern "C" int64_t myErrorHandler(GoStringWrap mmapID, int64_t error); - -static MMKVRecoverStrategic cErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; - - return static_cast(myErrorHandler(oID, static_cast(errorType))); -} - -void setWantsErrorHandle(bool errorHandle) { - if (errorHandle) { - MMKV::registerErrorHandler(&cErrorHandler); - } else { - MMKV::unRegisterErrorHandler(); - } -} - -extern "C" void myContentChangeHandler(GoStringWrap mmapID); - -static void cContentChangeHandler(const std::string &mmapID) { - GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; - - myContentChangeHandler(oID); -} - -void setWantsContentChangeHandle(bool errorHandle) { - if (errorHandle) { - MMKV::registerContentChangeHandler(&cContentChangeHandler); - } else { - MMKV::unRegisterContentChangeHandler(); + g_goHandler.m_wantLog = false; + g_goHandler.m_wantContentChange = false; + MMKV::unRegisterHandler(); } } diff --git a/POSIX/golang/golang-bridge.h b/POSIX/golang/golang-bridge.h index d3fe1d7f..d08dd910 100644 --- a/POSIX/golang/golang-bridge.h +++ b/POSIX/golang/golang-bridge.h @@ -132,9 +132,7 @@ int32_t pageSize(); const char *version(); const char *getRootDir(); -void setWantsLogRedirect(bool redirect); -void setWantsErrorHandle(bool errorHandle); -void setWantsContentChangeHandle(bool contentChange); +void setWantsHandler(bool hasHandler, bool wantLog, bool wantContent); bool removeStorage(GoStringWrap_t mmapID, GoStringWrap_t rootPath); diff --git a/POSIX/golang/mmkv.go b/POSIX/golang/mmkv.go index 8204daab..45d6dab1 100644 --- a/POSIX/golang/mmkv.go +++ b/POSIX/golang/mmkv.go @@ -338,10 +338,23 @@ func InitializeMMKVWithLogLevel(rootDir string, logLevel int) { C.mmkvInitialize(C.wrapGoString(rootDir), C.int32_t(logLevel), C.bool(false)) } -// InitializeMMKVWithLogLevelAndHandler Same as the function InitializeMMKVWithLogLevel() above, except that you can provide a logHandler at the very beginning. -func InitializeMMKVWithLogLevelAndHandler(rootDir string, logLevel int, logHandler LogHandler) { - gLogHandler = logHandler - C.mmkvInitialize(C.wrapGoString(rootDir), C.int32_t(logLevel), C.bool(true)) +// Deprecated: Use InitializeMMKVWithLogLevel() + RegisterHandler() instead. +func InitializeMMKVWithLogLevelAndHandler(rootDir string, logLevel int, logHandler func(level int, file string, line int, function string, message string)) { + C.mmkvInitialize(C.wrapGoString(rootDir), C.int32_t(logLevel), C.bool(false)) + if logHandler != nil { + RegisterHandler(&logHandlerAdapter{logHandler: logHandler}) + } +} + +// logHandlerAdapter wraps a legacy log handler function into the Handler interface. +type logHandlerAdapter struct { + DefaultHandler + logHandler func(level int, file string, line int, function string, message string) +} + +func (a *logHandlerAdapter) WantLogRedirect() bool { return true } +func (a *logHandlerAdapter) MMKVLog(level int, file string, line int, function string, message string) { + a.logHandler(level, file, line, function, message) } // OnExit Call before App exists, it's just fine not calling it on most case (except when the device shutdown suddenly). diff --git a/POSIX/golang/test/main.go b/POSIX/golang/test/main.go index c5336328..c954a559 100644 --- a/POSIX/golang/test/main.go +++ b/POSIX/golang/test/main.go @@ -14,8 +14,8 @@ func main() { // test NameSpace before mmkv.Initialize() testNameSpace() - // init MMKV with root dir and log redirecting - mmkv.InitializeMMKVWithLogLevelAndHandler("/tmp/mmkv", mmkv.MMKVLogInfo, logHandler) + // init MMKV with root dir + mmkv.InitializeMMKVWithLogLevel("/tmp/mmkv", mmkv.MMKVLogInfo) { fmt.Println("rootDir:", mmkv.GetRootDir()) @@ -23,13 +23,8 @@ func main() { fmt.Println("DefaultNameSpace:", ns.GetRootDir()) } - // you can set log redirecting - // mmkv.RegisterLogHandler(logHandler) - - // you can set error handler - mmkv.RegisterErrorHandler(errorHandler) - // you can get notify content change by other process (not in realtime) - mmkv.RegisterContentChangeHandler(contentChangeNotify) + // register unified callback handler (log redirect, error handler, content change) + mmkv.RegisterHandler(&myHandler{}) testExpectedCapacity() functionalTest() @@ -425,7 +420,14 @@ func testImport() { } } -func logHandler(level int, file string, line int, function string, message string) { +// myHandler implements mmkv.Handler with DefaultHandler for defaults +type myHandler struct { + mmkv.DefaultHandler +} + +func (h *myHandler) WantLogRedirect() bool { return true } + +func (h *myHandler) MMKVLog(level int, file string, line int, function string, message string) { var levelStr string switch level { case mmkv.MMKVLogDebug: @@ -442,18 +444,22 @@ func logHandler(level int, file string, line int, function string, message strin fmt.Printf("Redirect %v <%v:%v::%v> %v\n", levelStr, file, line, function, message) } -func errorHandler(mmapID string, error int) int { - var errorDesc string - if error == mmkv.MMKVCRCCheckFail { - errorDesc = "CRC-Error" - } else { - errorDesc = "File-Length-Error" - } - fmt.Println(mmapID, "has error type:", errorDesc) - +func (h *myHandler) OnMMKVCRCCheckFail(mmapID string) int { + fmt.Println(mmapID, "has error type: CRC-Error") return mmkv.OnErrorRecover } -func contentChangeNotify(mmapID string) { +func (h *myHandler) OnMMKVFileLengthError(mmapID string) int { + fmt.Println(mmapID, "has error type: File-Length-Error") + return mmkv.OnErrorRecover +} + +func (h *myHandler) WantContentChangeNotification() bool { return true } + +func (h *myHandler) OnContentChangedByOuterProcess(mmapID string) { fmt.Println(mmapID, "content changed by other process") } + +func (h *myHandler) OnMMKVContentLoadSuccessfully(mmapID string) { + fmt.Println(mmapID, "content loaded successfully") +} diff --git a/Python/libmmkv_python.cpp b/Python/libmmkv_python.cpp index b74aab01..9e4e56c7 100644 --- a/Python/libmmkv_python.cpp +++ b/Python/libmmkv_python.cpp @@ -61,26 +61,47 @@ static MMBuffer pyBytes2MMBuffer(const py::bytes &bytes) { static function g_logHandler = nullptr; -static void MyLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, const string &message) { - if (g_logHandler) { - g_logHandler(level, file, line, function, message); - } -} - static function g_errorHandler = nullptr; -static MMKVRecoverStrategic MyErrorHandler(const string &mmapID, MMKVErrorType errorType) { - if (g_errorHandler) { - return g_errorHandler(mmapID, errorType); - } - return OnErrorDiscard; -} - static function g_contentHandler = nullptr; -static void MyContentChangeHandler(const std::string &mmapID) { - if (g_contentHandler) { - g_contentHandler(mmapID); +static function g_contentLoadedHandler = nullptr; + +// C++ handler that bridges to Python callbacks +class PyMMKVHandler : public mmkv::MMKVHandler { +public: + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) override { + if (g_logHandler) { + g_logHandler(level, file, line, function, message); + } } -} + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (g_errorHandler) { + return g_errorHandler(mmapID, MMKVErrorType::MMKVCRCCheckFail); + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (g_errorHandler) { + return g_errorHandler(mmapID, MMKVErrorType::MMKVFileLength); + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (g_contentHandler) { + g_contentHandler(mmapID); + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (g_contentLoadedHandler) { + g_contentLoadedHandler(mmapID); + } + } +}; + +static PyMMKVHandler g_pyHandler; PYBIND11_MODULE(mmkv, m) { m.doc() = "An efficient, small key-value storage framework developed by WeChat Team."; @@ -207,7 +228,7 @@ PYBIND11_MODULE(mmkv, m) { [](const MMKVPath_t &rootDir, MMKVLogLevel logLevel, decltype(g_logHandler) logHandler) { if (logHandler) { g_logHandler = std::move(logHandler); - MMKV::initializeMMKV(rootDir, logLevel, MyLogHandler); + MMKV::initializeMMKV(rootDir, logLevel, &g_pyHandler); } else { MMKV::initializeMMKV(rootDir, logLevel, nullptr); } @@ -380,69 +401,41 @@ PYBIND11_MODULE(mmkv, m) { clsMMKV.def_static("rootDir", &MMKV::getRootDir, "get the root directory of MMKV"); - // log callback handler - clsMMKV.def_static( - "registerLogHandler", - [](decltype(g_logHandler) callback) { - g_logHandler = std::move(callback); - MMKV::registerLogHandler(MyLogHandler); - }, - "call this method to redirect MMKV's log,\n" - "must call MMKV.unRegisterLogHandler() or MMKV.onExit() before exit\n" - "Parameters:\n" - " log_handler: (logLevel: mmkv.MMKVLogLevel, file: str, line: int, function: str, message: str) -> None", - py::arg("log_handler")); - clsMMKV.def_static( - "unRegisterLogHandler", - [] { - g_logHandler = nullptr; - MMKV::unRegisterLogHandler(); - }, - "If you have registered a log handler, you must call this method or MMKV.onExit() before exit. " - "Otherwise your app/script won't exit properly."); - - // error callback handler - clsMMKV.def_static( - "registerErrorHandler", - [](decltype(g_errorHandler) callback) { - g_errorHandler = std::move(callback); - MMKV::registerErrorHandler(MyErrorHandler); - }, - "call this method to handle MMKV failure,\n" - "must call MMKV.unRegisterErrorHandler() or MMKV.onExit() before exit\n" - "Parameters:\n" - " error_handler: (mmapID: str, errorType: mmkv.MMKVErrorType) -> mmkv.MMKVRecoverStrategic", - py::arg("error_handler")); - clsMMKV.def_static( - "unRegisterErrorHandler", - [] { - g_errorHandler = nullptr; - MMKV::unRegisterErrorHandler(); - }, - "If you have registered an error handler, you must call this method or MMKV.onExit() before exit. " - "Otherwise your app/script won't exit properly."); - - // content change callback handler + // unified callback handler clsMMKV.def("checkContentChanged", &MMKV::checkContentChanged, "check if content been changed by other process"); clsMMKV.def_static( - "registerContentChangeHandler", - [](decltype(g_contentHandler) callback) { - g_contentHandler = std::move(callback); - MMKV::registerContentChangeHandler(MyContentChangeHandler); + "registerHandler", + [](decltype(g_logHandler) logHandler, + decltype(g_errorHandler) errorHandler, + decltype(g_contentHandler) contentChangeHandler, + decltype(g_contentLoadedHandler) contentLoadedHandler) { + g_logHandler = logHandler ? std::move(logHandler) : nullptr; + g_errorHandler = errorHandler ? std::move(errorHandler) : nullptr; + g_contentHandler = contentChangeHandler ? std::move(contentChangeHandler) : nullptr; + g_contentLoadedHandler = contentLoadedHandler ? std::move(contentLoadedHandler) : nullptr; + MMKV::registerHandler(&g_pyHandler); }, - "register a content change handler,\n" - "get notified when an MMKV instance has been changed by other process (not guarantee real-time notification),\n" - "must call MMKV.unRegisterContentChangeHandler() or MMKV.onExit() before exit\n" + "register a unified callback handler for MMKV,\n" + "must call MMKV.unRegisterHandler() or MMKV.onExit() before exit\n" "Parameters:\n" - " content_change_handler: (mmapID: str) -> None", - py::arg("content_change_handler")); + " log_handler: (logLevel: mmkv.MMKVLogLevel, file: str, line: int, function: str, message: str) -> None\n" + " error_handler: (mmapID: str, errorType: mmkv.MMKVErrorType) -> mmkv.MMKVRecoverStrategic\n" + " content_change_handler: (mmapID: str) -> None\n" + " content_loaded_handler: (mmapID: str) -> None", + py::arg("log_handler") = nullptr, + py::arg("error_handler") = nullptr, + py::arg("content_change_handler") = nullptr, + py::arg("content_loaded_handler") = nullptr); clsMMKV.def_static( - "unRegisterContentChangeHandler", + "unRegisterHandler", [] { + g_logHandler = nullptr; + g_errorHandler = nullptr; g_contentHandler = nullptr; - MMKV::unRegisterContentChangeHandler(); + g_contentLoadedHandler = nullptr; + MMKV::unRegisterHandler(); }, - "If you have registered a content change handler, you must call this method or MMKV.onExit() before exit. " + "If you have registered a handler, you must call this method or MMKV.onExit() before exit. " "Otherwise your app/script won't exit properly."); clsMMKV.def_static( @@ -452,6 +445,7 @@ PYBIND11_MODULE(mmkv, m) { g_logHandler = nullptr; g_errorHandler = nullptr; g_contentHandler = nullptr; + g_contentLoadedHandler = nullptr; }, "call this method before exit, especially if you have registered any callback handlers"); diff --git a/flutter/mmkv/lib/mmkv.dart b/flutter/mmkv/lib/mmkv.dart index 8a171240..8f62a0d1 100644 --- a/flutter/mmkv/lib/mmkv.dart +++ b/flutter/mmkv/lib/mmkv.dart @@ -235,6 +235,9 @@ class MMKV { final contentHandler = Pointer.fromFunction(_contentChangeHandler); _registerContentHandler(contentHandler); } + + final contentLoadedHandler = Pointer.fromFunction(_contentLoadedHandler); + _registerContentLoadedHandler(contentLoadedHandler); } return result; } @@ -919,6 +922,13 @@ void _contentChangeHandler(Pointer mmapIDPtr) { } } +void _contentLoadedHandler(Pointer mmapIDPtr) { + if (_mmkvPlatform.theHandler != null && mmapIDPtr != nullptr) { + final mmapID = _pointer2String(mmapIDPtr); + _mmkvPlatform.theHandler!.onMMKVContentLoadSuccessfully(mmapID!); + } +} + int _bool2Int(bool value) { return value ? 1 : 0; } @@ -1053,6 +1063,8 @@ final ErrorCallbackRegister _registerErrorHandler = _mmkvPlatform.registerErrorH final ContentCallbackRegister _registerContentHandler = _mmkvPlatform.registerContentHandlerFunc(); +final ContentCallbackRegister _registerContentLoadedHandler = _mmkvPlatform.registerContentLoadedHandlerFunc(); + final void Function(Pointer) _checkContentChanged = _mmkvPlatform.checkContentChangedFunc(); final bool Function(Pointer) _getNameSpace = _mmkvPlatform.getNameSpaceFunc(); diff --git a/flutter/mmkv_ios/darwin/Classes/flutter-bridge.h b/flutter/mmkv_ios/darwin/Classes/flutter-bridge.h index 0fa79133..3cda2464 100644 --- a/flutter/mmkv_ios/darwin/Classes/flutter-bridge.h +++ b/flutter/mmkv_ios/darwin/Classes/flutter-bridge.h @@ -24,6 +24,7 @@ using LogCallback_t = void (*)(uint32_t level, const char *file, int32_t line, c @property (atomic, assign) ErrorCallback_t errorCallback; @property (atomic, assign) LogCallback_t logCallback; @property (atomic, assign) ContenctChangeCallback_t contenctChangeCallback; +@property (atomic, assign) ContenctChangeCallback_t contentLoadedCallback; +(MyMMKVHandler *) getHandler; diff --git a/flutter/mmkv_ios/darwin/Classes/flutter-bridge.mm b/flutter/mmkv_ios/darwin/Classes/flutter-bridge.mm index 69b6b36e..87de2928 100644 --- a/flutter/mmkv_ios/darwin/Classes/flutter-bridge.mm +++ b/flutter/mmkv_ios/darwin/Classes/flutter-bridge.mm @@ -670,6 +670,12 @@ MMKV_EXPORT bool MMKV_FUNC(isReadOnly)(const void *handle) { } } +- (void)onMMKVContentLoadSuccessfully:(NSString *)mmapID { + if (self.contentLoadedCallback) { + self.contentLoadedCallback(mmapID.UTF8String); + } +} + @end MMKV_EXPORT void MMKV_FUNC(registerErrorHandler)(ErrorCallback_t callback) { @@ -680,6 +686,10 @@ MMKV_EXPORT void MMKV_FUNC(registerContentChangeNotify)(ContenctChangeCallback_t [MyMMKVHandler getHandler].contenctChangeCallback = callback; } +MMKV_EXPORT void MMKV_FUNC(registerContentLoadedNotify)(ContenctChangeCallback_t callback) { + [MyMMKVHandler getHandler].contentLoadedCallback = callback; +} + MMKV_EXPORT void MMKV_FUNC(checkContentChanged)(const void *handle) { MMKV *kv = (__bridge MMKV*)handle; if (kv) { diff --git a/flutter/mmkv_linux/linux/flutter-bridge.cpp b/flutter/mmkv_linux/linux/flutter-bridge.cpp index cd0c1d67..7ed2b46d 100644 --- a/flutter/mmkv_linux/linux/flutter-bridge.cpp +++ b/flutter/mmkv_linux/linux/flutter-bridge.cpp @@ -36,13 +36,50 @@ using namespace std; # define MMKV_EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used)) using LogCallback_t = void (*)(uint32_t level, const char *file, int32_t line, const char *funcname, const char *message); -LogCallback_t g_logCallback = nullptr; +using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); +using ContenctChangeCallback_t = void (*)(const char *mmapID); -static void myLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) { - if (g_logCallback) { - g_logCallback(level, file, line, function, message.c_str()); +class FlutterMMKVHandler : public mmkv::MMKVHandler { +public: + LogCallback_t logCallback = nullptr; + ErrorCallback_t errorCallback = nullptr; + ContenctChangeCallback_t contentChangeCallback = nullptr; + ContenctChangeCallback_t contentLoadedCallback = nullptr; + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) override { + if (logCallback) { + logCallback(level, file, line, function, message.c_str()); + } } -} + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVCRCCheckFail); + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVFileLength); + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (contentChangeCallback) { + contentChangeCallback(mmapID.c_str()); + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (contentLoadedCallback) { + contentLoadedCallback(mmapID.c_str()); + } + } +}; + +static FlutterMMKVHandler g_flutterHandler; MMKV_EXPORT void *mmkvInitialize(const char *rootDir, int32_t logLevel, LogCallback_t callback) { if (!rootDir) { @@ -51,8 +88,8 @@ MMKV_EXPORT void *mmkvInitialize(const char *rootDir, int32_t logLevel, LogCallb auto root = string(rootDir); if (callback) { - g_logCallback = callback; - MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel, myLogHandler); + g_flutterHandler.logCallback = callback; + MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel, &g_flutterHandler); } else { MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel); } @@ -645,40 +682,24 @@ MMKV_EXPORT bool isReadOnly(void *handle) { return false; } -using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); -static ErrorCallback_t g_errorCallback = nullptr; - -static MMKVRecoverStrategic myErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - if (g_errorCallback) { - return (MMKVRecoverStrategic) g_errorCallback(mmapID.c_str(), errorType); - } - return OnErrorDiscard; -} - MMKV_EXPORT void registerErrorHandler(ErrorCallback_t callback) { - g_errorCallback = callback; - if (callback) { - MMKV::registerErrorHandler(myErrorHandler); - } else { - MMKV::unRegisterErrorHandler(); - } -} - -using ContenctChangeCallback_t = void (*)(const char *mmapID); -static ContenctChangeCallback_t g_contentChanceCallback = nullptr; - -static void myContentChangeHandler(const std::string &mmapID) { - if (g_contentChanceCallback) { - g_contentChanceCallback(mmapID.c_str()); + g_flutterHandler.errorCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.contentChangeCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); } } MMKV_EXPORT void registerContentChangeNotify(ContenctChangeCallback_t callback) { - g_contentChanceCallback = callback; - if (callback) { - MMKV::registerContentChangeHandler(myContentChangeHandler); - } else { - MMKV::unRegisterContentChangeHandler(); + g_flutterHandler.contentChangeCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); + } +} + +MMKV_EXPORT void registerContentLoadedNotify(ContenctChangeCallback_t callback) { + g_flutterHandler.contentLoadedCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentChangeCallback) { + MMKV::registerHandler(&g_flutterHandler); } } diff --git a/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart b/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart index fd52ba37..9c500c0b 100644 --- a/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart +++ b/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart @@ -318,6 +318,11 @@ class MMKVPluginPlatformFFI extends MMKVPluginPlatform { return nativeLib().lookup>(nativeFuncName("registerContentChangeNotify")).asFunction(); } + @override + ContentCallbackRegister registerContentLoadedHandlerFunc() { + return nativeLib().lookup>(nativeFuncName("registerContentLoadedNotify")).asFunction(); + } + @override void Function(Pointer p1) checkContentChangedFunc() { return nativeLib().lookup)>>(nativeFuncName("checkContentChanged")).asFunction(); diff --git a/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart b/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart index 7616f0e4..62a29a57 100644 --- a/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart +++ b/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart @@ -81,6 +81,12 @@ class MMKVHandler { void onContentChangedByOuterProcess(String mmapID) { return; } + + /// Called when an MMKV file is loaded successfully. + /// [mmapID] The unique ID of the loaded MMKV instance. + void onMMKVContentLoadSuccessfully(String mmapID) { + return; + } } /// The interface class that all implementation of MMKV platform plugin must extend @@ -307,6 +313,10 @@ class MMKVPluginPlatform { throw UnimplementedError(); } + ContentCallbackRegister registerContentLoadedHandlerFunc() { + throw UnimplementedError(); + } + void Function(Pointer) checkContentChangedFunc() { throw UnimplementedError(); } diff --git a/flutter/mmkv_win32/windows/flutter-bridge.cpp b/flutter/mmkv_win32/windows/flutter-bridge.cpp index 5819427a..f0401771 100644 --- a/flutter/mmkv_win32/windows/flutter-bridge.cpp +++ b/flutter/mmkv_win32/windows/flutter-bridge.cpp @@ -36,39 +36,50 @@ using namespace std; # define MMKV_EXPORT extern "C" __declspec(dllexport) using LogCallback_t = void (*)(uint32_t level, const char *file, int32_t line, const char *funcname, const char *message); -LogCallback_t g_logCallback = nullptr; +using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); +using ContenctChangeCallback_t = void (*)(const char *mmapID); -static void myLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) { - if (g_logCallback) { - g_logCallback(level, file, line, function, message.c_str()); +class FlutterMMKVHandler : public mmkv::MMKVHandler { +public: + LogCallback_t logCallback = nullptr; + ErrorCallback_t errorCallback = nullptr; + ContenctChangeCallback_t contentChangeCallback = nullptr; + ContenctChangeCallback_t contentLoadedCallback = nullptr; + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) override { + if (logCallback) { + logCallback(level, file, line, function, message.c_str()); + } } -} -static void myOutputDebugString(MMKVLogLevel level, const char *file, int line, const char *function, const char *format, ...) { - if (!g_logCallback) { - return; + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVCRCCheckFail); + } + return OnErrorDiscard; } - va_list args; - va_start(args, format); - char buffer[1024]; - auto requiredLen = vsnprintf(buffer, sizeof(buffer), format, args); - if (requiredLen < 0) { - // Handle error in formatting - va_end(args); - g_logCallback(level, file, line, function, "Error formatting debug string\n"); - return; - } else if (requiredLen >= sizeof(buffer)) { - // If the buffer was not large enough, truncate the string - buffer[sizeof(buffer) - 1] = '\0'; + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVFileLength); + } + return OnErrorDiscard; } - - va_end(args); - g_logCallback(level, file, line, function, buffer); -} -extern const char *_getFileName(const char *path); + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (contentChangeCallback) { + contentChangeCallback(mmapID.c_str()); + } + } -#define InternalLog(fmt, ...) myOutputDebugString(MMKVLogLevel::MMKVLogInfo, _getFileName(__FILE__), __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__) + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (contentLoadedCallback) { + contentLoadedCallback(mmapID.c_str()); + } + } +}; + +static FlutterMMKVHandler g_flutterHandler; MMKV_EXPORT void *mmkvInitialize(const char *rootDir, int32_t logLevel, LogCallback_t callback) { if (!rootDir) { @@ -77,8 +88,8 @@ MMKV_EXPORT void *mmkvInitialize(const char *rootDir, int32_t logLevel, LogCallb auto root = string2MMKVPath_t(rootDir); if (callback) { - g_logCallback = callback; - MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel, myLogHandler); + g_flutterHandler.logCallback = callback; + MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel, &g_flutterHandler); } else { MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel); } @@ -672,40 +683,24 @@ MMKV_EXPORT bool isReadOnly(void *handle) { return false; } -using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); -static ErrorCallback_t g_errorCallback = nullptr; - -static MMKVRecoverStrategic myErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - if (g_errorCallback) { - return (MMKVRecoverStrategic) g_errorCallback(mmapID.c_str(), errorType); - } - return OnErrorDiscard; -} - MMKV_EXPORT void registerErrorHandler(ErrorCallback_t callback) { - g_errorCallback = callback; - if (callback) { - MMKV::registerErrorHandler(myErrorHandler); - } else { - MMKV::unRegisterErrorHandler(); - } -} - -using ContenctChangeCallback_t = void (*)(const char *mmapID); -static ContenctChangeCallback_t g_contentChanceCallback = nullptr; - -static void myContentChangeHandler(const std::string &mmapID) { - if (g_contentChanceCallback) { - g_contentChanceCallback(mmapID.c_str()); + g_flutterHandler.errorCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.contentChangeCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); } } MMKV_EXPORT void registerContentChangeNotify(ContenctChangeCallback_t callback) { - g_contentChanceCallback = callback; - if (callback) { - MMKV::registerContentChangeHandler(myContentChangeHandler); - } else { - MMKV::unRegisterContentChangeHandler(); + g_flutterHandler.contentChangeCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); + } +} + +MMKV_EXPORT void registerContentLoadedNotify(ContenctChangeCallback_t callback) { + g_flutterHandler.contentLoadedCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentChangeCallback) { + MMKV::registerHandler(&g_flutterHandler); } } diff --git a/iOS/MMKV/MMKV/libMMKV.mm b/iOS/MMKV/MMKV/libMMKV.mm index 790965ac..4589879b 100644 --- a/iOS/MMKV/MMKV/libMMKV.mm +++ b/iOS/MMKV/MMKV/libMMKV.mm @@ -48,10 +48,47 @@ static NSString *g_groupPath = nil; static BOOL g_isRunningInAppExtension = NO; #endif -static void LogHandler(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, NSString *message); -static mmkv::MMKVRecoverStrategic ErrorHandler(const string &mmapID, mmkv::MMKVErrorType errorType); -static void ContentChangeHandler(const string &mmapID); -static void ContentLoadedHandler(const string &mmapID); +#pragma makr - callbacks + +// C++ adapter class that bridges mmkv::MMKVHandler to Objective-C MMKVHandler protocol +class ObjCMMKVHandler : public mmkv::MMKVHandler { +public: + void mmkvLog(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) override { + if (g_callbackHandler && [g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) { + [g_callbackHandler mmkvLogWithLevel:(MMKVLogLevel) level file:file line:line func:function message:message]; + } + } + + mmkv::MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)]) { + auto ret = [g_callbackHandler onMMKVCRCCheckFail:[NSString stringWithUTF8String:mmapID.c_str()]]; + return (mmkv::MMKVRecoverStrategic) ret; + } + return mmkv::OnErrorDiscard; + } + + mmkv::MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if ([g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) { + auto ret = [g_callbackHandler onMMKVFileLengthError:[NSString stringWithUTF8String:mmapID.c_str()]]; + return (mmkv::MMKVRecoverStrategic) ret; + } + return mmkv::OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) { + [g_callbackHandler onMMKVContentChange:[NSString stringWithUTF8String:mmapID.c_str()]]; + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentLoadSuccessfully:)]) { + [g_callbackHandler onMMKVContentLoadSuccessfully:[NSString stringWithUTF8String:mmapID.c_str()]]; + } + } +}; + +static ObjCMMKVHandler g_cppHandler; @interface MMKVNameSpace () @@ -93,10 +130,12 @@ static BOOL g_hasCalledInitializeMMKV = NO; } [g_callbackHandler release]; g_callbackHandler = [handler retain]; - mmkv::LogHandler logHandler = nullptr; - if (g_callbackHandler && [g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) { - g_isLogRedirecting = true; - logHandler = LogHandler; + mmkv::MMKVHandler *cppHandler = nullptr; + if (g_callbackHandler) { + if ([g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) { + g_isLogRedirecting = true; + } + cppHandler = &g_cppHandler; } if (rootDir != nil) { @@ -106,17 +145,10 @@ static BOOL g_hasCalledInitializeMMKV = NO; [self mmkvBasePath]; } NSAssert(g_basePath.length > 0, @"MMKV not initialized properly, must not call +initializeMMKV: before -application:didFinishLaunchingWithOptions:"); - mmkv::MMKV::initializeMMKV(g_basePath.UTF8String, (mmkv::MMKVLogLevel) logLevel, logHandler); + mmkv::MMKV::initializeMMKV(g_basePath.UTF8String, (mmkv::MMKVLogLevel) logLevel, cppHandler); - if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)] || - [g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) { - mmkv::MMKV::registerErrorHandler(ErrorHandler); - } - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) { - mmkv::MMKV::registerContentChangeHandler(ContentChangeHandler); - } - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentLoadSuccessfully:)]) { - mmkv::MMKV::registerContentLoadedHandler(ContentLoadedHandler); + if (g_callbackHandler) { + mmkv::MMKV::registerHandler(&g_cppHandler); } #if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION) && !(TARGET_OS_MACCATALYST) @@ -1128,19 +1160,14 @@ static NSString *md5(NSString *value) { [g_callbackHandler release]; g_callbackHandler = [handler retain]; - if ([g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) { - g_isLogRedirecting = true; - mmkv::MMKV::registerLogHandler(LogHandler); - } - if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)] || - [g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) { - mmkv::MMKV::registerErrorHandler(ErrorHandler); - } - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) { - mmkv::MMKV::registerContentChangeHandler(ContentChangeHandler); - } - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentLoadSuccessfully:)]) { - mmkv::MMKV::registerContentLoadedHandler(ContentLoadedHandler); + if (g_callbackHandler) { + if ([g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) { + g_isLogRedirecting = true; + } + mmkv::MMKV::registerHandler(&g_cppHandler); + } else { + g_isLogRedirecting = false; + mmkv::MMKV::unRegisterHandler(); } } @@ -1151,10 +1178,7 @@ static NSString *md5(NSString *value) { [g_callbackHandler release]; g_callbackHandler = nil; - mmkv::MMKV::unRegisterLogHandler(); - mmkv::MMKV::unRegisterErrorHandler(); - mmkv::MMKV::unRegisterContentChangeHandler(); - mmkv::MMKV::unRegisterContentLoadedHandler(); + mmkv::MMKV::unRegisterHandler(); } + (void)setLogLevel:(MMKVLogLevel)logLevel { @@ -1284,39 +1308,6 @@ static NSString *md5(NSString *value) { @end -#pragma makr - callbacks - -static void LogHandler(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, NSString *message) { - [g_callbackHandler mmkvLogWithLevel:(MMKVLogLevel) level file:file line:line func:function message:message]; -} - -static mmkv::MMKVRecoverStrategic ErrorHandler(const string &mmapID, mmkv::MMKVErrorType errorType) { - if (errorType == mmkv::MMKVCRCCheckFail) { - if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)]) { - auto ret = [g_callbackHandler onMMKVCRCCheckFail:[NSString stringWithUTF8String:mmapID.c_str()]]; - return (mmkv::MMKVRecoverStrategic) ret; - } - } else { - if ([g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) { - auto ret = [g_callbackHandler onMMKVFileLengthError:[NSString stringWithUTF8String:mmapID.c_str()]]; - return (mmkv::MMKVRecoverStrategic) ret; - } - } - return mmkv::OnErrorDiscard; -} - -static void ContentChangeHandler(const string &mmapID) { - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) { - [g_callbackHandler onMMKVContentChange:[NSString stringWithUTF8String:mmapID.c_str()]]; - } -} - -static void ContentLoadedHandler(const string &mmapID) { - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentLoadSuccessfully:)]) { - [g_callbackHandler onMMKVContentLoadSuccessfully:[NSString stringWithUTF8String:mmapID.c_str()]]; - } -} - #pragma mark - MMKVNameSpace @implementation MMKVNameSpace {