首先声明,Android 插件化开发指南这本书所介绍的 Android 底层是基于 Android6.0(API level 23) 的, 而本书介绍的各种插件化解决方案, 以及配套的 70 多个例子, 在 Android7.0(API level 24) 手机上测试都是能正常工作的.
如果读者您的手机是 Android 26,27, 甚至 28(也就是 Android P), 那么会有 30 个插件化的例子不能正常工作, 这是因为 Android 系统底层的源码改动导致的.
本篇文章, 专门介绍 Android O 的改动对插件化产生的影响, 以及相应的插件化解决方案.
(一) 从 ActivityManagerNative 的重构谈起
首先是 ActivityManagerNative 这个类的 gDefault 字段, 这个字段在 API 25 以及之前的版本, 定义如下:
- public abstract class ActivityManagerNative extends Binder implements IActivityManager {
- private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
- protected IActivityManager create() {
- IBinder b = ServiceManager.getService("activity");
- if (false) {
- Log.v("ActivityManager", "default service binder =" + b);
- }
- IActivityManager am = asInterface(b);
- if (false) {
- Log.v("ActivityManager", "default service =" + am);
- }
- return am;
- }
- };
- }
所以, 我们可以通过反射获取 ActivityManagerNative 的 gDefault 字段, 执行它的 create 方法, 得到 IActivityManager 接口类型的对象.
看到这个接口类型, 我们眼前一亮, 可以通过 Proxy.newProxyInstance 方法, hook 掉这个 IActivityManager 对象, 拦截它的 startActivity 方法, 把要启动的, 没有在 Manifest 中声明的 Activity, 替换成占坑 StubActivity, 代码如下所示:
- public static void hookAMN() throws ClassNotFoundException,
- NoSuchMethodException, InvocationTargetException,
- IllegalAccessException, NoSuchFieldException {
- // 获取 AMN 的 gDefault 单例 gDefault,gDefault 是 final 静态的
- Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");
- // gDefault 是一个 android.util.Singleton<T > 对象; 我们取出这个单例里面的 mInstance 字段
- Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", gDefault, "mInstance");
- // 创建一个这个对象的代理对象 MockClass1, 然后替换这个字段, 让我们的代理对象帮忙干活
- Class<?> classB2Interface = Class.forName("android.app.IActivityManager");
- Object proxy = Proxy.newProxyInstance(
- Thread.currentThread().getContextClassLoader(),
- new Class<?>[] { classB2Interface },
- new MockClass1(mInstance));
- // 把 gDefault 的 mInstance 字段, 修改为 proxy
- Class class1 = gDefault.getClass();
- RefInvoke.setFieldObject("android.util.Singleton", gDefault, "mInstance", proxy);
- }
我们在书中的第 5 章详细讲解过上述这些代码. 但不幸的是, 这些代码在 Android O(API level 26) 以上的系统版本中就不能运行了, 在运行到这句话的时候, gDefault 的值为空:
Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");
这是因为 Google 在 Android O 中, 把 ActivityManagerNative 中的这个 gDefault 字段删除了, 转移到了 ActivityManager 类中, 但此时, 这个字段改名为 IActivityManagerSingleton, 所以在 Android P 中, 要把这句话改为:
Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManager", "IActivityManagerSingleton");
但这又不兼容于 Android O 以下的版本了, 所以写一个 if-else 条件语句, 根据 Android 系统的版本, 来做不同的处理, 如下所示:
- Object gDefault = null;
- if (android.os.Build.VERSION.SDK_INT <= 25) {
- // 获取 AMN 的 gDefault 单例 gDefault,gDefault 是静态的
- gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");
- } else {
- // 获取 ActivityManager 的单例 IActivityManagerSingleton, 他其实就是之前的 gDefault
- gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManager", "IActivityManagerSingleton");
- }
(二)Element 和 DexFile 的兴衰史
接下来我们把目光转移到插件类的加载. 我们在书中介绍了 3 种加载方式:
1. 为每一个插件创建一个 ClassLoader, 用插件 ClassLoader 去加载插件中的类.
2. 把所有插件中的 dex, 都合并到宿主 App 的 dex 数组中.
3. 把宿主 App 所使用的 ClassLoader, 替换成我们自己创建的 ClassLoader, 在这个新的 ClassLoader 中, 有一个容器变量, 承载所有插件的 ClassLoader, 用来加载插件中的类.
这其中, 第 2 种方式的实现是最简单的, 也就是合并所有插件的 dex 到一个数组中, 具体代码实现如下所示:
- public final class BaseDexClassLoaderHookHelper {
- public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
- throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
- // 获取 BaseDexClassLoader : pathList
- Object pathListObj = RefInvoke.getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList");
- // 获取 PathList: Element[] dexElements
- Object[] dexElements = (Object[]) RefInvoke.getFieldObject(pathListObj, "dexElements");
- // Element 类型
- Class<?> elementClass = dexElements.getClass().getComponentType();
- // 创建一个数组, 用来替换原始的数组
- Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);
- // 构造插件 Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数
- Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};
- Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};
- Object o = RefInvoke.createObject(elementClass, p1, v1);
- Object[] toAddElementArray = new Object[] { o };
- // 把原始的 elements 复制进去
- System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
- // 插件的那个 element 复制进去
- System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);
- // 替换
- RefInvoke.setFieldObject(pathListObj, "dexElements", newElements);
- }
- }
这个思路没问题. 注意其中的这么几句话:
- Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};
- Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};
- Object o = RefInvoke.createObject(elementClass, p1, v1);
- Object[] toAddElementArray = new Object[] { o };
这几句话中, 通过反射执行了 Element 的带有 4 个参数的构造函数, 但不幸的是, 在 Android O 以及之后的版本, 这个带有 4 个参数的构造函数就被废弃了.
此外, 在这个构造函数中使用到的 DexFile 这个类, 也被废弃了, 对此 Google 给出的解释是, 只有 Android 系统可以使用 DexFile,App 层面不能使用它.
于是, 我们不得不另辟蹊径, 通过执行 DexPathList 类的 makeDexElements 方法, 来生成插件中的 dex:
- List<File> legalFiles = new ArrayList<>();
- legalFiles.add(apkFile);
- List<IOException> suppressedExceptions = new ArrayList<IOException>();
- Class[] p1 = {List.class, File.class, List.class, ClassLoader.class};
- Object[] v1 = {legalFiles, optDexFile, suppressedExceptions, cl};
- Object[] toAddElementArray = (Object[])
- RefInvoke.invokeStaticMethod("dalvik.system.DexPathList", "makeDexElements", p1, v1);
这段代码, 在 Android O 之前的版本也是适用的. 所以, 我们找到了比 DexFile 更好用的 makeDexElements 方法, 进行 Hook.
来源: http://www.bubuko.com/infodetail-2738498.html