巧用 ContentProvider 帮助你的 Library 实现无侵初始化

Posted fundroid_方卓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了巧用 ContentProvider 帮助你的 Library 实现无侵初始化相关的知识,希望对你有一定的参考价值。

在这里插入图片描述
在使用某些第三方库时,经常需要为其传入 Context 以保证其正常工作。一般我们在 ApplicationonCreate 中进行初始化操作。此时推荐一个小技巧:借助 ContentProvider 实现完全"无侵"的初始化, 让 SDK 更加易用。


ContentProvider 初始化原理


ContentProvider 的 onCreate 的调用在 Application 的 attachBaseContextonCreate 之间, 此时 Application 已经创建成功, 因此在 ContentProvider 的 onCreate 中就可以获取 AppContext 完成初始化。

通过源码可以验证上述时序关系:

//ActivityThread.java

private void handleBindApplication(AppBindData data) {
    ...
    //1. 创建Application
    Applicatiohandlen app = data.info.makeApplication(data.restrictedBackupMode, null); 
    ...
    //2. 内部调用ContentProvider#onCreate
    installContentProviders(app, data.providers);
    ...
    //3. 内部调用Application#onCreate
    mInstrumentation.callApplicationOnCreate(app);

}

installContentProviders 内部调用 installProvider

private IActivityManager.ContentProviderHolder installProvider(Context context,IActivityManager.ContentProviderHolder holder, ProviderInfo info,boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ...
    final java.lang.ClassLoader cl = c.getClassLoader();
                localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
                provider = localProvider.getIContentProvider();
                if (provider == null) {
                    Slog.e(TAG, "Failed to instantiate class " +
                          info.name + " from sourceDir " +
                          info.applicationInfo.sourceDir);
                    return null;
                }
                localProvider.attachInfo(c, info);
    ...

}

从 ClassLoader 里加载 ContentProvider 类实例,并调用 attachInfo 方法

private void attachInfo(Context context, ProviderInfo info, boolean testing) {
    ...
    ContentProvider.this.onCreate();
    ...
}

这里调用了 ContentProvider 的 onCreate 。

接下来看几个借助 ContentProvider 初始化的例子。


Lifecycle 中的 ContentProvider


熟悉 Lifecycle 源码的人都知道,在 API26 之前,Activity 尚未实现 LifecycleOwner 接口,生命周期分发借助 LifecycleDispatcherProcessLifecycleOwner 实现。这不是本文讨论的重点,本文关注的是这几个对象的初始化时机,都是在 ProcessLifecycleOwnerInitializer 中进行初始化的。

public class ProcessLifecycleOwnerInitializer extends ContentProvider {
    @Override
    public boolean onCreate() {
        //Application中注册DispatcherActivityCallback
        LifecycleDispatcher.init(getContext()); 
        //DispatcherActivityCallback回调中为Activity添加ReportFramgent
        ProcessLifecycleOwner.init(getContext());
        return true;
    }
}

ProcessLifecycleOwnerInitializer 实际上是一个 ContentProvider, 在 onCreate 中进行上述初始化。

打开打包生成的apk文件,查看 androidManifest.xml,可以找到:

<provider
    android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer"
    android:exported="false"
    android:multiprocess="true"
    android:authorities="com.my.livedatademo.lifecycle-process" />

这个 ContentProvider 并不需要我们在 Manifest 中手动注册,它是在 androidx.lifecycle:lifecycle-process 的 aar 的 AndroidManifest 中定义的:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="androidx.lifecycle.process" >

    <uses-sdk android:minSdkVersion="14" />

    <application>
        <provider
            android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer"
            android:authorities="${applicationId}.lifecycle-process"
            android:exported="false"
            android:multiprocess="true" />
    </application>

</manifest>

这里还使用了 ${applicationId} 占位符,避免 authroities 冲突。


LeakCanery 中的 ContentProvider


除了 Lifecycle 之外,其他很多三方库也是这做的,比如 LeakCanary 的 1.0 版本需要开发者在 Application 的 onCreate 中手动初始化,但是 2.0 开始只需要添加一下 gradle 就可以了

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'

阅读源码可知,在 leakcancary-leaksentry 的 AndroidManifest 中同样有 Provider 的定义:

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.leakcanary.leaksentry"
    >

  <application>
    <provider
        android:name="leakcanary.internal.LeakSentryInstaller"
        android:authorities="${applicationId}.leak-sentry-installer"
        android:exported="false"/>
  </application>
</manifest>

可以猜到 LeakSentryInstaller 中进行必要的初始化。


Picasso 中的 ContentProvider


最后再看看同样出自方块公司的 Picasso :

public final class PicassoProvider extends ContentProvider {

    @SuppressLint("StaticFieldLeak") static Context context;

    @Override public boolean onCreate() {
        context = getContext();
        return true;
    }

}

AndridManifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.picasso"
    >

  <application>
    <provider
        android:name="com.squareup.picasso.PicassoProvider"
        android:authorities="${applicationId}.com.squareup.picasso"
        android:exported="false"/>
  </application>
</manifest>

自定义初始化 ContentProvider

我们可以效仿上面实现,在我们自己的 SDK 中使用 ContentProvider 进行初始化:

class LibraryInitializer: ContentProvider() {

    override fun onCreate(): Boolean {
        // 可以拿到ApplicationContext进行有些初始化操作
        return true
    }
    
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        return null
    }

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

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

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

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

}

然后在 Module 内部的 AndroidManifest.xml 中注册该 ContentProvider 即可。

以上是关于巧用 ContentProvider 帮助你的 Library 实现无侵初始化的主要内容,如果未能解决你的问题,请参考以下文章

如何使用ContentResolver

ContentProvider

巧用Vscode编辑器,快速格式化代码,让你的代码变得整洁又美观

巧用Vscode编辑器,快速格式化代码,让你的代码变得整洁又美观

巧用Vscode编辑器,快速格式化代码,让你的代码变得整洁又美观

contentprovider是如何实现数据共享