Android 系统 APK 分区存放剖析-你的apk应该放在哪里?

一、概述

在 Android 系统中,APK 的存放路径远不止 /system/app 这一个。从 Android 早期到现在,随着系统架构的演进(尤其是 Project Treble 之后),APK 的存放路径被划分成了多个不同的分区目录。不同分区的 APK 在卸载行为权限获取机制签名要求等方面都有本质区别。

本文基于 AOSP 15 源码设备的实际编译产物,对 Android 系统中所有 APK 存放路径做一个完整的梳理和对比。


二、所有分区路径总览

以下是从你的 AOSP 15 编译产物中列出的所有 APK 存放分区及其实际内容(以 target/product/grus 为例):

三、各分区详解

3.1 /data/app ------ 第三方应用

这是最普遍的第三方应用路径。通过 Google Play 或其他应用商店安装、或者用户手动 adb install 的 APK,都存放在这里。

  • 可卸载:用户可以随时卸载
  • 签名:无特殊要求,开发者自签名即可
  • 权限 :只能使用 normaldangerous 保护级别的权限
  • 实际路径 :每个 app 一个子目录,如 /data/app/com.example.app-xxxxxxxx/
  • 编译产物中不存在:因为它是运行时动态安装的,编译产物不会预置

3.2 /system/app ------ 系统内置应用

这是 Android 内置的系统级应用路径,与 /data/app 不同,这里的应用不能被普通用户卸载

  • 可卸载:普通用户不可卸载(需要 root 或系统级操作)

  • 签名无强制要求,可以是 platform 签名、shared 签名、media 签名、甚至是第三方自定义签名

  • 权限 :可以声明 normaldangerous 级别权限,但对于 signature|privileged 保护级别的权限默认无法获得(需满足额外条件,见后文)

  • 编译产物举例

    system/app/BasicDreams/ # 屏保
    system/app/BluetoothMidiService/ # 蓝牙MIDI服务
    system/app/CaptivePortalLogin/ # WiFi 登录门户
    system/app/CertInstaller/ # 证书安装器
    system/app/KeyChain/ # 密钥链
    system/app/PrintSpooler/ # 打印服务
    system/app/Stk/ # SIM工具包
    system/app/EasterEgg/ # Android彩蛋

  • Build 配置示例(Soong / Android.bp)

bp 复制代码
android_app {
    name: "BasicDreams",
    platform_apis: true,
    certificate: "platform",  // 签名自选
    // 没设 privileged: true,所以进 system/app
}

3.3 /system/priv-app ------ 系统特权应用

priv-app 是 Android 4.4(KitKat)引入的概念,在后续版本中不断增强。它与 system/app 最核心的区别在于:

priv-app 中的 APK 有"资格"申请 signature|privileged 保护级别的系统权限,但前提是必须在对应的 privapp-permissions.xml 文件中显式声明。

  • 可卸载:普通用户不可卸载

  • 签名:同 system/app,无强制要求

  • 权限门槛 :放置于 priv-app 只是获得了一张"门票",真正获得权限还需要第二步------白名单声明(见第四章)

  • 编译产物举例

    system/priv-app/CalendarProvider/ # 日历提供者
    system/priv-app/ContactsProvider/ # 联系人提供者
    system/priv-app/CredentialManager/ # 凭证管理
    system/priv-app/DocumentsUI/ # 文件选择器
    system/priv-app/DownloadProvider/ # 下载管理器
    system/priv-app/ExternalStorageProvider/ # 外部存储提供者
    system/priv-app/FusedLocation/ # 融合定位
    system/priv-app/IntentResolver/ # Intent解析器

  • Build 配置示例(Soong)

bp 复制代码
android_app {
    name: "CalendarProvider",
    platform_apis: true,
    certificate: "platform",
    privileged: true,   // <-- 这行决定进 system/priv-app
}
makefile 复制代码
LOCAL_PRIVILEGED_MODULE := true

3.4 /vendor/app/vendor/priv-app ------ 硬件厂商分区(Project Treble)

Android 8.0 引入 Project Treble,将**硬件相关代码(HAL 实现、SoC 驱动配套 app)**从 system 分区中分离出来,形成 vendor 分区。

  • 目的:让 system 分区可以独立升级(Generic System Image, GSI),不受硬件厂商定制影响

  • 存放内容:与硬件/SoC 紧耦合的 APK,如运营商网络相关(CneApp、IWlanService)、Qualcomm 配套服务等

  • 可卸载:不可卸载

  • 编译产物举例

    vendor/app/CneApp/ # 连接引擎(Qualcomm)
    vendor/app/IWlanService/ # IWLAN服务
    vendor/app/TimeService/ # 时间服务
    vendor/app/CACertService/ # CA证书服务

  • Build 配置示例(Soong)

bp 复制代码
android_app {
    name: "CneApp",
    proprietary: true,      // vendor 分区
    certificate: "platform",
    ...
}
  • 传统 Makefile 配置
makefile 复制代码
LOCAL_VENDOR_MODULE := true

3.5 /product/app/product/priv-app ------ 产品分区(Android 9+)

Android 9 引入 Product 分区,用于存放OEM 厂商定制的产品级应用。这些应用不是 AOSP 原生的,也不是硬件绑定的,而属于 OEM 的差异化定制(相机 App、时钟、短信等)。

  • 目的:更精细的模块化,让 OEM 的定制内容也可以单独构建和升级

  • 编译产物举例

    product/app/Aperture/ # 相机(LineageOS)
    product/app/DeskClock/ # 时钟
    product/app/Gallery2/ # 图库
    product/app/LatinIME/ # 输入法
    product/app/Jelly/ # 浏览器
    product/app/messaging/ # 短信
    product/app/Recorder/ # 录音机
    product/priv-app/Contacts/ # 联系人
    product/priv-app/Dialer/ # 拨号器
    product/priv-app/SettingsIntelligence/ # 设置建议

  • Build 配置示例(Soong)

bp 复制代码
android_app {
    name: "Dialer",
    product_specific: true,
    privileged: true,
    certificate: "platform",
    ...
}

3.6 /system_ext/app/system_ext/priv-app ------ 系统扩展分区(Android 10/11+)

Android 10/11 引入 SystemExt 分区。这是为了解决一个尴尬的问题:很多 AOSP 核心应用(如 Settings、SystemUI、Launcher3、Telecom)既不能放在 product 分区(因为它们是 AOSP 原生组件,不是 OEM 定制),又不宜留在 system 分区(为了更好的模块化)。

  • 定位:存放"扩展 AOSP 功能"的组件,介于 system 和 product 之间

  • 编译产物举例(在 grus 上这是最丰富的 priv-app 目录)

    system_ext/app/AccessibilityMenu/ # 无障碍菜单
    system_ext/app/IFAAService/ # IFAA生物识别服务
    system_ext/app/QtiTelephonyService/ # QTI电话服务
    system_ext/app/WAPPushManager/ # WAP推送管理

    system_ext/priv-app/Settings/ # <-- 设置!已从 system/priv-app 迁移
    system_ext/priv-app/SystemUI/ # <-- 系统UI!
    system_ext/priv-app/TrebuchetQuickStep/ # Launcher
    system_ext/priv-app/ThemePicker/ # 主题选择器
    system_ext/priv-app/CarrierConfig/ # 运营商配置
    system_ext/priv-app/Seedvault/ # 备份
    system_ext/priv-app/Updater/ # 系统更新
    system_ext/priv-app/AudioFX/ # 音效
    system_ext/priv-app/StorageManager/ # 存储管理

  • Build 配置示例(Settings APK)

bp 复制代码
android_app {
    name: "Settings",
    platform_apis: true,
    certificate: "platform",
    system_ext_specific: true,   // <-- 指定进 system_ext
    privileged: true,            // <-- 指定进 priv-app 子目录
    required: [
        "privapp_whitelist_com.android.settings",
        "settings-platform-compat-config",
    ],
    ...
}

关键代码位于 packages/apps/Settings/Android.bp:158-159

复制代码
system_ext_specific: true,
privileged: true,

3.7 /odm/app/odm/priv-app ------ ODM 分区

ODM(Original Design Manufacturer)分区是为原始设计制造商准备的。ODM 通常是设计并代工手机的工厂(如闻泰、华勤),它们在 OEM 品牌(如小米)的基础上做二次定制。

  • 使用场景:ODM 厂商需要加入自己的特定应用,但又不想和 OEM(product)或 SoC(vendor)混在一起

确认你的apk安装在哪

在开发过程中有时候需要知道某个app具体apk放在在哪个分区,那么这个时候只需要知道packageName就可以了。

相关命令:

先确认报名,这里先打开要确认app的Activity

然后使用

adb shell am stack list命令

bash 复制代码
 adb shell am stack list
 
RootTask id=1 bounds=[0,0][1080,2340] displayId=0 userId=0
 configuration={1.0 ?mcc0mnc [en_US] ldltr sw393dp w393dp h851dp 440dpi nrml long hdr widecg port night finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2340) mAppBounds=Rect(0, 0 - 1080, 2340) mMaxBounds=Rect(0, 0 - 1080, 2340) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mActivityType=home mAlwaysOnTop=undefined mRotation=ROTATION_0} as.3 s.28 fontWeightAdjustment=0}
  taskId=54: com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher bounds=[0,0][1080,2340] userId=0 visible=true topActivity=ComponentInfo{com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher}

RootTask id=2 bounds=[0,0][1080,2340] displayId=0 userId=0
 configuration={1.0 ?mcc0mnc [en_US] ldltr sw393dp w393dp h851dp 440dpi nrml long hdr widecg port night finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2340) mAppBounds=Rect(0, 0 - 1080, 2340) mMaxBounds=Rect(0, 0 - 1080, 2340) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} as.3 s.28 fontWeightAdjustment=0}
  taskId=3: unknown bounds=[0,0][1080,2340] userId=0 visible=false
  taskId=4: unknown bounds=[0,2340][1080,3510] userId=0 visible=false

可以看到launcher包名是com.android.launcher3。

那么接下来在看看包名对应的apk目录:

这里可以使用adb shell dumpsys package com.android.launcher3方式输出所有这个包名的pms相关信息

bash 复制代码
test@test:~/wmtrace$ adb shell  dumpsys package  com.android.launcher3 | grep path
      overlay paths:
      legacy overlay paths:
    path: /system_ext/priv-app/TrebuchetQuickStep/TrebuchetQuickStep.apk

可以确认apk路径位于

/system_ext/priv-app/TrebuchetQuickStep/TrebuchetQuickStep.apk。