Summary of Android Initializing SDK Library with ContentProvider

  • 2021-12-12 09:42:52
  • OfStack

When developing Android SDK, we will encapsulate the initialization method as, and then let the developer calling SDK initialize it in the onCreate method of Application. However, at present, some mainstream SDK frameworks do not provide relevant methods for initialization, but we can also use them normally when using them. By mining their source code, we can see that they generally use ContentProvider to initialize SDK. At present, well-known SDK using ContentProvider are: ButterKnife, Leakcanary, BlockCanary... and so on.

A new concept is added here. What is the essence of SDK initialization?

The essence of SDK initialization is to inject the context of App (Context) into SDK, so that it can access the resources and services of App through this context. It also includes calling SDK method for custom configuration of related options at initialization time.

1. Implementation of ContentProvider initializing SDK library

To initialize the SDK library in ContentProvider, first create an ContentProvider in the library, and then complete your library initialization with the help of Context returned by getContext () in the onCreate () method of ContentProvider. Of course, the actual type of this Context is the applied Application.

The following is the sample code for initializing the SDK library through ContentProvider:


class ToolContentProvider : ContentProvider() {

    override fun onCreate(): Boolean {
        Log.e(GlobalConfig.LOG_TAG, "ToolContentProvider onCreate")
        AppContextHelper.init(context!!.applicationContext)
        AppContextHelper.initRoomDB(context!!.applicationContext)
        return true
    }

    override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
        return null
    }

    override fun getType(uri: Uri): String? {
        return null
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        return null
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        return 0
    }

    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
        return 0
    }
}

<provider
      android:name=".ToolContentProvider"
      android:authorities="${applicationId}.library-tool"
      android:exported="false" />

class MaoApplication : Application() {

    private lateinit var currentActivityRef: WeakReference<Activity>;


    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        Log.e(GlobalConfig.LOG_TAG, "MaoApplication attachBaseContext")
    }

    override fun onCreate() {
        super.onCreate()
        Log.e(GlobalConfig.LOG_TAG, "MaoApplication onCreate")
        initMMKV()
        initCodeView()
	}

    /**
     *  Initialization MMKV Tools 
     */
    private fun initMMKV() {
        Log.e(GlobalConfig.LOG_TAG, "init MMKV")
        MMKV.initialize(this);
    }

    private fun initCodeView() {
        CodeProcessor.init(this)
    }

}

The function of initializing SDK library through ContentProvider is realized, so when was onCreate () method of ContentProvider called?

The following is the log output to help us understand the initialization timing:


com.renhui.maomaomedia E/MaoMaoMedia: MaoApplication attachBaseContext
com.renhui.maomaomedia E/MaoMaoMedia: ToolContentProvider onCreate
com.renhui.maomaomedia E/MaoMaoMedia: MaoApplication onCreate

As you can see, it is called between attachBaseContext (Context) and onCreate () of Application, and the attachBaseContext (Context) method of Application is called, which means that Context of Application is initialized. Again, we can initialize the SDK library through ContentProvider before onCreate of Application.

2. Advantages and disadvantages of ContentProvider initializing SDK library

Advantages:

The developer who uses SDK library does not need to call the process of initializing the library, which reduces the access cost Lower code intrusion makes the code isolation of SDK library better and convenient to upgrade and maintain.

Disadvantages:

This is not the case for the SDK library, because before onCreate () of ContentProvider executes the onCreate () method of Application, this is not the case if your library needs other business dependencies. It is necessary to pay attention to the application security vulnerabilities to avoid component exposure. It is necessary to configure exported as false when declaring provider. It must be noted that authorities of Provider must not be written dead, otherwise two App with the same SDK cannot coexist

3. Source code analysis of ContentProvider initializing SDK library

So why can you get application context by initializing ContentProvider? Look at the following several sections of source code to know.


 private void handleBindApplication(AppBindData data) {
     ....
     final InstrumentationInfo ii;
     ....
     if (ii != null) {
       //1. Create ContentImpl
       final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
            try {
                final ClassLoader cl = instrContext.getClassLoader();
                mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
            } catch (Exception e) {
                throw new RuntimeException(
                    "Unable to instantiate instrumentation "
                    + data.instrumentationName + ": " + e.toString(), e);
            }

        //2. Create Instrumentation
      final ComponentName component = new ComponentName(ii.packageName, ii.name);
            mInstrumentation.init(this, instrContext, appContext, component,
                    data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
        ....
        //3. Create Application Object 
         Application app;
         app = data.info.makeApplication(data.restrictedBackupMode, null);

         // Propagate autofill compat state
            app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);

            mInitialApplication = app;

        ...
        //4. Start the in the current process ContentProvider And call its onCreate Method 

        if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);
                    // For process that contains content providers, we want to
                    // ensure that the JIT is enabled "at some point".
                    mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                }
            }

        //5. Call Application Adj. onCreate Method 
        try {
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                if (!mInstrumentation.onException(app, e)) {
                    throw new RuntimeException(
                      "Unable to create application " + app.getClass().getName()
                      + ": " + e.toString(), e);
                }
            }
    }
 }

private void attachInfo(Context context, ProviderInfo info, boolean testing) {
    mNoPerms = testing;

    /*
     * Only allow it to be set once, so after the content service gives
     * this to us clients can't change it.
     */
    if (mContext == null) {
        mContext = context;
        if (context != null) {
            mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService (Context.APP_OPS_SERVICE);
        }
        mMyUid = Process.myUid();
        if (info != null) {
            setReadPermission(info.readPermission);
            setWritePermission(info.writePermission);
            setPathPermissions(info.pathPermissions);
            mExported = info.exported;
            mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
            setAuthorities(info.authority);
        }
        ContentProvider.this.onCreate();
    }
}

It can be seen that provider was loaded during the startup of App, and an instance of Application was passed in, and finally the onCreate () method was called in ContentProvider. Therefore, in a custom ContentProvider, an instance of Application can be obtained through the getContext () method.

In fact, from this source code, we can also see that the onCreate () method in ContentProvider is executed before the onCreate () method in Application (note that the Application object has been created at this time).

4. Google's new component-App Startup

Google's App Startup provides an efficient and direct way to initialize components when an application starts. App Startup can be used by both SDK developers and APP developers to simplify the startup sequence and explicitly set the initialization sequence. App Startup also allows greatly reduced application startup time by defining the initialization of shared ContentProvider 1 components.

If the initialization in the project is synchronous initialization, and multiple ContentProvider are used, App Startup is still good. After all, the system 1 is in one ContentProvider, and simple sequential dependence is supported at the same time.

However, in the scenario of pursuing App performance and startup speed, multiple SDK realize "self-startup" by using their own ContentProvider at the same time, and are optimized under various SDK initialization with sequence and dependence, then App Startup is not very useful. For this reason, App Startup is not currently recommended for use in a production environment.

The current recommended scheme is the same as that we have used before: synchronous + asynchronous initialization, and the initialization order of internal dependent components is guaranteed by topological sorting of directed acyclic graphs.


Related articles: