巧用 ContentProvider 帮助你的 Library 实现无侵初始化
Posted fundroid_方卓
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了巧用 ContentProvider 帮助你的 Library 实现无侵初始化相关的知识,希望对你有一定的参考价值。
在使用某些第三方库时,经常需要为其传入 Context 以保证其正常工作。一般我们在 Application
的 onCreate
中进行初始化操作。此时推荐一个小技巧:借助 ContentProvider
实现完全"无侵"的初始化, 让 SDK 更加易用。
ContentProvider 初始化原理
ContentProvider 的 onCreate
的调用在 Application 的 attachBaseContext
和 onCreate
之间, 此时 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 接口,生命周期分发借助 LifecycleDispatcher
和 ProcessLifecycleOwner
实现。这不是本文讨论的重点,本文关注的是这几个对象的初始化时机,都是在 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 实现无侵初始化的主要内容,如果未能解决你的问题,请参考以下文章
巧用Vscode编辑器,快速格式化代码,让你的代码变得整洁又美观
巧用Vscode编辑器,快速格式化代码,让你的代码变得整洁又美观