查看原文
其他

巧用 ContentProvider 实现“无侵”初始化

fundroid AndroidPub 2022-05-15


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

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

我们可以效仿上面实现,在我们自己的 Library 中使用 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 即可。

FIN




推荐阅读:


丢掉 onActivityResult,拥抱 Result API


Compose 1.0 即将发布,你准备好了吗?


MVI 架构:从双向绑定到单向数据流


打造一个 Compose 版的俄罗斯方块




↓关注公众号↓↓添加微信交流↓




您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存