Tasteless? App Startup may be easier than you think

Time:2022-11-13

Please like and follow, your support means a lot to me.

πŸ”₯ Hi, I’m Xiao Peng. This article has been included inGitHub Β· AndroidFamilymiddle. There is an Android advanced growth knowledge system here. If you have like-minded friends, pay attention to the official account [Peng Xurui] to help you build up your core competitiveness.

foreword

Hello everyone, my name is Peng.

October 28, 2020JetPack | App Startup 1.0.0Finally ushered in the official release. Just recently, I was summarizing the topic of componentized architecture, so I also specially learned the working principle of App Startup. In this article, I will take you to summarize the usage & implementation principle & source code analysis of App Startup. If it is useful, please give Star a thumbs up and give Xiao Peng a little motivation to create, thank you.


This article is the 13th in the Jetpack series, a list of columns:

2. Others:

  • 1. AppStartup: Lightweight Initialization Framework (this article)
  • 2. DataStore: a new generation of key-value storage solutions
  • 3. Room: ORM database access framework
  • 4. WindowManager: Enhanced support for multi-window mode
  • 5. WorkManager: Enhanced support for background tasks
  • 6. Compose: a new generation of view development solutions

Learning Roadmap:

Tasteless? App Startup may be easier than you think


1. Get to know AppStartup

1.1 What problem does App Startup solve?

App Startup is a lightweight initialization framework for Android provided by Google:

  • Advantages: Using the App Startup framework, you can simplify the startup sequence and explicitly set the initialization dependency order. In terms of simplicity and efficiency, App Startup basically meets the requirements.
  • Disadvantages: The shortcoming of the App Startup framework is also because it is too simple, the features provided are too simple, and often cannot perfectly meet the commercial needs. For example, the following properties App Startup cannot satisfy:

    • Lack of async await:Synchronous waiting refers to initializing the dependent components in the current thread first, and then initializing the current component. App Startup is supported, but asynchronous waiting is not supported. For example, if the dependent component needs to perform a time-consuming asynchronous task to complete the initialization, then App Startup cannot wait for the asynchronous task to return;
    • Lack of dependency callbacks:The callback is not issued after the component on which the current component depends has been initialized.

1.2 How does App Startup realize automatic initialization?

App Startup utilizes the feature that ContentProvider initializes when the application starts, and provides a custom ContentProvider to achieve automatic initialization. Many libraries use the startup mechanism of ContentProvider to achieve non-invasive initialization, such as LeakCanary, etc.

Using AppStartup can also merge all ContentProviders for initialization, reduce the creation of ContentProviders, and provide global management.

App Startup Diagram

Tasteless? App Startup may be easier than you think

Detailed source code analysis below.


2. How to use App Startup

In this section, we summarize the steps to use App Startup.

2.1 Basic usage

  • 1. Add dependencies

at the module levelbuild.gradleAdd dependencies:

Module-level build.gradle

implementation "androidx.startup:startup-runtime:1.0.0"
  • 2. Implement the Initializer interface

InitializerThe interface is the component interface defined by App Startup, which is used to specify the initialization logic and initialization sequence (that is, the dependency) of the component. The interface is defined as follows:

  • 1. create(…) initialization operation:The returned initialization result will be cached, wherecontextThe parameter is the current processApplicationobject;
  • 2. dependencies() dependencies:The return value is a list of dependent components, or an empty list if no dependencies are required. When App Startup initializes the current component, it will ensure that the dependent components have been initialized.

Initializer.java

public interface Initializer<T> {

    // 1. Initialization operation, the return value will be cached? ?
    @NonNull
    T create(@NonNull Context context);

    // 2. Dependencies, the return value is a list of dependent components
    @NonNull
    List<Class<? extends Initializer<?>>> dependencies();
}

sample program

// LeakCanary 2.9.1
internal class AppWatcherStartupInitializer : Initializer<AppWatcherStartupInitializer> {
    override fun create(context: Context) = apply {
        // implement the initialization operation
        val application = context.applicationContext as Application
            AppWatcher.manualInstall(application)
        }
    
    override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}
  • 3. Configuration<meta-data>

Configure the Initializer implementation class in the Manifest file toandroidx.startup.InitializationProviderof<meta-data>middle.

sample program

<!-- LeakCanary 2.9.1 -->
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">

    <meta-data
        android:name="leakcanary.internal.AppWatcherStartupInitializer"
        android:value="androidx.startup"/>
</provider>

The main points are as follows:

  • 1. The component name must beandroidx.startup.InitializationProviderοΌ›
  • 2. Need to declareandroid:exported="false", to restrict access to this component by other applications;
  • 3. Requirementsandroid:authoritiesIt is required to be globally unique in the device, usually using${applicationId}as a prefix;
  • 4. Need to declaretools:node="merge", to ensure that the manifest merger tool can correctly resolve conflicting nodes;
  • 5、meta-data android:nameFully qualified class name for the component’s Initializer implementation class,android:valuefixed asandroidx.startup。

hint:why shouldandroidx.startupSet to value, not name? Because in the key-value pair, the name is unique, and the value is allowed to be repeated, theandroidx.startupIf you put it in value, you can allow multiple configurations with the same semantics at the same time.<meta-data>。

So far, the basic use and configuration of App Startup is completed. When the application starts, App Startup will automatically collect the configuration information of each module.InitializerImplement classes, and execute them sequentially in dependency order.

2.2 Advanced usage

  • 1. Manual initialization

When your component needs to be initialized manually instead of automatically (for example, there are time-consuming tasks), manual initialization can be performed, and manual initialization can be called in child threads, while automatic initialization is performed in the main thread.

  • The initialized results will be cached in App Startup, and the call will be repeatedinitializeComponent()It also does not cause repeated initialization;
  • The Initializer implementation class to be manually initialized cannot be declared in the AndroidManifest, nor can it be depended on by other components, otherwise it will still be automatically initialized.

Manual initialization can be done by calling:

sample program

AppInitializer.getInstance(context).initializeComponent(ExampleLoggerInitializer::class.java)
  • 2. Cancel automatic initialization

If some libraries have been configured with automatic initialization, and we want to do lazy loading, we need to usemanifest merger toolThe merge rule to remove the Initializer corresponding to this library. details as follows:

sample program

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name="com.example.ExampleLoggerInitializer"
        tools:node="remove" />
</provider>
  • 3. Disable App Startup

If you need to completely disable the automatic initialization of App Startup, you can also usemanifest merger toolThe merge rules for:

sample program

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove" />

3. App Startup principle analysis

3.1 How does App Startup realize automatic initialization?

App Startup utilizes the startup mechanism of ContentProvider to achieve automatic initialization. ContentProvider is usually used to provide content services for the current process/remote process, which will be initialized when the application starts. Using this feature, App Startup’s solution is to customize a ContentProvider implementation classInitializationProvider, perform the initialization logic in the onCreate(…) method.

InitializationProvider.java

simplified

public final class InitializationProvider extends ContentProvider {

    @Override
    public boolean onCreate() {
        Context context = getContext();
        if (context != null) {
            // initialize
            AppInitializer.getInstance(context).discoverAndInitialize();
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true;
    }

    @Override
    public Cursor query(...) {
        throw new IllegalStateException("Not allowed.");
    }

    @Override
    public String getType(...) {
        throw new IllegalStateException("Not allowed.");
    }

    @Nullable
    @Override
    public Uri insert(...) {
        throw new IllegalStateException("Not allowed.");
    }

    @Override
    public int delete(...) {
        throw new IllegalStateException("Not allowed.");
    }

    @Override
    public int update(...) {
        throw new IllegalStateException("Not allowed.");
    }
}

Since other methods of ContentProvider are meaningless, they are all thrownIllegalStateException。

3.2 Talk about the initialization process of App Startup

As you can see from the previous section, App Startup isInitializationProvidercalled inAppInitializer#discoverAndInitialize()Perform automatic initialization.AppInitializerIt is the core class of the App StartUp framework. The code of the entire App Startup framework is actually very small, and most of the core code is in the AppInitializer class.

I summarize the entire automatic initialization process into 3 stages:

  • step1 – Get<meta-data> data:Scan Manifest defined in InitializationProvider inside<meta-data> data, from which to filter out the configuration information of the Initializer;
  • Step 2 – Execute the initializer recursively: Execute the logic of each initializer through Initializer#create(), and first ensure that the dependencies have been initialized through Initializer#dependencies();
  • Step 3 – Cache initialization results: Cache the initialization results in the mapping table to avoid repeated initialization.

The source code abstract is as follows:

AppInitializer.java

private static final Object sLock = new Object(); // will be mentioned later

// record scan<meta-data> The resulting initializer (which can be used to determine whether the component has been started automatically)
final Set<Class<? extends Initializer<?>>> mDiscovered;

// cache the initialization result of each component
final Map<Class<?>, Object> mInitialized;

void discoverAndInitialize() {
    // 1. Get androidx.startup.InitializationProvider component information
    ComponentName provider = new ComponentName(mContext.getPackageName(), InitializationProvider.class.getName());
    ProviderInfo providerInfo = mContext.getPackageManager().getProviderInfo(provider, GET_META_DATA);

    // 2. androidx.startup string
    String startup = mContext.getString(R.string.androidx_startup);

    // 3. Get the meta-data data in the component information
    Bundle metadata = providerInfo.metaData;

    // 4. Traverse all meta-data data
    if (metadata != null) {
        Set<Class<?>> initializing = new HashSet<>();
        Set<String> keys = metadata.keySet();
        for (String key : keys) {
            String value = metadata.getString(key, null);

            // 4.1 Filter the meta-data whose value is androidx.startup
            if (startup.equals(value)) {
                Class<?> clazz = Class.forName(key);

                // 4.2 Check that the specified class is an implementation class of the Initializer interface
                if (Initializer.class.isAssignableFrom(clazz)) {
                    Class<? extends Initializer<?>> component = (Class<? extends Initializer<?>>) clazz;

                    // 4.3 Add Class to mDiscovered Set
                    mDiscovered.add(component);

                    // 4.4 Initialize this component
                    doInitialize(component, initializing);
                }
            }
        }
    }
}

// -> 4.3 mDiscovered is used to determine whether the component has been automatically started
public boolean isEagerlyInitialized(@NonNull Class<? extends Initializer<?>> component) {
    return mDiscovered.contains(component);
}

// -> 4.4 Initialize this component (simplified)
<T> T doInitialize(Class<? extends Initializer<?>> component, Set<Class<?>> initializing) {
    // 1. Lock sLock, I will talk about it later.

    Object result;

    // 2. Judging that there is a current component in initializing, indicating that there is a circular dependency
    if (initializing.contains(component)) {
        String message = String.format("Cannot initialize %s. Cycle detected.", component.getName());
        throw new IllegalStateException(message);
    }

    // 3. Check whether the current component has been initialized
    if (!mInitialized.containsKey(component)) {
        // 3.1 The current component is not initialized

        // 3.1.1 The record is being initialized
        initializing.add(component);

        // 3.1.2 Instantiate the Initializer interface implementation class through reflection
        Object instance = component.getDeclaredConstructor().newInstance();
        Initializer<?> initializer = (Initializer<?>) instance;

        // 3.1.3 Traverse dependent components (key: prioritize dependent components)
        List<Class<? extends Initializer<?>>> dependencies = initializer.dependencies();
        if (!dependencies.isEmpty()) {
            for (Class<? extends Initializer<?>> clazz : dependencies) {

                // Recursive: If the dependent component is not initialized, perform initialization
                if (!mInitialized.containsKey(clazz)) {
                    // Note: initializing is passed as a parameter here to determine circular dependencies
                    doInitialize(clazz, initializing); 
                }
            }
        }

        // 3.1.4 (Here, the dependent components have been initialized) Initialize the current component
        result = initializer.create(mContext);

        // 3.1.5 remove initializing record
        initializing.remove(component);

        // 3.1.6 Cache initialization result
        mInitialized.put(component, result);
    } else {
        // 3.2 The current component has been initialized, return directly
        result = mInitialized.get(component);
    }
     return (T) result;
}

3.3 Execution process of manual initialization

Earlier we mentioned usinginitializeComponent()The method can be initialized manually, let’s look at the source code of manual initialization (lazy loading):

AppInitializer.java

public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) {
    // Call the doInitialize(...) method:
    return doInitialize(component, new HashSet<Class<?>>());
}

In fact, it is very simple, just call the previous sectiondoInitialize(...)Perform initialization. It should be noted that this method is allowed to be called in child threads. In other words, there is a thread synchronization problem between automatic initialization and manual initialization, so how does App Startup solve it? Remember we had one beforesLockDidn’t you say it? In fact, it is a lock used to ensure thread synchronization:

AppInitializer.java

<T> T doInitialize(Class<? extends Initializer<?>> component, Set<Class<?>> initializing) {
    // 1. Lock sLock
    synchronized (sLock) {
        ...
    }
}

4. Summary

At this point, the content of App Startup is finished. You can see that App Startup is just a lightweight initialization framework that can do limited things. Some developers on the market have open sourced the initialization framework based on the DAU directed acyclic graph, we will talk about this next time. Follow me and take you to know more.


References

I am Xiao Peng, and I will take you to build the Android knowledge system. For technical and workplace issues, please pay attention to the public account [Peng Xurui] and send me a private message to ask questions.