QigsawBundle之ContentProvider优化
缘由
其实不做优化,DynamicProviderSwitch也够用了。主要是想简化编译流程,提高编译速度,且尽量和官方流程一样。旧方案在api>=28的安卓版本上使用了隐藏api,新版本中可能变更。这次优化中使用api28新增的AppComponentFactory来替代隐藏api,以提升稳定性。api>=28时,使用的全是公开api。
Provider在应用启动时就会被调用,Dynamic中的Provider第一次启动是找不到的,应用会直接报错,无法启动。Qigsaw为每个DynamicProvider生成了一个代理类xxxContentProviderProxy(或叫装饰类),找不到原DynamicProvider时,就调用代理类。
QigsawBundle不生成任何装饰类,而是通过工具DynamicProviderSwitch自动把DynamicProvider设置为关闭状态(android:enabled="false")。启动应用时,再读取manifest中哪些DynamicProvider类存在,如果存在则启动Provider,不存在则忽略。Split安装后重试未启动成功的DynamicProvider。
DynamicProviderSwitch:2.0.0 以前是在编译期间修改编译输出的manifest文件来禁用DynamicProvider。2.0.0以后是在运行时,通过代码禁用DynamicProvider的。
干货
很早以前就发现一个工具HookUtil,觉得可以用来优化DynamicProviderSwitch,最近有时间就尝试并完成了,主要原理如下。
api<28
当api<28时,通过HookUtil在启动Provider前,把DynamicProvider禁用并从启动列表移除。
主要代码如下
/**
* api>=28 -> HookProviderFactory & ContentProviderProxy,
* else disable Provider.
* must call in Application#attachBaseContext
* */
fun compatDynamicProvider(base: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
try {
attachContext()
disableDynamicProviders(base)
initProvider(base)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
api>=28
当api>=28时,通过HookProviderFactory替换不存在的类为ContentProviderProxy(代码来自Qigsaw),然后通过ContentProviderProxy代理Provider的所有行为。每次触发事件时,尝试加载真实类。如果加载成功,以后则用真实类完成请求。这个逻辑和Qigsaw基本一致,但Qigsaw是在编译期间生成代理类,QigsawBundle则是用DynamicProviderSwitch在运行时替换成代理类。
@RequiresApi(Build.VERSION_CODES.P)
open class HookProviderFactory : AppComponentFactory() {
private val TAG = "ProviderSwitch"
override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
if (BuildConfig.DEBUG) Log.i(TAG, "instantiateProviderCompat $className")
val dynamicProvider = try {
Class.forName(className).name.isEmpty()
} catch (e: ClassNotFoundException) {
true
}
return if (dynamicProvider) {
if (BuildConfig.DEBUG) Log.i(TAG, "ContentProviderProxy $className")
super.instantiateProviderCompat(cl, ContentProviderProxy::class.java.name)
} else {
super.instantiateProviderCompat(cl, className)
}
}
}