dynamic feature是Google利用了安卓系统自带的split apk机制提供的官方‘插件化‘方案,通过使用此技术,我们可以做到模块按需下发,减少应用包体积。但使用过程中有一些坑点需要注意。
在首次触发dynamic feature安装之后,若app未重启,此时可能会出现如下问题
- base apk中使用Class的getResourceAsStream方法会返回空
- 使用System.loadLibarary会失败
但base apk的class加载似乎不受影响。
猜测如下:
首次触发dynamic feature安装的时候,系统也会重新安装base apk? 导致base apk的目录发生改变。原base apk目录被删除,产生新的base apk目录,新的目录包含base apk以及dynamic feature apk
为什么getResourceAsStream方法会失败?
在安卓中最终负责此工作的ClassLoader是PathClassLoader,PathClassLoader会将此工作委托给DexPathList,然后经过如下步骤
- 检查DexPathList.Element中含有此资源
- 返回一个有效的uri,这个uri以jar协议开头,同时后面会携带apk的目录以及资源名拼接成的uri
- 打开此uri
问题可能出在第1/3步,当执行第1步时,如果dynamic feature已经安装,分情况讨论
如果对应的DexPathList.Element没有打开过,会进行初始化,由于对应的目录已经不存在了,所以初始化会失败,但第1步catch了IOException,不会发生崩溃,但会直接返回null,
如果对应的DexPathList.Element打开过了,内存中会保留对应文件的文件描述符,安卓系统底层的linux保证对应的文件描述符没关闭期间,文件被删除移动,该对应的文件描述符仍然可以访问文件。因此第二步能够检查成功,返回一个有效的uri,问题出在第三步,通过这个uri打开对应文件的时候,由于对应的文件apk已经被移动了,这里打开会失败。返回为空。
为什么使用System.loadLibarary会失败?
原因类似getResourceAsStream. System.loadLibrary也会把对应的工作最终交给DexPathList.Element. DexPathList会有两个路径,一个是从apk中解压出来对应架构的so目录,另外一个就是apk目录,通常来说,会直接从解压的目录加载对应的so,因此这一步在查找中会失败,因为文件夹已经不存在了。当尝试从apk文件中解析的时候则面临着和getResourceAsStream一样的处境。
有的人可能会有心理误区,会想只要我加载的so不在dynamic feature的模块中,那应该就不会出问题,因为安装split apk似乎不会对base apk做什么更改,但问题在于base apk的目录也被改变了!!因此直接就加载失败了。
为什么base apk的class加载似乎不受影响?
因为base apk的文件已经被打开并且没有被关闭,因此不会受到重新安装的影响。
SplitCompat.install做了什么
- 会通过反射调用AssetManager的addAssetPath把split apk的路径加进去。
- 似乎会通过在线程池发起一个任务,修改PathClassLoader的dexPathList
注意,splitapk安装,部分设备存在兼容性问题,首次安装会导致进程被杀死。