每一个Android插件框架要解决的首要问题都是Activity的生命周期问题。Activity表明了Service等其余须要注册的组件。不一样是插件框架解决这个问题的前提也不彻底同样。而咱们的业务要求比较苛刻,再加上Android 9.0的非公开API限制,因此由这些前提要求:html
其实咱们早就在用一款也是基于代理组件转调插件组件的插件框架了。只不过这款插件框架用到了大量反射使用私有API,眼看着是不可能再Android 9.0上继续使用了。咱们也调研了外界口碑最好的RePlugin。因此大概就这两种方向,一是用代理Activity做为壳子注册在宿主中真正运行起来,而后让它持有插件Activity,想办法在收到系统的生命周期方法调用时转调插件Activity的对应生命周期方法。二是Hack修改宿主PathClassLoader,让它能在收到系统查询AndroidManifest中注册的Activity的类时返回插件的Activity类。android
方法二就是RePlugin的关键技术。它利用了JVM的特性。我也不太确定这算不算是bug,总之ClassLoader的loadClass方法返回的实际类能够和它被要求加载的类名字不同。举个例子,宿主的AndroidManifest.xml注册一个Activity名叫A,插件里有一个Activity名叫B。宿主代码或者apk中最终是没有A这个类的,只有在AndroidManifest中注册的一个名字而已。当想要加载插件Activity B时,就发出一个启动Activity A的Intent。系统收到这个Intent后会检查宿主安装的AndroidManifest信息,从中肯定A是哪一个apk安装的,就会找到宿主的PathClassLoader。而后系统就会试图从PathClassLoader中加载A这个类,而后做为Activity类型的对象使用(这很正常)。因此若是咱们把宿主的PathClassLoader给Hack了,控制它的加载逻辑,让它收到这个加载调用时实际返回的是插件Activity B的类。因为B也真的是Activity的子类,因此系统拿回去看成Activity类型使用没有任何问题。这里再扩展一下,若是类C继承自类A,在加载C时也会去加载A,若是这时拿B当A返回的话,C收到B以后是会发现B的名字不是A而出错的。关于RePlugin这段关键技术的实现,当时调研时就发现实现的有些麻烦了。RePlugin选择复制一个PathClassLoader,而后替换系统持有的PathClassLoader。因此复制PathClassLoader须要反射使用PathClassLoader的私有API,拿出来它里面的数据,替换系统持有的PathClassLoader也要反射修改私有API。咱们当时已经实现了“全动态插件框架”,其中代理壳子Activity的动态化使用的方法也能解决这个问题,咱们的选择是在宿主PathClassLoader上给它加一个parent ClassLoader。由于PathClassLoader也是一个有正常“双亲委派”逻辑的ClassLoader,它加载什么类都会先问本身parent ClassLoader先加载。因此咱们加上去的这个parent ClassLoader也能完成RePlugin想要作的事。不过咱们用它的目的是不但愿壳子Activity打包在宿主占用宿主不少方法数,还不能更新。这一点之后可能再单独讲。关于这个替换实现,最近给RePlugin提了一个PR:github.com/Qihoo360/Re… ,有兴趣的同窗能够看一下。git
RePlugin的这种方案还有一点很是不适合咱们的业务,就是宿主AndroidManifest中注册的“坑位”Activity,就是上面举例的Activity A,是不能同时供多个插件Activity使用的。就是我不能在宿主AndroidManifest中注册一个Activity A,而后让它同时支持插件Activity B和C。这是由于ClassLoader在loadClass的时候,收到的参数只有一个A的类名,咱们没有办法传递更多信息,让ClassLoader能在这个loadClass的调用中区分出应该返回B仍是应该返回C。因此这种方案须要在宿主中注册大量Activity,这对于咱们的宿主来讲是不可接受的。而方法一是用代理Activity持有插件Activity转调的方案,就能够在启动代理Activity时经过Intent传递不少参数,代理Activity经过Intent中的参数就能决定该构造一个B仍是一个C。这就使得这种方案下壳子是可复用的。github
还有一点就是咱们在旧框架上就已经设计了“全动态插件框架”,因此基于方法一的方向上开发新插件框架,咱们能够不修改宿主的任何代码,不跟宿主版本就能更新插件框架。关于这一点,后续文章再解析。编程
因此咱们探索的方向就这样肯定在方法一这个方向上了。bash
咱们的旧框架就是代理Activity转调插件Activity的方案,市面上还有不少插件框架也是这种方案。你们只是在实现转调的手段上不同。这里就不深刻分析它们都是怎么实现的了,总之就是一个目的,让插件Activity能收到生命周期回调。咱们的旧框架和其余一些框架有一种这样的方案,就是让壳子Activity直接反射调用插件Activity对应的生命周期方法。这样作要解决一个额外的问题。框架
Activity被系统构造出实例以后,并非直接调用onCreate方法的。首先会调用它的attach方法。attach方法实际上就是Activity的初始化方法,系统经过这个方法向Activity注入一些私有变量,好比Window、ActivityThread等等。插件Activity因为是咱们壳子Activity本身new出来的,因此系统不会调用插件Activity的attach方法初始化它。Activity若是没有初始化就被调用了onCreate会有什么问题呢?咱们前面说了咱们的一个前提是插件Activity要求也要能正常编译安装运行,因此插件Activity的onCreate方法里必定写了super.onCreate()
调用。咱们还要求对插件代码无侵入性,因此也不能在这个调用外面包一层“if (不是插件模式)"。那么在插件环境下,这个super.onCreate()
就必定会执行。Activity基类的onCreate方法就会使用那些应该初始化过的私有变量,可是如今它们没有初始化。因此这一类插件框架方案就要解决这个问题。因此要么是反射调用attach方法,传入从壳子Activity拿到的私有变量,好比说反射出壳子Activity的ActivityThread对象,传给插件Activity的attach方法。要么就干脆直接用反射枚举读写壳子Activity和插件Activity的私有变量,把它们写成同样的完成这个初始化。因此这就是为何旧框架须要使用反射和私有API。ide
实际上经过前面的分析,咱们发现其实根本不须要插件Activity执行super.onCreate()
方法。明确了这个方案本来的目的,就是在宿主中注册并启动一个壳子Activity,这个壳子Activity什么都不本身作,想办法让插件Activity的各个生命周期方法实现代码成为壳子Activity的各个生命周期实现方法的代码。所以咱们根本不须要插件Activity是一个系统Activity的子类。咱们只是由于须要插件Activity还能正常安装运行,才致使它是一个真正的系统Activity子类的。spa
咱们也知道若是不要求对插件代码无侵入性,也不要求插件能独立安装运行,其实是能够把让插件Activity不用继承系统Activity了,就简单继承一个普通类就好了。这个普通类上定义一些跟系统Activity类同样的生命周期方法,实现成空实现,而后这些生命周期方法能够设置成public的,这样壳子Activity以这个普通类类型持有插件Activity就能够直接调用插件Activity的生命周期方法了。这样实现既不用反射也不用私有API。插件
而咱们其实是不须要插件的apk能独立安装运行的,咱们但愿插件能独立安装运行的本质目的是节省人力,不要维护两套代码。因此看起来,这里只要引入AOP手段,经过AOP编程修改插件Activity的父类,把插件Activity的父类从系统Activity改为咱们想要的普通类就好了。
咱们先不说这个AOP手段怎么实现,由于问题还没完全搞清。咱们是真的想让A持有B,A收到什么调用就转调B的什么调用吗?这是咱们的真正目的吗?不是的。单纯的只是让插件Activity中的super.onCreate()
调用失效,并不完美。由于这跟插件Activity正常安装运行时还有点不同。如今插件Activity的onCreate方法的代码就至关因而壳子Activity的onCreate方法的代码的一部分了。好比:
class ShadowActivity {
public void onCreate(Bundle savedInstanceState) {
}
}
class PluginActivity extends ShadowActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.out.println("Hello World!");
}
}
class ContainerActivity extends Activity {
ShadowActivity pluginActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pluginActivity.onCreate(savedInstanceState);
}
}
复制代码
上面的ShadowActivity
就是咱们前面说的普通类。仔细看一下是否是就至关于ContainerActivity
本来就实现成这样:
class ContainerActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.out.println("Hello World!");
}
}
复制代码
只要PluginActivity
是动态加载的,就至关于ContainerActivity
的实现是动态的。可是若是本来PluginActivity
的代码是这样的呢?
class PluginActivity extends ShadowActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
savedInstanceState.clear();
super.onCreate(savedInstanceState);
}
}
复制代码
显然这种代码在正常安装运行时和插件环境运行时就不同了。由于变成了:
class ContainerActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
savedInstanceState.clear();
}
}
复制代码
这说明咱们要的不是插件Activity的super.onCreate()
调用不执行,咱们是但愿插件Activity的super.onCreate()
可以直接指挥壳子Activity何时调用super.onCreate()
。并且再想一想,这是否是继承关系很像?假如PluginActivity是继承自ContainerActivity的,运行时系统调用的是PluginActivity的实例,那么PluginActivity的super.onCreate()
就会直接指导ContainerActivity何时调用super.onCreate()
了。因此咱们在这里的真正需求是如何把本来的继承关系用持有关系实现了。因此Shadow是这样实现的:
class ShadowActivity {
ContainerActivity containerActivity;
public void onCreate(Bundle savedInstanceState) {
containerActivity.superOnCreate(savedInstanceState);
}
}
class PluginActivity extends ShadowActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
savedInstanceState.clear();
super.onCreate(savedInstanceState);
}
}
class ContainerActivity extends Activity {
ShadowActivity pluginActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
pluginActivity.onCreate(savedInstanceState);
}
public void superOnCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
复制代码
仔细思考下如今的调用结果,是否是PluginActivity在正常安装运行和插件环境下运行时行为就一致了?
好了,如今就能够考虑怎么实施AOP手段将PluginActivity的父类从系统Activity改成ShadowActivity了。Shadow最终使用的是字节码编辑手段。字节码编辑能够经过Android官方的构建过程当中的Transform API来实现,因此这个手段也是利用公开API实现的。关于字节码编辑的细节之后的文章再讲。总之,经过字节码编辑,咱们能够在不修改源码的状况下,将插件Activity的父类从系统Activity改成ShadowActivity。从而达到同一份源码应用不一样编译选项生成不一样的apk,一个能正常安装运行,一个能在插件环境运行。
其实字节码编辑并非达到咱们的目的的惟一手段。最初我并无一上来就用字节码编辑实现,由于字节码编辑在构建工程上要使用更多技巧,这些技巧后面也会分享。想达到这个AOP目的最简单的方法是利用Java语言的基本特性。Java类在编译时是没有连接过程的,在Java类的字节码中只记录了依赖的其余类的名字,在运行的时候才会去问ClassLoader查找这些类的具体实现。因为插件的ClassLoader就是咱们自行建立的,因此咱们彻底能够在实现插件的ClassLoader时将它设计成不遵循“双亲委派”机制的ClassLoader。在插件Activity查找它的父类系统Activity时,咱们就给它返回一个假的系统Activity类。把ShadowActivity当作系统Activity返回给它就能够了。这个代码实现就比字节码编辑简单太多了。惋惜的是,Android的Java虚拟机并非一个标准的JVM。对于系统内置的类,好比系统Activity来讲,它会直接使用预编译好的Native实现。在有些手机上Debug模式没有这一特性,上述方案就能正常运行。在Release模式下就会使用预编译的Native实现,致使JVM崩溃。我想写Android JVM虚拟机的人就没想到会有人返回一个假的系统类吧,因此它甚至没有抛出异常,出现了野指针。关于这一点,能够参考这篇文章了解一些细节:mshockwave.blogspot.com/2016/03/int…
后续的开发也利用了字节码编辑作了更多事情,那些事情是不能用ClassLoader技巧实现的。关于那些事情也在将来分享吧。
因此,这篇文章讲的东西能够说就是Shadow实现零反射、无非公开API调用的关键了。怎么样?大家以为是否是真的是一层窗户纸,一捅就破了?解决问题的关键不是最后的代码,而是最初的思想。“任何软件工程遇到的问题均可以经过增长一个中间层来解决”,Shadow在解决这个问题上加入的ShadowActivity就是这个中间层。经过这个中间层让Android系统看不到插件,也让插件看不到Android系统。这样就不会打破Android系统的限制。反观RePlugin的方案,在大方向上就将未安装的Activity交给了系统,所以后面必然不得不继续用Hack手段解决系统看到了未安装的Activity的问题。好比系统为插件Activity初始化的Context是以宿主的apk初始化的,插件框架就不得再也不去Hack修复。因此大方向的选择很重要。