探索Android开源框架 - 10. 插件化原理

什么是插件化

  • 插件化技术最初源于免安装运行apk的想法,这个免安装的apk可以理解为插件
  • 将app中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现app功能的动态扩展;
  • 插件框架有两个作用:一是“自解耦”,二是“免安装”
    • 自解耦指的是一个应用原本由一份代码编译而成,希望改成将其中的一些功能单独编译,像插件一样动态插在主应用上。这样一来可是使主应用体积变小,下载安装更方便。二来可以是比较独立的功能可以单独开发调试,甚至单独更新版本。
    • 免安装指的一个应用原本需要安装过程才能启动运行,希望改为无需安装即可从一个已经安装运行的App中启动起来。这一需求的主要目的是提高流量复用的能力。

插件化发展史

  1. AndroidDynamicLoader:给予 Fragment 实现了插件化框架,可以动态加载插件中的 Fragment 实现页面的切换;
  2. dynamic-load-apk(任玉刚):最早使用ProxyActivity这种静态代理技术,由ProxyActivity去控制插件中PluginActivity的生命周期(缺点:插件中的activity必须继承PluginActivity,开发时要小心处理context);
  3. DroidPlugin:通过Hook系统服务的方式启动插件中的Activity,使得开发插件的过程和开发普通的app没有什么区别(缺点:由于hook过多系统服务,异常复杂且不够稳定)
  • 之后各个框架在实现原理上都是趋近于选择尽量少的hook,并通过在manifest中预埋一些组件实现对四大组件的插件化,又做了不同程度的扩展
  1. 携程 DynamicApk
  2. VirtualApp:能够完全模拟app的运行环境,能够实现app的免安装运行和双开技术
  3. Small: 一个跨平台插件化框架
  4. 360 RePlugin
  5. 滴滴 VirtualApk
  6. 阿里 Atlas:一个结合组件化和热修复技术的一个app基础框架,号称是一个容器化框架
  7. 腾讯 Shadow:一个完全无Hack,甚至零反射实现的Android插件框架,插件的代码完全是一个正常可安装的App代码,无需引用任何Shadow的库

插件化原理

类加载 ClassLoader

java 中的 ClassLoader:

  1. BootstrapClassLoader:负责加载 JVM 运行时的核心类,比如 JAVA_HOME/lib/rt.jar 等
  2. ExtensionClassLoader:负责加载 JVM 的扩展类,比如 JAVA_HOME/lib/ext 下面的 jar 包
  3. AppClassLoader:负责加载 classpath 里的 jar 包和目录

android 中的 ClassLoader:

  • PathClassLoader 和 DexClassLoader 都能加载外部的 dex/apk,只不过区别是 DexClassLoader 可以指定 optimizedDirectory,也就是 dex2oat 的产物 .odex 存放的位置,而 PathClassLoader 只能使用系统默认位置。
  • 但是这个 optimizedDirectory 在 Android 8.0 以后也被舍弃了,只能使用系统默认的位置了,也就是说,在 8.0 上,PathClassLoader 和 DexClassLoader 其实已经没有什么区别了。

双亲委派机制:

  • 每一个 ClassLoader 中都有一个 parent 对象,代表的是父类加载器,在加载一个类的时候,会先使用父类加载器去加载,如果在父类加载器中没有找到,自己再进行加载,如果 parent 为空,那么就用系统类加载器来加载。通过这样的机制可以保证系统类都是由系统类加载器加载的。

如何加载插件中的类

  • 通过给插件apk生成相应的DexClassLoader便可以访问其中的类,这里又有两种实现方案单DexClassLoader与多DexClassLoader
单DexClassLoader
  • 插件的DexClassLoader中的pathList合并到主工程的DexClassLoader中
  • 优点是不同的插件以及主工程间可以直接互相调用类和方法,并且可以将不同插件的公共模块抽出来放在一个common插件中直接供其他插件使用(Small采用此方案)
  • 缺点:若两个不同的插件工程引用了一个库的不同版本,则程序可能会出错,所以要通过一些规范去避免该情况发生。
  • 实现代码如下
binding.btnSingleDexClassLoader.setOnClickListener {
    loadDex(this, listOf(plugin001Path,plugin002Path))

    val clazzApp = Class.forName("com.jinyang.plugindemo.TestApp")
    val methodApp = clazzApp.getMethod("test")
    methodApp.invoke(clazzApp.newInstance())

    val clazzPlugin001 = Class.forName("com.jinyang.plugin001.TestPlugin001")
    val methodPlugin001 = clazzPlugin001.getMethod("test")
    methodPlugin001.invoke(clazzPlugin001.newInstance())

    val clazzPlugin002 = Class.forName("com.jinyang.plugin002.TestPlugin002")
    val methodPlugin002 = clazzPlugin002.getMethod("test")
    methodPlugin002.invoke(clazzPlugin002.newInstance())
}

fun loadDex(context: Context, pluginPaths: List) {
    try {
        // 获取 pathList
        val systemClassLoader = Class.forName("dalvik.system.BaseDexClassLoader")
        val pathListField = systemClassLoader.getDeclaredField("pathList")
        pathListField.isAccessible = true
        // 获取 dexElements
        val dexPathListClass = Class.forName("dalvik.system.DexPathList")
        val dexElementsField = dexPathListClass.getDeclaredField("dexElements")
        dexElementsField.isAccessible = true
        // 获取宿主的Elements
        val hostClassLoader = context.classLoader
        val hostPathList = pathListField.get(hostClassLoader)
        val hostElements = dexElementsField.get(hostPathList) as kotlin.Array<*>
        var newElements: kotlin.Array<*> = hostElements
        // 遍历获取插件的Elements
        for (path in pluginPaths) {
            val pluginClassLoader = PathClassLoader(path, context.classLoader)
            val pluginPathList = pathListField.get(pluginClassLoader)
            val pluginElements = dexElementsField.get(pluginPathList) as kotlin.Array<*>
            // 创建数组
            val temp = Array.newInstance(
                pluginElements.javaClass.componentType!!,
                newElements.size + pluginElements.size
            ) as kotlin.Array<*>

            // 给新数组赋值,先用宿主的,再用插件的
            System.arraycopy(newElements, 0, temp, 0, newElements.size)
            System.arraycopy(
                pluginElements,
                0,
                temp,
                newElements.size,
                pluginElements.size
            )
            // 合并
            dexElementsField.set(hostPathList, temp)
            newElements = temp
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}
多DexClassLoader
  • 为每个插件都生成一个DexClassLoader,当加载该插件中的类时需要通过对应DexClassLoader加载
  • 这种方案的优点是不同插件的类是隔离的,当不同插件引用了同一个类库的不同版本时,不会出问题(RePlugin采用此方案)
  • 代码如下
val nativeLibDir = File(filesDir, "pluginlib").absolutePath
val dexOutPath = File(filesDir, "dexout").absolutePath

val plugin001Path = File(filesDir.absolutePath, "plugin001.apk").absolutePath
val pluginClassLoader = DexClassLoader(plugin001Path, dexOutPath, nativeLibDir, this::class.java.classLoader)

val plugin002Path: String = File(filesDir.absolutePath, "plugin002.apk").absolutePath
val pluginClassLoader2 = DexClassLoader(plugin002Path, dexOutPath, nativeLibDir, this::class.java.classLoader)

资源加载

  • Android系统通过Resource对象加载资源,因此,只要将插件apk的路径加入到AssetManager中,便能够实现对插件资源的访问
资源路径的处理
1. 合并式:
  • addAssetPath时加入所有插件和主工程的路径;
  • 优点:插件和宿主能直接相互访问资源
  • 缺点:会引入资源冲突(由于主工程和各个插件都是独立编译的,生成的资源id会存在相同的情况)
  • 实现代码如下,其中我将插件的包名与宿主设置成相同的,具体原因见下面shadow部分的说明:
binding.btnPrintResources.setOnClickListener {
    val plugin001Path = File(filesDir.absolutePath, "plugin001.apk").absolutePath
    val plugin002Path: String = File(filesDir.absolutePath, "plugin002.apk").absolutePath
    val mResources = loadResources(this,resources.assets, listOf(pluginPath, pluginPath2))
    val strAppId = mResources?.getIdentifier("str_app", "string", "com.jinyang.plugindemo")
    log("str_app:"+ strAppId?.let { it1 -> mResources.getString(it1) })
    val strPlugin001Id = mResources?.getIdentifier("str_plugin001", "string", "com.jinyang.plugindemo")
    log("str_plugin001:"+ strPlugin001Id?.let { it1 -> mResources.getString(it1) })
    val strPlugin002Id = mResources?.getIdentifier("str_plugin002", "string", "com.jinyang.plugindemo")
    log("str_plugin002:"+ strPlugin002Id?.let { it1 -> mResources.getString(it1) })
}

fun loadResources(context: Context,assetManager:AssetManager, pluginPaths: List): Resources? {
        try {
            val addAssetPathMethod = assetManager::class.java.getDeclaredMethod("addAssetPath", String::class.java)
            addAssetPathMethod.isAccessible = true
            for (path in pluginPaths) {
                addAssetPathMethod.invoke(assetManager, path)
            }
            return Resources(
                assetManager,
                context.resources.displayMetrics,
                context.resources.configuration
            )
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
}
2. 独立式:
  • 各个插件只添加自己apk路径
  • 优点:资源隔离,不存在资源冲突
  • 缺点:资源共享比较麻烦(如果想要实现资源的共享,必须拿到对应的Resource对象)
  • 实现方式如下,在各个插件的baseActivity中重写getResources,getAssets方法
open class PluginBaseActivity : Activity() {
    private var pluginClassLoader: ClassLoader? = null
    private var pluginPath: String?=null
    private var pluginAssetManager: AssetManager? = null
    private var pluginResources: Resources? = null
    private var pluginTheme: Resources.Theme? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val nativeLibDir = File(filesDir, "pluginlib").absolutePath
        val dexOutPath = File(filesDir, "dexout").absolutePath
        pluginPath = File(filesDir.absolutePath, "plugin002.apk").absolutePath
        pluginClassLoader = DexClassLoader(pluginPath, dexOutPath, nativeLibDir, this::class.java.classLoader)
        handleResources()
    }

    override fun getResources(): Resources? {
        return pluginResources ?: super.getResources()
    }

    override fun getAssets(): AssetManager {
        return pluginAssetManager ?: super.getAssets()
    }

    override fun getClassLoader(): ClassLoader {
        return pluginClassLoader ?: super.getClassLoader()
    }

    private fun handleResources() {
        try {
            pluginAssetManager = AssetManager::class.java.newInstance()
            val addAssetPathMethod = pluginAssetManager?.javaClass?.getMethod("addAssetPath", String::class.java)
            addAssetPathMethod?.invoke(pluginAssetManager, pluginPath)
        } catch (e: Exception) {
        }
        pluginResources = Resources(pluginAssetManager, super.getResources().displayMetrics, super.getResources().configuration)
        pluginTheme = pluginResources?.newTheme()
        pluginTheme?.setTo(super.getTheme())
    }
}
解决资源id冲突问题的方法:
  1. 修改aapt源码,定制aapt工具,编译期间修改PP段;(DynamicAPK使用此方案),原理参考:Android中如何修改编译的资源ID值
  2. 修改aapt的产物resources.arsc文件,即,编译后期重新整理插件Apk的资源,编排ID;(VirtualApk使用此方案),原理参考:插件化-解决插件资源ID与宿主资源ID冲突的问题
  3. 通过配置aaptOptions,build.gradle中的android节点加入如下代码,不过此方法只有在compileSdkVersion为28及以上才生效
android {
    aaptOptions {
        additionalParameters  "--package-id", "0x66","--allow-reserved-package-id"
    }
    ...
}
Context的处理
  • 通常我们通过Context对象访问资源,光创建出Resource对象还不够,因此还需要一些额外的工作
// 获取自己创建的resources
val resources = LoadUtils.getResources(application)
// 创建自己的Context
mContext = ContextThemeWrapper(baseContext, 0)
// 把自己的Context中的resources替换为我们自己的
val clazz = mContext::class.java
val mResourcesField = clazz.getDeclaredField("mResources")
mResourcesField.isAccessible = true
mResourcesField.set(mContext, resources)
  • 也可以参考VirtualAPK中的实现:com.didi.virtualapk.internal.ResourcesManager
public static void hookResources(Context base, Resources resources) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        return;
    }
    try {
        // 替换主工程context中LoadedApk的mResource对象
        Reflector reflector = Reflector.with(base);
        reflector.field("mResources").set(resources);
        Object loadedApk = reflector.field("mPackageInfo").get();
        Reflector.with(loadedApk).field("mResources").set(resources);

        // 将新的Resource添加到主工程ActivityThread的mResourceManager中,并且根据Android版本做了不同处理
        Object activityThread = ActivityThread.currentActivityThread();
        Object resManager;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            resManager = android.app.ResourcesManager.getInstance();
        } else {
            resManager = Reflector.with(activityThread).field("mResourcesManager").get();
        }
        Map> map = Reflector.with(resManager).field("mActiveResources").get();
        Object key = map.keySet().iterator().next();
        map.put(key, new WeakReference<>(resources));
    } catch (Exception e) {
        Log.w(TAG, e);
    }
}

加载四大组件

  • 四大组件的插件化是插件化技术的核心

加载 插件Activity

插件Activity的两个问题
  • 在宿主的Manifest没有注册:插件是动态加载的,所以插件的四大组件不可能注册到宿主的 Manifest 文件中,启动一个没有在 Manifest 中注册的 Activity会报错ActivityNotFoundException
  • 生命周期无法被调用:一个 Activity 主要的工作,都是在其生命周期方法中调用的
解决方法
  1. 手动去调用插件 Activity 的生命周期;
  2. 欺骗系统,让系统以为 Activity 是注册在 Manifest 中的
  • 调用插件 Activity 的生命周期主要有三种实现方式,反射、接口和Hook实现, 其中反射和接口 实现相对简单,不需要对系统内部实现做过多了解;而且比较稳定,不用适配各种厂商ROM及不同Android版本的API,但是通过反射效率太低,通过接口需要实现的方法数量很多;
1. 反射实现 调用 插件Activity 的生命周期:
  1. 创建一个反射生命周期的工具类ReflectActivityLifeCircle,其中通过class.getMethod来反射调用Activity的各个声明周期方法,代码如下:
class ReflectActivityLifeCircle(activity: String?, activityClassLoader: ClassLoader?) {
    private var clazz: Class? = activityClassLoader?.loadClass(activity) as Class?
    private var activity: Activity? = clazz?.newInstance()

    private fun getMethod(methodName: String, vararg params: Class<*>): Method? {
        return clazz?.getMethod(methodName, *params)
    }

    fun attach(proxyActivity: Activity?) {
        getMethod("attach", Activity::class.java)?.invoke(activity, proxyActivity)   }

    fun onCreate(savedInstanceState: Bundle?) {
        getMethod("onCreate", Bundle::class.java)?.invoke(activity, savedInstanceState)
    }

    fun onStart() {
        getMethod("onStart")?.invoke(activity)
    }

    fun onResume() {
        getMethod("onResume")?.invoke(activity)
    }

    fun onPause() {
        getMethod("onPause")?.invoke(activity)
    }

    fun onStop() {
        getMethod("onStop")?.invoke(activity)
    }

    fun onDestroy() {
        getMethod("onDestroy")?.invoke(activity)
    }
}
  1. 在宿主中创建一个代理Activity,其生命周期直接调用ReflectActivityLifeCircle的方法,通过反射调用插件Activity的生命周期,代码如下
class ProxyReflectActivity : ProxyBaseActivity() {
    private var reflectActivityLifeCircle: ReflectActivityLifeCircle? = null


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val nativeLibDir = File(filesDir, "pluginlib").absolutePath
        val dexOutPath = File(filesDir, "dexout").absolutePath
        val pluginPath = intent.getStringExtra("pluginPath")
        val pluginActivityName = intent.getStringExtra("activityName")
        val pluginClassLoader = DexClassLoader(pluginPath, dexOutPath, nativeLibDir, this::class.java.classLoader)
        reflectActivityLifeCircle= ReflectActivityLifeCircle(pluginActivityName,pluginClassLoader)
        reflectActivityLifeCircle?.attach(this)
        reflectActivityLifeCircle?.onCreate(savedInstanceState)
    }

    override fun onStart() {
        super.onStart()
        reflectActivityLifeCircle?.onStart()
    }

    override fun onResume() {
        super.onResume()
        reflectActivityLifeCircle?.onResume()
    }

    override fun onPause() {
        super.onPause()
        reflectActivityLifeCircle?.onPause()
    }

    override fun onStop() {
        super.onStop()
        reflectActivityLifeCircle?.onStop()
    }

    override fun onDestroy() {
        super.onDestroy()
        reflectActivityLifeCircle?.onDestroy()
    }

    companion object{
        fun startPluginActivity(context: Context, pluginPath: String, activityName: String) {
            val intent = Intent(context, ProxyReflectActivity::class.java)
            intent.putExtra("pluginPath", pluginPath)
            intent.putExtra("activityName", activityName)
            context.startActivity(intent)
        }
    }

}
  • 反射实现会对性能有所影响,主流的插件化框架没有采用此方式
2. 通过接口实现 调用 插件Activity 的生命周期
  1. 定义一个接口,注意宿主和插件中用的接口全路径应相同
interface IPluginActivity {
    fun attach(proxyActivity: Activity)
    fun onCreate(savedInstanceState: Bundle?)
    fun onStart()
    fun onResume()
    fun onPause()
    fun onStop()
    fun onDestroy()
}
  1. 在插件的baseActivity中实现该接口
open class BasePluginActivity : Activity(), IPluginActivity {
    var proxyActivity: Activity? = null

    override fun attach(proxyActivity: Activity) {
        this.proxyActivity = proxyActivity
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        if (proxyActivity == null) {
            super.onCreate(savedInstanceState)
        }
    }

    override fun setContentView(layoutResID: Int) {
        log("proxyActivity=$proxyActivity,layoutResID=$layoutResID")
        proxyActivity?.let {
            it.setContentView(layoutResID)
        } ?: run {
            super.setContentView(layoutResID)
        }
    }

    override fun setContentView(view: View?) {
        proxyActivity?.let {
            it.setContentView(view)
        } ?: run {
            super.setContentView(view)
        }
    }

    override fun onStart() {
        if (proxyActivity == null) {
            super.onStart()
        }
    }

    override fun onResume() {
        if (proxyActivity == null) {
            super.onResume()
        }
    }

    override fun onPause() {
        if (proxyActivity == null) {
            super.onPause()
        }
    }

    override fun onStop() {
        if (proxyActivity == null) {
            super.onStop()
        }
    }

    override fun onDestroy() {
        if (proxyActivity == null) {
            super.onDestroy()
        }
    }

    override fun getResources(): Resources? {
        if (proxyActivity == null) {
            return super.getResources()
        }
        return proxyActivity?.resources
    }

    override fun getTheme(): Resources.Theme? {
        if (proxyActivity == null) {
            return super.getTheme()
        }
        return proxyActivity?.theme
    }

    override fun getLayoutInflater(): LayoutInflater {
        if (proxyActivity == null) {
            return super.getLayoutInflater()
        }
        return proxyActivity?.layoutInflater!!
    }
}
  1. 在宿主中创建一个代理Activity,通过插件的classLoader及插件ActivityName获取插件Activity实例,并强转为IPluginActivity类型,并在宿主Activity的生命周期中调用IPluginActivity对应方法
class ProxyInterfaceActivity : Activity() {
    private  var activity: IPluginActivity?=null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val nativeLibDir = File(filesDir, "pluginlib").absolutePath
        val dexOutPath = File(filesDir, "dexout").absolutePath
        val pluginPath = intent.getStringExtra("pluginPath")
        val pluginActivityName = intent.getStringExtra("activityName")
        val pluginClassLoader = DexClassLoader(pluginPath, dexOutPath, nativeLibDir, this::class.java.classLoader)
        //通过插件的classLoader及插件ActivityName获取插件Activity实例,并强转为IPluginActivity类型
        activity=pluginClassLoader?.loadClass(pluginActivityName)?.newInstance() as IPluginActivity
        //在宿主Activity的生命周期中调用IPluginActivity对应生命周期方法
        activity?.attach(this)
        activity?.onCreate(savedInstanceState)
    }

    override fun onStart() {
        super.onStart()
        activity?.onStart()
    }

    override fun onResume() {
        super.onResume()
        activity?.onResume()
    }

    override fun onPause() {
        super.onPause()
        activity?.onPause()
    }

    override fun onStop() {
        super.onStop()
        activity?.onStop()
    }

    override fun onDestroy() {
        super.onDestroy()
        activity?.onDestroy()
    }

    companion object {
        fun startPluginActivity(context: Context, pluginPath: String, activityName: String) {
            val intent = Intent(context, ProxyInterfaceActivity::class.java)
            intent.putExtra("pluginPath", pluginPath)
            intent.putExtra("activityName", activityName)
            context.startActivity(intent)
        }
    }
}
3. Hook实现
  • 主要有两种解决方案 ,一种是通过Hook IActivityManager来实现,一种是Hook Instrumentation实现
Activity的启动过程
  • 分为两种,一种是根Activity的启动过程,一种是普通Activity的启动过程
  • 根Activity的启动过程: 首先Launcher进程向AMS请求创建根Activity,AMS会判断根Activity所需的应用程序进程是否存在并启动,如果不存在就会请求Zygote进程创建应用程序进程。应用程序进程启动后,AMS会请求应用程序进程创建并启动根Activity。
  • 普通Activity的启动过程: 在应用程序进程中的Activity向AMS请求创建普通Activity,AMS会对这个Activty的生命周期管和栈进行管理,校验Activity等等。如果Activity满足AMS的校验,AMS就会请求应用程序进程中的ActivityThread去创建并启动普通Activity。
Hook IActivityManager方案实现
  • AMS是在SystemServer进程中,无法直接进行修改,只能在应用程序进程中做文章。可以采用预先占坑的方式来解决没有在AndroidManifest.xml中显示声明的问题,具体做法就是使用一个在AndroidManifest.xml中注册的Activity来进行占坑,用来通过AMS的校验,然后用插件Activity替换占坑的Activity
  1. 在宿主中创建一个占坑Activity
class StubHookActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}


  1. 使用占坑Activity通过AMS验证
// Android 8.0与7.0的AMS家族有一些差别,主要是Android 8.0去掉了AMS的代理ActivityManagerProxy,代替它的是IActivityManager,直接采用AIDL来进行进程间通信。
// Android7.0的Activity的启动会调用ActivityManagerNative的getDefault方法, Android8.0的Activity的启动会调用ActivityManager的getService方法,两者都返回了IActivityManager类型的对象。
public static void hookAMS() {
    try {
        Object singleTon = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            //android 29或以上版本的API
            @SuppressLint("PrivateApi")
            Class activityManagerClass = Class.forName("android.app.ActivityTaskManager");
            Field iActivityManagerSingletonField = activityManagerClass.getDeclaredField("IActivityTaskManagerSingleton");
            iActivityManagerSingletonField.setAccessible(true);
            singleTon = iActivityManagerSingletonField.get(null);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //android 26或以上版本的API是一样的
            Class activityManagerClass = Class.forName("android.app.ActivityManager");
            Field iActivityManagerSingletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
            iActivityManagerSingletonField.setAccessible(true);
            singleTon = iActivityManagerSingletonField.get(null);
        } else {
            //android 26或以下版本的API是一个系列
            Class activityManagerClass = Class.forName("android.app.ActivityManagerNative");
            Field iActivityManagerSingletonField = activityManagerClass.getDeclaredField("gDefault");
            iActivityManagerSingletonField.setAccessible(true);
            singleTon = iActivityManagerSingletonField.get(null);
        }
        Class singleTonClass = Class.forName("android.util.Singleton");
        Field mInstanceField = singleTonClass.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);
        // 获取到IActivityManagerSingleton的对象
        final Object iActivityManager = mInstanceField.get(singleTon);
        Class iActivityManagerClass;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            iActivityManagerClass = Class.forName("android.app.IActivityTaskManager");
        } else {
            iActivityManagerClass = Class.forName("android.app.IActivityManager");
        }

        Object newInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{iActivityManagerClass},
                (o, method, args) -> {
                    if ("startActivity".equals(method.getName())) {
                        // 拦截startActivity方法,接着获取参数args中第一个Intent对象
                        // 它是原本要启动插件Plugin002Activity的Intent
                        Intent intent = null;
                        int index = 0;
                        for (int i = 0; i < args.length; i++)
                            if (args[i] instanceof Intent) {
                                index = i;
                                break;
                            }
                        intent = (Intent) args[index];
                        //新建一个subIntent用来启动的StubActivity
                        Intent subIntent = new Intent();
                        String packageName = "com.jinyang.plugindemo";
                        subIntent.setClassName(packageName, packageName + ".hook.StubHookActivity");
                        //将这个Plugin002Activity的Intent保存到subIntent中,便于以后还原Plugin002Activity
                        subIntent.putExtra(HookHelper.TARGET_INTENT, intent);
                        //用subIntent赋值给参数args,这样启动的目标就变为了StubActivity,用来通过AMS的校验。
                        args[index] = subIntent;
                    }
                    return method.invoke(iActivityManager, args);
                });
        mInstanceField.set(singleTon, newInstance);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • 试着跳转一下会发现并没有跳转到Plugin002Activity,而是跳到了StubHookActivity,因为被我们的hookAMS拦截了
binding.btnHookIActivityManager.setOnClickListener {
    hookAMS()//可以放到Application中对全局生效
    val intent = Intent()
    val packageName = "com.jinyang.plugindemo"
    val activityName = "com.jinyang.plugin002.Plugin002Activity"
    intent.component = ComponentName(packageName, activityName)
    startActivity(intent)
}
  1. 还原插件Activity:ActivityThread类中有一个静态变量sCurrentActivityThread,用于表示当前的ActivityThread对象,通过替换其mH:Handler, 重写handleMessage并拦截对应的msg,将启动StubHookActivity的Intent替换回启动Plugin002Activity
public static void hookHandler() {
    try {
        // 获取ActivityThread实例
        final Class activityThreadClass = Class.forName("android.app.ActivityThread");
        Field activityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
        activityThreadField.setAccessible(true);
        final Object activityThread = activityThreadField.get(null);
        // 获取Handler实例
        Field mHField = activityThreadClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        Object mH = mHField.get(activityThread);
        Class handlerClass = Class.forName("android.os.Handler");
        Field mCallbackField = handlerClass.getDeclaredField("mCallback");
        mCallbackField.setAccessible(true);
        mCallbackField.set(mH, (Handler.Callback) msg -> {
            switch (msg.what) {
                case 100: // API 28 以前直接接收
                    try {
                        // 获取ActivityClientRecord中的intent对象
                        Field intentField = msg.obj.getClass().getDeclaredField("intent");
                        intentField.setAccessible(true);
                        Intent proxyIntent = (Intent) intentField.get(msg.obj);
                        // 拿到插件的Intent
                        Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
                        // 替换回来
                        proxyIntent.setComponent(intent.getComponent());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;
                case 159: // API 28 以后加入了 lifecycle, 这里msg发生了变化
                    try {
                        Field mActivityCallbacksField = msg.obj.getClass().getDeclaredField("mActivityCallbacks");
                        mActivityCallbacksField.setAccessible(true);
                        List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
                        for (int i = 0; i < mActivityCallbacks.size(); i++) {
                            Class itemClass = mActivityCallbacks.get(i).getClass();
                            Log.d("LJY_LOG","itemClass:"+itemClass);
                            if (itemClass.getName().equals("android.app.servertransaction.LaunchActivityItem")) {
                                Field intentField = itemClass.getDeclaredField("mIntent");
                                intentField.setAccessible(true);
                                Intent proxyIntent = (Intent) intentField.get(mActivityCallbacks.get(i));
                                Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
                                proxyIntent.setComponent(intent.getComponent());
                                break;
                            }
                        }
                    } catch (Exception e) {
                        Log.d("LJY_LOG", "e = " + e.getMessage());
                    }
                    break;
                default:
                    break;
            }
            return false;// 这里必须返回false
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
}
 
 
  • 再次试着调用一下跳转
binding.btnHookIActivityManager.setOnClickListener {
    hookAMS()
    hookHandler()
    val intent = Intent()
    val packageName = "com.jinyang.plugindemo"
    val activityName = "com.jinyang.plugin002.Plugin002Activity"
    intent.component = ComponentName(packageName, activityName)
    startActivity(intent)
}
  • 会发现报错了,找不到类,我们还需要把插件类加载到classLoader,还记得前面类加载中的单DexClassLoader时我们的loadDex方法么
binding.btnHookIActivityManager.setOnClickListener {
    loadDex(this, listOf(plugin001Path,plugin002Path))
    hookAMS()
    hookHandler()
    val intent = Intent()
    val packageName = "com.jinyang.plugindemo"
    val activityName = "com.jinyang.plugin002.Plugin002Activity"
    intent.component = ComponentName(packageName, activityName)
    startActivity(intent)
}
  • 会发现又报错了Resources$NotFoundException,这里还需要在插件的baseActivity中替换一下Resources, 可以参考我们上面将资源替换时的loadResources方法
class Plugin002Activity : PluginBaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_plugin002)
    }
}

open class PluginBaseActivity2 : Activity() {
    private var mResources: Resources? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val plugin002Path: String = File(filesDir.absolutePath, "plugin002.apk").absolutePath
        mResources = loadResources(this, AssetManager::class.java.newInstance(), listOf( plugin002Path))
    }

    override fun getResources(): Resources? {
        return mResources ?: super.getResources()
    }

    override fun getAssets(): AssetManager {
        return mResources?.assets ?: super.getAssets()
    }
}
  • 再试着跳转一下就会成功了
Hook Instrumentation方案实现
  • startActivityForResult方法中调用了Instrumentation的execStartActivity方法来激活Activity的生命周期;
  • ActivityThread启动Activity的过程中会调用ActivityThread的performLaunchActivity方法,其中调用了mInstrumentation的newActivity方法,其内部会用类加载器来创建Activity的实例;
  • 那么我们就可以用自定义的Instrumentation来替换掉mInstrumentation,在Instrumentation的execStartActivity方法中用宿主的占坑Activity来通过AMS的验证,在Instrumentation的newActivity方法中还原为插件Activity;
  • Hook Instrumentation实现要比Hook IActivityManager实现要简单一些,实现步骤如下
  1. 自定义Instrumentation
class InstrumentationProxy(
    var realContext: Context,
    var base: Instrumentation,
    var context: ContextWrapper
) : Instrumentation() {
    private val KEY_COMPONENT = "commontec_component"

    companion object {
        /**
         * hook 系统,替换 Instrumentation 为我们自己的 InstrumentationProxy
         */
        fun inject(activity: Activity, context: ContextWrapper) {
            // Reflect 是从 VirtualApp 里拷贝的反射工具类,使用很流畅~
            val reflect = Reflect.on(activity)
            val activityThread = reflect.get("mMainThread")
            val base = Reflect.on(activityThread).get("mInstrumentation")
            val mInstrumentation = InstrumentationProxy(activity, base, context)
            Reflect.on(activityThread).set("mInstrumentation", mInstrumentation)
            Reflect.on(activity).set("mInstrumentation", mInstrumentation)
        }
    }

    /**
     * newActivity 是创建 Activity 实例,这里要返回真正需要运行的插件 Activity,
     * 这样后面系统就会基于这个 Activity 实例来进行对应的生命周期的调用。
     */
    override fun newActivity(cl: ClassLoader, className: String, intent: Intent): Activity? {
        val componentName = intent.getParcelableExtra(KEY_COMPONENT)
        var clazz = context.classLoader.loadClass(componentName?.className)
        intent.component = componentName
        return clazz.newInstance() as Activity?
    }

    /**
     *  hook 系统的资源处理方式: 生成 Resources 以后,直接反射替换掉 Activity 中的 mResource 变量即可
     */
    private fun injectActivity(activity: Activity?) {
        val intent = activity?.intent
        val base = activity?.baseContext
        try {
            //反射替换 mResources 资源
            Reflect.on(base).set("mResources", context.resources)
            Reflect.on(activity).set("mResources", context.resources)
            Reflect.on(activity).set("mBase", context)
            Reflect.on(activity).set("mApplication", context.applicationContext)
            // for native activity
            val componentName: ComponentName? =
                intent!!.getParcelableExtra(KEY_COMPONENT)
            val wrapperIntent = Intent(intent)
            wrapperIntent.setClassName(componentName?.packageName!!, componentName.className)
            activity.intent = wrapperIntent

        } catch (e: Exception) {
        }
    }

    override fun callActivityOnCreate(activity: Activity?, icicle: Bundle?) {
        injectActivity(activity)
        super.callActivityOnCreate(activity, icicle)
    }

    override fun callActivityOnCreate(
        activity: Activity?,
        icicle: Bundle?,
        persistentState: PersistableBundle?
    ) {
        injectActivity(activity)
        super.callActivityOnCreate(activity, icicle, persistentState)
    }

    /**
     *  替换 intent 中的类名为占位 Activity 的类名,这样系统在 Manifest 中查找的时候就可以找到 Activity
     */
    private fun injectIntent(intent: Intent?) {
        var component: ComponentName? = null
        var oldComponent = intent?.component
        if (component == null || component.packageName == realContext.packageName) {
            component = ComponentName(
                "com.jinyang.plugindemo",
                "com.jinyang.plugindemo.hook.StubHookActivity"
            )
            intent?.component = component
            intent?.putExtra(KEY_COMPONENT, oldComponent)
        }
    }

    /**
     * execStartActivity 是在启动 Activity 的时候必经的一个过程,这时还没有到达 AMS,
     * 所以,在这里把 Activity 替换成宿主中已经注册的 StubActivity,
     * 这样 AMS 在检测 Activity 的时候就认为已经注册过了
     */
    fun execStartActivity(
        who: Context,
        contextThread: IBinder,
        token: IBinder,
        target: Activity,
        intent: Intent,
        requestCode: Int
    ): Instrumentation.ActivityResult? {
        log("exec...")
        injectIntent(intent)
        return Reflect.on(base)
            .call("execStartActivity", who, contextThread, token, target, intent, requestCode).get()
    }

    fun execStartActivity(
        who: Context?,
        contextThread: IBinder?,
        token: IBinder?,
        target: Activity?,
        intent: Intent,
        requestCode: Int,
        options: Bundle?
    ): Instrumentation.ActivityResult? {
        log("exec...")
        injectIntent(intent)
        return Reflect.on(base)
            .call(
                "execStartActivity",
                who,
                contextThread,
                token,
                target,
                intent,
                requestCode,
                options ?: Bundle()
            )
            .get()
    }

    fun execStartActivity(
        who: Context,
        contextThread: IBinder,
        token: IBinder,
        target: Fragment,
        intent: Intent,
        requestCode: Int,
        options: Bundle?
    ): Instrumentation.ActivityResult? {
        log("exec...")
        injectIntent(intent)
        return Reflect.on(base)
            .call(
                "execStartActivity",
                who,
                contextThread,
                token,
                target,
                intent,
                requestCode,
                options ?: Bundle()
            )
            .get()
    }

    fun execStartActivity(
        who: Context,
        contextThread: IBinder,
        token: IBinder,
        target: String,
        intent: Intent,
        requestCode: Int,
        options: Bundle?
    ): Instrumentation.ActivityResult? {
        log("exec...")
        injectIntent(intent)
        return Reflect.on(base)
            .call(
                "execStartActivity",
                who,
                contextThread,
                token,
                target,
                intent,
                requestCode,
                options ?: Bundle()
            )
            .get()
    }
}

  1. 调用InstrumentationProxy.inject,hook系统,替换 Instrumentation 为我们自己的 AppInstrumentation,再进行跳转
binding.btnHookInstrumentation.setOnClickListener {
    InstrumentationProxy.inject(
        this,
        loadContext(this, resources.assets, listOf(plugin001Path, plugin002Path))
    )
    val intent = Intent()
    intent.setClass(this, pluginClassLoader.loadClass(activityName))
    startActivity(intent)
}

fun loadContext(baseContext: Context,
                assetManager: AssetManager,
                pluginPaths: List): ContextWrapper {
    loadDex(baseContext, pluginPaths)
    val resources = loadResources(baseContext,assetManager, pluginPaths)
    // 创建自己的Context
    val mContext = ContextThemeWrapper(baseContext, 0)
    // 把自己的Context中的resources替换为我们自己的
    val clazz = mContext::class.java
    val mResourcesField = clazz.getDeclaredField("mResources")
    mResourcesField.isAccessible = true
    mResourcesField.set(mContext, resources)
    return mContext
}

加载 插件Service

  1. 插件中创建一个service
class PluginService : Service() {

    override fun onCreate() {
        log("plugin onCreate")
        super.onCreate()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        log("plugin onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {
        log("plugin onDestroy")
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? {
        log("plugin onBind")
        return null
    }

    override fun onUnbind(intent: Intent?): Boolean {
        log("plugin onUnbind")
        return super.onUnbind(intent)
    }

}
  1. 在宿主 app 里添加一个占位 Service,然后在对应的生命周期里调用插件 Service 的生命周期方法


class StubService : Service() {
    var serviceName: String? = null
    var pluginService: Service? = null

    companion object {
        var pluginClassLoader: ClassLoader? = null
        fun startService(context: Context, classLoader: ClassLoader, serviceName: String) {
            log("StubService.startService")
            pluginClassLoader = classLoader
            val intent = Intent(context, StubService::class.java)
            intent.putExtra("serviceName", serviceName)
            context.startService(intent)
        }

        fun stopService(context: Context, classLoader: ClassLoader, serviceName: String) {
            log("StubService.stopService")
            pluginClassLoader = classLoader
            val intent = Intent(context, StubService::class.java)
            intent.putExtra("serviceName", serviceName)
            context.stopService(intent)
        }
    }

    override fun onCreate() {
        super.onCreate()
        log("StubService.onCreate")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        log("StubService.onStartCommand")
        val res = super.onStartCommand(intent, flags, startId)
        serviceName = intent?.getStringExtra("serviceName")
        if (pluginService == null) {
            pluginService = pluginClassLoader?.loadClass(serviceName)?.newInstance() as Service
            pluginService?.onCreate()
        }
        return pluginService?.onStartCommand(intent, flags, startId) ?: res
    }

    override fun onDestroy() {
        super.onDestroy()
        log("StubService.onDestroy")
        if (pluginService!=null) {
            pluginService?.onDestroy()
            pluginService = null
        }
    }

    override fun onBind(intent: Intent?): IBinder? {
        log("StubService.onBind")
        return pluginService?.onBind(intent)
    }

    override fun onUnbind(intent: Intent?): Boolean {
        log("StubService.onUnbind")
        return pluginService?.onUnbind(intent) ?: super.onUnbind(intent)
    }
}

  1. 通过占坑service启动和结束插件service
val plugin001Path = File(filesDir.absolutePath, "plugin001.apk").absolutePath
val nativeLibDir = File(filesDir, "pluginlib").absolutePath
val dexOutPath = File(filesDir, "dexout").absolutePath
val pluginClassLoader =
    DexClassLoader(plugin001Path, dexOutPath, nativeLibDir, this::class.java.classLoader)
val serviceName = "com.jinyang.plugin001.PluginService"

binding.btnStartService.setOnClickListener {
    StubService.startService(this, pluginClassLoader, serviceName)
}

binding.btnStopService.setOnClickListener {
    StubService.stopService(this, pluginClassLoader, serviceName)
}

加载 插件BroadcastReceiver

  • 动态广播:通过 ClassLoader 加载插件 apk 中的广播类然后直接注册
  1. 创建一个注册插件广播的工具类
class BroadcastUtils {
    companion object {
        private val broadcastMap = HashMap()

        fun registerBroadcastReceiver(context: Context, classLoader: ClassLoader, action: String, broadcastName: String) {
            log("BroadcastUtils.registerBroadcastReceiver")
            val receiver = classLoader.loadClass(broadcastName).newInstance() as BroadcastReceiver
            val intentFilter = IntentFilter(action)
            context.registerReceiver(receiver, intentFilter)
            broadcastMap[action] = receiver
        }

        fun unregisterBroadcastReceiver(context: Context, action: String) {
            log("BroadcastUtils.unregisterBroadcastReceiver")
            val receiver = broadcastMap.remove(action)
            if (receiver!=null) {
                context.unregisterReceiver(receiver)
            }
        }
    }
}
  1. 注册并使用插件中的广播
val testAction = "com.ljy.action.testBroadcastReceiver"
val broadcastName = "com.jinyang.plugin001.PluginBroadcastReceiver"
binding.btnRegisterBroadcastReceiver.setOnClickListener {
    BroadcastUtils.registerBroadcastReceiver(
        this,
        pluginClassLoader,
        testAction,
        broadcastName
    )
}
binding.btnSendBroadcast.setOnClickListener {
    sendBroadcast(Intent(testAction))
}
binding.btnUnregisterBroadcastReceiver.setOnClickListener {
    BroadcastUtils.unregisterBroadcastReceiver(this, testAction)
}
  • 静态广播:VirtualApk处理的方式是将插件apk的AndroidManifest.xml中静态注册的Receiver通过动态registerReceiver注册到宿主Context中, 实现代码如下
/**
 * 将插件apk的AndroidManifest.xml中静态注册的Receiver通过动态registerReceiver注册到宿主Context中
 */
fun parserPluginStaticBroadcast(context: Context, pluginPath: String?) {
    try {
        //实例化 PackageParser对象
        val mPackageParserClass = Class.forName("android.content.pm.PackageParser")
        val mPackageParser = mPackageParserClass.newInstance()
        // 1.执行此方法 public Package parsePackage(File packageFile, int flags),就是为了,拿到Package
        val mPackageParserMethod = mPackageParserClass.getMethod(
            "parsePackage",
            File::class.java,
            Int::class.javaPrimitiveType
        )
        val mPackage = mPackageParserMethod.invoke(
            mPackageParser,
            File(pluginPath),
            PackageManager.GET_ACTIVITIES
        )
        //获取mPackage中的ArrayList receivers属性
        val receiversField = mPackage.javaClass.getDeclaredField("receivers")
        val receivers = receiversField[mPackage]
        val arrayList = receivers as ArrayList<*>
        //此Activity不是组件的Activity,是PackageParser里面的内部类
        for (mActivity in arrayList) { // mActivity --> 
            //通过反射拿到intents ArrayList intents; 一个标签可以对应多个Intent-Filter
            val mComponentClass =
                Class.forName("android.content.pm.PackageParser\$Component")
            val intentsField = mComponentClass.getDeclaredField("intents")
            val intents: ArrayList = intentsField[mActivity] as ArrayList<*>
            //上面是拿到了IntentFilter,下面就是获取组件的名字 activityInfo.name
            /**
             * 执行此方法,就能拿到 ActivityInfo
             * public static final ActivityInfo generateActivityInfo(Activity a, int flags,
             * PackageUserState state, int userId)
             */
            val mPackageUserState = Class.forName("android.content.pm.PackageUserState")
            val mUserHandle = Class.forName("android.os.UserHandle")
            val userId = mUserHandle.getMethod("getCallingUserId").invoke(null) as Int
            val generateActivityInfoMethod = mPackageParserClass.getDeclaredMethod(
                "generateActivityInfo",
                mActivity.javaClass,
                Int::class.javaPrimitiveType,
                mPackageUserState,
                Int::class.javaPrimitiveType
            )
            generateActivityInfoMethod.isAccessible = true
            val mActivityInfo = generateActivityInfoMethod.invoke(
                null,
                mActivity,
                0,
                mPackageUserState.newInstance(),
                userId
            ) as ActivityInfo
            val receiverClassName = mActivityInfo.name
            Log.e("LJY_LOG", "receiverClassName : $receiverClassName")
            val mStaticReceiverClass = context.classLoader.loadClass(receiverClassName)
            val broadcastReceiver = mStaticReceiverClass.newInstance() as BroadcastReceiver
            for (intentFilter in intents) {
                Log.e("LJY_LOG", "intentFilter mActions size " + intentFilter.countActions())
                context.registerReceiver(broadcastReceiver, intentFilter)
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

加载 插件ContentProvider

  • 这里面需要处理的就是,如何转发对应的 Uri 到正确的插件 Provider 中呢,解决方案是在 Uri 中定义不同的插件路径,比如 plugin1 的 Uri 对应就是 content://com.zy.stubprovider/plugin1 ,plugin2 对应的 uri 就是 content://com.zy.stubprovider/plugin2 ,然后在 StubContentProvider 中根据对应的 plugin 分发不同的插件 Provider。
  1. 宿主中创建一个占坑的ContentProvider,并在其中通过插件classLoader加载插件ContentProvider


class StubContentProvider : ContentProvider() {

    private var pluginProvider: ContentProvider? = null
    private var uriMatcher: UriMatcher? = UriMatcher(UriMatcher.NO_MATCH)


    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        log("StubContentProvider.insert")
        return loadPluginProvider()?.insert(uri, values)
    }


    override fun query(uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, sortOrder: String?): Cursor? {
        log("StubContentProvider.query: uri=$uri")
        if (isPlugin1(uri)) {
            return loadPluginProvider()?.query(uri, projection, selection, selectionArgs, sortOrder)
        }
        return null
    }

    override fun onCreate(): Boolean {
        log("StubContentProvider.onCreate")
        uriMatcher?.addURI("com.ljy.StubContentProvider", "plugin001", 1)
        uriMatcher?.addURI("com.ljy.StubContentProvider", "plugin002", 2)
        return true
    }

    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?): Int {
        log("StubContentProvider.update")
        return loadPluginProvider()?.update(uri, values, selection, selectionArgs) ?: 0
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int {
        log("StubContentProvider.delete")
        return loadPluginProvider()?.delete(uri, selection, selectionArgs) ?: 0
    }

    override fun getType(uri: Uri): String {
        log("StubContentProvider.getType")
        return loadPluginProvider()?.getType(uri) ?: ""
    }

    private fun loadPluginProvider(): ContentProvider? {
        if (pluginProvider == null) {
            pluginProvider = classLoader?.loadClass("com.jinyang.plugin001.PluginContentProvider")?.newInstance() as ContentProvider?
        }
        return pluginProvider
    }

    private fun isPlugin1(uri: Uri?): Boolean {
        log("StubContentProvider.isPlugin1:${uriMatcher?.match(uri)}")
        if (uriMatcher?.match(uri) == 1) {
            return true
        }
        return false
    }
}
  1. 使用
binding.btnQueryContentProvider1.setOnClickListener {
    val uri = Uri.parse("content://com.ljy.StubContentProvider/plugin001")
    val cursor = contentResolver.query(uri, null, null, null, null)
    cursor?.moveToFirst()
    val res = cursor?.getString(0)
    log("provider query res: $res")
    cursor?.close()
}
binding.btnQueryContentProvider2.setOnClickListener {
    val uri = Uri.parse("content://com.ljy.StubContentProvider/plugin002")
    val cursor = contentResolver.query(uri, null, null, null, null)
    cursor?.moveToFirst()
    val res = cursor?.getString(0)
    log("provider query res: $res")
    cursor?.close()
}
  • 到此插件化的原理基本讲完了,下面我们来看看Shadow吧,毕竟本系列文章是探索Android开源框架

Shadow

Shadow原理:

  • 通过运用AOP思想,利用字节码编辑工具,在编译期把插件中的所有Activity的父类都改成一个普通类,然后让壳子持有这个普通类型的父类去转调它就不用Hack任何系统实现了。(任何软件工程遇到的问题都可以通过增加一个中间层来解决)

Shadow为什么要求插件和宿主包名一致

  • ApplicationId一般是在build.gradle中设置的,在编译时这个字符串会被记录在2个位置。第1是记录在应用的AndroidManifest.xml中,第2是记录在应用的resources.arsc文件中;
  • 记录在AndroidManifest.xml中的包名主要用来构造应用的Context对象,系统也会通过context获取包名来识别context来自于哪个安装的应用
  • Shadow设计时避免使用私有API,通过一层中间件将插件代码变成宿主代码的一部分,既然是一部分,ApplicationId怎么会不一样呢?所以要求插件和宿主的ApplicationId保持一致,就永远不会将插件代码没有安装这件事暴露给系统;
  • 记录在resources.arsc的ApplicationId,Resources对象上有一些API是接收包名作为参数的,如果这个包名在独立安装和插件环境下动态获取的不一样,那么有可能造成这些API失效,找不到想要的资源。

Shadow使用

  • 使用上主要参考官方的sample,路径为Shadow-master\projects\sample\maven,其中有三个project,host-project为宿主项目,manager-project为插件管理项目,plugin-project为插件项目;

参考

  • 深入理解Android插件化技术
  • 【Android 修炼手册】常用技术篇 -- Android 插件化解析
  • 从零开始实现一个插件化框架
  • Tencent Shadow—零反射全动态Android插件框架正式开源
  • Shadow的全动态设计原理解析(这个是Shadow作者的一系列关于Shadow文章中的一篇)
  • Android Tencent Shadow 插件接入指南
  • Shadow 插件化框架分析

你可能感兴趣的:(探索Android开源框架 - 10. 插件化原理)