Repository: Tencent/MMKV Branch: master Commit: f7f0f47fa940 Files: 614 Total size: 16.0 MB Directory structure: gitextract_t9qzaftc/ ├── .clang-format ├── .dockerignore ├── .github/ │ ├── FUNDING.yml │ └── ISSUE_TEMPLATE/ │ └── bug_report.md ├── .gitignore ├── .gitmodules ├── Android/ │ └── MMKV/ │ ├── .gitignore │ ├── build.gradle │ ├── checkstyle.xml │ ├── debug.keystore │ ├── gradle/ │ │ ├── android-publish-private.gradle │ │ ├── android-publish.gradle │ │ ├── build_library.gradle │ │ ├── check.gradle │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── mmkv/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── proguard-rules.pro │ │ └── src/ │ │ ├── androidTest/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── mmkv/ │ │ │ ├── MMKVTest.java │ │ │ └── MMKVTestService.java │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── aidl/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── mmkv/ │ │ │ └── ParcelableMMKV.aidl │ │ ├── cpp/ │ │ │ ├── flutter-bridge.cpp │ │ │ └── native-bridge.cpp │ │ └── java/ │ │ └── com/ │ │ └── tencent/ │ │ └── mmkv/ │ │ ├── MMKV.java │ │ ├── MMKVConfig.java │ │ ├── MMKVContentChangeNotification.java │ │ ├── MMKVContentProvider.java │ │ ├── MMKVHandler.java │ │ ├── MMKVLogLevel.java │ │ ├── MMKVProcessUtil.java │ │ ├── MMKVRecoverStrategic.java │ │ ├── NameSpace.java │ │ ├── NativeBuffer.java │ │ ├── ParcelableMMKV.java │ │ └── UnsupportedArchitectureException.java │ ├── mmkvannotation/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── dalvik/ │ │ └── annotation/ │ │ └── optimization/ │ │ └── FastNative.java │ ├── mmkvdemo/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── aidl/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── mmkvdemo/ │ │ │ └── IAshmemMMKV.aidl │ │ ├── cpp/ │ │ │ ├── CMakeLists.txt │ │ │ └── mmkvdemo.cpp │ │ ├── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── mmkvdemo/ │ │ │ ├── Baseline.java │ │ │ ├── BenchMarkBaseService.java │ │ │ ├── FakeInfo.java │ │ │ ├── Info.java │ │ │ ├── MainActivity.java │ │ │ ├── MultiProcessSharedPreferences.java │ │ │ ├── MyApplication.java │ │ │ ├── MyService.java │ │ │ ├── MyService_1.java │ │ │ ├── SQLiteKV.java │ │ │ └── TestParcelable.java │ │ ├── kotlin/ │ │ │ └── KotlinUsecase.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ └── ic_launcher_background.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── values/ │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── pmd-ruleset.xml │ ├── proguard-rules/ │ │ ├── proguard-rules-android-lib.pro │ │ └── proguard-rules-test.pro │ └── settings.gradle ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Core/ │ ├── .gitignore │ ├── CMakeLists.txt │ ├── CodedInputData.cpp │ ├── CodedInputData.h │ ├── CodedInputDataCrypt.cpp │ ├── CodedInputDataCrypt.h │ ├── CodedInputDataCrypt_OSX.cpp │ ├── CodedInputData_OSX.cpp │ ├── CodedOutputData.cpp │ ├── CodedOutputData.h │ ├── Core.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ ├── Core.xcscheme │ │ └── MMKVWatchCore.xcscheme │ ├── InterProcessLock.cpp │ ├── InterProcessLock.h │ ├── InterProcessLock_Android.cpp │ ├── InterProcessLock_Win32.cpp │ ├── KeyValueHolder.cpp │ ├── KeyValueHolder.h │ ├── MMBuffer.cpp │ ├── MMBuffer.h │ ├── MMKV.cpp │ ├── MMKV.h │ ├── MMKVHandler.h │ ├── MMKVLog.cpp │ ├── MMKVLog.h │ ├── MMKVLog_Android.cpp │ ├── MMKVMetaInfo.hpp │ ├── MMKVPredef.h │ ├── MMKV_Android.cpp │ ├── MMKV_IO.cpp │ ├── MMKV_IO.h │ ├── MMKV_OSX.cpp │ ├── MMKV_OSX.h │ ├── MemoryFile.cpp │ ├── MemoryFile.h │ ├── MemoryFile_Android.cpp │ ├── MemoryFile_Linux.cpp │ ├── MemoryFile_OSX.cpp │ ├── MemoryFile_Win32.cpp │ ├── MiniPBCoder.cpp │ ├── MiniPBCoder.h │ ├── MiniPBCoder_OSX.cpp │ ├── PBEncodeItem.hpp │ ├── PBUtility.cpp │ ├── PBUtility.h │ ├── ScopedLock.hpp │ ├── ThreadLock.cpp │ ├── ThreadLock.h │ ├── ThreadLock_Win32.cpp │ ├── aes/ │ │ ├── AESCrypt.cpp │ │ ├── AESCrypt.h │ │ └── openssl/ │ │ ├── openssl_aes.h │ │ ├── openssl_aes_core.cpp │ │ ├── openssl_aes_locl.h │ │ ├── openssl_aesv8-armx.S │ │ ├── openssl_arm_arch.h │ │ ├── openssl_cfb128.cpp │ │ ├── openssl_md32_common.h │ │ ├── openssl_md5.h │ │ ├── openssl_md5_dgst.cpp │ │ ├── openssl_md5_locl.h │ │ ├── openssl_md5_one.cpp │ │ └── openssl_opensslconf.h │ ├── core.vcxproj │ ├── core.vcxproj.filters │ ├── crc32/ │ │ ├── CMakeLists.txt │ │ ├── Checksum.h │ │ ├── crc32_armv8.cpp │ │ └── zlib/ │ │ ├── crc32.cpp │ │ ├── crc32.h │ │ ├── zconf.h │ │ └── zutil.h │ └── fakeinclude/ │ └── MMKVCore/ │ ├── MMBuffer.h │ ├── MMKV.h │ ├── MMKVHandler.h │ ├── MMKVLog.h │ ├── MMKVPredef.h │ ├── MemoryFile.h │ ├── MiniPBCoder.h │ ├── ScopedLock.hpp │ └── ThreadLock.h ├── Dockerfile ├── LICENSE.TXT ├── MMKV.podspec ├── MMKV.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist ├── MMKVAppExtension.podspec ├── MMKVCore.podspec ├── MMKVWatchExtension.podspec ├── Makefile ├── OpenHarmony/ │ ├── .gitignore │ ├── AppScope/ │ │ ├── app.json5 │ │ └── resources/ │ │ └── base/ │ │ └── element/ │ │ └── string.json │ ├── MMKV/ │ │ ├── .gitignore │ │ ├── BuildProfile.ets │ │ ├── CHANGELOG.md │ │ ├── Index.ets │ │ ├── LICENSE │ │ ├── README.md │ │ ├── build-profile.json5 │ │ ├── consumer-rules.txt │ │ ├── example/ │ │ │ ├── Index.ets │ │ │ └── Util.ets │ │ ├── hvigorfile.ts │ │ ├── obfuscation-rules.txt │ │ ├── oh-package.json5 │ │ └── src/ │ │ └── main/ │ │ ├── cpp/ │ │ │ ├── CMakeLists.txt │ │ │ ├── flutter-bridge.cpp │ │ │ ├── native_bridge.cpp │ │ │ └── types/ │ │ │ └── libmmkv/ │ │ │ ├── index.d.ts │ │ │ └── oh-package.json5 │ │ ├── ets/ │ │ │ └── utils/ │ │ │ ├── MMKV.ets │ │ │ ├── MMKVConfig.ets │ │ │ ├── MMKVHandler.ets │ │ │ ├── MMKVLogLevel.ets │ │ │ ├── NativeBuffer.ets │ │ │ └── Util.ts │ │ └── module.json5 │ ├── build-profile.json5 │ ├── dependencies/ │ │ ├── hvigor-4.3.0.tgz │ │ └── hvigor-ohos-plugin-4.3.0.tgz │ ├── entry/ │ │ ├── .gitignore │ │ ├── build-profile.json5 │ │ ├── hvigorfile.ts │ │ ├── obfuscation-rules.txt │ │ ├── oh-package.json5 │ │ └── src/ │ │ ├── main/ │ │ │ ├── cpp/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── napi_init.cpp │ │ │ │ └── types/ │ │ │ │ └── libentry/ │ │ │ │ ├── index.d.ts │ │ │ │ └── oh-package.json5 │ │ │ ├── ets/ │ │ │ │ ├── Util/ │ │ │ │ │ ├── Baseline.ets │ │ │ │ │ ├── MyMMKVHandler.ets │ │ │ │ │ └── Util.ets │ │ │ │ ├── entryability/ │ │ │ │ │ └── EntryAbility.ets │ │ │ │ ├── pages/ │ │ │ │ │ └── Index.ets │ │ │ │ └── process/ │ │ │ │ └── DemoProcess.ets │ │ │ ├── module.json5 │ │ │ └── resources/ │ │ │ ├── base/ │ │ │ │ ├── element/ │ │ │ │ │ ├── color.json │ │ │ │ │ └── string.json │ │ │ │ ├── media/ │ │ │ │ │ └── layered_image.json │ │ │ │ └── profile/ │ │ │ │ └── main_pages.json │ │ │ ├── en_US/ │ │ │ │ └── element/ │ │ │ │ └── string.json │ │ │ └── zh_CN/ │ │ │ └── element/ │ │ │ └── string.json │ │ ├── mock/ │ │ │ └── mock-config.json5 │ │ ├── ohosTest/ │ │ │ ├── ets/ │ │ │ │ ├── test/ │ │ │ │ │ ├── Ability.test.ets │ │ │ │ │ └── List.test.ets │ │ │ │ ├── testability/ │ │ │ │ │ ├── TestAbility.ets │ │ │ │ │ └── pages/ │ │ │ │ │ └── Index.ets │ │ │ │ └── testrunner/ │ │ │ │ └── OpenHarmonyTestRunner.ets │ │ │ ├── module.json5 │ │ │ └── resources/ │ │ │ └── base/ │ │ │ ├── element/ │ │ │ │ ├── color.json │ │ │ │ └── string.json │ │ │ └── profile/ │ │ │ └── test_pages.json │ │ └── test/ │ │ ├── List.test.ets │ │ └── LocalUnit.test.ets │ ├── hvigor/ │ │ └── hvigor-config.json5 │ ├── hvigorfile.ts │ └── oh-package.json5 ├── POSIX/ │ ├── CMakeLists.txt │ ├── demo/ │ │ ├── CMakeLists.txt │ │ ├── TestInterProcessLock.cpp │ │ ├── UnitTest.cpp │ │ ├── demo.cpp │ │ └── process.cpp │ ├── golang/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── callback.go │ │ ├── go.mod │ │ ├── golang-bridge.cpp │ │ ├── golang-bridge.h │ │ ├── mmkv.go │ │ ├── mmkv_test.go │ │ └── test/ │ │ ├── go.mod │ │ └── main.go │ └── src/ │ ├── CMakeLists.txt │ └── libmmkv.cpp ├── Package.swift ├── Python/ │ ├── CMakeLists.txt │ ├── README.md │ ├── demo.py │ ├── libmmkv_python.cpp │ ├── setup.py │ └── unit_test.py ├── README.md ├── README_CN.md ├── SECURITY.md ├── Script/ │ ├── dumpJavaSignature.py │ └── formatCode.py ├── Win32/ │ ├── .clang-format │ ├── Win32.sln │ ├── Win32Demo/ │ │ ├── Win32Demo.cpp │ │ ├── Win32Demo.vcxproj │ │ ├── Win32Demo.vcxproj.filters │ │ ├── pch.cpp │ │ └── pch.h │ └── Win32DemoProcess/ │ ├── Win32DemoProcess.cpp │ ├── Win32DemoProcess.vcxproj │ ├── Win32DemoProcess.vcxproj.filters │ ├── pch.cpp │ └── pch.h ├── flutter/ │ ├── .gitignore │ ├── .metadata │ ├── .vscode/ │ │ ├── launch.json │ │ └── settings.json │ ├── mmkv/ │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── example/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── .gitignore │ │ │ │ ├── app/ │ │ │ │ │ ├── build.gradle │ │ │ │ │ └── src/ │ │ │ │ │ ├── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── tencent/ │ │ │ │ │ │ │ └── mmkv_example/ │ │ │ │ │ │ │ └── MainActivity.java │ │ │ │ │ │ └── res/ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── profile/ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── build.gradle │ │ │ │ ├── gradle/ │ │ │ │ │ └── wrapper/ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ ├── gradle.properties │ │ │ │ ├── settings.gradle │ │ │ │ └── settings_aar.gradle │ │ │ ├── ios/ │ │ │ │ ├── .gitignore │ │ │ │ ├── Flutter/ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ └── ephemeral/ │ │ │ │ │ ├── flutter_lldb_helper.py │ │ │ │ │ └── flutter_lldbinit │ │ │ │ ├── Podfile │ │ │ │ ├── Runner/ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ └── README.md │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ ├── Info.plist │ │ │ │ │ ├── Runner.entitlements │ │ │ │ │ └── main.m │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── linux/ │ │ │ │ ├── .gitignore │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── flutter/ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── generated_plugin_registrant.cc │ │ │ │ │ ├── generated_plugin_registrant.h │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ └── runner/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── main.cc │ │ │ │ ├── my_application.cc │ │ │ │ └── my_application.h │ │ │ ├── macos/ │ │ │ │ ├── .gitignore │ │ │ │ ├── Flutter/ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ ├── Flutter-Release.xcconfig │ │ │ │ │ └── GeneratedPluginRegistrant.swift │ │ │ │ ├── Podfile │ │ │ │ ├── Runner/ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ │ └── Warnings.xcconfig │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ ├── Info.plist │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ └── Release.entitlements │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ └── RunnerTests/ │ │ │ │ └── RunnerTests.swift │ │ │ ├── ohos/ │ │ │ │ ├── .gitignore │ │ │ │ ├── AppScope/ │ │ │ │ │ ├── app.json5 │ │ │ │ │ └── resources/ │ │ │ │ │ └── base/ │ │ │ │ │ └── element/ │ │ │ │ │ └── string.json │ │ │ │ ├── build-profile.json5 │ │ │ │ ├── entry/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build-profile.json5 │ │ │ │ │ ├── hvigorfile.ts │ │ │ │ │ ├── oh-package.json5 │ │ │ │ │ └── src/ │ │ │ │ │ ├── main/ │ │ │ │ │ │ ├── ets/ │ │ │ │ │ │ │ ├── entryability/ │ │ │ │ │ │ │ │ └── EntryAbility.ets │ │ │ │ │ │ │ ├── pages/ │ │ │ │ │ │ │ │ └── Index.ets │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ └── GeneratedPluginRegistrant.ets │ │ │ │ │ │ ├── module.json5 │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ ├── base/ │ │ │ │ │ │ │ ├── element/ │ │ │ │ │ │ │ │ ├── color.json │ │ │ │ │ │ │ │ └── string.json │ │ │ │ │ │ │ └── profile/ │ │ │ │ │ │ │ └── main_pages.json │ │ │ │ │ │ ├── en_US/ │ │ │ │ │ │ │ └── element/ │ │ │ │ │ │ │ └── string.json │ │ │ │ │ │ └── zh_CN/ │ │ │ │ │ │ └── element/ │ │ │ │ │ │ └── string.json │ │ │ │ │ └── ohosTest/ │ │ │ │ │ ├── ets/ │ │ │ │ │ │ ├── test/ │ │ │ │ │ │ │ ├── Ability.test.ets │ │ │ │ │ │ │ └── List.test.ets │ │ │ │ │ │ ├── testability/ │ │ │ │ │ │ │ ├── TestAbility.ets │ │ │ │ │ │ │ └── pages/ │ │ │ │ │ │ │ └── Index.ets │ │ │ │ │ │ └── testrunner/ │ │ │ │ │ │ └── OpenHarmonyTestRunner.ts │ │ │ │ │ ├── module.json5 │ │ │ │ │ └── resources/ │ │ │ │ │ └── base/ │ │ │ │ │ ├── element/ │ │ │ │ │ │ ├── color.json │ │ │ │ │ │ └── string.json │ │ │ │ │ └── profile/ │ │ │ │ │ └── test_pages.json │ │ │ │ ├── hvigor/ │ │ │ │ │ └── hvigor-config.json5 │ │ │ │ ├── hvigorfile.ts │ │ │ │ └── oh-package.json5 │ │ │ ├── pubspec.yaml │ │ │ └── windows/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── flutter/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── generated_plugin_registrant.cc │ │ │ │ ├── generated_plugin_registrant.h │ │ │ │ └── generated_plugins.cmake │ │ │ └── runner/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Runner.rc │ │ │ ├── flutter_window.cpp │ │ │ ├── flutter_window.h │ │ │ ├── main.cpp │ │ │ ├── resource.h │ │ │ ├── runner.exe.manifest │ │ │ ├── utils.cpp │ │ │ ├── utils.h │ │ │ ├── win32_window.cpp │ │ │ └── win32_window.h │ │ ├── lib/ │ │ │ └── mmkv.dart │ │ ├── mmkv.iml │ │ ├── pubspec.yaml │ │ ├── test/ │ │ │ └── mmkv_test.dart │ │ └── tool/ │ │ ├── fix_mmkv_plugin_name.rb │ │ └── mmkvpodhelper.rb │ ├── mmkv.iml │ ├── mmkv_android/ │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── android/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── gradle/ │ │ │ │ └── wrapper/ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ │ ├── gradle.properties │ │ │ ├── gradlew │ │ │ ├── gradlew.bat │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── tencent/ │ │ │ └── mmkv/ │ │ │ └── MMKVPlugin.java │ │ ├── lib/ │ │ │ └── mmkv_android.dart │ │ └── pubspec.yaml │ ├── mmkv_ios/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── darwin/ │ │ │ ├── .gitignore │ │ │ ├── Classes/ │ │ │ │ ├── MMKVPlugin.h │ │ │ │ ├── MMKVPlugin.mm │ │ │ │ ├── flutter-bridge.h │ │ │ │ └── flutter-bridge.mm │ │ │ └── mmkv_ios.podspec │ │ ├── lib/ │ │ │ └── mmkv_ios.dart │ │ └── pubspec.yaml │ ├── mmkv_linux/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── lib/ │ │ │ └── mmkv_linux.dart │ │ ├── linux/ │ │ │ ├── CMakeLists.txt │ │ │ ├── flutter-bridge.cpp │ │ │ ├── include/ │ │ │ │ └── mmkv_linux/ │ │ │ │ └── mmkv_linux_plugin.h │ │ │ └── mmkv_linux_plugin.cc │ │ └── pubspec.yaml │ ├── mmkv_ohos/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── lib/ │ │ │ └── mmkv_ohos.dart │ │ ├── ohos/ │ │ │ ├── .gitignore │ │ │ ├── build-profile.json5 │ │ │ ├── hvigorfile.ts │ │ │ ├── index.ets │ │ │ ├── oh-package.json5 │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── ets/ │ │ │ │ └── components/ │ │ │ │ └── plugin/ │ │ │ │ └── MMKVPlugin.ets │ │ │ └── module.json5 │ │ └── pubspec.yaml │ ├── mmkv_platform_interface/ │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── lib/ │ │ │ ├── mmkv_platform_ffi.dart │ │ │ └── mmkv_platform_interface.dart │ │ └── pubspec.yaml │ └── mmkv_win32/ │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── lib/ │ │ └── mmkv_win32.dart │ └── pubspec.yaml └── iOS/ ├── MMKV/ │ ├── MMKV/ │ │ ├── AutoCleanInfo.hpp │ │ ├── MMKV.h │ │ ├── MMKVAppExtension/ │ │ │ ├── include/ │ │ │ │ └── MMKVAppExtension/ │ │ │ │ └── MMKV.h │ │ │ └── libMMKV.mm │ │ ├── MMKVHandler.h │ │ ├── Resources/ │ │ │ ├── Info.plist │ │ │ └── PrivacyInfo.xcprivacy │ │ ├── fakeinclude/ │ │ │ └── MMKV/ │ │ │ └── MMKV.h │ │ └── libMMKV.mm │ └── MMKV.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata/ │ └── xcschemes/ │ ├── MMKV For App Extension.xcscheme │ ├── MMKV Static.xcscheme │ ├── MMKV.xcscheme │ └── MMKVWatchExtension.xcscheme └── MMKVDemo/ ├── Config.xcconfig ├── MMKVCatalystDemo/ │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets/ │ │ ├── AccentColor.colorset/ │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj/ │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── MMKVCatalystDemo.entitlements │ ├── SceneDelegate.h │ ├── SceneDelegate.m │ ├── ViewController.h │ ├── ViewController.mm │ └── main.m ├── MMKVDemo/ │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── DemoSwiftUsage.swift │ ├── MMKVDemo-Bridging-Header.h │ ├── Resources/ │ │ ├── Assets.xcassets/ │ │ │ └── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── MMKVDemo.entitlements │ │ ├── testReadOnly │ │ └── testReadOnly.crc │ ├── TestMMKVCpp.cpp │ ├── TestMMKVCpp.hpp │ ├── ViewController+TestCaseBad.h │ ├── ViewController+TestCaseBad.mm │ ├── ViewController.h │ ├── ViewController.mm │ └── main.m ├── MMKVDemo.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata/ │ └── xcschemes/ │ ├── MMKVDemo.xcscheme │ ├── MMKVMacDemo.xcscheme │ ├── MMKVTodayExtensionDemo.xcscheme │ ├── WatchApp (Notification).xcscheme │ ├── WatchApp.xcscheme │ └── kvdemo.xcscheme ├── MMKVDemoTests/ │ ├── Info.plist │ ├── MMKVDemoTests.mm │ └── MMKVPerformanceTest.mm ├── MMKVMacDemo/ │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj/ │ │ └── Main.storyboard │ ├── Info.plist │ ├── MMKVMacDemo.entitlements │ ├── ViewController.h │ ├── ViewController.mm │ └── main.m ├── MMKVTodayExtensionDemo/ │ ├── Base.lproj/ │ │ └── MainInterface.storyboard │ ├── Info.plist │ ├── MMKVTodayExtensionDemo.entitlements │ ├── TodayViewController.h │ └── TodayViewController.m ├── MMKVVisionDemo/ │ ├── Assets.xcassets/ │ │ ├── AppIcon.solidimagestack/ │ │ │ ├── Back.solidimagestacklayer/ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.solidimagestacklayer/ │ │ │ │ ├── Content.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.solidimagestacklayer/ │ │ │ ├── Content.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ContentView.swift │ ├── Info.plist │ ├── MMKVVisionDemoApp.swift │ └── Preview Content/ │ └── Preview Assets.xcassets/ │ └── Contents.json ├── WatchApp/ │ ├── Assets.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj/ │ │ └── Interface.storyboard │ └── Info.plist ├── WatchApp Extension/ │ ├── Assets.xcassets/ │ │ ├── Complication.complicationset/ │ │ │ ├── Circular.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Extra Large.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Graphic Bezel.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Graphic Circular.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Graphic Corner.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Graphic Extra Large.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Graphic Large Rectangular.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── Modular.imageset/ │ │ │ │ └── Contents.json │ │ │ └── Utilitarian.imageset/ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ExtensionDelegate.h │ ├── ExtensionDelegate.mm │ ├── Info.plist │ ├── InterfaceController.h │ ├── InterfaceController.m │ ├── NotificationController.h │ ├── NotificationController.m │ └── PushNotificationPayload.apns └── kvdemo/ ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets/ │ ├── AccentColor.colorset/ │ │ └── Contents.json │ ├── AppIcon.appiconset/ │ │ └── Contents.json │ └── Contents.json ├── Base.lproj/ │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── SceneDelegate.h ├── SceneDelegate.m ├── ViewController.h ├── ViewController.m ├── kvdemo.entitlements └── main.m ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- BasedOnStyle: LLVM IndentWidth: 4 TabWidth: 4 AlwaysBreakTemplateDeclarations: true AllowShortFunctionsOnASingleLine: InlineOnly #AllowShortLambdasOnASingleLine: Inline BreakAfterJavaFieldAnnotations: true #BreakBeforeBraces: Linux SpaceAfterCStyleCast: true IndentCaseLabels: true AccessModifierOffset: -4 BreakBeforeBraces: Custom BraceWrapping: AfterNamespace: false AfterClass: false AfterFunction: false BreakConstructorInitializersBeforeComma: true ConstructorInitializerAllOnOneLineOrOnePerLine: true BinPackParameters: false ReflowComments: false ObjCBlockIndentWidth: 4 --- Language: Cpp ColumnLimit: 120 IndentPPDirectives: AfterHash --- Language: ObjC ColumnLimit: 0 #UseTab: ForIndentation --- Language: Java ColumnLimit: 120 AllowShortFunctionsOnASingleLine: None BreakBeforeBinaryOperators: NonAssignment ================================================ FILE: .dockerignore ================================================ # 排除特定文件 output/ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [lingol] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry polar: # Replace with a single Polar username buy_me_a_coffee: # Replace with a single Buy Me a Coffee username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve --- ## Note: An crash issue will be **ignored & closed** in a week **without logs**. ### The language of MMKV > e.g. Objective-C, Swift, Java, or Kotlin ### The version of MMKV > e.g. v1.2.2 > Note: For versions older than the latest version, please upgrade before posting any issue. > We don't have much time for old version tech support. ### The platform of MMKV > e.g. iOS or Android ### The installation of MMKV > e.g. Cocoapods, Maven, or Git clone ### What's the issue? > Post the outputs or screenshots for errors. > > Explain what you want by example or code **in English**. > If you have a crash/OOM/ANR on Android, please provide **symbolize stack traces**. https://developer.android.com/ndk/guides/ndk-stack.html ### What's the log of MMKV when that happened? > Your **detail logs**. As much as possible. > For how to forward MMKV's log, you can checkout the wiki on each platform. > An crash issue will be **ignored & closed** in a week **without logs**. ================================================ FILE: .gitignore ================================================ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata ## Other *.xccheckout *.moved-aside *.xcuserstate *.xcscmblueprint #python venv ## OS X .DS_Store ## Xcode DerivedData ## Visual Studio .vs [Dd]ebug/ [Rr]elease/ *.user *.VC.opendb *.VC.db ipch/ #cmake CMakeCache.txt CMakeFiles/ cmake_install.cmake Linux/Makefile cmake-build-debug/ cmake-build-release/ build/ build_*/ dist/ *.egg-info/ .cmake #CLion .idea #Android Studio .cxx pubspec.lock Podfile.lock #docker output/ #swiftpm .swiftpm ================================================ FILE: .gitmodules ================================================ [submodule "Python/pybind11"] path = Python/pybind11 url = https://github.com/pybind/pybind11.git ================================================ FILE: Android/MMKV/.gitignore ================================================ *.iml .gradle /local.properties /.idea/libraries /.idea/modules.xml /.idea/workspace.xml .DS_Store /build /captures .externalNativeBuild .idea/caches/build_file_checksums.ser .idea/ ================================================ FILE: Android/MMKV/build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = '2.2.20' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:8.13.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() mavenCentral() } } task clean(type: Delete) { delete rootProject.buildDir } ext { minSdkVersion = 23 compileSdk = 35 targetSdkVersion = compileSdk supportLibVersion = "25.4.0" javaVersion = JavaVersion.VERSION_11 defaultsToPrivateOnly = true GROUP = 'com.tencent' VERSION_NAME = "${VERSION_NAME_PREFIX}${VERSION_NAME_SUFFIX}" POM_PACKAGING = "pom" POM_DESCRIPTION = "MMKV for Android" POM_URL = "https://github.com/Tencent/MMKV" POM_SCM_URL = "https://github.com/Tencent/MMKV" POM_SCM_CONNECTION="scm:git:github.com/Tencent/MMKV.git" POM_SCM_DEV_CONNECTION="scm:git:ssh://git@github.com/Tencent/MMKV.git" POM_ISSUE_URL = 'https://github.com/Tencent/MMKV/issues' POM_LICENCE_NAME = "BSD License" POM_LICENCE_URL = "https://opensource.org/licenses/BSD-3-Clause" POM_DEVELOPER_ID = "Tencent Wechat" POM_DEVELOPER_NAME = "Tencent Wechat, Inc." } ================================================ FILE: Android/MMKV/checkstyle.xml ================================================ ================================================ FILE: Android/MMKV/gradle/android-publish-private.gradle ================================================ // OSS_ANDROID_TEMPLATE_FILE_HEADER /** * New android-publish gradle script (for private use only, without bintray support) * * 1. Multiple flavors support. * 2. Provide PRIVATE_RELEASE_REPOSITORY_URL and PRIVATE_SNAPSHOT_REPOSITORY_URL with gradle.properties or local.properties. * 3. Provide PRIVATE_REPOSITORY_USERNAME and PRIVATE_REPOSITORY_PASSWORD if needed. */ apply plugin: 'digital.wup.android-maven-publish' apply plugin: 'signing' def isReleaseBuild() { return version.contains("SNAPSHOT") == false } def getReleaseRepositoryUrl() { return hasProperty('PRIVATE_RELEASE_REPOSITORY_URL') ? PRIVATE_RELEASE_REPOSITORY_URL : readPropertyFromLocalProperties('PRIVATE_RELEASE_REPOSITORY_URL') } def getSnapshotRepositoryUrl() { return hasProperty('PRIVATE_SNAPSHOT_REPOSITORY_URL') ? PRIVATE_SNAPSHOT_REPOSITORY_URL : readPropertyFromLocalProperties('PRIVATE_SNAPSHOT_REPOSITORY_URL') } def readPropertyFromLocalProperties(String key) { Properties properties = new Properties() try { properties.load(project.rootProject.file('local.properties').newDataInputStream()) } catch (Exception e) { println("load local properties failed msg:${e.message}") } return properties.getProperty(key) } def getRepositoryUsername() { return hasProperty('PRIVATE_REPOSITORY_USERNAME') ? PRIVATE_REPOSITORY_USERNAME : readPropertyFromLocalProperties('PRIVATE_REPOSITORY_USERNAME') } def getRepositoryPassword() { return hasProperty('PRIVATE_REPOSITORY_PASSWORD') ? PRIVATE_REPOSITORY_PASSWORD : readPropertyFromLocalProperties('PRIVATE_REPOSITORY_PASSWORD') } def pomConfig = { scm { url POM_SCM_URL } licenses { license { name POM_LICENCE_NAME url POM_LICENCE_URL distribution POM_LICENCE_DIST } } developers { developer { id POM_DEVELOPER_ID name POM_DEVELOPER_NAME } } } android.libraryVariants.all { variant -> // Skipped debug variants if (variant.buildType.name == "debug") { return } def hasFlavors = !variant.flavorName.isEmpty() def artifactIdSuffix = hasFlavors ? variant.flavorName.replace('_', '-').capitalize() : '' variant.productFlavors.each { flavor -> def flavorArtifactIdSuffix = flavor.ext.has('artifactIdSuffix') ? flavor.ext.artifactIdSuffix : flavor.name if (!flavorArtifactIdSuffix.isEmpty()) { artifactIdSuffix = artifactIdSuffix.replace(flavor.name.capitalize(), "-${flavorArtifactIdSuffix}") } else { artifactIdSuffix = artifactIdSuffix.replace(flavor.name.capitalize(), "") } } if (!artifactIdSuffix.isEmpty() && !artifactIdSuffix.startsWith('-')) { artifactIdSuffix = '-' + artifactIdSuffix } def curArtifactId = "${POM_ARTIFACT_ID}${artifactIdSuffix}" /** * Includes */ def sourceDirs = variant.sourceSets.collect { it.javaDirectories // TODO: kotlin sources } def javadoc = task("${variant.name}Javadoc", type: Javadoc) { source = variant.javaCompile.source // TODO: deprecated options.encoding = 'utf-8' destinationDir = file("${buildDir}/docs/javadoc${hasFlavors ? artifactIdSuffix : ""}") ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdk}/android.jar" classpath += files(ext.androidJar) classpath += files(configurations.compile) exclude '**/BuildConfig.java' exclude '**/R.java' failOnError false } def javadocJar = task("${variant.name}JavadocJar", type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } def sourcesJar = task("${variant.name}SourcesJar", type: Jar) { classifier = 'sources' from sourceDirs } def jniSymbolsJar = task("${variant.name}SymbolJar", type: Jar, dependsOn: 'build') { classifier = "so-symbols" boolean hasNativeBuildTask = false tasks.each { task -> if (task.getName().startsWith("externalNativeBuild")) { hasNativeBuildTask = true } } if (!hasNativeBuildTask) { return } if (hasFlavors) { variant.productFlavors.each { flavor -> from file("build/intermediates/cmake/${flavor.name}/release/obj/") } } else { from file("build/intermediates/cmake/release/obj/") } } // require gradle 4.x + if (GradleVersion.current() < GradleVersion.version('4.0')) { throw new GradleException('android-publish.gradle need Gradle 4.0 or newer') } def publicationName = "component${variant.name.capitalize()}" // Declare publications publishing.publications { "$publicationName"(MavenPublication) { artifactId curArtifactId groupId groupId version version from components.findByName("android${variant.name.capitalize()}") artifact javadocJar artifact jniSymbolsJar } } } /** * Setup custom maven repo */ publishing.repositories { maven { url "${isReleaseBuild() ? getReleaseRepositoryUrl() : getSnapshotRepositoryUrl()}" credentials { username "${getRepositoryUsername()}" password "${getRepositoryPassword()}" } } } if (JavaVersion.current().isJava8Compatible()) { allprojects { tasks.withType(Javadoc) { options.addStringOption('Xdoclint:none', '-quiet') } } } task buildAndPublishToLocalMaven(type: Copy, dependsOn: ['build', 'publishToMavenLocal']) { group = 'publishing' // save artifacts files to artifacts folder from configurations.archives.allArtifacts.files into "${rootProject.buildDir}/outputs/artifacts/" rename { String fileName -> fileName.replace("release.aar", "${version}.aar") } doLast { println "* published to maven local: ${project.group}:${project.name}:${project.version}" } } task buildAndPublishRepo(type: Copy, dependsOn: ['build', 'publish']) { group = "publishing" // save artifacts files to artifacts folder from configurations.archives.allArtifacts.files into "${rootProject.buildDir}/outputs/artifacts/" rename { String fileName -> fileName.replace("release.aar", "${version}.aar") } doLast { println "* published to repo: ${project.group}:${project.name}:${project.version}" } } apply from: rootProject.file('gradle/check.gradle') ================================================ FILE: Android/MMKV/gradle/android-publish.gradle ================================================ import java.util.regex.Matcher import java.util.regex.Pattern // OSS_ANDROID_TEMPLATE_FILE_HEADER /** * New android-publish gradle script * * 1. Multiple flavors support. * 2. Provide RELEASE_REPOSITORY_URL and SNAPSHOT_REPOSITORY_URL with gradle.properties or local.properties. * 3. Provide REPOSITORY_USERNAME and REPOSITORY_PASSWORD if needed. */ apply plugin: 'maven-publish' apply plugin: 'signing' def isReleaseBuild() { return version.contains("SNAPSHOT") == false } def getReleaseRepositoryUrl() { return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL : readPropertyFromLocalProperties('RELEASE_REPOSITORY_URL') } def getSnapshotRepositoryUrl() { return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL : readPropertyFromLocalProperties('SNAPSHOT_REPOSITORY_URL') } def readPropertyFromLocalProperties(String key) { Properties properties = new Properties() try { properties.load(project.rootProject.file('local.properties').newDataInputStream()) } catch (Exception e) { println("load local properties failed msg:${e.message}") } return properties.getProperty(key) } def getRepositoryUsername() { return hasProperty('REPOSITORY_USERNAME') ? REPOSITORY_USERNAME : readPropertyFromLocalProperties('REPOSITORY_USERNAME') } def getRepositoryPassword() { return hasProperty('REPOSITORY_PASSWORD') ? REPOSITORY_PASSWORD : readPropertyFromLocalProperties('REPOSITORY_PASSWORD') } def curArtifactId android.libraryVariants.all { variant -> // Skipped debug variants if (variant.buildType.name == "debug") { return } // Skipped non publishing variants // println('android.defaultPublishConfig=' + android.defaultPublishConfig) // println('variant.flavorName=' + variant.flavorName) // if (!android.defaultPublishConfig.startsWith(variant.flavorName)) { // return // } def hasFlavors = !variant.flavorName.isEmpty() def artifactIdSuffix = hasFlavors ? variant.flavorName.replace('_', '-').capitalize() : '' variant.productFlavors.each { flavor -> def flavorArtifactIdSuffix = flavor.ext.has('artifactIdSuffix') ? flavor.ext.artifactIdSuffix : flavor.name if (!flavorArtifactIdSuffix.isEmpty()) { artifactIdSuffix = artifactIdSuffix.replace(flavor.name.capitalize(), "-${flavorArtifactIdSuffix}") } else { artifactIdSuffix = artifactIdSuffix.replace(flavor.name.capitalize(), "") } } if (!artifactIdSuffix.isEmpty() && !artifactIdSuffix.startsWith('-')) { artifactIdSuffix = '-' + artifactIdSuffix } curArtifactId = "${POM_ARTIFACT_ID}${artifactIdSuffix}" def jniJar = task("${variant.name}JniJar", type: Jar, dependsOn: 'build') { } def javadoc = task("${variant.name}Javadoc", type: Javadoc) { source = variant.javaCompileProvider.get().source options.encoding = 'utf-8' destinationDir = file("${buildDir}/docs/javadoc${hasFlavors ? artifactIdSuffix : ""}") ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdk}/android.jar" classpath += files(ext.androidJar) classpath += files(variant.javaCompileProvider.get().classpath) classpath += files(configurations.javadocDeps) exclude '**/BuildConfig.java' exclude '**/R.java' failOnError = false } def javadocJar = task("${variant.name}JavadocJar", type: Jar, dependsOn: javadoc) { archiveClassifier = 'javadoc' from javadoc.destinationDir } def jniSymbolsJar = task("${variant.name}SymbolJar", type: Jar, dependsOn: 'build') { archiveClassifier = "so-symbols" boolean hasNativeBuildTask = false tasks.each { task -> if (task.getName().startsWith("externalNativeBuild")) { hasNativeBuildTask = true } } if (!hasNativeBuildTask) { return } if (hasFlavors) { variant.productFlavors.each { flavor -> from file("build/intermediates/cmake/${flavor.name}Release/obj/") } } else { from file("build/intermediates/cmake/release/obj/") } } // Custom task to package headers into a ZIP file def headerZip = task("${variant.name}Header", type: Zip, dependsOn: 'build') { archiveClassifier = "header" from('../../../Core/include') { exclude '*/MemoryFile.h' exclude '*/MMKVLog.h' } archiveFileName = 'headers.zip' destinationDirectory = file("$buildDir/outputs/headers") } def publicationName = "component${variant.name.capitalize()}" // Declare publications publishing.publications { "$publicationName"(MavenPublication) { groupId groupId artifactId curArtifactId version version from components.findByName("android${variant.name.capitalize()}") if (hasFlavors) { variant.productFlavors.each { flavor -> artifact("build/outputs/aar/${project.getName()}-${flavor.name}-release.aar") } } else { artifact("build/outputs/aar/${project.getName()}-release.aar") } artifact javadocJar artifact jniSymbolsJar artifact headerZip pom { name = 'MMKV' description = POM_DESCRIPTION url = POM_URL licenses { license { name = POM_LICENCE_NAME url = POM_LICENCE_URL } } developers { developer { id = POM_DEVELOPER_ID name = POM_DEVELOPER_NAME } } scm { url = POM_SCM_URL connection = POM_SCM_CONNECTION developerConnection = POM_SCM_DEV_CONNECTION } // write the dependency of the library into pom withXml { def dependenciesNode = asNode().appendNode('dependencies') project.configurations.all { configuration -> def name = configuration.name if (name != "implementation" && name != "compile" && name != "api") { return } configuration.dependencies.each { if (it.name == "unspecified") { return } def dependencyNode = dependenciesNode.appendNode('dependency') dependencyNode.appendNode('groupId', it.group) dependencyNode.appendNode('artifactId', it.name) dependencyNode.appendNode('version', it.version) if (name == "api" || name == "compile") { dependencyNode.appendNode("scope", "compile") } else { dependencyNode.appendNode("scope", "runtime") } } } } } } } } signing { sign publishing.publications } publishing.repositories { maven { url "${isReleaseBuild() ? getReleaseRepositoryUrl() : getSnapshotRepositoryUrl()}" credentials { username "${getRepositoryUsername()}" password "${getRepositoryPassword()}" } } } if (JavaVersion.current().isJava8Compatible()) { allprojects { tasks.withType(Javadoc) { options.addStringOption('Xdoclint:none', '-quiet') } } } task buildAndPublishToLocalMaven(type: Copy, dependsOn: ['build', 'publishToMavenLocal']) { group = 'publishing' // save artifacts files to artifacts folder from "$buildDir/outputs/aar" include '*.aar' into "${rootProject.buildDir}/outputs/artifacts" rename { String fileName -> fileName.replace("release.aar", "${version}.aar") } doLast { println "* published to maven local: ${project.group}:${project.name}:${project.version}" } } task buildAndPublishRepo(type: Copy, dependsOn: ['build', 'publish']) { group = "publishing" // save artifacts files to artifacts folder from configurations.archives.allArtifacts.files into "${rootProject.buildDir}/outputs/artifacts/" rename { String fileName -> fileName.replace("release.aar", "${version}.aar") } doLast { println "* published to repo: ${project.group}:${project.name}:${project.version}" } } //tasks.register('closeCentralRepository', Exec) { // // This task will only run if the 'centralUsername' property exists //// onlyIf { project.hasProperty('centralUsername') && project.hasProperty('centralToken') } // // // Define your namespace here // def namespace = "com.tencent" // <-- IMPORTANT: Replace with your actual namespace // // group = "publishing" // description = "Closes the repository on the Central Publisher Portal." // // commandLine "curl", "-X", "POST", // "-u", "${getRepositoryUsername()}:${getRepositoryPassword()}", // "https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/${namespace}" //} // //// Find your specific publish task and make it trigger the new task //// Replace 'publish' with the actual name of your main publishing task if it's different. //tasks.named('publish') { // finalizedBy 'closeCentralRepository' //} apply from: rootProject.file('gradle/check.gradle') ================================================ FILE: Android/MMKV/gradle/build_library.gradle ================================================ // OSS_ANDROID_TEMPLATE_FILE_HEADER /** * Optional android libraries gradle file * * Simply apply from: rootProject.file('gradle/build_library.gradle') if needed * */ apply plugin: 'com.android.library' android { compileSdk rootProject.ext.compileSdk defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion } signingConfigs { configDebug { keyAlias 'key0' keyPassword 'mmkv.wxg' storeFile rootProject.file('debug.keystore') storePassword 'mmkv.wxg' } configRelease { keyAlias 'key0' keyPassword 'mmkv.wxg' storeFile rootProject.file('debug.keystore') storePassword 'mmkv.wxg' } } buildTypes { debug { debuggable true signingConfig signingConfigs.configDebug minifyEnabled false } release { signingConfig signingConfigs.configRelease minifyEnabled true proguardFile getDefaultProguardFile('proguard-android.txt') proguardFile project.file('proguard-rules-android-lib.pro') // NOTE: default rules for android library if this file exists } } } // Publication version = rootProject.ext.VERSION_NAME group = rootProject.ext.GROUP //def check_private_only = project.hasProperty('PRIVATE_ONLY') ? PRIVATE_ONLY.toBoolean() : rootProject.ext.defaultsToPrivateOnly // Dangerous, turn on for default // //if (check_private_only) { // // Publish to private only repositories // apply from: rootProject.file('gradle/android-publish-private.gradle') // //} else { // // Publish to public repositories // apply from: rootProject.file('gradle/android-publish.gradle') //} ================================================ FILE: Android/MMKV/gradle/check.gradle ================================================ // OSS_ANDROID_TEMPLATE_FILE_HEADER apply plugin: 'checkstyle' checkstyle { configFile rootProject.file('checkstyle.xml') toolVersion '6.19' ignoreFailures false showViolations true } task('checkstyle', type: Checkstyle) { source 'src/main/java' include '**/*.java' classpath = files() } check.dependsOn('checkstyle') //apply plugin: 'pmd' // //pmd { // toolVersion '5.4.0' //} // //task pmd(type: Pmd) { // targetJdk = TargetJdk.VERSION_1_7 // // description 'Run pmd' // group 'verification' // // // If ruleSets is not empty, it seems to contain some // // defaults which override rules in the ruleset file... // ruleSets = [] // ruleSetFiles = rootProject.files('pmd-ruleset.xml') // source = fileTree('src/main/java') // ignoreFailures = false // // reports { // xml.enabled = false // html.enabled = true // } //} // //check.dependsOn('pmd') //apply plugin: 'findbugs' // //def classTree = 'build/intermediates/classes/debug' // //if (project.plugins.hasPlugin('java')) { // classTree = 'build/classes' //} //task findbugs(type: FindBugs) { // // description 'Run findbugs' // group 'verification' // // classes = fileTree(classTree) // source = fileTree('src/main/java/') // classpath = files() // // effort = 'default' // // excludeFilter = rootProject.file("findbugs-exclude.xml") // // reports { // xml.enabled = false // html.enabled = true // } // ignoreFailures = true //} //check.dependsOn('findbugs') ================================================ FILE: Android/MMKV/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Oct 16 21:16:39 CST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip ================================================ FILE: Android/MMKV/gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true VERSION_NAME_PREFIX=2.4.0 #VERSION_NAME_SUFFIX=-SNAPSHOT VERSION_NAME_SUFFIX= RELEASE_REPOSITORY_URL=https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2 SNAPSHOT_REPOSITORY_URL=https://ossrh-staging-api.central.sonatype.com/content/repositories/snapshots android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false android.nonFinalResIds=false ================================================ FILE: Android/MMKV/gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: Android/MMKV/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: Android/MMKV/mmkv/.gitignore ================================================ /build ================================================ FILE: Android/MMKV/mmkv/CMakeLists.txt ================================================ # For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html project(mmkv) # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.10.0) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. add_subdirectory(../../../Core Core) add_library( mmkv # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-bridge.cpp src/main/cpp/flutter-bridge.cpp ) set_target_properties(mmkv PROPERTIES CXX_STANDARD 20 CXX_EXTENSIONS OFF POSITION_INDEPENDENT_CODE ON ) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log ) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( mmkv # Links the target library to the log library # included in the NDK. ${log-lib} core # aes ) ================================================ FILE: Android/MMKV/mmkv/build.gradle ================================================ plugins { id 'com.android.library' id 'maven-publish' id 'signing' // id 'org.jetbrains.dokka' version '1.4.32' // For Kotlin projects } apply from: rootProject.file('gradle/build_library.gradle') android { namespace "com.tencent.mmkv" compileSdk rootProject.ext.compileSdk publishing { multipleVariants("DefaultCppRelease") { includeBuildTypeValues('release') includeFlavorDimensionAndValues('stl_mode', 'DefaultCpp') withJavadocJar() } multipleVariants("StaticCppRelease") { includeBuildTypeValues('release') includeFlavorDimensionAndValues('stl_mode', 'StaticCpp') withJavadocJar() } multipleVariants("SharedCppRelease") { includeBuildTypeValues('release') includeFlavorDimensionAndValues('stl_mode', 'SharedCpp') withJavadocJar() } } defaultConfig { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { abiFilters 'arm64-v8a', 'x86_64' } } ndkVersion "28.1.13356709" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion } buildTypes { release { minifyEnabled false externalNativeBuild { cmake { cppFlags "-fvisibility=hidden", "-funwind-tables", "-fasynchronous-unwind-tables", "-O2" } } consumerProguardFiles 'proguard-rules.pro' // debuggable true // jniDebuggable true } debug { jniDebuggable true } } externalNativeBuild { cmake { path "CMakeLists.txt" version "3.18.0+" } } packagingOptions { jniLibs { useLegacyPackaging true } } flavorDimensions = ["stl_mode"] productFlavors { DefaultCpp { dimension "stl_mode" ext.artifactIdSuffix = '' externalNativeBuild { cmake { arguments = ["-DANDROID_STL=c++_static"] cppFlags "-DMMKV_STL_SHARED=0" } } } StaticCpp { dimension "stl_mode" ext.artifactIdSuffix = 'static' externalNativeBuild { cmake { arguments = ["-DANDROID_STL=c++_static"] cppFlags "-DMMKV_STL_SHARED=0" } } // not working // packagingOptions { // exclude "prefab/modules/**" // } } SharedCpp { dimension "stl_mode" ext.artifactIdSuffix = 'shared' externalNativeBuild { cmake { arguments = ["-DANDROID_STL=c++_shared"] cppFlags "-DMMKV_STL_SHARED=1" } } } } buildFeatures { prefab true prefabPublishing true aidl true } prefab { mmkv { headers "../../../Core/include" } } } afterEvaluate { android.libraryVariants.configureEach { variant -> if (variant.buildType.name == "release") { def abiFilters = ['arm64-v8a', 'x86_64'] def strippedLibsDir = "$buildDir/intermediates/stripped_native_libs/${variant.name}/strip${variant.name}DebugSymbols/out/lib" // Create per-variant copy task tasks.register("syncStrippedLibsToPrefab${variant.name.capitalize()}", Copy) { from(strippedLibsDir) { include "**/libmmkv.so" } into "$buildDir/intermediates/prefab_package/${variant.name.capitalize()}/prefab/modules/mmkv/libs" eachFile { // println('hhh before:' + path) for (abi in abiFilters) { if (path.contains("${abi}/")) { path = path.replace("${abi}/", "android.${abi}/") break } } // println('hhh after:' + path) } // Hook into build process dependsOn("strip${variant.name.capitalize()}DebugSymbols") } // Hook into build process tasks.named("externalNativeBuild${variant.name.capitalize()}") { finalizedBy("syncStrippedLibsToPrefab${variant.name.capitalize()}") } tasks.named("bundle${variant.name.capitalize()}Aar") { dependsOn("syncStrippedLibsToPrefab${variant.name.capitalize()}") } tasks.named("bundle${variant.name.capitalize()}LocalLintAar") { dependsOn("syncStrippedLibsToPrefab${variant.name.capitalize()}") } // Generate combined artifact suffix from all flavor dimensions def flavorSuffixes = variant.productFlavors.collect { it.ext.artifactIdSuffix } def artifactIdSuffix = flavorSuffixes.join("-") def fullArtifactId = "${POM_ARTIFACT_ID}${artifactIdSuffix ? "-$artifactIdSuffix" : ""}" // 1. Find the task that merges native libs (unstripped). // Name pattern is typically: mergeReleaseNativeLibs or mergeFlavorReleaseNativeLibs def mergeTaskName = "merge${variant.name.capitalize()}NativeLibs" def mergeTask = tasks.findByName(mergeTaskName) if (!mergeTask) { println "Warning: Could not find Native Merge task: $mergeTaskName" return // Skip creating the symbols jar if we can't find the source } // Create debug symbols task def symbolsTask = tasks.register("bundleSymbols${variant.name.capitalize()}", Jar) { // 2. FIX: Don't use a hardcoded path string. // Use the output files of the merge task directly. from(mergeTask.outputs.files) // 3. Ensure we only grab the .so files (the merge task might output a tree structure like /lib/arm64/...) include '**/*.so' archiveClassifier.set("so-symbols") destinationDirectory.set(file("$buildDir/intermediates/so-symbols/${variant.name}")) dependsOn mergeTask } // Create publication per variant publishing.publications.create(variant.name, MavenPublication) { from components.findByName(variant.name) groupId = group artifactId = fullArtifactId version = version // Add debug symbols artifact symbolsTask // Configure POM pom { name = 'MMKV' description = POM_DESCRIPTION url = POM_URL licenses { license { name = POM_LICENCE_NAME url = POM_LICENCE_URL } } developers { developer { id = POM_DEVELOPER_ID name = POM_DEVELOPER_NAME } } scm { url = POM_SCM_URL connection = POM_SCM_CONNECTION developerConnection = POM_SCM_DEV_CONNECTION } } } } } // Configure repositories and signing publishing.repositories { maven { name = "MavenCentral" url = RELEASE_REPOSITORY_URL credentials { username = REPOSITORY_USERNAME password = REPOSITORY_PASSWORD } } } signing { // Ensure signing only targets publications that were successfully created sign publishing.publications.matching { it.name in android.libraryVariants.collect { v -> v.name } } } } configurations { javadocDeps } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.annotation:annotation:1.9.1' javadocDeps 'androidx.annotation:annotation:1.9.1' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.7.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' compileOnly project(':mmkvannotation') } ================================================ FILE: Android/MMKV/mmkv/gradle.properties ================================================ POM_ARTIFACT_ID=mmkv PRIVATE_ONLY=false ================================================ FILE: Android/MMKV/mmkv/proguard-rules.pro ================================================ # Keep all native methods, their classes and any classes in their descriptors -keepclasseswithmembers,includedescriptorclasses class com.tencent.mmkv.** { native ; long nativeHandle; private static *** onMMKVCRCCheckFail(***); private static *** onMMKVFileLengthError(***); private static *** mmkvLogImp(...); private static *** onContentChangedByOuterProcess(***); private static *** onMMKVContentLoadSuccessfully(***); } ================================================ FILE: Android/MMKV/mmkv/src/androidTest/AndroidManifest.xml ================================================ ================================================ FILE: Android/MMKV/mmkv/src/androidTest/java/com/tencent/mmkv/MMKVTest.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkv; import static org.junit.Assert.*; import android.content.Context; import android.content.Intent; import android.os.SystemClock; import androidx.test.InstrumentationRegistry; import java.util.HashSet; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; public class MMKVTest { static MMKV mmkv; static final String KeyNotExist = "Key_Not_Exist"; static final float Delta = 0.000001f; @BeforeClass public static void setUp() throws Exception { Context appContext = InstrumentationRegistry.getTargetContext(); MMKV.initialize(appContext); mmkv = MMKV.mmkvWithID("unitTest", MMKV.SINGLE_PROCESS_MODE, "UnitTestCryptKey"); } @AfterClass public static void tearDown() throws Exception { mmkv.clearAll(); } @Test public void testBool() { boolean ret = mmkv.encode("bool", true); assertEquals(ret, true); boolean value = mmkv.decodeBool("bool"); assertEquals(value, true); value = mmkv.decodeBool(KeyNotExist); assertEquals(value, false); value = mmkv.decodeBool(KeyNotExist, true); assertEquals(value, true); } @Test public void testInt() { boolean ret = mmkv.encode("int", Integer.MAX_VALUE); assertEquals(ret, true); int value = mmkv.decodeInt("int"); assertEquals(value, Integer.MAX_VALUE); value = mmkv.decodeInt(KeyNotExist); assertEquals(value, 0); value = mmkv.decodeInt(KeyNotExist, -1); assertEquals(value, -1); } @Test public void testLong() { boolean ret = mmkv.encode("long", Long.MAX_VALUE); assertEquals(ret, true); long value = mmkv.decodeLong("long"); assertEquals(value, Long.MAX_VALUE); value = mmkv.decodeLong(KeyNotExist); assertEquals(value, 0); value = mmkv.decodeLong(KeyNotExist, -1); assertEquals(value, -1); } @Test public void testFloat() { boolean ret = mmkv.encode("float", Float.MAX_VALUE); assertEquals(ret, true); float value = mmkv.decodeFloat("float"); assertEquals(value, Float.MAX_VALUE, Delta); value = mmkv.decodeFloat(KeyNotExist); assertEquals(value, 0, Delta); value = mmkv.decodeFloat(KeyNotExist, -1); assertEquals(value, -1, Delta); } @Test public void testDouble() { boolean ret = mmkv.encode("double", Double.MAX_VALUE); assertEquals(ret, true); double value = mmkv.decodeDouble("double"); assertEquals(value, Double.MAX_VALUE, Delta); value = mmkv.decodeDouble(KeyNotExist); assertEquals(value, 0, Delta); value = mmkv.decodeDouble(KeyNotExist, -1); assertEquals(value, -1, Delta); } @Test public void testString() { String str = "Hello 2018 world cup 世界杯"; boolean ret = mmkv.encode("string", str); assertEquals(ret, true); String value = mmkv.decodeString("string"); assertEquals(value, str); value = mmkv.decodeString(KeyNotExist); assertEquals(value, null); value = mmkv.decodeString(KeyNotExist, "Empty"); assertEquals(value, "Empty"); } @Test public void testStringSet() { HashSet set = new HashSet(); set.add("W"); set.add("e"); set.add("C"); set.add("h"); set.add("a"); set.add("t"); boolean ret = mmkv.encode("string_set", set); assertEquals(ret, true); HashSet value = (HashSet) mmkv.decodeStringSet("string_set"); assertEquals(value, set); value = (HashSet) mmkv.decodeStringSet(KeyNotExist); assertEquals(value, null); set = new HashSet(); set.add("W"); value = (HashSet) mmkv.decodeStringSet(KeyNotExist, set); assertEquals(value, set); } @Test public void testBytes() { byte[] bytes = {'m', 'm', 'k', 'v'}; boolean ret = mmkv.encode("bytes", bytes); assertEquals(ret, true); byte[] value = mmkv.decodeBytes("bytes"); assertArrayEquals(value, bytes); } @Test public void testRemove() { boolean ret = mmkv.encode("bool_1", true); ret &= mmkv.encode("int_1", Integer.MIN_VALUE); ret &= mmkv.encode("long_1", Long.MIN_VALUE); ret &= mmkv.encode("float_1", Float.MIN_VALUE); ret &= mmkv.encode("double_1", Double.MIN_VALUE); ret &= mmkv.encode("string_1", "hello"); HashSet set = new HashSet(); set.add("W"); set.add("e"); set.add("C"); set.add("h"); set.add("a"); set.add("t"); ret &= mmkv.encode("string_set_1", set); byte[] bytes = {'m', 'm', 'k', 'v'}; ret &= mmkv.encode("bytes_1", bytes); assertEquals(ret, true); { long count = mmkv.count(); mmkv.removeValueForKey("bool_1"); mmkv.removeValuesForKeys(new String[] {"int_1", "long_1"}); long newCount = mmkv.count(); assertEquals(count, newCount + 3); } boolean bValue = mmkv.decodeBool("bool_1"); assertEquals(bValue, false); int iValue = mmkv.decodeInt("int_1"); assertEquals(iValue, 0); long lValue = mmkv.decodeLong("long_1"); assertEquals(lValue, 0); float fValue = mmkv.decodeFloat("float_1"); assertEquals(fValue, Float.MIN_VALUE, Delta); double dValue = mmkv.decodeDouble("double_1"); assertEquals(dValue, Double.MIN_VALUE, Delta); String sValue = mmkv.decodeString("string_1"); assertEquals(sValue, "hello"); HashSet hashSet = (HashSet) mmkv.decodeStringSet("string_set_1"); assertEquals(hashSet, set); byte[] byteValue = mmkv.decodeBytes("bytes_1"); assertArrayEquals(bytes, byteValue); } @Test public void testIPCUpdateInt() { MMKV mmkv = MMKV.mmkvWithID(MMKVTestService.SharedMMKVID, MMKV.MULTI_PROCESS_MODE); mmkv.encode(MMKVTestService.SharedMMKVKey, 1024); Context appContext = InstrumentationRegistry.getTargetContext(); Intent intent = new Intent(appContext, MMKVTestService.class); intent.putExtra(MMKVTestService.CMD_Key, MMKVTestService.CMD_Update); appContext.startService(intent); SystemClock.sleep(1000 * 3); int value = mmkv.decodeInt(MMKVTestService.SharedMMKVKey); assertEquals(value, 1024 + 1); } @Test public void testIPCLock() { Context appContext = InstrumentationRegistry.getTargetContext(); Intent intent = new Intent(appContext, MMKVTestService.class); intent.putExtra(MMKVTestService.CMD_Key, MMKVTestService.CMD_Lock); appContext.startService(intent); SystemClock.sleep(1000 * 3); MMKV mmkv = MMKV.mmkvWithID(MMKVTestService.SharedMMKVID, MMKV.MULTI_PROCESS_MODE); boolean ret = mmkv.tryLock(); assertEquals(ret, false); intent.putExtra(MMKVTestService.CMD_Key, MMKVTestService.CMD_Kill); appContext.startService(intent); SystemClock.sleep(1000 * 3); ret = mmkv.tryLock(); assertEquals(ret, true); } } ================================================ FILE: Android/MMKV/mmkv/src/androidTest/java/com/tencent/mmkv/MMKVTestService.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkv; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.Process; import androidx.annotation.Nullable; public class MMKVTestService extends Service { public static final String SharedMMKVID = "SharedMMKVID"; public static final String SharedMMKVKey = "SharedMMKVKey"; public static final String CMD_Key = "CMD_Key"; public static final String CMD_Update = "CMD_Update"; public static final String CMD_Lock = "CMD_Lock"; public static final String CMD_Kill = "CMD_Kill"; @Override public void onCreate() { super.onCreate(); MMKV.initialize(this); } @Override public int onStartCommand(Intent intent, int flags, int startId) { MMKV mmkv = MMKV.mmkvWithID(SharedMMKVID, MMKV.MULTI_PROCESS_MODE); String cmd = intent.getStringExtra(CMD_Key); switch (cmd) { case CMD_Update: int value = mmkv.decodeInt(SharedMMKVKey); value += 1; mmkv.encode(SharedMMKVKey, value); break; case CMD_Lock: mmkv.lock(); break; case CMD_Kill: stopSelf(); break; } return START_NOT_STICKY; } @Override public void onDestroy() { super.onDestroy(); Process.killProcess(Process.myPid()); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } } ================================================ FILE: Android/MMKV/mmkv/src/main/AndroidManifest.xml ================================================ ================================================ FILE: Android/MMKV/mmkv/src/main/aidl/com/tencent/mmkv/ParcelableMMKV.aidl ================================================ // MMKV.aidl package com.tencent.mmkv; parcelable ParcelableMMKV; ================================================ FILE: Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2020 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. */ #include #ifndef MMKV_DISABLE_FLUTTER # include # include # include # include using namespace mmkv; using namespace std; namespace mmkv { extern int g_android_api; extern string g_android_tmpDir; } # ifdef MMKV_EXPORT # undef MMKV_EXPORT # endif # 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); using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); using ContenctChangeCallback_t = void (*)(const char *mmapID); 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) { return nullptr; } if (cacheDir) { g_android_tmpDir = string(cacheDir); } g_android_api = sdkInt; #ifdef MMKV_STL_SHARED MMKVInfo("current API level = %d, libc++_shared=%d", g_android_api, MMKV_STL_SHARED); #else MMKVInfo("current API level = %d, libc++_shared=?", g_android_api); #endif if (callback) { g_flutterHandler.logCallback = callback; MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, &g_flutterHandler); } else { MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel); } return (void *) MMKV::getRootDir().c_str(); } MMKV_EXPORT void mmkvInitialize_v1(const char *rootDir, const char *cacheDir, int32_t sdkInt, int32_t logLevel) { mmkvInitialize_v2(rootDir, cacheDir, sdkInt, logLevel, nullptr); } MMKV_EXPORT void mmkvInitialize(const char *rootDir, int32_t logLevel) { mmkvInitialize_v2(rootDir, nullptr, 0, logLevel, nullptr); } MMKV_EXPORT void *getMMKVWithID(const char *mmapID, int32_t mode, const char *cryptKey, const char *rootPath, size_t expectedCapacity, bool fromNameSpace, bool aes256, int32_t enableKeyExpire, int32_t expiredInSeconds, bool enableCompareBeforeSet, int32_t recover, uint32_t itemSizeLimit) { MMKV *kv = nullptr; if (!mmapID) { return kv; } string str = mmapID; auto config = MMKVConfig(); config.mode = (MMKVMode) mode; config.aes256 = aes256; config.expectedCapacity = expectedCapacity; if (enableKeyExpire >= 0) { config.enableKeyExpire = (enableKeyExpire != 0); } config.expiredInSeconds = expiredInSeconds; config.enableCompareBeforeSet = enableCompareBeforeSet; if (recover >= 0) { config.recover = static_cast(recover); } config.itemSizeLimit = itemSizeLimit; bool done = false; if (cryptKey) { string crypt = cryptKey; if (!crypt.empty()) { config.cryptKey = &crypt; if (rootPath) { string path = rootPath; if (fromNameSpace) { auto ns = MMKV::nameSpace(path); config.rootPath = &ns.getRootDir(); kv = ns.mmkvWithID(str, config); } else { config.rootPath = &path; kv = MMKV::mmkvWithID(str, config); } } else { kv = MMKV::mmkvWithID(str, config); } done = true; } } if (!done) { if (rootPath) { string path = rootPath; if (fromNameSpace) { auto ns = MMKV::nameSpace(path); config.rootPath = &ns.getRootDir(); kv = ns.mmkvWithID(str, config); } else { config.rootPath = &path; kv = MMKV::mmkvWithID(str, config); } } else { kv = MMKV::mmkvWithID(str, config); } } return kv; } MMKV_EXPORT void *getDefaultMMKV(int32_t mode, const char *cryptKey, bool aes256, size_t expectedCapacity, int32_t enableKeyExpire, int32_t expiredInSeconds, bool enableCompareBeforeSet, int32_t recover, uint32_t itemSizeLimit) { MMKV *kv = nullptr; auto config = MMKVConfig(); config.mode = (MMKVMode) mode; config.aes256 = aes256; config.expectedCapacity = expectedCapacity; if (enableKeyExpire >= 0) { config.enableKeyExpire = (enableKeyExpire != 0); } config.expiredInSeconds = expiredInSeconds; config.enableCompareBeforeSet = enableCompareBeforeSet; if (recover >= 0) { config.recover = static_cast(recover); } config.itemSizeLimit = itemSizeLimit; if (cryptKey) { string crypt = cryptKey; if (crypt.length() > 0) { config.cryptKey = &crypt; kv = MMKV::defaultMMKV(config); } } if (!kv) { kv = MMKV::defaultMMKV(config); } return kv; } MMKV_EXPORT const char *mmapID(void *handle) { MMKV *kv = static_cast(handle); if (kv) { return kv->mmapID().c_str(); } return nullptr; } MMKV_EXPORT bool encodeBool(void *handle, const char *oKey, bool value) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->set((bool) value, key); } return false; } MMKV_EXPORT bool encodeBool_v2(void *handle, const char *oKey, bool value, uint32_t expiration) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->set((bool) value, key, expiration); } return false; } MMKV_EXPORT bool decodeBool(void *handle, const char *oKey, bool defaultValue) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->getBool(key, defaultValue); } return defaultValue; } MMKV_EXPORT bool encodeInt32(void *handle, const char *oKey, int32_t value) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->set((int32_t) value, key); } return false; } MMKV_EXPORT bool encodeInt32_v2(void *handle, const char *oKey, int32_t value, uint32_t expiration) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->set((int32_t) value, key, expiration); } return false; } MMKV_EXPORT int32_t decodeInt32(void *handle, const char *oKey, int32_t defaultValue) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->getInt32(key, defaultValue); } return defaultValue; } MMKV_EXPORT bool encodeInt64(void *handle, const char *oKey, int64_t value) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->set((int64_t) value, key); } return false; } MMKV_EXPORT bool encodeInt64_v2(void *handle, const char *oKey, int64_t value, uint32_t expiration) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->set((int64_t) value, key, expiration); } return false; } MMKV_EXPORT int64_t decodeInt64(void *handle, const char *oKey, int64_t defaultValue) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->getInt64(key, defaultValue); } return defaultValue; } MMKV_EXPORT bool encodeDouble(void *handle, const char *oKey, double value) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->set((double) value, key); } return false; } MMKV_EXPORT bool encodeDouble_v2(void *handle, const char *oKey, double value, uint32_t expiration) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->set((double) value, key, expiration); } return false; } MMKV_EXPORT double decodeDouble(void *handle, const char *oKey, double defaultValue) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); return kv->getDouble(key, defaultValue); } return defaultValue; } MMKV_EXPORT bool encodeBytes(void *handle, const char *oKey, void *oValue, uint64_t length) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); if (oValue) { auto value = MMBuffer(oValue, static_cast(length), MMBufferNoCopy); return kv->set(value, key); } else { kv->removeValueForKey(key); return true; } } return false; } MMKV_EXPORT bool encodeBytes_v2(void *handle, const char *oKey, void *oValue, uint64_t length, uint32_t expiration) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); if (oValue) { auto value = MMBuffer(oValue, static_cast(length), MMBufferNoCopy); return kv->set(value, key, expiration); } else { kv->removeValueForKey(key); return true; } } return false; } MMKV_EXPORT void *decodeBytes(void *handle, const char *oKey, uint64_t *lengthPtr) { MMKV *kv = static_cast(handle); if (kv && oKey) { auto key = string(oKey); mmkv::MMBuffer value; auto hasValue = kv->getBytes(key, value); if (hasValue) { if (value.length() > 0) { if (value.isStoredOnStack()) { auto result = malloc(value.length()); if (result) { memcpy(result, value.getPtr(), value.length()); *lengthPtr = value.length(); } return result; } void *result = value.getPtr(); *lengthPtr = value.length(); value.detach(); return result; } *lengthPtr = 0; // this ptr is intended for checking existence of the value // don't free this ptr return value.getPtr(); } } return nullptr; } # ifndef MMKV_DISABLE_CRYPT MMKV_EXPORT bool reKey(void *handle, char *oKey, uint64_t length, bool aes256) { MMKV *kv = static_cast(handle); if (kv) { if (oKey && length > 0) { string key(oKey, length); return kv->reKey(key, aes256); } else { return kv->reKey(string(), aes256); } } return false; } MMKV_EXPORT void *cryptKey(void *handle, uint64_t *lengthPtr) { MMKV *kv = static_cast(handle); if (kv && lengthPtr) { auto cryptKey = kv->cryptKey(); if (cryptKey.length() > 0) { auto ptr = malloc(cryptKey.length()); if (ptr) { memcpy(ptr, cryptKey.data(), cryptKey.length()); *lengthPtr = cryptKey.length(); return ptr; } } } return nullptr; } MMKV_EXPORT void checkReSetCryptKey(void *handle, char *oKey, uint64_t length, bool aes256) { MMKV *kv = static_cast(handle); if (kv) { if (oKey && length > 0) { string key(oKey, length); kv->checkReSetCryptKey(&key, aes256); } else { kv->checkReSetCryptKey(nullptr, aes256); } } } # endif // MMKV_DISABLE_CRYPT MMKV_EXPORT uint32_t valueSize(void *handle, char *oKey, bool actualSize) { MMKV *kv = static_cast(handle); if (kv && oKey) { string key(oKey); auto ret = kv->getValueSize(key, actualSize); return static_cast(ret); } return 0; } MMKV_EXPORT int32_t writeValueToNB(void *handle, char *oKey, void *pointer, uint32_t size) { MMKV *kv = static_cast(handle); if (kv && oKey) { string key(oKey); return kv->writeValueToBuffer(key, pointer, size); } return -1; } MMKV_EXPORT uint64_t allKeys(void *handle, char ***keyArrayPtr, uint32_t **sizeArrayPtr, bool filterExpire) { MMKV *kv = static_cast(handle); if (kv) { auto keys = kv->allKeys(filterExpire); if (!keys.empty()) { auto keyArray = (char **) malloc(keys.size() * sizeof(void *)); auto sizeArray = (uint32_t *) malloc(keys.size() * sizeof(uint32_t *)); if (!keyArray || !sizeArray) { free(keyArray); free(sizeArray); return 0; } *keyArrayPtr = keyArray; *sizeArrayPtr = sizeArray; for (size_t index = 0; index < keys.size(); index++) { auto &key = keys[index]; sizeArray[index] = static_cast(key.length()); keyArray[index] = (char *) malloc(key.length()); if (keyArray[index]) { memcpy(keyArray[index], key.data(), key.length()); } } } return keys.size(); } return 0; } MMKV_EXPORT bool containsKey(void *handle, char *oKey) { MMKV *kv = static_cast(handle); if (kv && oKey) { string key(oKey); return kv->containsKey(key); } return false; } MMKV_EXPORT uint64_t count(void *handle, bool filterExpire) { MMKV *kv = static_cast(handle); if (kv) { return kv->count(filterExpire); } return 0; } MMKV_EXPORT uint64_t totalSize(void *handle) { MMKV *kv = static_cast(handle); if (kv) { return kv->totalSize(); } return 0; } MMKV_EXPORT uint64_t actualSize(void *handle) { MMKV *kv = static_cast(handle); if (kv) { return kv->actualSize(); } return 0; } MMKV_EXPORT void removeValueForKey(void *handle, char *oKey) { MMKV *kv = static_cast(handle); if (kv && oKey) { string key(oKey); kv->removeValueForKey(key); } } MMKV_EXPORT void removeValuesForKeys(void *handle, char **keyArray, uint32_t *sizeArray, uint64_t count) { MMKV *kv = static_cast(handle); if (kv && keyArray && sizeArray && count > 0) { vector arrKeys; arrKeys.reserve(count); for (uint64_t index = 0; index < count; index++) { if (sizeArray[index] > 0 && keyArray[index]) { arrKeys.emplace_back(keyArray[index], sizeArray[index]); } } if (!arrKeys.empty()) { kv->removeValuesForKeys(arrKeys); } } } MMKV_EXPORT void clearAll(void *handle, bool keepSpace) { MMKV *kv = static_cast(handle); if (kv) { kv->clearAll(keepSpace); } } MMKV_EXPORT void mmkvSync(void *handle, bool sync) { MMKV *kv = static_cast(handle); if (kv) { kv->sync((SyncFlag) sync); } } MMKV_EXPORT void clearMemoryCache(void *handle) { MMKV *kv = static_cast(handle); if (kv) { kv->clearMemoryCache(); } } MMKV_EXPORT int32_t pageSize() { return static_cast(DEFAULT_MMAP_SIZE); } MMKV_EXPORT const char *version() { return MMKV_VERSION; } MMKV_EXPORT void trim(void *handle) { MMKV *kv = static_cast(handle); if (kv) { kv->trim(); } } MMKV_EXPORT void mmkvClose(void *handle) { MMKV *kv = static_cast(handle); if (kv) { kv->close(); } } MMKV_EXPORT void mmkvMemcpy(void *dst, const void *src, uint64_t size) { memcpy(dst, src, size); } MMKV_EXPORT bool backupOne(const char *mmapID, const char *dstDir, const char *rootPath) { if (rootPath) { auto root = string(rootPath); if (root.length() > 0) { return MMKV::backupOneToDirectory(mmapID, dstDir, &root); } } return MMKV::backupOneToDirectory(mmapID, dstDir); } MMKV_EXPORT bool restoreOne(const char *mmapID, const char *srcDir, const char *rootPath) { if (rootPath) { auto root = string(rootPath); if (root.length() > 0) { return MMKV::restoreOneFromDirectory(mmapID, srcDir, &root); } } return MMKV::restoreOneFromDirectory(mmapID, srcDir); } MMKV_EXPORT uint64_t backupAll(const char *dstDir/*, const char *rootPath*/) { // historically Android mistakenly use mmapKey as mmapID // makes everything tricky with customize root /*if (rootPath) { auto root = string(rootPath); if (root.length() > 0) { return MMKV::backupAllToDirectory(dstDir, &root); } }*/ return MMKV::backupAllToDirectory(dstDir); } MMKV_EXPORT uint64_t restoreAll(const char *srcDir/*, const char *rootPath*/) { // historically Android mistakenly use mmapKey as mmapID // makes everything tricky with customize root /*if (rootPath) { auto root = string(rootPath); if (root.length() > 0) { return MMKV::restoreAllFromDirectory(srcDir, &root); } }*/ return MMKV::restoreAllFromDirectory(srcDir); } MMKV_EXPORT bool enableAutoExpire(void *handle, uint32_t expireDuration) { MMKV *kv = static_cast(handle); if (kv) { return kv->enableAutoKeyExpire(expireDuration); } return false; } MMKV_EXPORT bool disableAutoExpire(void *handle) { MMKV *kv = static_cast(handle); if (kv) { return kv->disableAutoKeyExpire(); } return false; } MMKV_EXPORT bool enableCompareBeforeSet(void *handle) { MMKV *kv = static_cast(handle); if (kv) { return kv->enableCompareBeforeSet(); } return false; } MMKV_EXPORT bool disableCompareBeforeSet(void *handle) { MMKV *kv = static_cast(handle); if (kv) { return kv->disableCompareBeforeSet(); } return false; } MMKV_EXPORT bool isFileValid(const char *mmapID, const char *rootPath) { if (rootPath) { auto root = string(rootPath); if (root.length() > 0) { return MMKV::isFileValid(mmapID, &root); } } return MMKV::isFileValid(mmapID, nullptr); } MMKV_EXPORT bool removeStorage(const char *mmapID, const char *rootPath) { if (rootPath) { auto root = string(rootPath); if (root.length() > 0) { return MMKV::removeStorage(mmapID, &root); } } return MMKV::removeStorage(mmapID, nullptr); } MMKV_EXPORT bool isMultiProcess(void *handle) { MMKV *kv = static_cast(handle); if (kv) { return kv->isMultiProcess(); } return false; } MMKV_EXPORT bool isReadOnly(void *handle) { MMKV *kv = static_cast(handle); if (kv) { return kv->isReadOnly(); } return false; } MMKV_EXPORT void registerErrorHandler(ErrorCallback_t callback) { 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_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); } } MMKV_EXPORT void checkContentChanged(void *handle) { MMKV *kv = static_cast(handle); if (kv) { kv->checkContentChanged(); } } MMKV_EXPORT bool getNameSpace(const char *rootPath) { if (rootPath) { auto root = string(rootPath); if (!root.empty()) { MMKV::nameSpace(root); return true; } } return false; } MMKV_EXPORT bool checkExist(const char *mmapID, const char *rootPath) { if (rootPath) { auto root = string(rootPath); if (root.length() > 0) { return MMKV::checkExist(mmapID, &root); } } return MMKV::checkExist(mmapID, nullptr); } MMKV_EXPORT size_t importFrom(void *handle, void *srcHandle) { MMKV *kv = static_cast(handle); MMKV *kvSrc = static_cast(srcHandle); if (kv && kvSrc) { return kv->importFrom(kvSrc); } return 0; } #endif // MMKV_DISABLE_FLUTTER ================================================ FILE: Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ #include #ifdef MMKV_ANDROID # include # include # include # include # include # include # include # include using namespace std; using namespace mmkv; static jclass g_cls = nullptr; static jfieldID g_fileID = nullptr; 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); extern "C" void internalLogWithLevel(MMKVLogLevel level, const char *filename, const char *func, int line, const char *format, ...); extern MMKVLogLevel g_currentLogLevel; namespace mmkv { static void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message); 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, ...) \ internalLogWithLevel(MMKV_NAMESPACE_PREFIX::MMKVLogError, __MMKV_FILE_NAME__, __func__, __LINE__, format, ##__VA_ARGS__) #define InternalLogInfo(format, ...) \ internalLogWithLevel(MMKV_NAMESPACE_PREFIX::MMKVLogInfo, __MMKV_FILE_NAME__, __func__, __LINE__, format, ##__VA_ARGS__) extern "C" JNIEXPORT JNICALL jint JNI_OnLoad(JavaVM *vm, void *reserved) { g_currentJVM = vm; JNIEnv *env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } if (g_cls) { env->DeleteGlobalRef(g_cls); } static const char *clsName = "com/tencent/mmkv/MMKV"; jclass instance = env->FindClass(clsName); if (!instance) { MMKVError("fail to locate class: %s", clsName); return -2; } g_cls = reinterpret_cast(env->NewGlobalRef(instance)); if (!g_cls) { MMKVError("fail to create global reference for %s", clsName); return -3; } g_mmkvLogID = env->GetStaticMethodID(g_cls, "mmkvLogImp", "(ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;)V"); if (!g_mmkvLogID) { MMKVError("fail to get method id for mmkvLogImp"); } // every code from now on can use InternalLogXXX() int ret = registerNativeMethods(env, g_cls); if (ret != 0) { InternalLogError("fail to register native methods for class %s, ret = %d", clsName, ret); return -4; } g_fileID = env->GetFieldID(g_cls, "nativeHandle", "J"); if (!g_fileID) { InternalLogError("fail to locate fileID"); return -5; } g_callbackOnCRCFailID = env->GetStaticMethodID(g_cls, "onMMKVCRCCheckFail", "(Ljava/lang/String;)I"); if (!g_callbackOnCRCFailID) { InternalLogError("fail to get method id for onMMKVCRCCheckFail"); } g_callbackOnFileLengthErrorID = env->GetStaticMethodID(g_cls, "onMMKVFileLengthError", "(Ljava/lang/String;)I"); if (!g_callbackOnFileLengthErrorID) { InternalLogError("fail to get method id for onMMKVFileLengthError"); } g_callbackOnContentChange = env->GetStaticMethodID(g_cls, "onContentChangedByOuterProcess", "(Ljava/lang/String;)V"); 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(); #ifdef MMKV_STL_SHARED InternalLogInfo("current API level = %d, libc++_shared=%d", g_android_api, MMKV_STL_SHARED); #else InternalLogInfo("current API level = %d, libc++_shared=?", g_android_api); #endif return JNI_VERSION_1_6; } //#define MMKV_JNI extern "C" JNIEXPORT JNICALL # define MMKV_JNI static namespace mmkv { static string jstring2string(JNIEnv *env, jstring str); 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) { 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::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 || logReDirecting == JNI_TRUE) { MMKV::registerHandler(&g_jniHandler); } else { MMKV::unRegisterHandler(); } } } MMKV_JNI void onExit(JNIEnv *env, jobject obj) { MMKV::onExit(); } static MMKV *getMMKV(JNIEnv *env, jobject obj) { jlong handle = env->GetLongField(obj, g_fileID); return reinterpret_cast(handle); } static string jstring2string(JNIEnv *env, jstring str) { if (str) { const char *kstr = env->GetStringUTFChars(str, nullptr); if (kstr) { string result(kstr); env->ReleaseStringUTFChars(str, kstr); return result; } } return ""; } static jstring string2jstring(JNIEnv *env, const string &str) { return env->NewStringUTF(str.c_str()); } static vector jarray2vector(JNIEnv *env, jobjectArray array) { vector keys; if (array) { jsize size = env->GetArrayLength(array); keys.reserve(size); for (jsize i = 0; i < size; i++) { jstring str = (jstring) env->GetObjectArrayElement(array, i); if (str) { keys.push_back(jstring2string(env, str)); env->DeleteLocalRef(str); } } } return keys; } static jobjectArray vector2jarray(JNIEnv *env, const vector &arr) { jobjectArray result = env->NewObjectArray(arr.size(), env->FindClass("java/lang/String"), nullptr); if (result) { for (size_t index = 0; index < arr.size(); index++) { jstring value = string2jstring(env, arr[index]); env->SetObjectArrayElement(result, index, value); env->DeleteLocalRef(value); } } return result; } static JNIEnv *getCurrentEnv() { if (g_currentJVM) { JNIEnv *currentEnv = nullptr; auto ret = g_currentJVM->GetEnv(reinterpret_cast(¤tEnv), JNI_VERSION_1_6); if (ret == JNI_OK) { return currentEnv; } else { MMKVError("fail to get current JNIEnv: %d", ret); } } return nullptr; } extern "C" void internalLogWithLevel(MMKVLogLevel level, const char *filename, const char *func, int line, const char *format, ...) { if (level >= g_currentLogLevel) { std::string message; char buffer[16]; va_list args; va_start(args, format); auto length = std::vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); if (length < 0) { // something wrong message = {}; } else if (length < sizeof(buffer)) { message = std::string(buffer, static_cast(length)); } else { message.resize(static_cast(length), '\0'); va_start(args, format); std::vsnprintf(const_cast(message.data()), static_cast(length) + 1, format, args); va_end(args); } if (g_cls && g_mmkvLogID) { mmkvLog(level, filename, line, func, message); } else { _MMKVLogWithLevel(level, filename, func, line, message.c_str()); } } } static void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) { auto currentEnv = getCurrentEnv(); if (currentEnv && g_mmkvLogID) { jstring oFile = string2jstring(currentEnv, string(file)); jstring oFunction = string2jstring(currentEnv, string(function)); jstring oMessage = string2jstring(currentEnv, message); int readLevel = level; currentEnv->CallStaticVoidMethod(g_cls, g_mmkvLogID, readLevel, oFile, line, oFunction, oMessage); currentEnv->DeleteLocalRef(oMessage); currentEnv->DeleteLocalRef(oFunction); currentEnv->DeleteLocalRef(oFile); } } static void androidLogWrapper(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) { g_androidLogHandler(level, file, line, function, message.c_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) { MMKV *kv = nullptr; if (!mmapID) { return (jlong) kv; } string str = jstring2string(env, mmapID); auto config = MMKVConfig(); config.mode = (MMKVMode) mode; config.aes256 = aes256; config.expectedCapacity = expectedCapacity; if (enableKeyExpire >= 0) { config.enableKeyExpire = (enableKeyExpire != 0); } config.expiredInSeconds = expiredInSeconds; config.enableCompareBeforeSet = enableCompareBeforeSet; if (recover >= 0) { config.recover = static_cast(recover); } config.itemSizeLimit = itemSizeLimit; bool done = false; if (cryptKey) { string crypt = jstring2string(env, cryptKey); if (crypt.length() > 0) { config.cryptKey = &crypt; if (rootPath) { string path = jstring2string(env, rootPath); config.rootPath = &path; kv = MMKV::mmkvWithID(str, config); } else { kv = MMKV::mmkvWithID(str, config); } done = true; } } if (!done) { if (rootPath) { string path = jstring2string(env, rootPath); config.rootPath = &path; kv = MMKV::mmkvWithID(str, config); } else { kv = MMKV::mmkvWithID(str, config); } } return (jlong) kv; } MMKV_JNI jlong getDefaultMMKV(JNIEnv *env, jobject obj, jint mode, jstring cryptKey, jlong expectedCapacity, jboolean aes256, jint enableKeyExpire, jint expiredInSeconds, jboolean enableCompareBeforeSet, jint recover, jint itemSizeLimit) { MMKV *kv = nullptr; auto config = MMKVConfig(); config.mode = (MMKVMode) mode; config.aes256 = aes256; config.expectedCapacity = expectedCapacity; if (enableKeyExpire >= 0) { config.enableKeyExpire = (enableKeyExpire != 0); } config.expiredInSeconds = expiredInSeconds; config.enableCompareBeforeSet = enableCompareBeforeSet; if (recover >= 0) { config.recover = static_cast(recover); } config.itemSizeLimit = itemSizeLimit; if (cryptKey) { string crypt = jstring2string(env, cryptKey); if (crypt.length() > 0) { config.cryptKey = &crypt; kv = MMKV::defaultMMKV(config); } } if (!kv) { kv = MMKV::defaultMMKV(config); } return (jlong) kv; } MMKV_JNI jlong getMMKVWithAshmemFD(JNIEnv *env, jobject obj, jstring mmapID, jint fd, jint metaFD, jstring cryptKey, jboolean aes256) { MMKV *kv = nullptr; if (!mmapID || fd < 0 || metaFD < 0) { return (jlong) kv; } string id = jstring2string(env, mmapID); if (cryptKey) { string crypt = jstring2string(env, cryptKey); if (crypt.length() > 0) { kv = MMKV::mmkvWithAshmemFD(id, fd, metaFD, &crypt, aes256); } } if (!kv) { kv = MMKV::mmkvWithAshmemFD(id, fd, metaFD, nullptr, aes256); } return (jlong) kv; } MMKV_JNI jstring mmapID(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { return string2jstring(env, kv->mmapID()); } return nullptr; } MMKV_JNI jint ashmemFD(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { return kv->ashmemFD(); } return -1; } MMKV_JNI jint ashmemMetaFD(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { return kv->ashmemMetaFD(); } return -1; } MMKV_JNI jboolean checkProcessMode(JNIEnv *env, jobject, jlong handle) { MMKV *kv = reinterpret_cast(handle); if (kv) { return kv->checkProcessMode(); } return false; } MMKV_JNI jboolean encodeBool(JNIEnv *env, jobject, jlong handle, jstring oKey, jboolean value) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->set((bool) value, key); } return (jboolean) false; } MMKV_JNI jboolean encodeBool_2(JNIEnv *env, jobject, jlong handle, jstring oKey, jboolean value, jint expiration) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->set((bool) value, key, (uint32_t) expiration); } return (jboolean) false; } MMKV_JNI jboolean decodeBool(JNIEnv *env, jobject, jlong handle, jstring oKey, jboolean defaultValue) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->getBool(key, defaultValue); } return defaultValue; } MMKV_JNI jboolean encodeInt(JNIEnv *env, jobject obj, jlong handle, jstring oKey, jint value) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->set((int32_t) value, key); } return (jboolean) false; } MMKV_JNI jboolean encodeInt_2(JNIEnv *env, jobject obj, jlong handle, jstring oKey, jint value, jint expiration) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->set((int32_t) value, key, (uint32_t) expiration); } return (jboolean) false; } MMKV_JNI jint decodeInt(JNIEnv *env, jobject obj, jlong handle, jstring oKey, jint defaultValue) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jint) kv->getInt32(key, defaultValue); } return defaultValue; } MMKV_JNI jboolean encodeLong(JNIEnv *env, jobject obj, jlong handle, jstring oKey, jlong value) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->set((int64_t) value, key); } return (jboolean) false; } MMKV_JNI jboolean encodeLong_2(JNIEnv *env, jobject obj, jlong handle, jstring oKey, jlong value, jint expiration) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->set((int64_t) value, key, (uint32_t) expiration); } return (jboolean) false; } MMKV_JNI jlong decodeLong(JNIEnv *env, jobject obj, jlong handle, jstring oKey, jlong defaultValue) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jlong) kv->getInt64(key, defaultValue); } return defaultValue; } MMKV_JNI jboolean encodeFloat(JNIEnv *env, jobject obj, jlong handle, jstring oKey, jfloat value) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->set((float) value, key); } return (jboolean) false; } MMKV_JNI jboolean encodeFloat_2(JNIEnv *env, jobject obj, jlong handle, jstring oKey, jfloat value, jint expiration) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->set((float) value, key, (uint32_t) expiration); } return (jboolean) false; } MMKV_JNI jfloat decodeFloat(JNIEnv *env, jobject, jlong handle, jstring oKey, jfloat defaultValue) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jfloat) kv->getFloat(key, defaultValue); } return defaultValue; } MMKV_JNI jboolean encodeDouble(JNIEnv *env, jobject obj, jlong handle, jstring oKey, jdouble value) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->set((double) value, key); } return (jboolean) false; } MMKV_JNI jboolean encodeDouble_2(JNIEnv *env, jobject obj, jlong handle, jstring oKey, jdouble value, jint expiration) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->set((double) value, key, (uint32_t) expiration); } return (jboolean) false; } MMKV_JNI jdouble decodeDouble(JNIEnv *env, jobject, jlong handle, jstring oKey, jdouble defaultValue) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jdouble) kv->getDouble(key, defaultValue); } return defaultValue; } MMKV_JNI jboolean encodeString(JNIEnv *env, jobject, jlong handle, jstring oKey, jstring oValue) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); if (oValue) { string value = jstring2string(env, oValue); return (jboolean) kv->set(value, key); } else { kv->removeValueForKey(key); return (jboolean) true; } } return (jboolean) false; } MMKV_JNI jboolean encodeString_2(JNIEnv *env, jobject, jlong handle, jstring oKey, jstring oValue, jint expiration) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); if (oValue) { string value = jstring2string(env, oValue); return (jboolean) kv->set(value, key, (uint32_t) expiration); } else { kv->removeValueForKey(key); return (jboolean) true; } } return (jboolean) false; } MMKV_JNI jstring decodeString(JNIEnv *env, jobject obj, jlong handle, jstring oKey, jstring oDefaultValue) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); string value; bool hasValue = kv->getString(key, value); if (hasValue) { return string2jstring(env, value); } } return oDefaultValue; } MMKV_JNI jboolean encodeBytes(JNIEnv *env, jobject, jlong handle, jstring oKey, jbyteArray oValue) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); if (oValue) { MMBuffer value(0); { jsize len = env->GetArrayLength(oValue); void *bufferPtr = env->GetPrimitiveArrayCritical(oValue, nullptr); if (bufferPtr) { value = MMBuffer(bufferPtr, len); env->ReleasePrimitiveArrayCritical(oValue, bufferPtr, JNI_ABORT); } else { MMKVError("fail to get array: %s=%p", key.c_str(), oValue); } } return (jboolean) kv->set(value, key); } else { kv->removeValueForKey(key); return (jboolean) true; } } return (jboolean) false; } MMKV_JNI jboolean encodeBytes_2(JNIEnv *env, jobject, jlong handle, jstring oKey, jbyteArray oValue, jint expiration) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); if (oValue) { MMBuffer value(0); { jsize len = env->GetArrayLength(oValue); void *bufferPtr = env->GetPrimitiveArrayCritical(oValue, nullptr); if (bufferPtr) { value = MMBuffer(bufferPtr, len); env->ReleasePrimitiveArrayCritical(oValue, bufferPtr, JNI_ABORT); } else { MMKVError("fail to get array: %s=%p", key.c_str(), oValue); } } return (jboolean) kv->set(value, key, (uint32_t) expiration); } else { kv->removeValueForKey(key); return (jboolean) true; } } return (jboolean) false; } MMKV_JNI jbyteArray decodeBytes(JNIEnv *env, jobject obj, jlong handle, jstring oKey) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); mmkv::MMBuffer value; auto hasValue = kv->getBytes(key, value); if (hasValue) { jbyteArray result = env->NewByteArray(value.length()); env->SetByteArrayRegion(result, 0, value.length(), (const jbyte *) value.getPtr()); return result; } } return nullptr; } MMKV_JNI jobjectArray allKeys(JNIEnv *env, jobject instance, jlong handle, jboolean filterExpire) { MMKV *kv = reinterpret_cast(handle); if (kv) { vector keys = kv->allKeys((bool) filterExpire); return vector2jarray(env, keys); } return nullptr; } MMKV_JNI jboolean containsKey(JNIEnv *env, jobject instance, jlong handle, jstring oKey) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return (jboolean) kv->containsKey(key); } return (jboolean) false; } MMKV_JNI jlong count(JNIEnv *env, jobject instance, jlong handle, jboolean filterExpire) { MMKV *kv = reinterpret_cast(handle); if (kv) { jlong size = kv->count((bool) filterExpire); return size; } return 0; } MMKV_JNI jlong totalSize(JNIEnv *env, jobject instance, jlong handle) { MMKV *kv = reinterpret_cast(handle); if (kv) { jlong size = kv->totalSize(); return size; } return 0; } MMKV_JNI jlong actualSize(JNIEnv *env, jobject instance, jlong handle) { MMKV *kv = reinterpret_cast(handle); if (kv) { jlong size = kv->actualSize(); return size; } return 0; } MMKV_JNI void removeValueForKey(JNIEnv *env, jobject instance, jlong handle, jstring oKey) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); kv->removeValueForKey(key); } } MMKV_JNI void removeValuesForKeys(JNIEnv *env, jobject instance, jobjectArray arrKeys) { MMKV *kv = getMMKV(env, instance); if (kv && arrKeys) { vector keys = jarray2vector(env, arrKeys); if (!keys.empty()) { kv->removeValuesForKeys(keys); } } } MMKV_JNI void clearAll(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { kv->clearAll(); } } MMKV_JNI void sync(JNIEnv *env, jobject instance, jboolean sync) { MMKV *kv = getMMKV(env, instance); if (kv) { kv->sync((SyncFlag) sync); } } MMKV_JNI jboolean isFileValid(JNIEnv *env, jclass type, jstring oMmapID, jstring rootPath) { if (oMmapID) { string mmapID = jstring2string(env, oMmapID); if (!rootPath) { return (jboolean) MMKV::isFileValid(mmapID, nullptr); } else { auto root = jstring2string(env, rootPath); return (jboolean) MMKV::isFileValid(mmapID, &root); } } return (jboolean) false; } MMKV_JNI jboolean removeStorage(JNIEnv *env, jclass type, jstring oMmapID, jstring rootPath) { if (oMmapID) { string mmapID = jstring2string(env, oMmapID); if (!rootPath) { return (jboolean) MMKV::removeStorage(mmapID, nullptr); } else { auto root = jstring2string(env, rootPath); return (jboolean) MMKV::removeStorage(mmapID, &root); } } return (jboolean) false; } MMKV_JNI jboolean encodeSet(JNIEnv *env, jobject, jlong handle, jstring oKey, jobjectArray arrStr) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); if (arrStr) { vector value = jarray2vector(env, arrStr); return (jboolean) kv->set(value, key); } else { kv->removeValueForKey(key); return (jboolean) true; } } return (jboolean) false; } MMKV_JNI jboolean encodeSet_2(JNIEnv *env, jobject, jlong handle, jstring oKey, jobjectArray arrStr, jint expiration) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); if (arrStr) { vector value = jarray2vector(env, arrStr); return (jboolean) kv->set(value, key, (uint32_t) expiration); } else { kv->removeValueForKey(key); return (jboolean) true; } } return (jboolean) false; } MMKV_JNI jobjectArray decodeStringSet(JNIEnv *env, jobject, jlong handle, jstring oKey) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); vector value; bool hasValue = kv->getVector(key, value); if (hasValue) { return vector2jarray(env, value); } } return nullptr; } MMKV_JNI void clearMemoryCache(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { kv->clearMemoryCache(); } } MMKV_JNI void lock(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { kv->lock(); } } MMKV_JNI void unlock(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { kv->unlock(); } } MMKV_JNI jboolean tryLock(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { return (jboolean) kv->try_lock(); } return jboolean(false); } MMKV_JNI jint pageSize(JNIEnv *env, jclass type) { return DEFAULT_MMAP_SIZE; } MMKV_JNI jstring version(JNIEnv *env, jclass type) { return string2jstring(env, MMKV_VERSION); } # ifndef MMKV_DISABLE_CRYPT MMKV_JNI jstring cryptKey(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { string cryptKey = kv->cryptKey(); if (cryptKey.length() > 0) { return string2jstring(env, cryptKey); } } return nullptr; } MMKV_JNI jboolean doReKey(JNIEnv *env, jobject instance, jstring cryptKey, jboolean aes256) { MMKV *kv = getMMKV(env, instance); if (kv) { string newKey; if (cryptKey) { newKey = jstring2string(env, cryptKey); } return (jboolean) kv->reKey(newKey, aes256); } return (jboolean) false; } MMKV_JNI void doCheckReSetCryptKey(JNIEnv *env, jobject instance, jstring cryptKey, jboolean aes256) { MMKV *kv = getMMKV(env, instance); if (kv) { string newKey; if (cryptKey) { newKey = jstring2string(env, cryptKey); } if (!cryptKey || newKey.empty()) { kv->checkReSetCryptKey(nullptr, aes256); } else { kv->checkReSetCryptKey(&newKey, aes256); } } } # else MMKV_JNI jstring cryptKey(JNIEnv *env, jobject instance) { return nullptr; } # endif // MMKV_DISABLE_CRYPT MMKV_JNI void trim(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { kv->trim(); } } MMKV_JNI void close(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { kv->close(); env->SetLongField(instance, g_fileID, 0); } } MMKV_JNI jint valueSize(JNIEnv *env, jobject, jlong handle, jstring oKey, jboolean actualSize) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return static_cast(kv->getValueSize(key, (bool) actualSize)); } return 0; } MMKV_JNI void setLogLevel(JNIEnv *env, jclass type, jint level) { MMKV::setLogLevel((MMKVLogLevel) level); } MMKV_JNI void setCallbackHandler(JNIEnv *env, jclass type, jboolean logReDirecting, jboolean hasCallback, jlong nativeHandler) { g_jniHandler.setLogRedirecting(logReDirecting == JNI_TRUE); g_jniHandler.setHasCallback(hasCallback == JNI_TRUE); if (logReDirecting && nativeHandler != 0) { g_androidLogHandler = (AndroidLogHandler) nativeHandler; } else { g_androidLogHandler = nullptr; } if (hasCallback == JNI_TRUE || logReDirecting == JNI_TRUE) { MMKV::registerHandler(&g_jniHandler); } else { MMKV::unRegisterHandler(); } } MMKV_JNI jlong createNB(JNIEnv *env, jobject instance, jint size) { auto ptr = malloc(static_cast(size)); if (!ptr) { MMKVError("fail to create NativeBuffer:%s", strerror(errno)); return 0; } return reinterpret_cast(ptr); } MMKV_JNI void destroyNB(JNIEnv *env, jobject instance, jlong pointer, jint size) { free(reinterpret_cast(pointer)); } MMKV_JNI jint writeValueToNB(JNIEnv *env, jobject instance, jlong handle, jstring oKey, jlong pointer, jint size) { MMKV *kv = reinterpret_cast(handle); if (kv && oKey) { string key = jstring2string(env, oKey); return kv->writeValueToBuffer(key, reinterpret_cast(pointer), size); } return -1; } 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::registerHandler(&g_jniHandler); } } MMKV_JNI void checkContentChanged(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { kv->checkContentChanged(); } } MMKV_JNI jboolean backupOne(JNIEnv *env, jobject obj, jstring mmapID, jstring dstDir, jstring rootPath) { if (rootPath) { string root = jstring2string(env, rootPath); if (root.length() > 0) { return (jboolean) MMKV::backupOneToDirectory(jstring2string(env, mmapID), jstring2string(env, dstDir), &root); } } return (jboolean) MMKV::backupOneToDirectory(jstring2string(env, mmapID), jstring2string(env, dstDir)); } MMKV_JNI jboolean restoreOne(JNIEnv *env, jobject obj, jstring mmapID, jstring srcDir, jstring rootPath) { if (rootPath) { string root = jstring2string(env, rootPath); if (root.length() > 0) { return (jboolean) MMKV::restoreOneFromDirectory(jstring2string(env, mmapID), jstring2string(env, srcDir), &root); } } return (jboolean) MMKV::restoreOneFromDirectory(jstring2string(env, mmapID), jstring2string(env, srcDir)); } MMKV_JNI jlong backupAll(JNIEnv *env, jobject obj, jstring dstDir/*, jstring rootPath*/) { // historically Android mistakenly use mmapKey as mmapID // makes everything tricky with customize root /*if (rootPath) { string root = jstring2string(env, rootPath); if (root.length() > 0) { return (jlong) MMKV::backupAllToDirectory(jstring2string(env, dstDir), &root); } }*/ return (jlong) MMKV::backupAllToDirectory(jstring2string(env, dstDir)); } MMKV_JNI jlong restoreAll(JNIEnv *env, jobject obj, jstring srcDir/*, jstring rootPath*/) { // historically Android mistakenly use mmapKey as mmapID // makes everything tricky with customize root /*if (rootPath) { string root = jstring2string(env, rootPath); if (root.length() > 0) { return (jlong) MMKV::restoreAllFromDirectory(jstring2string(env, srcDir), &root); } }*/ return (jlong) MMKV::restoreAllFromDirectory(jstring2string(env, srcDir)); } MMKV_JNI jboolean enableAutoExpire(JNIEnv *env, jobject instance, jint expireDuration) { MMKV *kv = getMMKV(env, instance); if (kv) { return (jboolean) kv->enableAutoKeyExpire(expireDuration); } return (jboolean) false; } MMKV_JNI jboolean disableAutoExpire(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { return (jboolean) kv->disableAutoKeyExpire(); } return (jboolean) false; } MMKV_JNI void enableCompareBeforeSet(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { kv->enableCompareBeforeSet(); } } MMKV_JNI void disableCompareBeforeSet(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { kv->disableCompareBeforeSet(); } } MMKV_JNI bool isCompareBeforeSetEnabled(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { return kv->isCompareBeforeSetEnabled(); } return false; } MMKV_JNI bool isEncryptionEnabled(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { return kv->isEncryptionEnabled(); } return false; } MMKV_JNI bool isExpirationEnabled(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { return kv->isExpirationEnabled(); } return false; } MMKV_JNI void clearAllWithKeepingSpace(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { kv->clearAll(true); } } MMKV_JNI jboolean isMultiProcess(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { return (jboolean) kv->isMultiProcess(); } return jboolean(false); } MMKV_JNI jboolean isReadOnly(JNIEnv *env, jobject instance) { MMKV *kv = getMMKV(env, instance); if (kv) { return (jboolean) kv->isReadOnly(); } return jboolean(false); } MMKV_JNI jboolean getNameSpace(JNIEnv *env, jclass type, jstring rootPath) { if (rootPath) { auto root = jstring2string(env, rootPath); if (!root.empty()) { MMKV::nameSpace(root); return (jboolean) true; } } return (jboolean) false; } MMKV_JNI jboolean checkExist(JNIEnv *env, jclass type, jstring oMmapID, jstring rootPath) { if (oMmapID) { string mmapID = jstring2string(env, oMmapID); if (!rootPath) { return (jboolean) MMKV::checkExist(mmapID, nullptr); } else { auto root = jstring2string(env, rootPath); return (jboolean) MMKV::checkExist(mmapID, &root); } } return (jboolean) false; } MMKV_JNI void enableDisableProcessMode(JNIEnv *env, jclass type, jboolean notify) { if (notify == JNI_TRUE) { MMKV::enableDisableProcessMode(true); } else { MMKV::enableDisableProcessMode(false); } } MMKV_JNI jlong importFrom(JNIEnv *env, jobject instance, jlong handle, jlong srcHandle) { MMKV *kv = reinterpret_cast(handle); MMKV *src = reinterpret_cast(srcHandle); if (kv && src) { jlong size = kv->importFrom(src); return size; } return 0; } } // namespace mmkv static JNINativeMethod g_methods[] = { {"onExit", "()V", (void *) mmkv::onExit}, {"cryptKey", "()Ljava/lang/String;", (void *) mmkv::cryptKey}, # ifndef MMKV_DISABLE_CRYPT {"doReKey", "(Ljava/lang/String;Z)Z", (void *) mmkv::doReKey}, {"doCheckReSetCryptKey", "(Ljava/lang/String;Z)V", (void *) mmkv::doCheckReSetCryptKey}, # endif {"pageSize", "()I", (void *) mmkv::pageSize}, {"mmapID", "()Ljava/lang/String;", (void *) mmkv::mmapID}, {"version", "()Ljava/lang/String;", (void *) mmkv::version}, {"lock", "()V", (void *) mmkv::lock}, {"unlock", "()V", (void *) mmkv::unlock}, {"tryLock", "()Z", (void *) mmkv::tryLock}, {"allKeys", "(JZ)[Ljava/lang/String;", (void *) mmkv::allKeys}, {"removeValuesForKeys", "([Ljava/lang/String;)V", (void *) mmkv::removeValuesForKeys}, {"clearAll", "()V", (void *) mmkv::clearAll}, {"trim", "()V", (void *) mmkv::trim}, {"close", "()V", (void *) mmkv::close}, {"clearMemoryCache", "()V", (void *) mmkv::clearMemoryCache}, {"sync", "(Z)V", (void *) mmkv::sync}, {"isFileValid", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *) mmkv::isFileValid}, {"removeStorage", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *) mmkv::removeStorage}, {"ashmemFD", "()I", (void *) mmkv::ashmemFD}, {"ashmemMetaFD", "()I", (void *) mmkv::ashmemMetaFD}, {"jniInitialize", "(Ljava/lang/String;Ljava/lang/String;IZZJ)V", (void *) mmkv::jniInitialize}, {"getMMKVWithID", "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JZIIZII)J", (void *) mmkv::getMMKVWithID}, {"getDefaultMMKV", "(ILjava/lang/String;JZIIZII)J", (void *) mmkv::getDefaultMMKV}, {"getMMKVWithAshmemFD", "(Ljava/lang/String;IILjava/lang/String;Z)J", (void *) mmkv::getMMKVWithAshmemFD}, {"encodeBool", "(JLjava/lang/String;Z)Z", (void *) mmkv::encodeBool}, {"encodeBool_2", "(JLjava/lang/String;ZI)Z", (void *) mmkv::encodeBool_2}, {"decodeBool", "(JLjava/lang/String;Z)Z", (void *) mmkv::decodeBool}, {"encodeInt", "(JLjava/lang/String;I)Z", (void *) mmkv::encodeInt}, {"encodeInt_2", "(JLjava/lang/String;II)Z", (void *) mmkv::encodeInt_2}, {"decodeInt", "(JLjava/lang/String;I)I", (void *) mmkv::decodeInt}, {"encodeLong", "(JLjava/lang/String;J)Z", (void *) mmkv::encodeLong}, {"encodeLong_2", "(JLjava/lang/String;JI)Z", (void *) mmkv::encodeLong_2}, {"decodeLong", "(JLjava/lang/String;J)J", (void *) mmkv::decodeLong}, {"encodeFloat", "(JLjava/lang/String;F)Z", (void *) mmkv::encodeFloat}, {"encodeFloat_2", "(JLjava/lang/String;FI)Z", (void *) mmkv::encodeFloat_2}, {"decodeFloat", "(JLjava/lang/String;F)F", (void *) mmkv::decodeFloat}, {"encodeDouble", "(JLjava/lang/String;D)Z", (void *) mmkv::encodeDouble}, {"encodeDouble_2", "(JLjava/lang/String;DI)Z", (void *) mmkv::encodeDouble_2}, {"decodeDouble", "(JLjava/lang/String;D)D", (void *) mmkv::decodeDouble}, {"encodeString", "(JLjava/lang/String;Ljava/lang/String;)Z", (void *) mmkv::encodeString}, {"encodeString_2", "(JLjava/lang/String;Ljava/lang/String;I)Z", (void *) mmkv::encodeString_2}, {"decodeString", "(JLjava/lang/String;Ljava/lang/String;)Ljava/lang/String;", (void *) mmkv::decodeString}, {"encodeSet", "(JLjava/lang/String;[Ljava/lang/String;)Z", (void *) mmkv::encodeSet}, {"encodeSet_2", "(JLjava/lang/String;[Ljava/lang/String;I)Z", (void *) mmkv::encodeSet_2}, {"decodeStringSet", "(JLjava/lang/String;)[Ljava/lang/String;", (void *) mmkv::decodeStringSet}, {"encodeBytes", "(JLjava/lang/String;[B)Z", (void *) mmkv::encodeBytes}, {"encodeBytes_2", "(JLjava/lang/String;[BI)Z", (void *) mmkv::encodeBytes_2}, {"decodeBytes", "(JLjava/lang/String;)[B", (void *) mmkv::decodeBytes}, {"containsKey", "(JLjava/lang/String;)Z", (void *) mmkv::containsKey}, {"count", "(JZ)J", (void *) mmkv::count}, {"totalSize", "(J)J", (void *) mmkv::totalSize}, {"actualSize", "(J)J", (void *) mmkv::actualSize}, {"removeValueForKey", "(JLjava/lang/String;)V", (void *) mmkv::removeValueForKey}, {"valueSize", "(JLjava/lang/String;Z)I", (void *) mmkv::valueSize}, {"setLogLevel", "(I)V", (void *) mmkv::setLogLevel}, {"setCallbackHandler", "(ZZJ)V", (void *) mmkv::setCallbackHandler}, {"createNB", "(I)J", (void *) mmkv::createNB}, {"destroyNB", "(JI)V", (void *) mmkv::destroyNB}, {"writeValueToNB", "(JLjava/lang/String;JI)I", (void *) mmkv::writeValueToNB}, {"setWantsContentChangeNotify", "(Z)V", (void *) mmkv::setWantsContentChangeNotify}, {"checkContentChangedByOuterProcess", "()V", (void *) mmkv::checkContentChanged}, {"checkProcessMode", "(J)Z", (void *) mmkv::checkProcessMode}, {"backupOneToDirectory", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z", (void *) mmkv::backupOne}, {"restoreOneMMKVFromDirectory", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z", (void *) mmkv::restoreOne}, {"backupAllToDirectory", "(Ljava/lang/String;)J", (void *) mmkv::backupAll}, {"restoreAllFromDirectory", "(Ljava/lang/String;)J", (void *) mmkv::restoreAll}, {"enableAutoKeyExpire", "(I)Z", (void *) mmkv::enableAutoExpire}, {"disableAutoKeyExpire", "()Z", (void *) mmkv::disableAutoExpire}, {"nativeEnableCompareBeforeSet", "()V", (void *) mmkv::enableCompareBeforeSet}, {"disableCompareBeforeSet", "()V", (void *) mmkv::disableCompareBeforeSet}, {"isCompareBeforeSetEnabled", "()Z", (void *) mmkv::isCompareBeforeSetEnabled}, {"isEncryptionEnabled", "()Z", (void *) mmkv::isEncryptionEnabled}, {"isExpirationEnabled", "()Z", (void *) mmkv::isExpirationEnabled}, {"clearAllWithKeepingSpace", "()V", (void *) mmkv::clearAllWithKeepingSpace}, {"isMultiProcess", "()Z", (void *) mmkv::isMultiProcess}, {"isReadOnly", "()Z", (void *) mmkv::isReadOnly}, {"getNameSpace", "(Ljava/lang/String;)Z", (void *)mmkv::getNameSpace}, {"checkExist", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *) mmkv::checkExist}, {"enableDisableProcessMode", "(Z)V", (void *) mmkv::enableDisableProcessMode}, {"importFrom", "(JJ)J", (void *) mmkv::importFrom}, }; static int registerNativeMethods(JNIEnv *env, jclass cls) { return env->RegisterNatives(cls, g_methods, sizeof(g_methods) / sizeof(g_methods[0])); } #endif // MMKV_ANDROID ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKV.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkv; import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.jetbrains.annotations.Contract; import dalvik.annotation.optimization.FastNative; import java.lang.reflect.Field; import java.util.Arrays; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * An highly efficient, reliable, multi-process key-value storage framework. * THE PERFECT drop-in replacement for SharedPreferences and MultiProcessSharedPreferences. */ public class MMKV implements SharedPreferences, SharedPreferences.Editor { private static final EnumMap recoverIndex; private static final EnumMap logLevel2Index; private static final MMKVLogLevel[] index2LogLevel; private static final Set checkedHandleSet; static { recoverIndex = new EnumMap<>(MMKVRecoverStrategic.class); recoverIndex.put(MMKVRecoverStrategic.OnErrorDiscard, 0); recoverIndex.put(MMKVRecoverStrategic.OnErrorRecover, 1); logLevel2Index = new EnumMap<>(MMKVLogLevel.class); logLevel2Index.put(MMKVLogLevel.LevelDebug, 0); logLevel2Index.put(MMKVLogLevel.LevelInfo, 1); logLevel2Index.put(MMKVLogLevel.LevelWarning, 2); logLevel2Index.put(MMKVLogLevel.LevelError, 3); logLevel2Index.put(MMKVLogLevel.LevelNone, 4); index2LogLevel = new MMKVLogLevel[]{MMKVLogLevel.LevelDebug, MMKVLogLevel.LevelInfo, MMKVLogLevel.LevelWarning, MMKVLogLevel.LevelError, MMKVLogLevel.LevelNone}; checkedHandleSet = new HashSet(); } /** * The interface for providing a 3rd library loader (the ReLinker https://github.com/KeepSafe/ReLinker, etc). */ public interface LibLoader { void loadLibrary(String libName); } /** * Initialize MMKV with default configuration. * You must call one of the initialize() methods on App startup process before using MMKV. * * @param context The context of Android App, usually from Application. * @return The root folder of MMKV, defaults to $(FilesDir)/mmkv. */ public static String initialize(@NonNull Context context) { String root = context.getFilesDir().getAbsolutePath() + "/mmkv"; MMKVLogLevel logLevel = BuildConfig.DEBUG ? MMKVLogLevel.LevelDebug : MMKVLogLevel.LevelInfo; return initialize(context, root, null, logLevel, null); } /** * Initialize MMKV with customize log level. * You must call one of the initialize() methods on App startup process before using MMKV. * * @param context The context of Android App, usually from Application. * @param logLevel The log level of MMKV, defaults to {@link MMKVLogLevel#LevelInfo}. * @return The root folder of MMKV, defaults to $(FilesDir)/mmkv. */ public static String initialize(@NonNull Context context, MMKVLogLevel logLevel) { String root = context.getFilesDir().getAbsolutePath() + "/mmkv"; return initialize(context, root, null, logLevel, null); } /** * Initialize MMKV with a 3rd library loader. * You must call one of the initialize() methods on App startup process before using MMKV. * * @param context The context of Android App, usually from Application. * @param loader The 3rd library loader (for example, the ReLinker . * @return The root folder of MMKV, defaults to $(FilesDir)/mmkv. */ public static String initialize(@NonNull Context context, LibLoader loader) { String root = context.getFilesDir().getAbsolutePath() + "/mmkv"; MMKVLogLevel logLevel = BuildConfig.DEBUG ? MMKVLogLevel.LevelDebug : MMKVLogLevel.LevelInfo; return initialize(context, root, loader, logLevel, null); } /** * Initialize MMKV with a 3rd library loader, and customize log level. * You must call one of the initialize() methods on App startup process before using MMKV. * * @param context The context of Android App, usually from Application. * @param loader The 3rd library loader (for example, the ReLinker . * @param logLevel The log level of MMKV, defaults to {@link MMKVLogLevel#LevelInfo}. * @return The root folder of MMKV, defaults to $(FilesDir)/mmkv. */ public static String initialize(@NonNull Context context, LibLoader loader, MMKVLogLevel logLevel) { String root = context.getFilesDir().getAbsolutePath() + "/mmkv"; return initialize(context, root, loader, logLevel, null); } /** * Initialize MMKV with customize root folder. * You must call one of the initialize() methods on App startup process before using MMKV. * * @param context The context of Android App, usually from Application. * @param rootDir The root folder of MMKV, defaults to $(FilesDir)/mmkv. * @return The root folder of MMKV. */ public static String initialize(Context context, String rootDir) { MMKVLogLevel logLevel = BuildConfig.DEBUG ? MMKVLogLevel.LevelDebug : MMKVLogLevel.LevelInfo; return initialize(context, rootDir, null, logLevel, null); } /** * Initialize MMKV with customize root folder, and log level. * You must call one of the initialize() methods on App startup process before using MMKV. * * @param context The context of Android App, usually from Application. * @param rootDir The root folder of MMKV, defaults to $(FilesDir)/mmkv. * @param logLevel The log level of MMKV, defaults to {@link MMKVLogLevel#LevelInfo}. * @return The root folder of MMKV. */ public static String initialize(Context context, String rootDir, MMKVLogLevel logLevel) { return initialize(context, rootDir, null, logLevel, null); } /** * Initialize MMKV with customize root folder, and a 3rd library loader. * You must call one of the initialize() methods on App startup process before using MMKV. * * @param context The context of Android App, usually from Application. * @param rootDir The root folder of MMKV, defaults to $(FilesDir)/mmkv. * @param loader The 3rd library loader (for example, the ReLinker . * @return The root folder of MMKV. */ public static String initialize(Context context, String rootDir, LibLoader loader) { MMKVLogLevel logLevel = BuildConfig.DEBUG ? MMKVLogLevel.LevelDebug : MMKVLogLevel.LevelInfo; return initialize(context, rootDir, loader, logLevel, null); } /** * Initialize MMKV with customize settings. * You must call one of the initialize() methods on App startup process before using MMKV. * * @param context The context of Android App, usually from Application. * @param rootDir The root folder of MMKV, defaults to $(FilesDir)/mmkv. * @param loader The 3rd library loader (for example, the ReLinker . * @param logLevel The log level of MMKV, defaults to {@link MMKVLogLevel#LevelInfo}. * @return The root folder of MMKV. */ public static String initialize(Context context, String rootDir, LibLoader loader, MMKVLogLevel logLevel) { return initialize(context, rootDir, loader, logLevel, null); } public static String initialize(@NonNull Context context, String rootDir, LibLoader loader, MMKVLogLevel logLevel, MMKVHandler handler) { if (!Process.is64Bit()) { throw new UnsupportedArchitectureException("MMKV 2.0+ requires 64-bit App, use 1.3.x instead."); } String cacheDir = context.getCacheDir().getAbsolutePath(); gCallbackHandler = handler; boolean hasCallback = false; long nativeLogHandler = 0; if (handler != null) { hasCallback = true; if (handler.wantLogRedirecting()) { gWantLogReDirecting = true; nativeLogHandler = handler.getNativeLogHandler(); } } 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. if ((context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) { disableProcessModeChecker(); } else { enableProcessModeChecker(); } return ret; } private static String doInitialize(String rootDir, String cacheDir, LibLoader loader, MMKVLogLevel logLevel, boolean wantLogReDirecting, boolean hasCallback, long nativeHandler) { tryLoadNativeLib(loader); jniInitialize(rootDir, cacheDir, logLevel2Int(logLevel), wantLogReDirecting, hasCallback, nativeHandler); MMKV.rootDir = rootDir; return MMKV.rootDir; } private static boolean isNativeLibLoaded = false; private static void tryLoadNativeLib(@Nullable LibLoader loader) { if (isNativeLibLoaded) { return; } if (loader != null) { if (BuildConfig.FLAVOR.equals("SharedCpp")) { loader.loadLibrary("c++_shared"); } loader.loadLibrary("mmkv"); } else { if (BuildConfig.FLAVOR.equals("SharedCpp")) { System.loadLibrary("c++_shared"); } System.loadLibrary("mmkv"); } isNativeLibLoaded = true; } /** * @deprecated This method is deprecated due to failing to automatically disable checkProcessMode() without Context. * Use the {@link #initialize(Context, String)} method instead. */ @Deprecated public static String initialize(String rootDir) { MMKVLogLevel logLevel = BuildConfig.DEBUG ? MMKVLogLevel.LevelDebug : MMKVLogLevel.LevelInfo; return doInitialize(rootDir, rootDir + "/.tmp", null, logLevel, false, false, 0); } /** * @deprecated This method is deprecated due to failing to automatically disable checkProcessMode() without Context. * Use the {@link #initialize(Context, String, MMKVLogLevel)} method instead. */ @Deprecated public static String initialize(String rootDir, MMKVLogLevel logLevel) { return doInitialize(rootDir, rootDir + "/.tmp", null, logLevel, false, false, 0); } /** * @deprecated This method is deprecated due to failing to automatically disable checkProcessMode() without Context. * Use the {@link #initialize(Context, String, LibLoader)} method instead. */ @Deprecated public static String initialize(String rootDir, LibLoader loader) { MMKVLogLevel logLevel = BuildConfig.DEBUG ? MMKVLogLevel.LevelDebug : MMKVLogLevel.LevelInfo; return doInitialize(rootDir, rootDir + "/.tmp", loader, logLevel, false, false, 0); } /** * @deprecated This method is deprecated due to failing to automatically disable checkProcessMode() without Context. * Use the {@link #initialize(Context, String, LibLoader, MMKVLogLevel)} method instead. */ @Deprecated public static String initialize(String rootDir, LibLoader loader, MMKVLogLevel logLevel) { return doInitialize(rootDir, rootDir + "/.tmp", loader, logLevel, false, false, 0); } /** * @param dir the customize root directory of a NameSpace * @return a NameSpace with custom root dir * @throws RuntimeException if there's an runtime error. */ public static NameSpace nameSpace(String dir) throws RuntimeException { tryLoadNativeLib(null); if (getNameSpace(dir)) { return new NameSpace(dir); } throw new RuntimeException("Fail to get NameSpace[" + dir + "] in JNI."); } /** * identical with the original MMKV with the global root dir * @throws RuntimeException if there's an runtime error. */ public static NameSpace defaultNameSpace() throws RuntimeException { if (rootDir == null) { throw new IllegalStateException("You should Call MMKV.initialize() first."); } return new NameSpace(rootDir); } static private String rootDir = null; /** * @return The root folder of MMKV, defaults to $(FilesDir)/mmkv. */ public static String getRootDir() { return rootDir; } @Contract(pure = true) private static int logLevel2Int(@NonNull MMKVLogLevel level) { int realLevel; switch (level) { case LevelDebug: realLevel = 0; break; case LevelWarning: realLevel = 2; break; case LevelError: realLevel = 3; break; case LevelNone: realLevel = 4; break; case LevelInfo: default: realLevel = 1; break; } return realLevel; } /** * Set the log level of MMKV. * * @param level Defaults to {@link MMKVLogLevel#LevelInfo}. */ public static void setLogLevel(MMKVLogLevel level) { int realLevel = logLevel2Int(level); setLogLevel(realLevel); } /** * Notify MMKV that App is about to exit. It's totally fine not calling it at all. */ public static native void onExit(); /** * Single-process mode. The default mode on an MMKV instance. */ static public final int SINGLE_PROCESS_MODE = 1 << 0; /** * Multi-process mode. * To enable multi-process accessing of an MMKV instance, you must set this mode whenever you getting that instance. */ static public final int MULTI_PROCESS_MODE = 1 << 1; // in case someone mistakenly pass Context.MODE_MULTI_PROCESS static private final int CONTEXT_MODE_MULTI_PROCESS = 1 << 2; static private final int ASHMEM_MODE = 1 << 3; static private final int BACKUP_MODE = 1 << 4; /** * Read-only mode. */ static public final int READ_ONLY_MODE = 1 << 5; /** * Create an MMKV instance with an unique ID (in single-process mode). * * @param mmapID The unique ID of the MMKV instance. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID) throws RuntimeException { return mmkvWithID(mmapID, new MMKVConfig()); } /** * Create an MMKV instance with an unique ID (in single-process mode). * * @param mmapID The unique ID of the MMKV instance. * @param config The all-in-one configuration for the MMKV instance. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID, MMKVConfig config) throws RuntimeException { if (rootDir == null) { throw new IllegalStateException("You should Call MMKV.initialize() first."); } int enableKeyExpire = (config.enableKeyExpire != null) ? (config.enableKeyExpire ? 1 : 0) : -1; Integer value = recoverIndex.get(config.recover); int recover = (value == null) ? -1 : value; long handle = getMMKVWithID(mmapID, config.mode, config.cryptKey, config.rootPath, config.expectedCapacity, config.aes256, enableKeyExpire, config.expiredInSeconds, config.enableCompareBeforeSet, recover, config.itemSizeLimit); return checkProcessMode(handle, mmapID, config.mode); } /** * Create an MMKV instance in single-process or multi-process mode. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID, int mode) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance in single-process or multi-process mode. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param expectedCapacity The file size you expected when opening or creating file * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID, int mode, long expectedCapacity) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.expectedCapacity = expectedCapacity; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance in customize process mode, with an encryption key. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 16 bytes). * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.cryptKey = cryptKey; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance in customize process mode, with an encryption key. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 32 bytes). * @param aes256 Use AES 256 key length * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey, boolean aes256) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.aes256 = aes256; config.cryptKey = cryptKey; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance in customize folder. * * @param mmapID The unique ID of the MMKV instance. * @param rootPath The folder of the MMKV instance, defaults to $(FilesDir)/mmkv. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID, String rootPath) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.rootPath = rootPath; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance in customize folder. * * @param mmapID The unique ID of the MMKV instance. * @param rootPath The folder of the MMKV instance, defaults to $(FilesDir)/mmkv. * @param expectedCapacity The file size you expected when opening or creating file * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID, String rootPath, long expectedCapacity) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.rootPath = rootPath; config.expectedCapacity = expectedCapacity; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance with customize settings all in one. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 16 bytes). * @param rootPath The folder of the MMKV instance, defaults to $(FilesDir)/mmkv. * @param expectedCapacity The file size you expected when opening or creating file * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey, String rootPath, long expectedCapacity) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.cryptKey = cryptKey; config.rootPath = rootPath; config.expectedCapacity = expectedCapacity; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance with customize settings all in one. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 32 bytes). * @param aes256 Use AES 256 key length * @param rootPath The folder of the MMKV instance, defaults to $(FilesDir)/mmkv. * @param expectedCapacity The file size you expected when opening or creating file * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey, boolean aes256, String rootPath, long expectedCapacity) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.aes256 = aes256; config.cryptKey = cryptKey; config.rootPath = rootPath; config.expectedCapacity = expectedCapacity; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance with customize settings all in one. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 16 bytes). * @param rootPath The folder of the MMKV instance, defaults to $(FilesDir)/mmkv. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey, String rootPath) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.cryptKey = cryptKey; config.rootPath = rootPath; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance with customize settings all in one. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 32 bytes). * @param aes256 Use AES 256 key length. * @param rootPath The folder of the MMKV instance, defaults to $(FilesDir)/mmkv. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey, boolean aes256, String rootPath) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.aes256 = aes256; config.cryptKey = cryptKey; config.rootPath = rootPath; return mmkvWithID(mmapID, config); } /** * Get an backed-up MMKV instance with customize settings all in one. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 16 bytes). * @param rootPath The backup folder of the MMKV instance. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV backedUpMMKVWithID(String mmapID, int mode, @Nullable String cryptKey, String rootPath) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.cryptKey = cryptKey; config.rootPath = rootPath; return mmkvWithID(mmapID, config); } /** * Get an backed-up MMKV instance with customize settings all in one. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 32 bytes). * @param aes256 Use AES 256 key length. * @param rootPath The backup folder of the MMKV instance. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV backedUpMMKVWithID(String mmapID, int mode, @Nullable String cryptKey, boolean aes256, String rootPath) throws RuntimeException { MMKVConfig config = new MMKVConfig(); mode |= BACKUP_MODE; config.mode = mode; config.cryptKey = cryptKey; config.rootPath = rootPath; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance base on Anonymous Shared Memory, aka not synced to any disk files. * * @param context The context of Android App, usually from Application. * @param mmapID The unique ID of the MMKV instance. * @param size The maximum size of the underlying Anonymous Shared Memory. * Anonymous Shared Memory on Android can't grow dynamically, must set an appropriate size on creation. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 16 bytes). * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithAshmemID(Context context, String mmapID, int size, int mode, @Nullable String cryptKey) throws RuntimeException { return mmkvWithAshmemID(context, mmapID, size, mode, cryptKey, false); } /** * Create an MMKV instance base on Anonymous Shared Memory, aka not synced to any disk files. * * @param context The context of Android App, usually from Application. * @param mmapID The unique ID of the MMKV instance. * @param size The maximum size of the underlying Anonymous Shared Memory. * Anonymous Shared Memory on Android can't grow dynamically, must set an appropriate size on creation. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 32 bytes). * @param aes256 Use AES 256 key length. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithAshmemID(Context context, String mmapID, int size, int mode, @Nullable String cryptKey, boolean aes256) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode | ASHMEM_MODE; config.expectedCapacity = size; config.aes256 = aes256; config.cryptKey = cryptKey; return mmkvWithAshmemID(context, mmapID, config); } /** * Create an MMKV instance base on Anonymous Shared Memory, aka not synced to any disk files. * * @param context The context of Android App, usually from Application. * @param mmapID The unique ID of the MMKV instance. * @param config The all-in-one configuration for the MMKV instance. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV mmkvWithAshmemID(Context context, String mmapID, MMKVConfig config) throws RuntimeException { if (rootDir == null) { throw new IllegalStateException("You should Call MMKV.initialize() first."); } String processName = MMKVContentProvider.getProcessNameByPID(context, Process.myPid()); if (processName == null || processName.isEmpty()) { String message = "process name detect fail, try again later"; simpleLog(MMKVLogLevel.LevelError, message); throw new IllegalStateException(message); } if (processName.contains(":")) { Uri uri = MMKVContentProvider.contentUri(context); if (uri == null) { String message = "MMKVContentProvider has invalid authority"; simpleLog(MMKVLogLevel.LevelError, message); throw new IllegalStateException(message); } simpleLog(MMKVLogLevel.LevelInfo, "getting parcelable mmkv in process, Uri = " + uri); Bundle extras = new Bundle(); extras.putInt(MMKVContentProvider.KEY_SIZE, (int) config.expectedCapacity); extras.putInt(MMKVContentProvider.KEY_MODE, config.mode); if (config.cryptKey != null) { extras.putString(MMKVContentProvider.KEY_CRYPT, config.cryptKey); } ContentResolver resolver = context.getContentResolver(); Bundle result = resolver.call(uri, MMKVContentProvider.FUNCTION_NAME, mmapID, extras); if (result != null) { result.setClassLoader(ParcelableMMKV.class.getClassLoader()); ParcelableMMKV parcelableMMKV = result.getParcelable(MMKVContentProvider.KEY); if (parcelableMMKV != null) { MMKV mmkv = parcelableMMKV.toMMKV(); if (mmkv != null) { simpleLog(MMKVLogLevel.LevelInfo, mmkv.mmapID() + " fd = " + mmkv.ashmemFD() + ", meta fd = " + mmkv.ashmemMetaFD()); return mmkv; } } } } simpleLog(MMKVLogLevel.LevelInfo, "getting mmkv in main process"); config.mode = config.mode | ASHMEM_MODE; return mmkvWithID(mmapID, config); } /** * Create the default MMKV instance in single-process mode. * * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV defaultMMKV() throws RuntimeException { MMKVConfig config = new MMKVConfig(); return defaultMMKV(config); } /** * Create the default MMKV instance in customize process mode, with an encryption key. * * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 16 bytes). * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV defaultMMKV(int mode, @Nullable String cryptKey) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.cryptKey = cryptKey; return defaultMMKV(config); } /** * Create the default MMKV instance in customize process mode, with an encryption key. * * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 32 bytes). * @param aes256 Use AES 256 key length. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV defaultMMKV(int mode, @Nullable String cryptKey, boolean aes256) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.aes256 = aes256; config.cryptKey = cryptKey; return defaultMMKV(config); } /** * Create the default MMKV instance in customize configuration. * * @param config The all-in-one configuration for the MMKV instance. * @throws RuntimeException if there's an runtime error. */ @NonNull public static MMKV defaultMMKV(MMKVConfig config) throws RuntimeException { if (rootDir == null) { throw new IllegalStateException("You should Call MMKV.initialize() first."); } int enableKeyExpire = (config.enableKeyExpire != null) ? (config.enableKeyExpire ? 1 : 0) : -1; Integer value = recoverIndex.get(config.recover); int recover = (value == null) ? -1 : value; long handle = getDefaultMMKV(config.mode, config.cryptKey, config.expectedCapacity, config.aes256, enableKeyExpire, config.expiredInSeconds, config.enableCompareBeforeSet, recover, config.itemSizeLimit); return checkProcessMode(handle, "DefaultMMKV", config.mode); } @NonNull @Contract("_, _, _ -> new") static MMKV checkProcessMode(long handle, String mmapID, int mode) throws RuntimeException { if (handle == 0) { throw new RuntimeException("Fail to create an MMKV instance [" + mmapID + "] in JNI"); } if (!isProcessModeCheckerEnabled) { return new MMKV(handle); } synchronized (checkedHandleSet) { if (!checkedHandleSet.contains(handle)) { if (!checkProcessMode(handle)) { String message; if (mode == SINGLE_PROCESS_MODE) { message = "Opening a multi-process MMKV instance [" + mmapID + "] with SINGLE_PROCESS_MODE!"; } else { message = "Opening an MMKV instance [" + mmapID + "] with MULTI_PROCESS_MODE, "; message += "while it's already been opened with SINGLE_PROCESS_MODE by someone somewhere else!"; } throw new IllegalArgumentException(message); } checkedHandleSet.add(handle); } } return new MMKV(handle); } // Enable checkProcessMode() when initializing an MMKV instance, it's automatically enabled on debug build. private static boolean isProcessModeCheckerEnabled = true; /** * Manually enable the process mode checker. * By default, it's automatically enabled in DEBUG build, and disabled in RELEASE build. * If it's enabled, MMKV will throw exceptions when an MMKV instance is created with mismatch process mode. */ public static void enableProcessModeChecker() { synchronized (checkedHandleSet) { isProcessModeCheckerEnabled = true; } enableDisableProcessMode(true); Log.i("MMKV", "Enable checkProcessMode()"); } /** * Manually disable the process mode checker. * By default, it's automatically enabled in DEBUG build, and disabled in RELEASE build. * If it's enabled, MMKV will throw exceptions when an MMKV instance is created with mismatch process mode. */ public static void disableProcessModeChecker() { synchronized (checkedHandleSet) { isProcessModeCheckerEnabled = false; } enableDisableProcessMode(false); Log.i("MMKV", "Disable checkProcessMode()"); } /** * @return The encryption key (no more than 16 bytes). */ @Nullable public native String cryptKey(); /** * Transform plain text into encrypted text, or vice versa by passing a null encryption key. * You can also change existing crypt key with a different cryptKey. * * @param cryptKey The new encryption key (no more than 16 bytes). * @return True if success, otherwise False. */ public boolean reKey(@Nullable String cryptKey) { return doReKey(cryptKey, false); } /** * Transform plain text into encrypted text, or vice versa by passing a null encryption key. * You can also change existing crypt key with a different cryptKey. * * @param cryptKey The new encryption key (no more than 32 bytes). * @param aes256 Use AES 256 key length * @return True if success, otherwise False. */ public boolean reKey(@Nullable String cryptKey, boolean aes256) { return doReKey(cryptKey, aes256); } private native boolean doReKey(@Nullable String cryptKey, boolean aes256); /** * Just reset the encryption key (will not encrypt or decrypt anything). * Usually you should call this method after another process has {@link #reKey(String)} the multi-process MMKV instance. * * @param cryptKey The new encryption key (no more than 16 bytes). */ public void checkReSetCryptKey(@Nullable String cryptKey) { doCheckReSetCryptKey(cryptKey, false); } /** * Just reset the encryption key (will not encrypt or decrypt anything). * Usually you should call this method after another process has {@link #reKey(String)} the multi-process MMKV instance. * * @param cryptKey The new encryption key (no more than 16 bytes). * @param aes256 Use AES 256 key length */ public void checkReSetCryptKey(@Nullable String cryptKey, boolean aes256) { doCheckReSetCryptKey(cryptKey, aes256); } private native void doCheckReSetCryptKey(@Nullable String cryptKey, boolean aes256); /** * @return The device's memory page size. */ public static native int pageSize(); /** * @return The version of MMKV. */ public static native String version(); /** * @return The unique ID of the MMKV instance. */ public native String mmapID(); /** * Exclusively inter-process lock the MMKV instance. * It will block and wait until it successfully locks the file. * It will make no effect if the MMKV instance is created with {@link #SINGLE_PROCESS_MODE}. */ public native void lock(); /** * Exclusively inter-process unlock the MMKV instance. * It will make no effect if the MMKV instance is created with {@link #SINGLE_PROCESS_MODE}. */ public native void unlock(); /** * Try exclusively inter-process lock the MMKV instance. * It will not block if the file has already been locked by another process. * It will make no effect if the MMKV instance is created with {@link #SINGLE_PROCESS_MODE}. * * @return True if successfully locked, otherwise return immediately with False. */ public native boolean tryLock(); public boolean encode(String key, boolean value) { return encodeBool(nativeHandle, key, value); } /** * Set value with customize expiration in seconds. * * @param expireDurationInSecond override the default duration, {@link #ExpireNever} (0) means never expire. */ public boolean encode(String key, boolean value, int expireDurationInSecond) { return encodeBool_2(nativeHandle, key, value, expireDurationInSecond); } public boolean decodeBool(String key) { return decodeBool(nativeHandle, key, false); } public boolean decodeBool(String key, boolean defaultValue) { return decodeBool(nativeHandle, key, defaultValue); } public boolean encode(String key, int value) { return encodeInt(nativeHandle, key, value); } /** * Set value with customize expiration in seconds. * * @param expireDurationInSecond override the default duration, {@link #ExpireNever} (0) means never expire. */ public boolean encode(String key, int value, int expireDurationInSecond) { return encodeInt_2(nativeHandle, key, value, expireDurationInSecond); } public int decodeInt(String key) { return decodeInt(nativeHandle, key, 0); } public int decodeInt(String key, int defaultValue) { return decodeInt(nativeHandle, key, defaultValue); } public boolean encode(String key, long value) { return encodeLong(nativeHandle, key, value); } /** * Set value with customize expiration in seconds. * * @param expireDurationInSecond override the default duration, {@link #ExpireNever} (0) means never expire. */ public boolean encode(String key, long value, int expireDurationInSecond) { return encodeLong_2(nativeHandle, key, value, expireDurationInSecond); } public long decodeLong(String key) { return decodeLong(nativeHandle, key, 0); } public long decodeLong(String key, long defaultValue) { return decodeLong(nativeHandle, key, defaultValue); } public boolean encode(String key, float value) { return encodeFloat(nativeHandle, key, value); } /** * Set value with customize expiration in seconds. * * @param expireDurationInSecond override the default duration, {@link #ExpireNever} (0) means never expire. */ public boolean encode(String key, float value, int expireDurationInSecond) { return encodeFloat_2(nativeHandle, key, value, expireDurationInSecond); } public float decodeFloat(String key) { return decodeFloat(nativeHandle, key, 0); } public float decodeFloat(String key, float defaultValue) { return decodeFloat(nativeHandle, key, defaultValue); } public boolean encode(String key, double value) { return encodeDouble(nativeHandle, key, value); } /** * Set value with customize expiration in seconds. * * @param expireDurationInSecond override the default duration, {@link #ExpireNever} (0) means never expire. */ public boolean encode(String key, double value, int expireDurationInSecond) { return encodeDouble_2(nativeHandle, key, value, expireDurationInSecond); } public double decodeDouble(String key) { return decodeDouble(nativeHandle, key, 0); } public double decodeDouble(String key, double defaultValue) { return decodeDouble(nativeHandle, key, defaultValue); } public boolean encode(String key, @Nullable String value) { return encodeString(nativeHandle, key, value); } /** * Set value with customize expiration in seconds. * * @param expireDurationInSecond override the default duration, {@link #ExpireNever} (0) means never expire. */ public boolean encode(String key, @Nullable String value, int expireDurationInSecond) { return encodeString_2(nativeHandle, key, value, expireDurationInSecond); } @Nullable public String decodeString(String key) { return decodeString(nativeHandle, key, null); } @Nullable public String decodeString(String key, @Nullable String defaultValue) { return decodeString(nativeHandle, key, defaultValue); } public boolean encode(String key, @Nullable Set value) { return encodeSet(nativeHandle, key, (value == null) ? null : value.toArray(new String[0])); } /** * Set value with customize expiration in seconds. * * @param expireDurationInSecond override the default duration, {@link #ExpireNever} (0) means never expire. */ public boolean encode(String key, @Nullable Set value, int expireDurationInSecond) { return encodeSet_2(nativeHandle, key, (value == null) ? null : value.toArray(new String[0]), expireDurationInSecond); } @Nullable public Set decodeStringSet(String key) { return decodeStringSet(key, null); } @Nullable public Set decodeStringSet(String key, @Nullable Set defaultValue) { return decodeStringSet(key, defaultValue, HashSet.class); } @SuppressWarnings("unchecked") @Nullable public Set decodeStringSet(String key, @Nullable Set defaultValue, Class cls) { String[] result = decodeStringSet(nativeHandle, key); if (result == null) { return defaultValue; } Set a; try { a = cls.newInstance(); } catch (IllegalAccessException | InstantiationException e) { return defaultValue; } a.addAll(Arrays.asList(result)); return a; } public boolean encode(String key, @Nullable byte[] value) { return encodeBytes(nativeHandle, key, value); } /** * Set value with customize expiration in seconds. * * @param expireDurationInSecond override the default duration, {@link #ExpireNever} (0) means never expire. */ public boolean encode(String key, @Nullable byte[] value, int expireDurationInSecond) { return encodeBytes_2(nativeHandle, key, value, expireDurationInSecond); } @Nullable public byte[] decodeBytes(String key) { return decodeBytes(key, null); } @Nullable public byte[] decodeBytes(String key, @Nullable byte[] defaultValue) { byte[] ret = decodeBytes(nativeHandle, key); return (ret != null) ? ret : defaultValue; } private static final HashMap> mCreators = new HashMap<>(); private byte[] getParcelableByte(@NonNull Parcelable value) { Parcel source = Parcel.obtain(); value.writeToParcel(source, 0); byte[] bytes = source.marshall(); source.recycle(); return bytes; } public boolean encode(String key, @Nullable Parcelable value) { if (value == null) { return encodeBytes(nativeHandle, key, null); } byte[] bytes = getParcelableByte(value); return encodeBytes(nativeHandle, key, bytes); } /** * Set value with customize expiration in seconds. * * @param expireDurationInSecond override the default duration, {@link #ExpireNever} (0) means never expire. */ public boolean encode(String key, @Nullable Parcelable value, int expireDurationInSecond) { if (value == null) { return encodeBytes_2(nativeHandle, key, null, expireDurationInSecond); } byte[] bytes = getParcelableByte(value); return encodeBytes_2(nativeHandle, key, bytes, expireDurationInSecond); } @Nullable public T decodeParcelable(String key, Class tClass) { return decodeParcelable(key, tClass, null); } @SuppressWarnings("unchecked") @Nullable public T decodeParcelable(String key, Class tClass, @Nullable T defaultValue) { if (tClass == null) { return defaultValue; } byte[] bytes = decodeBytes(nativeHandle, key); if (bytes == null) { return defaultValue; } Parcel source = Parcel.obtain(); source.unmarshall(bytes, 0, bytes.length); source.setDataPosition(0); try { String name = tClass.toString(); Parcelable.Creator creator; synchronized (mCreators) { creator = (Parcelable.Creator) mCreators.get(name); if (creator == null) { Field f = tClass.getField("CREATOR"); creator = (Parcelable.Creator) f.get(null); if (creator != null) { mCreators.put(name, creator); } } } if (creator != null) { return creator.createFromParcel(source); } else { throw new Exception("Parcelable protocol requires a " + "non-null static Parcelable.Creator object called " + "CREATOR on class " + name); } } catch (Exception e) { simpleLog(MMKVLogLevel.LevelError, e.toString()); } finally { source.recycle(); } return defaultValue; } /** * Get the actual size consumption of the key's value. * Note: might be a little bigger than value's length. * * @param key The key of the value. */ public int getValueSize(String key) { return valueSize(nativeHandle, key, false); } /** * Get the actual size of the key's value. String's length or byte[]'s length, etc. * * @param key The key of the value. */ public int getValueActualSize(String key) { return valueSize(nativeHandle, key, true); } /** * Check whether or not MMKV contains the key. * * @param key The key of the value. */ public boolean containsKey(String key) { return containsKey(nativeHandle, key); } /** * @return All the keys. */ @Nullable public String[] allKeys() { return allKeys(nativeHandle, false); } /** * @return All non-expired keys. Note that this call has costs. */ @Nullable public String[] allNonExpireKeys() { return allKeys(nativeHandle, true); } /** * @return The total count of all the keys. */ public long count() { return count(nativeHandle, false); } /** * @return The total count of all non-expired keys. Note that this call has costs. */ public long countNonExpiredKeys() { return count(nativeHandle, true); } /** * Get the size of the underlying file. Align to the disk block size, typically 4K for an Android device. */ public long totalSize() { return totalSize(nativeHandle); } /** * Get the actual used size of the MMKV instance. * This size might increase and decrease as MMKV doing insertion and full write back. */ public long actualSize() { return actualSize(nativeHandle); } public void removeValueForKey(String key) { removeValueForKey(nativeHandle, key); } /** * Batch remove some keys from the MMKV instance. * * @param arrKeys The keys to be removed. */ public native void removeValuesForKeys(String[] arrKeys); /** * Clear all the key-values inside the MMKV instance. * The data file will be trimmed down to `pageSize`, and some sync operations will be called * If you do not want to trim the file, use {@link #clearAllWithKeepingSpace()} instead for better performance */ public native void clearAll(); /** * Faster {@link #clearAll()} implementation * The file size is kept as previous for later use */ public native void clearAllWithKeepingSpace(); /** * The {@link #totalSize()} of an MMKV instance won't reduce after deleting key-values, * call this method after lots of deleting if you care about disk usage. * Note that {@link #clearAll()} has a similar effect. */ public native void trim(); /** * import all key-value items from src * @return count of items imported */ public long importFrom(MMKV src) { return importFrom(nativeHandle, src.nativeHandle); } /** * Call this method if the MMKV instance is no longer needed in the near future. * Any subsequent call to any MMKV instances with the same ID is undefined behavior. */ public native void close(); /** * Clear memory cache of the MMKV instance. * You can call it on memory warning. * Any subsequent call to the MMKV instance will trigger all key-values loading from the file again. */ public native void clearMemoryCache(); /** * Save all mmap memory to file synchronously. * You don't need to call this, really, I mean it. * Unless you worry about the device running out of battery. */ public void sync() { sync(true); } /** * Save all mmap memory to file asynchronously. * No need to call this unless you worry about the device running out of battery. */ public void async() { sync(false); } private native void sync(boolean sync); /** * Check whether the MMKV file is valid or not. * Note: Don't use this to check the existence of the instance, the result is undefined on nonexistent files. */ public static boolean isFileValid(String mmapID) { return isFileValid(mmapID, null); } /** * Check whether the MMKV file is valid or not on customize folder. * * @param mmapID The unique ID of the MMKV instance. * @param rootPath The folder of the MMKV instance, defaults to $(FilesDir)/mmkv. */ public static native boolean isFileValid(String mmapID, @Nullable String rootPath); /** * remove the storage of the MMKV, including the data file & meta file (.crc) * Note: the existing instance (if any) will be closed & destroyed * * @param mmapID The unique ID of the MMKV instance. */ public static boolean removeStorage(String mmapID) { return removeStorage(mmapID, null); } /** * remove the storage of the MMKV, including the data file & meta file (.crc) * Note: the existing instance (if any) will be closed & destroyed * * @param mmapID The unique ID of the MMKV instance. * @param rootPath The folder of the MMKV instance, defaults to $(FilesDir)/mmkv. */ public static native boolean removeStorage(String mmapID, @Nullable String rootPath); /** * check existence of the MMKV file * @param mmapID The unique ID of the MMKV instance. */ public static boolean checkExist(String mmapID) { return checkExist(mmapID, null); } /** * check existence of the MMKV file * @param mmapID The unique ID of the MMKV instance. * @param rootPath The folder of the MMKV instance, defaults to $(FilesDir)/mmkv. */ public static native boolean checkExist(String mmapID, @Nullable String rootPath); /** * Atomically migrate all key-values from an existent SharedPreferences to the MMKV instance. * * @param preferences The SharedPreferences to import from. * @return The total count of key-values imported. */ @SuppressWarnings("unchecked") public int importFromSharedPreferences(@NonNull SharedPreferences preferences) { Map kvs = preferences.getAll(); if (kvs == null || kvs.size() <= 0) { return 0; } for (Map.Entry entry : kvs.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if (key == null || value == null) { continue; } if (value instanceof Boolean) { encodeBool(nativeHandle, key, (boolean) value); } else if (value instanceof Integer) { encodeInt(nativeHandle, key, (int) value); } else if (value instanceof Long) { encodeLong(nativeHandle, key, (long) value); } else if (value instanceof Float) { encodeFloat(nativeHandle, key, (float) value); } else if (value instanceof Double) { encodeDouble(nativeHandle, key, (double) value); } else if (value instanceof String) { encodeString(nativeHandle, key, (String) value); } else if (value instanceof Set) { encode(key, (Set) value); } else { simpleLog(MMKVLogLevel.LevelError, "unknown type: " + value.getClass()); } } return kvs.size(); } /** * backup one MMKV instance to dstDir * * @param mmapID the MMKV ID to backup * @param rootPath the customize root path of the MMKV, if null then backup from the root dir of MMKV * @param dstDir the backup destination directory */ public static native boolean backupOneToDirectory(String mmapID, String dstDir, @Nullable String rootPath); /** * restore one MMKV instance from srcDir * * @param mmapID the MMKV ID to restore * @param srcDir the restore source directory * @param rootPath the customize root path of the MMKV, if null then restore to the root dir of MMKV */ public static native boolean restoreOneMMKVFromDirectory(String mmapID, String srcDir, @Nullable String rootPath); /** * backup all MMKV instance from default root dir to dstDir * * @param dstDir the backup destination directory * @return count of MMKV successfully backuped */ public static native long backupAllToDirectory(String dstDir); /** * restore all MMKV instance from srcDir to default root dir * * @param srcDir the restore source directory * @return count of MMKV successfully restored */ public static native long restoreAllFromDirectory(String srcDir); // TODO: we can't have these functionality because we can't know for sure // that each instance inside NameSpace has been upgraded successfully or not. // The workaround is to manually call backup/restore on each instance of NameSpace. // /** // * backup all MMKV instance from srcDir to dstDir // * // * @param dstDir the backup destination directory // * @param srcDir the backup source directory // * @return count of MMKV successfully backuped // */ // public static native long backupAllToDirectory(String dstDir, String srcDir); // // /** // * restore all MMKV instance from srcDir to dstDir // * // * @param srcDir the restore source directory // * @param dstDir the restore destination directory // * @return count of MMKV successfully restored // */ // public static native long restoreAllFromDirectory(String srcDir, String dstDir); public static final int ExpireNever = 0; public static final int ExpireInMinute = 60; public static final int ExpireInHour = 60 * 60; public static final int ExpireInDay = 24 * 60 * 60; public static final int ExpireInMonth = 30 * 24 * 60 * 60; public static final int ExpireInYear = 365 * 30 * 24 * 60 * 60; /** * Enable auto key expiration. This is a upgrade operation, the file format will change. * And the file won't be accessed correctly by older version (v1.2.16) of MMKV. * NOTICE: enableCompareBeforeSet will be invalid when Expiration is on * @param expireDurationInSecond the expire duration for all keys, {@link #ExpireNever} (0) means no default duration (aka each key will have it's own expire date) */ public native boolean enableAutoKeyExpire(int expireDurationInSecond); /** * Disable auto key expiration. This is a downgrade operation. */ public native boolean disableAutoKeyExpire(); /** * Enable data compare before set, for better performance. * If data for key seldom changes, use it. * When encryption or expiration is on, compare-before-set will be invalid. * For encryption, compare operation must decrypt data which is time consuming. * For expiration, compare is useless because in most cases the expiration time changes every time. */ public void enableCompareBeforeSet() { if (isExpirationEnabled()) { Log.e("MMKV", "enableCompareBeforeSet is invalid when Expiration is on"); if (BuildConfig.DEBUG) { throw new RuntimeException("enableCompareBeforeSet is invalid when Expiration is on"); } } if (isEncryptionEnabled()) { Log.e("MMKV", "enableCompareBeforeSet is invalid when key encryption is on"); if (BuildConfig.DEBUG) { throw new RuntimeException("enableCompareBeforeSet is invalid when Expiration is on"); } } nativeEnableCompareBeforeSet(); } @FastNative private native void nativeEnableCompareBeforeSet(); /** * Disable data compare before set * disabled at default */ public native void disableCompareBeforeSet(); /** * Intentionally Not Supported. Because MMKV does type-eraser inside to get better performance. */ @Override public Map getAll() { throw new UnsupportedOperationException( "Intentionally Not Supported. Use allKeys() instead, getAll() not implement because type-erasure inside mmkv"); } @Nullable @Override public String getString(String key, @Nullable String defValue) { return decodeString(nativeHandle, key, defValue); } @Override public Editor putString(String key, @Nullable String value) { encodeString(nativeHandle, key, value); return this; } public Editor putString(String key, @Nullable String value, int expireDurationInSecond) { encodeString_2(nativeHandle, key, value, expireDurationInSecond); return this; } @Nullable @Override public Set getStringSet(String key, @Nullable Set defValues) { return decodeStringSet(key, defValues); } @Override public Editor putStringSet(String key, @Nullable Set values) { encode(key, values); return this; } public Editor putStringSet(String key, @Nullable Set values, int expireDurationInSecond) { encode(key, values, expireDurationInSecond); return this; } public Editor putBytes(String key, @Nullable byte[] bytes) { encode(key, bytes); return this; } public Editor putBytes(String key, @Nullable byte[] bytes, int expireDurationInSecond) { encode(key, bytes, expireDurationInSecond); return this; } public byte[] getBytes(String key, @Nullable byte[] defValue) { return decodeBytes(key, defValue); } @Override public int getInt(String key, int defValue) { return decodeInt(nativeHandle, key, defValue); } @Override public Editor putInt(String key, int value) { encodeInt(nativeHandle, key, value); return this; } public Editor putInt(String key, int value, int expireDurationInSecond) { encodeInt_2(nativeHandle, key, value, expireDurationInSecond); return this; } @Override public long getLong(String key, long defValue) { return decodeLong(nativeHandle, key, defValue); } @Override public Editor putLong(String key, long value) { encodeLong(nativeHandle, key, value); return this; } public Editor putLong(String key, long value, int expireDurationInSecond) { encodeLong_2(nativeHandle, key, value, expireDurationInSecond); return this; } @Override public float getFloat(String key, float defValue) { return decodeFloat(nativeHandle, key, defValue); } @Override public Editor putFloat(String key, float value) { encodeFloat(nativeHandle, key, value); return this; } public Editor putFloat(String key, float value, int expireDurationInSecond) { encodeFloat_2(nativeHandle, key, value, expireDurationInSecond); return this; } @Override public boolean getBoolean(String key, boolean defValue) { return decodeBool(nativeHandle, key, defValue); } @Override public Editor putBoolean(String key, boolean value) { encodeBool(nativeHandle, key, value); return this; } public Editor putBoolean(String key, boolean value, int expireDurationInSecond) { encodeBool_2(nativeHandle, key, value, expireDurationInSecond); return this; } @Override public Editor remove(String key) { removeValueForKey(key); return this; } /** * {@link #clearAll()} */ @Override public Editor clear() { clearAll(); return this; } /** * @deprecated This method is only for compatibility purpose. You should remove all the calls after migration to MMKV. * MMKV doesn't rely on commit() to save data to file. * If you really worry about losing battery and data corruption, call {@link #async()} or {@link #sync()} instead. */ @Override @Deprecated public boolean commit() { sync(true); return true; } /** * @deprecated This method is only for compatibility purpose. You should remove all the calls after migration to MMKV. * MMKV doesn't rely on apply() to save data to file. * If you really worry about losing battery and data corruption, call {@link #async()} instead. */ @Override @Deprecated public void apply() { sync(false); } @Override public boolean contains(String key) { return containsKey(key); } @Override public Editor edit() { return this; } /** * Intentionally Not Supported by MMKV. We believe it's better not for a storage framework to notify the change of data. * Check {@link #registerContentChangeNotify} for a potential replacement on inter-process scene. */ @Override public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { throw new UnsupportedOperationException("Intentionally Not implement in MMKV"); } /** * Intentionally Not Supported by MMKV. We believe it's better not for a storage framework to notify the change of data. */ @Override public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { throw new UnsupportedOperationException("Intentionally Not implement in MMKV"); } /** * Get an ashmem MMKV instance that has been initiated by another process. * Normally you should just call {@link #mmkvWithAshmemID(Context, String, int, int, String)} instead. * * @param mmapID The unique ID of the MMKV instance. * @param fd The file descriptor of the ashmem of the MMKV file, transferred from another process by binder. * @param metaFD The file descriptor of the ashmem of the MMKV crc file, transferred from another process by binder. * @param cryptKey The encryption key of the MMKV instance (no more than 16 bytes). * @throws RuntimeException If any failure in JNI or runtime. */ // Parcelable @NonNull @Contract("_, _, _, _ -> new") public static MMKV mmkvWithAshmemFD(String mmapID, int fd, int metaFD, String cryptKey) throws RuntimeException { return mmkvWithAshmemFD(mmapID, fd, metaFD, cryptKey, false); } /** * Get an ashmem MMKV instance that has been initiated by another process. * Normally you should just call {@link #mmkvWithAshmemID(Context, String, int, int, String)} instead. * * @param mmapID The unique ID of the MMKV instance. * @param fd The file descriptor of the ashmem of the MMKV file, transferred from another process by binder. * @param metaFD The file descriptor of the ashmem of the MMKV crc file, transferred from another process by binder. * @param cryptKey The encryption key of the MMKV instance (no more than 32 bytes). * @param aes256 Use AES 256 key length. * @throws RuntimeException If any failure in JNI or runtime. */ // Parcelable @NonNull @Contract("_, _, _, _, _ -> new") public static MMKV mmkvWithAshmemFD(String mmapID, int fd, int metaFD, String cryptKey, boolean aes256) throws RuntimeException { long handle = getMMKVWithAshmemFD(mmapID, fd, metaFD, cryptKey, aes256); if (handle == 0) { throw new RuntimeException("Fail to create an ashmem MMKV instance [" + mmapID + "] in JNI"); } return new MMKV(handle); } /** * @return The file descriptor of the ashmem of the MMKV file. */ public native int ashmemFD(); /** * @return The file descriptor of the ashmem of the MMKV crc file. */ public native int ashmemMetaFD(); /** * Create an native buffer, whose underlying memory can be directly transferred to another JNI method. * Avoiding unnecessary JNI boxing and unboxing. * An NativeBuffer must be manually {@link #destroyNativeBuffer} to avoid memory leak. * * @param size The size of the underlying memory. */ @Nullable public static NativeBuffer createNativeBuffer(int size) { long pointer = createNB(size); if (pointer == 0) { return null; } return new NativeBuffer(pointer, size); } /** * Destroy the native buffer. An NativeBuffer must be manually destroy to avoid memory leak. */ public static void destroyNativeBuffer(@NonNull NativeBuffer buffer) { destroyNB(buffer.pointer, buffer.size); } /** * Write the value of the key to the native buffer. * * @return The size written. Return -1 on any error. */ public int writeValueToNativeBuffer(String key, @NonNull NativeBuffer buffer) { return writeValueToNB(nativeHandle, key, buffer.pointer, buffer.size); } // callback handler private static MMKVHandler gCallbackHandler = null; private static boolean gWantLogReDirecting = false; /** * Register a handler for MMKV log redirecting, and error handling. * * @deprecated This method is deprecated. * Use the {@link #initialize(Context, String, LibLoader, MMKVLogLevel, MMKVHandler)} method instead. */ public static void registerHandler(MMKVHandler handler) { gCallbackHandler = handler; gWantLogReDirecting = gCallbackHandler.wantLogRedirecting(); long nativeLogHandler = gCallbackHandler.getNativeLogHandler(); setCallbackHandler(gWantLogReDirecting, true, nativeLogHandler); if (gCallbackHandler.wantContentChangeNotification()) { setWantsContentChangeNotify(true); } } /** * Unregister the handler for MMKV. */ public static void unregisterHandler() { gCallbackHandler = null; setCallbackHandler(false, false, 0); gWantLogReDirecting = false; setWantsContentChangeNotify(gContentChangeNotify != null); } private static int onMMKVCRCCheckFail(String mmapID) { MMKVRecoverStrategic strategic = MMKVRecoverStrategic.OnErrorDiscard; MMKVHandler handler = gCallbackHandler; if (handler != null) { strategic = handler.onMMKVCRCCheckFail(mmapID); } simpleLog(MMKVLogLevel.LevelInfo, "Recover strategic for " + mmapID + " is " + strategic); Integer value = recoverIndex.get(strategic); return (value == null) ? 0 : value; } private static int onMMKVFileLengthError(String mmapID) { MMKVRecoverStrategic strategic = MMKVRecoverStrategic.OnErrorDiscard; MMKVHandler handler = gCallbackHandler; if (handler != null) { strategic = handler.onMMKVFileLengthError(mmapID); } simpleLog(MMKVLogLevel.LevelInfo, "Recover strategic for " + mmapID + " is " + strategic); Integer value = recoverIndex.get(strategic); return (value == null) ? 0 : value; } private static void mmkvLogImp(int level, String file, int line, String function, String message) { MMKVHandler handler = gCallbackHandler; if (handler != null && gWantLogReDirecting) { handler.mmkvLog(index2LogLevel[level], file, line, function, message); } else { switch (index2LogLevel[level]) { case LevelDebug: Log.d("MMKV", message); break; case LevelInfo: Log.i("MMKV", message); break; case LevelWarning: Log.w("MMKV", message); break; case LevelError: Log.e("MMKV", message); break; case LevelNone: break; } } } private static void simpleLog(MMKVLogLevel level, String message) { StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); StackTraceElement e = stacktrace[stacktrace.length - 1]; Integer i = logLevel2Index.get(level); int intLevel = (i == null) ? 0 : i; mmkvLogImp(intLevel, e.getFileName(), e.getLineNumber(), e.getMethodName(), message); } // content change notification of other process // trigger by getXXX() or setXXX() or checkContentChangedByOuterProcess() private static MMKVContentChangeNotification gContentChangeNotify; /** * Register for MMKV inter-process content change notification. * The notification will trigger only when any method is manually called on the MMKV instance. * 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 || (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(gCallbackHandler != null && gCallbackHandler.wantContentChangeNotification()); } private static void onContentChangedByOuterProcess(String mmapID) { 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); /** * Check inter-process content change manually. */ public native void checkContentChangedByOuterProcess(); /** * Check if this instance is in multi-process mode. */ public native boolean isMultiProcess(); /** * Check if this instance is in read-only mode. */ public native boolean isReadOnly(); // jni private final long nativeHandle; private MMKV(long handle) { nativeHandle = handle; } private static native void jniInitialize(String rootDir, String cacheDir, int level, boolean wantLogReDirecting, boolean hasCallback, long nativeHandler); native static long getMMKVWithID(String mmapID, int mode, @Nullable String cryptKey, @Nullable String rootPath, long expectedCapacity, boolean aes256, int enableKeyExpire, int expiredInSeconds, boolean enableCompareBeforeSet, int recover, int itemSizeLimit); private native static long getDefaultMMKV(int mode, @Nullable String cryptKey, long expectedCapacity, boolean aes256, int enableKeyExpire, int expiredInSeconds, boolean enableCompareBeforeSet, int recover, int itemSizeLimit); private native static long getMMKVWithAshmemFD(String mmapID, int fd, int metaFD, @Nullable String cryptKey, boolean aes256); private native boolean encodeBool(long handle, String key, boolean value); private native boolean encodeBool_2(long handle, String key, boolean value, int expireDurationInSecond); private native boolean decodeBool(long handle, String key, boolean defaultValue); private native boolean encodeInt(long handle, String key, int value); private native boolean encodeInt_2(long handle, String key, int value, int expireDurationInSecond); private native int decodeInt(long handle, String key, int defaultValue); private native boolean encodeLong(long handle, String key, long value); private native boolean encodeLong_2(long handle, String key, long value, int expireDurationInSecond); private native long decodeLong(long handle, String key, long defaultValue); private native boolean encodeFloat(long handle, String key, float value); private native boolean encodeFloat_2(long handle, String key, float value, int expireDurationInSecond); private native float decodeFloat(long handle, String key, float defaultValue); private native boolean encodeDouble(long handle, String key, double value); private native boolean encodeDouble_2(long handle, String key, double value, int expireDurationInSecond); private native double decodeDouble(long handle, String key, double defaultValue); private native boolean encodeString(long handle, String key, @Nullable String value); private native boolean encodeString_2(long handle, String key, @Nullable String value, int expireDurationInSecond); @Nullable private native String decodeString(long handle, String key, @Nullable String defaultValue); private native boolean encodeSet(long handle, String key, @Nullable String[] value); private native boolean encodeSet_2(long handle, String key, @Nullable String[] value, int expireDurationInSecond); @Nullable private native String[] decodeStringSet(long handle, String key); private native boolean encodeBytes(long handle, String key, @Nullable byte[] value); private native boolean encodeBytes_2(long handle, String key, @Nullable byte[] value, int expireDurationInSecond); @Nullable private native byte[] decodeBytes(long handle, String key); private native boolean containsKey(long handle, String key); private native String[] allKeys(long handle, boolean filterExpire); private native long count(long handle, boolean filterExpire); private native long totalSize(long handle); private native long actualSize(long handle); private native void removeValueForKey(long handle, String key); private native int valueSize(long handle, String key, boolean actualSize); private static native void setLogLevel(int level); private static native void setCallbackHandler(boolean logReDirecting, boolean hasCallback, long nativeHandle); private static native long createNB(int size); private static native void destroyNB(long pointer, int size); private native int writeValueToNB(long handle, String key, long pointer, int size); private native boolean isCompareBeforeSetEnabled(); @FastNative private native boolean isEncryptionEnabled(); @FastNative private native boolean isExpirationEnabled(); private static native void enableDisableProcessMode(boolean enable); private static native boolean checkProcessMode(long handle); private static native boolean getNameSpace(String rootPath); private native long importFrom(long handle, long srcHandle); } ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVConfig.java ================================================ /* * 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. */ package com.tencent.mmkv; /** * The all-in-one configuration for creating an MMKV instance. */ public class MMKVConfig { public int mode = MMKV.SINGLE_PROCESS_MODE; // using AES-256 key length public boolean aes256 = false; public String cryptKey = null; public String rootPath = null; public long expectedCapacity = 0; // the initial file size public Boolean enableKeyExpire = null; public int expiredInSeconds = 0; // ExpireNever = 0 public boolean enableCompareBeforeSet = false; // if not set, use the old style callback public MMKVRecoverStrategic recover = null; // the size limit of a key-value pair, reject insert if pass limit public int itemSizeLimit = 0; } ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVContentChangeNotification.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2025 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. */ 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. * Triggered by any method call, such as getXXX() or setXXX() or {@link MMKV#checkContentChangedByOuterProcess()}. * @param mmapID The unique ID of the changed MMKV instance. */ void onContentChangedByOuterProcess(String mmapID); } ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVContentProvider.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkv; import android.app.ActivityManager; import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * A helper class for MMKV based on Anonymous Shared Memory. {@link MMKV#mmkvWithAshmemID} */ public class MMKVContentProvider extends ContentProvider { static protected final String KEY = "KEY"; static protected final String KEY_SIZE = "KEY_SIZE"; static protected final String KEY_MODE = "KEY_MODE"; static protected final String KEY_CRYPT = "KEY_CRYPT"; static protected final String FUNCTION_NAME = "mmkvFromAshmemID"; static private Uri gUri; @Nullable static protected Uri contentUri(Context context) { if (MMKVContentProvider.gUri != null) { return MMKVContentProvider.gUri; } if (context == null) { return null; } String authority = queryAuthority(context); if (authority == null) { return null; } MMKVContentProvider.gUri = Uri.parse(ContentResolver.SCHEME_CONTENT + "://" + authority); return MMKVContentProvider.gUri; } @NonNull private Bundle mmkvFromAshmemID(String ashmemID, int size, int mode, String cryptKey) throws RuntimeException { MMKV mmkv = MMKV.mmkvWithAshmemID(getContext(), ashmemID, size, mode, cryptKey); ParcelableMMKV parcelableMMKV = new ParcelableMMKV(mmkv); Log.i("MMKV", ashmemID + " fd = " + mmkv.ashmemFD() + ", meta fd = " + mmkv.ashmemMetaFD()); Bundle result = new Bundle(); result.putParcelable(MMKVContentProvider.KEY, parcelableMMKV); return result; } @Nullable private static String queryAuthority(Context context) { try { ComponentName componentName = new ComponentName(context, MMKVContentProvider.class.getName()); PackageManager mgr = context.getPackageManager(); if (mgr != null) { ProviderInfo providerInfo = mgr.getProviderInfo(componentName, 0); if (providerInfo != null) { return providerInfo.authority; } } } catch (Exception e) { e.printStackTrace(); } return null; } @Override public boolean onCreate() { Context context = getContext(); if (context == null) { return false; } return true; } protected static String getProcessNameByPID(@NonNull Context context, int pid) { if (pid == android.os.Process.myPid()) { return MMKVProcessUtil.getCurrentProcessName(context); } ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (manager != null) { // clang-format off for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) { if (processInfo.pid == pid) { return processInfo.processName; } } // clang-format on } return ""; } @Nullable @Override public Bundle call(@NonNull String method, @Nullable String mmapID, @Nullable Bundle extras) { if (method.equals(MMKVContentProvider.FUNCTION_NAME)) { if (extras != null) { int size = extras.getInt(MMKVContentProvider.KEY_SIZE); int mode = extras.getInt(MMKVContentProvider.KEY_MODE); String cryptKey = extras.getString(MMKVContentProvider.KEY_CRYPT); try { return mmkvFromAshmemID(mmapID, size, mode, cryptKey); } catch (Exception e) { Log.e("MMKV", e.getMessage()); return null; } } } return null; } @Nullable @Override public String getType(@NonNull Uri uri) { return null; } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { throw new java.lang.UnsupportedOperationException("Not implement in MMKV"); } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { throw new java.lang.UnsupportedOperationException("Not implement in MMKV"); } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { throw new java.lang.UnsupportedOperationException("Not implement in MMKV"); } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { throw new java.lang.UnsupportedOperationException("Not implement in MMKV"); } } ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVHandler.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkv; /** * Unified callback handler for MMKV. * Callback is called on the operating thread of the MMKV instance. */ public interface MMKVHandler { /** * By default MMKV will discard all data on crc32-check failure. {@link MMKVRecoverStrategic#OnErrorDiscard} * @param mmapID The unique ID of the MMKV instance. * @return Return {@link MMKVRecoverStrategic#OnErrorRecover} to recover any data on the file. */ MMKVRecoverStrategic onMMKVCRCCheckFail(String mmapID); /** * By default MMKV will discard all data on file length mismatch. {@link MMKVRecoverStrategic#OnErrorDiscard} * @param mmapID The unique ID of the MMKV instance. * @return Return {@link MMKVRecoverStrategic#OnErrorRecover} to recover any data on the file. */ MMKVRecoverStrategic onMMKVFileLengthError(String mmapID); /** * @return Return False if you don't want log redirecting. */ boolean wantLogRedirecting(); /** * Log Redirecting. * @param level The level of this log. * @param file The file name of this log. * @param line The line of code of this log. * @param function The function name of this log. * @param message The content of this log. */ void mmkvLog(MMKVLogLevel level, String file, int line, String function, String message); /** * handle the log in NDK native code * @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 */ 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) {} } ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVLogLevel.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2025 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. */ package com.tencent.mmkv; /** * The levels of MMKV log. */ public enum MMKVLogLevel { /** * Debug level. Not available for release/production build. */ LevelDebug, /** * Info level. The default level. */ LevelInfo, /** * Warning level. */ LevelWarning, /** * Error level. */ LevelError, /** * Special level for disabling all logging. * It's highly NOT suggested to turn off logging. Makes it hard to diagnose online/production bugs. */ LevelNone } ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVProcessUtil.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2025 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. */ package com.tencent.mmkv; import android.app.ActivityManager; import android.app.Application; import android.content.Context; import android.os.Build; import android.text.TextUtils; import androidx.annotation.NonNull; import java.lang.reflect.Method; import java.util.List; /** * Get current process name for AppStore review */ class MMKVProcessUtil { private static String currentProcessName = ""; public static String getCurrentProcessName(@NonNull Context context) { if (!TextUtils.isEmpty(currentProcessName)) { return currentProcessName; } currentProcessName = getCurrentProcessNameByApplication(); if (!TextUtils.isEmpty(currentProcessName)) { return currentProcessName; } currentProcessName = getCurrentProcessNameByActivityThread(); if (!TextUtils.isEmpty(currentProcessName)) { return currentProcessName; } currentProcessName = getCurrentProcessNameByActivityManager(context); return currentProcessName; } @NonNull private static String getCurrentProcessNameByApplication() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { return Application.getProcessName(); } return ""; } @NonNull private static String getCurrentProcessNameByActivityThread() { String processName = ""; try { Method declaredMethod = Class.forName("android.app.ActivityThread"). getDeclaredMethod("currentProcessName"); declaredMethod.setAccessible(true); final Object invoke = declaredMethod.invoke(null); if (invoke instanceof String) { processName = (String) invoke; } } catch (Throwable e) { e.printStackTrace(); } return processName; } private static String getCurrentProcessNameByActivityManager(@NonNull Context context) { int pid = android.os.Process.myPid(); ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (am != null) { List runningAppList = am.getRunningAppProcesses(); if (runningAppList != null) { for (ActivityManager.RunningAppProcessInfo processInfo : runningAppList) { if (processInfo.pid == pid) { return processInfo.processName; } } } } return ""; } } ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVRecoverStrategic.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkv; /** * The recover strategic of MMKV on errors. {@link MMKV#registerHandler} */ public enum MMKVRecoverStrategic { /** * The default strategic is to discard everything on errors. */ OnErrorDiscard, /** * The recover strategic will try to recover as much data as possible. */ OnErrorRecover, } ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/NameSpace.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2025 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. */ package com.tencent.mmkv; import static com.tencent.mmkv.MMKV.SINGLE_PROCESS_MODE; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * a facade wraps custom root directory */ public final class NameSpace { private final String rootDir; // make it internal NameSpace(String dir) { rootDir = dir; } /** * @return The root folder of this NameSpace. */ public String getRootDir() { return rootDir; } /** * Create an MMKV instance with an unique ID (in single-process mode). * * @param mmapID The unique ID of the MMKV instance. * @throws RuntimeException if there's an runtime error. */ @NonNull public MMKV mmkvWithID(String mmapID) throws RuntimeException { return mmkvWithID(mmapID, new MMKVConfig()); } /** * Create an MMKV instance with an unique ID (in single-process mode). * * @param mmapID The unique ID of the MMKV instance. * @param config The all-in-one configuration for the MMKV instance. * @throws RuntimeException if there's an runtime error. */ @NonNull public MMKV mmkvWithID(String mmapID, MMKVConfig config) throws RuntimeException { int enableKeyExpire = (config.enableKeyExpire != null) ? (config.enableKeyExpire ? 1 : 0) : -1; int recover = (config.recover == null) ? -1 : ((config.recover == MMKVRecoverStrategic.OnErrorDiscard) ? 0 : 1); long handle = MMKV.getMMKVWithID(mmapID, config.mode, config.cryptKey, rootDir, config.expectedCapacity, config.aes256, enableKeyExpire, config.expiredInSeconds, config.enableCompareBeforeSet, recover, config.itemSizeLimit); return MMKV.checkProcessMode(handle, mmapID, config.mode); } /** * Create an MMKV instance in single-process or multi-process mode. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @throws RuntimeException if there's an runtime error. */ @NonNull public MMKV mmkvWithID(String mmapID, int mode) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance in single-process or multi-process mode. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param expectedCapacity The file size you expected when opening or creating file * @throws RuntimeException if there's an runtime error. */ @NonNull public MMKV mmkvWithID(String mmapID, int mode, long expectedCapacity) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.expectedCapacity = expectedCapacity; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance in customize process mode, with an encryption key. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 16 bytes). * @throws RuntimeException if there's an runtime error. */ @NonNull public MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.cryptKey = cryptKey; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance in customize process mode, with an encryption key. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 32 bytes). * @param aes256 Use AES 256 key length. * @throws RuntimeException if there's an runtime error. */ @NonNull public MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey, boolean aes256) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.aes256 = aes256; config.cryptKey = cryptKey; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance with customize settings all in one. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 16 bytes). * @param expectedCapacity The file size you expected when opening or creating file * @throws RuntimeException if there's an runtime error. */ @NonNull public MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey, long expectedCapacity) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.cryptKey = cryptKey; config.expectedCapacity = expectedCapacity; return mmkvWithID(mmapID, config); } /** * Create an MMKV instance with customize settings all in one. * * @param mmapID The unique ID of the MMKV instance. * @param mode The process mode of the MMKV instance, defaults to {@link #SINGLE_PROCESS_MODE}. * @param cryptKey The encryption key of the MMKV instance (no more than 32 bytes). * @param aes256 Use AES 256 key length. * @param expectedCapacity The file size you expected when opening or creating file * @throws RuntimeException if there's an runtime error. */ @NonNull public MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey, boolean aes256, long expectedCapacity) throws RuntimeException { MMKVConfig config = new MMKVConfig(); config.mode = mode; config.aes256 = aes256; config.cryptKey = cryptKey; config.expectedCapacity = expectedCapacity; return mmkvWithID(mmapID, config); } /** * backup one MMKV instance to dstDir * * @param mmapID the MMKV ID to backup * @param dstDir the backup destination directory */ public boolean backupOneToDirectory(String mmapID, String dstDir) { return MMKV.backupOneToDirectory(mmapID, dstDir, rootDir); } /** * restore one MMKV instance from srcDir * * @param mmapID the MMKV ID to restore * @param srcDir the restore source directory */ public boolean restoreOneMMKVFromDirectory(String mmapID, String srcDir) { return MMKV.restoreOneMMKVFromDirectory(mmapID, srcDir, rootDir); } // TODO: we can't have these functionality because we can't know for sure // that each instance inside NameSpace has been upgraded successfully or not. // The workaround is to manually call backup/restore on each instance of NameSpace. // /** // * backup all MMKV instance to dstDir // * // * @param dstDir the backup destination directory // * @return count of MMKV successfully backuped // */ // public long backupAllToDirectory(String dstDir) { // return MMKV.backupAllToDirectory(dstDir, rootDir); // } // // /** // * restore all MMKV instance from srcDir // * // * @param srcDir the restore source directory // * @return count of MMKV successfully restored // */ // public long restoreAllFromDirectory(String srcDir) { // return MMKV.restoreAllFromDirectory(srcDir, rootDir); // } /** * Check whether the MMKV file is valid or not. * Note: Don't use this to check the existence of the instance, the result is undefined on nonexistent files. */ public boolean isFileValid(String mmapID) { return MMKV.isFileValid(mmapID, rootDir); } /** * remove the storage of the MMKV, including the data file & meta file (.crc) * Note: the existing instance (if any) will be closed & destroyed * * @param mmapID The unique ID of the MMKV instance. */ public boolean removeStorage(String mmapID) { return MMKV.removeStorage(mmapID, rootDir); } /** * check existence of the MMKV file * @param mmapID The unique ID of the MMKV instance. */ public boolean checkExist(String mmapID) { return MMKV.checkExist(mmapID, rootDir); } } ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/NativeBuffer.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2025 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. */ package com.tencent.mmkv; /** * A native memory wrapper, whose underlying memory can be passed to another JNI method directly. * Avoiding unnecessary JNI boxing and unboxing. * Must be destroy manually {@link MMKV#destroyNativeBuffer}. */ public final class NativeBuffer { public long pointer; public int size; public NativeBuffer(long ptr, int length) { pointer = ptr; size = length; } } ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/ParcelableMMKV.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkv; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.jetbrains.annotations.Contract; import java.io.IOException; /** * A helper class for MMKV based on Anonymous Shared Memory. {@link MMKV#mmkvWithAshmemID} */ public final class ParcelableMMKV implements Parcelable { private final String mmapID; private int ashmemFD = -1; private int ashmemMetaFD = -1; private String cryptKey = null; public ParcelableMMKV(@NonNull MMKV mmkv) { mmapID = mmkv.mmapID(); ashmemFD = mmkv.ashmemFD(); ashmemMetaFD = mmkv.ashmemMetaFD(); cryptKey = mmkv.cryptKey(); } private ParcelableMMKV(String id, int fd, int metaFD, String key) { mmapID = id; ashmemFD = fd; ashmemMetaFD = metaFD; cryptKey = key; } @Nullable public MMKV toMMKV() { if (ashmemFD >= 0 && ashmemMetaFD >= 0) { return MMKV.mmkvWithAshmemFD(mmapID, ashmemFD, ashmemMetaFD, cryptKey); } return null; } @Override public int describeContents() { return CONTENTS_FILE_DESCRIPTOR; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { try { dest.writeString(mmapID); ParcelFileDescriptor fd = ParcelFileDescriptor.fromFd(ashmemFD); ParcelFileDescriptor metaFD = ParcelFileDescriptor.fromFd(ashmemMetaFD); flags = flags | Parcelable.PARCELABLE_WRITE_RETURN_VALUE; fd.writeToParcel(dest, flags); metaFD.writeToParcel(dest, flags); if (cryptKey != null) { dest.writeString(cryptKey); } } catch (IOException e) { e.printStackTrace(); } } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Nullable @Override public ParcelableMMKV createFromParcel(@NonNull Parcel source) { String mmapID = source.readString(); ParcelFileDescriptor fd = ParcelFileDescriptor.CREATOR.createFromParcel(source); ParcelFileDescriptor metaFD = ParcelFileDescriptor.CREATOR.createFromParcel(source); String cryptKey = source.readString(); if (fd != null && metaFD != null) { return new ParcelableMMKV(mmapID, fd.detachFd(), metaFD.detachFd(), cryptKey); } return null; } @NonNull @Contract(value = "_ -> new", pure = true) @Override public ParcelableMMKV[] newArray(int size) { return new ParcelableMMKV[size]; } }; } ================================================ FILE: Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/UnsupportedArchitectureException.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2025 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. */ package com.tencent.mmkv; public class UnsupportedArchitectureException extends RuntimeException { public UnsupportedArchitectureException(String message) { super(message); } } ================================================ FILE: Android/MMKV/mmkvannotation/.gitignore ================================================ /build ================================================ FILE: Android/MMKV/mmkvannotation/build.gradle ================================================ apply plugin: 'com.android.library' android { namespace "com.tencent.mmkv" compileSdk rootProject.ext.compileSdk defaultConfig { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" minSdkVersion rootProject.ext.minSdkVersion } buildTypes { release { minifyEnabled false consumerProguardFiles 'proguard-rules.pro' } } } configurations { javadocDeps } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.annotation:annotation:1.9.1' javadocDeps 'androidx.annotation:annotation:1.9.1' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.6.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' } ================================================ FILE: Android/MMKV/mmkvannotation/src/main/AndroidManifest.xml ================================================ ================================================ FILE: Android/MMKV/mmkvannotation/src/main/java/dalvik/annotation/optimization/FastNative.java ================================================ /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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. */ package dalvik.annotation.optimization; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * An ART runtime built-in optimization for {@code native} methods to speed up JNI transitions: * Compared to normal {@code native} methods, {@code native} methods that are annotated with * {@literal @}{@code FastNative} use faster JNI transitions from managed code to the native code * and back. Calls from a {@literal @}{@code FastNative} method implementation to JNI functions * that access the managed heap or call managed code also have faster internal transitions. * *

* While executing a {@literal @}{@code FastNative} method, the garbage collection cannot * suspend the thread for essential work and may become blocked. Use with caution. Do not use * this annotation for long-running methods, including usually-fast, but generally unbounded, * methods. In particular, the code should not perform significant I/O operations or acquire * native locks that can be held for a long time. (Some logging or native allocations, which * internally acquire native locks for a short time, are generally OK. However, as the cost * of several such operations adds up, the {@literal @}{@code FastNative} performance gain * can become insignificant and overshadowed by potential GC delays.) * Acquiring managed locks is OK as it internally allows thread suspension. *

* *

* For performance critical methods that need this annotation, it is strongly recommended * to explicitly register the method(s) with JNI {@code RegisterNatives} instead of relying * on the built-in dynamic JNI linking. *

* *

* The {@literal @}{@code FastNative} optimization was implemented for system use since * Android 8 and became CTS-tested public API in Android 14. Developers aiming for maximum * compatibility should avoid calling {@literal @}{@code FastNative} methods on Android 13-. * The optimization is likely to work also on Android 8-13 devices (after all, it was used * in the system, albeit without the strong CTS guarantees), especially those that use * unmodified versions of ART, such as Android 12+ devices with the official ART Module. * The built-in dynamic JNI linking is working only in Android 12+, the explicit registration * with JNI {@code RegisterNatives} is strictly required for running on Android versions 8-11. * The annotation is ignored on Android 7-. *

* *

* Deadlock Warning: As a rule of thumb, any native locks acquired in a * {@literal @}{@link FastNative} call (despite the above warning that this is an unbounded * operation that can block GC for a long time) must be released before returning to managed code. *

* *

* Say some code does: * * * fast_jni_call_to_grab_a_lock(); * does_some_java_work(); * fast_jni_call_to_release_a_lock(); * * *

* This code can lead to deadlocks. Say thread 1 just finishes * {@code fast_jni_call_to_grab_a_lock()} and is in {@code does_some_java_work()}. * GC kicks in and suspends thread 1. Thread 2 now is in {@code fast_jni_call_to_grab_a_lock()} * but is blocked on grabbing the native lock since it's held by thread 1. * Now thread suspension can't finish since thread 2 can't be suspended since it's doing * FastNative JNI. *

* *

* Normal JNI doesn't have the issue since once it's in native code, * it is considered suspended from java's point of view. * FastNative JNI however doesn't do the state transition done by JNI. *

* *

* Note that even in FastNative methods you are allowed to * allocate objects and make upcalls into Java code. A call from Java to * a FastNative function and back to Java is equivalent to a call from one Java * method to another. What's forbidden in a FastNative method is blocking * the calling thread in some non-Java code and thereby preventing the thread * from responding to requests from the garbage collector to enter the suspended * state. *

* *

* Has no effect when used with non-native methods. *

*/ @Retention(RetentionPolicy.CLASS) // Save memory, don't instantiate as an object at runtime. @Target(ElementType.METHOD) public @interface FastNative { } ================================================ FILE: Android/MMKV/mmkvdemo/.gitignore ================================================ /build ================================================ FILE: Android/MMKV/mmkvdemo/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { namespace "com.tencent.mmkvdemo" compileSdk rootProject.ext.compileSdk signingConfigs { config { keyAlias 'key0' keyPassword 'mmkv.wxg' storeFile rootProject.file('debug.keystore') storePassword 'mmkv.wxg' } } defaultConfig { applicationId "com.tencent.mmkvdemo" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { arguments = ["-DANDROID_STL=c++_shared"] // use prefab or not: Gradle has bug handling local prefab modules, fails on linking // arguments += ["-DHAS_PREFAB=1"] cppFlags "-fvisibility=hidden", "-funwind-tables", "-fasynchronous-unwind-tables", "-O2", "-Wl,-z,max-page-size=16384" } } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.config // debuggable true // jniDebuggable true } debug { signingConfig signingConfigs.config jniDebuggable true } } sourceSets { main.java.srcDirs += 'src/main/kotlin' } flavorDimensions = ["stl_mode"] productFlavors { DefaultCpp.dimension = "stl_mode" StaticCpp.dimension = "stl_mode" SharedCpp.dimension = "stl_mode" } externalNativeBuild { cmake { path file('src/main/cpp/CMakeLists.txt') } } buildFeatures { aidl true // prefab true } kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } /*splits { // Configures multiple APKs based on ABI. abi { // Enables building multiple APKs per ABI. enable true // By default all ABIs are included, so use reset() and include to specify that we only want specific APKs // Resets the list of ABIs that Gradle should create APKs for to none. reset() // Specifies a list of ABIs that Gradle should create APKs for. include "armeabi-v7a" // Specifies that we do not want to also generate a universal APK that includes all ABIs. universalApk false } }*/ } repositories { mavenCentral() mavenLocal() } // Define the mmkv version to use def mmkvVersion = "${VERSION_NAME_PREFIX}${VERSION_NAME_SUFFIX}" dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation project(':mmkv') // implementation "com.tencent:mmkv:$mmkvVersion" // implementation "com.tencent:mmkv-static:$mmkvVersion" // this is identical to 'com.tencent:mmkv' // implementation "com.tencent:mmkv-shared:$mmkvVersion" // with prefab functionality implementation 'androidx.appcompat:appcompat:1.7.1' implementation 'androidx.constraintlayout:constraintlayout:2.2.1' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.7.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'com.getkeepsafe.relinker:relinker:1.4.5' } ================================================ FILE: Android/MMKV/mmkvdemo/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keepclasseswithmembers,includedescriptorclasses class com.tencent.mmkvdemo.** { native ; } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/AndroidManifest.xml ================================================ ================================================ FILE: Android/MMKV/mmkvdemo/src/main/aidl/com/tencent/mmkvdemo/IAshmemMMKV.aidl ================================================ // IAshmemMMKV.aidl package com.tencent.mmkvdemo; import com.tencent.mmkv.ParcelableMMKV; interface IAshmemMMKV { ParcelableMMKV GetAshmemMMKV(); } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/cpp/CMakeLists.txt ================================================ # For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html. # For more examples on how to use CMake, see https://github.com/android/ndk-samples. # Sets the minimum CMake version required for this project. cmake_minimum_required(VERSION 3.10.0) # Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, # Since this is the top level CMakeLists.txt, the project name is also accessible # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level # build script scope). project("mmkvdemo") # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. # # In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define # the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} # is preferred for the same purpose. # # In order to load a library into your app from Java/Kotlin, you must call # System.loadLibrary() and pass the name of the library defined here; # for GameActivity/NativeActivity derived applications, the same library name must be # used in the AndroidManifest.xml file. add_library(${CMAKE_PROJECT_NAME} SHARED # List C/C++ source files with relative paths to this CMakeLists.txt. mmkvdemo.cpp) # Find the mmkv lib in prefab if (HAS_PREFAB) find_package(mmkv REQUIRED CONFIG) target_link_libraries(${CMAKE_PROJECT_NAME} mmkv::mmkv ) target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -DENABLE_MMKV_NATIVE=1 ) endif () # Specifies libraries CMake should link to your target library. You # can link libraries from various origins, such as libraries defined in this # build script, prebuilt third-party libraries, or Android system libraries. target_link_libraries(${CMAKE_PROJECT_NAME} # List libraries link to the target library android log ) ================================================ FILE: Android/MMKV/mmkvdemo/src/main/cpp/mmkvdemo.cpp ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ // A demo for using MMKV C++ interface in native code #include #include #include #ifdef ENABLE_MMKV_NATIVE #include #endif using namespace std; constexpr auto APP_NAME = "MMKVNativeDemo"; void _MMKVLogWithLevel(android_LogPriority level, const char *filename, const char *func, int line, const char *format, ...) { string message; char buffer[16]; va_list args; va_start(args, format); auto length = std::vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); if (length < 0) { // something wrong message = {}; } else if (length < sizeof(buffer)) { message = string(buffer, static_cast(length)); } else { message.resize(static_cast(length), '\0'); va_start(args, format); std::vsnprintf(const_cast(message.data()), static_cast(length) + 1, format, args); va_end(args); } __android_log_print(level, APP_NAME, "<%s:%d::%s> %s", filename, line, func, message.c_str()); } #define MMKVError(format, ...) \ _MMKVLogWithLevel(ANDROID_LOG_ERROR, __FILE_NAME__, __func__, __LINE__, format, ##__VA_ARGS__) #define MMKVWarning(format, ...) \ _MMKVLogWithLevel(ANDROID_LOG_WARN, __FILE_NAME__, __func__, __LINE__, format, ##__VA_ARGS__) #define MMKVInfo(format, ...) \ _MMKVLogWithLevel(ANDROID_LOG_INFO, __FILE_NAME__, __func__, __LINE__, format, ##__VA_ARGS__) #define MMKVLog MMKVInfo static string jstring2string(JNIEnv *env, jstring str) { if (str) { const char *kstr = env->GetStringUTFChars(str, nullptr); if (kstr) { string result(kstr); env->ReleaseStringUTFChars(str, kstr); return result; } } return ""; } string to_string(const std::string& str) { return str; } template string to_string(const vector &arr, const char* sp = ", ") { string str; for (const auto &element : arr) { str += to_string(element); str += sp; } if (!str.empty()) { str.erase(str.length() - strlen(sp)); } return str; } #ifdef ENABLE_MMKV_NATIVE void functionalTest(MMKV *mmkv, bool decodeOnly) { if (!decodeOnly) { mmkv->set(true, "bool"); } MMKVLog("bool = %d\n", mmkv->getBool("bool")); if (!decodeOnly) { mmkv->set(1024, "int32"); } MMKVLog("int32 = %d\n", mmkv->getInt32("int32")); if (!decodeOnly) { mmkv->set(std::numeric_limits::max(), "uint32"); } MMKVLog("uint32 = %u\n", mmkv->getUInt32("uint32")); if (!decodeOnly) { mmkv->set(std::numeric_limits::min(), "int64"); } MMKVLog("int64 = %lld\n", mmkv->getInt64("int64")); if (!decodeOnly) { mmkv->set(std::numeric_limits::max(), "uint64"); } MMKVLog("uint64 = %llu\n", mmkv->getUInt64("uint64")); if (!decodeOnly) { mmkv->set(3.14f, "float"); } MMKVLog("float = %f\n", mmkv->getFloat("float")); if (!decodeOnly) { mmkv->set(std::numeric_limits::max(), "double"); } MMKVLog("double = %f\n", mmkv->getDouble("double")); if (!decodeOnly) { mmkv->set("Hello, MMKV-示例 for POSIX", "raw_string"); std::string str = "Hello, MMKV-示例 for POSIX string"; mmkv->set(str, "string"); mmkv->set(std::string_view(str).substr(7, 21), "string_view"); } std::string result; mmkv->getString("raw_string", result); MMKVLog("raw_string = %s\n", result.c_str()); mmkv->getString("string", result); MMKVLog("string = %s\n", result.c_str()); mmkv->getString("string_view", result); MMKVLog("string_view = %s\n", result.c_str()); // containerTest(mmkv, decodeOnly); MMKVLog("allKeys: %s\n", ::to_string(mmkv->allKeys()).c_str()); MMKVLog("count = %zu, totalSize = %zu\n", mmkv->count(), mmkv->totalSize()); MMKVLog("containsKey[string]: %d\n", mmkv->containsKey("string")); mmkv->removeValueForKey("bool"); MMKVLog("bool: %d\n", mmkv->getBool("bool")); mmkv->removeValuesForKeys({"int", "long"}); mmkv->set("some string", "null string"); result.erase(); mmkv->getString("null string", result); MMKVLog("string before set null: %s\n", result.c_str()); mmkv->set((const char *) nullptr, "null string"); //mmkv->set("", "null string"); result.erase(); mmkv->getString("null string", result); MMKVLog("string after set null: %s, containsKey: %d\n", result.c_str(), mmkv->containsKey("null string")); //kv.sync(); //kv.async(); //kv.clearAll(); mmkv->clearMemoryCache(); MMKVLog("allKeys: %s\n", ::to_string(mmkv->allKeys()).c_str()); MMKVLog("isFileValid[%s]: %d\n", mmkv->mmapID().c_str(), MMKV::isFileValid(mmkv->mmapID())); } #endif static void testNameSpaceInNative(JNIEnv *env, jobject obj, jstring rootDir, jstring mmapID) { string root = jstring2string(env, rootDir); string id = jstring2string(env, mmapID); #ifdef ENABLE_MMKV_NATIVE auto ns = MMKV::nameSpace(root); auto kv = ns.mmkvWithID(id); functionalTest(kv, false); #else MMKVWarning("ENABLE_MMKV_NATIVE not defined."); #endif } #ifndef ENABLE_MMKV_NATIVE enum MMKVLogLevel : int { MMKVLogDebug = 0, // not available for release/product build MMKVLogInfo = 1, // default level MMKVLogWarning, MMKVLogError, MMKVLogNone, // special level used to disable all log messages }; #endif static android_LogPriority MMKVLogLevelDesc(MMKVLogLevel level) { switch (level) { case MMKVLogDebug: return ANDROID_LOG_DEBUG; case MMKVLogInfo: return ANDROID_LOG_INFO; case MMKVLogWarning: return ANDROID_LOG_WARN; case MMKVLogError: return ANDROID_LOG_ERROR; default: return ANDROID_LOG_UNKNOWN; } } static void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const char *message) { auto desc = MMKVLogLevelDesc(level); __android_log_print(desc, APP_NAME, "<%s:%d::%s> %s", file, line, function, message); } static jlong getNativeLogHandler(JNIEnv *env, jobject obj) { return (jlong) mmkvLog; } static JNINativeMethod g_methods[] = { {"testNameSpaceInNative", "(Ljava/lang/String;Ljava/lang/String;)V", (void *) testNameSpaceInNative}, {"getNativeLogHandler", "()J", (void *) getNativeLogHandler}, }; static int registerNativeMethods(JNIEnv *env, jclass cls) { return env->RegisterNatives(cls, g_methods, sizeof(g_methods) / sizeof(g_methods[0])); } extern "C" JNIEXPORT JNICALL jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } static const char *clsName = "com/tencent/mmkvdemo/MyApplication"; jclass instance = env->FindClass(clsName); if (!instance) { MMKVError("fail to locate class: %s", clsName); return -2; } int ret = registerNativeMethods(env, instance); if (ret != 0) { MMKVError("fail to register native methods for class %s, ret = %d", clsName, ret); return -4; } return JNI_VERSION_1_6; } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/Baseline.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkvdemo; import static android.content.Context.MODE_PRIVATE; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; import com.tencent.mmkv.MMKV; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Locale; import java.util.Random; public final class Baseline { private String[] m_arrStrings; private String[] m_arrKeys; private String[] m_arrIntKeys; private int m_loops = 1000; private Context m_context; private static final String MMKV_ID = "baseline3"; private static final String CryptKey = null; //private static final String CryptKey = "baseline_key3"; private static final String TAG = "MMKV"; private DecimalFormat m_formatter; Baseline(Context context, int loops) { m_context = context; m_loops = loops; m_arrStrings = new String[loops]; m_arrKeys = new String[loops]; m_arrIntKeys = new String[loops]; Random r = new Random(); final String filename = "mmkv/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/Baseline.java_"; for (int index = 0; index < loops; index++) { //String str = "[MMKV] [Info]: protection on [/var/mobile/Containers/Data/Application/B93F2BD3-E0DB-49B3-9BB0-C662E2FC11D9/Documents/mmkv/cips_commoncache] is NSFileProtectionCompleteUntilFirstUserAuthentication_"; //m_arrStrings[index] = str + r.nextInt(); m_arrStrings[index] = filename + r.nextInt(); m_arrKeys[index] = "str_" + index; m_arrIntKeys[index] = "int_" + index; } m_formatter = new DecimalFormat("0.0", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); m_formatter.setRoundingMode(RoundingMode.DOWN); } public void mmkvBaselineTest() { mmkvBatchWriteInt(); mmkvBatchReadInt(); mmkvBatchWriteString(); mmkvBatchReadString(); //mmkvBatchDeleteString(); MMKV mmkv = mmkvForTest(); //mmkv.trim(); mmkv.clearMemoryCache(); mmkv.totalSize(); } private MMKV mmkvForTest() { return MMKV.mmkvWithID(MMKV_ID, MMKV.SINGLE_PROCESS_MODE, CryptKey); //return MMKV.mmkvWithAshmemID(m_context, MMKV_ID, 65536, MMKV.SINGLE_PROCESS_MODE, CryptKey); } private void mmkvBatchWriteInt() { Random r = new Random(); long startTime = System.nanoTime(); MMKV mmkv = mmkvForTest(); for (int index = 0; index < m_loops; index++) { int tmp = r.nextInt(); String key = m_arrIntKeys[index]; mmkv.encode(key, tmp); } double endTime = (System.nanoTime() - startTime) / 1000000.0; Log.i(TAG, "MMKV write int: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } private void mmkvBatchReadInt() { long startTime = System.nanoTime(); MMKV mmkv = mmkvForTest(); for (int index = 0; index < m_loops; index++) { String key = m_arrIntKeys[index]; int tmp = mmkv.decodeInt(key); } double endTime = (System.nanoTime() - startTime) / 1000000.0; Log.i(TAG, "MMKV read int: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } private void mmkvBatchWriteString() { long startTime = System.nanoTime(); MMKV mmkv = mmkvForTest(); for (int index = 0; index < m_loops; index++) { final String valueStr = m_arrStrings[index]; final String strKey = m_arrKeys[index]; mmkv.encode(strKey, valueStr); } double endTime = (System.nanoTime() - startTime) / 1000000.0; Log.i(TAG, "MMKV write String: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } private void mmkvBatchReadString() { long startTime = System.nanoTime(); MMKV mmkv = mmkvForTest(); for (int index = 0; index < m_loops; index++) { String strKey = m_arrKeys[index]; String tmpStr = mmkv.decodeString(strKey); } double endTime = (System.nanoTime() - startTime) / 1000000.0; Log.i(TAG, "MMKV read String: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } private void mmkvBatchDeleteString() { long startTime = System.nanoTime(); MMKV mmkv = mmkvForTest(); for (int index = 0; index < m_loops; index++) { String strKey = m_arrKeys[index]; mmkv.removeValueForKey(strKey); } double endTime = (System.nanoTime() - startTime) / 1000000.0; Log.i(TAG, "MMKV delete String: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } public void sharedPreferencesBaselineTest() { spBatchWriteInt(); spBatchReadInt(); spBatchWriteString(); spBatchReadString(); } private void spBatchWriteInt() { Random r = new Random(); long startTime = System.nanoTime(); SharedPreferences preferences = m_context.getSharedPreferences(MMKV_ID, MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); for (int index = 0; index < m_loops; index++) { int tmp = r.nextInt(); String key = m_arrIntKeys[index]; editor.putInt(key, tmp); // editor.commit(); editor.apply(); } double endTime = (System.nanoTime() - startTime) / 1000000.0; Log.i(TAG, "SharedPreferences write int: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } private void spBatchReadInt() { long startTime = System.nanoTime(); SharedPreferences preferences = m_context.getSharedPreferences(MMKV_ID, MODE_PRIVATE); for (int index = 0; index < m_loops; index++) { String key = m_arrIntKeys[index]; int tmp = preferences.getInt(key, 0); } double endTime = (System.nanoTime() - startTime) / 1000000.0; Log.i(TAG, "SharedPreferences read int: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } private void spBatchWriteString() { long startTime = System.nanoTime(); SharedPreferences preferences = m_context.getSharedPreferences(MMKV_ID, MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); for (int index = 0; index < m_loops; index++) { final String str = m_arrStrings[index]; final String key = m_arrKeys[index]; editor.putString(key, str); // editor.commit(); editor.apply(); } double endTime = (System.nanoTime() - startTime) / 1000000.0; Log.i(TAG, "SharedPreferences write String: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } private void spBatchReadString() { long startTime = System.nanoTime(); SharedPreferences preferences = m_context.getSharedPreferences(MMKV_ID, MODE_PRIVATE); for (int index = 0; index < m_loops; index++) { final String key = m_arrKeys[index]; final String tmp = preferences.getString(key, null); } double endTime = (System.nanoTime() - startTime) / 1000000.0; Log.i(TAG, "SharedPreferences read String: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } public void sqliteBaselineTest(boolean useTransaction) { sqliteWriteInt(useTransaction); sqliteReadInt(useTransaction); sqliteWriteString(useTransaction); sqliteReadString(useTransaction); } private void sqliteWriteInt(boolean useTransaction) { Random r = new Random(); long startTime = System.nanoTime(); SQLiteKV sqliteKV = new SQLiteKV(m_context); if (useTransaction) { sqliteKV.beginTransaction(); } for (int index = 0; index < m_loops; index++) { int tmp = r.nextInt(); String key = m_arrIntKeys[index]; sqliteKV.putInt(key, tmp); } if (useTransaction) { sqliteKV.endTransaction(); } double endTime = (System.nanoTime() - startTime) / 1000000.0; final String msg = useTransaction ? "sqlite transaction" : "sqlite"; Log.i(TAG, msg + " write int: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } private void sqliteReadInt(boolean useTransaction) { long startTime = System.nanoTime(); SQLiteKV sqliteKV = new SQLiteKV(m_context); if (useTransaction) { sqliteKV.beginTransaction(); } for (int index = 0; index < m_loops; index++) { String key = m_arrIntKeys[index]; int tmp = sqliteKV.getInt(key); } if (useTransaction) { sqliteKV.endTransaction(); } double endTime = (System.nanoTime() - startTime) / 1000000.0; final String msg = useTransaction ? "sqlite transaction" : "sqlite"; Log.i(TAG, msg + " read int: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } private void sqliteWriteString(boolean useTransaction) { long startTime = System.nanoTime(); SQLiteKV sqliteKV = new SQLiteKV(m_context); if (useTransaction) { sqliteKV.beginTransaction(); } for (int index = 0; index < m_loops; index++) { final String value = m_arrStrings[index]; final String key = m_arrKeys[index]; sqliteKV.putString(key, value); } if (useTransaction) { sqliteKV.endTransaction(); } double endTime = (System.nanoTime() - startTime) / 1000000.0; final String msg = useTransaction ? "sqlite transaction" : "sqlite"; Log.i(TAG, msg + " write String: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } private void sqliteReadString(boolean useTransaction) { long startTime = System.nanoTime(); SQLiteKV sqliteKV = new SQLiteKV(m_context); if (useTransaction) { sqliteKV.beginTransaction(); } for (int index = 0; index < m_loops; index++) { final String key = m_arrKeys[index]; final String tmp = sqliteKV.getString(key); } if (useTransaction) { sqliteKV.endTransaction(); } double endTime = (System.nanoTime() - startTime) / 1000000.0; final String msg = useTransaction ? "sqlite transaction" : "sqlite"; Log.i(TAG, msg + " read String: loop[" + m_loops + "]: " + m_formatter.format(endTime) + " ms"); } } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/BenchMarkBaseService.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkvdemo; import android.app.Service; import android.content.Intent; import android.content.SharedPreferences; import android.os.IBinder; import android.util.Log; import androidx.annotation.Nullable; import com.tencent.mmkv.MMKV; import com.tencent.mmkv.MMKVConfig; import com.tencent.mmkv.ParcelableMMKV; import java.util.Random; public abstract class BenchMarkBaseService extends Service { public static final String CMD_ID = "cmd_id"; public static final String CMD_READ_INT = "cmd_read_int"; public static final String CMD_WRITE_INT = "cmd_write_int"; public static final String CMD_READ_STRING = "cmd_read_string"; public static final String CMD_WRITE_STRING = "cmd_write_string"; public static final String CMD_PREPARE_ASHMEM_BY_CP = "cmd_prepare_ashmem_by_ContentProvider"; public static final String CMD_PREPARE_ASHMEM_KEY = "cmd_prepare_ashmem_key"; // 1M, ashmem cannot change size after opened public static final int AshmemMMKV_Size = 1024 * 1024; public static final String AshmemMMKV_ID = "testAshmemMMKVByCP"; private String[] m_arrStrings; private String[] m_arrKeys; private String[] m_arrIntKeys; private static final int m_loops = 1000; public static final String MMKV_ID = "benchmark_interprocess"; //public static final String MMKV_ID = "benchmark_interprocess_crypt1"; private static final String SP_ID = "benchmark_interprocess_sp"; public static final String CryptKey = null; //public static final String CryptKey = "Tencent MMKV"; private static final boolean SQLite_Use_Transaction = false; private static final String TAG = "MMKV"; @Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate BenchMarkBaseService"); MMKV.initialize(this); MMKV.unregisterHandler(); MMKV.unregisterContentChangeNotify(); { long startTime = System.currentTimeMillis(); MMKV mmkv = MMKV.mmkvWithID(MMKV_ID, MMKV.MULTI_PROCESS_MODE, CryptKey); long endTime = System.currentTimeMillis(); Log.i(TAG, "load [" + MMKV_ID + "]: " + (endTime - startTime) + " ms"); } m_arrStrings = new String[m_loops]; m_arrKeys = new String[m_loops]; m_arrIntKeys = new String[m_loops]; Random r = new Random(); final String filename = "mmkv/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/BenchMarkBaseService.java_"; for (int index = 0; index < m_loops; index++) { //String str = "[MMKV] [Info]: protection on [/var/mobile/Containers/Data/Application/B93F2BD3-E0DB-49B3-9BB0-C662E2FC11D9/Documents/mmkv/cips_commoncache] is NSFileProtectionCompleteUntilFirstUserAuthentication_"; //m_arrStrings[index] = str + r.nextInt(); m_arrStrings[index] = filename + r.nextInt(); m_arrKeys[index] = "testStr_" + index; m_arrIntKeys[index] = "int_" + index; } } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy BenchMarkBaseService"); MMKV.onExit(); } protected void batchWriteInt(String caller) { mmkvBatchWriteInt(caller); sqliteWriteInt(caller, SQLite_Use_Transaction); spBatchWriteInt(caller); } private void mmkvBatchWriteInt(String caller) { Random r = new Random(); Log.i(TAG, caller + " mmkv write int: loop[" + m_loops + "] star."); long startTime = System.currentTimeMillis(); MMKV mmkv = GetMMKV(); for (int index = 0; index < m_loops; index++) { int tmp = r.nextInt(); String key = m_arrIntKeys[index]; // MMKV mmkv = MMKV.mmkvWithID(MMKV_ID + key, MMKV.MULTI_PROCESS_MODE, CryptKey); mmkv.encode(key, tmp); } long endTime = System.currentTimeMillis(); Log.i(TAG, caller + " mmkv write int: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } private void sqliteWriteInt(String caller, boolean useTransaction) { Random r = new Random(); long startTime = System.currentTimeMillis(); SQLiteKV sqliteKV = new SQLiteKV(this); if (useTransaction) { sqliteKV.beginTransaction(); } for (int index = 0; index < m_loops; index++) { int tmp = r.nextInt(); String key = m_arrIntKeys[index]; sqliteKV.putInt(key, tmp); } if (useTransaction) { sqliteKV.endTransaction(); } long endTime = System.currentTimeMillis(); final String msg = useTransaction ? " sqlite transaction " : " sqlite "; Log.i(TAG, caller + msg + "write int: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } private void spBatchWriteInt(String caller) { Random r = new Random(); long startTime = System.currentTimeMillis(); SharedPreferences preferences = MultiProcessSharedPreferences.getSharedPreferences(this, SP_ID, MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); for (int index = 0; index < m_loops; index++) { int tmp = r.nextInt(); String key = m_arrIntKeys[index]; editor.putInt(key, tmp); //editor.commit(); editor.apply(); } long endTime = System.currentTimeMillis(); Log.i(TAG, caller + " MultiProcessSharedPreferences write int: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } protected void batchReadInt(String caller) { mmkvBatchReadInt(caller); sqliteReadInt(caller, SQLite_Use_Transaction); spBatchReadInt(caller); } private void mmkvBatchReadInt(String caller) { Log.i(TAG, caller + " mmkv read int: loop[" + m_loops + "] star."); long startTime = System.currentTimeMillis(); MMKV mmkv = GetMMKV(); for (int index = 0; index < m_loops; index++) { String key = m_arrIntKeys[index]; int tmp = mmkv.decodeInt(key); } long endTime = System.currentTimeMillis(); Log.i(TAG, caller + " mmkv read int: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } private void sqliteReadInt(String caller, boolean useTransaction) { long startTime = System.currentTimeMillis(); SQLiteKV sqliteKV = new SQLiteKV(this); if (useTransaction) { sqliteKV.beginTransaction(); } for (int index = 0; index < m_loops; index++) { String key = m_arrIntKeys[index]; int tmp = sqliteKV.getInt(key); } if (useTransaction) { sqliteKV.endTransaction(); } long endTime = System.currentTimeMillis(); final String msg = useTransaction ? " sqlite transaction " : " sqlite "; Log.i(TAG, caller + msg + "read int: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } private void spBatchReadInt(String caller) { long startTime = System.currentTimeMillis(); SharedPreferences preferences = MultiProcessSharedPreferences.getSharedPreferences(this, SP_ID, MODE_PRIVATE); for (int index = 0; index < m_loops; index++) { String key = m_arrIntKeys[index]; int tmp = preferences.getInt(key, 0); } long endTime = System.currentTimeMillis(); Log.i(TAG, caller + " MultiProcessSharedPreferences read int: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } protected void batchWriteString(String caller) { mmkvBatchWriteString(caller); sqliteWriteString(caller, SQLite_Use_Transaction); spBatchWrieString(caller); } private void mmkvBatchWriteString(String caller) { Log.i(TAG, caller + " mmkv write String: loop[" + m_loops + "] star."); long startTime = System.currentTimeMillis(); MMKV mmkv = GetMMKV(); for (int index = 0; index < m_loops; index++) { final String valueStr = m_arrStrings[index]; final String strKey = m_arrKeys[index]; mmkv.encode(strKey, valueStr); } long endTime = System.currentTimeMillis(); Log.i(TAG, caller + " mmkv write String: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } private void sqliteWriteString(String caller, boolean useTransaction) { long startTime = System.currentTimeMillis(); SQLiteKV sqliteKV = new SQLiteKV(this); if (useTransaction) { sqliteKV.beginTransaction(); } for (int index = 0; index < m_loops; index++) { final String value = m_arrStrings[index]; final String key = m_arrKeys[index]; sqliteKV.putString(key, value); } if (useTransaction) { sqliteKV.endTransaction(); } long endTime = System.currentTimeMillis(); final String msg = useTransaction ? " sqlite transaction " : " sqlite "; Log.i(TAG, caller + msg + "write String: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } private void spBatchWrieString(String caller) { long startTime = System.currentTimeMillis(); SharedPreferences preferences = MultiProcessSharedPreferences.getSharedPreferences(this, SP_ID, MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); for (int index = 0; index < m_loops; index++) { final String str = m_arrStrings[index]; final String key = m_arrKeys[index]; editor.putString(key, str); //editor.commit(); editor.apply(); } long endTime = System.currentTimeMillis(); Log.i(TAG, caller + " MultiProcessSharedPreferences write String: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } protected void batchReadString(String caller) { mmkvBatchReadString(caller); sqliteReadString(caller, SQLite_Use_Transaction); spBatchReadStrinfg(caller); } private void mmkvBatchReadString(String caller) { Log.i(TAG, caller + " mmkv read String: loop[" + m_loops + "] star."); long startTime = System.currentTimeMillis(); MMKV mmkv = GetMMKV(); for (int index = 0; index < m_loops; index++) { final String strKey = m_arrKeys[index]; final String tmpStr = mmkv.decodeString(strKey); } long endTime = System.currentTimeMillis(); Log.i(TAG, caller + " mmkv read String: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } private void sqliteReadString(String caller, boolean useTransaction) { long startTime = System.currentTimeMillis(); SQLiteKV sqliteKV = new SQLiteKV(this); if (useTransaction) { sqliteKV.beginTransaction(); } for (int index = 0; index < m_loops; index++) { final String key = m_arrKeys[index]; final String tmp = sqliteKV.getString(key); } if (useTransaction) { sqliteKV.endTransaction(); } long endTime = System.currentTimeMillis(); final String msg = useTransaction ? " sqlite transaction " : " sqlite "; Log.i(TAG, caller + msg + "read String: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } private void spBatchReadStrinfg(String caller) { long startTime = System.currentTimeMillis(); SharedPreferences preferences = MultiProcessSharedPreferences.getSharedPreferences(this, SP_ID, MODE_PRIVATE); for (int index = 0; index < m_loops; index++) { final String key = m_arrKeys[index]; final String tmp = preferences.getString(key, null); } long endTime = System.currentTimeMillis(); Log.i(TAG, caller + " MultiProcessSharedPreferences read String: loop[" + m_loops + "]: " + (endTime - startTime) + " ms"); } MMKV m_ashmemMMKV; protected MMKV GetMMKV() { if (m_ashmemMMKV != null) { return m_ashmemMMKV; } else { return MMKV.mmkvWithID(MMKV_ID, MMKV.MULTI_PROCESS_MODE, CryptKey); } } public class AshmemMMKVGetter extends IAshmemMMKV.Stub { private AshmemMMKVGetter() { // 1M, ashmem cannot change size after opened final String id = "tetAshmemMMKV"; try { MMKVConfig config = new MMKVConfig(); config.expectedCapacity = AshmemMMKV_Size; config.mode = MMKV.MULTI_PROCESS_MODE; config.cryptKey = CryptKey; m_ashmemMMKV = MMKV.mmkvWithAshmemID(BenchMarkBaseService.this, id, config); m_ashmemMMKV.encode("bool", true); } catch (Exception e) { Log.e("MMKV", e.getMessage()); } } public ParcelableMMKV GetAshmemMMKV() { return new ParcelableMMKV(m_ashmemMMKV); } } @Nullable @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind, intent=" + intent); return new AshmemMMKVGetter(); } protected void prepareAshmemMMKVByCP() { // it's ok for other process not knowing cryptKey final String cryptKey = null; try { m_ashmemMMKV = MMKV.mmkvWithAshmemID(this, AshmemMMKV_ID, AshmemMMKV_Size, MMKV.MULTI_PROCESS_MODE, cryptKey); } catch (Exception e) { Log.e("MMKV", e.getMessage()); } } } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/FakeInfo.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2025 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. */ package com.tencent.mmkvdemo; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; public class FakeInfo implements Parcelable { public String channelId; public int position; public FakeInfo() { } @NonNull @Override public String toString() { return "fake channelID = " + channelId + ", position = " + position; } protected FakeInfo(Parcel in) { channelId = in.readString(); position = in.readInt(); } public static final Creator CREATOR = new Creator() { @Override public FakeInfo createFromParcel(Parcel in) { return new FakeInfo(in); } @Override public FakeInfo[] newArray(int size) { return new FakeInfo[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(channelId); dest.writeInt(position); } } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/Info.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2025 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. */ package com.tencent.mmkvdemo; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; class Info implements Parcelable { public String channelId; public int position; public Info(String id, int pos) { channelId = id; position = pos; } @NonNull @Override public String toString() { return "channelID = " + channelId + ", position = " + position; } protected Info(Parcel in) { channelId = in.readString(); position = in.readInt(); } public static final Creator CREATOR = new Creator() { @Override public Info createFromParcel(Parcel in) { return new Info(in); } @Override public Info[] newArray(int size) { return new Info[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(channelId); dest.writeInt(position); } } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MainActivity.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkvdemo; import static com.tencent.mmkvdemo.BenchMarkBaseService.AshmemMMKV_ID; import static com.tencent.mmkvdemo.BenchMarkBaseService.AshmemMMKV_Size; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.os.SystemClock; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import androidx.core.view.ViewCompat; import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; import com.tencent.mmkv.MMKV; import com.tencent.mmkv.MMKVConfig; import com.tencent.mmkv.MMKVRecoverStrategic; import com.tencent.mmkv.NameSpace; import com.tencent.mmkv.NativeBuffer; import java.io.File; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Objects; public class MainActivity extends AppCompatActivity { static private final String KEY_1 = "Ashmem_Key_1"; static private final String KEY_2 = "Ashmem_Key_2"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Enable edge-to-edge display WindowCompat.setDecorFitsSystemWindows(getWindow(), false); setContentView(R.layout.activity_main); View mainLayout = findViewById(R.id.main_layout); ViewCompat.setOnApplyWindowInsetsListener(mainLayout, (v, insets) -> { int statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top; int navigationBarHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom; v.setPadding(0, statusBarHeight, 0, navigationBarHeight); return insets; }); TextView tv = (TextView) findViewById(R.id.sample_text); String rootDir = MMKV.getRootDir(); tv.setText(rootDir); final Button button = findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { final Baseline baseline = new Baseline(getApplicationContext(), 1000); public void onClick(View v) { baseline.mmkvBaselineTest(); baseline.sharedPreferencesBaselineTest(); baseline.sqliteBaselineTest(false); //testInterProcessReKey(); //testInterProcessLockPhase2(); } }); //testHolderForMultiThread(); //prepareInterProcessAshmem(); //prepareInterProcessAshmemByContentProvider(KEY_1); final Button button_read_int = findViewById(R.id.button_read_int); button_read_int.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { interProcessBaselineTest(BenchMarkBaseService.CMD_READ_INT); } }); final Button button_write_int = findViewById(R.id.button_write_int); button_write_int.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { interProcessBaselineTest(BenchMarkBaseService.CMD_WRITE_INT); } }); final Button button_read_string = findViewById(R.id.button_read_string); button_read_string.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { interProcessBaselineTest(BenchMarkBaseService.CMD_READ_STRING); } }); final Button button_write_string = findViewById(R.id.button_write_string); button_write_string.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { interProcessBaselineTest(BenchMarkBaseService.CMD_WRITE_STRING); } }); String otherDir = getFilesDir().getAbsolutePath() + "/mmkv_3"; MMKV kv = testMMKV("test/AES", "Tencent MMKV", false, false, otherDir); kv.checkContentChangedByOuterProcess(); kv.close(); // prepare for backup customize root path kv = testMMKV("test_backup", "MMKV Backup", false, false, otherDir); kv.close(); testAshmem(); testReKey(); KotlinUsecaseKt.kotlinFunctionalTest(); testInterProcessLogic(); testImportSharedPreferences(); //testInterProcessLockPhase1(); //testCornerSize(); //testFastRemoveCornerSize(); //testTrimNonEmptyInterProcess(); //testItemSizeHolderOverride(); // testDiskFull(); testBackup(); testRestore(); testAutoExpire(); testExpectedCapacity(); testCompareBeforeSet(); testClearAllKeepSpace(); // testFastNativeSpeed(); testRemoveStorageAndCheckExist(); overrideTest(); testReadOnly(); testImport(); } private void testCompareBeforeSet() { MMKV mmkv = MMKV.mmkvWithID("testCompareBeforeSet"); mmkv.enableCompareBeforeSet(); mmkv.encode("key", "extra"); { String key = "int"; int v = 12345; mmkv.encode(key, v); long actualSize = mmkv.actualSize(); Log.d("mmkv", "testCompareBeforeSet actualSize = " + actualSize); Log.d("mmkv", "testCompareBeforeSet v = " + mmkv.getInt(key, -1)); mmkv.encode(key, v); long actualSize2 = mmkv.actualSize(); Log.d("mmkv", "testCompareBeforeSet actualSize = " + actualSize2); if (actualSize2 != actualSize) { Log.e("mmkv", "testCompareBeforeSet fail"); } mmkv.encode(key, v * 23); Log.d("mmkv", "testCompareBeforeSet actualSize = " + mmkv.actualSize()); Log.d("mmkv", "testCompareBeforeSet v = " + mmkv.getInt(key, -1)); } { String key = "string"; String v = "w012A🏊🏻good"; mmkv.encode(key, v); long actualSize = mmkv.actualSize(); Log.d("mmkv", "testCompareBeforeSet actualSize = " + actualSize); Log.d("mmkv", "testCompareBeforeSet v = " + mmkv.getString(key, "")); mmkv.encode(key, v); long actualSize2 = mmkv.actualSize(); Log.d("mmkv", "testCompareBeforeSet actualSize = " + actualSize2); if (actualSize2 != actualSize) { Log.e("mmkv", "testCompareBeforeSet fail"); } mmkv.encode(key, "temp data 👩🏻‍🏫"); Log.d("mmkv", "testCompareBeforeSet actualSize = " + mmkv.actualSize()); Log.d("mmkv", "testCompareBeforeSet v = " + mmkv.getString(key, "")); } } /** * FastNative * Before Test, remove `MMKVInfo` log print in `enableCompareBeforeSet` function */ private void testFastNativeSpeed() { int repeatCount = 5000000; MMKV mmkv = MMKV.mmkvWithID("test_fastnative_speed"); long start, end; start = System.currentTimeMillis(); for (int i = 0; i < repeatCount; i++) { mmkv.enableCompareBeforeSet(); } end = System.currentTimeMillis(); Log.e("MMKV", "testFastNativeSpeed: " + (end - start)); } private void testInterProcessLogic() { MMKV mmkv = MMKV.mmkvWithID(MyService.MMKV_ID, MMKV.MULTI_PROCESS_MODE, MyService.CryptKey); mmkv.putInt(MyService.CMD_ID, 1024); Log.d("mmkv in main", "" + mmkv.decodeInt(MyService.CMD_ID)); Intent intent = new Intent(this, MyService.class); intent.putExtra(BenchMarkBaseService.CMD_ID, MyService.CMD_REMOVE); startService(intent); SystemClock.sleep(1000 * 3); int value = mmkv.decodeInt(MyService.CMD_ID); Log.d("mmkv", "" + value); } private MMKV testMMKV(String mmapID, String cryptKey, boolean aes256, boolean decodeOnly, String rootPath) { //MMKV kv = MMKV.defaultMMKV(); MMKV kv = MMKV.mmkvWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, cryptKey, aes256, rootPath); testMMKV(kv, decodeOnly); Log.i("MMKV", "isFileValid[" + kv.mmapID() + "]: " + MMKV.isFileValid(kv.mmapID(), rootPath)); return kv; } static void testMMKV(MMKV kv, boolean decodeOnly) { if (!decodeOnly) { kv.encode("bool", true); } Log.i("MMKV", "bool: " + kv.decodeBool("bool")); if (!decodeOnly) { kv.encode("int", Integer.MIN_VALUE); } Log.i("MMKV", "int: " + kv.decodeInt("int")); if (!decodeOnly) { kv.encode("long", Long.MAX_VALUE); } Log.i("MMKV", "long: " + kv.decodeLong("long")); if (!decodeOnly) { kv.encode("float", -3.14f); } Log.i("MMKV", "float: " + kv.decodeFloat("float")); if (!decodeOnly) { kv.encode("double", Double.MIN_VALUE); } Log.i("MMKV", "double: " + kv.decodeDouble("double")); if (!decodeOnly) { kv.encode("string", "Hello from mmkv"); } Log.i("MMKV", "string: " + kv.decodeString("string")); if (!decodeOnly) { byte[] bytes = {'m', 'm', 'k', 'v'}; kv.encode("bytes", bytes); } byte[] bytes = kv.decodeBytes("bytes"); Log.i("MMKV", "bytes: " + new String(bytes)); Log.i("MMKV", "bytes length = " + bytes.length + ", value size consumption = " + kv.getValueSize("bytes") + ", value size = " + kv.getValueActualSize("bytes")); int sizeNeeded = kv.getValueActualSize("bytes"); NativeBuffer nativeBuffer = MMKV.createNativeBuffer(sizeNeeded); if (nativeBuffer != null) { int size = kv.writeValueToNativeBuffer("bytes", nativeBuffer); Log.i("MMKV", "size Needed = " + sizeNeeded + " written size = " + size); MMKV.destroyNativeBuffer(nativeBuffer); } if (!decodeOnly) { TestParcelable testParcelable = new TestParcelable(1024, "Hi Parcelable"); kv.encode("parcel", testParcelable); } TestParcelable result = kv.decodeParcelable("parcel", TestParcelable.class); if (result != null) { Log.d("MMKV", "parcel: " + result.iValue + ", " + result.sValue + ", " + result.list); } else { Log.e("MMKV", "fail to decodeParcelable of key:parcel"); } kv.encode("null string", "some string"); Log.i("MMKV", "string before set null: " + kv.decodeString("null string")); kv.encode("null string", (String) null); Log.i("MMKV", "string after set null: " + kv.decodeString("null string") + ", containsKey:" + kv.contains("null string")); Log.i("MMKV", "allKeys: " + Arrays.toString(kv.allKeys())); Log.i("MMKV", "count = " + kv.count() + ", totalSize = " + kv.totalSize() + ", actualSize = " + kv.actualSize()); Log.i("MMKV", "containsKey[string]: " + kv.containsKey("string")); kv.removeValueForKey("bool"); Log.i("MMKV", "bool: " + kv.decodeBool("bool")); kv.removeValuesForKeys(new String[] {"int", "long"}); //kv.sync(); //kv.async(); //kv.clearAll(); kv.clearMemoryCache(); Log.i("MMKV", "allKeys: " + Arrays.toString(kv.allKeys())); } private void testImportSharedPreferences() { SharedPreferences preferences = getSharedPreferences("imported", MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putBoolean("bool", true); editor.putInt("int", Integer.MIN_VALUE); editor.putLong("long", Long.MAX_VALUE); editor.putFloat("float", -3.14f); editor.putString("string", "hello, imported"); HashSet set = new HashSet(); set.add("W"); set.add("e"); set.add("C"); set.add("h"); set.add("a"); set.add("t"); editor.putStringSet("string-set", set); editor.commit(); MMKV kv = MMKV.mmkvWithID("imported"); kv.clearAll(); kv.importFromSharedPreferences(preferences); editor.clear().commit(); Log.i("MMKV", "allKeys: " + Arrays.toString(kv.allKeys())); Log.i("MMKV", "bool: " + kv.getBoolean("bool", false)); Log.i("MMKV", "int: " + kv.getInt("int", 0)); Log.i("MMKV", "long: " + kv.getLong("long", 0)); Log.i("MMKV", "float: " + kv.getFloat("float", 0)); Log.i("MMKV", "double: " + kv.decodeDouble("double")); Log.i("MMKV", "string: " + kv.getString("string", null)); Log.i("MMKV", "string-set: " + kv.getStringSet("string-set", null)); Log.i("MMKV", "linked-string-set: " + kv.decodeStringSet("string-set", null, LinkedHashSet.class)); // test @Nullable kv.putStringSet("string-set", null); Log.i("MMKV", "after set null, string-set: " + kv.getStringSet("string-set", null)); } private void testReKey() { final String mmapID = "test/AES_reKey1"; MMKV kv = testMMKV(mmapID, null, false, false, null); kv.reKey("Key_seq_1"); kv.clearMemoryCache(); testMMKV(mmapID, "Key_seq_1", false, true, null); kv.reKey("Key_Seq_Very_Looooooooong", true); kv.clearMemoryCache(); testMMKV(mmapID, "Key_Seq_Very_Looooooooong", true, true, null); kv.reKey(null); kv.clearMemoryCache(); testMMKV(mmapID, null, false, true, null); } private void interProcessBaselineTest(String cmd) { Intent intent = new Intent(this, MyService.class); intent.putExtra(BenchMarkBaseService.CMD_ID, cmd); startService(intent); intent = new Intent(this, MyService_1.class); intent.putExtra(BenchMarkBaseService.CMD_ID, cmd); startService(intent); } private void testAshmem() { String cryptKey = "Tencent MMKV"; MMKV kv = MMKV.mmkvWithAshmemID(this, "testAshmem", MMKV.pageSize(), MMKV.SINGLE_PROCESS_MODE, cryptKey); kv.encode("bool", true); Log.i("MMKV", "bool: " + kv.decodeBool("bool")); kv.encode("int", Integer.MIN_VALUE); Log.i("MMKV", "int: " + kv.decodeInt("int")); kv.encode("long", Long.MAX_VALUE); Log.i("MMKV", "long: " + kv.decodeLong("long")); kv.encode("float", -3.14f); Log.i("MMKV", "float: " + kv.decodeFloat("float")); kv.encode("double", Double.MIN_VALUE); Log.i("MMKV", "double: " + kv.decodeDouble("double")); kv.encode("string", "Hello from mmkv"); Log.i("MMKV", "string: " + kv.decodeString("string")); byte[] bytes = {'m', 'm', 'k', 'v'}; kv.encode("bytes", bytes); Log.i("MMKV", "bytes: " + new String(kv.decodeBytes("bytes"))); Log.i("MMKV", "allKeys: " + Arrays.toString(kv.allKeys())); Log.i("MMKV", "count = " + kv.count() + ", totalSize = " + kv.totalSize() + ", actualSize = " + kv.actualSize()); Log.i("MMKV", "containsKey[string]: " + kv.containsKey("string")); kv.removeValueForKey("bool"); Log.i("MMKV", "bool: " + kv.decodeBool("bool")); kv.removeValueForKey("int"); kv.removeValueForKey("long"); //kv.removeValuesForKeys(new String[] {"int", "long"}); //kv.clearAll(); kv.clearMemoryCache(); Log.i("MMKV", "allKeys: " + Arrays.toString(kv.allKeys())); Log.i("MMKV", "isFileValid[" + kv.mmapID() + "]: " + MMKV.isFileValid(kv.mmapID())); } private void prepareInterProcessAshmem() { Intent intent = new Intent(this, MyService_1.class); intent.putExtra(BenchMarkBaseService.CMD_ID, MyService_1.CMD_PREPARE_ASHMEM); startService(intent); } private void prepareInterProcessAshmemByContentProvider(String cryptKey) { // first of all, init ashmem mmkv in main process MMKV.mmkvWithAshmemID(this, AshmemMMKV_ID, AshmemMMKV_Size, MMKV.MULTI_PROCESS_MODE, cryptKey); // then other process can get by ContentProvider Intent intent = new Intent(this, MyService.class); intent.putExtra(BenchMarkBaseService.CMD_ID, BenchMarkBaseService.CMD_PREPARE_ASHMEM_BY_CP); startService(intent); intent = new Intent(this, MyService_1.class); intent.putExtra(BenchMarkBaseService.CMD_ID, BenchMarkBaseService.CMD_PREPARE_ASHMEM_BY_CP); startService(intent); } private void testInterProcessReKey() { MMKV mmkv = MMKV.mmkvWithAshmemID(this, AshmemMMKV_ID, AshmemMMKV_Size, MMKV.MULTI_PROCESS_MODE, KEY_1); mmkv.reKey(KEY_2); prepareInterProcessAshmemByContentProvider(KEY_2); } private void testHolderForMultiThread() { final int COUNT = 1; final int THREAD_COUNT = 1; final String ID = "Hotel"; final String KEY = "California"; final String VALUE = "You can checkout any time you like, but you can never leave."; final MMKV mmkv = MMKV.mmkvWithID(ID); Runnable task = new Runnable() { public void run() { for (int i = 0; i < COUNT; ++i) { mmkv.putString(KEY, VALUE); mmkv.getString(KEY, null); mmkv.remove(KEY); } } }; for (int i = 0; i < THREAD_COUNT; ++i) { new Thread(task, "MMKV-" + i).start(); } } private void testInterProcessLockPhase1() { MMKV mmkv1 = MMKV.mmkvWithID(MyService.LOCK_PHASE_1, MMKV.MULTI_PROCESS_MODE); mmkv1.lock(); Log.d("locked in main", MyService.LOCK_PHASE_1); Intent intent = new Intent(this, MyService.class); intent.putExtra(BenchMarkBaseService.CMD_ID, MyService.CMD_LOCK); startService(intent); } private void testInterProcessLockPhase2() { MMKV mmkv2 = MMKV.mmkvWithID(MyService.LOCK_PHASE_2, MMKV.MULTI_PROCESS_MODE); mmkv2.lock(); Log.d("locked in main", MyService.LOCK_PHASE_2); } private void testCornerSize() { MMKV mmkv = MMKV.mmkvWithID("cornerSize", MMKV.MULTI_PROCESS_MODE, "aes"); mmkv.clearAll(); int size = MMKV.pageSize() - 2; size -= 4; String key = "key"; int keySize = 3 + 1; size -= keySize; int valueSize = 3; size -= valueSize; byte[] value = new byte[size]; mmkv.encode(key, value); } private void testFastRemoveCornerSize() { MMKV mmkv = MMKV.mmkvWithID("fastRemoveCornerSize"); mmkv.clearAll(); int size = MMKV.pageSize() - 4; size -= 4; // place holder size String key = "key"; int keySize = 3 + 1; size -= keySize; int valueSize = 3; size -= valueSize; size -= (keySize + 1); // total size of fast remove size /= 16; byte[] value = new byte[size]; for (int i = 0; i < value.length; i++) { value[i] = 'A'; } for (int i = 0; i < 16; i++) { mmkv.encode(key, value); // when a full write back is occur, here's corruption happens mmkv.removeValueForKey(key); } } private void testTrimNonEmptyInterProcess() { MMKV mmkv = MMKV.mmkvWithID("trimNonEmptyInterProcess", MMKV.MULTI_PROCESS_MODE); mmkv.clearAll(); mmkv.encode("NonEmptyKey", "Hello, world!"); byte[] value = new byte[MMKV.pageSize()]; mmkv.encode("largeKV", value); mmkv.removeValueForKey("largeKV"); mmkv.trim(); Intent intent = new Intent(this, MyService.class); intent.putExtra(BenchMarkBaseService.CMD_ID, MyService.CMD_TRIM_FINISH); startService(intent); SystemClock.sleep(1000 * 3); Log.i("MMKV", "NonEmptyKey: " + mmkv.decodeString("NonEmptyKey")); } private void testItemSizeHolderOverride() { // final String mmapID = "testItemSizeHolderOverride_crypted"; // final String encryptKey = "encrypeKey"; final String mmapID = "testItemSizeHolderOverride_plaintext"; final String encryptKey = null; MMKV mmkv = MMKV.mmkvWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, encryptKey); /* do this in v1.1.2 { // mmkv.encode("b", true); byte[] value = new byte[512]; mmkv.encode("data", value); Log.i("MMKV", "allKeys: " + Arrays.toString(mmkv.allKeys())); }*/ // do this in v1.2.0 { long totalSize = mmkv.totalSize(); long bufferSize = totalSize - 512; byte[] value = new byte[(int) bufferSize]; // force a fullwriteback() mmkv.encode("bigData", value); mmkv.clearMemoryCache(); Log.i("MMKV", "allKeys: " + Arrays.toString(mmkv.allKeys())); } } private void testBackup() { File f = new File(MMKV.getRootDir()); String backupRootDir = f.getParent() + "/mmkv_backup_3"; String mmapID = "test/AES"; String otherDir = getFilesDir().getAbsolutePath() + "/mmkv_3"; boolean ret = MMKV.backupOneToDirectory(mmapID, backupRootDir, otherDir); Log.i("MMKV", "backup one [" + mmapID + "] ret = " + ret); if (ret) { MMKV mmkv = MMKV.backedUpMMKVWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, "Tencent MMKV", backupRootDir); Log.i("MMKV", "check on backup file[" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); } // test backup a normal mmkv from custom root path mmapID = "test_backup"; ret = MMKV.backupOneToDirectory(mmapID, backupRootDir, otherDir); Log.i("MMKV", "backup one [" + mmapID + "] ret = " + ret); if (ret) { MMKV mmkv = MMKV.backedUpMMKVWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, "MMKV Backup", backupRootDir); Log.i("MMKV", "check on backup file[" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); } /*{ MMKV mmkv = MMKV.mmkvWithID("imported"); mmkv.close(); mmkv = MMKV.mmkvWithID("test/AES_reKey1"); mmkv.close(); }*/ backupRootDir = f.getParent() + "/mmkv_backup"; long count = MMKV.backupAllToDirectory(backupRootDir); Log.i("MMKV", "backup all count " + count); if (count > 0) { MMKV mmkv = MMKV.backedUpMMKVWithID("imported", MMKV.SINGLE_PROCESS_MODE, null, backupRootDir); Log.i("MMKV", "check on backup file[" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); mmkv = MMKV.backedUpMMKVWithID("testKotlin", MMKV.SINGLE_PROCESS_MODE, null, backupRootDir); Log.i("MMKV", "check on backup file[" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); mmkv = MMKV.backedUpMMKVWithID("test/AES_reKey1", MMKV.SINGLE_PROCESS_MODE, null, backupRootDir); Log.i("MMKV", "check on backup file[" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); mmkv = MMKV.backedUpMMKVWithID("benchmark_interprocess", MMKV.MULTI_PROCESS_MODE, null, backupRootDir); Log.i("MMKV", "check on backup file[" + mmkv.mmapID() + "] allKeys count: " + mmkv.count()); } } private void testRestore() { File f = new File(MMKV.getRootDir()); String backupRootDir = f.getParent() + "/mmkv_backup_3"; String mmapID = "test/AES"; String otherDir = getFilesDir().getAbsolutePath() + "/mmkv_3"; MMKV mmkv = MMKV.mmkvWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, "Tencent MMKV", otherDir); mmkv.encode("test_restore", true); Log.i("MMKV", "before restore [" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); boolean ret = MMKV.restoreOneMMKVFromDirectory(mmapID, backupRootDir, otherDir); Log.i("MMKV", "restore one [" + mmapID + "] ret = " + ret); if (ret) { Log.i("MMKV", "after restore [" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); } // test backup a normal mmkv from custom root path mmapID = "test_backup"; mmkv = MMKV.mmkvWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, "MMKV Backup", otherDir); mmkv.encode("test_restore", 1024); Log.i("MMKV", "before restore [" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); ret = MMKV.restoreOneMMKVFromDirectory(mmapID, backupRootDir, otherDir); Log.i("MMKV", "backup one [" + mmapID + "] ret = " + ret); if (ret) { Log.i("MMKV", "check on backup file[" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); } /*{ mmkv = MMKV.mmkvWithID("imported"); mmkv.close(); mmkv = MMKV.mmkvWithID("test/AES_reKey1"); mmkv.close(); }*/ backupRootDir = f.getParent() + "/mmkv_backup"; long count = MMKV.restoreAllFromDirectory(backupRootDir); Log.i("MMKV", "restore all count " + count); if (count > 0) { mmkv = MMKV.mmkvWithID("imported"); Log.i("MMKV", "check on restore file[" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); mmkv = MMKV.mmkvWithID("testKotlin"); Log.i("MMKV", "check on restore file[" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); mmkv = MMKV.mmkvWithID("test/AES_reKey1"); Log.i("MMKV", "check on restore file[" + mmkv.mmapID() + "] allKeys: " + Arrays.toString(mmkv.allKeys())); mmkv = MMKV.mmkvWithID("benchmark_interprocess", MMKV.MULTI_PROCESS_MODE); Log.i("MMKV", "check on restore file[" + mmkv.mmapID() + "] allKeys count: " + mmkv.count()); } } private void testAutoExpire(MMKV kv, boolean decodeOnly, int expiration) { if (!decodeOnly) { kv.encode("bool", true, expiration); } Log.i("MMKV", "bool: " + kv.decodeBool("bool")); if (!decodeOnly) { kv.encode("int", Integer.MIN_VALUE, expiration); } Log.i("MMKV", "int: " + kv.decodeInt("int")); if (!decodeOnly) { kv.encode("long", Long.MAX_VALUE, expiration); } Log.i("MMKV", "long: " + kv.decodeLong("long")); if (!decodeOnly) { kv.encode("float", -3.14f, expiration); } Log.i("MMKV", "float: " + kv.decodeFloat("float")); if (!decodeOnly) { kv.encode("double", Double.MIN_VALUE, expiration); } Log.i("MMKV", "double: " + kv.decodeDouble("double")); if (!decodeOnly) { kv.encode("string", "Hello from mmkv", expiration); } Log.i("MMKV", "string: " + kv.decodeString("string")); if (!decodeOnly) { byte[] bytes = {'m', 'm', 'k', 'v'}; kv.encode("bytes", bytes, expiration); } byte[] bytes = kv.decodeBytes("bytes"); if (bytes != null) { Log.i("MMKV", "bytes: " + new String(bytes)); Log.i("MMKV", "bytes length = " + bytes.length + ", value size consumption = " + kv.getValueSize("bytes") + ", value size = " + kv.getValueActualSize("bytes")); int sizeNeeded = kv.getValueActualSize("bytes"); NativeBuffer nativeBuffer = MMKV.createNativeBuffer(sizeNeeded); if (nativeBuffer != null) { int size = kv.writeValueToNativeBuffer("bytes", nativeBuffer); Log.i("MMKV", "size Needed = " + sizeNeeded + " written size = " + size); MMKV.destroyNativeBuffer(nativeBuffer); } } if (!decodeOnly) { TestParcelable testParcelable = new TestParcelable(1024, "Hi Parcelable"); kv.encode("parcel", testParcelable, expiration); } TestParcelable result = kv.decodeParcelable("parcel", TestParcelable.class); if (result != null) { Log.i("MMKV", "parcel: " + result.iValue + ", " + result.sValue + ", " + result.list); } if (!decodeOnly) { kv.encode("null string", "some string", expiration); } Log.i("MMKV", "string before set null: " + kv.decodeString("null string")); if (!decodeOnly) { kv.encode("null string", (String) null, expiration); } Log.i("MMKV", "string after set null: " + kv.decodeString("null string") + ", containsKey:" + kv.contains("null string")); Log.i("MMKV", "allKeys: " + Arrays.toString(kv.allNonExpireKeys())); Log.i("MMKV", "count = " + kv.countNonExpiredKeys() + ", totalSize = " + kv.totalSize() + ", actualSize = " + kv.actualSize()); Log.i("MMKV", "containsKey[string]: " + kv.containsKey("string")); } private void testAutoExpire() { // disable auto expire by configuration MMKVConfig config = new MMKVConfig(); config.enableKeyExpire = false; // config.recover = MMKVRecoverStrategic.OnErrorRecover; // config.itemSizeLimit = 1; MMKV mmkv = MMKV.mmkvWithID("test_auto_expire", config); mmkv.clearAll(); mmkv.disableAutoKeyExpire(); // this become a no-op // enable auto expire by configuration mmkv.close(); config.enableKeyExpire = true; config.expiredInSeconds = 1; mmkv = MMKV.mmkvWithID("test_auto_expire", config); mmkv.enableAutoKeyExpire(1); // this become a no-op mmkv.encode("auto_expire_key_1", true); mmkv.encode("never_expire_key_1", true, MMKV.ExpireNever); // mmkv.enableCompareBeforeSet(); testAutoExpire(mmkv, false, 1); SystemClock.sleep(1000 * 2); testAutoExpire(mmkv, true, 1); // mmkv.encode("string", "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJFbHFTUjB"); // mmkv.clearMemoryCache(); // Log.i("MMKV", "space string = " + mmkv.decodeString("string", "")); if (mmkv.contains("auto_expire_key_1")) { Log.e("MMKV", "auto key expiration auto_expire_key_1"); } else { Log.i("MMKV", "auto key expiration auto_expire_key_1"); } if (mmkv.contains("never_expire_key_1")) { Log.i("MMKV", "auto key expiration never_expire_key_1"); } else { Log.e("MMKV", "auto key expiration never_expire_key_1"); } } private int addExtraRoundUp(int len) { int pageSize = MMKV.pageSize(); int rest = len % pageSize; return rest == 0 ? len + pageSize : (2 + len / pageSize) * pageSize; } private void testDiskFull() { new Thread(new Runnable() { @Override public void run() { MMKV mmkv = MMKV.mmkvWithID("disk_full"); long i = 0; StringBuilder value = new StringBuilder(); for (int j = 0; j < 100000; j++) { value.append("a"); } while (true) { boolean ret = mmkv.encode(i++ + "", value.toString()); if (!ret) { break; } } } }).start(); } private void testClearAllKeepSpace() { MMKV mmkv = MMKV.mmkvWithID("testClearAllKeepSpace"); mmkv.encode("key", "value"); mmkv.encode("key2", "value2"); mmkv.clearAllWithKeepingSpace(); mmkv.encode("key3", "value3"); mmkv.clearAll(); mmkv.encode("key4", "value4"); } private void testExpectedCapacity() { String key = "key0"; String value = "🏊🏻®4️⃣🐅_"; int len = 10000; for (int i = 0; i < len; i++) { value += "0"; } Log.i("MMKV", "value size = " + value.getBytes().length); int expectedSize = key.getBytes().length + value.getBytes().length; // if we know exactly the sizes of key and value, set expectedCapacity for performance improvement // extra space can be added to round up MMKV mmkv = MMKV.mmkvWithID("test_expected_capacity0", MMKV.SINGLE_PROCESS_MODE, addExtraRoundUp(len)); // 0 times expand mmkv.encode(key, value); int count = 10; expectedSize = expectedSize * count; MMKV mmkv1 = MMKV.mmkvWithID("test_expected_capacity1", MMKV.SINGLE_PROCESS_MODE, addExtraRoundUp(expectedSize)); for (int i = 0; i < count; i++) { String k = "key" + i; // 0 times expand mmkv1.encode(k, value); } } private void testRemoveStorageAndCheckExist() { String mmapID = "test_remove"; { MMKV mmkv = MMKV.mmkvWithID(mmapID, MMKV.MULTI_PROCESS_MODE); mmkv.encode("bool", true); } Log.i("MMKV", "checkExist = " + MMKV.checkExist(mmapID)); MMKV.removeStorage(mmapID); Log.i("MMKV", "after remove, checkExist = " + MMKV.checkExist(mmapID)); { MMKV mmkv = MMKV.mmkvWithID(mmapID, MMKV.MULTI_PROCESS_MODE); if (mmkv.count() != 0) { Log.e("MMKV", "storage not successfully removed"); } } mmapID = "test_remove/sg"; String rootDir = getFilesDir().getAbsolutePath() + "/mmkv_sg"; MMKV mmkv = MMKV.mmkvWithID(mmapID, rootDir); mmkv.encode("bool", true); Log.i("MMKV", "checkExist = " + MMKV.checkExist(mmapID, rootDir)); MMKV.removeStorage(mmapID, rootDir); Log.i("MMKV", "after remove, checkExist = " + MMKV.checkExist(mmapID, rootDir)); mmkv = MMKV.mmkvWithID(mmapID, rootDir); if (mmkv.count() != 0) { Log.e("MMKV", "storage not successfully removed"); } } private void overrideTest() { MMKV mmkv0 = MMKV.mmkvWithID("overrideTest"); String key = "hello"; String key2 = "hello2"; String value = "world"; mmkv0.encode(key, value); String v2 = mmkv0.decodeString(key); if (!value.equals(v2)) { System.out.println("value = " + v2); System.exit(1); } mmkv0.removeValueForKey(key); mmkv0.encode(key2, value); v2 = mmkv0.decodeString(key2); if (!value.equals(v2)) { System.out.println("value = " + v2); System.exit(1); } mmkv0.removeValueForKey(key2); int len = 10000; StringBuilder bigValue = new StringBuilder("🏊🏻®4️⃣🐅_"); for (int i = 0; i < len; i++) { bigValue.append("0"); } mmkv0.encode(key, bigValue.toString()); String v3 = mmkv0.decodeString(key); if (!bigValue.toString().equals(v3)) { System.exit(1); } // rewrite mmkv0.encode(key, "OK"); String v4 = mmkv0.decodeString(key); if (!"OK".equals(v4)) { System.out.println("value = " + v2); System.exit(1); } mmkv0.encode(key, 12345); int v5 = mmkv0.decodeInt(key); if (v5 != 12345) { System.out.println("value = " + v5); System.exit(1); } mmkv0.removeValueForKey(key); mmkv0.clearAll(); overrideTestEncrypt(); } private void overrideTestEncrypt() { // test small value encryptionTest("cryptworld"); // test medium value encryptionTest("An efficient, small mobile key-value storage framework developed by WeChat. Works on Android, iOS, macOS, Windows, and POSIX."); // test large value encryptionTest("An efficient, small mobile key-value storage framework developed by WeChat. Works on Android, iOS, macOS, Windows, and POSIX. MMKV is an efficient, small, easy-to-use mobile key-value storage framework used in the WeChat application. It's currently available on Android, iOS/macOS, Windows, POSIX and HarmonyOS NEXT."); } private void encryptionTest(String value) { String key = "hello"; String key2 = "hello2"; encryptionTestKV(key, value); encryptionTestKV(key2, value); } private void encryptionTestKV(String key, String value) { String crypt = "fastestCrypt"; MMKV mmkv0 = MMKV.mmkvWithID("overrideCryptTest", MMKV.SINGLE_PROCESS_MODE, crypt); mmkv0.encode(key, value); String v2 = mmkv0.decodeString(key); if (!value.equals(v2)) { System.out.println("value = " + value + ", result = " + v2); System.exit(1); } mmkv0.close(); mmkv0 = MMKV.mmkvWithID("overrideCryptTest", MMKV.SINGLE_PROCESS_MODE, crypt); v2 = mmkv0.decodeString(key); if (!value.equals(v2)) { System.out.println("value = " + value + ", result = " + v2); System.exit(1); } mmkv0.encode(key, value); v2 = mmkv0.decodeString(key); if (!value.equals(v2)) { System.out.println("value = " + value + ", result = " + v2); System.exit(1); } mmkv0.removeValueForKey(key); } private void testReadOnly() { final String name = "testReadOnly"; final String key = "readonly+key"; { MMKV kv = MMKV.mmkvWithID(name, MMKV.SINGLE_PROCESS_MODE, key); testMMKV(kv, false); kv.close(); } String path = MMKV.getRootDir() + "/" + name; File file = new File(path); file.setReadOnly(); File crcFile = new File(path + ".crc"); crcFile.setReadOnly(); MMKV kv = MMKV.mmkvWithID(name, MMKV.SINGLE_PROCESS_MODE | MMKV.READ_ONLY_MODE, key); testMMKV(kv, true); // also check if it tolerate update operations without crash testMMKV(kv, false); file.setWritable(true); crcFile.setWritable(true); } private void testImport() { final String mmapID = "testImportSrc"; MMKV src = MMKV.mmkvWithID(mmapID); src.encode("bool", true); src.encode("int", Integer.MIN_VALUE); src.encode("long", Long.MAX_VALUE); src.encode("string", "test import"); MMKV dst = MMKV.mmkvWithID("testImportDst"); dst.clearAll(); dst.enableAutoKeyExpire(1); dst.encode("bool", false); dst.encode("int", -1); dst.encode("long", 0); dst.encode("string", mmapID); long count = dst.importFrom(src); if (count != 4 || dst.count() != 4) { Log.e("MMKV", "import check count fail"); } if (!dst.decodeBool("bool")) { Log.e("MMKV", "import check bool fail"); } if (dst.decodeInt("int") != Integer.MIN_VALUE) { Log.e("MMKV", "import check int fail"); } if (dst.decodeLong("long") != Long.MAX_VALUE) { Log.e("MMKV", "import check long fail"); } if (!Objects.equals(dst.decodeString("string"), "test import")) { Log.e("MMKV", "import check string fail"); } SystemClock.sleep(1000 * 2); if (dst.countNonExpiredKeys() != 0) { Log.e("MMKV", "import check expire fail"); } } } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MultiProcessSharedPreferences.java ================================================ package com.tencent.mmkvdemo; import android.content.BroadcastReceiver; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.UriMatcher; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ProviderInfo; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.Bundle; import java.lang.ref.SoftReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * 使用ContentProvider实现多进程SharedPreferences读写;
* 1、ContentProvider天生支持多进程访问;
* 2、使用内部私有BroadcastReceiver实现多进程OnSharedPreferenceChangeListener监听;
* * 使用方法:AndroidManifest.xml中添加provider申明:
*
 * <provider android:name="com.tencent.mm.sdk.patformtools.MultiProcessSharedPreferences"
 * android:authorities="com.tencent.mm.sdk.patformtools.MultiProcessSharedPreferences"
 * android:exported="false" />
 * <!-- authorities属性里面最好使用包名做前缀,apk在安装时authorities同名的provider需要校验签名,否则无法安装;--!/>
*
* * ContentProvider方式实现要注意:
* 1、当ContentProvider所在进程android.os.Process.killProcess(pid)时,会导致整个应用程序完全意外退出或者ContentProvider所在进程重启;
* 重启报错信息:Acquiring provider for user 0: existing object's process dead;
* 2、如果设备处在“安全模式”下,只有系统自带的ContentProvider才能被正常解析使用,因此put值时默认返回false,get值时默认返回null;
* * 其他方式实现SharedPreferences的问题:
* 使用FileLock和FileObserver也可以实现多进程SharedPreferences读写,但是会有兼容性问题:
* 1、某些设备上卸载程序时锁文件无法删除导致卸载残留,进而导致无法重新安装该程序(报INSTALL_FAILED_UID_CHANGED错误);
* 2、某些设备上FileLock会导致僵尸进程出现进而导致耗电;
* 3、僵尸进程出现后,正常进程的FileLock会一直阻塞等待僵尸进程中的FileLock释放,导致进程一直阻塞;
* * @author seven456@gmail.com * @version 1.0 * @since JDK1.6 * */ public class MultiProcessSharedPreferences extends ContentProvider implements SharedPreferences { private static final String TAG = "MicroMsg.MultiProcessSharedPreferences"; public static final boolean DEBUG = BuildConfig.DEBUG; private Context mContext; private String mName; private int mMode; private boolean mIsSafeMode; private List> mListeners; private BroadcastReceiver mReceiver; private static String AUTHORITY; private static volatile Uri AUTHORITY_URI; private UriMatcher mUriMatcher; private static final String KEY = "value"; private static final String KEY_NAME = "name"; private static final String PATH_WILDCARD = "*/"; private static final String PATH_GET_ALL = "getAll"; private static final String PATH_GET_STRING = "getString"; private static final String PATH_GET_INT = "getInt"; private static final String PATH_GET_LONG = "getLong"; private static final String PATH_GET_FLOAT = "getFloat"; private static final String PATH_GET_BOOLEAN = "getBoolean"; private static final String PATH_CONTAINS = "contains"; private static final String PATH_APPLY = "apply"; private static final String PATH_COMMIT = "commit"; private static final String PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = "registerOnSharedPreferenceChangeListener"; private static final String PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = "unregisterOnSharedPreferenceChangeListener"; private static final int GET_ALL = 1; private static final int GET_STRING = 2; private static final int GET_INT = 3; private static final int GET_LONG = 4; private static final int GET_FLOAT = 5; private static final int GET_BOOLEAN = 6; private static final int CONTAINS = 7; private static final int APPLY = 8; private static final int COMMIT = 9; private static final int REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = 10; private static final int UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = 11; private Map mListenersCount; private static class ReflectionUtil { public static ContentValues contentValuesNewInstance(HashMap values) { try { Constructor c = ContentValues.class.getDeclaredConstructor(new Class[] {HashMap.class}); // hide c.setAccessible(true); return c.newInstance(values); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } } public static Editor editorPutStringSet(Editor editor, String key, Set values) { try { Method method = editor.getClass().getDeclaredMethod( "putStringSet", new Class[] {String.class, Set.class}); // Android 3.0 return (Editor) method.invoke(editor, key, values); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } public static void editorApply(Editor editor) { try { Method method = editor.getClass().getDeclaredMethod("apply"); // Android 2.3 method.invoke(editor); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } } private void checkInitAuthority(Context context) { if (AUTHORITY_URI == null) { String AUTHORITY = null; Uri AUTHORITY_URI = MultiProcessSharedPreferences.AUTHORITY_URI; synchronized (MultiProcessSharedPreferences.this) { if (AUTHORITY_URI == null) { AUTHORITY = queryAuthority(context); AUTHORITY_URI = Uri.parse(ContentResolver.SCHEME_CONTENT + "://" + AUTHORITY); if (DEBUG) { // Log.d(TAG, "checkInitAuthority.AUTHORITY = " + AUTHORITY); } } if (AUTHORITY == null) { throw new IllegalArgumentException("'AUTHORITY' initialize failed."); } } MultiProcessSharedPreferences.AUTHORITY = AUTHORITY; MultiProcessSharedPreferences.AUTHORITY_URI = AUTHORITY_URI; } } private static String queryAuthority(Context context) { PackageInfo packageInfos = null; try { PackageManager mgr = context.getPackageManager(); if (mgr != null) { packageInfos = mgr.getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS); } } catch (NameNotFoundException e) { if (DEBUG) { // Log.printErrStackTrace(TAG, e, ""); } } if (packageInfos != null && packageInfos.providers != null) { for (ProviderInfo providerInfo : packageInfos.providers) { if (providerInfo.name.equals(MultiProcessSharedPreferences.class.getName())) { return providerInfo.authority; } } } return null; } /** * mode不使用{@link Context#MODE_MULTI_PROCESS}特可以支持多进程了; * * @param mode * * @see Context#MODE_PRIVATE * @see Context#MODE_WORLD_READABLE * @see Context#MODE_WORLD_WRITEABLE */ public static SharedPreferences getSharedPreferences(Context context, String name, int mode) { return new MultiProcessSharedPreferences(context, name, mode); } public MultiProcessSharedPreferences() { } private MultiProcessSharedPreferences(Context context, String name, int mode) { mContext = context; mName = name; mMode = mode; PackageManager mgr = context.getPackageManager(); if (mgr != null) { mIsSafeMode = mgr.isSafeMode(); // 如果设备处在“安全模式”下,只有系统自带的ContentProvider才能被正常解析使用; } } @SuppressWarnings("unchecked") @Override public Map getAll() { return (Map) getValue(PATH_GET_ALL, null, null); } @Override public String getString(String key, String defValue) { String v = (String) getValue(PATH_GET_STRING, key, defValue); return v != null ? v : defValue; } // @Override // Android 3.0 public Set getStringSet(String key, Set defValues) { synchronized (this) { @SuppressWarnings("unchecked") Set v = (Set) getValue(PATH_GET_STRING, key, defValues); return v != null ? v : defValues; } } @Override public int getInt(String key, int defValue) { Integer v = (Integer) getValue(PATH_GET_INT, key, defValue); return v != null ? v : defValue; } @Override public long getLong(String key, long defValue) { Long v = (Long) getValue(PATH_GET_LONG, key, defValue); return v != null ? v : defValue; } @Override public float getFloat(String key, float defValue) { Float v = (Float) getValue(PATH_GET_FLOAT, key, defValue); return v != null ? v : defValue; } @Override public boolean getBoolean(String key, boolean defValue) { Boolean v = (Boolean) getValue(PATH_GET_BOOLEAN, key, defValue); return v != null ? v : defValue; } @Override public boolean contains(String key) { Boolean v = (Boolean) getValue(PATH_CONTAINS, key, null); return v != null ? v : false; } @Override public Editor edit() { return new EditorImpl(); } @Override public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { synchronized (this) { if (mListeners == null) { mListeners = new ArrayList>(); } Boolean result = (Boolean) getValue(PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, null, false); if (result != null && result) { mListeners.add(new SoftReference(listener)); if (mReceiver == null) { mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String name = intent.getStringExtra(KEY_NAME); @SuppressWarnings("unchecked") List keysModified = (List) intent.getSerializableExtra(KEY); if (mName.equals(name) && keysModified != null) { List arrayListeners; synchronized (MultiProcessSharedPreferences.this) { arrayListeners = mListeners; } List> listeners = new ArrayList>(arrayListeners); for (int i = keysModified.size() - 1; i >= 0; i--) { final String key = keysModified.get(i); for (SoftReference srlistener : listeners) { OnSharedPreferenceChangeListener listener = srlistener.get(); if (listener != null) { listener.onSharedPreferenceChanged(MultiProcessSharedPreferences.this, key); } } } } } }; mContext.registerReceiver(mReceiver, new IntentFilter(makeAction(mName))); } } } } @Override public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { synchronized (this) { getValue(PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, null, false); if (mListeners != null) { List> removing = new ArrayList<>(); for (SoftReference srlistener : mListeners) { OnSharedPreferenceChangeListener listenerFromSR = srlistener.get(); if (listenerFromSR != null && listenerFromSR.equals(listener)) { removing.add(srlistener); } } for (SoftReference srlistener : removing) { mListeners.remove(srlistener); } if (mListeners.isEmpty() && mReceiver != null) { mContext.unregisterReceiver(mReceiver); mReceiver = null; mListeners = null; } } } } public final class EditorImpl implements Editor { private final Map mModified = new HashMap(); private boolean mClear = false; @Override public Editor putString(String key, String value) { synchronized (this) { mModified.put(key, value); return this; } } // @Override // Android 3.0 public Editor putStringSet(String key, Set values) { synchronized (this) { mModified.put(key, (values == null) ? null : new HashSet(values)); return this; } } @Override public Editor putInt(String key, int value) { synchronized (this) { mModified.put(key, value); return this; } } @Override public Editor putLong(String key, long value) { synchronized (this) { mModified.put(key, value); return this; } } @Override public Editor putFloat(String key, float value) { synchronized (this) { mModified.put(key, value); return this; } } @Override public Editor putBoolean(String key, boolean value) { synchronized (this) { mModified.put(key, value); return this; } } @Override public Editor remove(String key) { synchronized (this) { mModified.put(key, null); return this; } } @Override public Editor clear() { synchronized (this) { mClear = true; return this; } } @Override public void apply() { setValue(PATH_APPLY); } @Override public boolean commit() { return setValue(PATH_COMMIT); } private boolean setValue(String pathSegment) { if (mIsSafeMode) { // 如果设备处在“安全模式”,返回false; return false; } else { synchronized (MultiProcessSharedPreferences.this) { checkInitAuthority(mContext); String[] selectionArgs = new String[] {String.valueOf(mMode), String.valueOf(mClear)}; synchronized (this) { Uri uri = Uri.withAppendedPath(Uri.withAppendedPath(AUTHORITY_URI, mName), pathSegment); ContentValues values = ReflectionUtil.contentValuesNewInstance((HashMap) mModified); return mContext.getContentResolver().update(uri, values, null, selectionArgs) > 0; } } } } } private Object getValue(String pathSegment, String key, Object defValue) { if (mIsSafeMode) { // 如果设备处在“安全模式”,返回null; return null; } else { checkInitAuthority(mContext); Object v = null; Uri uri = Uri.withAppendedPath(Uri.withAppendedPath(AUTHORITY_URI, mName), pathSegment); String[] selectionArgs = new String[] {String.valueOf(mMode), key, defValue == null ? null : String.valueOf(defValue)}; Cursor cursor = mContext.getContentResolver().query(uri, null, null, selectionArgs, null); if (cursor != null) { try { Bundle bundle = cursor.getExtras(); if (bundle != null) { v = bundle.get(KEY); bundle.clear(); } } catch (Exception e) { } cursor.close(); } return v != null ? v : defValue; } } private String makeAction(String name) { return String.format("%1$s_%2$s", MultiProcessSharedPreferences.class.getName(), name); } @Override public boolean onCreate() { checkInitAuthority(getContext()); mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_ALL, GET_ALL); mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_STRING, GET_STRING); mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_INT, GET_INT); mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_LONG, GET_LONG); mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_FLOAT, GET_FLOAT); mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_BOOLEAN, GET_BOOLEAN); mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_CONTAINS, CONTAINS); mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_APPLY, APPLY); mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_COMMIT, COMMIT); mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER); mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { String name = uri.getPathSegments().get(0); int mode = Integer.parseInt(selectionArgs[0]); String key = selectionArgs[1]; String defValue = selectionArgs[2]; Bundle bundle = new Bundle(); switch (mUriMatcher.match(uri)) { case GET_ALL: bundle.putSerializable(KEY, (HashMap) getContext().getSharedPreferences(name, mode).getAll()); break; case GET_STRING: bundle.putString(KEY, getContext().getSharedPreferences(name, mode).getString(key, defValue)); break; case GET_INT: bundle.putInt(KEY, getContext().getSharedPreferences(name, mode).getInt(key, Integer.parseInt(defValue))); break; case GET_LONG: bundle.putLong(KEY, getContext().getSharedPreferences(name, mode).getLong(key, Long.parseLong(defValue))); break; case GET_FLOAT: bundle.putFloat( KEY, getContext().getSharedPreferences(name, mode).getFloat(key, Float.parseFloat(defValue))); break; case GET_BOOLEAN: bundle.putBoolean( KEY, getContext().getSharedPreferences(name, mode).getBoolean(key, Boolean.parseBoolean(defValue))); break; case CONTAINS: bundle.putBoolean(KEY, getContext().getSharedPreferences(name, mode).contains(key)); break; case REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER: { checkInitListenersCount(); Integer countInteger = mListenersCount.get(name); int count = (countInteger == null ? 0 : countInteger) + 1; mListenersCount.put(name, count); countInteger = mListenersCount.get(name); bundle.putBoolean(KEY, count == (countInteger == null ? 0 : countInteger)); } break; case UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER: { checkInitListenersCount(); Integer countInteger = mListenersCount.get(name); int count = (countInteger == null ? 0 : countInteger) - 1; if (count <= 0) { mListenersCount.remove(name); bundle.putBoolean(KEY, !mListenersCount.containsKey(name)); } else { mListenersCount.put(name, count); countInteger = mListenersCount.get(name); bundle.putBoolean(KEY, count == (countInteger == null ? 0 : countInteger)); } } break; default: throw new IllegalArgumentException("This is Unknown Uri:" + uri); } return new BundleCursor(bundle); } @Override public String getType(Uri uri) { throw new UnsupportedOperationException("No external call"); } @Override public Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException("No external insert"); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("No external delete"); } @SuppressWarnings("unchecked") @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int result = 0; String name = uri.getPathSegments().get(0); int mode = Integer.parseInt(selectionArgs[0]); SharedPreferences preferences = getContext().getSharedPreferences(name, mode); int match = mUriMatcher.match(uri); switch (match) { case APPLY: case COMMIT: boolean hasListeners = mListenersCount != null && mListenersCount.get(name) != null && mListenersCount.get(name) > 0; ArrayList keysModified = null; Map map = new HashMap(); if (hasListeners) { keysModified = new ArrayList(); map = (Map) preferences.getAll(); } Editor editor = preferences.edit(); boolean clear = Boolean.parseBoolean(selectionArgs[1]); if (clear) { if (hasListeners && map != null && !map.isEmpty()) { for (Map.Entry entry : map.entrySet()) { keysModified.add(entry.getKey()); } } editor.clear(); } for (Map.Entry entry : values.valueSet()) { String k = entry.getKey(); Object v = entry.getValue(); // Android 5.L_preview : "this" is the magic value for a removal mutation. In addition, // setting a value to "null" for a given key is specified to be // equivalent to calling remove on that key. if (v instanceof EditorImpl || v == null) { editor.remove(k); if (hasListeners && map != null && map.containsKey(k)) { keysModified.add(k); } } else { if (hasListeners && map != null && (!map.containsKey(k) || (map.containsKey(k) && !v.equals(map.get(k))))) { keysModified.add(k); } } if (v instanceof String) { editor.putString(k, (String) v); } else if (v instanceof Set) { ReflectionUtil.editorPutStringSet(editor, k, (Set) v); // Android 3.0 } else if (v instanceof Integer) { editor.putInt(k, (Integer) v); } else if (v instanceof Long) { editor.putLong(k, (Long) v); } else if (v instanceof Float) { editor.putFloat(k, (Float) v); } else if (v instanceof Boolean) { editor.putBoolean(k, (Boolean) v); } } if (hasListeners && keysModified.isEmpty()) { result = 1; } else { switch (match) { case APPLY: ReflectionUtil.editorApply(editor); // Android 2.3 result = 1; // Okay to notify the listeners before it's hit disk // because the listeners should always get the same // SharedPreferences instance back, which has the // changes reflected in memory. notifyListeners(name, keysModified); break; case COMMIT: if (editor.commit()) { result = 1; notifyListeners(name, keysModified); } break; } } values.clear(); break; default: throw new IllegalArgumentException("This is Unknown Uri:" + uri); } return result; } @Override public void onLowMemory() { if (mListenersCount != null) { mListenersCount.clear(); } super.onLowMemory(); } @Override public void onTrimMemory(int level) { if (mListenersCount != null) { mListenersCount.clear(); } super.onTrimMemory(level); } private void checkInitListenersCount() { if (mListenersCount == null) { mListenersCount = new HashMap(); } } private void notifyListeners(String name, ArrayList keysModified) { if (keysModified != null && !keysModified.isEmpty()) { Intent intent = new Intent(); intent.setAction(makeAction(name)); intent.setPackage(getContext().getPackageName()); intent.putExtra(KEY_NAME, name); intent.putExtra(KEY, keysModified); getContext().sendBroadcast(intent); } } private static final class BundleCursor extends MatrixCursor { private Bundle mBundle; public BundleCursor(Bundle extras) { super(new String[] {}, 0); mBundle = extras; } @Override public Bundle getExtras() { return mBundle; } @Override public Bundle respond(Bundle extras) { mBundle = extras; return mBundle; } } } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MyApplication.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2025 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. */ package com.tencent.mmkvdemo; import android.app.Application; import android.util.Log; import com.getkeepsafe.relinker.ReLinker; import com.tencent.mmkv.MMKV; 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 { @Override public void onCreate() { super.onCreate(); // NameSpace can access an MMKV instance regardless of MMKV.initialized() been called or not testNameSpace(); // set root dir //String rootDir = MMKV.initialize(this); String dir = getFilesDir().getAbsolutePath() + "/mmkv"; String rootDir = MMKV.initialize(this, dir, new MMKV.LibLoader() { @Override public void loadLibrary(String libName) { ReLinker.loadLibrary(MyApplication.this, libName); } }, MMKVLogLevel.LevelInfo, this); Log.i("MMKV", "mmkv root: " + rootDir); // set log level // MMKV.setLogLevel(MMKVLogLevel.LevelInfo); // you can turn off logging //MMKV.setLogLevel(MMKVLogLevel.LevelNone); // register log redirecting & recover handler is moved into MMKV.initialize() // MMKV.registerHandler(this); // deprecated: content change notification // MMKV.registerContentChangeNotify(this); } @Override public void onTerminate() { MMKV.onExit(); super.onTerminate(); } @Override public MMKVRecoverStrategic onMMKVCRCCheckFail(String mmapID) { return MMKVRecoverStrategic.OnErrorRecover; } @Override public MMKVRecoverStrategic onMMKVFileLengthError(String mmapID) { return MMKVRecoverStrategic.OnErrorRecover; } @Override public boolean wantLogRedirecting() { return true; } @Override public void mmkvLog(MMKVLogLevel level, String file, int line, String func, String message) { String log = "<" + file + ":" + line + "::" + func + "> " + message; switch (level) { case LevelDebug: Log.d("redirect logging MMKV", log); break; case LevelNone: case LevelInfo: Log.i("redirect logging MMKV", log); break; case LevelWarning: Log.w("redirect logging MMKV", log); break; case LevelError: Log.e("redirect logging MMKV", log); break; } } @Override public native long getNativeLogHandler(); @Override public void onContentChangedByOuterProcess(String mmapID) { 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); MMKV mmkv = ns.mmkvWithID("test/namespace/crypt", MMKV.MULTI_PROCESS_MODE, "crypt_key", 4096 * 2); MainActivity.testMMKV(mmkv, false); System.loadLibrary("mmkvdemo"); long nativeLogger = getNativeLogHandler(); Log.i("MMKVDemo", "native log handler: " + nativeLogger); testNameSpaceInNative(nsRoot, "testNameSpaceInNative"); } private native void testNameSpaceInNative(String nameSpaceRoot, String mmapID); } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MyService.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkvdemo; import android.content.Intent; import android.util.Log; import com.tencent.mmkv.MMKV; public class MyService extends BenchMarkBaseService { private static final String CALLER = "MyService"; public static final String CMD_REMOVE = "cmd_remove"; public static final String CMD_LOCK = "cmd_lock"; public static final String LOCK_PHASE_1 = "lock_phase_1"; public static final String LOCK_PHASE_2 = "lock_phase_2"; public static final String CMD_TRIM_FINISH = "trim_finish"; @Override public void onCreate() { super.onCreate(); Log.i("MMKV", "onCreate MyService"); } @Override public void onDestroy() { super.onDestroy(); Log.i("MMKV", "onDestroy MyService"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("MMKV", "MyService onStartCommand"); if (intent != null) { String cmd = intent.getStringExtra(CMD_ID); if (cmd != null) { if (cmd.equals(CMD_READ_INT)) { super.batchReadInt(CALLER); } else if (cmd.equals(CMD_READ_STRING)) { super.batchReadString(CALLER); } else if (cmd.equals(CMD_WRITE_INT)) { super.batchWriteInt(CALLER); } else if (cmd.equals(CMD_WRITE_STRING)) { super.batchWriteString(CALLER); } else if (cmd.equals(CMD_PREPARE_ASHMEM_BY_CP)) { super.prepareAshmemMMKVByCP(); } else if (cmd.equals(CMD_REMOVE)) { testRemove(); } else if (cmd.equals(CMD_LOCK)) { testLock(); } else if (cmd.equals(CMD_TRIM_FINISH)) { testTrimNonEmpty(); } } } return super.onStartCommand(intent, flags, startId); } private void testRemove() { MMKV mmkv = GetMMKV(); Log.d("mmkv in child", "" + mmkv.decodeInt(CMD_ID)); mmkv.remove(CMD_ID); } private void testLock() { // get the lock immediately MMKV mmkv2 = MMKV.mmkvWithID(LOCK_PHASE_2, MMKV.MULTI_PROCESS_MODE); mmkv2.lock(); Log.d("locked in child", LOCK_PHASE_2); Runnable waiter = new Runnable() { @Override public void run() { MMKV mmkv1 = MMKV.mmkvWithID(LOCK_PHASE_1, MMKV.MULTI_PROCESS_MODE); mmkv1.lock(); Log.d("locked in child", LOCK_PHASE_1); } }; // wait infinitely new Thread(waiter).start(); } private void testTrimNonEmpty() { MMKV mmkv = MMKV.mmkvWithID("trimNonEmptyInterProcess", MMKV.MULTI_PROCESS_MODE); byte[] value = new byte[64]; mmkv.encode("SomeKey", value); } } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MyService_1.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkvdemo; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import com.tencent.mmkv.ParcelableMMKV; public class MyService_1 extends BenchMarkBaseService implements ServiceConnection { public static final String CMD_PREPARE_ASHMEM = "cmd_prepare_ashmem"; private static final String CALLER = "MyService_1"; @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("MMKV", "MyService_1 onStartCommand:"); if (intent != null) { String cmd = intent.getStringExtra(CMD_ID); Log.i("MMKV", "----MyService_1 onStartCommand:" + cmd); if (cmd != null) { if (cmd.equals(CMD_READ_INT)) { super.batchReadInt(CALLER); } else if (cmd.equals(CMD_READ_STRING)) { super.batchReadString(CALLER); } else if (cmd.equals(CMD_WRITE_INT)) { super.batchWriteInt(CALLER); } else if (cmd.equals(CMD_WRITE_STRING)) { super.batchWriteString(CALLER); } else if (cmd.equals(CMD_PREPARE_ASHMEM)) { Intent i = new Intent("com.tencent.mmkvdemo.MyService").setPackage("com.tencent.mmkvdemo"); bindService(i, this, BIND_AUTO_CREATE); } else if (cmd.equals(CMD_PREPARE_ASHMEM_BY_CP)) { super.prepareAshmemMMKVByCP(); } } } return super.onStartCommand(intent, flags, startId); } @Override public void onCreate() { super.onCreate(); Log.i("MMKV", "onCreate MyService_1"); } @Override public void onDestroy() { super.onDestroy(); Log.i("MMKV", "onDestroy MyService_1"); } @Override public void onServiceConnected(ComponentName name, IBinder service) { IAshmemMMKV ashmemMMKV = IAshmemMMKV.Stub.asInterface(service); try { ParcelableMMKV parcelableMMKV = ashmemMMKV.GetAshmemMMKV(); if (parcelableMMKV != null) { m_ashmemMMKV = parcelableMMKV.toMMKV(); if (m_ashmemMMKV != null) { Log.i("MMKV", "ashmem bool: " + m_ashmemMMKV.decodeBool("bool")); } } } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/SQLiteKV.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkvdemo; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public final class SQLiteKV { private static class SQLiteKVDBHelper extends SQLiteOpenHelper { private static final int DB_VERSION = 1; private static final String DB_NAME = "kv.db"; public static final String TABLE_NAME_STR = "kv_str"; public static final String TABLE_NAME_INT = "kv_int"; public SQLiteKVDBHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { String sql = "create table if not exists " + TABLE_NAME_STR + " (k text UNIQUE on conflict replace, v text)"; sqLiteDatabase.execSQL(sql); sql = "create table if not exists " + TABLE_NAME_INT + " (k text UNIQUE on conflict replace, v integer)"; sqLiteDatabase.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { String sql = "DROP TABLE IF EXISTS " + TABLE_NAME_STR; sqLiteDatabase.execSQL(sql); sql = "DROP TABLE IF EXISTS " + TABLE_NAME_INT; sqLiteDatabase.execSQL(sql); onCreate(sqLiteDatabase); } } private SQLiteKVDBHelper m_dbHelper; private SQLiteDatabase m_writetableDB; private SQLiteDatabase m_readableDB; public SQLiteKV(Context context) { m_dbHelper = new SQLiteKVDBHelper(context); m_dbHelper.setWriteAheadLoggingEnabled(true); } public void beginTransaction() { getWritetableDB().beginTransaction(); } public void endTransaction() { if (m_writetableDB != null) { try { m_writetableDB.setTransactionSuccessful(); } finally { m_writetableDB.endTransaction(); } } } private SQLiteDatabase getWritetableDB() { if (m_writetableDB == null) { m_writetableDB = m_dbHelper.getWritableDatabase(); } return m_writetableDB; } private SQLiteDatabase getReadableDatabase() { if (m_readableDB == null) { m_readableDB = m_dbHelper.getReadableDatabase(); } return m_readableDB; } @Override protected void finalize() throws Throwable { if (m_readableDB != null) { m_readableDB.close(); } if (m_writetableDB != null) { m_writetableDB.close(); } super.finalize(); } public boolean putInt(String key, int value) { ContentValues contentValues = new ContentValues(); contentValues.put("k", key); contentValues.put("v", value); long rowID = getWritetableDB().insert(SQLiteKVDBHelper.TABLE_NAME_INT, null, contentValues); return rowID != -1; } public int getInt(String key) { int value = 0; Cursor cursor = getReadableDatabase().rawQuery( "select v from " + SQLiteKVDBHelper.TABLE_NAME_INT + " where k=?", new String[] {key}); if (cursor.moveToFirst()) { value = cursor.getInt(cursor.getColumnIndexOrThrow("v")); } cursor.close(); return value; } public boolean putString(String key, String value) { ContentValues contentValues = new ContentValues(); contentValues.put("k", key); contentValues.put("v", value); long rowID = getWritetableDB().insert(SQLiteKVDBHelper.TABLE_NAME_STR, null, contentValues); return rowID != -1; } public String getString(String key) { String value = null; Cursor cursor = getReadableDatabase().rawQuery( "select v from " + SQLiteKVDBHelper.TABLE_NAME_STR + " where k=?", new String[] {key}); if (cursor.moveToFirst()) { value = cursor.getString(cursor.getColumnIndexOrThrow("v")); } cursor.close(); return value; } } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/TestParcelable.java ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2025 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. */ package com.tencent.mmkvdemo; import android.os.Parcel; import android.os.Parcelable; import java.util.LinkedList; import java.util.List; class TestParcelable implements Parcelable { int iValue; String sValue; List list; @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(iValue); parcel.writeString(sValue); // parcel.writeTypedList(list); } @Override public int describeContents() { return 0; } TestParcelable(int i, String s) { iValue = i; sValue = s; // list = new LinkedList<>(); // list.add(new Info("channel", 1)); // list.add(new Info("oppo", 2)); } private TestParcelable(Parcel in) { iValue = in.readInt(); sValue = in.readString(); list = in.createTypedArrayList(FakeInfo.CREATOR); } public static final Creator CREATOR = new Creator() { @Override public TestParcelable createFromParcel(Parcel parcel) { return new TestParcelable(parcel); } @Override public TestParcelable[] newArray(int i) { return new TestParcelable[i]; } }; } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/kotlin/KotlinUsecase.kt ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ package com.tencent.mmkvdemo import com.tencent.mmkv.MMKV import java.util.* fun kotlinFunctionalTest() { val mmkv = MMKV.mmkvWithID("testKotlin") mmkv.encode("bool", true) println("bool = " + mmkv.decodeBool("bool")) mmkv.encode("int", Integer.MIN_VALUE) println("int: " + mmkv.decodeInt("int")) mmkv.encode("long", java.lang.Long.MAX_VALUE) println("long: " + mmkv.decodeLong("long")) mmkv.encode("float", -3.14f) println("float: " + mmkv.decodeFloat("float")) mmkv.encode("double", java.lang.Double.MIN_VALUE) println("double: " + mmkv.decodeDouble("double")) mmkv.encode("string", "Hello from mmkv") println("string: " + mmkv.decodeString("string")) val bytes = byteArrayOf('m'.code.toByte(), 'm'.code.toByte(), 'k'.code.toByte(), 'v'.code.toByte()) mmkv.encode("bytes", bytes) println("bytes: " + mmkv.decodeBytes("bytes")?.let { String(it) }) mmkv.encode("empty_bytes", "".toByteArray()); println("empty_bytes: " + mmkv.decodeBytes("empty_bytes")?.let { String(it) }) mmkv.encode("stringSet", HashSet()) println("empty string set: " + mmkv.decodeStringSet("stringSet")) println("allKeys: " + Arrays.toString(mmkv.allKeys())) println("count = " + mmkv.count() + ", totalSize = " + mmkv.totalSize()) println("containsKey[string]: " + mmkv.containsKey("string")) mmkv.removeValueForKey("bool") println("bool: " + mmkv.decodeBool("bool")) mmkv.removeValuesForKeys(arrayOf("int", "long")) //mmkv.clearAll(); mmkv.clearMemoryCache() println("allKeys: " + Arrays.toString(mmkv.allKeys())) println("isFileValid[" + mmkv.mmapID() + "]: " + MMKV.isFileValid(mmkv.mmapID())) } ================================================ FILE: Android/MMKV/mmkvdemo/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: Android/MMKV/mmkvdemo/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ ================================================ FILE: Android/MMKV/mmkvdemo/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: iOS/MMKVDemo/MMKVDemo/Resources/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS UIBackgroundModes fetch UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: iOS/MMKVDemo/MMKVDemo/Resources/MMKVDemo.entitlements ================================================ com.apple.security.application-groups group.tencent.mmkv ================================================ FILE: iOS/MMKVDemo/MMKVDemo/TestMMKVCpp.cpp ================================================ // // TestMMKVCpp.cpp // MMKVDemo // // Created by lingol on 2025/2/12. // Copyright © 2025 Lingol. All rights reserved. // #include "TestMMKVCpp.hpp" #include using namespace std; using namespace mmkv; #define MMKVLog printf string to_string(const std::string& str) { return str; } template string to_string(const vector &arr, const char* sp = ", ") { string str; for (const auto &element : arr) { str += to_string(element); str += sp; } if (!str.empty()) { str.erase(str.length() - strlen(sp)); } return str; } void containerTest(MMKV* mmkv, bool decodeOnly) { { if (!decodeOnly) { vector vec = {"Hello", "MMKV-示例", "for", "POSIX"}; mmkv->set(vec, "string-set"); } vector vecResult; mmkv->getVector("string-set", vecResult); printf("string-set = %s\n", to_string(vecResult).c_str()); } #if __cplusplus>=202002L { if (!decodeOnly) { vector vec = {true, false, std::numeric_limits::min(), std::numeric_limits::max()}; mmkv->set(vec, "bool-set"); } vector vecResult; mmkv->getVector("bool-set", vecResult); printf("bool-set = %s\n", to_string(vecResult).c_str()); } { if (!decodeOnly) { vector vec = {1024, 0, std::numeric_limits::min(), std::numeric_limits::max()}; mmkv->set(vec, "int32-set"); } vector vecResult; mmkv->getVector("int32-set", vecResult); printf("int32-set = %s\n", to_string(vecResult).c_str()); } { if (!decodeOnly) { constexpr uint32_t arr[] = {2048, 0, std::numeric_limits::min(), std::numeric_limits::max()}; std::span vec = arr; mmkv->set(vec, "uint32-set"); } vector vecResult; mmkv->getVector("uint32-set", vecResult); printf("uint32-set = %s\n", to_string(vecResult).c_str()); } { if (!decodeOnly) { constexpr int64_t vec[] = {1024, 0, std::numeric_limits::min(), std::numeric_limits::max()}; mmkv->set(std::span(vec), "int64-set"); } vector vecResult; mmkv->getVector("int64-set", vecResult); printf("int64-set = %s\n", to_string(vecResult).c_str()); } { if (!decodeOnly) { vector vec = {1024, 0, std::numeric_limits::min(), std::numeric_limits::max()}; mmkv->set(vec, "uint64-set"); } vector vecResult; mmkv->getVector("uint64-set", vecResult); printf("uint64-set = %s\n", to_string(vecResult).c_str()); } { if (!decodeOnly) { vector vec = {1024.0f, 0.0f, std::numeric_limits::min(), std::numeric_limits::max()}; mmkv->set(vec, "float-set"); } vector vecResult; mmkv->getVector("float-set", vecResult); printf("float-set = %s\n", to_string(vecResult).c_str()); } { if (!decodeOnly) { vector vec = {1024.0, 0.0, std::numeric_limits::min(), std::numeric_limits::max()}; mmkv->set(vec, "double-set"); } vector vecResult; mmkv->getVector("double-set", vecResult); printf("double-set = %s\n", to_string(vecResult).c_str()); // Un-comment to test the functionality of set>(const T& value, key) // mmkv->set(&vecResult, "unsupported-type"); } #endif // __cplusplus>=202002L } void functionalTest(MMKV *mmkv, bool decodeOnly) { if (!decodeOnly) { mmkv->set(true, "bool"); } MMKVLog("bool = %d\n", mmkv->getBool("bool")); if (!decodeOnly) { mmkv->set(1024, "int32"); } MMKVLog("int32 = %d\n", mmkv->getInt32("int32")); if (!decodeOnly) { mmkv->set(std::numeric_limits::max(), "uint32"); } MMKVLog("uint32 = %u\n", mmkv->getUInt32("uint32")); if (!decodeOnly) { mmkv->set(std::numeric_limits::min(), "int64"); } MMKVLog("int64 = %lld\n", mmkv->getInt64("int64")); if (!decodeOnly) { mmkv->set(std::numeric_limits::max(), "uint64"); } MMKVLog("uint64 = %llu\n", mmkv->getUInt64("uint64")); if (!decodeOnly) { mmkv->set(3.14f, "float"); } MMKVLog("float = %f\n", mmkv->getFloat("float")); if (!decodeOnly) { mmkv->set(std::numeric_limits::max(), "double"); } MMKVLog("double = %f\n", mmkv->getDouble("double")); if (!decodeOnly) { mmkv->set("Hello, MMKV-示例 for POSIX", "raw_string"); std::string str = "Hello, MMKV-示例 for POSIX string"; mmkv->set(str, "string"); mmkv->set(std::string_view(str).substr(7, 21), "string_view"); } std::string result; mmkv->getString("raw_string", result); MMKVLog("raw_string = %s\n", result.c_str()); mmkv->getString("string", result); MMKVLog("string = %s\n", result.c_str()); mmkv->getString("string_view", result); MMKVLog("string_view = %s\n", result.c_str()); containerTest(mmkv, decodeOnly); MMKVLog("allKeys: %s\n", ::to_string(mmkv->allKeys()).c_str()); MMKVLog("count = %zu, totalSize = %zu\n", mmkv->count(), mmkv->totalSize()); MMKVLog("containsKey[string]: %d\n", mmkv->containsKey("string")); mmkv->removeValueForKey("bool"); MMKVLog("bool: %d\n", mmkv->getBool("bool")); mmkv->removeValuesForKeys({"int", "long"}); mmkv->set("some string", "null string"); result.erase(); mmkv->getString("null string", result); MMKVLog("string before set null: %s\n", result.c_str()); mmkv->set((const char *) nullptr, "null string"); //mmkv->set("", "null string"); result.erase(); mmkv->getString("null string", result); MMKVLog("string after set null: %s, containsKey: %d\n", result.c_str(), mmkv->containsKey("null string")); //kv.sync(); //kv.async(); //kv.clearAll(); mmkv->clearMemoryCache(); MMKVLog("allKeys: %s\n", ::to_string(mmkv->allKeys()).c_str()); MMKVLog("isFileValid[%s]: %d\n", mmkv->mmapID().c_str(), MMKV::isFileValid(mmkv->mmapID())); } void functionalTest(bool decodeOnly) { auto kv = MMKV::mmkvWithID("testCpp"); functionalTest(kv, decodeOnly); } // void baseline(mmkv::MMKV *kv) { // return; // } ================================================ FILE: iOS/MMKVDemo/MMKVDemo/TestMMKVCpp.hpp ================================================ // // TestMMKVCpp.hpp // MMKVDemo // // Created by lingol on 2025/2/12. // Copyright © 2025 Lingol. All rights reserved. // #ifndef TestMMKVCpp_hpp #define TestMMKVCpp_hpp #include void functionalTest(bool decodeOnly); void baseline(mmkv::MMKV *kv); #endif /* TestMMKVCpp_hpp */ ================================================ FILE: iOS/MMKVDemo/MMKVDemo/ViewController+TestCaseBad.h ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2020 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. */ #import "ViewController.h" NS_ASSUME_NONNULL_BEGIN @interface ViewController (TestCaseBad) - (void)testCornerSize; - (void)testFastRemoveCornerSize; - (void)testChineseCharKey; - (void)testItemSizeHolderOverride; - (void)testAutoExpireWildPtr; @end NS_ASSUME_NONNULL_END ================================================ FILE: iOS/MMKVDemo/MMKVDemo/ViewController+TestCaseBad.mm ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2020 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. */ #import "ViewController+TestCaseBad.h" #import #import #import @implementation ViewController (TestCaseBad) - (void)testCornerSize { // auto mmkv = [MMKV mmkvWithID:@"test/cornerSize" cryptKey:[@"crypt" dataUsingEncoding:NSUTF8StringEncoding]]; auto mmkv = [MMKV mmkvWithID:@"test/cornerSize1"]; [mmkv clearAll]; auto size = getpagesize() - 2; size -= 4; NSString *key = @"key"; auto keySize = 3 + 1; size -= keySize; auto valueSize = 3; size -= valueSize; NSData *value = [NSMutableData dataWithLength:size]; [mmkv setObject:value forKey:key]; } - (void)testFastRemoveCornerSize { // auto mmkv = [MMKV mmkvWithID:@"test/FastRemoveCornerSize" cryptKey:[@"crypt" dataUsingEncoding:NSUTF8StringEncoding]]; auto mmkv = [MMKV mmkvWithID:@"test/FastRemoveCornerSize1"]; [mmkv clearAll]; auto size = getpagesize() - 4; size -= 4; NSString *key = @"key"; auto keySize = 3 + 1; size -= keySize; auto valueSize = 3; size -= valueSize; size -= (keySize + 1); // total size of fast remove size /= 16; NSMutableData *value = [NSMutableData dataWithLength:size]; auto ptr = (char *) value.mutableBytes; for (int i = 0; i < value.length; i++) { ptr[i] = 'A'; } for (int i = 0; i < 16; i++) { [mmkv setObject:value forKey:key]; // when a full write back is occur, here's corruption happens [mmkv removeValueForKey:key]; } } - (void)testChineseCharKey { std::vector> fakeKeyValues = { {"UUID", 36, 37}, {"webViewShake", 1, 2}, {"install_now", 1, 1}, {"HomeContainerViewControllerGuide", 32, 33}, {"AdvertisingKey", 397, 397}, {"PrivacyPolicy", 1, 1}, {"appVersion", 6, 7}, {"IS_RECEIVE_PUSH", 1, 1}, {"invite_url", 0, 1}, {"kHaveOneDayOrLongTime", 8, 8}, {"kHaveOneHourOrLongTime", 8, 8}, {"PO_TYPE", 139, 139}, {"invite_url", 41, 42}, {"watermark", 95, 96}, {"BrowserWhitelist", 253, 253}, {"open_log", 0, 1}, {"xz_district_key", 1390, 1390}, {"open_position", 1, 1}, {"xz_auth_wechat", 1, 1}, {"xz_auth_weibo", 0, 1}, {"xz_auth_qq", 0, 1}, {"cache_expire", 0, 8}, {"PopupWindowNotice", 461, 461}, {"推送通知提示间隔", 1, 1}, {"版本更新提示间隔", 0, 1}, {"定位提醒显示间隔", 0, 8}, {"QFH5_JAVASCRIPT", 2157, 2159}, {"上次定位_city", 9, 10}, {"PopupWindowNotice", 0, 0}, }; auto mmkv = [MMKV mmkvWithID:@"testChineseCharKey"]; for (auto &kv : fakeKeyValues) { auto key = [NSString stringWithUTF8String:std::get<0>(kv).c_str()]; auto size = std::get<1>(kv); const auto orgSize = std::get<2>(kv); if (orgSize == 8) { [mmkv setDouble:0.0 forKey:key]; } else if (size < orgSize) { auto buf = [NSMutableData dataWithLength:size]; [mmkv setObject:buf forKey:key]; } else if (size == 1) { [mmkv setBool:YES forKey:key]; } else if (size == 0) { [mmkv removeValueForKey:key]; } else if (size > 8) { if (orgSize <= 127) { size -= 1; } else { size -= 2; } auto buf = [NSMutableData dataWithLength:size]; [mmkv setObject:buf forKey:key]; } else { assert(0); } auto resultSize = [mmkv getValueSizeForKey:key actualSize:NO]; assert(resultSize == orgSize); NSLog(@"%@, %zu", key, [mmkv actualSize]); } } - (void)testItemSizeHolderOverride { auto mmapID = @"testItemSizeHolderOverride"; auto cryptKey = nil; // auto mmapID = @"testItemSizeHolderOverride_crypt"; // auto cryptKey = [@"Key_seq_1" dataUsingEncoding:NSUTF8StringEncoding]; /* do these on v1.1.2 { auto mmkv = [MMKV mmkvWithID:mmapID cryptKey:cryptKey]; // turn on to check crypt MMKV // [mmkv setBool:YES forKey:@"b"]; // turn on (mutex with the above setBool:forKey testcase) to check crypt MMKV // [mmkv setInt32:0xff forKey:@"i"]; auto value = [NSMutableData dataWithLength:512]; [mmkv setData:value forKey:@"data"]; NSLog(@"%@", [mmkv allKeys]); }*/ // do these on v1.2.0 { auto mmkv = [MMKV mmkvWithID:mmapID cryptKey:cryptKey]; auto actualSize = [mmkv actualSize]; auto fileSize = [mmkv totalSize]; // force a fullwrieback() auto valueSize = fileSize - actualSize; auto value = [NSMutableData dataWithLength:valueSize]; [mmkv setObject:value forKey:@"bigData"]; [mmkv clearMemoryCache]; // it might throw exception // you won't find the key "data" NSLog(@"%@", [mmkv allKeys]); } } - (void)testAutoExpireWildPtr { NSString *mmapID = @"testAutoExpire"; auto mmkv = [MMKV mmkvWithID:mmapID]; [mmkv clearAll]; [mmkv trim]; [mmkv enableAutoKeyExpire:1]; auto size = getpagesize() - 4; size -= 4; NSString *key = @"key"; auto keySize = 3 + 1; size -= keySize; auto valueSize = 3; size -= valueSize; // size -= (keySize + 1); // total size of fast remove size /= 16; NSMutableData *value = [NSMutableData dataWithLength:size]; auto ptr = (char *) value.mutableBytes; for (int i = 0; i < value.length; i++) { ptr[i] = 'A'; } for (int i = 0; i < 15; i++) { [mmkv setObject:value forKey:key]; // when a full write back is occur, here's corruption happens } sleep(2); [mmkv setObject:value forKey:key]; assert([mmkv containsKey:key]); } @end ================================================ FILE: iOS/MMKVDemo/MMKVDemo/ViewController.h ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ #import @class MMKV; @interface ViewController : UIViewController @property(weak, nonatomic) IBOutlet UIButton *m_btn; @property(weak, nonatomic) IBOutlet UIActivityIndicatorView *m_loading; + (void)testMMKV:(MMKV *)mmkv decodeOnly:(BOOL)decodeOnly; @end ================================================ FILE: iOS/MMKVDemo/MMKVDemo/ViewController.mm ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ #import "MMKVDemo-Swift.h" #import "ViewController+TestCaseBad.h" #import #import "TestMMKVCpp.hpp" @interface TestNSArchive : NSObject @property(nonatomic, strong) NSString *m_username; @property(nonatomic, assign) int32_t m_age; @property(nonatomic, assign) float m_score; @end @implementation TestNSArchive - (id)initWithCoder:(NSCoder *)decoder { self = [super init]; if (!self) { return nil; } self.m_username = [decoder decodeObjectForKey:@"m_username"]; self.m_age = [decoder decodeInt32ForKey:@"m_age"]; self.m_score = [decoder decodeFloatForKey:@"m_score"]; return self; } - (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeObject:self.m_username forKey:@"m_username"]; [encoder encodeInteger:self.m_age forKey:@"m_age"]; [encoder encodeFloat:self.m_score forKey:@"m_score"]; } + (BOOL)supportsSecureCoding { return YES; } @end @implementation ViewController { NSMutableArray *m_arrStrings; NSMutableArray *m_arrStrKeys; NSMutableArray *m_arrIntKeys; NSMutableArray *m_arrObjKeys; NSMutableArray *m_arrNSCodingObjs; int m_loops; } - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateTodayContent) name:UIApplicationDidBecomeActiveNotification object:nil]; [self functionTestCpp]; [self funcionalTest:NO]; [self testReKey]; [self testImportFromUserDefault]; // [self testCornerSize]; // [self testFastRemoveCornerSize]; // [self testChineseCharKey]; // [self testItemSizeHolderOverride]; [self testAutoExpire]; // [self testAutoExpireWildPtr]; DemoSwiftUsage *swiftUsageDemo = [[DemoSwiftUsage alloc] init]; [swiftUsageDemo testSwiftFunctionality]; [swiftUsageDemo testSwiftAutoExpire]; [self testMultiProcess]; // [self testMultiProcess]; [self testBackup]; [self testRestore]; [self testExpectedCapacity]; [self onlyOneKeyTest]; [self overrideTest]; [self testCompareBeforeSet]; [self testClearAllWithKeepingSpace]; [self testRemoveStorage]; [self testReadOnly:NO]; [self testImport]; m_loops = 10000; m_arrStrings = [NSMutableArray arrayWithCapacity:m_loops]; m_arrStrKeys = [NSMutableArray arrayWithCapacity:m_loops]; m_arrIntKeys = [NSMutableArray arrayWithCapacity:m_loops]; m_arrObjKeys = [NSMutableArray arrayWithCapacity:m_loops]; m_arrNSCodingObjs = [NSMutableArray arrayWithCapacity:m_loops]; for (size_t index = 0; index < m_loops; index++) { // auto str = @"[MMKV] [Info]: protection on [/var/mobile/Containers/Data/Application/B93F2BD3-E0DB-49B3-9BB0-C662E2FC11D9/Documents/mmkv/cips_commoncache] is NSFileProtectionCompleteUntilFirstUserAuthentication"; // str = [str stringByAppendingFormat:@", %s-%d", __FILE__, rand()]; NSString *str = [NSString stringWithFormat:@"%s-%d", __FILE__, rand()]; [m_arrStrings addObject:str]; NSString *strKey = [NSString stringWithFormat:@"str-%zu", index]; [m_arrStrKeys addObject:strKey]; NSString *intKey = [NSString stringWithFormat:@"int-%zu", index]; [m_arrIntKeys addObject:intKey]; /* NSString *objKey = [NSString stringWithFormat:@"obj-%zu", index]; [m_arrObjKeys addObject:objKey]; TestNSArchive *obj = [[TestNSArchive alloc] init]; obj.m_username = str; obj.m_age = rand(); obj.m_age = rand() * rand() * 0.5; [m_arrNSCodingObjs addObject:obj];*/ } //getMMKVForBatchTest(); } - (void)funcionalTest:(BOOL)decodeOnly { auto path = [MMKV mmkvBasePath]; path = [path stringByDeletingLastPathComponent]; path = [path stringByAppendingPathComponent:@"mmkv_2"]; NSData *key_1 = [@"Key_seq_1" dataUsingEncoding:NSUTF8StringEncoding]; auto mmkv = [MMKV mmkvWithID:@"test/case_aes" cryptKey:key_1 rootPath:path]; if (!decodeOnly) { [mmkv setBool:YES forKey:@"bool"]; } NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]); if (!decodeOnly) { [mmkv setInt32:-1024 forKey:@"int32"]; } NSLog(@"int32:%d", [mmkv getInt32ForKey:@"int32"]); if (!decodeOnly) { [mmkv setUInt32:std::numeric_limits::max() forKey:@"uint32"]; } NSLog(@"uint32:%u", [mmkv getUInt32ForKey:@"uint32"]); if (!decodeOnly) { [mmkv setInt64:std::numeric_limits::min() forKey:@"int64"]; } NSLog(@"int64:%lld", [mmkv getInt64ForKey:@"int64"]); if (!decodeOnly) { [mmkv setUInt64:std::numeric_limits::max() forKey:@"uint64"]; } NSLog(@"uint64:%llu", [mmkv getInt64ForKey:@"uint64"]); if (!decodeOnly) { [mmkv setFloat:-3.1415926 forKey:@"float"]; } NSLog(@"float:%f", [mmkv getFloatForKey:@"float"]); if (!decodeOnly) { [mmkv setDouble:std::numeric_limits::max() forKey:@"double"]; } NSLog(@"double:%f", [mmkv getDoubleForKey:@"double"]); if (!decodeOnly) { [mmkv setString:@"hello, mmkv" forKey:@"string"]; } NSLog(@"string:%@", [mmkv getStringForKey:@"string"]); if (!decodeOnly) { [mmkv setObject:nil forKey:@"string"]; NSLog(@"string after set nil:%@, containsKey:%d", [mmkv getObjectOfClass:NSString.class forKey:@"string"], [mmkv containsKey:@"string"]); } if (!decodeOnly) { [mmkv setDate:[NSDate date] forKey:@"date"]; } NSLog(@"date:%@", [mmkv getDateForKey:@"date"]); if (!decodeOnly) { auto str = @"[MMKV] [Info]: protection on [/var/mobile/Containers/Data/Application/B93F2BD3-E0DB-49B3-9BB0-C662E2FC11D9/Documents/mmkv/cips_commoncache] is NSFileProtectionCompleteUntilFirstUserAuthentication"; [mmkv setData:[str dataUsingEncoding:NSUTF8StringEncoding] forKey:@"data"]; } NSData *data = [mmkv getDataForKey:@"data"]; NSLog(@"data:%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); NSLog(@"data length:%zu, value size consumption:%zu", data.length, [mmkv getValueSizeForKey:@"data" actualSize:NO]); if (!decodeOnly) { [mmkv setObject:[NSData data] forKey:@"data"]; NSLog(@"data after set empty data:%@, containsKey:%d", [mmkv getObjectOfClass:NSData.class forKey:@"data"], [mmkv containsKey:@"data"]); } if (!decodeOnly) { auto array = @[ @"1984", @"2046", @"Move" ]; [mmkv setObject:array forKey:@"array"]; } NSArray *array = [mmkv getObjectOfClass:NSArray.class forKey:@"array"]; NSLog(@"array:%@", array); if (!decodeOnly) { [mmkv removeValueForKey:@"bool"]; NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]); [mmkv removeValuesForKeys:@[ @"int32", @"uint64" ]]; NSLog(@"allKeys %@", [mmkv allKeys]); } [mmkv close]; } - (void)functionTestCpp { functionalTest(false); } - (void)testMMKV:(NSString *)mmapID withCryptKey:(NSData *)cryptKey aes256:(BOOL)aes256 decodeOnly:(BOOL)decodeOnly { MMKV *mmkv = [MMKV mmkvWithID:mmapID cryptKey:cryptKey aes256:aes256]; [ViewController testMMKV:mmkv decodeOnly:decodeOnly]; NSLog(@"isFileValid[%@]: %d, checkExist: %d", mmapID, [MMKV isFileValid:mmapID], [MMKV checkExist:mmapID rootPath:nil]); } + (void)testMMKV:(MMKV *)mmkv decodeOnly:(BOOL)decodeOnly { if (!decodeOnly) { [mmkv setInt32:-1024 forKey:@"int32"]; } NSLog(@"int32:%d", [mmkv getInt32ForKey:@"int32"]); if (!decodeOnly) { [mmkv setUInt32:std::numeric_limits::max() forKey:@"uint32"]; } NSLog(@"uint32:%u", [mmkv getUInt32ForKey:@"uint32"]); if (!decodeOnly) { [mmkv setInt64:std::numeric_limits::min() forKey:@"int64"]; } NSLog(@"int64:%lld", [mmkv getInt64ForKey:@"int64"]); if (!decodeOnly) { [mmkv setUInt64:std::numeric_limits::max() forKey:@"uint64"]; } NSLog(@"uint64:%llu", [mmkv getInt64ForKey:@"uint64"]); if (!decodeOnly) { [mmkv setFloat:-3.1415926 forKey:@"float"]; } NSLog(@"float:%f", [mmkv getFloatForKey:@"float"]); if (!decodeOnly) { [mmkv setDouble:std::numeric_limits::max() forKey:@"double"]; } NSLog(@"double:%f", [mmkv getDoubleForKey:@"double"]); if (!decodeOnly) { [mmkv setObject:@"An efficient, small mobile key-value storage framework developed by WeChat. Works on Android, iOS, macOS, Windows, and POSIX." forKey:@"string"]; } NSLog(@"string:%@", [mmkv getObjectOfClass:NSString.class forKey:@"string"]); if (!decodeOnly) { [mmkv setObject:[NSDate date] forKey:@"date"]; } NSLog(@"date:%@", [mmkv getObjectOfClass:NSDate.class forKey:@"date"]); if (!decodeOnly) { [mmkv setObject:[@"An efficient, small mobile key-value storage framework developed by WeChat(微信). Works on Android, iOS, macOS, Windows, and POSIX." dataUsingEncoding:NSUTF8StringEncoding] forKey:@"data"]; } NSData *data = [mmkv getObjectOfClass:NSData.class forKey:@"data"]; NSLog(@"data:%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); if (!decodeOnly) { [mmkv removeValueForKey:@"bool"]; } NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]); NSLog(@"containsKey[string]: %d", [mmkv containsKey:@"string"]); [mmkv removeValuesForKeys:@[ @"int", @"long" ]]; [mmkv clearMemoryCache]; } - (void)testReKey { NSString *mmapID = @"testAES_reKey"; [self testMMKV:mmapID withCryptKey:nullptr aes256:NO decodeOnly:NO]; MMKV *kv = [MMKV mmkvWithID:mmapID cryptKey:nullptr]; NSData *key_1 = [@"Key_Seq_1" dataUsingEncoding:NSUTF8StringEncoding]; [kv reKey:key_1]; [kv clearMemoryCache]; [self testMMKV:mmapID withCryptKey:key_1 aes256:NO decodeOnly:YES]; NSData *key_2 = [@"Key_Seq_Very_Looooooooong" dataUsingEncoding:NSUTF8StringEncoding]; [kv reKey:key_2 aes256:YES]; [kv clearMemoryCache]; [self testMMKV:mmapID withCryptKey:key_2 aes256:YES decodeOnly:YES]; [kv reKey:nullptr]; [kv clearMemoryCache]; [self testMMKV:mmapID withCryptKey:nullptr aes256:NO decodeOnly:YES]; } - (void)testImportFromUserDefault { NSUserDefaults *userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"testNSUserDefaults1"]; [userDefault setBool:YES forKey:@"bool"]; [userDefault setInteger:std::numeric_limits::max() forKey:@"NSInteger"]; [userDefault setFloat:3.14 forKey:@"float"]; [userDefault setDouble:std::numeric_limits::max() forKey:@"double"]; [userDefault setObject:@"hello, NSUserDefaults" forKey:@"string"]; [userDefault setObject:[NSDate date] forKey:@"date"]; [userDefault setObject:[@"hello, NSUserDefaults again" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"data"]; [userDefault setURL:[NSURL URLWithString:@"https://mail.qq.com"] forKey:@"url"]; NSNumber *number = [NSNumber numberWithBool:YES]; [userDefault setObject:number forKey:@"number_bool"]; number = [NSNumber numberWithChar:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_char"]; number = [NSNumber numberWithUnsignedChar:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_unsigned_char"]; number = [NSNumber numberWithShort:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_short"]; number = [NSNumber numberWithUnsignedShort:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_unsigned_short"]; number = [NSNumber numberWithInt:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_int"]; number = [NSNumber numberWithUnsignedInt:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_unsigned_int"]; number = [NSNumber numberWithLong:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_long"]; number = [NSNumber numberWithUnsignedLong:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_unsigned_long"]; number = [NSNumber numberWithLongLong:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_long_long"]; number = [NSNumber numberWithUnsignedLongLong:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_unsigned_long_long"]; number = [NSNumber numberWithFloat:3.1415]; [userDefault setObject:number forKey:@"number_float"]; number = [NSNumber numberWithDouble:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_double"]; number = [NSNumber numberWithInteger:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_NSInteger"]; number = [NSNumber numberWithUnsignedInteger:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_NSUInteger"]; auto mmkv = [MMKV mmkvWithID:@"testImportNSUserDefaults1"]; [mmkv migrateFromUserDefaultsDictionaryRepresentation:userDefault.dictionaryRepresentation]; [mmkv clearMemoryCache]; NSLog(@"%@", [mmkv allKeys]); NSLog(@"migrate from NSUserDefault begin"); NSLog(@"bool = %d", [mmkv getBoolForKey:@"bool"]); NSLog(@"NSInteger = %lld", [mmkv getInt64ForKey:@"NSInteger"]); NSLog(@"float = %f", [mmkv getFloatForKey:@"float"]); NSLog(@"double = %f", [mmkv getDoubleForKey:@"double"]); NSLog(@"string = %@", [mmkv getStringForKey:@"string"]); NSLog(@"date = %@", [mmkv getDateForKey:@"date"]); NSLog(@"data = %@", [[NSString alloc] initWithData:[mmkv getDataForKey:@"data"] encoding:NSUTF8StringEncoding]); NSLog(@"url = %@", [NSKeyedUnarchiver unarchivedObjectOfClass:NSURL.class fromData:[mmkv getDataForKey:@"url"] error:nil]); NSLog(@"number_bool = %d", [mmkv getBoolForKey:@"number_bool"]); NSLog(@"number_char = %d", [mmkv getInt32ForKey:@"number_char"]); NSLog(@"number_unsigned_char = %d", [mmkv getInt32ForKey:@"number_unsigned_char"]); NSLog(@"number_short = %d", [mmkv getInt32ForKey:@"number_short"]); NSLog(@"number_unsigned_short = %d", [mmkv getInt32ForKey:@"number_unsigned_short"]); NSLog(@"number_int = %d", [mmkv getInt32ForKey:@"number_int"]); NSLog(@"number_unsigned_int = %u", [mmkv getUInt32ForKey:@"number_unsigned_int"]); NSLog(@"number_long = %lld", [mmkv getInt64ForKey:@"number_long"]); NSLog(@"number_unsigned_long = %llu", [mmkv getUInt64ForKey:@"number_unsigned_long"]); NSLog(@"number_long_long = %lld", [mmkv getInt64ForKey:@"number_long_long"]); NSLog(@"number_unsigned_long_long = %llu", [mmkv getUInt64ForKey:@"number_unsigned_long_long"]); NSLog(@"number_float = %f", [mmkv getFloatForKey:@"number_float"]); NSLog(@"number_double = %f", [mmkv getDoubleForKey:@"number_double"]); NSLog(@"number_NSInteger = %lld", [mmkv getInt64ForKey:@"number_NSInteger"]); NSLog(@"number_NSUInteger = %llu", [mmkv getUInt64ForKey:@"number_NSUInteger"]); NSLog(@"migrate from NSUserDefault end"); } - (void)testAutoExpire { NSString *mmapID = @"testAutoExpire"; // disable auto expire by config auto config = MMKVConfigDefault(); config.enableKeyExpire = @NO; // config.recover = MMKVOnErrorRecover; // config.itemSizeLimit = 1; auto mmkv = [MMKV mmkvWithID:mmapID config:config]; [mmkv clearAll]; [mmkv trim]; // [mmkv disableAutoKeyExpire]; // this call become a no-op [self testMMKV:mmapID withCryptKey:nil aes256:NO decodeOnly:NO]; [mmkv setBool:YES forKey:@"auto_expire_key_1"]; // enable auto expire by config [mmkv close]; mmkv = nil; config.enableKeyExpire = @YES; config.expiredInSeconds = 1; mmkv = [MMKV mmkvWithID:mmapID config:config]; // [mmkv enableAutoKeyExpire:1]; // this call become a no-op [mmkv setString:@"never_expire_key_1" forKey:@"never_expire_key_1" expireDuration:MMKVExpireNever]; auto arr = @[ @"str1", @"str2" ]; [mmkv setObject:arr forKey:@"arr" expireDuration:0]; NSArray *newArr = [mmkv getObjectOfClass:NSArray.class forKey:@"arr"]; assert([arr isEqualToArray:newArr]); // sleep(2); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ assert([mmkv containsKey:@"auto_expire_key_1"] == NO); assert([mmkv containsKey:@"never_expire_key_1"] == YES); [self testMMKV:mmapID withCryptKey:nil aes256:NO decodeOnly:YES]; [mmkv removeValueForKey:@"never_expire_key_1"]; [mmkv enableAutoKeyExpire:MMKVExpireNever]; [mmkv setString:@"never_expire_key_1" forKey:@"never_expire_key_1"]; [mmkv setBool:YES forKey:@"auto_expire_key_1" expireDuration:1]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // sleep(2); assert([mmkv containsKey:@"never_expire_key_1"] == YES); assert([mmkv containsKey:@"auto_expire_key_1"] == NO); }); }); } - (IBAction)onBtnClick:(id)sender { [self.m_loading startAnimating]; self.m_btn.enabled = NO; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self->m_arrStrings = [NSMutableArray arrayWithCapacity:self->m_loops]; for (size_t index = 0; index < self->m_loops; index++) { // auto str = @"[MMKV] [Info]: protection on [/var/mobile/Containers/Data/Application/B93F2BD3-E0DB-49B3-9BB0-C662E2FC11D9/Documents/mmkv/cips_commoncache] is NSFileProtectionCompleteUntilFirstUserAuthentication"; // str = [str stringByAppendingFormat:@", %s-%d", __FILE__, rand()]; NSString *str = [NSString stringWithFormat:@"%s-%d", __FILE__, rand()]; [self->m_arrStrings addObject:str]; //TestNSArchive *obj = self->m_arrNSCodingObjs[index]; //obj.m_username = str; } [self mmkvBaselineTest:self->m_loops]; [self userDefaultBaselineTest:self->m_loops]; //[self brutleTest]; [self.m_loading stopAnimating]; self.m_btn.enabled = YES; }); } #pragma mark - mmkv baseline test - (void)mmkvBaselineTest:(int)loops { [self mmkvBatchReadInt:loops]; [self mmkvBatchWriteInt:loops]; [self mmkvBatchReadString:loops]; [self mmkvBatchWriteString:loops]; //[self mmkvBatchWriteObject:loops]; //[self mmkvBatchReadObject:loops]; //[self mmkvBatchDeleteString:loops]; //[[MMKV defaultMMKV] trim]; // auto mmkv = getMMKVForBatchTest(); // [mmkv clearMemoryCache]; // [mmkv actualSize]; } MMKV *getMMKVForBatchTest() { // return [MMKV mmkvWithID:@"inter-process" mode:MMKVMultiProcess]; // auto cryptKey = [@"crypt_key" dataUsingEncoding:NSUTF8StringEncoding]; NSData *cryptKey = nil; // static auto key = [NSString stringWithFormat:@"batchTest_%d", rand()]; // auto key = @"batchTest_crypt1"; static auto key = @"batchTest1"; MMKV *mmkv = [MMKV mmkvWithID:key cryptKey:cryptKey]; return mmkv; } - (void)mmkvBatchWriteInt:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; MMKV *mmkv = getMMKVForBatchTest(); for (int index = 0; index < loops; index++) { int32_t tmp = rand(); NSString *intKey = m_arrIntKeys[index]; // NSString *intKey = [NSString stringWithFormat:@"6AB741D2-426B-4CC2-918B-EC910753FF74-%d", index]; [mmkv setInt32:tmp forKey:intKey]; } NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"mmkv write int %d times, cost:%.1f ms", loops, cost); /* delete some startDate = [NSDate date]; for (int index = 0; index < (loops / 2); index++) { int32_t tmp = rand() % loops; NSString *intKey = m_arrIntKeys[tmp]; [mmkv removeValueForKey:intKey]; } endDate = [NSDate date]; cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"mmkv delete int %d times, cost:%d ms", (loops / 2), cost);*/ } } - (void)mmkvBatchReadInt:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; MMKV *mmkv = getMMKVForBatchTest(); for (int index = 0; index < loops; index++) { NSString *intKey = m_arrIntKeys[index]; // NSString *intKey = [NSString stringWithFormat:@"6AB741D2-426B-4CC2-918B-EC910753FF74-%d", index]; [mmkv getInt32ForKey:intKey]; } NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"mmkv read int %d times, cost:%.1f ms", loops, cost); } } - (void)mmkvBatchWriteString:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; MMKV *mmkv = getMMKVForBatchTest(); for (int index = 0; index < loops; index++) { NSString *str = m_arrStrings[index]; NSString *strKey = m_arrStrKeys[index]; [mmkv setObject:str forKey:strKey]; } NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"mmkv write string %d times, cost:%.1f ms", loops, cost); /* delete some startDate = [NSDate date]; for (int index = 0; index < (loops / 2); index++) { int32_t tmp = rand() % loops; NSString *strKey = m_arrStrKeys[tmp]; [mmkv removeValueForKey:strKey]; } endDate = [NSDate date]; cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"mmkv delete string %d times, cost:%d ms", (loops / 2), cost);*/ } } - (void)mmkvBatchReadString:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; MMKV *mmkv = getMMKVForBatchTest(); for (int index = 0; index < loops; index++) { NSString *strKey = m_arrStrKeys[index]; [mmkv getObjectOfClass:NSString.class forKey:strKey]; } NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"mmkv read string %d times, cost:%.1f ms", loops, cost); } } - (void)mmkvBatchDeleteString:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; MMKV *mmkv = getMMKVForBatchTest(); for (int index = 0; index < loops; index++) { NSString *strKey = m_arrStrKeys[index]; [mmkv removeValueForKey:strKey]; } NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"mmkv delete string %d times, cost:%.1f ms", loops, cost); } } - (void)mmkvBatchWriteObject:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; MMKV *mmkv = getMMKVForBatchTest(); for (int index = 0; index < loops; index++) { TestNSArchive *obj = m_arrNSCodingObjs[index]; NSString *objKey = m_arrObjKeys[index]; [mmkv setObject:obj forKey:objKey]; } NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"mmkv write object %d times, cost:%.1f ms", loops, cost); } } - (void)mmkvBatchReadObject:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; MMKV *mmkv = getMMKVForBatchTest(); for (int index = 0; index < loops; index++) { NSString *objKey = m_arrObjKeys[index]; [mmkv getObjectOfClass:TestNSArchive.class forKey:objKey]; } NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"mmkv read object %d times, cost:%.1f ms", loops, cost); } } #pragma mark - NSUserDefault baseline test - (void)userDefaultBaselineTest:(int)loops { [self userDefaultBatchWriteInt:loops]; [self userDefaultBatchReadInt:loops]; [self userDefaultBatchWriteString:loops]; [self userDefaultBatchReadString:loops]; //[self userDefaultBatchWriteObject:loops]; //[self userDefaultBatchReadObject:loops]; } - (void)userDefaultBatchWriteInt:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; NSUserDefaults *userdefault = [NSUserDefaults standardUserDefaults]; for (int index = 0; index < loops; index++) { NSInteger tmp = rand(); NSString *intKey = m_arrIntKeys[index]; [userdefault setInteger:tmp forKey:intKey]; } [userdefault synchronize]; NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"NSUserDefaults write int %d times, cost:%.1f ms", loops, cost); } } - (void)userDefaultBatchReadInt:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; NSUserDefaults *userdefault = [NSUserDefaults standardUserDefaults]; for (int index = 0; index < loops; index++) { NSString *intKey = m_arrIntKeys[index]; [userdefault integerForKey:intKey]; } NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"NSUserDefaults read int %d times, cost:%.1f ms", loops, cost); } } - (void)userDefaultBatchWriteString:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; NSUserDefaults *userdefault = [NSUserDefaults standardUserDefaults]; for (int index = 0; index < loops; index++) { NSString *str = m_arrStrings[index]; NSString *strKey = m_arrStrKeys[index]; [userdefault setObject:str forKey:strKey]; } [userdefault synchronize]; NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"NSUserDefaults write string %d times, cost:%.1f ms", loops, cost); } } - (void)userDefaultBatchReadString:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; NSUserDefaults *userdefault = [NSUserDefaults standardUserDefaults]; for (int index = 0; index < loops; index++) { NSString *strKey = m_arrStrKeys[index]; [userdefault objectForKey:strKey]; } NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"NSUserDefaults read string %d times, cost:%.1f ms", loops, cost); } } - (void)userDefaultBatchWriteObject:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; NSUserDefaults *userdefault = [NSUserDefaults standardUserDefaults]; for (int index = 0; index < loops; index++) { TestNSArchive *obj = m_arrNSCodingObjs[index]; NSString *objKey = m_arrObjKeys[index]; auto tmp = [NSKeyedArchiver archivedDataWithRootObject:obj requiringSecureCoding:YES error:nil]; [userdefault setObject:tmp forKey:objKey]; } [userdefault synchronize]; NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"NSUserDefaults write object %d times, cost:%.1f ms", loops, cost); } } - (void)userDefaultBatchReadObject:(int)loops { @autoreleasepool { NSDate *startDate = [NSDate date]; NSUserDefaults *userdefault = [NSUserDefaults standardUserDefaults]; for (int index = 0; index < loops; index++) { NSString *objKey = m_arrObjKeys[index]; NSData *tmp = [userdefault objectForKey:objKey]; [NSKeyedUnarchiver unarchivedObjectOfClass:TestNSArchive.class fromData:tmp error:nil]; } NSDate *endDate = [NSDate date]; auto cost = [endDate timeIntervalSinceDate:startDate] * 1000; NSLog(@"NSUserDefaults read object %d times, cost:%.1f ms", loops, cost); } } #pragma mark - brutle test - (void)brutleTest { auto mmkv = [MMKV mmkvWithID:@"brutleTest"]; auto ptr = malloc(1024); auto data = [NSData dataWithBytes:ptr length:1024]; free(ptr); for (size_t index = 0; index < std::numeric_limits::max(); index++) { NSString *key = [NSString stringWithFormat:@"key-%zu", index]; [mmkv setObject:data forKey:key]; if (index % 1000 == 0) { NSLog(@"brutleTest size=%zu", mmkv.totalSize); } } } #pragma mark - multi-process - (void)testMultiProcess { NSData *key_1 = [@"multi_process" dataUsingEncoding:NSUTF8StringEncoding]; auto mmkv = [MMKV mmkvWithID:@"multi_process" cryptKey:key_1 mode:MMKVMultiProcess]; [mmkv setBool:YES forKey:@"bool"]; NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]); [mmkv setInt32:-1024 forKey:@"int32"]; NSLog(@"int32:%d", [mmkv getInt32ForKey:@"int32"]); [mmkv setUInt32:std::numeric_limits::max() forKey:@"uint32"]; NSLog(@"uint32:%u", [mmkv getUInt32ForKey:@"uint32"]); [mmkv setInt64:std::numeric_limits::min() forKey:@"int64"]; NSLog(@"int64:%lld", [mmkv getInt64ForKey:@"int64"]); [mmkv setUInt64:std::numeric_limits::max() forKey:@"uint64"]; NSLog(@"uint64:%llu", [mmkv getInt64ForKey:@"uint64"]); [mmkv setFloat:-3.1415926 forKey:@"float"]; NSLog(@"float:%f", [mmkv getFloatForKey:@"float"]); [mmkv setDouble:std::numeric_limits::max() forKey:@"double"]; NSLog(@"double:%f", [mmkv getDoubleForKey:@"double"]); [mmkv setString:@"hello, mmkv" forKey:@"string"]; NSLog(@"string:%@", [mmkv getStringForKey:@"string"]); [mmkv setObject:nil forKey:@"string"]; NSLog(@"string after set nil:%@, containsKey:%d", [mmkv getObjectOfClass:NSString.class forKey:@"string"], [mmkv containsKey:@"string"]); [mmkv setDate:[NSDate date] forKey:@"date"]; NSLog(@"date:%@", [mmkv getDateForKey:@"date"]); [mmkv setData:[@"hello, mmkv again and again" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"data"]; NSData *data = [mmkv getDataForKey:@"data"]; NSLog(@"data:%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); NSLog(@"data length:%zu, value size consumption:%zu", data.length, [mmkv getValueSizeForKey:@"data" actualSize:NO]); [mmkv removeValueForKey:@"bool"]; NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]); [mmkv removeValuesForKeys:@[ @"int32", @"uint64" ]]; NSLog(@"allKeys %@", [mmkv allKeys]); [mmkv close]; } - (void)updateTodayContent { static int count = 0; NSData *key_1 = [@"multi_process" dataUsingEncoding:NSUTF8StringEncoding]; auto mmkv = [MMKV mmkvWithID:@"multi_process" cryptKey:key_1 mode:MMKVMultiProcess]; NSString *content = [NSString stringWithFormat:@"count: %d", count++]; [mmkv setString:content forKey:@"content"]; } #pragma mark - backup & restore - (void)testBackup { auto parentPath = [[MMKV mmkvBasePath] stringByDeletingLastPathComponent]; auto dstPath = [parentPath stringByAppendingPathComponent:@"mmkv_backup"]; auto rootPath = [parentPath stringByAppendingPathComponent:@"mmkv_2"]; auto ret = [MMKV backupOneMMKV:@"test/case_aes" rootPath:rootPath toDirectory:dstPath]; NSLog(@"MMKV backup one file ret: %d", ret); if (ret) { NSData *key_1 = [@"Key_seq_1" dataUsingEncoding:NSUTF8StringEncoding]; auto backupedMMKV = [MMKV mmkvWithID:@"test/case_aes" cryptKey:key_1 rootPath:dstPath]; NSLog(@"check on backup file:%@", [backupedMMKV allKeys]); } auto count = [MMKV backupAll:nil toDirectory:dstPath]; NSLog(@"MMKV backup all count: %zu", count); if (count > 0) { NSData *key_1 = [@"Key_seq_1" dataUsingEncoding:NSUTF8StringEncoding]; auto backupedMMKV = [MMKV mmkvWithID:@"test/case_aes" cryptKey:key_1 rootPath:dstPath]; NSLog(@"check on backup file[%@] keys:%@", backupedMMKV.mmapID, [backupedMMKV allKeys]); backupedMMKV = [MMKV mmkvWithID:@"testAES_reKey" rootPath:dstPath]; NSLog(@"check on backup file[%@] keys:%@", backupedMMKV.mmapID, [backupedMMKV allKeys]); backupedMMKV = [MMKV mmkvWithID:@"testImportNSUserDefaults1" rootPath:dstPath]; NSLog(@"check on backup file[%@] keys:%@", backupedMMKV.mmapID, [backupedMMKV allKeys]); backupedMMKV = [MMKV mmkvWithID:@"testSwift" rootPath:dstPath]; NSLog(@"check on backup file[%@] keys:%@", backupedMMKV.mmapID, [backupedMMKV allKeys]); } } - (void)testRestore { auto ID = @"test/case_aes"; auto parentPath = [[MMKV mmkvBasePath] stringByDeletingLastPathComponent]; auto dstPath = [parentPath stringByAppendingPathComponent:@"mmkv_backup"]; auto rootPath = [parentPath stringByAppendingPathComponent:@"mmkv_2"]; auto ret = [MMKV backupOneMMKV:ID rootPath:rootPath toDirectory:dstPath]; NSLog(@"MMKV backup one file ret: %d", ret); if (ret) { NSData *key_1 = [@"Key_seq_1" dataUsingEncoding:NSUTF8StringEncoding]; auto originMMKV = [MMKV mmkvWithID:ID cryptKey:key_1 rootPath:rootPath]; [originMMKV setInt32:__LINE__ forKey:@"test_restore_key"]; NSLog(@"file[%@] before restore:%@", originMMKV.mmapID, [originMMKV allKeys]); ret = [MMKV restoreOneMMKV:ID rootPath:rootPath fromDirectory:dstPath]; NSLog(@"MMKV restore one file ret: %d", ret); if (ret) { NSLog(@"file[%@] after restore:%@", originMMKV.mmapID, [originMMKV allKeys]); } } auto count = [MMKV restoreAll:nil fromDirectory:dstPath]; NSLog(@"MMKV restore all count: %zu", count); if (count > 0) { NSData *key_1 = [@"Key_seq_1" dataUsingEncoding:NSUTF8StringEncoding]; auto restoredKV = [MMKV mmkvWithID:ID cryptKey:key_1]; NSLog(@"check on restore file[%@] keys:%@", restoredKV.mmapID, [restoredKV allKeys]); restoredKV = [MMKV mmkvWithID:@"testAES_reKey"]; NSLog(@"check on restore file[%@] keys:%@", restoredKV.mmapID, [restoredKV allKeys]); restoredKV = [MMKV mmkvWithID:@"testImportNSUserDefaults1"]; NSLog(@"check on restore file[%@] keys:%@", restoredKV.mmapID, [restoredKV allKeys]); restoredKV = [MMKV mmkvWithID:@"testSwift"]; NSLog(@"check on restore file[%@] keys:%@", restoredKV.mmapID, [restoredKV allKeys]); } } #pragma mark - expected capacity - (void)testExpectedCapacity { int len = 10000; NSString *value = [NSString stringWithFormat:@"🏊🏻®4️⃣🐅_"]; for (int i = 0; i < len; i++) { value = [value stringByAppendingString:@"0"]; } NSLog(@"value size = %ld", [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); NSString *key = [NSString stringWithFormat:@"key0"]; // if we know exactly the sizes of key and value, set expectedCapacity for performance improvement size_t expectedSize = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; auto mmkv0 = [MMKV mmkvWithID:@"expectedCapacityTest0" expectedCapacity:expectedSize]; // 0 times expand [mmkv0 setString:value forKey:key]; int count = 10; expectedSize *= count; auto mmkv1 = [MMKV mmkvWithID:@"expectedCapacityTest1" expectedCapacity:expectedSize]; for (int i = 0; i < count; i++) { // 0 times expand [mmkv1 setString:value forKey:[NSString stringWithFormat:@"key%d", i]]; } } - (void)overrideTest { { auto mmkv0 = [MMKV mmkvWithID:@"overrideTest"]; NSString *key = [NSString stringWithFormat:@"hello"]; NSString *key2 = [NSString stringWithFormat:@"hello2"]; NSString *value = [NSString stringWithFormat:@"world"]; [mmkv0 setString:value forKey:key]; auto v2 = [mmkv0 getStringForKey:key]; if (![v2 isEqualToString:value]) { NSLog(@"value = %@", v2); abort(); } [mmkv0 removeValueForKey:key]; [mmkv0 setString:value forKey:key2]; v2 = [mmkv0 getStringForKey:key2]; if (![v2 isEqualToString:value]) { NSLog(@"value = %@", v2); abort(); } [mmkv0 removeValueForKey:key2]; int len = 10000; NSMutableString *bigValue = [NSMutableString stringWithFormat:@"🏊🏻®4️⃣🐅_"]; for (int i = 0; i < len; i++) { [bigValue appendString:@"0"]; } [mmkv0 setString:bigValue forKey:key]; auto v3 = [mmkv0 getStringForKey:key]; // NSLog(@"value = %@", v3); if (![bigValue isEqualToString:v3]) { abort(); } // rewrite [mmkv0 setString:@"OK" forKey:key]; auto v4 = [mmkv0 getStringForKey:key]; if (![v4 isEqualToString:@"OK"]) { NSLog(@"value = %@", v2); abort(); } [mmkv0 setInt32:12345 forKey:key]; auto v5 = [mmkv0 getInt32ForKey:key]; if (v5 != 12345) { NSLog(@"value = %d", v5); abort(); } [mmkv0 removeValueForKey:key]; [mmkv0 clearAll]; } auto encryptionTestKV = [](NSString* key, NSString* value) { NSData *crypt = [@"fastestCrypt" dataUsingEncoding:NSUTF8StringEncoding]; auto mmkv0 = [MMKV mmkvWithID:@"overrideCryptTest" cryptKey:crypt]; [mmkv0 setString:value forKey:key]; auto v2 = [mmkv0 getStringForKey:key]; if (![value isEqualToString:v2]) { NSLog(@"value = %@, result = %@", value, v2); abort(); } [mmkv0 close]; mmkv0 = nil; mmkv0 = [MMKV mmkvWithID:@"overrideCryptTest" cryptKey:crypt mode:MMKVSingleProcess]; v2 = [mmkv0 getStringForKey:key]; if (![value isEqualToString:v2]) { NSLog(@"value = %@, result = %@", value, v2); abort(); } [mmkv0 setString:value forKey:key]; v2 = [mmkv0 getStringForKey:key]; if (![value isEqualToString:v2]) { NSLog(@"value = %@, result = %@", value, v2); abort(); } [mmkv0 removeValueForKey:key]; }; auto encryptionTest = [&](NSString* value) { NSString *key = [NSString stringWithFormat:@"hello"]; NSString *key2 = [NSString stringWithFormat:@"hello2"]; encryptionTestKV(key, value); encryptionTestKV(key2, value); }; // [MMKV removeStorage:@"overrideCryptTest" rootPath:nil]; // test small value encryptionTest(@"cryptworld"); // test medium value encryptionTest(@"An efficient, small mobile key-value storage framework developed by WeChat. Works on Android, iOS, macOS, Windows, and POSIX."); // test large value encryptionTest(@"An efficient, small mobile key-value storage framework developed by WeChat. Works on Android, iOS, macOS, Windows, and POSIX. MMKV is an efficient, small, easy-to-use mobile key-value storage framework used in the WeChat application. It's currently available on Android, iOS/macOS, Windows, POSIX and HarmonyOS NEXT."); } - (void)onlyOneKeyTest { { auto mmkv0 = [MMKV mmkvWithID:@"onlyOneKeyTest"]; NSString *key = [NSString stringWithFormat:@"hello"]; NSString *value = [NSString stringWithFormat:@"world"]; auto v = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v); [mmkv0 setString:value forKey:key]; auto v2 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v2); for (int i = 0; i < 10; i++) { NSString *value2 = [NSString stringWithFormat:@"world_%d", i]; [mmkv0 setString:value2 forKey:key]; auto v2 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v2); } int len = 10000; NSMutableString *bigValue = [NSMutableString stringWithFormat:@"🏊🏻®4️⃣🐅_"]; for (int i = 0; i < len; i++) { [bigValue appendString:@"0"]; } [mmkv0 setString:bigValue forKey:key]; auto v3 = [mmkv0 getStringForKey:key]; // NSLog(@"value = %@", v3); if (![bigValue isEqualToString:v3]) { abort(); } [mmkv0 setString:@"OK" forKey:key]; auto v4 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v4); [mmkv0 setInt32:12345 forKey:@"int"]; auto v5 = [mmkv0 getInt32ForKey:key]; NSLog(@"int value = %d", v5); [mmkv0 removeValueForKey:@"int"]; } { NSString *crypt = [NSString stringWithFormat:@"fastest"]; auto mmkv0 = [MMKV mmkvWithID:@"onlyOneKeyCryptTest" cryptKey:[crypt dataUsingEncoding:NSUTF8StringEncoding] mode:MMKVSingleProcess]; NSString *key = [NSString stringWithFormat:@"hello"]; NSString *value = [NSString stringWithFormat:@"cryptworld"]; auto v = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v); [mmkv0 setString:value forKey:key]; auto v2 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v2); [mmkv0 setString:@"hello, cryptworld" forKey:key]; auto v3 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v3); [mmkv0 close]; mmkv0 = nil; auto mmkv1 = [MMKV mmkvWithID:@"onlyOneKeyCryptTest" cryptKey:[crypt dataUsingEncoding:NSUTF8StringEncoding] mode:MMKVSingleProcess]; auto v4 = [mmkv1 getStringForKey:key]; if (![v3 isEqualToString:v4]) { NSLog(@"value = %@", v4); abort(); } for (int i = 0; i < 10; i++) { NSString *value2 = [NSString stringWithFormat:@"cryptworld_%d", i]; [mmkv1 setString:value2 forKey:key]; auto v2 = [mmkv1 getStringForKey:key]; if (![v2 isEqualToString:value2]) { NSLog(@"value = %@", v2); abort(); } } } } - (void)testClearAllWithKeepingSpace { { auto mmkv = [MMKV mmkvWithID:@"testClearAllWithKeepingSpace"]; [mmkv setFloat:123.456f forKey:@"key1"]; for (int i = 0; i < 10000; i++) { [mmkv setFloat:123.456f forKey:[NSString stringWithFormat:@"key_%d", i]]; } auto previousSize = [mmkv totalSize]; // assert(previousSize > [PAGE_SIZE]); [mmkv clearAllWithKeepingSpace]; assert([mmkv totalSize] == previousSize); NSLog(@"testClearAllWithKeepingSpace, size = %zu", previousSize); assert([mmkv count] == 0); [mmkv setFloat:123.4567f forKey:@"key2"]; assert([mmkv count] == 1); } { NSString *crypt = [NSString stringWithFormat:@"Crypt123"]; auto mmkv = [MMKV mmkvWithID:@"testClearAllWithKeepingSpaceCrypt" cryptKey:[crypt dataUsingEncoding:NSUTF8StringEncoding] mode:MMKVSingleProcess]; [mmkv setFloat:123.456f forKey:@"key1"]; for (int i = 0; i < 10000; i++) { [mmkv setFloat:123.456f forKey:[NSString stringWithFormat:@"key_%d", i]]; } auto previousSize = [mmkv totalSize]; // assert(previousSize > PAGE_SIZE); [mmkv clearAllWithKeepingSpace]; assert([mmkv totalSize] == previousSize); assert([mmkv count] == 0); [mmkv setFloat:123.4567f forKey:@"key2"]; [mmkv setFloat:223.47f forKey:@"key3"]; assert([mmkv count] == 2); } } - (void)testCompareBeforeSet { auto mmkv = [MMKV mmkvWithID:@"testCompareBeforeSet"]; [mmkv enableCompareBeforeSet]; [mmkv setBool:true forKey:@"extra"]; { NSString *key = @"int64"; int64_t v = 123456L; [mmkv setInt64:v forKey:key]; long actualSize = [mmkv actualSize]; NSLog(@"testCompareBeforeSet actualSize = %ld", actualSize); NSLog(@"testCompareBeforeSet v = %lld", [mmkv getInt64ForKey:key]); [mmkv setInt64:v forKey:key]; long actualSize2 = [mmkv actualSize]; NSLog(@"testCompareBeforeSet actualSize = %ld", actualSize2); if (actualSize != actualSize2) { abort(); } [mmkv setInt64:v << 1 forKey:key]; NSLog(@"testCompareBeforeSet actualSize = %ld", [mmkv actualSize]); NSLog(@"testCompareBeforeSet v = %lld", [mmkv getInt64ForKey:key]); } { NSString *key = @"string"; NSString *v = [NSString stringWithFormat:@"w012A🏊🏻good"]; [mmkv setString:v forKey:key]; long actualSize = [mmkv actualSize]; NSLog(@"testCompareBeforeSet actualSize = %ld", actualSize); NSLog(@"testCompareBeforeSet v = %@", [mmkv getStringForKey:key]); [mmkv setString:v forKey:key]; long actualSize2 = [mmkv actualSize]; NSLog(@"testCompareBeforeSet actualSize = %ld", actualSize2); if (actualSize != actualSize2) { abort(); } [mmkv setString:@"another string" forKey:key]; NSLog(@"testCompareBeforeSet actualSize = %ld", [mmkv actualSize]); NSLog(@"testCompareBeforeSet v = %@", [mmkv getStringForKey:key]); } } - (void)testRemoveStorage { auto mmapID = @"test_remove"; { auto mmkv = [MMKV mmkvWithID:mmapID mode:MMKVMultiProcess]; [mmkv setBool:YES forKey:@"bool"]; } [MMKV removeStorage:mmapID mode:MMKVMultiProcess]; { auto mmkv = [MMKV mmkvWithID:mmapID mode:MMKVMultiProcess]; if (mmkv.count != 0) { abort(); } } NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSString *libraryPath = (NSString *) [paths firstObject]; NSString *rootDir = [libraryPath stringByAppendingPathComponent:@"mmkv_1"]; mmapID = @"test_remove/sg"; // { auto mmkv = [MMKV mmkvWithID:mmapID rootPath:rootDir]; [mmkv setBool:YES forKey:@"bool"]; // } [MMKV removeStorage:mmapID rootPath:rootDir]; // { mmkv = [MMKV mmkvWithID:mmapID rootPath:rootDir]; if (mmkv.count != 0) { abort(); } // } } - (void)testReadOnly:(BOOL) isForPrepare { auto name = @"testReadOnly"; NSData *key_1 = [@"Key_ReadOnly" dataUsingEncoding:NSUTF8StringEncoding]; if (isForPrepare) { [self testMMKV:name withCryptKey:key_1 aes256:NO decodeOnly:NO]; } else { auto mmkvPath = [[NSBundle mainBundle] pathForResource:name ofType:nil]; auto mmkvDir = [mmkvPath stringByDeletingLastPathComponent]; auto mmkv = [MMKV mmkvWithID:name cryptKey:key_1 rootPath:mmkvDir mode:MMKVReadOnly expectedCapacity:0]; [ViewController testMMKV:mmkv decodeOnly:YES]; // also check if it tolerate update operations without crash [ViewController testMMKV:mmkv decodeOnly:NO]; } } - (void)testImport { auto mmapID = @"test_import_src"; auto src = [MMKV mmkvWithID:mmapID]; [src setBool:YES forKey:@"bool"]; [src setInt32:std::numeric_limits::min() forKey:@"int32"]; [src setUInt64:std::numeric_limits::max() forKey:@"uint64"]; [src setString:mmapID forKey:@"string"]; auto dst = [MMKV mmkvWithID:@"test_import_dst"]; [dst clearAll]; [dst enableAutoKeyExpire:1]; [dst setBool:NO forKey:@"bool"]; [dst setInt32:-1 forKey:@"int32"]; [dst setUInt64:1 forKey:@"uint64"]; [dst setString:@"mmkv" forKey:@"string"]; auto count = [dst importFrom:src]; assert(count == 4 && dst.count == 4); assert([dst getBoolForKey:@"bool"] == YES); assert([dst getInt32ForKey:@"int32"] == std::numeric_limits::min()); assert([dst getUInt64ForKey:@"uint64"] == std::numeric_limits::max()); assert([[dst getStringForKey:@"string"] isEqualToString:mmapID]); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ assert([dst countNonExpiredKeys] == 0); }); } @end ================================================ FILE: iOS/MMKVDemo/MMKVDemo/main.m ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ #import "AppDelegate.h" #import int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: iOS/MMKVDemo/MMKVDemo.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 702CE6842B7E85070015E8A4 /* MMKV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB36F25420F8A20C009AF46F /* MMKV.framework */; }; 702CE6852B7E85070015E8A4 /* MMKV.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CB36F25420F8A20C009AF46F /* MMKV.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 70A630972B7E7EC9003AB1DF /* MMKVVisionDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70A630962B7E7EC9003AB1DF /* MMKVVisionDemoApp.swift */; }; 70A630992B7E7EC9003AB1DF /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70A630982B7E7EC9003AB1DF /* ContentView.swift */; }; 70A6309B2B7E7ECA003AB1DF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 70A6309A2B7E7ECA003AB1DF /* Assets.xcassets */; }; 70A6309E2B7E7ECA003AB1DF /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 70A6309D2B7E7ECA003AB1DF /* Preview Assets.xcassets */; }; CB0DCC72242B57FC009AFE59 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CBD5359823B4718200D3A404 /* libc++.tbd */; }; CB1E0659242B8E3F00F2FD42 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CB1FD4D12046AF2300931B5F /* libz.tbd */; }; CB1E065F242B8E4A00F2FD42 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CB1E065E242B8E4A00F2FD42 /* libz.tbd */; }; CB1FD42520455E2D00931B5F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CB1FD42420455E2D00931B5F /* AppDelegate.m */; }; CB1FD42820455E2D00931B5F /* ViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB1FD42720455E2D00931B5F /* ViewController.mm */; }; CB1FD42B20455E2D00931B5F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CB1FD42920455E2D00931B5F /* Main.storyboard */; }; CB1FD42D20455E2D00931B5F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CB1FD42C20455E2D00931B5F /* Assets.xcassets */; }; CB1FD43020455E2D00931B5F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CB1FD42E20455E2D00931B5F /* LaunchScreen.storyboard */; }; CB1FD43320455E2D00931B5F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CB1FD43220455E2D00931B5F /* main.m */; }; CB2C907E2BE9DB890052A2D7 /* MMKV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB36F25420F8A20C009AF46F /* MMKV.framework */; }; CB2C907F2BE9DB920052A2D7 /* MMKV.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB36F25420F8A20C009AF46F /* MMKV.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CB3166FE2A4C6B5F0003221B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CB3166FD2A4C6B5F0003221B /* AppDelegate.m */; }; CB3167012A4C6B5F0003221B /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CB3167002A4C6B5F0003221B /* SceneDelegate.m */; }; CB3167042A4C6B5F0003221B /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CB3167032A4C6B5F0003221B /* ViewController.m */; }; CB3167072A4C6B5F0003221B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CB3167052A4C6B5F0003221B /* Main.storyboard */; }; CB3167092A4C6B600003221B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CB3167082A4C6B600003221B /* Assets.xcassets */; }; CB31670C2A4C6B600003221B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CB31670A2A4C6B600003221B /* LaunchScreen.storyboard */; }; CB31670F2A4C6B600003221B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CB31670E2A4C6B600003221B /* main.m */; }; CB3167142A4C6B7D0003221B /* MMKV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB36F25420F8A20C009AF46F /* MMKV.framework */; }; CB3167172A4C6E450003221B /* MMKV.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB36F25420F8A20C009AF46F /* MMKV.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CB36F24C20F8A20C009AF46F /* MMKVDemoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB36F24B20F8A20C009AF46F /* MMKVDemoTests.mm */; }; CB36F25A20F8D728009AF46F /* MMKVPerformanceTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB36F25920F8D728009AF46F /* MMKVPerformanceTest.mm */; }; CB467FBC2431EE3000FD7421 /* MMKVAppExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB467FBB2431EE2D00FD7421 /* MMKVAppExtension.framework */; }; CB5C469724AB87F3001B25B8 /* MMKVWatchExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBF1907C243D7116001C82ED /* MMKVWatchExtension.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CB6180EA26E904AC003FF912 /* MMKVAppExtension.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB467FBB2431EE2D00FD7421 /* MMKVAppExtension.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CB6D44CC23B3A44400F369AA /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB6D44CB23B3A44400F369AA /* NotificationCenter.framework */; }; CB6D44D023B3A44400F369AA /* TodayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CB6D44CF23B3A44400F369AA /* TodayViewController.m */; }; CB6D44D323B3A44400F369AA /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CB6D44D123B3A44400F369AA /* MainInterface.storyboard */; }; CB6D44D723B3A44400F369AA /* MMKVTodayExtensionDemo.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = CB6D44CA23B3A44400F369AA /* MMKVTodayExtensionDemo.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; CB76C5A82C3D69A0008B0E61 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CB76C5A72C3D69A0008B0E61 /* AppDelegate.m */; }; CB76C5AB2C3D69A0008B0E61 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CB76C5AA2C3D69A0008B0E61 /* SceneDelegate.m */; }; CB76C5AE2C3D69A0008B0E61 /* ViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB76C5AD2C3D69A0008B0E61 /* ViewController.mm */; }; CB76C5B12C3D69A0008B0E61 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = CB76C5B02C3D69A0008B0E61 /* Base */; }; CB76C5B32C3D69A1008B0E61 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CB76C5B22C3D69A1008B0E61 /* Assets.xcassets */; }; CB76C5B62C3D69A1008B0E61 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = CB76C5B52C3D69A1008B0E61 /* Base */; }; CB76C5B92C3D69A1008B0E61 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CB76C5B82C3D69A1008B0E61 /* main.m */; }; CB76C5BD2C3D6A05008B0E61 /* MMKV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB36F25420F8A20C009AF46F /* MMKV.framework */; }; CB76C5BF2C3D6A11008B0E61 /* MMKV.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB36F25420F8A20C009AF46F /* MMKV.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CB9C2731215E5DF000448B6C /* libMMKV.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CB1FD4B52046994D00931B5F /* libMMKV.a */; }; CB9F963920FCA7C500A1C14C /* DemoSwiftUsage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9F963820FCA7C500A1C14C /* DemoSwiftUsage.swift */; }; CBB306612435E8F200CB593E /* ViewController+TestCaseBad.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBB306602435E8F200CB593E /* ViewController+TestCaseBad.mm */; }; CBB6C823215CDB9500487192 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB6C822215CDB9500487192 /* AppDelegate.m */; }; CBB6C826215CDB9500487192 /* ViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBB6C825215CDB9500487192 /* ViewController.mm */; }; CBB6C828215CDB9600487192 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBB6C827215CDB9600487192 /* Assets.xcassets */; }; CBB6C82B215CDB9600487192 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBB6C829215CDB9600487192 /* Main.storyboard */; }; CBB6C82E215CDB9600487192 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CBB6C82D215CDB9600487192 /* main.m */; }; CBDC22E12C8ABDD20000DDA5 /* testReadOnly in Resources */ = {isa = PBXBuildFile; fileRef = CBDC22DF2C8ABDD20000DDA5 /* testReadOnly */; }; CBDC22E22C8ABDD20000DDA5 /* testReadOnly.crc in Resources */ = {isa = PBXBuildFile; fileRef = CBDC22E02C8ABDD20000DDA5 /* testReadOnly.crc */; }; CBE6689F2D5C52E5005C184E /* TestMMKVCpp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBE6689E2D5C52E5005C184E /* TestMMKVCpp.cpp */; }; CBF19016243D6F59001C82ED /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBF19014243D6F59001C82ED /* Interface.storyboard */; }; CBF19018243D6F5A001C82ED /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBF19017243D6F5A001C82ED /* Assets.xcassets */; }; CBF1901F243D6F5A001C82ED /* WatchApp Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = CBF1901E243D6F5A001C82ED /* WatchApp Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; CBF19025243D6F5A001C82ED /* InterfaceController.m in Sources */ = {isa = PBXBuildFile; fileRef = CBF19024243D6F5A001C82ED /* InterfaceController.m */; }; CBF19028243D6F5A001C82ED /* ExtensionDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBF19027243D6F5A001C82ED /* ExtensionDelegate.mm */; }; CBF1902B243D6F5A001C82ED /* NotificationController.m in Sources */ = {isa = PBXBuildFile; fileRef = CBF1902A243D6F5A001C82ED /* NotificationController.m */; }; CBF1902D243D6F5A001C82ED /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBF1902C243D6F5A001C82ED /* Assets.xcassets */; }; CBF19032243D6F5A001C82ED /* WatchApp.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = CBF19012243D6F59001C82ED /* WatchApp.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; CBF1907E243D71E8001C82ED /* MMKVWatchExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBF1907C243D7116001C82ED /* MMKVWatchExtension.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ CB1FD4B42046994D00931B5F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD4B02046994C00931B5F /* MMKV.xcodeproj */; proxyType = 2; remoteGlobalIDString = CB1FD4912046984F00931B5F; remoteInfo = MMKV; }; CB2C907C2BE9DB7D0052A2D7 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD4B02046994C00931B5F /* MMKV.xcodeproj */; proxyType = 1; remoteGlobalIDString = CBC1A8F520DA946200AD5087; remoteInfo = MMKV; }; CB36F24E20F8A20C009AF46F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD41820455E2D00931B5F /* Project object */; proxyType = 1; remoteGlobalIDString = CB1FD41F20455E2D00931B5F; remoteInfo = MMKVDemo; }; CB36F25320F8A20C009AF46F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD4B02046994C00931B5F /* MMKV.xcodeproj */; proxyType = 2; remoteGlobalIDString = CBC1A8F620DA946200AD5087; remoteInfo = MMKV; }; CB467FBA2431EE2D00FD7421 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD4B02046994C00931B5F /* MMKV.xcodeproj */; proxyType = 2; remoteGlobalIDString = CB1E0651242B84D800F2FD42; remoteInfo = MMKVAppExtension; }; CB5C469824AB8837001B25B8 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD4B02046994C00931B5F /* MMKV.xcodeproj */; proxyType = 1; remoteGlobalIDString = CBF1903C243D706D001C82ED; remoteInfo = MMKVWatchExtension; }; CB6D44D523B3A44400F369AA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD41820455E2D00931B5F /* Project object */; proxyType = 1; remoteGlobalIDString = CB6D44C923B3A44400F369AA; remoteInfo = MMKVTodayExtensionDemo; }; CB9C272F215E5D2400448B6C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD4B02046994C00931B5F /* MMKV.xcodeproj */; proxyType = 1; remoteGlobalIDString = CB1FD4902046984F00931B5F; remoteInfo = "MMKV Static"; }; CBB306642435EC6000CB593E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD4B02046994C00931B5F /* MMKV.xcodeproj */; proxyType = 1; remoteGlobalIDString = CB1E0641242B84D800F2FD42; remoteInfo = MMKVAppExtension; }; CBF19020243D6F5A001C82ED /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD41820455E2D00931B5F /* Project object */; proxyType = 1; remoteGlobalIDString = CBF1901D243D6F5A001C82ED; remoteInfo = "WatchApp Extension"; }; CBF19030243D6F5A001C82ED /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD41820455E2D00931B5F /* Project object */; proxyType = 1; remoteGlobalIDString = CBF19011243D6F59001C82ED; remoteInfo = WatchApp; }; CBF1907B243D7116001C82ED /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CB1FD4B02046994C00931B5F /* MMKV.xcodeproj */; proxyType = 2; remoteGlobalIDString = CBF1904C243D706D001C82ED; remoteInfo = MMKVWatchExtension; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 702CE6862B7E85070015E8A4 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 702CE6852B7E85070015E8A4 /* MMKV.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; CB3167162A4C6E380003221B /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( CB3167172A4C6E450003221B /* MMKV.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; CB6180E926E90493003FF912 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( CB2C907F2BE9DB920052A2D7 /* MMKV.framework in CopyFiles */, CB6180EA26E904AC003FF912 /* MMKVAppExtension.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; CB6D44DB23B3A44400F369AA /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( CB6D44D723B3A44400F369AA /* MMKVTodayExtensionDemo.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; CB76C5BE2C3D6A0A008B0E61 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( CB76C5BF2C3D6A11008B0E61 /* MMKV.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; CBF19033243D6F5A001C82ED /* Embed Watch Content */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; dstSubfolderSpec = 16; files = ( CBF19032243D6F5A001C82ED /* WatchApp.app in Embed Watch Content */, ); name = "Embed Watch Content"; runOnlyForDeploymentPostprocessing = 0; }; CBF19036243D6F5A001C82ED /* Embed Foundation Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( CBF1901F243D6F5A001C82ED /* WatchApp Extension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; CBF190AE243D78D4001C82ED /* Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( CB5C469724AB87F3001B25B8 /* MMKVWatchExtension.framework in Frameworks */, ); name = Frameworks; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 702CE6802B7E84360015E8A4 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/XROS.platform/Developer/SDKs/XROS1.0.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; }; 70A630902B7E7EC9003AB1DF /* MMKVVisionDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MMKVVisionDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 70A630962B7E7EC9003AB1DF /* MMKVVisionDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MMKVVisionDemoApp.swift; sourceTree = ""; }; 70A630982B7E7EC9003AB1DF /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 70A6309A2B7E7ECA003AB1DF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 70A6309D2B7E7ECA003AB1DF /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 70A6309F2B7E7ECA003AB1DF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CB1E065E242B8E4A00F2FD42 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; }; CB1FD42020455E2D00931B5F /* MMKVDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MMKVDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; CB1FD42320455E2D00931B5F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; CB1FD42420455E2D00931B5F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; CB1FD42620455E2D00931B5F /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; CB1FD42720455E2D00931B5F /* ViewController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ViewController.mm; sourceTree = ""; }; CB1FD42A20455E2D00931B5F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; CB1FD42C20455E2D00931B5F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CB1FD42F20455E2D00931B5F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CB1FD43120455E2D00931B5F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CB1FD43220455E2D00931B5F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; CB1FD4B02046994C00931B5F /* MMKV.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MMKV.xcodeproj; path = ../MMKV/MMKV.xcodeproj; sourceTree = ""; }; CB1FD4D12046AF2300931B5F /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; CB3166FA2A4C6B5F0003221B /* kvdemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = kvdemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; CB3166FC2A4C6B5F0003221B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; CB3166FD2A4C6B5F0003221B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; CB3166FF2A4C6B5F0003221B /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; CB3167002A4C6B5F0003221B /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; CB3167022A4C6B5F0003221B /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; CB3167032A4C6B5F0003221B /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; CB3167062A4C6B5F0003221B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; CB3167082A4C6B600003221B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CB31670B2A4C6B600003221B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CB31670D2A4C6B600003221B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CB31670E2A4C6B600003221B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; CB3167152A4C6BA90003221B /* kvdemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = kvdemo.entitlements; sourceTree = ""; }; CB36F24920F8A20C009AF46F /* MMKVDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MMKVDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CB36F24B20F8A20C009AF46F /* MMKVDemoTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MMKVDemoTests.mm; sourceTree = ""; }; CB36F24D20F8A20C009AF46F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CB36F25920F8D728009AF46F /* MMKVPerformanceTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MMKVPerformanceTest.mm; sourceTree = ""; }; CB6D44C423B3924E00F369AA /* MMKVDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MMKVDemo.entitlements; sourceTree = ""; }; CB6D44CA23B3A44400F369AA /* MMKVTodayExtensionDemo.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MMKVTodayExtensionDemo.appex; sourceTree = BUILT_PRODUCTS_DIR; }; CB6D44CB23B3A44400F369AA /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; CB6D44CE23B3A44400F369AA /* TodayViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TodayViewController.h; sourceTree = ""; }; CB6D44CF23B3A44400F369AA /* TodayViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TodayViewController.m; sourceTree = ""; }; CB6D44D223B3A44400F369AA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; CB6D44D423B3A44400F369AA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CB6D44DC23B3A45A00F369AA /* MMKVTodayExtensionDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MMKVTodayExtensionDemo.entitlements; sourceTree = ""; }; CB76C5A42C3D69A0008B0E61 /* MMKVCatalystDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MMKVCatalystDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; CB76C5A62C3D69A0008B0E61 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; CB76C5A72C3D69A0008B0E61 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; CB76C5A92C3D69A0008B0E61 /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; CB76C5AA2C3D69A0008B0E61 /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; CB76C5AC2C3D69A0008B0E61 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; CB76C5AD2C3D69A0008B0E61 /* ViewController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ViewController.mm; sourceTree = ""; }; CB76C5B02C3D69A0008B0E61 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; CB76C5B22C3D69A1008B0E61 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CB76C5B52C3D69A1008B0E61 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CB76C5B72C3D69A1008B0E61 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CB76C5B82C3D69A1008B0E61 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; CB9C2732215E5ECC00448B6C /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; }; CB9F963720FCA7C400A1C14C /* MMKVDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MMKVDemo-Bridging-Header.h"; sourceTree = ""; }; CB9F963820FCA7C500A1C14C /* DemoSwiftUsage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoSwiftUsage.swift; sourceTree = ""; }; CBB3065F2435E8F200CB593E /* ViewController+TestCaseBad.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ViewController+TestCaseBad.h"; sourceTree = ""; }; CBB306602435E8F200CB593E /* ViewController+TestCaseBad.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "ViewController+TestCaseBad.mm"; sourceTree = ""; }; CBB6C81F215CDB9500487192 /* MMKVMacDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MMKVMacDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; CBB6C821215CDB9500487192 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; CBB6C822215CDB9500487192 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; CBB6C824215CDB9500487192 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; CBB6C825215CDB9500487192 /* ViewController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ViewController.mm; sourceTree = ""; }; CBB6C827215CDB9600487192 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CBB6C82A215CDB9600487192 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; CBB6C82C215CDB9600487192 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CBB6C82D215CDB9600487192 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; CBB6C82F215CDB9600487192 /* MMKVMacDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MMKVMacDemo.entitlements; sourceTree = ""; }; CBD5359823B4718200D3A404 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; CBDC22DF2C8ABDD20000DDA5 /* testReadOnly */ = {isa = PBXFileReference; lastKnownFileType = file; path = testReadOnly; sourceTree = ""; }; CBDC22E02C8ABDD20000DDA5 /* testReadOnly.crc */ = {isa = PBXFileReference; lastKnownFileType = file; path = testReadOnly.crc; sourceTree = ""; }; CBE6689D2D5C52E5005C184E /* TestMMKVCpp.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TestMMKVCpp.hpp; sourceTree = ""; }; CBE6689E2D5C52E5005C184E /* TestMMKVCpp.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TestMMKVCpp.cpp; sourceTree = ""; }; CBF19012243D6F59001C82ED /* WatchApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WatchApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; CBF19015243D6F59001C82ED /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = ""; }; CBF19017243D6F5A001C82ED /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CBF19019243D6F5A001C82ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CBF1901E243D6F5A001C82ED /* WatchApp Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "WatchApp Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; CBF19023243D6F5A001C82ED /* InterfaceController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InterfaceController.h; sourceTree = ""; }; CBF19024243D6F5A001C82ED /* InterfaceController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InterfaceController.m; sourceTree = ""; }; CBF19026243D6F5A001C82ED /* ExtensionDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExtensionDelegate.h; sourceTree = ""; }; CBF19027243D6F5A001C82ED /* ExtensionDelegate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ExtensionDelegate.mm; sourceTree = ""; }; CBF19029243D6F5A001C82ED /* NotificationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationController.h; sourceTree = ""; }; CBF1902A243D6F5A001C82ED /* NotificationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationController.m; sourceTree = ""; }; CBF1902C243D6F5A001C82ED /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CBF1902E243D6F5A001C82ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CBF1902F243D6F5A001C82ED /* PushNotificationPayload.apns */ = {isa = PBXFileReference; lastKnownFileType = text; path = PushNotificationPayload.apns; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 70A6308D2B7E7EC9003AB1DF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 702CE6842B7E85070015E8A4 /* MMKV.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; CB1FD41D20455E2D00931B5F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CB2C907E2BE9DB890052A2D7 /* MMKV.framework in Frameworks */, CB1E0659242B8E3F00F2FD42 /* libz.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; CB3166F72A4C6B5F0003221B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CB3167142A4C6B7D0003221B /* MMKV.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; CB36F24620F8A20C009AF46F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; CB6D44C723B3A44400F369AA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CB467FBC2431EE3000FD7421 /* MMKVAppExtension.framework in Frameworks */, CB0DCC72242B57FC009AFE59 /* libc++.tbd in Frameworks */, CB6D44CC23B3A44400F369AA /* NotificationCenter.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; CB76C5A12C3D699F008B0E61 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CB76C5BD2C3D6A05008B0E61 /* MMKV.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; CBB6C81C215CDB9500487192 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CB1E065F242B8E4A00F2FD42 /* libz.tbd in Frameworks */, CB9C2731215E5DF000448B6C /* libMMKV.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; CBF1901B243D6F5A001C82ED /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CBF1907E243D71E8001C82ED /* MMKVWatchExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 70A630912B7E7EC9003AB1DF /* MMKVVisionDemo */ = { isa = PBXGroup; children = ( 70A630962B7E7EC9003AB1DF /* MMKVVisionDemoApp.swift */, 70A630982B7E7EC9003AB1DF /* ContentView.swift */, 70A6309A2B7E7ECA003AB1DF /* Assets.xcassets */, 70A6309F2B7E7ECA003AB1DF /* Info.plist */, 70A6309C2B7E7ECA003AB1DF /* Preview Content */, ); path = MMKVVisionDemo; sourceTree = ""; }; 70A6309C2B7E7ECA003AB1DF /* Preview Content */ = { isa = PBXGroup; children = ( 70A6309D2B7E7ECA003AB1DF /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; CB1FD41720455E2D00931B5F = { isa = PBXGroup; children = ( CB1FD4B02046994C00931B5F /* MMKV.xcodeproj */, CB1FD42220455E2D00931B5F /* MMKVDemo */, CB36F24A20F8A20C009AF46F /* MMKVDemoTests */, CBB6C820215CDB9500487192 /* MMKVMacDemo */, CB6D44CD23B3A44400F369AA /* MMKVTodayExtensionDemo */, CBF19013243D6F59001C82ED /* WatchApp */, CBF19022243D6F5A001C82ED /* WatchApp Extension */, CB3166FB2A4C6B5F0003221B /* kvdemo */, 70A630912B7E7EC9003AB1DF /* MMKVVisionDemo */, CB76C5A52C3D69A0008B0E61 /* MMKVCatalystDemo */, CB1FD42120455E2D00931B5F /* Products */, CB1FD45720455F3500931B5F /* Frameworks */, ); indentWidth = 4; sourceTree = ""; }; CB1FD42120455E2D00931B5F /* Products */ = { isa = PBXGroup; children = ( CB1FD42020455E2D00931B5F /* MMKVDemo.app */, CB36F24920F8A20C009AF46F /* MMKVDemoTests.xctest */, CBB6C81F215CDB9500487192 /* MMKVMacDemo.app */, CB6D44CA23B3A44400F369AA /* MMKVTodayExtensionDemo.appex */, CBF19012243D6F59001C82ED /* WatchApp.app */, CBF1901E243D6F5A001C82ED /* WatchApp Extension.appex */, CB3166FA2A4C6B5F0003221B /* kvdemo.app */, 70A630902B7E7EC9003AB1DF /* MMKVVisionDemo.app */, CB76C5A42C3D69A0008B0E61 /* MMKVCatalystDemo.app */, ); name = Products; sourceTree = ""; }; CB1FD42220455E2D00931B5F /* MMKVDemo */ = { isa = PBXGroup; children = ( CB1FD42320455E2D00931B5F /* AppDelegate.h */, CB1FD42420455E2D00931B5F /* AppDelegate.m */, CBDC22DE2C8ABD880000DDA5 /* Resources */, CB9F963820FCA7C500A1C14C /* DemoSwiftUsage.swift */, CB1FD43220455E2D00931B5F /* main.m */, CB9F963720FCA7C400A1C14C /* MMKVDemo-Bridging-Header.h */, CB1FD42620455E2D00931B5F /* ViewController.h */, CB1FD42720455E2D00931B5F /* ViewController.mm */, CBB3065F2435E8F200CB593E /* ViewController+TestCaseBad.h */, CBB306602435E8F200CB593E /* ViewController+TestCaseBad.mm */, CBE6689D2D5C52E5005C184E /* TestMMKVCpp.hpp */, CBE6689E2D5C52E5005C184E /* TestMMKVCpp.cpp */, ); path = MMKVDemo; sourceTree = ""; }; CB1FD45720455F3500931B5F /* Frameworks */ = { isa = PBXGroup; children = ( CB1E065E242B8E4A00F2FD42 /* libz.tbd */, 702CE6802B7E84360015E8A4 /* libz.tbd */, CBD5359823B4718200D3A404 /* libc++.tbd */, CB9C2732215E5ECC00448B6C /* libz.tbd */, CB1FD4D12046AF2300931B5F /* libz.tbd */, CB6D44CB23B3A44400F369AA /* NotificationCenter.framework */, ); name = Frameworks; sourceTree = ""; }; CB1FD4B12046994C00931B5F /* Products */ = { isa = PBXGroup; children = ( CB1FD4B52046994D00931B5F /* libMMKV.a */, CB36F25420F8A20C009AF46F /* MMKV.framework */, CB467FBB2431EE2D00FD7421 /* MMKVAppExtension.framework */, CBF1907C243D7116001C82ED /* MMKVWatchExtension.framework */, ); name = Products; sourceTree = ""; }; CB3166FB2A4C6B5F0003221B /* kvdemo */ = { isa = PBXGroup; children = ( CB3167152A4C6BA90003221B /* kvdemo.entitlements */, CB3166FC2A4C6B5F0003221B /* AppDelegate.h */, CB3166FD2A4C6B5F0003221B /* AppDelegate.m */, CB3166FF2A4C6B5F0003221B /* SceneDelegate.h */, CB3167002A4C6B5F0003221B /* SceneDelegate.m */, CB3167022A4C6B5F0003221B /* ViewController.h */, CB3167032A4C6B5F0003221B /* ViewController.m */, CB3167052A4C6B5F0003221B /* Main.storyboard */, CB3167082A4C6B600003221B /* Assets.xcassets */, CB31670A2A4C6B600003221B /* LaunchScreen.storyboard */, CB31670D2A4C6B600003221B /* Info.plist */, CB31670E2A4C6B600003221B /* main.m */, ); path = kvdemo; sourceTree = ""; }; CB36F24A20F8A20C009AF46F /* MMKVDemoTests */ = { isa = PBXGroup; children = ( CB36F24B20F8A20C009AF46F /* MMKVDemoTests.mm */, CB36F25920F8D728009AF46F /* MMKVPerformanceTest.mm */, CB36F24D20F8A20C009AF46F /* Info.plist */, ); path = MMKVDemoTests; sourceTree = ""; }; CB6D44CD23B3A44400F369AA /* MMKVTodayExtensionDemo */ = { isa = PBXGroup; children = ( CB6D44DC23B3A45A00F369AA /* MMKVTodayExtensionDemo.entitlements */, CB6D44CE23B3A44400F369AA /* TodayViewController.h */, CB6D44CF23B3A44400F369AA /* TodayViewController.m */, CB6D44D123B3A44400F369AA /* MainInterface.storyboard */, CB6D44D423B3A44400F369AA /* Info.plist */, ); path = MMKVTodayExtensionDemo; sourceTree = ""; }; CB76C5A52C3D69A0008B0E61 /* MMKVCatalystDemo */ = { isa = PBXGroup; children = ( CB76C5A62C3D69A0008B0E61 /* AppDelegate.h */, CB76C5A72C3D69A0008B0E61 /* AppDelegate.m */, CB76C5A92C3D69A0008B0E61 /* SceneDelegate.h */, CB76C5AA2C3D69A0008B0E61 /* SceneDelegate.m */, CB76C5AC2C3D69A0008B0E61 /* ViewController.h */, CB76C5AD2C3D69A0008B0E61 /* ViewController.mm */, CB76C5AF2C3D69A0008B0E61 /* Main.storyboard */, CB76C5B22C3D69A1008B0E61 /* Assets.xcassets */, CB76C5B42C3D69A1008B0E61 /* LaunchScreen.storyboard */, CB76C5B72C3D69A1008B0E61 /* Info.plist */, CB76C5B82C3D69A1008B0E61 /* main.m */, ); path = MMKVCatalystDemo; sourceTree = ""; }; CBB6C820215CDB9500487192 /* MMKVMacDemo */ = { isa = PBXGroup; children = ( CBB6C821215CDB9500487192 /* AppDelegate.h */, CBB6C822215CDB9500487192 /* AppDelegate.m */, CBB6C824215CDB9500487192 /* ViewController.h */, CBB6C825215CDB9500487192 /* ViewController.mm */, CBB6C827215CDB9600487192 /* Assets.xcassets */, CBB6C829215CDB9600487192 /* Main.storyboard */, CBB6C82C215CDB9600487192 /* Info.plist */, CBB6C82D215CDB9600487192 /* main.m */, CBB6C82F215CDB9600487192 /* MMKVMacDemo.entitlements */, ); path = MMKVMacDemo; sourceTree = ""; }; CBDC22DE2C8ABD880000DDA5 /* Resources */ = { isa = PBXGroup; children = ( CBDC22DF2C8ABDD20000DDA5 /* testReadOnly */, CBDC22E02C8ABDD20000DDA5 /* testReadOnly.crc */, CB6D44C423B3924E00F369AA /* MMKVDemo.entitlements */, CB1FD42C20455E2D00931B5F /* Assets.xcassets */, CB1FD43120455E2D00931B5F /* Info.plist */, CB1FD42E20455E2D00931B5F /* LaunchScreen.storyboard */, CB1FD42920455E2D00931B5F /* Main.storyboard */, ); path = Resources; sourceTree = ""; }; CBF19013243D6F59001C82ED /* WatchApp */ = { isa = PBXGroup; children = ( CBF19014243D6F59001C82ED /* Interface.storyboard */, CBF19017243D6F5A001C82ED /* Assets.xcassets */, CBF19019243D6F5A001C82ED /* Info.plist */, ); path = WatchApp; sourceTree = ""; }; CBF19022243D6F5A001C82ED /* WatchApp Extension */ = { isa = PBXGroup; children = ( CBF19023243D6F5A001C82ED /* InterfaceController.h */, CBF19024243D6F5A001C82ED /* InterfaceController.m */, CBF19026243D6F5A001C82ED /* ExtensionDelegate.h */, CBF19027243D6F5A001C82ED /* ExtensionDelegate.mm */, CBF19029243D6F5A001C82ED /* NotificationController.h */, CBF1902A243D6F5A001C82ED /* NotificationController.m */, CBF1902C243D6F5A001C82ED /* Assets.xcassets */, CBF1902E243D6F5A001C82ED /* Info.plist */, CBF1902F243D6F5A001C82ED /* PushNotificationPayload.apns */, ); path = "WatchApp Extension"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 70A6308F2B7E7EC9003AB1DF /* MMKVVisionDemo */ = { isa = PBXNativeTarget; buildConfigurationList = 70A630A32B7E7ECA003AB1DF /* Build configuration list for PBXNativeTarget "MMKVVisionDemo" */; buildPhases = ( 70A6308C2B7E7EC9003AB1DF /* Sources */, 70A6308D2B7E7EC9003AB1DF /* Frameworks */, 70A6308E2B7E7EC9003AB1DF /* Resources */, 702CE6862B7E85070015E8A4 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( ); name = MMKVVisionDemo; packageProductDependencies = ( ); productName = MMKVVisionDemo; productReference = 70A630902B7E7EC9003AB1DF /* MMKVVisionDemo.app */; productType = "com.apple.product-type.application"; }; CB1FD41F20455E2D00931B5F /* MMKVDemo */ = { isa = PBXNativeTarget; buildConfigurationList = CB1FD43620455E2D00931B5F /* Build configuration list for PBXNativeTarget "MMKVDemo" */; buildPhases = ( CB1FD41C20455E2D00931B5F /* Sources */, CB1FD41D20455E2D00931B5F /* Frameworks */, CB1FD41E20455E2D00931B5F /* Resources */, CB6D44DB23B3A44400F369AA /* Embed Foundation Extensions */, CBF19033243D6F5A001C82ED /* Embed Watch Content */, CB6180E926E90493003FF912 /* CopyFiles */, ); buildRules = ( ); dependencies = ( CB2C907D2BE9DB7D0052A2D7 /* PBXTargetDependency */, CB6D44D623B3A44400F369AA /* PBXTargetDependency */, CBF19031243D6F5A001C82ED /* PBXTargetDependency */, ); name = MMKVDemo; productName = MMKVDemo; productReference = CB1FD42020455E2D00931B5F /* MMKVDemo.app */; productType = "com.apple.product-type.application"; }; CB3166F92A4C6B5F0003221B /* kvdemo */ = { isa = PBXNativeTarget; buildConfigurationList = CB3167132A4C6B600003221B /* Build configuration list for PBXNativeTarget "kvdemo" */; buildPhases = ( CB3166F62A4C6B5F0003221B /* Sources */, CB3166F72A4C6B5F0003221B /* Frameworks */, CB3166F82A4C6B5F0003221B /* Resources */, CB3167162A4C6E380003221B /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = kvdemo; productName = kvdemo; productReference = CB3166FA2A4C6B5F0003221B /* kvdemo.app */; productType = "com.apple.product-type.application"; }; CB36F24820F8A20C009AF46F /* MMKVDemoTests */ = { isa = PBXNativeTarget; buildConfigurationList = CB36F25520F8A20C009AF46F /* Build configuration list for PBXNativeTarget "MMKVDemoTests" */; buildPhases = ( CB36F24520F8A20C009AF46F /* Sources */, CB36F24620F8A20C009AF46F /* Frameworks */, CB36F24720F8A20C009AF46F /* Resources */, ); buildRules = ( ); dependencies = ( CB36F24F20F8A20C009AF46F /* PBXTargetDependency */, ); name = MMKVDemoTests; productName = MMKVDemoTests; productReference = CB36F24920F8A20C009AF46F /* MMKVDemoTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; CB6D44C923B3A44400F369AA /* MMKVTodayExtensionDemo */ = { isa = PBXNativeTarget; buildConfigurationList = CB6D44D823B3A44400F369AA /* Build configuration list for PBXNativeTarget "MMKVTodayExtensionDemo" */; buildPhases = ( CB6D44C623B3A44400F369AA /* Sources */, CB6D44C723B3A44400F369AA /* Frameworks */, CB6D44C823B3A44400F369AA /* Resources */, ); buildRules = ( ); dependencies = ( CBB306652435EC6000CB593E /* PBXTargetDependency */, ); name = MMKVTodayExtensionDemo; productName = MMKVTodayExtensionDemo; productReference = CB6D44CA23B3A44400F369AA /* MMKVTodayExtensionDemo.appex */; productType = "com.apple.product-type.app-extension"; }; CB76C5A32C3D699F008B0E61 /* MMKVCatalystDemo */ = { isa = PBXNativeTarget; buildConfigurationList = CB76C5BA2C3D69A1008B0E61 /* Build configuration list for PBXNativeTarget "MMKVCatalystDemo" */; buildPhases = ( CB76C5A02C3D699F008B0E61 /* Sources */, CB76C5A12C3D699F008B0E61 /* Frameworks */, CB76C5A22C3D699F008B0E61 /* Resources */, CB76C5BE2C3D6A0A008B0E61 /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = MMKVCatalystDemo; productName = MMKVCatalystDemo; productReference = CB76C5A42C3D69A0008B0E61 /* MMKVCatalystDemo.app */; productType = "com.apple.product-type.application"; }; CBB6C81E215CDB9500487192 /* MMKVMacDemo */ = { isa = PBXNativeTarget; buildConfigurationList = CBB6C833215CDB9600487192 /* Build configuration list for PBXNativeTarget "MMKVMacDemo" */; buildPhases = ( CBB6C81B215CDB9500487192 /* Sources */, CBB6C81C215CDB9500487192 /* Frameworks */, CBB6C81D215CDB9500487192 /* Resources */, ); buildRules = ( ); dependencies = ( CB9C2730215E5D2400448B6C /* PBXTargetDependency */, ); name = MMKVMacDemo; productName = MMKVMacDemo; productReference = CBB6C81F215CDB9500487192 /* MMKVMacDemo.app */; productType = "com.apple.product-type.application"; }; CBF19011243D6F59001C82ED /* WatchApp */ = { isa = PBXNativeTarget; buildConfigurationList = CBF1903B243D6F5A001C82ED /* Build configuration list for PBXNativeTarget "WatchApp" */; buildPhases = ( CBF19010243D6F59001C82ED /* Resources */, CBF19036243D6F5A001C82ED /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( CBF19021243D6F5A001C82ED /* PBXTargetDependency */, ); name = WatchApp; productName = WatchApp; productReference = CBF19012243D6F59001C82ED /* WatchApp.app */; productType = "com.apple.product-type.application.watchapp2"; }; CBF1901D243D6F5A001C82ED /* WatchApp Extension */ = { isa = PBXNativeTarget; buildConfigurationList = CBF1903A243D6F5A001C82ED /* Build configuration list for PBXNativeTarget "WatchApp Extension" */; buildPhases = ( CBF1901A243D6F5A001C82ED /* Sources */, CBF1901B243D6F5A001C82ED /* Frameworks */, CBF1901C243D6F5A001C82ED /* Resources */, CBF190AE243D78D4001C82ED /* Frameworks */, ); buildRules = ( ); dependencies = ( CB5C469924AB8837001B25B8 /* PBXTargetDependency */, ); name = "WatchApp Extension"; productName = "WatchApp Extension"; productReference = CBF1901E243D6F5A001C82ED /* WatchApp Extension.appex */; productType = "com.apple.product-type.watchkit2-extension"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ CB1FD41820455E2D00931B5F /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1530; LastUpgradeCheck = 1530; ORGANIZATIONNAME = Lingol; TargetAttributes = { 70A6308F2B7E7EC9003AB1DF = { CreatedOnToolsVersion = 15.2; ProvisioningStyle = Automatic; }; CB1FD41F20455E2D00931B5F = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1020; ProvisioningStyle = Manual; SystemCapabilities = { com.apple.BackgroundModes = { enabled = 1; }; }; }; CB3166F92A4C6B5F0003221B = { CreatedOnToolsVersion = 14.2; ProvisioningStyle = Manual; }; CB36F24820F8A20C009AF46F = { CreatedOnToolsVersion = 9.4.1; ProvisioningStyle = Automatic; TestTargetID = CB1FD41F20455E2D00931B5F; }; CB6D44C923B3A44400F369AA = { CreatedOnToolsVersion = 11.3; ProvisioningStyle = Manual; }; CB76C5A32C3D699F008B0E61 = { CreatedOnToolsVersion = 15.3; }; CBB6C81E215CDB9500487192 = { CreatedOnToolsVersion = 10.0; ProvisioningStyle = Manual; }; CBF19011243D6F59001C82ED = { CreatedOnToolsVersion = 11.4; ProvisioningStyle = Manual; }; CBF1901D243D6F5A001C82ED = { CreatedOnToolsVersion = 11.4; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = CB1FD41B20455E2D00931B5F /* Build configuration list for PBXProject "MMKVDemo" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = CB1FD41720455E2D00931B5F; productRefGroup = CB1FD42120455E2D00931B5F /* Products */; projectDirPath = ""; projectReferences = ( { ProductGroup = CB1FD4B12046994C00931B5F /* Products */; ProjectRef = CB1FD4B02046994C00931B5F /* MMKV.xcodeproj */; }, ); projectRoot = ""; targets = ( CB1FD41F20455E2D00931B5F /* MMKVDemo */, CB36F24820F8A20C009AF46F /* MMKVDemoTests */, CBB6C81E215CDB9500487192 /* MMKVMacDemo */, CB6D44C923B3A44400F369AA /* MMKVTodayExtensionDemo */, CBF19011243D6F59001C82ED /* WatchApp */, CBF1901D243D6F5A001C82ED /* WatchApp Extension */, CB3166F92A4C6B5F0003221B /* kvdemo */, 70A6308F2B7E7EC9003AB1DF /* MMKVVisionDemo */, CB76C5A32C3D699F008B0E61 /* MMKVCatalystDemo */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ CB1FD4B52046994D00931B5F /* libMMKV.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libMMKV.a; remoteRef = CB1FD4B42046994D00931B5F /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; CB36F25420F8A20C009AF46F /* MMKV.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = MMKV.framework; remoteRef = CB36F25320F8A20C009AF46F /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; CB467FBB2431EE2D00FD7421 /* MMKVAppExtension.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = MMKVAppExtension.framework; remoteRef = CB467FBA2431EE2D00FD7421 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; CBF1907C243D7116001C82ED /* MMKVWatchExtension.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = MMKVWatchExtension.framework; remoteRef = CBF1907B243D7116001C82ED /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ 70A6308E2B7E7EC9003AB1DF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 70A6309E2B7E7ECA003AB1DF /* Preview Assets.xcassets in Resources */, 70A6309B2B7E7ECA003AB1DF /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; CB1FD41E20455E2D00931B5F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( CB1FD43020455E2D00931B5F /* LaunchScreen.storyboard in Resources */, CBDC22E22C8ABDD20000DDA5 /* testReadOnly.crc in Resources */, CB1FD42D20455E2D00931B5F /* Assets.xcassets in Resources */, CBDC22E12C8ABDD20000DDA5 /* testReadOnly in Resources */, CB1FD42B20455E2D00931B5F /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; CB3166F82A4C6B5F0003221B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( CB31670C2A4C6B600003221B /* LaunchScreen.storyboard in Resources */, CB3167092A4C6B600003221B /* Assets.xcassets in Resources */, CB3167072A4C6B5F0003221B /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; CB36F24720F8A20C009AF46F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; CB6D44C823B3A44400F369AA /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( CB6D44D323B3A44400F369AA /* MainInterface.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; CB76C5A22C3D699F008B0E61 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( CB76C5B32C3D69A1008B0E61 /* Assets.xcassets in Resources */, CB76C5B62C3D69A1008B0E61 /* Base in Resources */, CB76C5B12C3D69A0008B0E61 /* Base in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; CBB6C81D215CDB9500487192 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( CBB6C828215CDB9600487192 /* Assets.xcassets in Resources */, CBB6C82B215CDB9600487192 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; CBF19010243D6F59001C82ED /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( CBF19018243D6F5A001C82ED /* Assets.xcassets in Resources */, CBF19016243D6F59001C82ED /* Interface.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; CBF1901C243D6F5A001C82ED /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( CBF1902D243D6F5A001C82ED /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 70A6308C2B7E7EC9003AB1DF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 70A630992B7E7EC9003AB1DF /* ContentView.swift in Sources */, 70A630972B7E7EC9003AB1DF /* MMKVVisionDemoApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; CB1FD41C20455E2D00931B5F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CB1FD42820455E2D00931B5F /* ViewController.mm in Sources */, CBB306612435E8F200CB593E /* ViewController+TestCaseBad.mm in Sources */, CBE6689F2D5C52E5005C184E /* TestMMKVCpp.cpp in Sources */, CB1FD43320455E2D00931B5F /* main.m in Sources */, CB9F963920FCA7C500A1C14C /* DemoSwiftUsage.swift in Sources */, CB1FD42520455E2D00931B5F /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; CB3166F62A4C6B5F0003221B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CB3167042A4C6B5F0003221B /* ViewController.m in Sources */, CB3166FE2A4C6B5F0003221B /* AppDelegate.m in Sources */, CB31670F2A4C6B600003221B /* main.m in Sources */, CB3167012A4C6B5F0003221B /* SceneDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; CB36F24520F8A20C009AF46F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CB36F24C20F8A20C009AF46F /* MMKVDemoTests.mm in Sources */, CB36F25A20F8D728009AF46F /* MMKVPerformanceTest.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; CB6D44C623B3A44400F369AA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CB6D44D023B3A44400F369AA /* TodayViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; CB76C5A02C3D699F008B0E61 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CB76C5AE2C3D69A0008B0E61 /* ViewController.mm in Sources */, CB76C5A82C3D69A0008B0E61 /* AppDelegate.m in Sources */, CB76C5B92C3D69A1008B0E61 /* main.m in Sources */, CB76C5AB2C3D69A0008B0E61 /* SceneDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; CBB6C81B215CDB9500487192 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CBB6C826215CDB9500487192 /* ViewController.mm in Sources */, CBB6C82E215CDB9600487192 /* main.m in Sources */, CBB6C823215CDB9500487192 /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; CBF1901A243D6F5A001C82ED /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CBF19028243D6F5A001C82ED /* ExtensionDelegate.mm in Sources */, CBF19025243D6F5A001C82ED /* InterfaceController.m in Sources */, CBF1902B243D6F5A001C82ED /* NotificationController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ CB2C907D2BE9DB7D0052A2D7 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = MMKV; targetProxy = CB2C907C2BE9DB7D0052A2D7 /* PBXContainerItemProxy */; }; CB36F24F20F8A20C009AF46F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = CB1FD41F20455E2D00931B5F /* MMKVDemo */; targetProxy = CB36F24E20F8A20C009AF46F /* PBXContainerItemProxy */; }; CB5C469924AB8837001B25B8 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = MMKVWatchExtension; targetProxy = CB5C469824AB8837001B25B8 /* PBXContainerItemProxy */; }; CB6D44D623B3A44400F369AA /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = CB6D44C923B3A44400F369AA /* MMKVTodayExtensionDemo */; targetProxy = CB6D44D523B3A44400F369AA /* PBXContainerItemProxy */; }; CB9C2730215E5D2400448B6C /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "MMKV Static"; targetProxy = CB9C272F215E5D2400448B6C /* PBXContainerItemProxy */; }; CBB306652435EC6000CB593E /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = MMKVAppExtension; targetProxy = CBB306642435EC6000CB593E /* PBXContainerItemProxy */; }; CBF19021243D6F5A001C82ED /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = CBF1901D243D6F5A001C82ED /* WatchApp Extension */; targetProxy = CBF19020243D6F5A001C82ED /* PBXContainerItemProxy */; }; CBF19031243D6F5A001C82ED /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = CBF19011243D6F59001C82ED /* WatchApp */; targetProxy = CBF19030243D6F5A001C82ED /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ CB1FD42920455E2D00931B5F /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( CB1FD42A20455E2D00931B5F /* Base */, ); name = Main.storyboard; sourceTree = ""; }; CB1FD42E20455E2D00931B5F /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( CB1FD42F20455E2D00931B5F /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; CB3167052A4C6B5F0003221B /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( CB3167062A4C6B5F0003221B /* Base */, ); name = Main.storyboard; sourceTree = ""; }; CB31670A2A4C6B600003221B /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( CB31670B2A4C6B600003221B /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; CB6D44D123B3A44400F369AA /* MainInterface.storyboard */ = { isa = PBXVariantGroup; children = ( CB6D44D223B3A44400F369AA /* Base */, ); name = MainInterface.storyboard; sourceTree = ""; }; CB76C5AF2C3D69A0008B0E61 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( CB76C5B02C3D69A0008B0E61 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; CB76C5B42C3D69A1008B0E61 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( CB76C5B52C3D69A1008B0E61 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; CBB6C829215CDB9600487192 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( CBB6C82A215CDB9600487192 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; CBF19014243D6F59001C82ED /* Interface.storyboard */ = { isa = PBXVariantGroup; children = ( CBF19015243D6F59001C82ED /* Base */, ); name = Interface.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 70A630A02B7E7ECA003AB1DF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"MMKVVisionDemo/Preview Content\""; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "$(TARGET_NAME)/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.MMKVVisionDemo; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = xros; SUPPORTED_PLATFORMS = "xros xrsimulator"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,7"; XROS_DEPLOYMENT_TARGET = 1.0; }; name = Debug; }; 70A630A12B7E7ECA003AB1DF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"MMKVVisionDemo/Preview Content\""; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "$(TARGET_NAME)/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.MMKVVisionDemo; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = xros; SUPPORTED_PLATFORMS = "xros xrsimulator"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,7"; XROS_DEPLOYMENT_TARGET = 1.0; }; name = Release; }; CB1FD43420455E2D00931B5F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; }; CB1FD43520455E2D00931B5F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; CB1FD43720455E2D00931B5F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MMKVDemo/Resources/MMKVDemo.entitlements; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = SG5PVJM4JW; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = SG5PVJM4JW; INFOPLIST_FILE = MMKVDemo/Resources/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvdemo; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = MMKVDemo; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = MMKVDemo; SWIFT_OBJC_BRIDGING_HEADER = "MMKVDemo/MMKVDemo-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; CB1FD43820455E2D00931B5F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MMKVDemo/Resources/MMKVDemo.entitlements; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = SG5PVJM4JW; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = SG5PVJM4JW; INFOPLIST_FILE = MMKVDemo/Resources/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvdemo; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = MMKVDemo; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = MMKVDemo; SWIFT_OBJC_BRIDGING_HEADER = "MMKVDemo/MMKVDemo-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; CB3167102A4C6B600003221B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = kvdemo/kvdemo.entitlements; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = SG5PVJM4JW; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = kvdemo/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.kvdemo; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = mmkvdemo2; SWIFT_EMIT_LOC_STRINGS = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; CB3167112A4C6B600003221B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = kvdemo/kvdemo.entitlements; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = SG5PVJM4JW; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = kvdemo/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.kvdemo; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = mmkvdemo2; SWIFT_EMIT_LOC_STRINGS = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; CB36F25020F8A20C009AF46F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 52LMU3A345; INFOPLIST_FILE = MMKVDemoTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvdemoTests; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MMKVDemo.app/MMKVDemo"; }; name = Debug; }; CB36F25120F8A20C009AF46F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 52LMU3A345; INFOPLIST_FILE = MMKVDemoTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvdemoTests; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MMKVDemo.app/MMKVDemo"; }; name = Release; }; CB6D44D923B3A44400F369AA /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = MMKVTodayExtensionDemo/MMKVTodayExtensionDemo.entitlements; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = SG5PVJM4JW; INFOPLIST_FILE = MMKVTodayExtensionDemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvdemo.MMKVTodayExtensionDemo; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = MMKVTodayExtension; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; CB6D44DA23B3A44400F369AA /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = MMKVTodayExtensionDemo/MMKVTodayExtensionDemo.entitlements; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = SG5PVJM4JW; INFOPLIST_FILE = MMKVTodayExtensionDemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvdemo.MMKVTodayExtensionDemo; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = MMKVTodayExtension; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; CB76C5BB2C3D69A1008B0E61 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = MMKVCatalystDemo/MMKVCatalystDemo.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = SG5PVJM4JW; "DEVELOPMENT_TEAM[sdk=macosx*]" = SG5PVJM4JW; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MMKVCatalystDemo/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.MMKVCatalystDemo; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = MMKViOSCatalystDemo; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = MMKVCatalystDemo; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SWIFT_EMIT_LOC_STRINGS = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; CB76C5BC2C3D69A1008B0E61 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = MMKVCatalystDemo/MMKVCatalystDemo.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = SG5PVJM4JW; "DEVELOPMENT_TEAM[sdk=macosx*]" = SG5PVJM4JW; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MMKVCatalystDemo/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.MMKVCatalystDemo; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = MMKViOSCatalystDemo; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = MMKVCatalystDemo; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SWIFT_EMIT_LOC_STRINGS = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; CBB6C830215CDB9600487192 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = MMKVMacDemo/MMKVMacDemo.entitlements; CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = SG5PVJM4JW; "DEVELOPMENT_TEAM[sdk=macosx*]" = SG5PVJM4JW; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = MMKVMacDemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvmacdemo; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = MMKVMacDemo; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = MMKVMacDemo; SDKROOT = macosx; }; name = Debug; }; CBB6C831215CDB9600487192 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = MMKVMacDemo/MMKVMacDemo.entitlements; CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = SG5PVJM4JW; "DEVELOPMENT_TEAM[sdk=macosx*]" = SG5PVJM4JW; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = MMKVMacDemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvmacdemo; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = MMKVMacDemo; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = MMKVMacDemo; SDKROOT = macosx; }; name = Release; }; CBF19034243D6F5A001C82ED /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = SG5PVJM4JW; EXCLUDED_ARCHS = armv7k; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvdemo.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = MMKVWatchApp; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 6.0; }; name = Debug; }; CBF19035243D6F5A001C82ED /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = SG5PVJM4JW; EXCLUDED_ARCHS = armv7k; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvdemo.watchkitapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = MMKVWatchApp; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 6.0; }; name = Release; }; CBF19037243D6F5A001C82ED /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = SG5PVJM4JW; EXCLUDED_ARCHS = armv7k; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvdemo.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; PROVISIONING_PROFILE_SPECIFIER = MMKVWatchExtension; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 6.0; }; name = Debug; }; CBF19038243D6F5A001C82ED /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = SG5PVJM4JW; EXCLUDED_ARCHS = armv7k; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.tencent.mmkvdemo.watchkitapp.watchkitextension; PRODUCT_NAME = "${TARGET_NAME}"; PROVISIONING_PROFILE_SPECIFIER = MMKVWatchExtension; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 6.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 70A630A32B7E7ECA003AB1DF /* Build configuration list for PBXNativeTarget "MMKVVisionDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( 70A630A02B7E7ECA003AB1DF /* Debug */, 70A630A12B7E7ECA003AB1DF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CB1FD41B20455E2D00931B5F /* Build configuration list for PBXProject "MMKVDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( CB1FD43420455E2D00931B5F /* Debug */, CB1FD43520455E2D00931B5F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CB1FD43620455E2D00931B5F /* Build configuration list for PBXNativeTarget "MMKVDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( CB1FD43720455E2D00931B5F /* Debug */, CB1FD43820455E2D00931B5F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CB3167132A4C6B600003221B /* Build configuration list for PBXNativeTarget "kvdemo" */ = { isa = XCConfigurationList; buildConfigurations = ( CB3167102A4C6B600003221B /* Debug */, CB3167112A4C6B600003221B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CB36F25520F8A20C009AF46F /* Build configuration list for PBXNativeTarget "MMKVDemoTests" */ = { isa = XCConfigurationList; buildConfigurations = ( CB36F25020F8A20C009AF46F /* Debug */, CB36F25120F8A20C009AF46F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CB6D44D823B3A44400F369AA /* Build configuration list for PBXNativeTarget "MMKVTodayExtensionDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( CB6D44D923B3A44400F369AA /* Debug */, CB6D44DA23B3A44400F369AA /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CB76C5BA2C3D69A1008B0E61 /* Build configuration list for PBXNativeTarget "MMKVCatalystDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( CB76C5BB2C3D69A1008B0E61 /* Debug */, CB76C5BC2C3D69A1008B0E61 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CBB6C833215CDB9600487192 /* Build configuration list for PBXNativeTarget "MMKVMacDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( CBB6C830215CDB9600487192 /* Debug */, CBB6C831215CDB9600487192 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CBF1903A243D6F5A001C82ED /* Build configuration list for PBXNativeTarget "WatchApp Extension" */ = { isa = XCConfigurationList; buildConfigurations = ( CBF19037243D6F5A001C82ED /* Debug */, CBF19038243D6F5A001C82ED /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; CBF1903B243D6F5A001C82ED /* Build configuration list for PBXNativeTarget "WatchApp" */ = { isa = XCConfigurationList; buildConfigurations = ( CBF19034243D6F5A001C82ED /* Debug */, CBF19035243D6F5A001C82ED /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = CB1FD41820455E2D00931B5F /* Project object */; } ================================================ FILE: iOS/MMKVDemo/MMKVDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: iOS/MMKVDemo/MMKVDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: iOS/MMKVDemo/MMKVDemo.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: iOS/MMKVDemo/MMKVDemo.xcodeproj/xcshareddata/xcschemes/MMKVDemo.xcscheme ================================================ ================================================ FILE: iOS/MMKVDemo/MMKVDemo.xcodeproj/xcshareddata/xcschemes/MMKVMacDemo.xcscheme ================================================ ================================================ FILE: iOS/MMKVDemo/MMKVDemo.xcodeproj/xcshareddata/xcschemes/MMKVTodayExtensionDemo.xcscheme ================================================ ================================================ FILE: iOS/MMKVDemo/MMKVDemo.xcodeproj/xcshareddata/xcschemes/WatchApp (Notification).xcscheme ================================================ ================================================ FILE: iOS/MMKVDemo/MMKVDemo.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme ================================================ ================================================ FILE: iOS/MMKVDemo/MMKVDemo.xcodeproj/xcshareddata/xcschemes/kvdemo.xcscheme ================================================ ================================================ FILE: iOS/MMKVDemo/MMKVDemoTests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: iOS/MMKVDemo/MMKVDemoTests/MMKVDemoTests.mm ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ #import #import #import using namespace std; #define KeyNotExist @"KeyNotExist" @interface MockNSCoding : NSObject @property NSString *string1; @property NSString *string2; @property NSUInteger integer; @property NSSet *set; - (BOOL)isEqualToObject:(MockNSCoding *)object; @end @implementation MockNSCoding #pragma mark - NSCoding - (id)initWithCoder:(NSCoder *)decoder { self = [super init]; if (!self) { return nil; } self.string1 = [decoder decodeObjectForKey:@"string1"]; self.string2 = [decoder decodeObjectForKey:@"string2"]; self.integer = [decoder decodeIntegerForKey:@"integer"]; self.set = [decoder decodeObjectForKey:@"set"]; return self; } - (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeObject:self.string1 forKey:@"string1"]; [encoder encodeObject:self.string2 forKey:@"string2"]; [encoder encodeInteger:self.integer forKey:@"integer"]; [encoder encodeObject:self.set forKey:@"set"]; } - (BOOL)isEqualToObject:(MockNSCoding *)object { return [self.string1 isEqualToString:object.string1] && [self.string2 isEqualToString:object.string2] && self.integer == object.integer && [self.set isEqualToSet:object.set]; } @end @interface MMKVDemoTests : XCTestCase @end @implementation MMKVDemoTests { MMKV *mmkv; } - (void)setUp { [super setUp]; mmkv = [MMKV mmkvWithID:@"unit_test"]; } - (void)tearDown { mmkv = nil; [super tearDown]; } - (void)testBool { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. BOOL ret = [mmkv setBool:YES forKey:@"bool"]; XCTAssertEqual(ret, YES); BOOL value = [mmkv getBoolForKey:@"bool"]; XCTAssertEqual(value, YES); value = [mmkv getBoolForKey:KeyNotExist]; XCTAssertEqual(value, NO); value = [mmkv getBoolForKey:KeyNotExist defaultValue:YES]; XCTAssertEqual(value, YES); } - (void)testInt32 { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. BOOL ret = [mmkv setInt32:numeric_limits::max() forKey:@"int32"]; XCTAssertEqual(ret, YES); int32_t value = [mmkv getInt32ForKey:@"int32"]; XCTAssertEqual(value, numeric_limits::max()); value = [mmkv getInt32ForKey:KeyNotExist]; XCTAssertEqual(value, 0); value = [mmkv getInt32ForKey:KeyNotExist defaultValue:-1]; XCTAssertEqual(value, -1); } - (void)testInt64 { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. BOOL ret = [mmkv setInt64:numeric_limits::max() forKey:@"int64"]; XCTAssertEqual(ret, YES); int64_t value = [mmkv getInt64ForKey:@"int64"]; XCTAssertEqual(value, numeric_limits::max()); value = [mmkv getInt64ForKey:KeyNotExist]; XCTAssertEqual(value, 0); value = [mmkv getInt64ForKey:KeyNotExist defaultValue:-1]; XCTAssertEqual(value, -1); } - (void)testUInt32 { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. BOOL ret = [mmkv setUInt32:numeric_limits::max() forKey:@"uint32"]; XCTAssertEqual(ret, YES); uint32_t value = [mmkv getUInt32ForKey:@"uint32"]; XCTAssertEqual(value, numeric_limits::max()); value = [mmkv getUInt32ForKey:KeyNotExist]; XCTAssertEqual(value, 0); value = [mmkv getUInt32ForKey:KeyNotExist defaultValue:-1]; XCTAssertEqual(value, -1); } - (void)testUInt64 { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. BOOL ret = [mmkv setUInt64:numeric_limits::max() forKey:@"uint64"]; XCTAssertEqual(ret, YES); uint64_t value = [mmkv getUInt64ForKey:@"uint64"]; XCTAssertEqual(value, numeric_limits::max()); value = [mmkv getUInt64ForKey:KeyNotExist]; XCTAssertEqual(value, 0); value = [mmkv getUInt64ForKey:KeyNotExist defaultValue:1024]; XCTAssertEqual(value, 1024); } - (void)testFloat { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. BOOL ret = [mmkv setFloat:numeric_limits::max() forKey:@"float"]; XCTAssertEqual(ret, YES); float value = [mmkv getFloatForKey:@"float"]; XCTAssertEqualWithAccuracy(value, numeric_limits::max(), 0.001); value = [mmkv getFloatForKey:KeyNotExist]; XCTAssertEqualWithAccuracy(value, 0, 0.001); value = [mmkv getFloatForKey:KeyNotExist defaultValue:-1]; XCTAssertEqualWithAccuracy(value, -1, 0.001); } - (void)testDouble { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. BOOL ret = [mmkv setDouble:numeric_limits::max() forKey:@"double"]; XCTAssertEqual(ret, YES); double value = [mmkv getDoubleForKey:@"double"]; XCTAssertEqualWithAccuracy(value, numeric_limits::max(), 0.001); value = [mmkv getDoubleForKey:KeyNotExist]; XCTAssertEqualWithAccuracy(value, 0, 0.001); value = [mmkv getDoubleForKey:KeyNotExist defaultValue:-1]; XCTAssertEqualWithAccuracy(value, -1, 0.001); } - (void)testNSString { NSString *str = @"Hello 2018 world cup 世界杯"; BOOL ret = [mmkv setObject:str forKey:@"string"]; XCTAssertEqual(ret, YES); NSString *value = [mmkv getObjectOfClass:NSString.class forKey:@"string"]; XCTAssertEqualObjects(value, str); value = [mmkv getObjectOfClass:NSString.class forKey:KeyNotExist]; XCTAssertEqualObjects(value, nil); } - (void)testNSStringForNewGetSet { NSString *str = @"Hello 2018 world cup 世界杯"; BOOL ret = [mmkv setString:str forKey:@"string"]; XCTAssertEqual(ret, YES); NSString *value = [mmkv getStringForKey:@"string"]; XCTAssertEqualObjects(value, str); value = [mmkv getStringForKey:KeyNotExist]; XCTAssertEqualObjects(value, nil); } - (void)testNSData { NSString *str = @"Hello 2018 world cup 世界杯"; NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding]; BOOL ret = [mmkv setObject:data forKey:@"data"]; XCTAssertEqual(ret, YES); NSData *value = [mmkv getObjectOfClass:NSData.class forKey:@"data"]; XCTAssertEqualObjects(value, data); value = [mmkv getObjectOfClass:NSData.class forKey:KeyNotExist]; XCTAssertEqualObjects(value, nil); } - (void)testNSDataForNewGetSet { NSString *str = @"Hello 2018 world cup 世界杯"; NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding]; BOOL ret = [mmkv setData:data forKey:@"data"]; XCTAssertEqual(ret, YES); NSData *value = [mmkv getDataForKey:@"data"]; XCTAssertEqualObjects(value, data); value = [mmkv getDataForKey:KeyNotExist]; XCTAssertEqualObjects(value, nil); } - (void)testNSDate { NSDate *date = [NSDate date]; BOOL ret = [mmkv setObject:date forKey:@"date"]; XCTAssertEqual(ret, YES); NSDate *value = [mmkv getObjectOfClass:NSDate.class forKey:@"date"]; [self compareDate:date withDate:value]; value = [mmkv getObjectOfClass:NSDate.class forKey:KeyNotExist]; XCTAssertEqualObjects(value, nil); } - (void)testNSDateForNewGetSet { NSDate *date = [NSDate date]; BOOL ret = [mmkv setDate:date forKey:@"date"]; XCTAssertEqual(ret, YES); NSDate *value = [mmkv getDateForKey:@"date"]; [self compareDate:date withDate:value]; value = [mmkv getObjectOfClass:NSDate.class forKey:KeyNotExist]; XCTAssertEqualObjects(value, nil); } - (void)testNSDictionary { NSDictionary *dic = @{@"key1" : @"value1", @"key2" : @(2)}; BOOL ret = [mmkv setObject:dic forKey:@"dictionary"]; XCTAssertTrue(ret); NSDictionary *value = [mmkv getObjectOfClass:[NSDictionary class] forKey:@"dictionary"]; XCTAssertTrue([@"value1" isEqualToString:value[@"key1"]]); XCTAssertTrue(2 == [value[@"key2"] intValue]); } - (void)testNSMutableDictionary { NSMutableDictionary *dic = [@{@"key1" : @"value1", @"key2" : @(2)} mutableCopy]; BOOL ret = [mmkv setObject:dic forKey:@"dictionary"]; XCTAssertTrue(ret); NSDictionary *value = [mmkv getObjectOfClass:[NSDictionary class] forKey:@"dictionary"]; XCTAssertTrue([@"value1" isEqualToString:value[@"key1"]]); XCTAssertTrue(2 == [value[@"key2"] intValue]); } - (void)testNSArray { NSArray *array = @[ @"0", @"1", @"2" ]; BOOL ret = [mmkv setObject:array forKey:@"array"]; XCTAssertTrue(ret); NSArray *value = [mmkv getObjectOfClass:[NSDictionary class] forKey:@"array"]; XCTAssertTrue([@"0" isEqualToString:value[0]]); XCTAssertTrue([@"1" isEqualToString:value[1]]); XCTAssertTrue([@"2" isEqualToString:value[2]]); } - (void)testNSMutableArray { NSMutableArray *array = [@[ @"0", @"1", @"2" ] copy]; BOOL ret = [mmkv setObject:array forKey:@"array"]; XCTAssertTrue(ret); NSArray *value = [mmkv getObjectOfClass:[NSDictionary class] forKey:@"array"]; XCTAssertTrue([@"0" isEqualToString:value[0]]); XCTAssertTrue([@"1" isEqualToString:value[1]]); XCTAssertTrue([@"2" isEqualToString:value[2]]); } - (void)testNSSet { NSSet *set = [NSSet setWithObjects:@1, @2, @3, @4, @4, nil]; BOOL ret = [mmkv setObject:set forKey:@"set"]; XCTAssertTrue(ret); NSSet *value = [mmkv getObjectOfClass:[NSSet class] forKey:@"set"]; XCTAssertTrue([value isEqualToSet:set]); } - (void)testNSMutableSet { NSMutableSet *set = [NSMutableSet setWithObjects:@1, @2, @3, @4, @4, nil]; BOOL ret = [mmkv setObject:set forKey:@"set"]; XCTAssertTrue(ret); NSSet *value = [mmkv getObjectOfClass:[NSSet class] forKey:@"set"]; XCTAssertTrue([value isEqualToSet:set]); } - (void)testCustomNSCodingObject { MockNSCoding *object = [[MockNSCoding alloc] init]; object.string1 = @"hello"; object.string2 = @"world"; object.integer = 1024; object.set = [NSSet setWithObjects:@1, @2, @3, @4, @4, nil]; BOOL ret = [mmkv setObject:object forKey:@"MockNSCoding"]; XCTAssertTrue(ret); MockNSCoding *value = [mmkv getObjectOfClass:[MockNSCoding class] forKey:@"MockNSCoding"]; XCTAssertTrue([value isEqualToObject:object]); } - (void)testCustomNSCodingObjectInArray { MockNSCoding *object1 = [[MockNSCoding alloc] init]; object1.string1 = @"hello"; object1.string2 = @"world"; object1.integer = 1024; object1.set = [NSSet setWithObjects:@1, @2, @3, @4, @4, nil]; MockNSCoding *object2 = [[MockNSCoding alloc] init]; object2.string1 = @"hello"; object2.string2 = @"100mango"; object2.integer = 1023; object2.set = [NSSet setWithObjects:@1, @2, @3, @4, @4, nil]; MockNSCoding *object3 = [[MockNSCoding alloc] init]; object3.string1 = @"hello"; object3.string2 = @"apple"; object3.integer = 1023; object3.set = [NSSet setWithObjects:@1, @2, @3, @4, @4, nil]; BOOL ret = [mmkv setObject:@[ object1, object2, object3 ] forKey:@"MockNSCoding"]; XCTAssertTrue(ret); NSArray *value = [mmkv getObjectOfClass:[NSArray class] forKey:@"MockNSCoding"]; XCTAssertTrue([value[0] isEqualToObject:object1]); XCTAssertTrue([value[1] isEqualToObject:object2]); XCTAssertTrue([value[2] isEqualToObject:object3]); } - (void)testRemove { BOOL ret = [mmkv setBool:YES forKey:@"bool_1"]; ret &= [mmkv setInt32:numeric_limits::max() forKey:@"int_1"]; ret &= [mmkv setInt64:numeric_limits::max() forKey:@"long_1"]; ret &= [mmkv setFloat:numeric_limits::min() forKey:@"float_1"]; ret &= [mmkv setDouble:numeric_limits::min() forKey:@"double_1"]; ret &= [mmkv setObject:@"hello" forKey:@"string_1"]; ret &= [mmkv setObject:@{@"key" : @"value"} forKey:@"dictionary"]; XCTAssertEqual(ret, YES); { long count = mmkv.count; [mmkv removeValueForKey:@"bool_1"]; [mmkv removeValuesForKeys:@[ @"int_1", @"long_1" ]]; long newCount = mmkv.count; XCTAssertEqual(count, newCount + 3); } BOOL bValue = [mmkv getBoolForKey:@"bool_1"]; XCTAssertEqual(bValue, NO); int32_t iValue = [mmkv getInt32ForKey:@"int_1"]; XCTAssertEqual(iValue, 0); int64_t lValue = [mmkv getInt64ForKey:@"long_1"]; XCTAssertEqual(lValue, 0); float fValue = [mmkv getFloatForKey:@"float_1"]; XCTAssertEqualWithAccuracy(fValue, numeric_limits::min(), 0.001); double dValue = [mmkv getDoubleForKey:@"double_1"]; XCTAssertEqualWithAccuracy(dValue, numeric_limits::min(), 0.001); NSString *sValue = [mmkv getObjectOfClass:NSString.class forKey:@"string_1"]; XCTAssertEqualObjects(sValue, @"hello"); } - (void)compareDate:(NSDate *)date withDate:(NSDate *)other { XCTAssertEqualWithAccuracy(date.timeIntervalSince1970, other.timeIntervalSince1970, 0.001); } - (void)testImportFromNSUserDefaults { NSUserDefaults *userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"testNSUserDefaults"]; [userDefault setBool:YES forKey:@"bool"]; [userDefault setInteger:std::numeric_limits::max() forKey:@"NSInteger"]; [userDefault setFloat:3.14 forKey:@"float"]; [userDefault setDouble:std::numeric_limits::max() forKey:@"double"]; [userDefault setObject:@"hello, NSUserDefaults" forKey:@"string"]; [userDefault setObject:[NSDate date] forKey:@"date"]; [userDefault setObject:[@"hello, NSUserDefaults again" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"data"]; [userDefault setURL:[NSURL URLWithString:@"https://mail.qq.com"] forKey:@"url"]; NSNumber *number = [NSNumber numberWithBool:YES]; [userDefault setObject:number forKey:@"number_bool"]; number = [NSNumber numberWithChar:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_char"]; number = [NSNumber numberWithUnsignedChar:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_unsigned_char"]; number = [NSNumber numberWithShort:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_short"]; number = [NSNumber numberWithUnsignedShort:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_unsigned_short"]; number = [NSNumber numberWithInt:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_int"]; number = [NSNumber numberWithUnsignedInt:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_unsigned_int"]; number = [NSNumber numberWithLong:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_long"]; number = [NSNumber numberWithUnsignedLong:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_unsigned_long"]; number = [NSNumber numberWithLongLong:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_long_long"]; number = [NSNumber numberWithUnsignedLongLong:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_unsigned_long_long"]; number = [NSNumber numberWithFloat:3.1415]; [userDefault setObject:number forKey:@"number_float"]; number = [NSNumber numberWithDouble:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_double"]; number = [NSNumber numberWithInteger:std::numeric_limits::min()]; [userDefault setObject:number forKey:@"number_NSInteger"]; number = [NSNumber numberWithUnsignedInteger:std::numeric_limits::max()]; [userDefault setObject:number forKey:@"number_NSUInteger"]; [mmkv migrateFromUserDefaults:userDefault]; XCTAssertEqual([mmkv getBoolForKey:@"bool"], [userDefault boolForKey:@"bool"]); XCTAssertEqual([mmkv getInt64ForKey:@"NSInteger"], [userDefault integerForKey:@"NSInteger"]); XCTAssertEqualWithAccuracy([mmkv getFloatForKey:@"float"], [userDefault floatForKey:@"float"], 0.001); XCTAssertEqualWithAccuracy([mmkv getDoubleForKey:@"double"], [userDefault doubleForKey:@"double"], 0.001); XCTAssertEqualObjects([mmkv getStringForKey:@"string"], [userDefault stringForKey:@"string"]); [self compareDate:[mmkv getDateForKey:@"date"] withDate:[userDefault objectForKey:@"date"]]; XCTAssertEqualObjects([mmkv getDataForKey:@"data"], [userDefault dataForKey:@"data"]); XCTAssertEqualObjects([NSKeyedUnarchiver unarchivedObjectOfClass:NSURL.class fromData:[mmkv getDataForKey:@"url"] error:nil], [userDefault URLForKey:@"url"]); number = [userDefault objectForKey:@"number_bool"]; XCTAssertEqual([mmkv getBoolForKey:@"number_bool"], number.boolValue); number = [userDefault objectForKey:@"number_char"]; XCTAssertEqual([mmkv getInt32ForKey:@"number_char"], number.charValue); number = [userDefault objectForKey:@"number_unsigned_char"]; XCTAssertEqual([mmkv getInt32ForKey:@"number_unsigned_char"], number.unsignedCharValue); number = [userDefault objectForKey:@"number_short"]; XCTAssertEqual([mmkv getInt32ForKey:@"number_short"], number.shortValue); number = [userDefault objectForKey:@"number_unsigned_short"]; XCTAssertEqual([mmkv getInt32ForKey:@"number_unsigned_short"], number.unsignedShortValue); number = [userDefault objectForKey:@"number_int"]; XCTAssertEqual([mmkv getInt32ForKey:@"number_int"], number.intValue); number = [userDefault objectForKey:@"number_unsigned_int"]; XCTAssertEqual([mmkv getUInt32ForKey:@"number_unsigned_int"], number.unsignedIntValue); number = [userDefault objectForKey:@"number_long"]; XCTAssertEqual([mmkv getInt64ForKey:@"number_long"], number.longValue); number = [userDefault objectForKey:@"number_unsigned_long"]; XCTAssertEqual([mmkv getUInt64ForKey:@"number_unsigned_long"], number.unsignedLongValue); number = [userDefault objectForKey:@"number_long_long"]; XCTAssertEqual([mmkv getInt64ForKey:@"number_long_long"], number.longLongValue); number = [userDefault objectForKey:@"number_unsigned_long_long"]; XCTAssertEqual([mmkv getUInt64ForKey:@"number_unsigned_long_long"], number.unsignedLongLongValue); number = [userDefault objectForKey:@"number_float"]; XCTAssertEqualWithAccuracy([mmkv getFloatForKey:@"number_float"], number.floatValue, 0.001); number = [userDefault objectForKey:@"number_double"]; XCTAssertEqualWithAccuracy([mmkv getDoubleForKey:@"number_double"], number.doubleValue, 0.001); number = [userDefault objectForKey:@"number_NSInteger"]; XCTAssertEqual([mmkv getInt64ForKey:@"number_NSInteger"], number.integerValue); number = [userDefault objectForKey:@"number_NSUInteger"]; XCTAssertEqual([mmkv getUInt64ForKey:@"number_NSUInteger"], number.unsignedIntegerValue); } - (void)testMultiTimesOverwriteValue { NSData *data; int loops = 1000000; for (int index = 0; index < loops; index++) { NSString *str = [NSString stringWithFormat:@"%s-%d", __FILE__, rand()]; data = [str dataUsingEncoding:NSUTF8StringEncoding]; BOOL ret = [mmkv setData:data forKey:@"data"]; XCTAssertEqual(ret, YES); } NSData *value = [mmkv getObjectOfClass:NSData.class forKey:@"data"]; XCTAssertEqualObjects(value, data); value = [mmkv getObjectOfClass:NSData.class forKey:KeyNotExist]; XCTAssertEqualObjects(value, nil); } @end ================================================ FILE: iOS/MMKVDemo/MMKVDemoTests/MMKVPerformanceTest.mm ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2018 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. */ #import #import @interface MMKVPerformanceTest : XCTestCase @end @implementation MMKVPerformanceTest { MMKV *mmkv; } - (void)setUp { [super setUp]; mmkv = [MMKV mmkvWithID:@"performance_test"]; } - (void)tearDown { mmkv = nil; [super tearDown]; } - (void)testWriteIntPerformance { int loops = 10000; NSMutableArray *arrIntKeys = [NSMutableArray arrayWithCapacity:loops]; for (size_t index = 0; index < loops; index++) { NSString *intKey = [NSString stringWithFormat:@"int-%zu", index]; [arrIntKeys addObject:intKey]; } [self measureBlock:^{ for (int index = 0; index < loops; index++) { int32_t tmp = rand(); NSString *intKey = arrIntKeys[index]; [self->mmkv setInt32:tmp forKey:intKey]; } }]; } - (void)testReadIntPerformance { int loops = 10000; NSMutableArray *arrIntKeys = [NSMutableArray arrayWithCapacity:loops]; for (size_t index = 0; index < loops; index++) { NSString *intKey = [NSString stringWithFormat:@"int-%zu", index]; [arrIntKeys addObject:intKey]; } [self measureBlock:^{ for (int index = 0; index < loops; index++) { NSString *intKey = arrIntKeys[index]; [self->mmkv getInt32ForKey:intKey]; } }]; } - (void)testWriteStringPerformance { int loops = 10000; NSMutableArray *arrStrings = [NSMutableArray arrayWithCapacity:loops]; NSMutableArray *arrStrKeys = [NSMutableArray arrayWithCapacity:loops]; for (size_t index = 0; index < loops; index++) { NSString *str = [NSString stringWithFormat:@"%s-%d", __FILE__, rand()]; [arrStrings addObject:str]; NSString *strKey = [NSString stringWithFormat:@"str-%zu", index]; [arrStrKeys addObject:strKey]; } [self measureBlock:^{ for (int index = 0; index < loops; index++) { NSString *str = arrStrings[index]; NSString *strKey = arrStrKeys[index]; [self->mmkv setObject:str forKey:strKey]; } }]; } - (void)testReadStringPerformance { int loops = 10000; NSMutableArray *arrStrings = [NSMutableArray arrayWithCapacity:loops]; NSMutableArray *arrStrKeys = [NSMutableArray arrayWithCapacity:loops]; for (size_t index = 0; index < loops; index++) { NSString *str = [NSString stringWithFormat:@"%s-%d", __FILE__, rand()]; [arrStrings addObject:str]; NSString *strKey = [NSString stringWithFormat:@"str-%zu", index]; [arrStrKeys addObject:strKey]; } [self measureBlock:^{ for (int index = 0; index < loops; index++) { NSString *strKey = arrStrKeys[index]; [self->mmkv getObjectOfClass:NSString.class forKey:strKey]; } }]; } @end ================================================ FILE: iOS/MMKVDemo/MMKVMacDemo/AppDelegate.h ================================================ // // AppDelegate.h // MMKVMacDemo // // Created by Ling Guo on 2018/9/27. // Copyright © 2018 Lingol. All rights reserved. // #import @interface AppDelegate : NSObject @end ================================================ FILE: iOS/MMKVDemo/MMKVMacDemo/AppDelegate.m ================================================ // // AppDelegate.m // MMKVMacDemo // // Created by Ling Guo on 2018/9/27. // Copyright © 2018 Lingol. All rights reserved. // #import "AppDelegate.h" #import @interface AppDelegate () @end @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Insert code here to initialize your application } - (void)applicationWillTerminate:(NSNotification *)aNotification { // Insert code here to tear down your application [MMKV onAppTerminate]; } @end ================================================ FILE: iOS/MMKVDemo/MMKVMacDemo/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "size" : "16x16", "scale" : "1x" }, { "idiom" : "mac", "size" : "16x16", "scale" : "2x" }, { "idiom" : "mac", "size" : "32x32", "scale" : "1x" }, { "idiom" : "mac", "size" : "32x32", "scale" : "2x" }, { "idiom" : "mac", "size" : "128x128", "scale" : "1x" }, { "idiom" : "mac", "size" : "128x128", "scale" : "2x" }, { "idiom" : "mac", "size" : "256x256", "scale" : "1x" }, { "idiom" : "mac", "size" : "256x256", "scale" : "2x" }, { "idiom" : "mac", "size" : "512x512", "scale" : "1x" }, { "idiom" : "mac", "size" : "512x512", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: iOS/MMKVDemo/MMKVMacDemo/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: iOS/MMKVDemo/MMKVMacDemo/Base.lproj/Main.storyboard ================================================ Default Left to Right Right to Left Default Left to Right Right to Left ================================================ FILE: iOS/MMKVDemo/MMKVMacDemo/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright Copyright © 2018 Lingol. All rights reserved. NSMainStoryboardFile Main NSPrincipalClass NSApplication ================================================ FILE: iOS/MMKVDemo/MMKVMacDemo/MMKVMacDemo.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.files.user-selected.read-only ================================================ FILE: iOS/MMKVDemo/MMKVMacDemo/ViewController.h ================================================ // // ViewController.h // MMKVMacDemo // // Created by Ling Guo on 2018/9/27. // Copyright © 2018 Lingol. All rights reserved. // #import @interface ViewController : NSViewController @end ================================================ FILE: iOS/MMKVDemo/MMKVMacDemo/ViewController.mm ================================================ // // ViewController.m // MMKVMacDemo // // Created by Ling Guo on 2018/9/27. // Copyright © 2018 Lingol. All rights reserved. // #import "ViewController.h" #import @implementation ViewController { NSMutableArray *m_arrStrings; NSMutableArray *m_arrStrKeys; NSMutableArray *m_arrIntKeys; int m_loops; } - (void)viewDidLoad { [super viewDidLoad]; [self testCompareBeforeSet]; [self onlyOneKeyTest]; [self overrideTest]; [self expectedCapacityTest]; [self testClearAllWithKeepingSpace]; [self funcionalTest:NO]; [self testNeedLoadFromFile]; m_loops = 10000; m_arrStrings = [NSMutableArray arrayWithCapacity:m_loops]; m_arrStrKeys = [NSMutableArray arrayWithCapacity:m_loops]; m_arrIntKeys = [NSMutableArray arrayWithCapacity:m_loops]; for (size_t index = 0; index < m_loops; index++) { NSString *str = [NSString stringWithFormat:@"%s-%d", __FILE__, rand()]; [m_arrStrings addObject:str]; NSString *strKey = [NSString stringWithFormat:@"str-%zu", index]; [m_arrStrKeys addObject:strKey]; NSString *intKey = [NSString stringWithFormat:@"int-%zu", index]; [m_arrIntKeys addObject:intKey]; } } - (void) testClearAllWithKeepingSpace { { auto mmkv = [MMKV mmkvWithID:@"testClearAllWithKeepingSpace"]; [mmkv setFloat:123.456f forKey:@"key1"]; for (int i = 0; i < 10000; i++) { [mmkv setFloat:123.456f forKey:[NSString stringWithFormat:@"key_%d", i]]; } auto previousSize =[mmkv totalSize]; assert(previousSize > PAGE_SIZE); [mmkv clearAllWithKeepingSpace]; assert([mmkv totalSize] == previousSize); assert([mmkv count] == 0); [mmkv setFloat:123.4567f forKey:@"key2"]; [mmkv setFloat:223.47f forKey:@"key3"]; assert([mmkv count] == 2); } { NSString *crypt = [NSString stringWithFormat:@"Crypt123"]; auto mmkv = [MMKV mmkvWithID:@"testClearAllWithKeepingSpaceCrypt" cryptKey:[crypt dataUsingEncoding:NSUTF8StringEncoding] mode:MMKVSingleProcess]; [mmkv setFloat:123.456f forKey:@"key1"]; for (int i = 0; i < 10000; i++) { [mmkv setFloat:123.456f forKey:[NSString stringWithFormat:@"key_%d", i]]; } auto previousSize =[mmkv totalSize]; assert(previousSize > PAGE_SIZE); [mmkv clearAllWithKeepingSpace]; assert([mmkv totalSize] == previousSize); assert([mmkv count] == 0); [mmkv setFloat:123.4567f forKey:@"key2"]; [mmkv setFloat:223.47f forKey:@"key3"]; assert([mmkv count] == 2); } } - (void) onlyOneKeyTest { { auto mmkv0 = [MMKV mmkvWithID:@"onlyOneKeyTest"]; NSString *key = [NSString stringWithFormat:@"hello"]; NSString *value = [NSString stringWithFormat:@"world"]; auto v = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v); [mmkv0 setString:value forKey:key]; auto v2 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v2); for (int i = 0; i < 10; i++) { NSString * value2 = [NSString stringWithFormat:@"world_%d", i]; [mmkv0 setString:value2 forKey:key]; auto v2 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v2); } int len = 10000; NSMutableString *bigValue = [NSMutableString stringWithFormat:@"🏊🏻®4️⃣🐅_"]; for (int i = 0; i < len; i++) { [bigValue appendString:@"0"]; } [mmkv0 setString:bigValue forKey:key]; auto v3 = [mmkv0 getStringForKey:key]; // NSLog(@"value = %@", v3); if (![bigValue isEqualToString:v3]) { abort(); } [mmkv0 setString:@"OK" forKey:key]; auto v4 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v4); [mmkv0 setInt32:12345 forKey:@"int"]; auto v5 = [mmkv0 getInt32ForKey:key]; NSLog(@"int value = %d", v5); [mmkv0 removeValueForKey:@"int"]; } { NSString *crypt = [NSString stringWithFormat:@"fastest"]; auto mmkv0 = [MMKV mmkvWithID:@"onlyOneKeyCryptTest" cryptKey:[crypt dataUsingEncoding:NSUTF8StringEncoding] mode:MMKVSingleProcess]; NSString *key = [NSString stringWithFormat:@"hello"]; NSString *value = [NSString stringWithFormat:@"cryptworld"]; auto v = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v); [mmkv0 setString:value forKey:key]; auto v2 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v2); for (int i = 0; i < 10; i++) { NSString * value2 = [NSString stringWithFormat:@"cryptworld_%d", i]; [mmkv0 setString:value2 forKey:key]; auto v2 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v2); } } } - (void) overrideTest { { auto mmkv0 = [MMKV mmkvWithID:@"overrideTest"]; NSString *key = [NSString stringWithFormat:@"hello"]; NSString *key2 = [NSString stringWithFormat:@"hello2"]; NSString *value = [NSString stringWithFormat:@"world"]; [mmkv0 setString:value forKey:key]; auto v2 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v2); [mmkv0 removeValueForKey:key]; [mmkv0 setString:value forKey:key2]; v2 = [mmkv0 getStringForKey:key2]; NSLog(@"value = %@", v2); [mmkv0 removeValueForKey:key2]; int len = 10000; NSMutableString *bigValue = [NSMutableString stringWithFormat:@"🏊🏻®4️⃣🐅_"]; for (int i = 0; i < len; i++) { [bigValue appendString:@"0"]; } [mmkv0 setString:bigValue forKey:key]; auto v3 = [mmkv0 getStringForKey:key]; // NSLog(@"value = %@", v3); if (![bigValue isEqualToString:v3]) { abort(); } // rewrite [mmkv0 setString:@"OK" forKey:key]; auto v4 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v4); [mmkv0 setInt32:12345 forKey:@"int"]; auto v5 = [mmkv0 getInt32ForKey:key]; NSLog(@"int value = %d", v5); [mmkv0 removeValueForKey:@"int"]; [mmkv0 clearAll]; } { NSString *crypt = [NSString stringWithFormat:@"fastestCrypt"]; auto mmkv0 = [MMKV mmkvWithID:@"overrideCryptTest" cryptKey:[crypt dataUsingEncoding:NSUTF8StringEncoding] mode:MMKVSingleProcess]; // [mmkv0 enableCompareBeforeSet]; NSString *key = [NSString stringWithFormat:@"hello"]; NSString *key2 = [NSString stringWithFormat:@"hello2"]; NSString *value = [NSString stringWithFormat:@"cryptworld"]; [mmkv0 setString:value forKey:key]; auto v2 = [mmkv0 getStringForKey:key]; NSLog(@"value = %@", v2); [mmkv0 removeValueForKey:key]; [mmkv0 setString:value forKey:key2]; v2 = [mmkv0 getStringForKey:key2]; NSLog(@"value = %@", v2); [mmkv0 removeValueForKey:key2]; [mmkv0 clearAll]; } } - (void)expectedCapacityTest { int len = 10000; NSString *value = [NSString stringWithFormat:@"🏊🏻®4️⃣🐅_"]; for (int i = 0; i < len; i++) { value = [value stringByAppendingString:@"0"]; } NSLog(@"value size = %ld", [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); NSString *key = [NSString stringWithFormat:@"key0"]; // if we know exactly the sizes of key and value, set expectedCapacity for performance improvement size_t expectedSize = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; auto mmkv0 = [MMKV mmkvWithID:@"expectedCapacityTest0" expectedCapacity:expectedSize]; // 0 times expand [mmkv0 setString:value forKey:key]; int count = 10; expectedSize *= count; auto mmkv1 = [MMKV mmkvWithID:@"expectedCapacityTest1" expectedCapacity:expectedSize]; for (int i = 0; i < count; i++) { // 0 times expand [mmkv1 setString:value forKey:[NSString stringWithFormat:@"key%d", i]]; } } - (void) testCompareBeforeSet { auto mmkv = [MMKV mmkvWithID:@"testCompareBeforeSet"]; [mmkv enableCompareBeforeSet]; [mmkv setBool:true forKey:@"extra"]; { NSString *key = @"int64"; int64_t v = 123456L; [mmkv setInt64:v forKey:key]; long actualSize = [mmkv actualSize]; NSLog(@"testCompareBeforeSet actualSize = %ld", actualSize); NSLog(@"testCompareBeforeSet v = %lld", [mmkv getInt64ForKey:key]); [mmkv setInt64:v forKey:key]; long actualSize2 = [mmkv actualSize]; NSLog(@"testCompareBeforeSet actualSize = %ld", actualSize2); if (actualSize != actualSize2) { abort(); } [mmkv setInt64:v << 1 forKey:key]; NSLog(@"testCompareBeforeSet actualSize = %ld", [mmkv actualSize]); NSLog(@"testCompareBeforeSet v = %lld", [mmkv getInt64ForKey:key]); } { NSString *key = @"string"; NSString *v = [NSString stringWithFormat:@"w012A🏊🏻good"]; [mmkv setString:v forKey:key]; long actualSize = [mmkv actualSize]; NSLog(@"testCompareBeforeSet actualSize = %ld", actualSize); NSLog(@"testCompareBeforeSet v = %@", [mmkv getStringForKey:key]); [mmkv setString:v forKey:key]; long actualSize2 = [mmkv actualSize]; NSLog(@"testCompareBeforeSet actualSize = %ld", actualSize2); if (actualSize != actualSize2) { abort(); } [mmkv setString:@"another string" forKey:key]; NSLog(@"testCompareBeforeSet actualSize = %ld", [mmkv actualSize]); NSLog(@"testCompareBeforeSet v = %@", [mmkv getStringForKey:key]); } } - (void)funcionalTest:(BOOL)decodeOnly { MMKV *mmkv = [MMKV defaultMMKV]; if (!decodeOnly) { [mmkv setBool:YES forKey:@"bool"]; } NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]); if (!decodeOnly) { [mmkv setInt32:-1024 forKey:@"int32"]; } NSLog(@"int32:%d", [mmkv getInt32ForKey:@"int32"]); if (!decodeOnly) { [mmkv setUInt32:std::numeric_limits::max() forKey:@"uint32"]; } NSLog(@"uint32:%u", [mmkv getUInt32ForKey:@"uint32"]); if (!decodeOnly) { [mmkv setInt64:std::numeric_limits::min() forKey:@"int64"]; } NSLog(@"int64:%lld", [mmkv getInt64ForKey:@"int64"]); if (!decodeOnly) { [mmkv setUInt64:std::numeric_limits::max() forKey:@"uint64"]; } NSLog(@"uint64:%llu", [mmkv getInt64ForKey:@"uint64"]); if (!decodeOnly) { [mmkv setFloat:-3.1415926 forKey:@"float"]; } NSLog(@"float:%f", [mmkv getFloatForKey:@"float"]); if (!decodeOnly) { [mmkv setDouble:std::numeric_limits::max() forKey:@"double"]; } NSLog(@"double:%f", [mmkv getDoubleForKey:@"double"]); if (!decodeOnly) { [mmkv setString:@"hello, mmkv" forKey:@"string"]; } NSLog(@"string:%@", [mmkv getStringForKey:@"string"]); if (!decodeOnly) { [mmkv setDate:[NSDate date] forKey:@"date"]; } NSLog(@"date:%@", [mmkv getDateForKey:@"date"]); if (!decodeOnly) { [mmkv setObject:[@"hello, mmkv again and again" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"data"]; } NSData *data = [mmkv getObjectOfClass:NSData.class forKey:@"data"]; NSLog(@"data:%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); if (!decodeOnly) { [mmkv removeValueForKey:@"bool"]; NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]); } } - (void)setRepresentedObject:(id)representedObject { [super setRepresentedObject:representedObject]; // Update the view, if already loaded. } - (void)testNeedLoadFromFile { auto mmkv = [MMKV mmkvWithID:@"testNeedLoadFromFile"]; [mmkv clearMemoryCache]; // or may be triggered by Memory Warning [mmkv clearAll]; NSAssert([mmkv setString:@"value" forKey:@"key"], @"Fail to save"); } @end ================================================ FILE: iOS/MMKVDemo/MMKVMacDemo/main.m ================================================ // // main.m // MMKVMacDemo // // Created by Ling Guo on 2018/9/27. // Copyright © 2018 Lingol. All rights reserved. // #import #import int main(int argc, const char *argv[]) { [MMKV initializeMMKV:nil]; return NSApplicationMain(argc, argv); } ================================================ FILE: iOS/MMKVDemo/MMKVTodayExtensionDemo/Base.lproj/MainInterface.storyboard ================================================ ================================================ FILE: iOS/MMKVDemo/MMKVTodayExtensionDemo/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName MMKVTodayExtensionDemo CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 NSExtension NSExtensionMainStoryboard MainInterface NSExtensionPointIdentifier com.apple.widget-extension ================================================ FILE: iOS/MMKVDemo/MMKVTodayExtensionDemo/MMKVTodayExtensionDemo.entitlements ================================================ com.apple.security.application-groups group.tencent.mmkv ================================================ FILE: iOS/MMKVDemo/MMKVTodayExtensionDemo/TodayViewController.h ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2019 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. */ #import @interface TodayViewController : UIViewController @end ================================================ FILE: iOS/MMKVDemo/MMKVTodayExtensionDemo/TodayViewController.m ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2019 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. */ #import "TodayViewController.h" #import #import @interface TodayViewController () @property (nonatomic) IBOutlet UILabel *m_label; @end @implementation TodayViewController { NSData *m_encryptionKey; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.tencent.mmkv"].path; [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogInfo]; m_encryptionKey = [@"multi_process" dataUsingEncoding:NSUTF8StringEncoding]; // [self testMultiProcess]; } - (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler { // Perform any setup necessary in order to update the view. // If an error is encountered, use NCUpdateResultFailed // If there's no update required, use NCUpdateResultNoData // If there's an update, use NCUpdateResultNewData MMKV *mmkv = [MMKV mmkvWithID:@"multi_process" cryptKey:m_encryptionKey mode:MMKVMultiProcess]; NSString *newContent = [mmkv getStringForKey:@"content"]; _m_label.text = newContent; completionHandler(NCUpdateResultNewData); } - (void)testMultiProcess { MMKV *mmkv = [MMKV mmkvWithID:@"multi_process" cryptKey:m_encryptionKey mode:MMKVMultiProcess]; [mmkv setBool:YES forKey:@"bool"]; NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]); [mmkv setInt32:-1024 forKey:@"int32"]; NSLog(@"int32:%d", [mmkv getInt32ForKey:@"int32"]); [mmkv setUInt32:UINT32_MAX forKey:@"uint32"]; NSLog(@"uint32:%u", [mmkv getUInt32ForKey:@"uint32"]); [mmkv setInt64:INT64_MIN forKey:@"int64"]; NSLog(@"int64:%lld", [mmkv getInt64ForKey:@"int64"]); [mmkv setUInt64:UINT64_MAX forKey:@"uint64"]; NSLog(@"uint64:%llu", [mmkv getInt64ForKey:@"uint64"]); [mmkv setFloat:-3.1415926 forKey:@"float"]; NSLog(@"float:%f", [mmkv getFloatForKey:@"float"]); [mmkv setDouble:DBL_MAX forKey:@"double"]; NSLog(@"double:%f", [mmkv getDoubleForKey:@"double"]); [mmkv setString:@"hello, mmkv" forKey:@"string"]; NSLog(@"string:%@", [mmkv getStringForKey:@"string"]); [mmkv setObject:nil forKey:@"string"]; NSLog(@"string after set nil:%@, containsKey:%d", [mmkv getObjectOfClass:NSString.class forKey:@"string"], [mmkv containsKey:@"string"]); [mmkv setDate:[NSDate date] forKey:@"date"]; NSLog(@"date:%@", [mmkv getDateForKey:@"date"]); [mmkv setData:[@"hello, mmkv again and again" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"data"]; NSData *data = [mmkv getDataForKey:@"data"]; NSLog(@"data:%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); NSLog(@"data length:%zu, value size consumption:%zu", data.length, [mmkv getValueSizeForKey:@"data" actualSize:NO]); [mmkv removeValueForKey:@"bool"]; NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]); [mmkv removeValuesForKeys:@[ @"int32", @"uint64" ]]; NSLog(@"allKeys %@", [mmkv allKeys]); [mmkv close]; } @end ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "vision", "scale" : "2x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/Assets.xcassets/AppIcon.solidimagestack/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 }, "layers" : [ { "filename" : "Front.solidimagestacklayer" }, { "filename" : "Middle.solidimagestacklayer" }, { "filename" : "Back.solidimagestacklayer" } ] } ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "vision", "scale" : "2x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "vision", "scale" : "2x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/ContentView.swift ================================================ // // ContentView.swift // MMKVVisionDemo // // Created by Cyril Bonaccini on 15/02/2024. // Copyright © 2024 Lingol. All rights reserved. // import SwiftUI import MMKV struct ContentView: View { var body: some View { VStack { Text("Hello, world!") Button("Press Me") { guard let mmkv = MMKV(mmapID: "testSwift", mode: MMKVMode.singleProcess) else { return } mmkv.set(true, forKey: "bool") print("Swift: bool = \(mmkv.bool(forKey: "bool"))") mmkv.set(Int32(-1024), forKey: "int32") print("Swift: int32 = \(mmkv.int32(forKey: "int32"))") mmkv.set(UInt32.max, forKey: "uint32") print("Swift: uint32 = \(mmkv.uint32(forKey: "uint32"))") mmkv.set(Int64.min, forKey: "int64") print("Swift: int64 = \(mmkv.int64(forKey: "int64"))") mmkv.set(UInt64.max, forKey: "uint64") print("Swift: uint64 = \(mmkv.uint64(forKey: "uint64"))") mmkv.set(Float(-3.1415926), forKey: "float") print("Swift: float = \(mmkv.float(forKey: "float"))") mmkv.set(Double.infinity, forKey: "double") print("Swift: double = \(mmkv.double(forKey: "double"))") mmkv.set("Hello from Swift", forKey: "string") print("Swift: string = \(mmkv.string(forKey: "string") ?? "")") mmkv.set(NSDate(), forKey: "date") let date = mmkv.date(forKey: "date") print("Swift: date = \(date?.description(with: .current) ?? "null")") mmkv.set("Hello from Swift".data(using: .utf8) ?? Data(), forKey: "data") let data = mmkv.data(forKey: "data") let str = String(data: data ?? Data(), encoding: .utf8) ?? "" print("Swift: data = \(str)") let arr = [1, 0, 2, 4] if let objArr = arr as NSArray? { mmkv.set(objArr, forKey:"array") let result = mmkv.object(of: NSArray.self, forKey: "array"); print("Swift: array = \(result as! NSArray)") } mmkv.removeValue(forKey: "bool") print("Swift: after delete bool = \(mmkv.bool(forKey: "bool"))") } } .padding() } } #Preview(windowStyle: .automatic) { ContentView() } ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/Info.plist ================================================ UIApplicationSceneManifest UIApplicationPreferredDefaultSceneSessionRole UIWindowSceneSessionRoleApplication UIApplicationSupportsMultipleScenes UISceneConfigurations ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/MMKVVisionDemoApp.swift ================================================ // // MMKVVisionDemoApp.swift // MMKVVisionDemo // // Created by Cyril Bonaccini on 15/02/2024. // Copyright © 2024 Lingol. All rights reserved. // import SwiftUI import MMKV @main struct MMKVVisionDemoApp: App { init() { MMKV.initialize(rootDir: nil) } var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: iOS/MMKVDemo/MMKVVisionDemo/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "watch", "role" : "notificationCenter", "scale" : "2x", "size" : "24x24", "subtype" : "38mm" }, { "idiom" : "watch", "role" : "notificationCenter", "scale" : "2x", "size" : "27.5x27.5", "subtype" : "42mm" }, { "idiom" : "watch", "role" : "companionSettings", "scale" : "2x", "size" : "29x29" }, { "idiom" : "watch", "role" : "companionSettings", "scale" : "3x", "size" : "29x29" }, { "idiom" : "watch", "role" : "appLauncher", "scale" : "2x", "size" : "40x40", "subtype" : "38mm" }, { "idiom" : "watch", "role" : "appLauncher", "scale" : "2x", "size" : "44x44", "subtype" : "40mm" }, { "idiom" : "watch", "role" : "appLauncher", "scale" : "2x", "size" : "50x50", "subtype" : "44mm" }, { "idiom" : "watch", "role" : "quickLook", "scale" : "2x", "size" : "86x86", "subtype" : "38mm" }, { "idiom" : "watch", "role" : "quickLook", "scale" : "2x", "size" : "98x98", "subtype" : "42mm" }, { "idiom" : "watch", "role" : "quickLook", "scale" : "2x", "size" : "108x108", "subtype" : "44mm" }, { "idiom" : "watch-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp/Base.lproj/Interface.storyboard ================================================ ================================================ FILE: iOS/MMKVDemo/WatchApp/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName MMKVDemo CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown WKCompanionAppBundleIdentifier com.tencent.mmkvdemo WKWatchKitApp ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "watch", "scale" : "2x", "screen-width" : "<=145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">161" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">183" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Assets.xcassets/Complication.complicationset/Contents.json ================================================ { "assets" : [ { "filename" : "Circular.imageset", "idiom" : "watch", "role" : "circular" }, { "filename" : "Extra Large.imageset", "idiom" : "watch", "role" : "extra-large" }, { "filename" : "Graphic Bezel.imageset", "idiom" : "watch", "role" : "graphic-bezel" }, { "filename" : "Graphic Circular.imageset", "idiom" : "watch", "role" : "graphic-circular" }, { "filename" : "Graphic Corner.imageset", "idiom" : "watch", "role" : "graphic-corner" }, { "filename" : "Graphic Extra Large.imageset", "idiom" : "watch", "role" : "graphic-extra-large" }, { "filename" : "Graphic Large Rectangular.imageset", "idiom" : "watch", "role" : "graphic-large-rectangular" }, { "filename" : "Modular.imageset", "idiom" : "watch", "role" : "modular" }, { "filename" : "Utilitarian.imageset", "idiom" : "watch", "role" : "utilitarian" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "watch", "scale" : "2x", "screen-width" : "<=145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">161" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">183" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "watch", "scale" : "2x", "screen-width" : "<=145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">161" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">183" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "watch", "scale" : "2x", "screen-width" : "<=145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">161" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">183" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "watch", "scale" : "2x", "screen-width" : "<=145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">161" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">183" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "watch", "scale" : "2x" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : "<=145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">183" } ], "info" : { "author" : "xcode", "version" : 1 }, "properties" : { "auto-scaling" : "auto" } } ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "watch", "scale" : "2x", "screen-width" : "<=145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">161" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">183" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "watch", "scale" : "2x", "screen-width" : "<=145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">161" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">183" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "watch", "scale" : "2x", "screen-width" : "<=145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">161" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">145" }, { "idiom" : "watch", "scale" : "2x", "screen-width" : ">183" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/ExtensionDelegate.h ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2020 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. */ #import @interface ExtensionDelegate : NSObject @end ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/ExtensionDelegate.mm ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2020 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. */ #import "ExtensionDelegate.h" #import @implementation ExtensionDelegate - (void)applicationDidFinishLaunching { // Perform any final initialization of your application. [MMKV initializeMMKV:nil]; [self funcionalTest]; } - (void)funcionalTest { auto path = [MMKV mmkvBasePath]; path = [path stringByDeletingLastPathComponent]; path = [path stringByAppendingPathComponent:@"mmkv_2"]; auto mmkv = [MMKV mmkvWithID:@"test/case1" rootPath:path]; [mmkv setBool:YES forKey:@"bool"]; NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]); [mmkv setInt32:-1024 forKey:@"int32"]; NSLog(@"int32:%d", [mmkv getInt32ForKey:@"int32"]); [mmkv setUInt32:std::numeric_limits::max() forKey:@"uint32"]; NSLog(@"uint32:%u", [mmkv getUInt32ForKey:@"uint32"]); [mmkv setInt64:std::numeric_limits::min() forKey:@"int64"]; NSLog(@"int64:%lld", [mmkv getInt64ForKey:@"int64"]); [mmkv setUInt64:std::numeric_limits::max() forKey:@"uint64"]; NSLog(@"uint64:%llu", [mmkv getInt64ForKey:@"uint64"]); [mmkv setFloat:-3.1415926 forKey:@"float"]; NSLog(@"float:%f", [mmkv getFloatForKey:@"float"]); [mmkv setDouble:std::numeric_limits::max() forKey:@"double"]; NSLog(@"double:%f", [mmkv getDoubleForKey:@"double"]); [mmkv setString:@"hello, mmkv" forKey:@"string"]; NSLog(@"string:%@", [mmkv getStringForKey:@"string"]); [mmkv setObject:nil forKey:@"string"]; NSLog(@"string after set nil:%@, containsKey:%d", [mmkv getObjectOfClass:NSString.class forKey:@"string"], [mmkv containsKey:@"string"]); [mmkv setDate:[NSDate date] forKey:@"date"]; NSLog(@"date:%@", [mmkv getDateForKey:@"date"]); [mmkv setData:[@"hello, mmkv again and again" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"data"]; NSData *data = [mmkv getDataForKey:@"data"]; NSLog(@"data:%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); NSLog(@"data length:%zu, value size consumption:%zu", data.length, [mmkv getValueSizeForKey:@"data" actualSize:NO]); [mmkv setObject:[NSData data] forKey:@"data"]; NSLog(@"data after set empty data:%@, containsKey:%d", [mmkv getObjectOfClass:NSData.class forKey:@"data"], [mmkv containsKey:@"data"]); [mmkv removeValueForKey:@"bool"]; NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]); [mmkv removeValuesForKeys:@[ @"int32", @"uint64" ]]; NSLog(@"allKeys %@", [mmkv allKeys]); [mmkv close]; } - (void)applicationDidBecomeActive { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - (void)applicationWillResignActive { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, etc. } - (void)handleBackgroundTasks:(NSSet *)backgroundTasks { // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one. for (WKRefreshBackgroundTask * task in backgroundTasks) { // Check the Class of each task to decide how to process it if ([task isKindOfClass:[WKApplicationRefreshBackgroundTask class]]) { // Be sure to complete the background task once you’re done. WKApplicationRefreshBackgroundTask *backgroundTask = (WKApplicationRefreshBackgroundTask*)task; [backgroundTask setTaskCompletedWithSnapshot:NO]; } else if ([task isKindOfClass:[WKSnapshotRefreshBackgroundTask class]]) { // Snapshot tasks have a unique completion call, make sure to set your expiration date WKSnapshotRefreshBackgroundTask *snapshotTask = (WKSnapshotRefreshBackgroundTask*)task; [snapshotTask setTaskCompletedWithDefaultStateRestored:YES estimatedSnapshotExpiration:[NSDate distantFuture] userInfo:nil]; } else if ([task isKindOfClass:[WKWatchConnectivityRefreshBackgroundTask class]]) { // Be sure to complete the background task once you’re done. WKWatchConnectivityRefreshBackgroundTask *backgroundTask = (WKWatchConnectivityRefreshBackgroundTask*)task; [backgroundTask setTaskCompletedWithSnapshot:NO]; } else if ([task isKindOfClass:[WKURLSessionRefreshBackgroundTask class]]) { // Be sure to complete the background task once you’re done. WKURLSessionRefreshBackgroundTask *backgroundTask = (WKURLSessionRefreshBackgroundTask*)task; [backgroundTask setTaskCompletedWithSnapshot:NO]; } else if ([task isKindOfClass:[WKRelevantShortcutRefreshBackgroundTask class]]) { // Be sure to complete the relevant-shortcut task once you’re done. WKRelevantShortcutRefreshBackgroundTask *relevantShortcutTask = (WKRelevantShortcutRefreshBackgroundTask*)task; [relevantShortcutTask setTaskCompletedWithSnapshot:NO]; } else if ([task isKindOfClass:[WKIntentDidRunRefreshBackgroundTask class]]) { // Be sure to complete the intent-did-run task once you’re done. WKIntentDidRunRefreshBackgroundTask *intentDidRunTask = (WKIntentDidRunRefreshBackgroundTask*)task; [intentDidRunTask setTaskCompletedWithSnapshot:NO]; } else { // make sure to complete unhandled task types [task setTaskCompletedWithSnapshot:NO]; } } } @end ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName WatchApp Extension CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 CFBundleVersion 1 NSExtension NSExtensionAttributes WKAppBundleIdentifier com.tencent.mmkvdemo.watchkitapp NSExtensionPointIdentifier com.apple.watchkit WKExtensionDelegateClassName ExtensionDelegate ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/InterfaceController.h ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2020 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. */ #import #import @interface InterfaceController : WKInterfaceController @end ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/InterfaceController.m ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2020 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. */ #import "InterfaceController.h" @interface InterfaceController () @end @implementation InterfaceController - (void)awakeWithContext:(id)context { [super awakeWithContext:context]; // Configure interface objects here. } - (void)willActivate { // This method is called when watch view controller is about to be visible to user [super willActivate]; } - (void)didDeactivate { // This method is called when watch view controller is no longer visible [super didDeactivate]; } @end ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/NotificationController.h ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2020 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. */ #import #import @interface NotificationController : WKUserNotificationInterfaceController @end ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/NotificationController.m ================================================ /* * Tencent is pleased to support the open source community by making * MMKV available. * * Copyright (C) 2020 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. */ #import "NotificationController.h" @interface NotificationController () @end @implementation NotificationController - (instancetype)init { self = [super init]; if (self){ // Initialize variables here. // Configure interface objects here. } return self; } - (void)willActivate { // This method is called when watch view controller is about to be visible to user [super willActivate]; } - (void)didDeactivate { // This method is called when watch view controller is no longer visible [super didDeactivate]; } - (void)didReceiveNotification:(UNNotification *)notification { // This method is called when a notification needs to be presented. // Implement it if you use a dynamic notification interface. // Populate your dynamic notification interface as quickly as possible. } @end ================================================ FILE: iOS/MMKVDemo/WatchApp Extension/PushNotificationPayload.apns ================================================ { "aps": { "alert": { "body": "Test message", "title": "Optional title", "subtitle": "Optional subtitle" }, "category": "myCategory", "thread-id": "5280" }, "WatchKit Simulator Actions": [ { "title": "First Button", "identifier": "firstButtonAction" } ], "customKey": "Use this file to define a testing payload for your notifications. The aps dictionary specifies the category, alert text and title. The WatchKit Simulator Actions array can provide info for one or more action buttons in addition to the standard Dismiss button. Any other top level keys are custom payload. If you have multiple such JSON files in your project, you'll be able to select them when choosing to debug the notification interface of your Watch App." } ================================================ FILE: iOS/MMKVDemo/kvdemo/AppDelegate.h ================================================ // // AppDelegate.h // kvdemo // // Created by lingol on 2023/6/28. // Copyright © 2023 Lingol. All rights reserved. // #import @interface AppDelegate : UIResponder @end ================================================ FILE: iOS/MMKVDemo/kvdemo/AppDelegate.m ================================================ // // AppDelegate.m // kvdemo // // Created by lingol on 2023/6/28. // Copyright © 2023 Lingol. All rights reserved. // #import "AppDelegate.h" #import @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSString *libraryPath = (NSString *) [paths firstObject]; if ([libraryPath length] > 0) { NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.tencent.mmkv"].path; [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogInfo handler:nil]; NSLog(@"MMKV version: %@", [MMKV version]); } return YES; } #pragma mark - UISceneSession lifecycle - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; } - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } @end ================================================ FILE: iOS/MMKVDemo/kvdemo/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/kvdemo/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/kvdemo/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: iOS/MMKVDemo/kvdemo/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: iOS/MMKVDemo/kvdemo/Base.lproj/Main.storyboard ================================================ ================================================ FILE: iOS/MMKVDemo/kvdemo/Info.plist ================================================ UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneConfigurationName Default Configuration UISceneDelegateClassName SceneDelegate UISceneStoryboardFile Main ================================================ FILE: iOS/MMKVDemo/kvdemo/SceneDelegate.h ================================================ // // SceneDelegate.h // kvdemo // // Created by lingol on 2023/6/28. // Copyright © 2023 Lingol. All rights reserved. // #import @interface SceneDelegate : UIResponder @property (strong, nonatomic) UIWindow * window; @end ================================================ FILE: iOS/MMKVDemo/kvdemo/SceneDelegate.m ================================================ // // SceneDelegate.m // kvdemo // // Created by lingol on 2023/6/28. // Copyright © 2023 Lingol. All rights reserved. // #import "SceneDelegate.h" @interface SceneDelegate () @end @implementation SceneDelegate - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). } - (void)sceneDidDisconnect:(UIScene *)scene { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). } - (void)sceneDidBecomeActive:(UIScene *)scene { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } - (void)sceneWillResignActive:(UIScene *)scene { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } - (void)sceneWillEnterForeground:(UIScene *)scene { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } - (void)sceneDidEnterBackground:(UIScene *)scene { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } @end ================================================ FILE: iOS/MMKVDemo/kvdemo/ViewController.h ================================================ // // ViewController.h // kvdemo // // Created by lingol on 2023/6/28. // Copyright © 2023 Lingol. All rights reserved. // #import @interface ViewController : UIViewController @end ================================================ FILE: iOS/MMKVDemo/kvdemo/ViewController.m ================================================ // // ViewController.m // kvdemo // // Created by lingol on 2023/6/28. // Copyright © 2023 Lingol. All rights reserved. // #import "ViewController.h" #import @interface ViewController () @property(weak, nonatomic) IBOutlet UILabel *m_label; @end @implementation ViewController { MMKV *m_kv; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. NSData *encryptionKey = [@"multi_process" dataUsingEncoding:NSUTF8StringEncoding]; m_kv = [MMKV mmkvWithID:@"multi_process" cryptKey:encryptionKey mode:MMKVMultiProcess]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateTodayContent) name:UIApplicationDidBecomeActiveNotification object:nil]; } - (void)updateTodayContent { NSString *newContent = [m_kv getStringForKey:@"content"]; _m_label.text = newContent; } @end ================================================ FILE: iOS/MMKVDemo/kvdemo/kvdemo.entitlements ================================================ com.apple.security.application-groups group.tencent.mmkv ================================================ FILE: iOS/MMKVDemo/kvdemo/main.m ================================================ // // main.m // kvdemo // // Created by lingol on 2023/6/28. // Copyright © 2023 Lingol. All rights reserved. // #import #import "AppDelegate.h" int main(int argc, char * argv[]) { NSString * appDelegateClassName; @autoreleasepool { // Setup code that might create autoreleased objects goes here. appDelegateClassName = NSStringFromClass([AppDelegate class]); } return UIApplicationMain(argc, argv, nil, appDelegateClassName); }