Make `AppInitializer` a singleton.
* Makes testing a lot simpler.
Test: Updated unit test. Ran integration tests.
Change-Id: I7afe54671796e299c93be3526c91657c768ffe70
diff --git a/startup/startup-runtime/api/1.0.0-alpha01.txt b/startup/startup-runtime/api/1.0.0-alpha01.txt
index ce81b08..da9dd9d 100644
--- a/startup/startup-runtime/api/1.0.0-alpha01.txt
+++ b/startup/startup-runtime/api/1.0.0-alpha01.txt
@@ -2,8 +2,9 @@
package androidx.startup {
public final class AppInitializer {
- method public static void initialize(android.content.Context);
- method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+ method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+ method public void initializeAllComponents();
+ method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
}
public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/current.txt b/startup/startup-runtime/api/current.txt
index ce81b08..da9dd9d 100644
--- a/startup/startup-runtime/api/current.txt
+++ b/startup/startup-runtime/api/current.txt
@@ -2,8 +2,9 @@
package androidx.startup {
public final class AppInitializer {
- method public static void initialize(android.content.Context);
- method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+ method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+ method public void initializeAllComponents();
+ method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
}
public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/public_plus_experimental_1.0.0-alpha01.txt b/startup/startup-runtime/api/public_plus_experimental_1.0.0-alpha01.txt
index ce81b08..da9dd9d 100644
--- a/startup/startup-runtime/api/public_plus_experimental_1.0.0-alpha01.txt
+++ b/startup/startup-runtime/api/public_plus_experimental_1.0.0-alpha01.txt
@@ -2,8 +2,9 @@
package androidx.startup {
public final class AppInitializer {
- method public static void initialize(android.content.Context);
- method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+ method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+ method public void initializeAllComponents();
+ method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
}
public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/public_plus_experimental_current.txt b/startup/startup-runtime/api/public_plus_experimental_current.txt
index ce81b08..da9dd9d 100644
--- a/startup/startup-runtime/api/public_plus_experimental_current.txt
+++ b/startup/startup-runtime/api/public_plus_experimental_current.txt
@@ -2,8 +2,9 @@
package androidx.startup {
public final class AppInitializer {
- method public static void initialize(android.content.Context);
- method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+ method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+ method public void initializeAllComponents();
+ method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
}
public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/restricted_1.0.0-alpha01.txt b/startup/startup-runtime/api/restricted_1.0.0-alpha01.txt
index ce81b08..da9dd9d 100644
--- a/startup/startup-runtime/api/restricted_1.0.0-alpha01.txt
+++ b/startup/startup-runtime/api/restricted_1.0.0-alpha01.txt
@@ -2,8 +2,9 @@
package androidx.startup {
public final class AppInitializer {
- method public static void initialize(android.content.Context);
- method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+ method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+ method public void initializeAllComponents();
+ method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
}
public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/restricted_current.txt b/startup/startup-runtime/api/restricted_current.txt
index ce81b08..da9dd9d 100644
--- a/startup/startup-runtime/api/restricted_current.txt
+++ b/startup/startup-runtime/api/restricted_current.txt
@@ -2,8 +2,9 @@
package androidx.startup {
public final class AppInitializer {
- method public static void initialize(android.content.Context);
- method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+ method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+ method public void initializeAllComponents();
+ method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
}
public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/src/androidTest/java/androidx/startup/AppInitializerTest.kt b/startup/startup-runtime/src/androidTest/java/androidx/startup/AppInitializerTest.kt
index 46f93ac..6b1172e 100644
--- a/startup/startup-runtime/src/androidTest/java/androidx/startup/AppInitializerTest.kt
+++ b/startup/startup-runtime/src/androidTest/java/androidx/startup/AppInitializerTest.kt
@@ -19,71 +19,64 @@
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
+import androidx.test.filters.MediumTest
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.containsString
-import org.hamcrest.Matchers.containsInAnyOrder
import org.junit.Assert.assertThat
+import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-@SmallTest
+@MediumTest
class AppInitializerTest {
private lateinit var context: Context
+ private lateinit var appInitializer: AppInitializer
@Before
fun setUp() {
context = ApplicationProvider.getApplicationContext()
+ appInitializer = AppInitializer(context)
}
@Test
fun basicUsageTest() {
- AppInitializer.initialize(context, listOf<Class<*>>(InitializerNoDependencies::class.java))
- assertThat(AppInitializer.sInitialized.get(), `is`(true))
+ appInitializer.initializeComponents(listOf<Class<*>>(InitializerNoDependencies::class.java))
+ assertTrue(appInitializer.mInitialized.containsKey(InitializerNoDependencies::class.java))
}
@Test
fun basicInitializationTest() {
val initializing = mutableSetOf<Class<*>>()
- val initialized = mutableSetOf<Class<*>>()
val components = listOf<Class<*>>(InitializerNoDependencies::class.java)
- AppInitializer.doInitialize(context, components, initializing, initialized)
+ appInitializer.doInitialize(components, initializing)
assertThat(initializing.size, `is`(0))
- assertThat(initialized.size, `is`(1))
- assertThat<Collection<Class<*>>>(
- initialized,
- containsInAnyOrder<Class<*>>(*components.toTypedArray())
- )
+ assertThat(appInitializer.mInitialized.size, `is`(1))
+ for (component in components) {
+ assertTrue(appInitializer.mInitialized.containsKey(component))
+ }
}
@Test
fun initializationWithDependencies() {
val initializing = mutableSetOf<Class<*>>()
- val initialized = mutableSetOf<Class<*>>()
val components = listOf<Class<*>>(InitializerWithDependency::class.java)
- AppInitializer.doInitialize(context, components, initializing, initialized)
+ appInitializer.doInitialize(components, initializing)
assertThat(initializing.size, `is`(0))
- assertThat(initialized.size, `is`(2))
- assertThat<Collection<Class<*>>>(
- initialized,
- containsInAnyOrder<Class<*>>(
- InitializerNoDependencies::class.java,
- InitializerWithDependency::class.java
- )
- )
+ assertThat(appInitializer.mInitialized.size, `is`(2))
+ assertTrue(appInitializer.mInitialized.containsKey(InitializerNoDependencies::class.java))
+ assertTrue(appInitializer.mInitialized.containsKey(InitializerWithDependency::class.java))
}
@Test
fun initializationWithCyclicDependencies() {
val initializing = mutableSetOf<Class<*>>()
- val initialized = mutableSetOf<Class<*>>()
val components = listOf<Class<*>>(CyclicDependencyInitializer::class.java)
try {
- AppInitializer.doInitialize(context, components, initializing, initialized)
+ appInitializer.doInitialize(components, initializing)
fail()
} catch (exception: StartupException) {
assertThat(exception.localizedMessage, containsString("Cycle detected."))
diff --git a/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java b/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
index 1e2b023..2b49ab9 100644
--- a/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
+++ b/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
@@ -28,10 +28,11 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* An {@link AppInitializer} can be used to initialize all discovered [ComponentInitializer]s.
@@ -42,49 +43,67 @@
public final class AppInitializer {
/**
+ * The {@link AppInitializer} instance.
+ */
+ private static AppInitializer sInstance;
+
+ /**
* Guards app initialization.
*/
private static final Object sLock = new Object();
+ @NonNull
+ final List<Class<?>> mDiscovered;
+
+ @NonNull
+ final Map<Class<?>, Object> mInitialized;
+
+ @NonNull
+ final Context mContext;
+
/**
- * Keeps track of whether components were initialized.
+ * Creates an instance of {@link AppInitializer}
+ *
+ * @param context The application context
+ */
+ AppInitializer(@NonNull Context context) {
+ mContext = context.getApplicationContext();
+ mInitialized = new HashMap<>();
+ mDiscovered = discoverComponents();
+ }
+
+ /**
+ * @param context The Application {@link Context}
+ * @return The instance of {@link AppInitializer} after initialization.
*/
@NonNull
- static final AtomicBoolean sInitialized = new AtomicBoolean(false);
-
- private AppInitializer() {
- // Does nothing.
+ @SuppressWarnings("UnusedReturnValue")
+ public static AppInitializer getInstance(@NonNull Context context) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new AppInitializer(context);
+ }
+ return sInstance;
+ }
}
/**
* Discovers an initializes all available {@link ComponentInitializer} classes based on the
* merged manifest `<meta-data>` entries in the `AndroidManifest.xml`.
- *
- * @param context The Application context
*/
- public static void initialize(@NonNull Context context) {
- Context applicationContext = context.getApplicationContext();
- List<Class<?>> components = discoverComponents(applicationContext);
- initialize(applicationContext, components);
+ public void initializeAllComponents() {
+ initializeComponents(mDiscovered);
}
/**
* Initializes a {@link List} of {@link ComponentInitializer} class types.
*
- * @param context The Application context
* @param components The {@link List} of {@link Class}es that represent all discovered
* {@link ComponentInitializer}s
*/
- public static void initialize(@NonNull Context context, @NonNull List<Class<?>> components) {
+ public void initializeComponents(@NonNull List<Class<?>> components) {
synchronized (sLock) {
- Context applicationContext = context.getApplicationContext();
- if (sInitialized.compareAndSet(false, true)) {
- doInitialize(applicationContext, components, new HashSet<>(), new HashSet<>());
- } else {
- if (StartupLogger.DEBUG) {
- StartupLogger.i("Already initialized");
- }
- }
+ doInitialize(components, new HashSet<>());
}
}
@@ -92,13 +111,10 @@
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
- public static void doInitialize(
- @NonNull Context context,
+ public void doInitialize(
@NonNull List<Class<?>> components,
- @NonNull Set<Class<?>> initializing,
- @NonNull Set<Class<?>> initialized) {
+ @NonNull Set<Class<?>> initializing) {
- Context applicationContext = context.getApplicationContext();
for (Class<?> component : components) {
if (initializing.contains(component)) {
String message = String.format(
@@ -106,7 +122,7 @@
);
throw new IllegalStateException(message);
}
- if (!initialized.contains(component)) {
+ if (!mInitialized.containsKey(component)) {
initializing.add(component);
try {
Object instance = component.getDeclaredConstructor().newInstance();
@@ -121,22 +137,22 @@
initializer.dependencies();
List<Class<?>> filtered = new ArrayList<>(dependencies.size());
for (Class<? extends ComponentInitializer<?>> clazz : dependencies) {
- if (!initialized.contains(clazz)) {
+ if (!mInitialized.containsKey(clazz)) {
filtered.add(clazz);
}
}
if (!filtered.isEmpty()) {
- doInitialize(applicationContext, filtered, initializing, initialized);
+ doInitialize(filtered, initializing);
}
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initializing %s", component.getName()));
}
- initializer.create(applicationContext);
+ Object result = initializer.create(mContext);
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initialized %s", component.getName()));
}
initializing.remove(component);
- initialized.add(component);
+ mInitialized.put(component, result);
} catch (Throwable throwable) {
throw new StartupException(throwable);
}
@@ -149,15 +165,14 @@
*/
@NonNull
@RestrictTo(RestrictTo.Scope.LIBRARY)
- public static List<Class<?>> discoverComponents(@NonNull Context context) {
+ public List<Class<?>> discoverComponents() {
try {
- Context applicationContext = context.getApplicationContext();
ApplicationInfo applicationInfo =
- applicationContext.getPackageManager()
- .getApplicationInfo(applicationContext.getPackageName(), GET_META_DATA);
+ mContext.getPackageManager()
+ .getApplicationInfo(mContext.getPackageName(), GET_META_DATA);
Bundle metadata = applicationInfo.metaData;
- String startup = applicationContext.getString(R.string.androidx_startup);
+ String startup = mContext.getString(R.string.androidx_startup);
if (metadata != null) {
List<Class<?>> components = new ArrayList<>(metadata.size());
Set<String> keys = metadata.keySet();
diff --git a/startup/startup-runtime/src/main/java/androidx/startup/ComponentInitializer.java b/startup/startup-runtime/src/main/java/androidx/startup/ComponentInitializer.java
index ca10fd1..280a3ad 100644
--- a/startup/startup-runtime/src/main/java/androidx/startup/ComponentInitializer.java
+++ b/startup/startup-runtime/src/main/java/androidx/startup/ComponentInitializer.java
@@ -36,7 +36,6 @@
* @param context The application context.
*/
@NonNull
- @SuppressWarnings("UnusedReturnValue")
T create(@NonNull Context context);
/**
diff --git a/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java b/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java
index 6e5978d..24873746 100644
--- a/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java
+++ b/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java
@@ -39,7 +39,7 @@
public boolean onCreate() {
Context context = getContext();
if (context != null) {
- AppInitializer.initialize(context);
+ AppInitializer.getInstance(context).initializeAllComponents();
} else {
throw new StartupException("Context cannot be null");
}