Refactor EmojiCompat.init(Context)
Added benchmark to run through the entire code path in
EmojiCompat.init(Context).
Change:
benchmark: 1,032 ns 9 allocs CachedEmojiCompatInitBenchmark.actualEmojiCompatContextInit_fromQuickFactory
benchmark: 1,121 ns 9 allocs CachedEmojiCompatInitBenchmark.actualEmojiCompatContextInit_fromQuickFactory_newVersion
Benchmark has high variance and it is unclear if this caused a change.
Even if it did bump, 70ns is acceptable.
No change to early-exit benchmarks.
Test: New benchmark
Bug: b/178035684
Change-Id: I1b43acf2eac5632375f534fa68b2496302d581fd
diff --git a/emoji2/emoji2-benchmark/build.gradle b/emoji2/emoji2-benchmark/build.gradle
index b632e87..c3dd0be 100644
--- a/emoji2/emoji2-benchmark/build.gradle
+++ b/emoji2/emoji2-benchmark/build.gradle
@@ -34,6 +34,9 @@
androidTestImplementation(ANDROIDX_TEST_CORE)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(ANDROIDX_TEST_RULES)
+ androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+ androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+ androidTestImplementation project(':internal-testutils-runtime')
androidTestImplementation(KOTLIN_STDLIB)
}
diff --git a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/CachedEmojiCompatInitBenchmark.kt b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/CachedEmojiCompatInitBenchmark.kt
index bfe89c1..ea969a4 100644
--- a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/CachedEmojiCompatInitBenchmark.kt
+++ b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/CachedEmojiCompatInitBenchmark.kt
@@ -19,10 +19,12 @@
import android.content.Context
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
+import androidx.emoji2.text.DefaultEmojiCompatConfig
import androidx.emoji2.text.EmojiCompat
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import org.junit.Assert.assertNotNull
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,4 +49,42 @@
EmojiCompat.init(context)
}
}
+
+ @Test
+ fun cachedEmojiCompatInit_returningNonNull() {
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ val config = NoFontTestEmojiConfig.emptyConfig()
+ .setMetadataLoadStrategy(EmojiCompat.LOAD_STRATEGY_MANUAL)
+ EmojiCompat.reset(config)
+ EmojiCompat.skipDefaultConfigurationLookup(true)
+ benchmarkRule.measureRepeated {
+ EmojiCompat.init(context)
+ }
+ }
+
+ @Test
+ fun actualEmojiCompatContextInit_fromQuickFactory() {
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ val config = NoFontTestEmojiConfig.emptyConfig()
+ .setMetadataLoadStrategy(EmojiCompat.LOAD_STRATEGY_MANUAL)
+ val factory = TestEmojiCompatConfigFactory(config)
+
+ benchmarkRule.measureRepeated {
+ runWithTimingDisabled {
+ EmojiCompat.reset(null as EmojiCompat?)
+ EmojiCompat.skipDefaultConfigurationLookup(false)
+ }
+ val result = EmojiCompat.init(context, factory)
+ runWithTimingDisabled {
+ assertNotNull(result)
+ }
+ }
+ }
+
+ class TestEmojiCompatConfigFactory(private val config: EmojiCompat.Config) :
+ DefaultEmojiCompatConfig.DefaultEmojiCompatConfigFactory(null) {
+ override fun create(context: Context): EmojiCompat.Config {
+ return config
+ }
+ }
}
diff --git a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/NoFontTestEmojiConfig.java b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/NoFontTestEmojiConfig.java
new file mode 100644
index 0000000..f706f84
--- /dev/null
+++ b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/text/NoFontTestEmojiConfig.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 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 androidx.emoji2.benchmark.text;
+
+import static org.mockito.Mockito.mock;
+
+import android.graphics.Typeface;
+
+import androidx.annotation.NonNull;
+import androidx.emoji2.text.EmojiCompat;
+import androidx.emoji2.text.MetadataRepo;
+
+public class NoFontTestEmojiConfig extends EmojiCompat.Config {
+
+ static EmojiCompat.Config emptyConfig() {
+ return new NoFontTestEmojiConfig(new EmptyEmojiDataLoader());
+ }
+
+ static EmojiCompat.Config neverLoadsConfig() {
+ return new NoFontTestEmojiConfig(new NeverCompletesMetadataRepoLoader());
+ }
+
+ static EmojiCompat.Config fromLoader(EmojiCompat.MetadataRepoLoader loader) {
+ return new NoFontTestEmojiConfig(loader);
+ }
+
+ private NoFontTestEmojiConfig(EmojiCompat.MetadataRepoLoader loader) {
+ super(loader);
+ }
+
+ private static class EmptyEmojiDataLoader implements EmojiCompat.MetadataRepoLoader {
+ @Override
+ public void load(@NonNull EmojiCompat.MetadataRepoLoaderCallback loaderCallback) {
+ loaderCallback.onLoaded(MetadataRepo.create(mock(Typeface.class)));
+ }
+ }
+
+ private static class NeverCompletesMetadataRepoLoader
+ implements EmojiCompat.MetadataRepoLoader {
+ @Override
+ public void load(@NonNull final EmojiCompat.MetadataRepoLoaderCallback loaderCallback) {
+ // do nothing, this will be called on the test thread and is a no-op
+ }
+ }
+}
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java
index 4ab73f9..94ecef2 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java
@@ -199,10 +199,11 @@
static final int EMOJI_COUNT_UNLIMITED = Integer.MAX_VALUE;
private static final Object INSTANCE_LOCK = new Object();
+ private static final Object CONFIG_LOCK = new Object();
@GuardedBy("INSTANCE_LOCK")
private static volatile @Nullable EmojiCompat sInstance;
- @GuardedBy("INSTANCE_LOCK")
+ @GuardedBy("CONFIG_LOCK")
private static volatile boolean sHasDoneDefaultConfigLookup;
private final @NonNull ReadWriteLock mInitLock;
@@ -323,25 +324,27 @@
@SuppressWarnings("GuardedBy") /* double-check lock; volatile; threadsafe obj */
public static EmojiCompat init(@NonNull Context context,
@Nullable DefaultEmojiCompatConfig.DefaultEmojiCompatConfigFactory defaultFactory) {
- if (sInstance != null || sHasDoneDefaultConfigLookup) {
+ EmojiCompat.Config config;
+ if (sHasDoneDefaultConfigLookup) {
// sInstance is safe to return outside the lock because
// 1) static fields are volatile
// 2) all fields on EmojiCompat are final, or guarded by a lock
+ // 3) we only write this after sInstance is settled by the call to `init`
return sInstance;
+ } else {
+ DefaultEmojiCompatConfig.DefaultEmojiCompatConfigFactory factory =
+ defaultFactory != null ? defaultFactory :
+ new DefaultEmojiCompatConfig.DefaultEmojiCompatConfigFactory(null);
+ config = factory.create(context);
}
- synchronized (INSTANCE_LOCK) {
- if (sInstance == null && !sHasDoneDefaultConfigLookup) {
+ synchronized (CONFIG_LOCK) {
+ if (!sHasDoneDefaultConfigLookup) {
// sDefaultConfigLookup allows us to early-exit above, as well as avoid repeated
// calls to create in the case where the font provider is not found
-
- DefaultEmojiCompatConfig.DefaultEmojiCompatConfigFactory factory =
- defaultFactory != null ? defaultFactory :
- new DefaultEmojiCompatConfig.DefaultEmojiCompatConfigFactory(null);
- EmojiCompat.Config config = factory.create(context);
if (config != null) {
- sInstance = new EmojiCompat(config);
+ init(config);
}
- // write this after sInstance to allow early-exit
+ // write this after init to allow safe early-exit
sHasDoneDefaultConfigLookup = true;
}
@@ -412,7 +415,7 @@
*/
@RestrictTo(TESTS)
public static void skipDefaultConfigurationLookup(boolean shouldSkip) {
- synchronized (INSTANCE_LOCK) {
+ synchronized (CONFIG_LOCK) {
sHasDoneDefaultConfigLookup = shouldSkip;
}
}