一文搞懂Apk的各种类型
戳蓝字“牛晓伟”关注我哦!
用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章,技术文章也可以有温度。
本文摘要
本文主要介绍Android中Apk的各种类型,通过本文您将了解到Apk分为哪些类型,系统Apk、普通Apk、特权Apk、core Apk、product Apk等这些Apk之间的区别和作用。 (文中代码基于Android13)
本文采用对话的方式,人物小昱和大牛,小昱是Android新人,为了能进入大厂,利用工作之余恶补Android知识。大牛是具有多年开发经验的老手。小昱有问题就会向大牛请教。
本文大纲
1. Apk分类
小昱最近正在梳理包管理 (PackageManagerService) 和 权限管理 (PermissionManagerService)的相关内容,但是在梳理关于Apk类型和权限类型时,又被搞的头晕脑胀的。于是想起了向大牛请教。
小昱:“大牛,不好意思又来麻烦你了,我看Android中的Apk有普通Apk、系统Apk、privileged Apk (特权Apk)、persistent Apk、product Apk等,就这些不同类别的Apk就把我搞的云里雾里了。而更让人头疼的是Android中的权限还有normal权限、dangerous权限、privileged权限(特权权限)等这些类别的权限。我在梳理这些类别的时候,真的是越梳理越乱,能帮帮梳理梳理吗?谢谢。”
大牛:“没问题小昱,千万不要慌,一口吃不成一个胖子,那我就先从Apk的类型说起吧,Apk从大类上主要分为系统Apk和普通Apk,而系统Apk还可以继续分类,你刚刚提到的privileged Apk就属于系统Apk的一种,那我就先从最复杂的系统Apk说起吧。”
2. 系统Apk
大牛:“Android中像launcher、systemui、setting、camera、gallery等Apk都是系统Apk,能成为系统Apk可是很多普通Apk梦寐以求的事情啊,因为系统Apk相对于普通Apk确实有很多的特权…"
小昱突然礼貌性的打断了大牛的讲话:“大牛,这也是我正想知道的事情,一个Apk需要具备什么样的特性才能成为系统Apk,或者PackageManagerService是根据啥来识别一个Apk是系统Apk的,这个事情一直困扰着我,让我久久不能睡眠。快点告诉我吧,我实在太想知道答案了。”
2.1 如何成为系统Apk
大牛:“这个问题的答案非常的简单,PackageManagerService服务是根据Apk所处的目录来判断Apk到底是系统Apk还是普通Apk的,我特意绘制了一幅图,展示了系统Apk所存放的所有目录,凡是Apk存放于以下目录都是系统Apk。”
小昱有些不敢相信的说:“啊!难道就这么简单吗?如果是这么简单,那我也可以把一个普通Apk放入这些目录下面,就可以让它变成系统Apk了。”
听了小昱的话,大牛有些好笑又有些气愤,心里默念不知者不为过,说:“把普通Apk放入这些目录,这不是开国际玩笑嘛,要想把普通Apk放入这些目录除非有root权限,否则别白日做梦啊。系统Apk确实就是根据Apk所存放的目录来决定的,那就听我细细道来吧。”
还记得在PackageManagerService服务启动的时候会做一件非常重要的事情扫描所有Apk (不记得可以看这篇文章),扫描所有Apk分为扫描所有系统Apk和扫描所有普通Apk,而扫描所有系统Apk需要做如下几个关键事情:
- 首先要依次扫描system、odm、oem、product、system_ext、vendor、apex这几个目录 (这几个目录定义在Partition类)
- 而在扫描这些目录的时候会增加一些scan flags值,其中对所有目录都要增加的一个值是SCAN_AS_SYSTEM,而不同的目录也会增加自己对应的scan flags值。比如扫描odm目录会增加SCAN_AS_ODM 和 SCAN_AS_SYSTEM 值,扫描product目录会增加SCAN_AS_PRODUCT 和 SCAN_AS_SYSTEM 值 (这些scan值定义在PackageManagerService类)
- 扫描Apk的其中一个环节是解析Apk信息,而解析完的Apk信息会存储在ParsedPackage对象中,进而再根据上面的 scan flags 值,对ParsedPackage对象的相应属性进行设置,比如是否是系统Apk,是否是product apk等。如下是相关代码:
//ScanPackageUtils类//该方法会用scanFlags来设置parsedPackage的相应属性
public static void applyPolicy(ParsedPackage parsedPackage,final @PackageManagerService.ScanFlags int scanFlags, AndroidPackage platformPkg,boolean isUpdatedSystemApp) {//scanFlags有SCAN_AS_SYSTEM,则是系统Apkif ((scanFlags & SCAN_AS_SYSTEM) != 0) {//setSystem为true,则认为是系统apkparsedPackage.setSystem(true);省略代码······} 省略代码······//根据scanFlags值设置是否是Oem、product等等parsedPackage.setPrivileged((scanFlags & SCAN_AS_PRIVILEGED) != 0) .setOem((scanFlags & SCAN_AS_OEM) != 0).setVendor((scanFlags & SCAN_AS_VENDOR) != 0).setProduct((scanFlags & SCAN_AS_PRODUCT) != 0).setSystemExt((scanFlags & SCAN_AS_SYSTEM_EXT) != 0).setOdm((scanFlags & SCAN_AS_ODM) != 0);省略代码······}
小昱:“大牛,我看了你的解释后,终于明白了,也就是说在PackageManagerService执行扫描Apk的过程,不同的目录会携带不同的scan flags值,最终根据该值来判断是不是系统Apk。”
大牛:“是的非常正确,还有一个点要说下系统Apk的安装是在PackageManagerService的扫描阶段完成的,不像普通Apk是有安装界面一说的。那咱们接着介绍下系统Apk的分类吧,系统Apk可以按存放的目录分类,也可以按Apk所具备的能力或特性分类,那就先从前者开始介绍吧。”
2.2 按存放目录分类
下图展示了系统Apk可以存放的目录及其子目录,请看下图:
如上图,系统Apk可以存放于/system、/system_ext、/product、/vendor、/odm、/oem、/apex这几个目录下面的子目录中,而系统Apk的分类又可以按根目录分类也可以按按子目录分类。
2.2.1 按根目录分类
系统Apk根据存放的根目录可以划分为vendor Apk、product Apk、systemExt Apk、system Apk、odm Apk、oem Apk (由于存放在apex根目录下的Apk不是咱们的重点因此在这不予介绍)。
2.2.2 按子目录分类
系统Apk一般主要存放于各自根目录下的/app、/priv_app、/overlay这三个子目录中,为啥这里用了一般这个词呢,因为对于system根目录来说,它的framework子目录也是可以存放系统Apk的,比如framework-res.apk就存放于此。
存放于/priv-app子目录的系统Apk又被称为privileged Apk (特权Apk),存放于/overlay子目录的系统Apk又被称为overlay Apk,既不是privileged Apk也不是overlay Apk的系统Apk,是存放于/app子目录的。那就来介绍下privileged Apk和overlay Apk
privileged Apk
privileged Apk翻译为中文是特权Apk,该种类型Apk主要存放于/priv-app目录下,这里的特权是特殊权限 (privileged permission)的简称,Apk使用的权限是有很多种的比如危险权限、normal权限等,而特殊权限是其中一种。
privileged Apk也就是该类型的Apk是可以使用特殊权限的,其他类型Apk是不可以使用特殊权限的。也就是特殊权限只归privileged Apk使用,但并不是说privileged Apk只可以使用特殊权限,它还可以使用别的权限。
要变为该类型的Apk,其实特别简单只需要把Apk放入上面提到的几个目录下面的 /priv-app 目录中即可,如/product/priv-app、/system/priv-app等。在扫描所有系统Apk的过程中,针对priv-app目录,会增加SCAN_AS_PRIVILEGED的flag值。如果在privileged Apk的AndroidManifest.xml文件中使用了特殊权限,那需要在对应的特权名单内把所有的特殊权限都加入,否则会导致系统启动不了,如下是一个特权名单的例子:
//特权名单的名字是 包名.xml(如android/com.example.myapplication2.xml),并且需要放在对应 xxx/etc/permissions 目录下,xxx代表系统apk存放的根目录,如product、system等
<permissions><privapp-permissions package="com.example.myapplication2">//permission代表授予某个特殊权限<permission name="android.permission.STATUS_BAR"/>//deny-permission代表拒绝某个特殊权限<deny-permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/></privapp-permissions>
</permissions>
overlay Apk
该种类型的Apk主要存放于overlay子目录下,该种类型的Apk不包含任何的代码,只包含资源,该资源是res目录下的资源。该Apk的作用就是起到换肤的作用。当然这种类型的Apk只是对相应系统Apk进行换肤操作,而不会影响普通Apk。
2.2.3 小结
系统Apk可以按根目录分类也可以按子目录分类,比如存放于/product/priv-app/目录下的Apk,该Apk既是product Apk,也是privileged Apk。存放于/system/app/目录下的Apk,就是一个system Apk即系统Apk。
2.3 按Apk所具备的能力或特性分类
系统Apk按Apk所具备的能力或特性可以分为core Apk 和 persistent Apk,那就来介绍下它们。
core Apk
core Apk翻译为中文是核心Apk,用一句话总结该Apk就是说当Android设备配置特别特别低端的时候,其他的Apk都可以不要,但是core Apk是必须的。该类型的Apk会在PackageManagerService服务启动的时候前置于其他Apk创建data目录。像systemui都属于该类型的Apk。
要变为该类型的Apk,只需要在AndroidManifest.xml文件中,增加 coreApp=“true” 即可,如下例子:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.android.systemui"android:sharedUserId="android.uid.systemui"coreApp="true">
persistent Apk
persistent Apk翻译为中文是持久的Apk,是啥子意思呢?就是说该类别的Apk在App运行过程中,如果意外退出了,系统还会把它给拉起,让它继续保持运行状态。并且在Android设备启动后,是会把所有符合情况的persistent Apk提前启动,如下是相关代码:
//ActivityManagerService类//该方法会在系统准备好后开始调用
void startPersistentApps(int matchFlags) {if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) return;synchronized (this) {try {//从PackageManagerService获取符合条件的persistent Appfinal List<ApplicationInfo> apps = AppGlobals.getPackageManager().getPersistentApplications(STOCK_PM_FLAGS | matchFlags).getList();for (ApplicationInfo app : apps) {if (!"android".equals(app.packageName)) {//启动它们final ProcessRecord proc = addAppLocked(app, null, false, null /* ABI override */,ZYGOTE_POLICY_FLAG_BATCH_LAUNCH);省略代码······}}} catch (RemoteException ex) {}}}
小昱:“那一个Apk如何变为该类型Apk呢?”
大牛:“答案很简单,只需要在AndroidManifest.xml文件的application tag中加入android:persistent="true"即可,该配置只有对系统Apk才有效。如下例子。”
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><applicationandroid:persistent="true">
2.4 小结
- 系统Apk按Apk存放的根目录可以分为vendor Apk、product Apk、systemExt Apk、system Apk、odm Apk、oem Apk
- 系统Apk按Apk存放的子目录可以分为privileged Apk、overlay Apk
- 系统Apk按Apk所具备的能力或特性可以分为persistent Apk、core Apk
大牛:“我从按目录分类和按Apk所具备的能力或特性分类两个方面来介绍系统Apk的分类,而这两个方面分类的系统Apk是可以进行随机组合的。”
小昱:“随机组合?这是啥意思吗?”
大牛:“小昱别急啊,我正要说呢,比如存放于/vendor/priv-app/目录下的Apk是可以配置为persistent Apk或者core Apk甚至这两种类型都可以配置,这样这个Apk可以是vendor privileged persistent类型的Apk或者是vendor privileged core类型的Apk或者是vendor privileged persistent core类型的Apk。这就是它们可以随即组合的意思。好那我来介绍相对简单的普通Apk。”
3. 普通Apk
普通Apk就很简单了,别看微信、抖音是超级Apk,但是它们依然逃脱不了普通Apk的命运,普通Apk被安装后Apk文件是被存放于/data/app目录下的,普通Apk因为它不是系统Apk,因此它也不可能是vendor Apk或者上面提到的其他类型Apk,甚至也不能是persistent Apk和core Apk。在PackageManagerService扫描所有普通Apk时是没有加像扫描系统Apk那些scan flags值的,因此扫描完所有普通Apk后,这些Apk只能被识别为普通Apk。
下面是相关代码,请自行取阅:
//InitAppsHelper 类//扫描所有普通Apk
public void initNonSystemApps(PackageParser2 packageParser, @NonNull int[] userIds,long startTime) {if (!mIsOnlyCoreApps) {省略代码······//其中 mPm.getAppInstallDir() 获取的值是 data/app,而 mScanFlags值是没有增加扫描系统Apk的那些 scan flag值的scanDirTracedLI(mPm.getAppInstallDir(), /* frameworkSplits= */ null, 0,mScanFlags | SCAN_REQUIRE_KNOWN,packageParser, mExecutorService); }省略代码······}
4. 总结
大牛:“小昱,关于Apk类型的知识就介绍完了,那我来介绍下系统Apk与普通Apk的主要区别,以及系统Apk具有哪些特权来作为结尾吧。”
先来说下它们区别:
- 系统Apk的安装主要是在PackageManagerService启动时候扫描所有Apk的阶段;而普通Apk的安装是需要通过用户来安装,在安装过程是有安装界面的。
- 系统Apk使用Android.bp来配置编译信息;而普通Apk使用gradle进行编译。
- 系统Apk是不可以被用户卸载的;而普通Apk是可以被用户卸载的。
- 系统Apk的Apk文件是存放在/system、/system_ext、/product、/vendor、/odm、/oem、/apex目录下的子目录中;而普通Apk被安装后Apk文件是存放在/data/app目录下的。
- 系统Apk拥有很多的特权;而普通Apk啥也没有。
不想当CTO的程序员不是好程序员,不想成为系统Apk的普通Apk不是好Apk,那就来说说系统Apk到底有多大的魅力,让普通Apk这么着迷吧。
- 有些系统Apk希望自己的uid是1000,也就是和systemserver进程一样的uid,那就需要在该Apk的AndroidManifest.xml文件中配置android:sharedUserId=“android.uid.system”。该Apk的uid是1000后那做的事情可就多了,比如可以访问systemserver进程的各种文件。
- 系统Apk若配置为persistent Apk的话,就可以保持长久运行了。
- 若在内存紧张的情况下,普通App被杀掉的概率要远大于系统App。
当然上面只是列出了一些系统Apk相对于普通Apk的优势,其实还有很多没有列出来,关于Apk类型的介绍就到此为止。
下面是我的知识星球,欢迎大家加入