首先声明,《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。

Android插件化的兼容性(上):Android O的适配的更多相关文章

  1. Android插件化的兼容性(中):Android P的适配

    Android系统的每次版本升级,都会对原有代码进行重构,这就为插件化带来了麻烦. Android P对插件化的影响,主要体现在两方面,一是它重构了H类中Activity相关的逻辑,另一个是它重构了I ...

  2. Android插件化的兼容性(下):突破Android P中灰黑名单的限制

    在Android P系统中,加入了访问私有API接口的限制.

  3. 《Android插件化开发指南》勘误

    一些常识: 1)全书70个代码例子中,涉及到插件的例子,请先assemble插件的项目,这会在HostApp项目中生成assets目录,并在该目录下plugin1.apk.这样,HostApp才能正常 ...

  4. 自己动手写Android插件化框架,让老板对你刮目相看

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由达文西发表于云+社区专栏 最近在工作中接触到了Android插件内的开发,发现自己这种技术还缺乏最基本的了解,以至于在一些基本问题上浪 ...

  5. 自己动手写Android插件化框架

    自己动手写Android插件化框架 转 http://www.imooc.com/article/details/id/252238   最近在工作中接触到了Android插件内的开发,发现自己这种技 ...

  6. Android插件化(三)载入插件apk中的Resource资源

    Android载入插件apk中的Resource资源 简单介绍 怎样载入未安装apk中的资源文件呢?我们从android.content.res.AssetManager.java的源代码中发现,它有 ...

  7. 有关Android插件化思考

    最近几年移动开发业界兴起了「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优越性,令人目不暇接.随着公司业务快速发展,项目增多,开发资源却有限,如何能在有限资源内 ...

  8. Android 插件化和热修复知识梳理

    概述 在Android开发中,插件化和热修复的话题越来越多的被大家提及,同时随着技术的迭代,各种框架的发展更新,插件化和热修复的框架似乎已经日趋成熟,许多开发者也把这两项技术运用到实际开发协作和正式的 ...

  9. Android插件化技术——原理篇

    <Android插件化技术——原理篇>     转载:https://mp.weixin.qq.com/s/Uwr6Rimc7Gpnq4wMFZSAag?utm_source=androi ...

随机推荐

  1. Python基础测试有关联的接口

    test_guanlian.py放在case文件夹下 test_guanlian.pyimport unittest import requestsfrom urllib.parse import u ...

  2. django后台对某些字段设置颜色

    在model.py的class函数中添加如下代码 #对签收状态设置颜色 def color_state(self): ': assign_state_name = '待签收' color_code = ...

  3. IDEA 上 Tigase8.0 源代码编译运行

    Tigase交流群 :310790965 一些小伙伴们经常问Tigase8 check下来的源代码怎么跑起来,因为我不能及时一 一回答,所以还是写个博文来演示一下吧,一般针对新手而言,老手的话,就跳过 ...

  4. luogu||P1776||宝物筛选||多重背包||dp||二进制优化

    题目描述 终于,破解了千年的难题.小FF找到了王室的宝物室,里面堆满了无数价值连城的宝物……这下小FF可发财了,嘎嘎.但是这里的宝物实在是太多了,小FF的采集车似乎装不下那么多宝物.看来小FF只能含泪 ...

  5. Java 字符编码(三)Reader 中的编解码

    Java 字符编码(三)Reader 中的编解码 我们知道 BufferedReader 可以将字节流转化为字符流,那它是如何编解码的呢? try (BufferedReader reader = n ...

  6. 茶杯头开枪ahk代码

    ;说明这个工具是为了茶杯头写的,F1表示换枪攻击,F3表示不换枪攻击,F2表示停止攻击. $F1::loop{ GetKeyState, state, F2, Pif state = D{break ...

  7. quartz一次简单应用经历

    项目里使用到每月的第一天需要定时统计上一个月的某种数据,这个时候,我开始第一次知道任务调度这么一个专业词汇.也初次了解quartz这个任务调度框架.网上看了好几篇博文,有种quartz越看越难的感觉, ...

  8. MySQL优化:使用show status查看MySQL服务器状态信息

    在网站开发过程中,有些时候我们需要了解MySQL的服务器状态信息,譬如当前MySQL启动后的运行时间,当前MySQL的客户端会话连接数,当前MySQL服务器执行的慢查询数,当前MySQL执行了多少SE ...

  9. 学习Acegi应用到实际项目中(10)- 保护业务方法

    前面已经讲过关于保护Web资源的方式,其中包括直接在XML文件中配置和自定义实现FilterInvocationDefinitionSource接口两种方式.在实际企业应用中,保护Web资源非常重要, ...

  10. easyui属性赋值

    了解easyui tree组件的童鞋估计都知道tree的node有他自己单独的属性(id,text,iconCls,checked,state,attribute,target).而原先这个几个属性想 ...