Android使用ContentProvider初始化SDK库方案总结
做Android SDK开发的时候,一般我们会将初始化的方法封装为,然后让调用SDK的开发者在Application的onCreate方法中进行初始化。但是目前一些主流的SDK框架,并没有提供相关的方法进行初始化,但是我们在使用的时候也能正常使用,通过挖掘其源码,可以看出来他们一般使用的ContentProvider来进行SDK的初始化的,目前使用ContentProvider的知名SDK有:ButterKnife、Leakcanary、BlockCanary...等等。
这里补充一个概念,SDK初始化的本质是什么?
SDK初始化的本质是将App的上下文(Context)注入到SDK中,使其能通过这个上下文访问到App的资源与服务。也包括在初始化时调用SDK方法进行相关选项的自定义配置。
一、ContentProvider初始化SDK库的实现
要实现在ContentProvider初始化SDK库,首先要在库中创建一个 ContentProvider,然后在 ContentProvider 的 onCreate() 方法中借助 getContext() 返回的 Context 来完成你的库初始化,当然,这个 Context 的实际类型就是应用的 Application。
下面是通过ContentProvider实现SDK库初始化的示例代码:
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()
}
/**
* 初始化MMKV工具
*/
private fun initMMKV() {
Log.e(GlobalConfig.LOG_TAG, "init MMKV")
MMKV.initialize(this);
}
private fun initCodeView() {
CodeProcessor.init(this)
}
}
通过ContentProvider实现SDK库初始化的功能实现了,那么 ContentProvider 的 onCreate() 方法是什么时候被调用的呢?
下面是日志输出,来帮助助我们理解初始化时机:
com.renhui.maomaomedia E/MaoMaoMedia: MaoApplication attachBaseContext
com.renhui.maomaomedia E/MaoMaoMedia: ToolContentProvider onCreate
com.renhui.maomaomedia E/MaoMaoMedia: MaoApplication onCreate
可以看到,它是介于 Application 的 attachBaseContext(Context) 和 onCreate() 之间所调用的,Application 的 attachBaseContext(Context) 方法被调用这就意味着 Application 的 Context 被初始化了。这也再次说明我们确实可以通过ContentProvider来进行SDK库的初始化,并且执行时间在Application的onCreate之前。
二、ContentProvider初始化SDK库的优缺点
优点:
- 不需要使用SDK库的开发者调用初始化库的流程,降低了接入成本
- 代码侵入更低,使得SDK库的代码隔离性做的更好,而且方便升级和维护。
缺点:
- 不一定适用SDK库的使用场景,因为在 ContentProvider 的 onCreate() 执行在 Application 的 onCreate() 方法之前,倘若你的库需要有其它业务的依赖,那么就不适合这种方式了。
- 需要注意应用安全漏洞问题,避免组件暴露,需要在声明provider的时候,配置exported为false。
- 必须注意Provider的authorities千万别写死,否则两个引入同样SDK的App就无法共存了
三、ContentProvider初始化SDK库实现的源码分析
那么为什么在ContentProvider做初始化,能获取到application context的呢?看一下下面几段源码就能知道了。
private void handleBindApplication(AppBindData data) {
....
final InstrumentationInfo ii;
....
if (ii != null) {
//1.创建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.创建Instrumentation
final ComponentName component = new ComponentName(ii.packageName, ii.name);
mInstrumentation.init(this, instrContext, appContext, component,
data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
....
//3.创建Application对象
Application app;
app = data.info.makeApplication(data.restrictedBackupMode, null);
// Propagate autofill compat state
app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);
mInitialApplication = app;
...
//4.启动当前进程中的ContentProvider和调用其onCreate方法
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.调用Application的onCreate方法
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();
}
}
可以看到App的启动过程中加载了provider,并且传了一个Application实例进去,最终在ContentProvider中调用了onCreate()方法。因此,在自定义的ContentProvider中,通过getContext()方法就可以获取到Application的实例了。
其实从这段源码中,我们也可以看到,ContentProvider中的onCreate()方法是先于Application中的onCreate()方法执行的(注意:此时Application对象已经创建)。
四、谷歌的新组件 - App Startup
谷歌推出的App Startup提供了一种在应用程序启动时高效、直接初始化组件的方法。SDK开发人员和APP开发人员都可以使用App Startup简化启动顺序并显式设置初始化顺序。App Startup还允许通过定义共享的ContentProvider统一组件的初始化,大大缩短应用启动时间。
如果项目中的初始化都是同步初始化的话,并且使用到了多个ContentProvider,App Startup 还是不错的,毕竟统一到了一个ContentProvider中,同时支持了简单的顺序依赖。
但是如果在追求App性能与启动速度的场景中,多个SDK同时利用各自定义的ContentProvider实现“自启动”, 在各种有先后顺序与依赖的SDK初始化下做优化,那么 App Startup 就不是很好用了。也正式这个原因,目前不建议将 App Startup 用于生产环境中。
目前的推荐方案还是之前我们都使用过的:同步+异步初始化,并通过有向无环图拓扑排序的方式来保证内部依赖组件的初始化顺序。
Android使用ContentProvider初始化SDK库方案总结的更多相关文章
- 各种Android UI开源框架 开源库
各种Android UI开源框架 开源库 转 https://blog.csdn.net/zhangdi_gdk2016/article/details/84643668 自己总结的Android开源 ...
- Android高效率编码-第三方SDK详解系列(一)——百度地图,绘制,覆盖物,导航,定位,细腻分解!
Android高效率编码-第三方SDK详解系列(一)--百度地图,绘制,覆盖物,导航,定位,细腻分解! 这是一个系列,但是我也不确定具体会更新多少期,最近很忙,主要还是效率的问题,所以一些有效的东西还 ...
- Android 开源项目android-open-project工具库解析之(一) 依赖注入,图片缓存,网络相关,数据库orm工具包,Android公共库
一.依赖注入DI 通过依赖注入降低View.服务.资源简化初始化.事件绑定等反复繁琐工作 AndroidAnnotations(Code Diet) android高速开发框架 项目地址:https: ...
- Android消息推送 SDK 集成指南
使用提示 本文是 Android SDK 标准的集成指南文档. 匹配的 SDK 版本为:r1.8.0及以后版本. 本文随SDK压缩包分发.在你看到本文时,可能当前的版本与本文已经不是很适配.所以建议关 ...
- Xamarin.Android 集成百度地图SDK
前言:趁着周六闲得没事干,赶紧搞一搞Xamarin,最近也是怪无聊的,枯燥的生活不如打几行代码带劲:好了我们进入正题 我这篇文章时参考一位大佬的博客进行改变的,当然他写的需要一定的经验才可以看得懂,我 ...
- 编写ios和android共用的c/c++库时 使用iconv的问题(转)
因为在项目中需要同时维护ios和Android,不同的代码不利于开发的便捷和以后的维护,所以在最近的一个项目中,两种手机应用的通信部分打算使用c/c++库来统一编写,ios调用.a静态库,androi ...
- 【转载】cocos2dx 中 Android NDK 加载动态库的问题
原文地址:http://blog.csdn.net/sozell/article/details/10551309 cocos2dx 中 Android NDK 加载动态库的问题 闲聊 最近在接入各 ...
- 《Android原生整合虹软SDK开发uniapp插件》
1.项目背景 应公司要求,需要开发一套类似人脸打卡功能的app,但是因为我们公司没有很强的原生android开发者,所以根据现状选择了第三方跨平台的uniapp,想必目前大多人都了解这个平台了,我也就 ...
- Android之ContentProvider数据存储
一.ContentProvider保存数据介绍 一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据完全暴露出去,而且ContentProvider是以类似数据库中表的方式将数 ...
随机推荐
- io流+网络+线程池 实现简单的多客户端与服务器端通信
1 import java.io.IOException; 2 import java.io.InputStream; 3 import java.io.OutputStream; 4 import ...
- es命令测试
1.新建索引并赋值 :put/索引名/文档名/id //文档名后面会逐渐取消 相当表 PUT /test1/type1/1{ "nmae":"hb", &quo ...
- ImportError: No module named _ssl解决方法
import ssl时出现ImportError: No module named _ssl错误是因为咱安装Python的时候没有把ssl模块编译进去导致的. 解决步骤: 系统没有openssl,手动 ...
- 【转载】快速理解android View的测量onMeasure()与MeasureSpec
笔者之前有一篇文章已经使用onMeasure()解决了listview与scollview的显示冲突问题,博客地址如下: onMeasure简单方法 完美解决ListView与ScollView冲突问 ...
- 文件查询 select name,age where age>22
# 员工信息表: 完善代码,背下来给代码加注释column_dic = {'id': 0, 'name': 1, 'age': 2, 'phone': 3, 'job': 4} # 将文件每一列的名字 ...
- PicGo 图床配置【工具篇】
Github图床(舍弃) step1 下载PicGo 下载链接: https://github.com/Molunerfinn/picgo/releases step2 新建仓库作为上传图片的目标地址 ...
- 前端坑多:使用js模拟按键输入的踩坑记录
坑 一开始在Google搜索了一番,找到了用jQuery的方案,代码量很少,看起来很美好很不错,结果,根本没用-- 我反复试了这几个版本: var e = $.Event('keyup') e.key ...
- MySql历史与架构
MySQL 逻辑架构
- js前端技术
一.前端技术 1.HTML HTML(hypertext markup language)超文本标记语言,不同于编程语言. 超文本就是超出纯文本的范畴,描述文本的颜色.大小.字体. HTML由一个个标 ...
- python 序列与字典
序列概念: 序列的成员有序排列,可以通过下标访问到一个或几个元素,就类似与c语言的数组. 序列的通用的操作: 1:索引 11 = [1,2,3,4] 11[0] = 1 2:切片 11[1,2,3,4 ...