手机插件加载失败,手机无法加载插件解决方法

首页 > 实用技巧 > 作者:YD1662023-04-18 00:33:27

总结:

以上,通过编译时的一些处理,即可解决插件 java 代码中引用宿主资源时免资源固定的问题。

4.1.2 处理 xml 代码中引用宿主的资源

xml 中引用宿主资源的问题仅靠编译时是无法解决的,因为 xml 不像 java 代码一样可以执行逻辑,前面介绍了,xml 在编译结束后,资源全部编成了数值,而我们在编译时又无法知道未来运行在哪个宿主,值为多少。所以修改 xml 中资源id的工作只能搬到运行时去搞。当然也需要在编译时做一些事情,辅助运行时的修改操作。

运行时我们需要修改 apk 的 xml 中 0x7f 开头的资源,将其数值改为对应当前宿主的正确数值,而通过 xml,我们只能拿到一个数值,因此我们可以在插件编译时收集插件 xml 中使用的宿主资源所在的 xml 文件以及它们所对应的资源 name,运行时借助前文提到的mapRes方法即可获取到需要被修改后的值。

开始实践

前文介绍过,aapt2 编译/链接后会生成一个 ap_ 文件,这个文件中包含了最终会进入插件中的所有编译后的资源(包括各种 xml、resources.arsc、AndroidManifest.xml ),我们只需要分析这些文件中引用的 0x7f 开头的资源,根据 R.txt(aapt2生成的一个文件)找到对应的资源名,将资源名、id 值、所在文件记录到一个文件中,一并打包进插件 apk 中。

至于如何扫描这些文件中 0x7f 的资源,我们在不同阶段使用了不同方式,大家可以自行选择:

  1. 使用 aapt2 命令 dump 文件信息,分析 dump 后的文本内容(我们编译时是这么做的,简单粗暴、性能较差、不够优雅);
  2. 根据文件格式分析对文件内容进行解析,找到 0x7f 开头的资源(比较优雅,效率也高,我们运行时是这样做的)。

总结:

以上,便生成了一个文件,内部存储了插件 xml 中使用到的宿主资源的信息。大概长下面这样:

手机插件加载失败,手机无法加载插件解决方法(5)

前文一直在说 xml 中使用的宿主资源,看上面这个配置文件发现 fileNames 中怎么会有 resoureces.arsc ?它明明不是 xml 文件?

其实 Android 资源编译之后,values 相关的一些资源文件都不存在了,会直接进入到 resources.arsc 中,layout 这类文件还存在,resoureces.arsc 中 layout 指向的正是各种 layout.xml,而 string 等 value 类型的资源指向的是一个真实的内容。感兴趣的同学可以通过 Android Studio 打开 apk,观察一下 resources.arsc 中的结构。

4.2 插件安装时的工作

前面介绍了在插件编译时,给 java 代码中插入了一些逻辑,实现了插件动态根据宿主环境获取资源 id 的效果。但是 xml 编译完之后,资源 id 都直接编译成了数字,xml 中也无法插入逻辑,因此我们只能在插件运行前,根据宿主环境进行修改。

插件在宿主中运行前都有一个插件安装的过程,类似于 apk 在 Android 系统中的安装,因此只需要在每次插件安装前,或者宿主升级后,根据编译时生成的配置文件,结合 mapRes 方法,对插件中的 xml、resources.arsc 文件进行修改即可。

确定了修改时机和修改内容,接下来就要详细介绍怎么修改这些文件了。

5. 修改 apk 中的资源文件

5.1 如何修改 xml、arsc 文件

Android 中的 layout、drawable、AndroidManifest 等文件在编译成 apk 后,不再是常规的 xml 文件了,而是新的一种文件格式 Android Binary XML,我们这里称之为 axml。那么如何修改 axml 文件呢?

所有的文件都有自己的文件格式,程序在读取文件时都是读的 byte 数组,然后根据文件格式解析 byte 数组中每一个元素的含义。因此我们只需要了解了 axml 的文件格式,按照规范解析这个文件,在 byte 数组中找到其中表示资源 id 的位置,将原本的资源 id 根据 resMap 方法映射出新的值,然后修改 byte 数组中对应的部分。(非常幸运,我们这里修改的只是 axml 文件中的一个 8 位 16 进制数,这个修改不会导致文件中内容的长度、偏移等信息改变,因此直接替换对应部分的 byte 数组即可。)

resources.arsc 是 apk 的资源索引表,里面记录了 apk 中所有的资源,对于 values 类型的资源,资源对应的内容会全部进入到 resources.arsc 中,因此我们也需要对这个文件进行修改(如一个 style 的 parent 是宿主资源,我们就需要修改它)。修改的方法和 xml 类似,只需要按照规范解析 byte 数组,找到要修改内容的偏移量,替换即可。

关于 axml、arsc 的文件格式,网上有很多文章介绍,这里就不详细叙述了。

Apktool 是一款强大、开源的逆向工具,它可以把 apk 反编译成源码,那它肯定也有读取 apk 中 axml、arsc 的代码,不然怎么输出一个可以编辑的 xml 源码文件?所以我们可以直接去扒 apktool 中读取 axml、arsc 的代码,当读取到 axml 中属于宿主的 id 时,记录一下 byte 数组的偏移量,直接替换对应位置的 byte 子数组。

aapt2 为我们提供了 dump 资源内容的能力,可以帮助我们直接用“肉眼”去看 axml、arsc 的内容,借助这个工具可以让我们很方便的确认修改内容,验证修改是否生效。以 30.0 版本的 build-tools 中的 aapt2 为例,它的命令为aapt2 dump apk路径 --file 资源路径。后面不跟--File 资源路径,会直接 dump arsc。

以下是 dump 出来的 arsc,可以看到最后一个 style 的 parent 是一个 0x7f 开头的宿主资源。

手机插件加载失败,手机无法加载插件解决方法(6)

以下是 dump 出来的 activity_plugin1.xml,可以看到 TextView 中引用了一个宿主中的资源作为 backgroud。

手机插件加载失败,手机无法加载插件解决方法(7)

5.2 修改 apk 中的 xml/arsc 文件

以上我们知道了如何修改一个 axml、arsc 文件。插件安装时我们拿到的是 apk 文件,那么如何修改 apk 中的 axml、arsc 文件呢?

5.2.1 重压缩方式修改

Apk 其实就是一个 zip 文件,修改 apk 中的文件内容,首先想到的最简单的方法就是读取 zipFile 里面的文件,修改之后重压缩。

java 为我们提供了一套操作 zipFile 的 api,我们可以轻松的将 zip 文件中的内容读取到内存,在内存中修改之后利用 ZipOutputStream 重新写入到新的 zipFile 中。

代码实现非常简单。修改成功后,测试发现是可行的,那我们的第一步就算是成功了,说明运行时动态修改插件的路子是行的通的。

窃喜之于,发现修改过程十分耗时。以公司的直播插件为例(直播插件大约 30 MB,属于比较大的插件了),在 9.0 及其以上的设备上耗时约 8s,在 7~8 的设备耗时大约 20~40s,在 7.x 以下设备大约耗时 10~20s。尽管插件安装是在后台进行,适当的增加一些时间是可以接受的,但是几十秒的耗时很明显不可以接受。那我们只能想别的办法了。

关于各个版本的耗时差异:

Android7.0 开始,官方使用 ZLIB 来提供 Deflater、Inflater 的实现,优化了解压压缩算法速度(可以查看 Deflater.java、Inflater.java 的注释)。但是 7.x/8.x 的 ZipFileInputStream 在读取数据时有一个 8192 的 BUFSIZE 限制( 8.x 之后移除了这个限制),导致在读取数据时循环次数增多,效率反而下降。

7.0 开始,ZipFileInpugStream 在读取数据时是通过 native 方法 ZipFile_read 进行的。以下是 android8.0 和 android9.0 中 ZipFile_read 的部分代码。

手机插件加载失败,手机无法加载插件解决方法(8)

上一页123下一页

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.