QigsawBundle之ContentProvider优化

2022-03-07  本文已影响0人  DonaldDu

缘由

其实不做优化,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)
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读