rea-reverse

安装包似乎无效怎么解决:查出自己的 APK 到底哪里坏了

基于 REA 6.4.0更新于 约 10 分钟读完
本页目录

结论先行:手机上的“应用未安装:软件包似乎无效”(英文系统是 “App not installed as package appears to be invalid”)只是一层外壳,背后是一整类安装失败。我在 Android 14 和 Android 16 模拟器上逐一复现,真正触发这句话的有四种情况:APK 没签名、只有 v1 签名、签名之后又被改过、把单个拆分包拿去单独安装。resources.arsc 被压缩、下载不完整也会装不上,但手机上的提示不一样。排查的第一步永远是用 adb install 拿到真实的 INSTALL_* 代码,然后回到构建流程里修。不要去改设备,也不要想办法关掉签名校验。

本站的 APK/AAB 分析与上架体检 能在发包前查出其中大部分问题:签名区块、ZIP 布局和 Manifest 都在浏览器里读取,文件不会上传。下面所有输出都来自我自己构建、自己签名的测试 APK。

适合谁读:通过文件安装自家构建的 Android 开发者和测试人员,比如 CI 产物、官网下载、聊天里传的安装包、企业内部商店。

读完你会知道:

  • 哪些安装失败会显示成“软件包似乎无效”,哪些不会
  • 怎么拿到真实的 INSTALL_PARSE_FAILED_* 代码
  • 分析器的 SIGN-03、SIGN-08、SIDE-08、SIDE-09 各查什么,又有哪些看不到
  • 每种原因只用官方工具链怎么修

只在你有权测试的设备上,安装和检查你自己的应用或已获授权的应用。

这句提示从哪来

系统安装器不会把平台的错误代码直接显示出来。它先把代码归到一个公开状态,再按状态挑一句提示。AOSP 的 PackageManager.installStatusToPublicStatus 把 20 个代码都归到 STATUS_FAILURE_INVALID,包括 INSTALL_PARSE_FAILED_NO_CERTIFICATES、INSTALL_PARSE_FAILED_NOT_APK、INSTALL_PARSE_FAILED_MANIFEST_MALFORMED、INSTALL_FAILED_INVALID_APK、INSTALL_FAILED_TEST_ONLY,连 INSTALL_FAILED_VERSION_DOWNGRADE 也在里面。安装器的中文字符串里,这个状态对应的就是“应用未安装:软件包似乎无效。”

我在中文系统的 Android 14 模拟器上,通过“文件”应用逐个点开测试包。没签名、只有 v1 签名、签名后被篡改这三个包,弹出的都是“应用未安装:软件包似乎无效。”,logcat 里也只有一行 InstallFailed: Installation status code: 4。状态 4 就是 STATUS_FAILURE_INVALID。具体原因既不上屏,也不进日志,只能自己去问。

有两种失败在手机上长得不一样:

  • resources.arsc 被压缩:代码是 -124,归到通用失败状态,弹窗只有一句“未安装应用。”
  • 文件被截断:根本走不到安装那一步,安装器提示“解析软件包时出现问题。”

第一步:用 adb 拿到真实代码

连上开了 USB 调试的设备或启动模拟器,用 adb 装同一个文件:

adb install app-release.apk

下面是我的测试包 dev.example.invaliddemo(minSdk 24、targetSdk 35)的结果。Android 14(API 34)和 Android 16(API 36)模拟器给出的代码完全一致,临时路径做了缩写:

$ adb install unsigned.apk
Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl…tmp/base.apk: Attempt to get length of null array]
$ adb install v1only.apk
Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Scanning Failed.: No signature found in package of version 2 or newer for package dev.example.invaliddemo]
$ adb install modified.apk
Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from …/base.apk: META-INF/RELEASE.SF indicates …/base.apk is signed using APK Signature Scheme v2, but no such signature was found. Signature stripped?]
$ adb install tampered.apk
Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from …/base.apk using APK Signature Scheme v3: SHA-256 digest of contents did not verify]
$ adb install arsc-compressed.apk
Failure [-124: Failed parse during installPackageLI: Targeting R+ (version 30 and above) requires the resources.arsc of installed APKs to be stored uncompressed and aligned on a 4-byte boundary]
$ adb install truncated.apk
Failure [INSTALL_PARSE_FAILED_NOT_APK: Failed to parse …/base.apk: Failed to load asset path …/base.apk]
$ adb install base-fr.apk
Failure [INSTALL_FAILED_INVALID_APK: Full install must include a base package]
$ adb install app.aab
adb: filename doesn't end .apk or .apex: app.aab
$ adb install testonly.apk
Failure [INSTALL_FAILED_TEST_ONLY: Failed to install test-only apk. Did you forget to add -t?]

(testonly.apk 只在 Android 16 模拟器上跑过。)看代码就知道往哪查:NO_CERTIFICATES 是签名问题,NOT_APK 是文件本身坏了,-124 是 resources.arsc,INVALID_APK 加上 “base package” 是拆分包问题。

第二步:把文件拖进分析器

打开 APK/AAB 分析器,分发场景选“Play 以外(线下分发)“或”两者都要”,再把 APK 拖进去。准备上传 Google Play 的版本,可以用 Google Play 上架合规体检,规则相同,默认选好了 Play 场景。下表是我这几个文件的结果,证据一栏和报告里打印的原文一致:

文件 规则与级别 证据
unsigned.apk SIGN-03,拒审 no v1, v2, v3 or v3.1 signature found
v1only.apk SIGN-03,拒审 v1 signature only (no APK Signing Block with v2/v3)、targetSdkVersion=35
modified.apk SIGN-03,拒审 同样是只有 v1,因为 ZIP 工具把签名区块丢掉了
arsc-compressed.apk SIGN-08,拒审 resources.arsc is compressed、targetSdkVersion=35
base-fr.apk SIDE-08,拒审 <manifest split="config.fr"> (this file is a split, not the base APK)
truncated.apk 无法生成报告 “文件看起来是 ZIP,但内部结构已损坏。”
tampered.apk 没有发现 分析器不做签名的密码学校验
app.aab 以上都不触发 按 App Bundle 读取,仅适用于 APK 的规则跳过

注意最后两行:分析器读取证书和签名方案,但还不会重新计算签名摘要。字节在签名后被改过,要靠 apksigner verify 才能发现。

原因一:没有签名,或只有 v1 签名

没签名的 APK,Android 一律拒装。从 Android 11 开始,targetSdk ≥ 30 的应用还必须带 APK 签名方案 v2 或更新版本;官方原话是,只用 v1 签名的这类应用,用户在 Android 11 设备上无法安装或更新。

SIGN-03 检查的就是这件事,触发条件是:“仅 APK。没有 v2/v3 签名:只有 targetSdk ≥ 30 时为拒审(无法安装),低于 30 为高风险;完全没有签名单独提示。“完全没签名时,结果会附一句说明:“这个 APK 完全没有签名,Android 无法安装。”

修法:用 apksigner 签名(默认开启 v2 和 v3),或者在 Android Studio 里配好 release 签名,然后复核:

$ apksigner verify -v v1only.apk
DOES NOT VERIFY
ERROR: Target SDK version 35 requires a minimum of signature scheme v2; the APK is not signed with this or a later signature scheme

签好的包会输出 Verifies 和 Verified using v2 scheme (APK Signature Scheme v2): true。只剩 v1 签名,多半是构建脚本里还留着一步老的 jarsigner。

原因二:签名之后又改了文件

apksigner 文档写得很直接:用 apksigner 签名后再修改 APK,签名就失效了。我试了两种改法:

  • 重写 ZIP:用 zip 往包里加一个文件,整个归档被重写,APK 签名区块随之丢失,只剩 META-INF/ 下的 v1 文件,而这些文件里还写着“用过 v2”,所以设备报 “Signature stripped?”。分析器把它报成 SIGN-03(只有 v1)。新加的文件没有按 4 字节对齐,SIGN-08 也给出一条建议:1 uncompressed entry not 4-byte aligned, e.g. extra.txt at 2957。明明用 v2/v3 签过的包,在报告里却只剩 v1,说明签名之后有东西重写过它。
  • 原地改字节:我把 resources.arsc 里的一个比特翻转了。布局和签名区块都没动,分析器什么也没报。设备拒装(“SHA-256 digest of contents did not verify”),apksigner verify -v 也报错:APK integrity check failed. CHUNKED_SHA256 digest mismatch.

修法是理顺顺序:zipalign 必须在签名之前做,签名必须是最后一个碰这个文件的步骤。写渠道号、删资源、加固之类的后处理,统统放在 apksigner 之前;处理完用自己的密钥重新签名,再跑一遍 apksigner verify -v。

加壳和加固是正当的保护手段,很多加固工具会重新签名输出文件。加固后的包报 NO_CERTIFICATES 时,按厂商文档里的签名步骤,用你自己的发布密钥给最终产物签名并复核即可。分析器的 PROT-01(建议级)识别到防护运行库时会说明利弊:启发式引擎可能把壳误报,带签名自校验的防护在 Play App Signing 重新签名后可能失效。涉及 Play 版本的,优先选明确支持 Play App Signing 的方案。

原因三:resources.arsc 被压缩或没对齐

Android 11 行为变更规定:targetSdk ≥ 30 的应用,如果 resources.arsc 被压缩,或者没有按 4 字节边界对齐,就无法安装。

SIGN-08 的触发条件是:“仅 APK。只有 targetSdk ≥ 30 且 resources.arsc 被压缩或未按 4 字节对齐时为拒审(无法安装)。其他未压缩条目未按 4 字节对齐为建议。”

我模拟了手工“重新打包”:解压 APK,再用 zip -9 打回去。unzip -v 一眼就能看出区别:

$ unzip -v good.apk | grep arsc
     688  Stored      688   0% 01-01-1980 00:00 c58b3cbb  resources.arsc
$ unzip -v arsc-compressed.apk | grep arsc
     688  Defl:X      189  73% 01-01-1980 00:00 c58b3cbb  resources.arsc

要当心的是,zipalign -c 对这个文件照样通过(630 resources.arsc (OK - compressed)),因为它只检查不压缩的条目。要看压缩方式那一列,或者交给分析器。

修法:别手工重新压缩 APK。用 AGP 重新构建,或者保持 resources.arsc 不压缩,先对齐再签名。zipalign 文档现在已把 -p 标为弃用,建议改用 -P 16:

zipalign -P 16 -f -v 4 unaligned.apk aligned.apk
apksigner sign --ks release.jks --out app-release.apk aligned.apk
zipalign -c -P 16 -v 4 app-release.apk

让 resources.arsc 按 4 字节对齐的是参数 4;-P 16 只负责把未压缩的 .so 按页对齐。

原因四:拿 AAB 或单个拆分包去装

.aab 是上架用的发布格式,不是安装包,adb 在发送到设备之前就拒绝了。分析器会按 App Bundle 读取它,SIGN-03、SIGN-08 这类只针对 APK 的规则直接跳过。

拆分 APK 更容易踩坑。我的 bundle 用 bundletool build-apks 默认参数生成了 base-master.apk 和 base-fr.apk。单独安装语言拆分包,得到 INSTALL_FAILED_INVALID_APK: Full install must include a base package。SIDE-08 会把它标出来,触发条件开头是:“Play 以外,仅 APK。拒审(无法单独安装):文件本身是拆分包(、isFeatureSplit),会报 INSTALL_FAILED_INVALID_APK “Full install must include a base package”;或 base APK 的 上设置了 android:isSplitRequired=“true” 或 android:requiredSplitTypes,会报 INSTALL_FAILED_MISSING_SPLIT。”

“离不开拆分包”的 base APK 就没那么一刀切了,关键看标记写在哪。我在 <application> 上加 android:isSplitRequired="true" 后单独安装,两台模拟器都返回 Success。原因在 AOSP 源码里:ApkLiteParseUtils 只从 <manifest> 元素读取 isSplitRequired 和 requiredSplitTypes,PackageInstallerSession 也只在这个 manifest 级标记存在、又没有对应拆分包时,才以 INSTALL_FAILED_MISSING_SPLIT 拒绝完整安装。在 Android 16 模拟器上,把同一个属性挪到 <manifest> 上就失败了:INSTALL_FAILED_MISSING_SPLIT: Missing split for …。android:requiredSplitTypes="language" 同样失败(aapt2 看到这个属性时会顺带在 <manifest> 上写入 isSplitRequired="true",而且 ApkLite 本身就把任何必需拆分类型视为“需要拆分包”)。所以 SIDE-08 现在把 <application> 上的标记,以及 bundletool 写入的 com.android.vending.splits.required meta-data(Play 读取,安装器不读)报为高风险而不是拒审:“可能装得上,但缺了拆分包就不完整(拆分包里的代码或资源运行时可能找不到)”。

修法:线下分发就生成一个通用 APK。bundletool 文档对 universal 模式的描述是,生成一个包含应用全部代码和资源的 APK:

java -jar bundletool.jar build-apks --bundle=app.aab --output=universal.apks \
  --mode=universal --ks=release.jks --ks-key-alias=release

解压 universal.apks,分发里面的 universal.apk,我这边安装结果为 Success。自己测试时,可以用 bundletool install-apks --apks=app.apks 装整套,或者用 adb install-multiple base-master.apk base-fr.apk 一次装上全部拆分包,两种方式我都装成功了。

原因五:文件没下载完整

文件被截断或损坏不是构建问题,但症状很像。我把一个正常 APK 截成 2000 字节,adb 报 INSTALL_PARSE_FAILED_NOT_APK,手机提示“解析软件包时出现问题”,分析器直接停在“文件看起来是 ZIP,但内部结构已损坏。“建议每次发版都公布 SHA-256:分析器能读的文件都会显示整包 SHA-256,命令行用 shasum -a 256 app.apk 也行。测试人员先对一下哈希,再报 bug。

SIDE-09(高风险,两种场景都适用)管的是相近的“ZIP 结构不规范”:重复条目、中央目录与本地头不一致、不安全路径、加密标志位或未知压缩方式为高风险;归档末尾有多余数据为建议。这一项我没有专门造样本。官方工具链不会产出这种结构,出现了就说明有非标准工具动过这个文件。

另外两种情况

  • 测试包:Android Studio 点“运行”生成的 APK 带有 android:testOnly="true"。adb install 要加 -t 才能装,而安装器会把 INSTALL_FAILED_TEST_ONLY 也归为“软件包无效”。分析器的 PLAY-06 在选“Google Play”或“两者都要”时会报出 testOnly。发给别人的包,请从 release 变体构建。
  • 版本降级:在 AOSP 里同样归到这个状态。代码是 INSTALL_FAILED_VERSION_DOWNGRADE 时,提高 versionCode 即可,实测过程见安装包与现有包冲突一文。

REA 6.4.0 能补什么

REA(morluto 的开源项目)没有打包或政策方面的检查。设备侧,install-adb-package 的契约写明会保留设备返回的 Success/Failure 结论和原因;但我用 adb 37.0.1 实测安装 unsigned.apk,只拿到 unreadable_output(“Analysis returned an unreadable result”)。安装成功时,它会把 APK 的 sha256 和字节数记为 Evidence。所以失败代码还是用原生 adb install 看。rea inspect-artifact 说明它的 ZIP 清单“只列中央目录里的条目”,APK 签名区块不在范围内,遇到截断的文件则报 artifact_operation_failed。rea inspect-android-package 需要自备 JADX MCP 的 JAR,结果里写明 signature_verification: "not_performed"。

常见问题

有没有设置能让手机硬装上? 没有。签名、解析和拆分包检查都是平台本身的机制,唯一的办法是交付一个构建正确、签名正确的 APK。

同一个 APK,这台手机能装,那台装不上? 两台都用 adb 看代码。只有 v1 签名、resources.arsc 被压缩这两项限制是 Android 11 才加上的,所以 Android 10 的手机可能接受 Android 11 以上拒绝的包。怀疑两边文件不一样,就对比 SHA-256。

换一把新密钥重新签名能解决吗? “软件包无效”能解决,但如果设备上装着用原密钥签名的旧版本,接下来就会变成“与现有包冲突”。请用原来的密钥签名。

分析器会上传我的 APK 吗? 不会。分析在浏览器的 Web Worker 里完成。可选的 VirusTotal 链接只带哈希值,而且只有你点了才会打开。

下一步