整个插件化框架历史部分参考了包建强在 2016GMTC 全球开发大会上的演讲
2012 年 AndroidDynamicLoader 给予 Fragment 实现了插件化框架,能够动态加载插件中的 Fragment 实现页面的切换。
2013 年 23Code 提供了一个壳,能够在壳里动态化下载插件而后运行。
2013 年 阿里技术沙龙上,伯奎作了 Atlas 插件化框架的分享,说明那时候阿里已经在作插件化的运用和开发了。
2014 年 任玉刚开源了 dynamic-load-apk,经过代理分发的方式实现了动态化,若是看过 Android 开发艺术探索这本书,应该会对这个方式有了解。
2015 年 张勇 发布了 DroidPlugin,使用 hook 系统方式实现插件化。
2015 年 携程发布 DynamicApk
2015 - 2016 之间(这块时间不太肯定),Lody 发布了 VirtualApp,能够直接运行未安装的 apk,基本上仍是使用 hook 系统的方式实现的,不过里面的实现要精致不少,实现了本身的一套 AMS 来管理插件 Activity 等等。
2017 年阿里推出 Atlas
2017 年 360 推出 RePlugin
2017 年滴滴推出 VirtualApk
2019 年腾讯推出了 Shadow,号称是零反射,而且框架自身也可实现动态化,看了代码之后发现,其实本质上仍是使用了代理分发生命周期实现四大组件动态化,而后抽象接口来实现框架的动态化。后面有机会能够对其作一下分析。java
这基本上就是插件化框架的历史,从 2012 至今,能够说插件化技术基本成型了,主要就是代理和 hook 系统两种方式(这里没有统计热修复的发展,热修复其实和插件化仍是有些想通的地方,后面的文章会对热修复进行介绍)。若是看将来的话,斗胆预测,插件化技术的原理,应该不会有太大的变更了。android
在插件化中有一些专有名词,若是是第一次接触可能不太了解,这里解释一下。
宿主
负责加载插件的 apk,通常来讲就是已经安装的应用自己。
StubActivity
宿主中的占位 Activity,注册在宿主 Manifest 文件中,负责加载插件 Activity。
PluginActivity
插件 Activity,在插件 apk 中,没有注册在 Manifest 文件中,须要 StubActivity 来加载。git
在学习和开发插件化的时候,咱们须要动态去加载插件 apk,因此开发过程当中通常须要有两个 apk,一个是宿主 apk,一个是插件 apk,对应的就须要有宿主项目和插件项目。
在 CommonTec 这里建立了 app 做为宿主项目,plugin 为插件项目。为了方便,咱们直接把生成的插件 apk 放到宿主 apk 中的 assets 中,apk 启动时直接放到内部存储空间中方便加载。
这样的项目结构,咱们调试问题时的流程就是下面这样:
修改插件项目 -> 编译生成插件 apk -> 拷贝插件 apk 到宿主 assets -> 修改宿主项目 -> 编译生成宿主 apk -> 安装宿主 apk -> 验证问题
若是每次咱们修改一个很小的问题,都经历这么长的流程,那么耐心很快就耗尽了。最好是能够直接编译宿主 apk 的时候自动打包插件 apk 并拷贝到宿主 assets 目录下,这样咱们无论修改什么,都直接编译宿主项目就行了。如何实现呢?还记得咱们以前讲解过的 gradle 系列么?如今就是学以至用的时候了。
首先在 plugin 项目的 build.gradle 添加下面的代码:github
project.afterEvaluate {
project.tasks.each {
if (it.name == "assembleDebug") {
it.doLast {
copy {
from new File(project.getBuildDir(), 'outputs/apk/debug/plugin-debug.apk').absolutePath
into new File(project.getRootProject().getProjectDir(), 'app/src/main/assets')
rename 'plugin-debug.apk', 'plugin.apk'
}
}
}
}
}
复制代码
这段代码是在 afterEvaluate 的时候,遍历项目的 task,找到打包 task 也就是 assembleDebug,而后在打包以后,把生成的 apk 拷贝到宿主项目的 assets 目录下,而且重命名为 plugin.apk。 而后在 app 项目的 build.gradle 添加下面的代码:apache
project.afterEvaluate {
project.tasks.each {
if (it.name == 'mergeDebugAssets') {
it.dependsOn ':plugin:assembleDebug'
}
}
}
复制代码
找到宿主打包的 mergeDebugAssets 任务,依赖插件项目的打包,这样每次编译宿主项目的时候,会先编译插件项目,而后拷贝插件 apk 到宿主 apk 的 assets 目录下,之后每次修改,只要编译宿主项目就能够了。架构
ClassLoader 是插件化中必需要掌握的,由于插件是未安装的 apk,系统不会处理其中的类,因此须要咱们本身来处理。app
BootstrapClassLoader
负责加载 JVM 运行时的核心类,好比 JAVA_HOME/lib/rt.jar 等等框架
ExtensionClassLoader
负责加载 JVM 的扩展类,好比 JAVA_HOME/lib/ext 下面的 jar 包ide
AppClassLoader
负责加载 classpath 里的 jar 包和目录函数
在这里,咱们统称 dex 文件,包含 dex 的 apk 文件以及 jar 文件为 dex 文件 PathClassLoader 用来加载系统类和应用程序类,能够加载已经安装的 apk 目录下的 dex 文件
DexClassLoader 用来加载 dex 文件,能够从存储空间加载 dex 文件。
咱们在插件化中通常使用的是 DexClassLoader。
每个 ClassLoader 中都有一个 parent 对象,表明的是父类加载器,在加载一个类的时候,会先使用父类加载器去加载,若是在父类加载器中没有找到,本身再进行加载,若是 parent 为空,那么就用系统类加载器来加载。经过这样的机制能够保证系统类都是由系统类加载器加载的。 下面是 ClassLoader 的 loadClass 方法的具体实现。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 先从父类加载器中进行加载
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 没有找到,再本身加载
c = findClass(name);
}
}
return c;
}
复制代码
要加载插件中的类,咱们首先要建立一个 DexClassLoader,先看下 DexClassLoader 的构造函数须要那些参数。
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
// ...
}
}
复制代码
构造函数须要四个参数:
dexPath 是须要加载的 dex / apk / jar 文件路径
optimizedDirectory 是 dex 优化后存放的位置,在 ART 上,会执行 oat 对 dex 进行优化,生成机器码,这里就是存放优化后的 odex 文件的位置
librarySearchPath 是 native 依赖的位置
parent 就是父类加载器,默认会先从 parent 加载对应的类
建立出 DexClassLaoder 实例之后,只要调用其 loadClass(className) 方法就能够加载插件中的类了。具体的实如今下面:
// 从 assets 中拿出插件 apk 放到内部存储空间
private fun extractPlugin() {
var inputStream = assets.open("plugin.apk")
File(filesDir.absolutePath, "plugin.apk").writeBytes(inputStream.readBytes())
}
private fun init() {
extractPlugin()
pluginPath = File(filesDir.absolutePath, "plugin.apk").absolutePath
nativeLibDir = File(filesDir, "pluginlib").absolutePath
dexOutPath = File(filesDir, "dexout").absolutePath
// 生成 DexClassLoader 用来加载插件类
pluginClassLoader = DexClassLoader(pluginPath, dexOutPath, nativeLibDir, this::class.java.classLoader)
}
复制代码
插件化,就是从插件中加载咱们想要的类并运行,若是这个类是一个普通类,那么使用上面说到的 DexClassLoader 就能够直接加载了,若是这个类是特殊的类,好比说 Activity 等四大组件,那么就须要一些特殊的处理,由于四大组件是须要和系统进行交互的。插件化中,四大组件须要解决的难点以下:
咱们以前说到 Activity 插件化的难点,咱们先来理顺一下为何会有这两个问题。
由于插件是动态加载的,因此插件的四大组件不可能注册到宿主的 Manifest 文件中,而没有在 Manifest 中注册的四大组件是不能和系统直接进行交互的。
可能有些同窗会问,那为何不能直接把插件的 Activity 注册到宿主 Manifest 里呢?这样是能够,不过就失去了插件化的动态特性,若是每次插件中新增 Activity 都要修改宿主 Manifest 而且从新打包,那就和直接写在宿主中没什么区别了。
咱们再来讲一下为何没有注册的 Activity 不能和系统交互
这里的不能直接交互的含义有两个
android.content.ActivityNotFoundException: Unable to find explicit activity class {com.zy.commontec/com.zy.plugin.PluginActivity}; have you declared this activity in your AndroidManifest.xml?
复制代码
这个 log 在 Instrumentation 的 checkStartActivityResult 方法中能够看到:
public class Instrumentation {
public static void checkStartActivityResult(int res, Object intent) {
if (!ActivityManager.isStartResultFatalError(res)) {
return;
}
switch (res) {
case ActivityManager.START_INTENT_NOT_RESOLVED:
case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity in your AndroidManifest.xml?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
...
}
}
}
复制代码
其实上面两个问题,最终都指向同一个难点,那就是插件中的 Activity 的生命周期如何被调用。 解决问题以前咱们先看一下正常系统是如何启动一个 Activity 的。
这里对 Activity 的启动流程进行一些简单的介绍,具体的流程代码就不分析了,由于分析的话大概又能写一篇文章了,并且其实关于 Activity 的启动过程也有很多文章有分析了。这里放一张简图说明一下:
整个调用路径以下:
Activity.startActivity -> Instrumentation.execStartActivity -> Binder -> AMS.startActivity -> ActivityStarter.startActivityMayWait -> startActivityLocked -> startActivityUnChecked -> ActivityStackSupervisor.resumeFocusedStackTopActivityLocked -> ActivityStatk.resumeTopAcitivityUncheckLocked -> resumeTopActivityInnerLocked -> ActivityStackSupervisor.startSpecificActivityLocked -> realStartActivityLocked -> Binder -> ApplictionThread.scheduleLauchActivity -> H -> ActivityThread.scheduleLauchActivity -> handleLaunchActivity -> performLaunchActivity -> Instrumentation.newActivity 建立 Activity -> callActivityOnCreate 一系列生命周期
复制代码
其实咱们能够把 AMS 理解为一个公司的背后大 Boss,Activity 至关于小职员,没有权限直接和大 Boss 说话,想作什么事情都必须通过秘书向上汇报,而后秘书再把大 Boss AMS 的命令传达下来。并且大 Boss 那里有全部职员的名单,若是想要混入非法职员时不可能的。而咱们想让没有在大 Boss 那里注册的编外人员执行任务,只有两种方法,一种是正式职员领取任务,再分发给编外人员,另外一种就是欺骗 Boss,让 Boss 觉得这个职员是已经注册的。
对应到实际的解决方法就是:
说完生命周期的问题,再来看一下资源的问题
在 Activity 中,基本上都会展现界面,而展现界面基本上都要用到资源。
在 Activity 中,有一个 mResources 变量,是 Resources 类型。这个变量能够理解为表明了整个 apk 的资源。
在宿主中调用的 Activity,mResources 天然表明了宿主的资源,因此须要咱们对插件的资源进行特殊的处理。
咱们先看一下如何生成表明插件资源的 Resources 类。
首先要生成一个 AssetManager 实例,而后经过其 addAssetPath 方法添加插件的路径,这样 AssetManager 中就包含了插件的资源。而后经过 Resources 构造函数生成插件资源。具体代码以下:
private fun handleResources() {
try {
// 首先经过反射生成 AssetManager 实例
pluginAssetManager = AssetManager::class.java.newInstance()
// 而后调用其 addAssetPath 把插件的路径添加进去。
val addAssetPathMethod = pluginAssetManager?.javaClass?.getMethod("addAssetPath", String::class.java)
addAssetPathMethod?.invoke(pluginAssetManager, pluginPath)
} catch (e: Exception) {
}
// 调用 Resources 构造函数生成实例
pluginResources = Resources(pluginAssetManager, super.getResources().displayMetrics, super.getResources().configuration)
}
复制代码
前期准备的知识点差很少介绍完了,咱们接着就看看具体的实现方法。
手动调用生命周期原理以下图:
咱们手动调用插件 Activity 生命周期时,须要在正确的时机去调用,如何在正确的时机调用呢?那就是启动一个真正的 Activity,咱们俗称占坑 Activity(StubActivity),而后在 StubActivity 的生命周期里调用插件 Activity 对应的生命周期,这样就间接的启动了插件 Activity。
在 StubActivity 中调用 插件 Activity 生命周期的方法有两种,一种是直接反射其生命周期方法,粗暴简单,惟一的缺点就是反射的效率问题。另一种方式就是生成一个接口,接口里对应的是生命周期方法,让插件 Activity 实现这个接口,在 StubActivity 里就能直接调用接口方法了,从而避免了反射的效率低下问题。
具体的代码实如今CommonTec项目里能够找到,这里贴一下主要的实现(这里的实现和 CommonTec 里的可能会有些区别,CommonTec 里有些代码作了一些封装,这里主要作原理的解释)。
具体的实现见 反射调用生命周期,下面列出了重点代码。
class StubReflectActivity : Activity() {
protected var activityClassLoader: ClassLoader? = null
protected var activityName = ""
private var pluginPath = ""
private var nativeLibDir: String? = null
private var dexOutPath: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
nativeLibDir = File(filesDir, "pluginlib").absolutePath
dexOutPath = File(filesDir, "dexout").absolutePath
pluginPath = intent.getStringExtra("pluginPath")
activityName = intent.getStringExtra("activityName")
// 建立插件 ClassLoader
activityClassLoader = DexClassLoader(pluginPath, dexOutPath, nativeLibDir, this::class.java.classLoader)
}
// 以 onCreate 方法为例,其余 onStart 等生命周期方法相似
fun onCreate(savedInstanceState: Bundle?) {
// 获取插件 Activity 的 onCreate 方法并调用
getMethod("onCreate", Bundle::class.java)?.invoke(activity, savedInstanceState)
}
fun getMethod(methodName: String, vararg params: Class<*>): Method? {
return activityClassLoader?.loadClass(activity)?.getMethod(methodName, *params)
}
}
复制代码
具体的实现见 接口调用生命周期,下面列出了重点代码。 经过接口调用 Activity 生命周期的前提是要定义一个接口 IPluginActivity
interface IPluginActivity {
fun attach(proxyActivity: Activity)
fun onCreate(savedInstanceState: Bundle?)
fun onStart()
fun onResume()
fun onPause()
fun onStop()
fun onDestroy()
}
复制代码
而后在插件 Activity 中实现这个接口
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)
}
}
// ...
}
复制代码
在 StubActivity 经过接口调用插件 Activity 生命周期
class StubInterfaceActivity : StubBaseActivity() {
protected var activityClassLoader: ClassLoader? = null
protected var activityName = ""
private var pluginPath = ""
private var activity: IPluginActivity? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
nativeLibDir = File(filesDir, "pluginlib").absolutePath
dexOutPath = File(filesDir, "dexout").absolutePath
pluginPath = intent.getStringExtra("pluginPath")
activityName = intent.getStringExtra("activityName")
// 生成插件 ClassLoader
activityClassLoader = DexClassLoader(pluginPath, dexOutPath, nativeLibDir, this::class.java.classLoader)
// 加载插件 Activity 类并转化成 IPluginActivity 接口
activity = activityClassLoader?.loadClass(activityName)?.newInstance() as IPluginActivity?
activity?.attach(this)
// 经过接口直接调用对应的生命周期方法
activity?.onCreate(savedInstanceState)
}
}
复制代码
因为手动调用生命周期的方式,会重写大量的 Activity 生命周期方法,因此咱们只要重写 getResources 方法,返回插件的资源实例就能够了。下面是具体代码。
open class StubBaseActivity : Activity() {
protected var activityClassLoader: ClassLoader? = null
protected var activityName = ""
private var pluginPath = ""
private var pluginAssetManager: AssetManager? = null
private var pluginResources: Resources? = null
private var pluginTheme: Resources.Theme? = null
private var nativeLibDir: String? = null
private var dexOutPath: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
nativeLibDir = File(filesDir, "pluginlib").absolutePath
dexOutPath = File(filesDir, "dexout").absolutePath
pluginPath = intent.getStringExtra("pluginPath")
activityName = intent.getStringExtra("activityName")
activityClassLoader = DexClassLoader(pluginPath, dexOutPath, nativeLibDir, this::class.java.classLoader)
handleResources()
}
override fun getResources(): Resources? {
// 这里返回插件的资源,这样插件 Activity 中使用的就是插件资源了
return pluginResources ?: super.getResources()
}
override fun getAssets(): AssetManager {
return pluginAssetManager ?: super.getAssets()
}
override fun getClassLoader(): ClassLoader {
return activityClassLoader ?: super.getClassLoader()
}
private fun handleResources() {
try {
// 生成 AssetManager
pluginAssetManager = AssetManager::class.java.newInstance()
// 添加插件 apk 路径
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)
}
}
复制代码
上面讲了如何经过手动调用插件 Activity 的生命周期方法来启动插件 Activity,如今来看一下欺骗系统的方法。
上面简单介绍了 Activity 的启动流程,咱们能够看到,其实 Android 系统的运行是很巧妙的,AMS 是系统服务,应用经过 Binder 和 AMS 进行交互,其实和咱们平常开发中客户端和服务端交互有些相似,只不过这里使用了 Binder 作为交互方式,关于 Binder,能够简单看看这篇文章。咱们暂时只要知道经过 Binder 应用能够和 AMS 进行对话就行。
这种架构的设计方式,也为咱们提供了一些机会。理论上来讲,咱们只要在启动 Activity 的消息到达 AMS 以前把 Activity 的信息就行修改,而后再消息回来之后再把信息恢复,就能够达到欺骗系统的目的了。
在这个流程里,有不少 hook 点能够进行,并且不一样的插件化框架对于 hook 点的选择也不一样,这里咱们选择 hook Instrumentation 的方式进行介绍(缘由是我的感受这种方式要简单一点)。
简化之后的流程以下:
Instrumentation 至关于 Activity 的管理者,Activity 的建立,以及生命周期的调用都是 AMS 通知之后经过 Instrumentation 来调用的。
咱们上面说到,AMS 至关于一个公司的背后大 Boss,而 Instrumentation 至关于秘书,Activity 至关于小职员,没有权限直接和大 Boss 说话,想作什么事情都必须通过秘书向上汇报,而后 Instrumentation 再把大 Boss AMS 的命令传达下来。并且大 Boss 那里有全部职员的名单,若是想要混入非法职员时不可能的。不过在整个过程当中,因为 java 的语言特性,大 Boss 在和秘书 Instrumentation 对话时,不会管秘书究竟是谁,只会确认这我的是否是秘书(是不是 Instrumentation 类型)。咱们加载插件中的 Activity,至关于让一个不在 Boss 名单上的编外职员去申请执行任务。在正常状况下,大 Boss 会检查职员的名单,确认职员的合法性,必定是经过不了的。可是上有政策,下有对策,咱们悄悄的替换了秘书,在秘书和 Boss 汇报时,把职员名字改为大 Boss 名单中的职员,在 Boss 安排工做之后,秘书再把名字换回来,让编外职员去执行任务。
而咱们 hook 的方式就是替换调 Instrumentation,修改 Activity 类名,达到隐瞒 AMS 的效果。
hook 方式原理图
接下来看看具体的代码实现。 具体的实现见 hook 实现插件化,下面主要讲解重点代码。
替换 Instrumentation 以前,首先咱们要实现一个咱们本身的 Instrumentation,具体实现以下:
class AppInstrumentation(var realContext: Context, var base: Instrumentation, var pluginContext: PluginContext) :
Instrumentation() {
private val KEY_COMPONENT = "commontec_component"
companion object {
fun inject(activity: Activity, pluginContext: PluginContext) {
// hook 系统,替换 Instrumentation 为咱们本身的 AppInstrumentation,Reflect 是从 VirtualApp 里拷贝的反射工具类,使用很流畅~
var reflect = Reflect.on(activity)
var activityThread = reflect.get<Any>("mMainThread")
var base = Reflect.on(activityThread).get<Instrumentation>("mInstrumentation")
var appInstrumentation = AppInstrumentation(activity, base, pluginContext)
Reflect.on(activityThread).set("mInstrumentation", appInstrumentation)
Reflect.on(activity).set("mInstrumentation", appInstrumentation)
}
}
override fun newActivity(cl: ClassLoader, className: String, intent: Intent): Activity? {
// 建立 Activity 的时候会调用这个方法,在这里须要返回插件 Activity 的实例
val componentName = intent.getParcelableExtra<ComponentName>(KEY_COMPONENT)
var clazz = pluginContext.classLoader.loadClass(componentName.className)
intent.component = componentName
return clazz.newInstance() as Activity?
}
private fun injectIntent(intent: Intent?) {
var component: ComponentName? = null
var oldComponent = intent?.component
if (component == null || component.packageName == realContext.packageName) {
// 替换 intent 中的类名为占位 Activity 的类名,这样系统在 Manifest 中查找的时候就能够找到 Activity
component = ComponentName("com.zy.commontec", "com.zy.commontec.activity.hook.HookStubActivity")
intent?.component = component
intent?.putExtra(KEY_COMPONENT, oldComponent)
}
}
fun execStartActivity( who: Context, contextThread: IBinder, token: IBinder, target: Activity, intent: Intent, requestCode: Int ): Instrumentation.ActivityResult? {
// 启动 activity 的时候会调用这个方法,在这个方法里替换 Intent 中的 ClassName 为已经注册的宿主 Activity
injectIntent(intent)
return Reflect.on(base)
.call("execStartActivity", who, contextThread, token, target, intent, requestCode).get()
}
// ...
}
复制代码
在 AppInstrumentation 中有两个关键点,execStartActivity 和 newActivity。
execStartActivity 是在启动 Activity 的时候必经的一个过程,这时尚未到达 AMS,因此,在这里把 Activity 替换成宿主中已经注册的 StubActivity,这样 AMS 在检测 Activity 的时候就认为已经注册过了。newActivity 是建立 Activity 实例,这里要返回真正须要运行的插件 Activity,这样后面系统就会基于这个 Activity 实例来进行对应的生命周期的调用。
由于咱们 hook 了 Instrumentation 的实现,仍是把 Activity 生命周期的调用交给了系统,因此咱们的资源处理方式和手动调用生命周期不太同样,这里咱们生成 Resources 之后,直接反射替换掉 Activity 中的 mResource 变量便可。下面是具体代码。
class AppInstrumentation(var realContext: Context, var base: Instrumentation, var pluginContext: PluginContext) : Instrumentation() {
private fun injectActivity(activity: Activity?) {
val intent = activity?.intent
val base = activity?.baseContext
try {
// 反射替换 mResources 资源
Reflect.on(base).set("mResources", pluginContext.resources)
Reflect.on(activity).set("mResources", pluginContext.resources)
Reflect.on(activity).set("mBase", pluginContext)
Reflect.on(activity).set("mApplication", pluginContext.applicationContext)
// for native activity
val componentName = intent!!.getParcelableExtra<ComponentName>(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)
}
}
public class PluginContext extends ContextWrapper {
private void generateResources() {
try {
// 反射生成 AssetManager 实例
assetManager = AssetManager.class.newInstance();
// 调用 addAssetPath 添加插件路径
Method method = assetManager.getClass().getMethod("addAssetPath", String.class);
method.invoke(assetManager, pluginPath);
// 生成 Resources 实例
resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
讲完上面两种方法,咱们这里对比一下这两种方法的优缺点:
实现方法 | 优势 | 缺点 |
---|---|---|
手动调用 | 1. 比较稳定,不须要 hook 系统实现 2. 实现相对简单,不须要对系统内部实现作过多了解 | 经过反射效率过低,经过接口须要实现的方法数量不少 |
hook 系统 | 1. 不须要实现大量接口方法 2. 因为最终仍是交给系统去处理,各类处理相对比较完整 | 1. 须要适配不一样的系统及设备 2. 对开发者要求比较高,须要对系统实现有深刻的了解 |
Service 比起 Activity 要简单很多,Service 没有太复杂的生命周期须要处理,相似的 onCreate 或者 onStartCommand 能够直接经过代理分发。能够直接在宿主 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) {
pluginClassLoader = classLoader
val intent = Intent(context, StubService::class.java)
intent.putExtra("serviceName", serviceName)
context.startService(intent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val res = super.onStartCommand(intent, flags, startId)
serviceName = intent?.getStringExtra("serviceName")
pluginService = pluginClassLoader?.loadClass(serviceName)?.newInstance() as Service
pluginService?.onCreate()
return pluginService?.onStartCommand(intent, flags, startId) ?: res
}
override fun onDestroy() {
super.onDestroy()
pluginService?.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
复制代码
动态广播的处理也比较简单,也没有复杂的生命周期,也不须要在 Manifest 中进行注册,使用的时候直接注册便可。因此只要经过 ClassLoader 加载插件 apk 中的广播类而后直接注册就好。
class BroadcastUtils {
companion object {
private val broadcastMap = HashMap<String, BroadcastReceiver>()
fun registerBroadcastReceiver(context: Context, classLoader: ClassLoader, action: String, broadcastName: String) {
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) {
val receiver = broadcastMap.remove(action)
context.unregisterReceiver(receiver)
}
}
}
复制代码
静态广播稍微麻烦一点,这里能够解析 Manifest 文件找到其中静态注册的 Broadcast 并进行动态注册,这里就不对 Manifest 进行解析了,知道其原理便可。
其实在平常开发中对于插件化中的 ContentProvider 使用仍是比较少的,这里只介绍一种比较简单的 ContentProvider 插件化实现方法,就是相似 Service,在宿主 app 中注册占位 ContentProvider,而后转发相应的操做到插件 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? {
loadPluginProvider()
return pluginProvider?.insert(uri, values)
}
override fun query(uri: Uri?, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
loadPluginProvider()
if (isPlugin1(uri)) {
return pluginProvider?.query(uri, projection, selection, selectionArgs, sortOrder)
}
return null
}
override fun onCreate(): Boolean {
uriMatcher?.addURI("com.zy.stubprovider", "plugin1", 0)
uriMatcher?.addURI("com.zy.stubprovider", "plugin2", 0)
return true
}
override fun update(uri: Uri?, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
loadPluginProvider()
return pluginProvider?.update(uri, values, selection, selectionArgs) ?: 0
}
override fun delete(uri: Uri?, selection: String?, selectionArgs: Array<out String>?): Int {
loadPluginProvider()
return pluginProvider?.delete(uri, selection, selectionArgs) ?: 0
}
override fun getType(uri: Uri?): String {
loadPluginProvider()
return pluginProvider?.getType(uri) ?: ""
}
private fun loadPluginProvider() {
if (pluginProvider == null) {
pluginProvider = PluginUtils.classLoader?.loadClass("com.zy.plugin.PluginContentProvider")?.newInstance() as ContentProvider?
}
}
private fun isPlugin1(uri: Uri?): Boolean {
if (uriMatcher?.match(uri) == 0) {
return true
}
return false
}
}
复制代码
这里面须要处理的就是,如何转发对应的 Uri 到正确的插件 Provider 中呢,解决方案是在 Uri 中定义不一样的插件路径,好比 plugin1 的 Uri 对应就是 content://com.zy.stubprovider/plugin1,plugin2 对应的 uri 就是 content://com.zy.stubprovider/plugin2,而后在 StubContentProvider 中根据对应的 plugin 分发不一样的插件 Provider。
本文介绍了插件化的相关实现,主要集中在 Activity 的实现上。重点以下:
最后推荐你们在学习插件化的同时,也去学习一些四大组件以及 Binder 的系统实现~
插件化历史
www.infoq.cn/article/and…
Activity 启动流程
blog.csdn.net/AndrLin/art…
gityuan.com/2016/03/12/…