最近维护Neptune框架的时候,发现插件在升级compileSdkVersion、targetSdkVersion到30之后,会出现插件无法安装的问题。这里记录下相关的内容。
这里先放下根本原因:Android R+不在允许app压缩resource.asrc。
这里简单描述下一些前置的背景知识:
以下的代码,就是主动获取插件Apk的一些基本信息,包括包名、版本号等。
PackageManager pm = context.getPackageManager();
PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkFilePath, PackageManager.GET_ACTIVITIES);
这个代码在compileSdkVersion、targetSdkVersion为30以下的时候,并不会有任何的问题。
这里放下当插件的compileSdkVersion、targetSdkVersion升级到30后出现的崩溃信息:
Targeting R+ (version 30 and above) requires the resources.arsc of installed APKs to be stored uncompressed and aligned on a 4-byte boundary.
相关代码请见:
相关源码:
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
String codePath, AssetManager assets, int flags) {
final String apkPath = apkFile.getAbsolutePath();
...忽略了大部分的代码...
final ParsingPackage pkg = result.getResult();
// 抛出异常的地方
if (assets.containsAllocatedTable()) {
final ParseResult<?> deferResult = input.deferError(
"Targeting R+ (version " + Build.VERSION_CODES.R + " and above) requires"
+ " the resources.arsc of installed APKs to be stored uncompressed"
+ " and aligned on a 4-byte boundary",
DeferredError.RESOURCES_ARSC_COMPRESSED);
if (deferResult.isError()) {
return input.error(INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED,
deferResult.getErrorMessage());
}
}
}
详见:Compressed resource files
Apps that target Android 11 (API level 30) or higher can’t be installed if they contain a compressed resources.arsc
file or if this file is not aligned on a 4-byte boundary. This file cannot by memory-mapped by the system if either of these conditions is present. Resources tables that cannot be memory-mapped must be read into a buffer in RAM resulting in unnecessary memory pressure on the system and greatly increased device RAM usage.
如果以 Android 11(API 级别 30)或更高版本为目标平台的应用包含压缩的 resources.arsc
文件或者如果此文件未按 4 字节边界对齐,应用将无法安装。如果存在其中任意一种情况,系统将无法对此文件进行内存映射。无法进行内存映射的资源表必须读入 RAM 中的缓冲区,从而给系统造成不必要的内存压力,并大大增加设备的 RAM 使用量。
zipalign 是一种归档对齐工具,可对 Android 应用 (APK) 文件提供重要的优化。
找一个无法安装的插件验证下:
➜ 30.0.2 ./zipalign -c -v 4 ~/Desktop/com.qiyi.xxx.apk
5078317 res/xml/filepaths.xml (OK - compressed)
5078776 res/xml/network_security_config.xml (OK - compressed)
5079095 res/xml/network_security_config_release.xml (OK - compressed)
5079286 resources.arsc (OK - compressed) # 被压缩了
Verification succesful
resources.arsc (OK - compressed)
。看起来是官方限制死了。当Android工程的compileSdkVersion、targetSdkVersion升级到30,强制要求resources.arsc不能被压缩。
关于资源编译相关的部分,可以先从booster分析-App资源压缩了解下基本原理。
图片源于:booster分析-App资源压缩
插件框架的Gradle插件主要是用于上述*.ap_
生成后,对资源文件进行修改、删除、添加的操作,以便固定资源id和剔除不必要的资源。
核心代码:com.qiyi.plugin.hooker.TaskHookerManager#reWriteArscFile
private void reWriteArscFile(ProcessAndroidResources par, ApkVariant variant) {
// 删除多余的资源
ZipUtil.with(apFile).deleteAll(filteredResources + updatedResources)
// 将有变动的资源重新使用aapt add回去
addUpdatedResources(aaptPath, resourcesDir, apFile, updatedResources)
}
/**
* 重新更新资源 - 通过验证,使用该命令添加的资源默认会被压缩
*
* $ aapt add resources.ap_ file1 file2
*/
def addUpdatedResources(String aaptPath, File resourcesDir,
File apFile, Collection<String> updatedResources) {
project.exec {
executable aaptPath
workingDir resourcesDir
args 'add', apFile.path
args updatedResources
standardOutput = System.out
errorOutput = System.err
}
}
aapt add resources.ap_ file1 file2
默认会压缩。从AAPT的文档中,没有发现add的命令支持不压缩的方案,我们只能手动的通过Java的Zip API将resources.arsc
以存档的形式补充回去。其他的资源还是老样子添加即可。
Class ZipEntry文档
STORED
:未压缩条目的压缩方法。DEFLATED
:压缩条目的压缩方法。这里就不放代码了,具体实现可以参见: