App Startup, launched by Google, is used to centrally manage the initialized component library during application Startup. The library can set the initialization sequence of components, and can specify all components in a ContentProriver, avoid storing multiple components, multiple contentProviders, so as to improve the startup time of app, but also through lazy loading, to specify the initialization time of components. The following content will introduce one by one and analyze the source code of App Startup.
Why use App Startup and what problems are solved?
Some libraries typically provide external initialization methods like this:
class MApp :Application() {override fun onCreate(a) {
super.onCreate()
CrashReport.initCrashReport(getApplicationContext(), "APPID requested at registration".false)
LeakCanary.install(this)
XXX.init(this)
XXX.install(this)}}Copy the code
The ContentProvider’s onCreate() method precedes the Application’s onCreate() method. So some open source libraries do their own initialization internally through the ContentProvider, without having to call the initialization code outside.
public class XXXProvider extends ContentProvider {
@Override
public boolean onCreate() {
XXX.init(getContext());
return true; }... } <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.jetpackappstartuptes">
<application>
<provider
android:authorities="${applicationId}.init-provider"
android:name=".XXXProvider"
android:exported="false"/>
</application>
</manifest>
Copy the code
If each third-party library creates its own ContentProvider internally for initialization, and when we use many open source libraries, there will be the problem of creating multiple ContentProviders, which will affect the startup time of the App. To solve these problems, Google launched App Startup
2: use
1: Adds dependencies
dependencies {
// Use App Startup
implementation "Androidx. Startup: startup - runtime: 1.0.0"
}
Copy the code
2: Implements the Initializer interface
// Initializer needs to implement two interfaces
public interface Initializer<T> {
@NonNull
T create(@NonNull Context context);
@NonNullList<Class<? extends Initializer<? >>> dependencies(); }Copy the code
class BuglyInitializer :Initializer<Unit>{
override fun create(context: Context) {
CrashReport.initCrashReport(context, "APPID requested at registration".false)}override fun dependencies(a): List<Class<out Initializer<*>>> {
return emptyList()
}
}
Copy the code
The generic T is the object type provided by the Sdk to be initialized; The create(Context) method is where the Sdk initializes, taking Context as an Application Context and returning an object instance provided by the Sdk. The dependencies() method returns a list. If the SDK is independent and does not depend on other SDKS, it returns an empty list (as above). If this SDK depends on other SDKS and must be initialized after the other SDKS are initialized, you need to specify this in the Dependencies () method. TestSDK1 must be initialized after TestSDK2 is initialized
class Test1SDKInitializer :Initializer<Test1SDK>{
override fun create(context: Context):Test1SDK{
Test1SDK.init(context)
return Test1SDK.getInstance()
}
override fun dependencies(a): List<Class<out Initializer<*>>> {
val list = ArrayList<Class<out Initializer<*>>>()
list.add(Test2SDKInitializer::class.java)
return list
}
}
class Test2SDKInitializer :Initializer<Test2SDK>{
override fun create(context: Context):Test2SDK{
Test2SDK.init(context)
return Test2SDK.getInstance()
}
override fun dependencies(a): List<Class<out Initializer<*>>> {
return emptyList()
}
}
Copy the code
3: Register the provider in AndroidMainfest.xml
<provider
android:authorities="${applicationId}.androidx-startup"
android:name="androidx.startup.InitializationProvider"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.example.jetpackappstartuptesty.BuglyInitializer"
android:value="@string/androidx_startup"/>
</provider>
Copy the code
Normally, each Initializer has a tag, but if some initializers are already relied on by a registered Initializer (for example, TestSDK2Initializer is already relied on by TestSDK1Initializer), You don’t need to specify it explicitly in the Androidmanifest.xml file because App Startup is already found through the registered TestSDK1Initializer.
The value attribute of the tag here must be the value of the string androidX_startup (” androidX.startup “), otherwise it will not take effect.
If there is an SDK that internally helps users handle initialization through App Startup, then the SDK androidmanifest.xml file already has the provider label of InitializationProvider, which will conflict with the App module. Therefore, specify tools:node=”merge” in the provider tag of the app module, using the merging mechanism of the Androidmanifest.xml file.
4: App Startup lazy loading implementation
Some SDKS do not need to be initialized at the beginning, but just before use. Consider using lazy loading, calling the following code to initialize before using the SDK functionality
// call before use
AppInitializer.getInstance(applicationContext).initializeComponent(BuglyInitializer::class.java)
Copy the code
And declare remove in androidMainfest.xml
<provider
android:authorities="${applicationId}.androidx-startup"
android:name="androidx.startup.InitializationProvider"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.example.jetpackappstartuptesty.BuglyInitializer"
android:value="@string/androidx_startup"
tools:node="remove"/>
</provider>
Copy the code
Three: source code analysis
App Startup is simple, with only five classes: AppInitializer, InitializationProvider, Initializer, StartupException, and StartupLogger. Let’s do one by one
// StartupLogger is just a log to use
public final class StartupLogger {
private StartupLogger() {
}
private static final String TAG = "StartupLogger";
static final boolean DEBUG = false;
public static void i(@NonNull String message) {
Log.i(TAG, message);
}
public static void e(@NonNull String message, @NullableThrowable throwable) { Log.e(TAG, message, throwable); }}// StartupException just defines an exception
public final class StartupException extends RuntimeException {
public StartupException(@NonNull String message) {
super(message);
}
public StartupException(@NonNull Throwable throwable) {
super(throwable);
}
public StartupException(@NonNull String message, @NonNull Throwable throwable) {
super(message, throwable); }}// Initializer only defines an interface
public interface Initializer<T> {
@NonNull
T create(@NonNull Context context);
@NonNullList<Class<? extends Initializer<? >>> dependencies(); }// InitializationProvider is customized
public final class InitializationProvider extends ContentProvider {
@Override
public boolean onCreate() {
Context context = getContext();
if(context ! =null) {
AppInitializer.getInstance(context).discoverAndInitialize();
} else {
throw new StartupException("Context cannot be null");
}
return true;
}
@Nullable
@Override
public Cursor query(
@NonNull Uri uri,
@Nullable String[] projection,
@Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder) {
throw new IllegalStateException("Not allowed.");
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
throw new IllegalStateException("Not allowed.");
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
throw new IllegalStateException("Not allowed.");
}
@Override
public int delete(
@NonNull Uri uri,
@Nullable String selection,
@Nullable String[] selectionArgs) {
throw new IllegalStateException("Not allowed.");
}
@Override
public int update(
@NonNull Uri uri,
@Nullable ContentValues values,
@Nullable String selection,
@Nullable String[] selectionArgs) {
throw new IllegalStateException("Not allowed."); }}Copy the code
Initializer only defines an interface, StartupException a custom exception class, and StartupLogger hits the log. InitializationProvider is a custom ContentProvider. He calls onCreate method AppInitializer. GetInstance (context). DiscoverAndInitialize (); So the key is to look at the discoverAndInitialize method of AppInitializer.
void discoverAndInitialize() {
try {
Trace.beginSection(SECTION_NAME);
ComponentName provider = new ComponentName(mContext.getPackageName(),
InitializationProvider.class.getName());
ProviderInfo providerInfo = mContext.getPackageManager()
.getProviderInfo(provider, GET_META_DATA);
Bundle metadata = providerInfo.metaData;
String startup = mContext.getString(R.string.androidx_startup);
if(metadata ! =null) { Set<Class<? >> initializing = new HashSet<>(); Set<String> keys = metadata.keySet();for (String key : keys) {
String value = metadata.getString(key, null);
if(startup.equals(value)) { Class<? > clazz = Class.forName(key);if (Initializer.class.isAssignableFrom(clazz)) { Class<? extends Initializer<? >> component = (Class<? extends Initializer<? >>) clazz; mDiscovered.add(component);if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Discovered %s", key));
}
doInitialize(component, initializing);
}
}
}
}
} catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {
throw new StartupException(exception);
} finally{ Trace.endSection(); }}Copy the code
- ComponentName provider = new ComponentName(mContext.getPackageName(), InitializationProvider.class.getName()); Get InitializationProvider
- ProviderInfo providerInfo = mContext.getPackageManager() .getProviderInfo(provider, GET_META_DATA); Bundle metadata = providerInfo.metaData; Retrieve metadata information
- for (String key : Keys) traversing metadata, If (startup.equals(value)) determines whether a value is androidx_startup, add the component to the mDiscovered collection. And perform the component’s doInitialize()
- final Set
> mDiscovered; mDiscovered = new HashSet<>(); MDiscovered is a HashSet
Next, analyze the doInitialize method
<T> T doInitialize(
@NonNullClass<? extends Initializer<? >> component,@NonNullSet<Class<? >> initializing) { synchronized (sLock) { boolean isTracingEnabled = Trace.isEnabled();try {
if (isTracingEnabled) {
// Use the simpleName here because section names would get too big otherwise.
Trace.beginSection(component.getSimpleName());
}
if (initializing.contains(component)) {
String message = String.format(
"Cannot initialize %s. Cycle detected.", component.getName()
);
throw new IllegalStateException(message);
}
Object result;
if(! mInitialized.containsKey(component)) { initializing.add(component);try{ Object instance = component.getDeclaredConstructor().newInstance(); Initializer<? > initializer = (Initializer<? >) instance; List<Class<? extends Initializer<? >>> dependencies = initializer.dependencies();if(! dependencies.isEmpty()) {for(Class<? extends Initializer<? >> clazz : dependencies) {if(! mInitialized.containsKey(clazz)) { doInitialize(clazz, initializing); }}}if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initializing %s", component.getName()));
}
result = initializer.create(mContext);
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initialized %s", component.getName()));
}
initializing.remove(component);
mInitialized.put(component, result);
} catch (Throwable throwable) {
thrownew StartupException(throwable); }}else {
result = mInitialized.get(component);
}
return (T) result;
} finally{ Trace.endSection(); }}}Copy the code
As you can see, this method mainly performs initialization and also initializes dependent components
-
Object instance = component.getDeclaredConstructor().newInstance(); Initializer initializer = (Initializer) instance; Initialization of its own
-
List<Class>> dependencies = initializer.dependencies(); Get the Sdk component collection
if(! dependencies.isEmpty()) {for(Class<? extends Initializer<? >> clazz : dependencies) {if(! mInitialized.containsKey(clazz)) { doInitialize(clazz, initializing); }}}Copy the code
Iterate over the initialization of the dependent SDK.