Skip to content

Commit 6ad828c

Browse files
authored
Merge pull request #1673 from Tencent/dev
for v2.4.1
2 parents 5028f5d + aba19d4 commit 6ad828c

160 files changed

Lines changed: 8189 additions & 421 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,5 @@ output/
6060

6161
#swiftpm
6262
.swiftpm
63+
64+
.claude/

Android/MMKV/gradle.properties

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ org.gradle.jvmargs=-Xmx1536m
1414
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
1515
# org.gradle.parallel=true
1616

17-
VERSION_NAME_PREFIX=2.4.0
17+
VERSION_NAME_PREFIX=2.4.1
1818
#VERSION_NAME_SUFFIX=-SNAPSHOT
1919
VERSION_NAME_SUFFIX=
2020

2121
RELEASE_REPOSITORY_URL=https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2
22-
SNAPSHOT_REPOSITORY_URL=https://ossrh-staging-api.central.sonatype.com/content/repositories/snapshots
23-
android.defaults.buildfeatures.buildconfig=true
22+
SNAPSHOT_REPOSITORY_URL=https://central.sonatype.com/repository/maven-snapshots/
23+
# The aggregate MavenCentral publication task automatically transfers the
24+
# implicit OSSRH Staging API repository to Central Portal for manual review.
25+
CENTRAL_PORTAL_NAMESPACE=com.tencent
26+
CENTRAL_PORTAL_PUBLISHING_TYPE=user_managed
2427
android.nonTransitiveRClass=false
2528
android.nonFinalResIds=false
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import java.nio.charset.StandardCharsets
2+
import java.util.Base64
3+
4+
/**
5+
* Gradle's built-in maven-publish plugin only performs Maven-style PUT
6+
* requests. The Central OSSRH Staging API therefore cannot know when a
7+
* deployment is complete. Sonatype requires a same-IP POST after the final
8+
* artifact upload to transfer the implicit repository to Central Portal.
9+
*/
10+
11+
def centralPortalNamespace = providers.gradleProperty('CENTRAL_PORTAL_NAMESPACE')
12+
.orElse(rootProject.ext.GROUP)
13+
def centralPortalBaseUrl = providers.gradleProperty('CENTRAL_PORTAL_STAGING_API_URL')
14+
.orElse('https://ossrh-staging-api.central.sonatype.com')
15+
def centralPortalPublishingType = providers.gradleProperty('CENTRAL_PORTAL_PUBLISHING_TYPE')
16+
.orElse('user_managed')
17+
def centralPortalDryRun = providers.gradleProperty('CENTRAL_PORTAL_DRY_RUN')
18+
.map { it.toBoolean() }
19+
.orElse(false)
20+
def centralPortalRemotePublishTasks = tasks.matching {
21+
it.name.startsWith('publish') &&
22+
it.name.endsWith('ToMavenCentralRepository') &&
23+
it.name != 'publishAllPublicationsToMavenCentralRepository'
24+
}
25+
26+
tasks.register('uploadDefaultRepositoryToCentralPortal') {
27+
group = 'publishing'
28+
description = 'Transfers the completed OSSRH Staging API repository to Central Portal.'
29+
30+
onlyIf {
31+
!rootProject.ext.VERSION_NAME.endsWith('-SNAPSHOT') &&
32+
centralPortalRemotePublishTasks.every { it.state.failure == null }
33+
}
34+
35+
doLast {
36+
def username = providers.gradleProperty('REPOSITORY_USERNAME').orNull
37+
def password = providers.gradleProperty('REPOSITORY_PASSWORD').orNull
38+
if (!username || !password) {
39+
throw new GradleException('Missing REPOSITORY_USERNAME or REPOSITORY_PASSWORD.')
40+
}
41+
42+
def namespace = centralPortalNamespace.get()
43+
def publishingType = centralPortalPublishingType.get()
44+
if (!(publishingType in ['user_managed', 'automatic', 'portal_api'])) {
45+
throw new GradleException(
46+
"Invalid CENTRAL_PORTAL_PUBLISHING_TYPE '$publishingType'. " +
47+
'Expected user_managed, automatic, or portal_api.'
48+
)
49+
}
50+
51+
def encodedNamespace = URLEncoder.encode(namespace, StandardCharsets.UTF_8)
52+
def encodedType = URLEncoder.encode(publishingType, StandardCharsets.UTF_8)
53+
def endpoint = "${centralPortalBaseUrl.get()}/manual/upload/defaultRepository/" +
54+
"${encodedNamespace}?publishing_type=${encodedType}"
55+
56+
if (centralPortalDryRun.get()) {
57+
logger.lifecycle("Central Portal dry run: POST $endpoint")
58+
return
59+
}
60+
61+
def bearer = Base64.encoder.encodeToString(
62+
"${username}:${password}".getBytes(StandardCharsets.UTF_8)
63+
)
64+
def connection = new URL(endpoint).openConnection() as HttpURLConnection
65+
connection.requestMethod = 'POST'
66+
connection.setRequestProperty('Authorization', "Bearer $bearer")
67+
connection.setRequestProperty('Accept', 'application/json')
68+
connection.connectTimeout = 30_000
69+
connection.readTimeout = 10 * 60_000
70+
71+
try {
72+
def status = connection.responseCode
73+
def stream = status >= 400 ? connection.errorStream : connection.inputStream
74+
def response = stream != null ? stream.getText(StandardCharsets.UTF_8.name()) : ''
75+
if (status < 200 || status >= 300) {
76+
throw new GradleException(
77+
"Central Portal handoff failed with HTTP $status" +
78+
(response ? ": $response" : '.')
79+
)
80+
}
81+
logger.lifecycle("Transferred '$namespace' to Central Portal ($publishingType).")
82+
if (response) {
83+
logger.info("Central Portal response: $response")
84+
}
85+
} finally {
86+
connection.disconnect()
87+
}
88+
}
89+
}
90+
91+
tasks.matching {
92+
it.name == 'publishAllPublicationsToMavenCentralRepository' || it.name == 'publish'
93+
}.configureEach {
94+
finalizedBy(tasks.named('uploadDefaultRepositoryToCentralPortal'))
95+
}

Android/MMKV/mmkv/build.gradle

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ android {
1313
multipleVariants("DefaultCppRelease") {
1414
includeBuildTypeValues('release')
1515
includeFlavorDimensionAndValues('stl_mode', 'DefaultCpp')
16+
withSourcesJar()
1617
withJavadocJar()
1718
}
1819
multipleVariants("StaticCppRelease") {
1920
includeBuildTypeValues('release')
2021
includeFlavorDimensionAndValues('stl_mode', 'StaticCpp')
22+
withSourcesJar()
2123
withJavadocJar()
2224
}
2325
multipleVariants("SharedCppRelease") {
2426
includeBuildTypeValues('release')
2527
includeFlavorDimensionAndValues('stl_mode', 'SharedCpp')
28+
withSourcesJar()
2629
withJavadocJar()
2730
}
2831
}
@@ -103,6 +106,7 @@ android {
103106
}
104107
}
105108
buildFeatures {
109+
buildConfig true
106110
prefab true
107111
prefabPublishing true
108112
aidl true
@@ -114,6 +118,16 @@ android {
114118
}
115119
}
116120

121+
afterEvaluate {
122+
tasks.matching { task ->
123+
task.name.startsWith("source") && task.name.endsWith("ReleaseJar")
124+
}.configureEach {
125+
// AIDL generates ParcelableMMKV.java from the checked-in source, so
126+
// the Android sources artifact otherwise sees the same path twice.
127+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
128+
}
129+
}
130+
117131
afterEvaluate {
118132
android.libraryVariants.configureEach { variant ->
119133
if (variant.buildType.name == "release") {
@@ -241,6 +255,8 @@ afterEvaluate {
241255
}
242256
}
243257

258+
apply from: rootProject.file('gradle/central-portal.gradle')
259+
244260
configurations {
245261
javadocDeps
246262
}

Android/MMKV/mmkv/src/androidTest/java/com/tencent/mmkv/MMKVTest.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,34 @@ public void testBytes() {
175175
assertArrayEquals(value, bytes);
176176
}
177177

178+
@Test
179+
public void testBufferAndFeatureStateAPIs() {
180+
MMKV kv = MMKV.mmkvWithID("bufferAndFeatureStateTest");
181+
kv.clearAll();
182+
try {
183+
byte[] bytes = {'m', 'm', 'k', 'v'};
184+
assertTrue(kv.encode("buffer", bytes));
185+
186+
byte[] output = new byte[bytes.length];
187+
assertEquals(bytes.length, kv.writeValueToBuffer("buffer", output));
188+
assertArrayEquals(bytes, output);
189+
190+
assertFalse(kv.isExpirationEnabled());
191+
assertTrue(kv.enableAutoKeyExpire(MMKV.ExpireNever));
192+
assertTrue(kv.isExpirationEnabled());
193+
assertTrue(kv.disableAutoKeyExpire());
194+
assertFalse(kv.isExpirationEnabled());
195+
196+
assertFalse(kv.isCompareBeforeSetEnabled());
197+
kv.enableCompareBeforeSet();
198+
assertTrue(kv.isCompareBeforeSetEnabled());
199+
kv.disableCompareBeforeSet();
200+
assertFalse(kv.isCompareBeforeSetEnabled());
201+
} finally {
202+
kv.clearAll();
203+
}
204+
}
205+
178206
@Test
179207
public void testRemove() {
180208
boolean ret = mmkv.encode("bool_1", true);
@@ -232,6 +260,46 @@ public void testRemove() {
232260
assertArrayEquals(bytes, byteValue);
233261
}
234262

263+
@Test
264+
public void testExpirationOverflow() {
265+
MMKV kv = MMKV.mmkvWithID("expirationOverflowTest");
266+
kv.clearAll();
267+
// JNI casts int to uint32_t, so -1 exercises UINT32_MAX.
268+
assertTrue(kv.enableAutoKeyExpire(-1));
269+
270+
assertTrue(kv.encode("expiration_overflow_auto", true));
271+
assertTrue(kv.decodeBool("expiration_overflow_auto"));
272+
273+
assertTrue(kv.encode("expiration_overflow_manual", "manual", -1));
274+
assertEquals("manual", kv.decodeString("expiration_overflow_manual"));
275+
276+
byte[] bytes = {'d', 'a', 't', 'a'};
277+
assertTrue(kv.encode("expiration_overflow_bytes", bytes, -1));
278+
assertArrayEquals(bytes, kv.decodeBytes("expiration_overflow_bytes"));
279+
280+
assertEquals(3, kv.countNonExpiredKeys());
281+
kv.clearAll();
282+
}
283+
284+
@Test
285+
public void testCloseIsIdempotentAndAllowsReopen() {
286+
String mmapID = "closeLifecycleTest";
287+
MMKV kv = MMKV.mmkvWithID(mmapID);
288+
assertTrue(kv.encode("value", true));
289+
290+
kv.close();
291+
kv.close();
292+
assertFalse(kv.encode("afterClose", true));
293+
// Two live wrappers backed by the same native instance are outside the
294+
// supported close() contract. Discard the closed wrapper before reopen.
295+
kv = null;
296+
297+
MMKV reopened = MMKV.mmkvWithID(mmapID);
298+
assertTrue(reopened.decodeBool("value"));
299+
reopened.clearAll();
300+
reopened.close();
301+
}
302+
235303
@Test
236304
public void testIPCUpdateInt() {
237305
MMKV mmkv = MMKV.mmkvWithID(MMKVTestService.SharedMMKVID, MMKV.MULTI_PROCESS_MODE);

Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class JNIMMKVHandler : public mmkv::MMKVHandler {
7575
} else {
7676
mmkv::mmkvLog(level, file, line, function, message);
7777
}
78+
} else {
79+
mmkv::mmkvLog(level, file, line, function, message);
7880
}
7981
}
8082

@@ -107,6 +109,9 @@ class JNIMMKVHandler : public mmkv::MMKVHandler {
107109
}
108110

109111
void onContentChangedByOuterProcess(const std::string &mmapID) override {
112+
if (!m_wantsContentChange) {
113+
return;
114+
}
110115
auto currentEnv = getCurrentEnv();
111116
if (currentEnv && g_callbackOnContentChange) {
112117
jstring str = string2jstring(currentEnv, mmapID);
@@ -971,11 +976,10 @@ MMKV_JNI void trim(JNIEnv *env, jobject instance) {
971976
}
972977
}
973978

974-
MMKV_JNI void close(JNIEnv *env, jobject instance) {
975-
MMKV *kv = getMMKV(env, instance);
979+
MMKV_JNI void close(JNIEnv *env, jclass, jlong handle) {
980+
MMKV *kv = reinterpret_cast<MMKV *>(handle);
976981
if (kv) {
977982
kv->close();
978-
env->SetLongField(instance, g_fileID, 0);
979983
}
980984
}
981985

@@ -1021,6 +1025,14 @@ MMKV_JNI void destroyNB(JNIEnv *env, jobject instance, jlong pointer, jint size)
10211025
free(reinterpret_cast<void *>(pointer));
10221026
}
10231027

1028+
MMKV_JNI void readNB(JNIEnv *env, jobject instance, jlong pointer, jbyteArray buffer, jint size) {
1029+
if (pointer && buffer && size > 0) {
1030+
auto arraySize = env->GetArrayLength(buffer);
1031+
auto copySize = size < arraySize ? size : arraySize;
1032+
env->SetByteArrayRegion(buffer, 0, copySize, reinterpret_cast<jbyte *>(pointer));
1033+
}
1034+
}
1035+
10241036
MMKV_JNI jint writeValueToNB(JNIEnv *env, jobject instance, jlong handle, jstring oKey, jlong pointer, jint size) {
10251037
MMKV *kv = reinterpret_cast<MMKV *>(handle);
10261038
if (kv && oKey) {
@@ -1227,7 +1239,7 @@ static JNINativeMethod g_methods[] = {
12271239
{"removeValuesForKeys", "([Ljava/lang/String;)V", (void *) mmkv::removeValuesForKeys},
12281240
{"clearAll", "()V", (void *) mmkv::clearAll},
12291241
{"trim", "()V", (void *) mmkv::trim},
1230-
{"close", "()V", (void *) mmkv::close},
1242+
{"close", "(J)V", (void *) mmkv::close},
12311243
{"clearMemoryCache", "()V", (void *) mmkv::clearMemoryCache},
12321244
{"sync", "(Z)V", (void *) mmkv::sync},
12331245
{"isFileValid", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *) mmkv::isFileValid},
@@ -1272,6 +1284,7 @@ static JNINativeMethod g_methods[] = {
12721284
{"setCallbackHandler", "(ZZJ)V", (void *) mmkv::setCallbackHandler},
12731285
{"createNB", "(I)J", (void *) mmkv::createNB},
12741286
{"destroyNB", "(JI)V", (void *) mmkv::destroyNB},
1287+
{"readNB", "(J[BI)V", (void *) mmkv::readNB},
12751288
{"writeValueToNB", "(JLjava/lang/String;JI)I", (void *) mmkv::writeValueToNB},
12761289
{"setWantsContentChangeNotify", "(Z)V", (void *) mmkv::setWantsContentChangeNotify},
12771290
{"checkContentChangedByOuterProcess", "()V", (void *) mmkv::checkContentChanged},

0 commit comments

Comments
 (0)