上周有个做测试的朋友找我说他那台旧机器里还留着一个两年前的版本新版本把某个功能砍掉了他想把旧包装到新手机上去做对比。他打开命令行敲了一句adb pull拉回来一个二十八K的 base.apk装上去直接闪退。他第一反应是包坏了其实是只拿到了个外壳。这件事几乎每个刚碰这块的人都会撞一次。搜索框里android 生成安装包从 Android 手机中取出已安装的 app 包导出 apk这几个词经常被混在一起用但它们指向的其实是两条完全不同的技术路线一条是你手里有源码要把工程编译成一个能装进手机的包另一条是包已经在别人手机上跑着了你要把它从系统目录里捞出来。前者是构建和签名问题后者是文件系统权限和安装机制问题混着学只会越绕越晕。这篇就把这两条路拆开讲清楚从 adb 环境怎么配、包名怎么定、路径怎么读到分卷时代一个 app 散成五六个文件怎么收再到反过来从源码出包时签名和对齐的坑在哪。文中会给出可以直接抄的命令和脚本也会把我在实测里踩过的几类报错按排查顺序列出来。不管你是刚学 adb 的新手还是天天和构建脚本打交道的老人应该都能挑到能用的部分。1. 标题里其实藏着两条完全不同的技术路线1.1 生成和取出是两套知识体系很多人以为这两个动作是一回事的正反面实际上它们的知识依赖几乎没有交集。生成安装包你面对的是 Gradle 构建流程、资源压缩、代码混淆、签名方案版本、zipalign 对齐这几样东西出问题的地方通常在构建日志里和手机没太大关系而导出已安装的包你面对的是包管理器PackageManager记录的安装路径、Linux 文件权限、SELinux 上下文、以及 Android 从 9.0 之后引入的分卷安装机制出问题的地方全在设备侧。我把这两条路线的差异整理成一张表你可以对照自己当下的需求先归个类维度从源码生成安装包从设备导出已安装包输入工程源码、依赖、签名文件一台已装好目标应用的手机核心工具Gradle、apksigner、zipalignadb、pm、文件系统访问权限产物形态单个 apk 或 aabbase.apk 加若干 split 分卷主要失败点依赖冲突、混淆规则、签名不一致权限不足、只拉到 base、分卷缺失典型使用场景发版、内测分发、上架版本留存、兼容性对照、体积分析分清楚之后你会发现网上大量导出 apk 失败的帖子底层原因都是拿构建侧的经验来套设备侧的问题比如有人一直在纠结我的 apk 怎么没签名可他拉出来的那个包本来就是签好名的只是缺了分卷。1.2 已经装在手机里的 app为什么往往不是一个 apk这是新手最容易误判的一点。在 Android 早期一个应用就是一个 apk 文件扔在/data/app/包名-1.apk这样的路径下adb pull一把就完事。但从应用开始用 App Bundle 格式分发之后情况变了一个应用在安装时会被拆成基础包 若干配置分卷基础包里放代码和主资源配置分卷里放特定 CPU 架构的原生库、特定屏幕密度的图片资源、特定语言的字符串。拆分的逻辑其实很朴素就是为了减少下载量。一台 1080p 分辨率、arm64 架构、中文环境的手机没必要下载 x86 的原生库和 4K 的图片资源。系统在安装时会按设备特征挑出匹配的那几个分卷把它们放在同一个目录下一起注册给包管理器使用。所以你在这样的设备上执行导出如果只用最朴素的pm path取第一条结果拿到的就是那份不含原生库的基础包。装到别的机器上只要那个应用启动时需要加载 native 库就会在System.loadLibrary那一步崩掉。这就是我朋友那个二十八K闪退包的由来——那台设备上的应用恰好把 dex 也放在了分卷里base 几乎只剩个清单文件。1.3 哪些场景适合自己动手哪些不适合技术本身是中性的但有件事得先说清楚只对自己有权处理的包动手。自己开发的、公司内部测试分发的、开源渠道发布的、自己购买并需要在备用机上留存的这些都没问题。至于把别人家的付费应用导出来再传播出去那属于另一码事不在技术讨论范围内。从实用角度看导出已安装包这件事真正的价值集中在几个地方一是版本留存某些工具类应用新版本改得面目全非旧版本反而顺手二是兼容性对照同一份代码在高低版本系统上的表现差异用固定的包去复现最干净三是体积分析想知道某个应用为什么占那么大空间把它拆开看看资源分布比看统计报表直观得多。这几种需求都很实在也都能用后面几节的方法解决。2. 让 adb 认出你的手机环境准备里最容易翻车的三处2.1 电脑侧adb 装在哪、版本要多新adb 是整套流程的地基但地基这东西平时没人注意塌了才知道疼。Windows 上最省事的做法是直接把平台工具包解压到某个固定目录比如D:\tools\platform-tools然后把这个目录加进系统环境变量 Path这样在任何终端里都能直接敲adb。别去装那种带一堆附加组件的集成套件版本容易乱出问题时你连自己用的是哪份 adb 都搞不清。macOS 和 Linux 上如果用包管理器装要注意版本。系统仓库里的 adb 经常落后好几个大版本遇到新设备时会报adb server version doesnt match this client或者干脆识别不到设备。我的建议是统一用官方发布的平台工具包解压后把路径加到 shell 配置里# 以 macOS / Linux 为例把平台工具目录放到 PATH 最前面 export PATH$HOME/tools/platform-tools:$PATH # 验证版本顺手看看有没有旧进程占着端口 adb version adb kill-server adb start-server版本这块有个经验值设备系统越新adb 越要跟着更新。Android 11 之后引入了无线调试配对机制老版本 adb 完全不认Android 13 之后的一些权限模型调整也需要较新的客户端才能正确读到包信息。判断标准很简单adb version输出的版本号如果比设备系统发布时间早一年以上就换掉。还有一个特别容易被忽略的点同时装了多个 adb 会互相抢 5037 端口。典型症状是adb devices一会儿能看到设备一会儿看不到或者报cannot connect to daemon。排查方法是adb kill-server之后先确认没有残留进程再启动。2.2 手机侧开发者选项、USB 调试、安装权限设备侧要打开的东西有三层缺一层都会卡住而且报错信息往往指向别处特别迷惑人。第一层是开发者选项。设置里连点七次版本号各家定制系统入口不完全一样有的在关于手机里有的在我的设备里解锁出开发者选项菜单。第二层是 USB 调试。打开之后插线手机会弹一个允许 USB 调试吗的对话框勾选一律允许再确定。这一步有个坑如果弹窗没出现先别急着换线去开发者选项里找撤销 USB 调试授权点一下清空旧授权拔线重插弹窗基本就出来了。这是因为之前授权过的电脑指纹被记住了但密钥对不上。第三层很多人不知道要开——部分定制系统有独立的USB 安装或通过 USB 安装应用开关默认关闭。这个开关不影响adb pull但会影响后面用adb install把包装回去症状是安装时静默失败或者报权限不足。另外提一句无线调试。Android 11 及以上可以在同一局域网里配对连接省掉线材折腾# 先在手机无线调试页面拿到配对码和端口执行配对 adb pair 192.168.1.20:37105 # 配对成功后连接调试端口注意配对端口和连接端口不是同一个 adb connect 192.168.1.20:40001 adb devices无线调试在导出大包时比 USB 2.0 线快得多实测拉一个 200MB 的包USB 2.0 要将近一分钟同一局域网下能压到十几秒。2.3 用 pm list packages 把目标包名钉死包名是所有后续操作的主键搞错了后面全白费。最直接的办法是列全部已安装包再过滤# 只列第三方应用输出更干净 adb shell pm list packages -3 # 带上 apk 路径一眼能看到装在哪 adb shell pm list packages -3 -f # 按关键字过滤注意包名一般不区分大小写 adb shell pm list packages | grep -i demo三个参数要记牢-3只看第三方应用-s只看系统应用-f同时输出路径。日常导出场景基本都用-3 -f因为系统应用大多在/system分区能不能拉出来取决于分区挂载方式情况复杂得多。如果连关键字都想不起来还有个笨办法很好用先把目标应用切到前台然后执行adb shell dumpsys activity activities | grep -i mResumedActivity输出里那串包名/Activity名就是当前前台应用。这招在排查这个界面到底是哪个应用弹出来的时特别有效我用了很多年。3. pm path 加 adb pull导出单包最稳的一条链路3.1 pm path 返回的那串路径在说什么拿到包名之后第一件事是问系统这个包的文件在哪命令是pm pathadb shell pm path com.example.demo在 Android 10 之后的设备上输出大概长这样package:/data/app/~~kR2vB9xQzLmN/com.example.demo-9XyT4pQr/base.apk这串东西值得逐段拆开看。package:是固定前缀脚本处理时要剥掉。/data/app/说明这是用户安装的应用不是系统预装。中间那层~~kR2vB9xQzLmN是 Android 9 之后引入的随机化目录名目的是防止有人硬编码路径去猜测应用位置。再下一层com.example.demo-9XyT4pQr是应用自己的安装目录同样带随机后缀。最后才是base.apk。提示那两段随机串每次重装都会变所以任何脚本里都不要写死路径必须每次现查。这一点是很多人脚本跑一次就失效的根本原因。另外有个细节如果应用的安装目录里有多份分卷pm path会输出多行每行都以package:开头。第 3.1 节这里先按下不表第 4 节专门讲。3.2 adb pull 的实操细节与权限边界知道路径之后直接拉adb pull /data/app/~~kR2vB9xQzLmN/com.example.demo-9XyT4pQr/base.apk ./demo-base.apk大部分情况下这一句就能成功原因是apk 文件本身的权限是0644也就是所有用户可读上层目录是0755允许穿过。adb pull底层是以 shell 用户身份执行的所以能读到。但也有失败的时候典型报错是adb: error: failed to stat remote object: Permission denied。这通常出现在三种情况一是某些定制系统收紧了/data/app的目录权限二是目标应用被放在了工作资料或多用户空间里需要指定用户三是 SELinux 策略更严格shell 域被挡住了。第二种情况的解决方式是加--user参数# 指定用户 0也就是主用户 adb shell pm path --user 0 com.example.demo # 如果应用装在多开空间或工作资料里用户 ID 可能是 10、11 等 adb shell pm list users第三种情况可以绕一下先复制到可读目录再拉adb shell cp /data/app/~~xxx/com.example.demo-yyy/base.apk /sdcard/Download/demo-base.apk adb pull /sdcard/Download/demo-base.apk ./demo-base.apk这个中转思路很实用。shell 用户对/sdcard有完整写权限而读原文件的权限和直接 pull 时是一样的所以能 pull 的时候基本不需要中转但遇到 SELinux 上下文导致的诡异失败时换个路径往往就通了。3.3 系统应用和第三方应用路径差别带来的麻烦第三方应用在/data/app/下系统预装应用在/system/app/、/system/priv-app/、/product/app/等分区目录里pm path会如实告诉你。这两类包在导出时待遇差别很大。系统应用的 apk 通常直接躺在分区目录里文件名不叫 base.apk而是应用自己的名字比如/system/priv-app/SomeService/SomeService.apk。这类文件权限一般是0644理论上能拉。但如果在系统升级过程中被更新过实际生效的版本可能已经搬到/data/app/下了pm path会返回/data/app/那条路径而/system里那份成了残留。这时候你拉/system那份装出来的是出厂旧版本和手机上跑的根本不是同一个东西。判断方法很简单看路径前缀就行。前缀是/data/app/的就是当前生效版本前缀是/system、/product、/vendor的要看包管理器有没有同时给出/data/app的覆盖路径。稳妥做法是pm path输出多行时优先取/data/app/那几行。还有一类容易忽略的有些系统应用是拆分安装到了/system和/data两处dex 和资源各放一边这种拉出来必然不完整。遇到这种就别硬来了换台设备或者换个场景更省时间。4. 分卷安装时代一次安装散成五六个文件怎么收4.1 好好的一个 app为什么会被拆成好几块承接前面说的分卷不是多此一举而是分发效率的必然结果。一个典型的应用打成分发格式后在手机上落地的文件可能包括base.apk主包含清单、dex 代码、基础资源split_config.arm64_v8a.apk64 位 arm 架构的原生库split_config.armeabi_v7a.apk32 位 arm 架构的原生库split_config.xxhdpi.apk高密度图片资源split_config.zh.apk中文字符串资源一台具体设备只会拿到其中匹配的几份。这也是为什么在 A 手机上导出的包装到 B 手机上可能闪退——A 是 arm64 设备只带了 arm64 分卷B 是老设备需要 32 位库而那份分卷压根没被拉出来。理解这一点之后导出流程的思路就变了不再是拉一个文件而是把这个包名对应的所有文件全拉一遍。4.2 一次列全、批量拉取的脚本写法先看pm path的完整输出长什么样$ adb shell pm path com.example.demo package:/data/app/~~kR2vB9xQzLmN/com.example.demo-9XyT4pQr/base.apk package:/data/app/~~kR2vB9xQzLmN/com.example.demo-9XyT4pQr/split_config.arm64_v8a.apk package:/data/app/~~kR2vB9xQzLmN/com.example.demo-9XyT4pQr/split_config.xxhdpi.apk package:/data/app/~~kR2vB9xQzLmN/com.example.demo-9XyT4pQr/split_config.zh.apk四条输出就是四份文件。写个循环批量拉注意两个细节一是剥掉package:前缀二是去掉 Windows 换行符带进来的\r用 Git Bash 或 WSL 拉包时这个坑非常经典路径末尾多一个不可见字符adb pull会报文件不存在#!/usr/bin/env bash set -euo pipefail PKG${1:-com.example.demo} OUT./apk_${PKG} mkdir -p $OUT adb shell pm path $PKG \ | sed s/^package:// \ | tr -d \r \ | while read -r remote; do echo pulling: $remote adb pull $remote $OUT/ done echo ---- 结果 ---- ls -lh $OUT把这脚本存成pull_apk.shchmod x之后直接./pull_apk.sh 包名就能用。实测下来这个脚本能覆盖九成以上的导出需求。注意Windows 原生命令行cmd下用不了 bash 脚本装个 Git for Windows 用 Git Bash 跑最省事。PowerShell 也能写但sed那些处理要换成-replace反而更啰嗦。如果目标应用装在多用户环境里比如双开、工作资料循环前先加一句--useradb shell pm path --user 0 $PKG4.3 把分卷原样装回去install-multiple 的用法导出的目的是装回去。单个 apk 用adb install就行分卷必须用adb install-multipleadb install-multiple -r ./apk_com.example.demo/*.apk-r表示覆盖安装并保留数据。如果目标机上已经装了这个应用的另一个签名版本会报INSTALL_FAILED_UPDATE_INCOMPATIBLE这时得先卸干净adb uninstall com.example.demo adb install-multiple -r ./apk_com.example.demo/*.apk有个细节值得说install-multiple不要求你按 base 在前的顺序传参包管理器会自己识别哪个是主包但所有分卷必须是同一次构建的产物。混用不同版本的 base 和 split会报INSTALL_FAILED_INVALID_APK或者装完启动就崩。另外用install-multiple装分卷时系统会把它们当作一个整体注册。如果你想确认装上去的分卷齐不齐装完再跑一次pm path把输出行数和导出时比一下行数一致就说明齐了。这个自查动作很便宜能省掉后面一堆莫名其妙的崩溃排查。4.4 常见分卷文件命名对照不同构建工具产出的分卷命名不完全一样但规律很稳定认得几个关键词就够分卷后缀含义缺失后的典型症状split_config.arm64_v8a64 位 ARM 原生库启动即崩日志出现UnsatisfiedLinkErrorsplit_config.armeabi_v7a32 位 ARM 原生库老设备上加载 so 失败split_config.x86_64模拟器架构原生库装到模拟器上无法运行业务逻辑split_config.xxhdpi/xxxhdpi高密度图片资源图标、背景图缺失或模糊split_config.zh/en语言字符串界面显示英文或键名split_config.tcf_arm64_v8a特定机型特性包某些定制功能不可用看到tcf_前缀别慌那是针对特定设备厂商的优化分卷一般不影响主流程缺失时只会损失一些定制特性。5. 不想开电脑手机端导出的几条可行路径5.1 没有 root 时你到底能碰到什么很多人想直接在手机上把包导出来省得插线。现实是无 root 的普通应用根本没有权限读/data/app下的其他应用文件这是沙箱机制的基本设定绕不过去。你在文件管理器里能看到/data目录点进去就是空的或者被拒绝这不是软件不够强是内核层面挡着。无 root 情况下真正可行的只有两条路。第一条是用系统自带的备份或分享功能部分定制系统在应用管理页面提供了导出安装包或分享应用的入口点一下就能生成一个 apk 到下载目录。这条路的问题在于大多数实现只导出基础包分卷不包含在内导出的包在别的机器上可能装得上但跑不起来。第二条是用调试通道也就是在手机上跑一个能执行 shell 命令的终端应用通过adb的本地调试或者无线调试让手机自己对自己执行pm path原理和电脑侧完全一样。5.2 有 root 之后直接进 /data/app拿到 root 之后操作就自由了。最直接的方式是给文件管理器授予 root 权限然后进/data/app/找到对应包名的目录把里面所有 apk 复制到/sdcard/Download/。用命令行更精准# 需要有 root 权限的 shell su -c cp /data/app/~~xxx/com.example.demo-yyy/*.apk /sdcard/Download/demo/注意通配符*.apk这一句就能把 base 和所有分卷一起拷出来比手动点选靠谱得多。拷完之后记得用文件管理器确认一下文件数量和pm path输出行数对得上才算完整。root 方案的代价也很明确设备需要解锁引导程序这个过程会清空数据而且不同厂商的解锁政策和保修条款差异很大。如果只是为了导一次包用 adb 更划算没必要为此折腾设备。5.3 第三方导出工具的通用原理与风险判断市面上有一些专门做安装包导出的工具它们的实现路径基本逃不出三种一是走 root直接拷文件二是走系统备份接口把应用的安装包备份出来三是自己充当一个包管理器客户端调用系统提供的安装会话接口把分卷读出来。判断一个工具靠不靠谱看两点就够。第一看它请求什么权限如果一个导出安装包的工具要求读取联系人、短信、位置那它要的东西和它宣称的功能完全不匹配直接放弃。第二看它是否明确说明支持分卷导出只字不提分卷的工具导出来的包在多架构设备上大概率会出问题。还有个思路可以完全避开第三方工具如果你只是想留个版本做对比与其折腾导出不如直接在旧设备上把应用冻结掉不让它自动更新。冻结之后/data/app/里的文件就保持不变需要的时候再拉出来比存档更省事。这个思路我用得越来越多尤其是对那些更新频繁的工具类应用。6. 换到构建侧从源码生成安装包真正要盯的几件事6.1 debug 包和 release 包差的不只是一个后缀很多人第一次从源码出包直接跑./gradlew assembleDebug拿到 apk 就发出去了然后在别的机器上装不上或者被系统拦截。原因在于 debug 包用的是自动生成的调试签名这个签名文件放在用户目录下的.android/debug.keystore里每台开发机不一样而且默认带android:debuggabletrue。调试签名的包有几个硬伤不同机器上构建的 debug 包签名不同互相覆盖安装会报签名冲突带 debuggable 标记的应用在某些系统上会被安全策略拦截不能用于正式分发。所以只要包要离开你自己这台机器就必须走 release 构建。# 生成 release 安装包 ./gradlew assembleRelease # 需要分发给应用商店时用的分发格式 ./gradlew bundleRelease两者产物位置不同assembleRelease输出在app/build/outputs/apk/release/app-release.apkbundleRelease输出在app/build/outputs/bundle/release/app-release.aab。aab 不能直接装到手机上它是给分发渠道用的渠道侧会按设备特征生成分卷。这一点和前面讲的分卷机制是同一套逻辑的两端——你导出的分卷包源头很可能就是一个 aab。6.2 签名keystore、v1/v2/v3 与对齐顺序release 构建必须配置签名否则产物是 unsigned 的装不上。签名配置有两种写法一种写在build.gradle里一种走命令行手动签名。工程里配置更省心android { signingConfigs { release { storeFile file(../keystore/release.jks) storePassword System.getenv(KS_PASS) keyAlias demo keyPassword System.getenv(KEY_PASS) } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro } } }密码从环境变量读别写死在文件里这是最基本的纪律。keystore 文件本身要做备份丢了就再也签不出能覆盖升级的同名包这是我在实际项目里见过最惨的一类事故签名的 keystore 随着某台旧电脑一起报废之后所有更新都得让用户卸载重装。手动签名的话顺序不能错先用zipalign对齐再用apksigner签名。反过来做会把 v2 签名破坏掉装的时候报INSTALL_PARSE_FAILED_NO_CERTIFICATES或者更隐蔽地装上但校验失败。# 1. 对齐-p 表示按页对齐适用于 Android 6.0 及以上 zipalign -p -f -v 4 app-release-unsigned.apk app-aligned.apk # 2. 签名同时启用 v1/v2/v3 apksigner sign \ --ks release.jks \ --ks-key-alias demo \ --v1-signing-enabled true \ --v2-signing-enabled true \ --v3-signing-enabled true \ --out app-release-signed.apk app-aligned.apkv1 是 jar 签名兼容 Android 7.0 以下的设备v2 是整个文件的签名校验更快v3 增加了密钥轮换能力。三个都开着最稳代价是包体积增加几十 KB可以忽略。6.3 产物在哪怎么验证签对了出包之后别急着发先自己验一遍apksigner verify --verbose --print-certs app-release-signed.apk输出里要确认三件事Verifies那一行是 truev1、v2、v3 的方案显示为 verified证书指纹和你预期的一致。指纹这一项经常被忽略但它能挡住一类很隐蔽的事故——构建机上有多个 keystore脚本里的路径写错了一个字母签出来的包能装但没法覆盖升级等到用户反馈更新失败时才发现。再补一个自查手段把签好的包装到一台干净的设备上跑一遍主流程。这一步听着多余但我见过太多次签名验过了、装也能装、一启动就崩的情况原因可能是混淆规则把反射用到的类裁掉了。release 构建开了minifyEnabled之后任何通过反射、序列化、JNI 回调访问的类都可能被误删必须在proguard-rules.pro里显式保留。6.4 构建失败最常见的三个源头第一个是依赖版本冲突。症状是编译期报Duplicate class或者运行期报NoSuchMethodError。排查用./gradlew app:dependencies看依赖树找到同一个库的两个版本用resolutionStrategy强制统一。第二个是资源压缩和代码混淆打架。开了shrinkResources之后只在代码里通过字符串拼接引用的资源会被判定为未使用而删掉症状是运行时找不到资源 ID。解决办法是在res/raw/keep.xml里声明保留规则。第三个是 JDK 版本不匹配。新版本构建工具要求 JDK 17机器上装的还是 JDK 11报错信息往往指向一个和 JDK 毫无关系的类特别容易带偏方向。跑构建之前先看一眼./gradlew -version把 JDK 版本确认掉能省掉大量无效排查。7. 实测撞到的几类报错与排查顺序7.1 adb devices 显示 unauthorized 或 offlineunauthorized表示设备连上了但没授权。按这个顺序排查先看手机屏幕上有没有弹授权框没有就撤销授权重插再看是不是插在了充电专用口或者劣质扩展坞上换主机后面板的 USB 口试最后看电脑上是不是同时跑了两个 adb。offline通常是连接状态异常。adb kill-server adb start-server能解决大半剩下的是线材或者端口问题。如果一台设备反复在device和offline之间跳八成是线的问题别浪费时间在软件上。还有一种特别隐蔽的情况设备显示正常adb shell也能进但pm命令执行超时。这通常是设备端包管理器正在做后台扫描比如刚开机或者刚装完大批应用等一会儿再试就行。7.2 拉下来的包装不上解析失败与签名冲突INSTALL_PARSE_FAILED_NO_CERTIFICATES意思是包没签名或者签名被破坏。从设备导出的包本来应该是签好的出现这个报错通常说明你拉的过程中文件被截断了。检查文件大小和adb shell ls -l里的原始大小对一下。INSTALL_FAILED_UPDATE_INCOMPATIBLE是签名不一致。设备上已装的版本和你现在推的版本签名不同只能先卸载。注意卸载会清数据如果应用里有重要数据先备份。INSTALL_FAILED_MISSING_SPLIT是分卷缺失这个报错信息很直白回头把pm path的每一行都拉全就行。7.3 INSTALL_FAILED 系列错误速查错误码含义处理方式INSTALL_FAILED_ALREADY_EXISTS已存在同包名应用加-r覆盖或先卸载INSTALL_FAILED_VERSION_DOWNGRADE目标版本低于已装版本加-d允许降级或先卸载INSTALL_FAILED_TEST_ONLY包带测试标记加-t允许测试包INSTALL_FAILED_INSUFFICIENT_STORAGE存储不足清理空间注意分卷安装需要额外临时空间INSTALL_FAILED_INVALID_APK包结构损坏或分卷不匹配重新导出确认分卷同源INSTALL_FAILED_MISSING_SPLIT分卷缺失用install-multiple补齐所有分卷这张表建议存下来遇到报错先对一眼比在网上翻帖子快得多。7.4 拉回来的包体积明显偏小这是最值得警惕的一类现象。如果拉出来的 base.apk 只有几十 KB 到几百 KB而应用本身在设置里显示占了几百 MB那基本可以确定分卷没拉全或者这个应用的代码和资源大量放在分卷里。判断方法很直接把pm path的所有输出行数记下来和你实际拉到的文件数比对。行数是 4、文件数是 1问题一目了然。另一种情况是文件数对得上但总大小还是偏小那要看应用的数据目录——/data/data/包名/下的内容不属于安装包用户数据、缓存、下载的资源都在那里不会被打进 apk这部分拉不出来是正常的。我在实际操作中的体会是导包这件事真正的难点从来不在命令本身pm path加adb pull就两行谁都会敲。难的是知道自己拉到的到底是什么是一个完整可迁移的安装单元还是一个只能在原设备上跑的空壳。养成两个习惯能省掉九成的返工——一是每次导出后数一遍文件、比一遍大小二是装回新设备后立刻跑一遍主流程而不是只看图标能不能点开。这两个动作各花不了一分钟但能挡住绝大多数看起来成功了其实没成功的情况。