系统应用集成过程中的一些坑

本篇文章已授权微信公众号dasu_Android(大苏)独家发布这次想来讲讲系统应用集成过程中遇到的一些坑,尤其是so文件相关的坑。背景埋这些坑的最初来源是由于测试人员在集成新终端设备时提了个bug:app在这个设备上无法启动。随后抛来了一份日志,过滤了下,最重要的其实就一条,crash日志:java.lang.UnsatisfiedLinkError:Noimplementationfoundf...

系统应用集成过程中的一些坑

本篇文章已授权微信公众号 dasu_Android(大苏)独家发布

这次想来讲讲系统应用集成过程中遇到的一些坑,尤其是 so 文件相关的坑。

背景

埋这些坑的最初来源是由于测试人员在集成新终端设备时提了个 bug: app 在这个设备上无法启动。

随后抛来了一份日志,过滤了下,最重要的其实就一条,crash 日志:

java.lang.UnsatisfiedLinkError: No implementation found for long com.facebook.imagepipeline.memory.NativeMemoryChunk.nativeAllocate(int) (tried Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeAllocate and Java_com_facebook_imagepipeline_memory_NativeMemoryChunk_nativeAllocate__I)

app 使用了 fresco 图片库,最初猜想是不是因为 so 文件没有 push,因为我们的 app 是系统应用,集成的时候是直接将 apk push 到 system/app 下的,因为没有 install 过程,所以 so 文件得自己 push 到 system/lib 下。

但把机子拿过来一看,so 文件有在啊,尝试将其删掉,再运行,又报出了如下异常:

java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file “/system/app/xxxx.apk“],nativeLibraryDirectories=[/system/lib64/xxxx, /vendor/lib64, /system/lib64]]] couldn't find “libimagepipeline.so“

看了下日志,它是说,在 system/lib64 目录下没有找到 so 文件,奇怪,以前都是只集成到 system/lib 下就可以了啊,怎么这次多出了个 system/lib64,难道这个机子支持的 CPUABI 不一样?试着运行了下 getprop | get cpu

cpu.png

果然,这个机子支持的 CPUABI 多了个 arm64-v8a。

那这个机子既支持 arm64-v8a,又支持 armeabi-v7a,我怎么知道,我的 app 该将 so 文件集成在哪里,什么场景该放 system/lib 下,什么时候该集成到 system/lib64 中?还是说,两个地方都放?

应该不至于两个目录都得集成,因为三方应用安装时,从 apk 包中也只会解压一份 so 文件而已,并不会将 lib 下所有 abi 架构的 so 文件都解压。

后来,试着查找相关资料,发现可以在 data/system/packages.xml 文件中找到自己 app 的相关配置信息,这里有明确指出该去哪里加载 so 文件,以及 app 所运行的 CPU 架构,所以我们可以运行如下命令:

cat /data/system/packages.xml | grep {你自己app的包名}

packages.png

后来有些疑惑,这里的 primaryCpuAbi 属性值,系统是如何确定的,因为遇到过,明明这次的值是 armeabi-v7a,但当重启之后,有时候居然变成 arm64-v8a 了,所以就又去查找了相关资料,发现,这个值确定的流程蛮复杂的,影响因素也很多。

那么,就没有办法根据某些条件确定某个场景来确定 so 文件是该放 system/lib,还是 system/lib64 了,只能两个都集成了。于是乎,尝试着直接将 system/lib 下的 so 文件拷贝了一份到 system/lib64,结果发现运行报了如下异常:

java.lang.UnsatisfiedLinkError: dlopen failed: “libimagepipeline.so“ is 32-bit instead of 64-bit

哎,想当然了,不同 CPU 架构的 so 文件肯定不一样,哪里可以直接将 armeabi-v7a 的 so 文件放到 system/lib64 里。因此,重新编译、打包了一份 arm64-v8a 架构的 so 文件,集成到 system/lib64 下,再运行,搞定。

但你以为事情到这里就结束了吗?年轻人,too yang.

由于以前 app 合作的机子,都只有 armabi-v7a 的,所以集成方式就一种,只需要集成到 system/lib 下就可以了,但由于新合作的机子有 arm64-v8a 的了,那么此时就需要修改以前的集成方式,分别将对应的 so 文件集成到对应的 system/lib 和 system/lib64 目录下。

但运维人员表示说,他不懂这些,他怎么判断说,什么时候该用旧的集成方式,什么时候用新的集成方式。我跟他说,你需要先执行 getprop | grep cpu 命令,查看当前机子支持的 CPUABI,然后再来决定你如何集成。但运维又说,这好复杂,能否有方法就统一一种集成方式,不必分场景考虑。

emmm,你们都是老大,你们说了算。只能又去瞎搞了,这次去开源库的 issue 里尝试寻找了下,结果发现,哈哈哈,原来这么多人碰到过这个问题:

issus.png

要相信,你绝对不是第一个遇到问题的人。是吧,这么多人都来这里提问了,开源库的负责人肯定给出解决方案了,所以接下去继续在这些 issues 里过滤一下,找出那些跟你一样的问题就可以了。如下面这篇:

java.lang.UnsatisfiedLinkError #1552

issue.png

官方人员已经说了,可以尝试使用 Relinker 或 SoLoader 来解决。

最后,我选择了 ReLinker,发现它的源码并不多,直接将所有源码拷贝到项目中,修改了源码中某个流程的逻辑,用于解决我自己这种场景下的 so 文件加载问题,搞定,具体在下面的埋坑一节讲述。

这整个过程中,遇到了一个又一个问题,一个又一个坑,解决这个异常,出现另一个异常,但整个过程梳理过来,也掌握了很多干货知识点,下面就用自己的理解,将这些相关的知识点梳理一下:

知识点

看完本篇,你能了解到哪些知识点呢,如下:

P1:了解系统应用集成方式,大概清楚 apk 的 install 过程都做了些什么。

P2:知道如何判断系统应用是否安装成功,懂得查看 data/system/packages.xml 文件来得知应用的基础信息,如 so 库地址,primaryCpuAbi 等。

P3:掌握 System.load() 和 System.loadlibrary() 的区别。

P4:清楚系统寻找 so 文件的大体流程,知道系统什么时候会去 system/lib 下加载 so 文件,什么时候去 system/lib64。

P5:了解 ReLinker 和 SoLoder 库的用途和大体原理。

正文

ps: 由于接触尚浅,还看不懂源码,正文部分大多数是直接从各大神博客中梳理出的结论,再用以自己的理解表达出来,因为并没有结合源码来分析,因此给出的结论观点不保证百分百正确,如有错误,欢迎指点一下。

ps: 以下知识点梳理基于的设备系统 Android 5.1.1,api 22,不同系统的设备,也许过程会有些许差别。

1. install 过程

要了解 apk 的 install 过程都干了哪些事,先要清楚一个 apk 文件中都有哪些东西,其实 apk 文件就是一个压缩包,后缀改为 zip 就可以直接打开查看内容了,或者 Android Studio 的 Analyze APK 功能也可以查看:

apk结构.png

classes.dex 是源代码,到时候要加载进内存运行在机器上的;lib 是存放 so 文件;res 是存放资源文件,包括布局文件、图片资源等等;assert 同样存放一些资源文件;AndroidManifest.xml 是清单配置文件;

既然 apk 其实就是个压缩包,将程序运行所需要的东西,比如源代码,比如资源文件等等都打包在一起。那么,install 的过程,其实也就是解压&拷贝&解析的过程。

但不管是哪个过程,首先,这个 apk 文件得先传送到终端设备上,所以,我们开发期间使用的 adb install 命令,或者是直接点击 AndroidStudio 的 run 图标指令(本质上也是运行的 adb install),这个命令其实就干了两件事:

  1. adb push
  2. pm install

先将 apk push 到终端设备的临时目录,大多数场景下是:data/local/tmp

adbinstall.png

如果你有注意执行完 adb install 命令后,会先有一个百分比的进度,这个进度其实并不是安装的进度,而是 adb push 的进度,你可以试着直接执行 adb push 命令,看一下是否会有一个进度提示。

先将 apk 从电脑上 push 到终端设备,然后调用 pm install 命令来安装 apk。

调用了 pm install 命令后就会通知系统去安装这个 apk 了,也就是上述说的拷贝、解压、解析这几个过程。

拷贝是因为,存放在 data/local/tmp 下的 apk 文件始终是位于临时目录下,随时可能被删掉,系统会先将这个 apk 拷贝一份到 data/app 目录下。所以,在 data/app 这个目录下,你基本可以看到所有三方 app 的 apk 包,如果三方 app 都没有另外指定安装到 SD 卡的话。

拷贝结束后,就是对这个 apk 文件进行解压操作,获取里面的文件,将相关文件解压到指定目录,如:

  • 创建 data/data/{包名} 目录,存放应用运行期间所需的数据
  • 扫描 apk 包中 lib 目录的 so 文件结构,解压到应用自身存放 so 库的目录,不同版本系统路径有些不同,我设备的版本是 android 5.1.1,api 22,三方应用的 so 文件存放目录就在 data/app/{包名}-1/lib 下
  • class.dex 源代码转换成 odex 格式,缓存到 data/dalvik-cache 目录下,加快应用的代码运行效率
  • 解析 AndroidManifext.xml 文件以及其他相关文件,将 app 的相关信息写入 data/system/packages.xml 注册表中
  • 还有其他我不清楚的安装工作

梳理一下,安装 apk 过程中,就是解析 apk 中的内容,然后将不同作用的文件拷贝到指定目录中待用,涉及的目录有:

  • data/data/{包名}
  • data/dalvik-cache
  • data/app/{包名}-1/lib (后缀有可能是 -1,-2)
  • data/system/packages.xml

我没有找到存放 res,assert 这些资源文件的目录,所以我猜想,这些资源文件其实并没有解压出来,仍旧是存放在 apk 中。我是这么猜想的:

应用运行期间,类加载器所需的源代码是从 data/dalvik-cache 缓存中加载,如果这里没有缓存,则去 data/app/ 对应的 apk 中解压拿到 class.dex,转换成 odex,再次缓存到 data/dalvik-cache,然后让类加载器去加载。

而代码运行期间所需的数据库数据,xml 数据等则直接从 data/data/{包名} 中读取,如果代码需要 res 或 assert 资源文件,则再去 data/app 下的 apk 中拿取。

这是我的猜想,这也才能解释,为什么一旦将 data/app 下的 apk 删掉,应用就无法运行,而如果将 data/data/{包名} 以及 data/dalvik-cache 缓存的 odex 源代码文件删掉,应用仍旧可以照常运行。

正确性与否不清楚,只是我的猜想,以后有时间翻阅源码验证一下。

小结一下

一个三方 apk 的安装过程,不管是通过设备的有界面交互方式下的安装,还是没有交互界面直接通过 adb install 命令安装,还是通过 Android Studio 的 run (本质上是执行 adb install 命令) 安装。

这个过程,首先得先将 apk 文件传送到终端设备上,设备上有了这个 apk 后,系统安装应用的过程其实也就是先将这个 apk 拷贝一份到 data/app 目录下,然后对其进行解压工作,将 apk 包中的 so 文件解压出来,将 dex 文件解压之后对其进行优化处理缓存到 data/dalvik-cache 目录,以便加快之后应用的运行,最后解析 AndroidManifext.xml 文件,将这个应用的基本信息写入 data/system/packages.xml 文件中,然后创建 data/data/{包名} 目录供应用运行期间存放数据。

2. 系统应用安装

系统应用的安装方式就不同于三方应用了,系统应用无法通过 install 命令来安装,其实也可以说,adb install 安装的都是三方应用,这些 apk 最后都被安装到了 data/app 目录下。

系统应用只能是在出 rom 包时集成,也就是你设备第一次买来开机时就已跟随着 rom 包自带的应用,除非你的应用有 root 权限。这些应用可以升级,但升级后权限会降为三方应用,将不在拥有系统权限,但将升级后的删掉,重启,就又会恢复初始版本的系统应用了。

这是因为,系统应用的安装过程基本都是在系统启动时才去进行的。

常见的集成方式是直接将 apk 手动 push 到 system/app 目录下,同时解压出 apk 里面的 so 文件,手动将其 push 到 system/lib 下(大部分场景,有的需要 push 到 system/lib64)。

当 push 完成时,如果是首次 push,那么 data/system/packages.xml 注册表中是没有这个系统应用的任何信息的,所以需要重启一下,才能够运行这个应用。

系统在重启的时候,会去扫描 system/app 目录下的 apk 文件,如果发现这个 apk 没有安装,那么会去触发它的安装工作。这也是为什么重启有时候会很耗时,尤其是升级完 rom 包后,因为此时需要安装一些 apk。

<
源文地址:https://www.guoxiongfei.cn/cntech/2709.html