RK3399预置APK及问题
背景
- Platform: RK3399
- OS: Android7.1.2
- Kernel: v4.4.103
正常流程
按照官方文档,步骤如下:
- 在
device/rockchip/rk3399/xxx/
目录下新建preinstall_del
、preinstall_del_forever
或preinstall
文件夹。preinstall_del
:可卸载预装,恢复出厂后应用会恢复preinstall_del_forever
:可卸载预装,恢复出厂后应用不会恢复preinstall
:不可卸载预装
- 拷贝预安装的APK到上述新建的文件夹中,注意文件名尽量使用英文,避免空格
- 编译。
auto_generator.py
会在编译过程中,将拷贝的apk解包,自动生成编译mk文件。编译完之后预置的APK会拷贝到system固件中,烧录后,系统启动的时候会自动安装到data/app
目录中(通过PackageManager
)
注:预置的 APK 应用需要得到对应厂商授权
附auto_generator.py
源码:
#!/usr/bin/env python
import sys
import os
import re
import zipfile
import shutil
templet = """include $(CLEAR_VARS)
LOCAL_MODULE := %s
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/%s
LOCAL_SRC_FILES := $(LOCAL_MODULE)$(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
#LOCAL_DEX_PREOPT := false
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_JNI_SHARED_LIBRARIES_ABI := %s
MY_LOCAL_PREBUILT_JNI_LIBS := %s
MY_APP_LIB_PATH := $(TARGET_OUT_VENDOR)/%s/$(LOCAL_MODULE)/lib/$(LOCAL_JNI_SHARED_LIBRARIES_ABI)
ifneq ($(LOCAL_JNI_SHARED_LIBRARIES_ABI), None)
$(warning MY_APP_LIB_PATH=$(MY_APP_LIB_PATH))
LOCAL_POST_INSTALL_CMD := \
mkdir -p $(MY_APP_LIB_PATH) \
$(foreach lib, $(MY_LOCAL_PREBUILT_JNI_LIBS), ; cp -f $(LOCAL_PATH)/$(lib) $(MY_APP_LIB_PATH)/$(notdir $(lib)))
endif
include $(BUILD_PREBUILT)
"""
copy_app_templet = """LOCAL_PATH := $(my-dir)
include $(CLEAR_VARS)
LOCAL_APK_NAME := %s
LOCAL_POST_PROCESS_COMMAND := $(shell mkdir -p $(TARGET_OUT_VENDOR)/%s/$(LOCAL_APK_NAME) && cp $(LOCAL_PATH)/$(LOCAL_APK_NAME).apk $(TARGET_OUT_VENDOR)/%s/$(LOCAL_APK_NAME)/)
"""
def main(argv):
preinstall_dir = os.path.join(argv[1],argv[2])
if os.path.exists(preinstall_dir):
#Use to include modules
isfound = 'not_found_lib'
include_path = preinstall_dir + '/preinstall.mk'
android_path = preinstall_dir + '/Android.mk'
if os.path.exists(include_path):
os.remove(include_path)
if os.path.exists(android_path):
os.remove(android_path)
includefile = file(include_path, 'w')
androidfile = file(android_path, 'w')
androidfile.write("include $(call all-subdir-makefiles)\n\n")
MY_LOCAL_PREBUILT_JNI_LIBS = '\\' + '\n'
for root, dirs, files in os.walk(preinstall_dir):
for file_name in files:
p = re.compile(r'\S*(?=.apk\b)')
found = p.search(file_name)
if found:
include_apk_path = preinstall_dir + '/' + found.group()
makefile_path = include_apk_path + '/Android.mk'
apk = preinstall_dir + '/' + found.group() + '.apk'
try:
zfile = zipfile.ZipFile(apk,'r')
except:
if os.path.exists(include_apk_path):
shutil.rmtree(include_apk_path)
os.makedirs(include_apk_path)
apkpath = preinstall_dir + '/' + found.group() + '/'
shutil.move(apk,apkpath)
makefile = file(makefile_path,'w')
makefile.write("LOCAL_PATH := $(my-dir)\n\n")
makefile.write(templet % (found.group(),argv[3],'None',MY_LOCAL_PREBUILT_JNI_LIBS,argv[3]))
continue
for lib_name in zfile.namelist():
include_apklib_path = include_apk_path + '/lib' + '/arm'
if os.path.exists(include_apk_path):
shutil.rmtree(include_apk_path)
os.makedirs(include_apklib_path)
makefile = file(makefile_path,'w')
makefile.write("LOCAL_PATH := $(my-dir)\n\n")
apkpath = preinstall_dir + '/' + found.group() + '/'
for lib_name in zfile.namelist():
lib = re.compile(r'\A(lib/armeabi-v7a/)+?')
find_name = 'lib/armeabi-v7a/'
if not cmp(lib_name,find_name):
continue
libfound = lib.search(lib_name)
if libfound:
isfound = 'armeabi-v7a'
data = zfile.read(lib_name)
string = lib_name.split(libfound.group())
libfile = include_apklib_path + '/' + string[1]
MY_LOCAL_PREBUILT_JNI_LIBS += '\t' + 'lib/arm' + '/' + string[1] + '\\' + '\n'
if(os.path.isdir(libfile)):
continue
else:
includelib = file(libfile,'w')
includelib.write(data)
if not cmp(isfound,'not_found_lib'):
for lib_name in zfile.namelist():
lib = re.compile(r'\A(lib/armeabi/)+?')
find_name = 'lib/armeabi/'
if not cmp(lib_name,find_name):
continue
libfound = lib.search(lib_name)
if libfound:
data = zfile.read(lib_name)
string = lib_name.split(libfound.group())
libfile = include_apklib_path + '/' + string[1]
MY_LOCAL_PREBUILT_JNI_LIBS += '\t' + 'lib/arm' + '/' + string[1] + '\\' + '\n'
if(os.path.isdir(libfile)):
continue
else:
includelib = file(libfile,'w')
includelib.write(data)
tmp_jni_libs = '\\' + '\n'
if not cmp(MY_LOCAL_PREBUILT_JNI_LIBS,tmp_jni_libs):
nolibpath = preinstall_dir + '/' + found.group() + '/lib'
shutil.rmtree(nolibpath)
makefile.write(templet % (found.group(),argv[3],'None',MY_LOCAL_PREBUILT_JNI_LIBS,argv[3]))
else:
if argv[2]=='preinstall_del' or argv[2]=='preinstall_del_forever':
makefile.write(copy_app_templet % (found.group(), argv[3], argv[3]))
else:
makefile.write(templet % (found.group(),argv[3],'arm',MY_LOCAL_PREBUILT_JNI_LIBS,argv[3]))
shutil.move(apk,apkpath)
isfound = 'not_found_lib'
MY_LOCAL_PREBUILT_JNI_LIBS = '\\' + '\n'
makefile.close()
break
for root, dirs,files in os.walk(preinstall_dir):
for dir_file in dirs:
includefile.write('PRODUCT_PACKAGES += %s\n' %dir_file)
break
includefile.close()
if __name__=="__main__":
main(sys.argv)
排坑记录
问题
预置打包我们公司一个产品的APK进去,发现系统起来以后没有安装。查看日志,发现报错:
PackageManager: Failed to parse /system/vendor/bundled_uninstall_back-app/xxx: Failed to collect certificates from /system/vendor/bundled_uninstall_back-app/xxx/xxx.apk
分析过程
参照网上别人遇到的坑,由于apk的
Signature Scheme v2标识
导致。但我们的APK是debug版本,使用的是开发工具默认的签名,且通过查看源码(/frameworks/base/core/java/android/content/pm/PackageParser.java
),如果是Signature Scheme v2 signature
问题,log后面会加"using APK Signature Scheme v2"
,所以这个原因排除。通过
adb install
手动安装没问题,如果是上面的签名问题,手动安装都会报错,所有进一步证实了前面一点。如果手动安装没有问题,则很有可能是预置apk后系统编译阶段出问题。进一步分析上面的
auto_generator.py
脚本,发现该脚本不支持仅包含arm64-v8a库的apk,如果仅包含arm64库,lib不会被解压抽取。进一步分析APK的编译配置。在该APK的
build.gradle
文件中指定了ABI 配置,原始的如下:... defaultConfig { ... ndk { abiFilters 'armeabi', 'arm64-v8a' } }
而刚好该apk依赖的库文件里面包括
'armeabi-v7a'
,且配置里面没有添加'armeabi-v7a'
。解压编译出来的APK,发现确实没有/lib/armeabi-v7a
目录,仅有/lib/arm64-v8a
。解压后面正常的APK,2个目录都有。进一步分析
PackageManager
源码,出现Failed to collect certificates from
的地方共有4处,log后面加apkPath
的有3处,且上面Signature Scheme v2
的地方排除了1处。主要是在检查APK完整性是抛出了异常:StrictJarFile jarFile = null; try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor"); // Ignore signature stripping protections when verifying APKs from system partition. // For those APKs we only care about extracting signer certificates, and don't care // about verifying integrity. boolean signatureSchemeRollbackProtectionsEnforced = (parseFlags & PARSE_IS_SYSTEM_DIR) == 0; jarFile = new StrictJarFile( apkPath, !verified, // whether to verify JAR signature signatureSchemeRollbackProtectionsEnforced); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); // Always verify manifest, regardless of source final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME); if (manifestEntry == null) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Package " + apkPath + " has no manifest"); } // Optimization: early termination when APK already verified if (verified) { return; } // APK's integrity needs to be verified using JAR signature scheme. Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV1"); final List<ZipEntry> toVerify = new ArrayList<>(); toVerify.add(manifestEntry); // If we're parsing an untrusted package, verify all contents if ((parseFlags & PARSE_IS_SYSTEM_DIR) == 0) { final Iterator<ZipEntry> i = jarFile.iterator(); while (i.hasNext()) { final ZipEntry entry = i.next(); if (entry.isDirectory()) continue; final String entryName = entry.getName(); if (entryName.startsWith("META-INF/")) continue; if (entryName.equals(ANDROID_MANIFEST_FILENAME)) continue; toVerify.add(entry); } } // Verify that entries are signed consistently with the first entry // we encountered. Note that for splits, certificates may have // already been populated during an earlier parse of a base APK. for (ZipEntry entry : toVerify) { final Certificate[][] entryCerts = loadCertificates(jarFile, entry); if (ArrayUtils.isEmpty(entryCerts)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " + apkPath + " has no certificates at entry " + entry.getName()); } final Signature[] entrySignatures = convertToSignatures(entryCerts); if (pkg.mCertificates == null) { pkg.mCertificates = entryCerts; pkg.mSignatures = entrySignatures; pkg.mSigningKeys = new ArraySet<PublicKey>(); for (int i=0; i < entryCerts.length; i++) { pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey()); } } else { if (!Signature.areExactMatch(pkg.mSignatures, entrySignatures)) { throw new PackageParserException( INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath + " has mismatched certificates at entry " + entry.getName()); } } } Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } catch (GeneralSecurityException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, "Failed to collect certificates from " + apkPath, e); } catch (IOException | RuntimeException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Failed to collect certificates from " + apkPath, e); } finally { closeQuietly(jarFile); }
生成的Android.mk文件对比
有问题的:
LOCAL_PATH := $(my-dir) include $(CLEAR_VARS) LOCAL_MODULE := LVI8031A_error LOCAL_MODULE_CLASS := APPS LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/bundled_uninstall_back-app LOCAL_SRC_FILES := $(LOCAL_MODULE)$(COMMON_ANDROID_PACKAGE_SUFFIX) LOCAL_CERTIFICATE := PRESIGNED #LOCAL_DEX_PREOPT := false LOCAL_MODULE_TAGS := optional LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX) LOCAL_JNI_SHARED_LIBRARIES_ABI := None MY_LOCAL_PREBUILT_JNI_LIBS := \ MY_APP_LIB_PATH := $(TARGET_OUT_VENDOR)/bundled_uninstall_back-app/$(LOCAL_MODULE)/lib/$(LOCAL_JNI_SHARED_LIBRARIES_ABI) ifneq ($(LOCAL_JNI_SHARED_LIBRARIES_ABI), None) $(warning MY_APP_LIB_PATH=$(MY_APP_LIB_PATH)) LOCAL_POST_INSTALL_CMD := mkdir -p $(MY_APP_LIB_PATH) $(foreach lib, $(MY_LOCAL_PREBUILT_JNI_LIBS), ; cp -f $(LOCAL_PATH)/$(lib) $(MY_APP_LIB_PATH)/$(notdir $(lib))) endif include $(BUILD_PREBUILT)
正常的:
LOCAL_PATH := $(my-dir) LOCAL_PATH := $(my-dir) include $(CLEAR_VARS) LOCAL_APK_NAME := LVI8031A LOCAL_POST_PROCESS_COMMAND := $(shell mkdir -p $(TARGET_OUT_VENDOR)/bundled_uninstall_back-app/$(LOCAL_APK_NAME) && cp $(LOCAL_PATH)/$(LOCAL_APK_NAME).apk $(TARGET_OUT_VENDOR)/bundled_uninstall_back-app/$(LOCAL_APK_NAME)/)
解决
在配置中删除ndk相关的配置,或者在ndk的配置中添加
'armeabi-v7a'
,均已验证通过。
扩展
APK安装过程
APK签名校验过程