0

    想吃透APK的安装原理,这些知识你是逃不掉的

    2023.05.24 | admin | 141次围观

    / 今日科技快讯 /

    8月5日,据报道,目前中国约2.4亿辆汽车对收费电子设备(ETC)的需求激增,使得包括科技巨头、银行和制造商等各行各业公司都竞相投身相关市场。

    / 作者简介 /

    本篇文章来自MxsQ的投稿,分享了他对APK安装原理的个人理解,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。

    文章有点长,耐心读完哦!

    MxsQ的博客地址:

    / 背景 /

    你是否知道APK是如何进行装载的?又是否知道APK具体的安装原理。当你以此为契机查阅各种资料的时候,发现各不相同,抓不到核心部分,找不到原因安装解析包时出现问题,很容易陷入萌萌哒状态。

    仔细想想,平日能接触到的APK安装场景,主要有四种。针对每一种为出发点,有不同的应对策略,这也是为什么能查阅到的资料各不相同的原因。场景如下

    因此此文章的目的有两个:

    了解APK的核心安装原理

    了解各场景下,到达核心安装状态的应对过程原理

    / 运行APP条件 /

    当所有系统环境都准备好,安装一个APK后,运行起这个APP需要什么条件?可以借助Activity的启动过程来略窥一二,可以简述为以下过程:

    AMS从PMS获取要启动的Activity的启动信息

    AMS需要确认此Activity所运行的进程有没有启动,没有则需要请求Zygote孵化

    以上两过程正常,执行Activity启动流程

    实际上,PMS加载APK主要完成三件事情:

    解析AndroidManifest.xml,拿到构成此APP的各组件信息,以及启动信息

    为每个APP分配UID、GID,以此创建APP运行的进程,这涉及Android的沙箱模型,可以理解为应用程序资源归属问题的解决

    更新应用程序权限

    因此,所有不同的APK安装场景,在完成了各自必要的准备后,均需完成上述三件事情。这是殊途同归的过程。先说同归,再续殊途。

    note: 文章源码版本为8.0。

    / 同归 /

    先了解Package类:

    从APK中解析出的信息将存于Package。需要注意codePath区分分包的情况,即5.0后,为了解决65536问题,将APK拆成多个APK策略。以及Activity、Service、Provider等并不是日常所用的相应组件,而是存储了对应组件信息的信息聚合类,大概如下:

    public final static class Activity extends Component<ActivityIntentInfoimplements Parcelable

    第一步 解析AndroidManifest.xml

    第一件事情,解析AndroidManifest.xml,直接定位 PackageParser.parsePackage()。

    Android中存在各种包、如APK、Jar、so等均以静态文件的形式存在,需要对各样的包进行包管理。但包管理在内存中进行,因此需要PackageParser将各种包转换为内存中的数据结构。

    上面代码片段if()是针对是否使用分包策略的不同执行路径,但均通过此调用路径:

    上述通过XmlResourceParser逐步解析AndroidManifest.xml文件中 Manifest 节点个字节点信息,如parseBaseApplication()解析 application 节点信息等,解析出的数据存于pkg中。具体包含的信息大致如图:

    xml具体解析的细枝末节就不再深入。在parseBaseApkCommon()执行完后,能拿到最关键的信息分别是 application 节点下的各组成APP的组件信息,以及 uses-permission 和 manifest 下拿到的UID、GID、GIDS、share uid信息,这将决定能访问的资源。

    第二步,分配进程UID

    定位PMS.scanPackageDirtyLI()。

    上述代码有两个类做简要了解:

    整段代码实际完成三件事情:

    为pkg所描述的应用程序分配UID,并更新PackageSetting,因为APK的安装涉及到新应用安装、旧版本更新以及share user id场景。新版本情况下,要准备新的PackageSetting并分配UID;旧版本更新情况下,更新PackageSetting,UID已做过分配;share user id情况下则需考虑PackageSetting的可用性

    将pkg所指向的Pacakge保存到PMS

    将pkg描述的四大组件保存到PMS,以供AMS访问

    接下来仅对分配UID进行追踪。见Setting.addUserToSettingLPw()和Setting.newUserIdLPw()。

    一般情况,为应用程序分配LAST_APPLICATION_UID - FIRST_APPLICATION_UID之间的UID,小于LAST_APPLICATION_UID的UID给特权用户使用,能以共享的方式被应用程序使用。应用进程安装后,uid是不变的,可以通过adb命令查看:

    adb shell ps | grap packageName

    第三步 权限更新

    APP在运行过程中,需要不断地访问系统资源以及使用资源。在第一步解析AndroidManifest.xml时,uses-permission、permission 节点下申请的权限已做记录但还未授权,需要对权限状态进行更新。

    常见的权限如网络权限、文件读写权限等;危险权限则如定位权限、摄像头权限等,也包括自定义权限。权限状态更新定位 PMS.grantPermissionsLPw()。

    权限文件位于 /system/etc/permissions/platform.xml,而权限ID保存在PMS.mSetting.mPemissions中,默认全局可访问的权限在mGlobalGids中,有兴趣可自行查看。权限等级有三种:

    不同的权限等级,需要根据系统情况,设置不同的授权策略,授权策略有四种:

    上面代码可以简述为,根据所申请的等级以及系统状态,来确认授权策略。当然,在得知授权策略后,还要进行记录更新。在上述代码switch (grant)里,会调用PermissionsState.updatePermissionFlags()进行更新记录, 这里不做展开。

    小结

    APK安装核心步骤为:

    从AndroidManifest中解析出应用信息、各组件信息、权限信息

    为应用程序分配UID,并让PMS记录个组件信息,AMS启动四大组件时,需要这些信息

    更新应用程序权限信息,授权应用程序资源访问权

    如果仅对APK安装核心部分感兴趣,看到这里已经可以结束。

    / 殊途 /

    当前遗留的问题时,APK安装的核心步骤,是散落的,缺少控制逻辑。而之后的文章篇幅,则是针对各种APK安装场景进行跟踪阐述,了解在进行核心步骤时,都经历了怎样的过程。场景顺序为系统启动安装、第三方应用安装、ABD安装、Martket安装,每一个场景可以视为独立章节。

    系统启动安装

    System进程在启动时,会初始化系统运行时的各种环境参数、并启动各种辅助。在启动Boot服务时,激活PMS.main()创建PMS,并注册入ServiceManager中。

    系统在每次启动时,都会重新安装所有应用程序,共有四种类型的应用程序,分别存于不同文件夹之下:

    而 /system/framework 则是资源性应用程序安装解析包时出现问题,是用来打包资源文件的,不包含有执行代码。

    之前说过,每一用户安装的应用程序被分配的UID是不变的,因此系统通过 /data/system/package.xml 可以到达此目的。package.xml文件保存了上一次安装应用程序的信息,其中也包括了应用程序UID,因此可以在解析出package.xml 信息后,向系统申请分配各应用程序的UID 。

    紧接着,在拿到各种程序的文件夹后,通过scanDirTracedLI()进行安装,再通过updatePermissionsLPw()触发grantPermissionsLPw()更新所有应用程序权限。最后将最新的pacakge.xml写入保存。这里也就将之前所有的安装APK的核心步骤串联了起来。

    Package.xml 文件的解析

    package.xml 文件格式如图:

    想吃透APK的安装原理,这些知识你是逃不掉的

    实际了解关键节点的作用,即可大致知道程序如何解析。

    在解析package节点时,解析出userId后,通过Setting.addPackageLPw() 和Setting.addUserIdLPw()让系统分配UID,并能拿到描述应用程序信息的的PackageSetting。

    Package.xml文件保存

    见Settings.writeLPr()。

    package.xml的保存,除了根据规则生成xml外,还做了备份处理,备份文件路径为/data/system/package-backup.xml。备份文件是为了防止package.xml在写入过程中防止意外中断而做的保障。

    scanDirTracedLI()

    解析安装各类型的应用程序就比较好理解了,当前拿到的是Director,因此逐个分出安装文件进行安装。见PMS.scanDirTracedLI()和scanDirLI()。

    第三方应用安装

    第三方应用需要PMS向PackageHandler发送信息来驱动安装,见PMS.PackageHandler.doHandleMessage()。

    上面的逻辑是这样的。

    INIT_COPY

    HandlerParams包含了安装APK必要的参数。在收到INIT_COPY信息后,会与DefaultContainerService进行连接,DefaultContainerService负责处理文件检查与拷贝等耗时操作,与PMS运行在不同进程。在与DefaultContainerService链接后,PMS获得可转为IMediaContainerService(AIDL)的Binder,可以用来与DefaultContainerService通信。如果链接已建立,将HandlerParams加入安装任务队列。连接操作见PackageHandler.connectToService。

    注意在连接成功后,向PackageHandler发送了MCS_BOUND信号,并将imcs作为参数对象。

    MCS_BOUND

    收到此信号有两种可能,一者是与DefaultContainerService建立了连接,在此接收通信用的AIDL;二者是接收安装请求。

    在接收安装请求时,如果mContainerService不为空,但mBound标志位没有正确设置,说明出现了异常,将安装请求队列清空。如果一切正常,将安装请求HandlerParams加入请求队列,取出位于0位置的进行startCopy()操作。在处理完后,将HandlerParams移除请求队列。如果队列还有任务,发送MCS_BOUND信息号继续执行下一条,否则发送MCS_UNBIND信号解决服务。

    startCopy()

    HandlerParams为抽象类,子类需实现以下三个方法。

    作为包安装请求时,实际类为InstallParams。

    HandlerParams.startCopy()

    HandlerParams实际操作步骤为:

    检查尝试安装次数,超过限制则放弃安装请求,发送MCS_GIVE_UP信号,调用handleServiceError()

    调用子类handleStartCopy()处理具体安装

    调用子类handleReturnCode()处理安装结果

    复制APK

    见InstallParams.handleStartCopy()。

    需要确认APK的安装位置,不同的安装位置需要构建不同的InstallArgs,InstallArgs子类有MoveInstallArgs、AsecInstallArgs、FileInstallArgs。最后通过binder让DefaultContainerService复制apk到相应位置下,具体的复制流程不进行跟踪。

    apk复制后,再由InstallParams处理结果,见InstallParams.handleStartCopyhandleReturnCode()和processPendingInstall()。

    直接跟进installPackageTracedLI()和installPackageLI()。

    这里就触发核心的安装过程。

    第三方安装小结

    PMS驱动APK安装可以用上图表示:

    第三方安装交互过程 (这小节跳过不看也没关系)

    前面说过第三方安装涉及到安装页面以及和用户交互,但目前为止未提及此类信息。原因是本质上是需要PMS来驱动安装,安装页面和用户交互是为了解决如何获取安装数据,并把安装请求发给PMS,这里做简单说明。

    系统内置了应用程序PackageInstaller处理APK的安装和卸载,PackageInstaller涉及到的页面分别是InstallStart(入口)、InstallStaging、PackageInstallerActivity、InstallInstalling。

    在7.0以下的版本能通过file:// Uri的 intent启动InstallStart。

    但在7.0之后禁止将file:// Uri暴露给其它程序,因此需要FileProvider来解决。

    在InstallStart.onCreate()通过协议的不同启动InstallStaging或PackageInstallerActivity。但处理逻辑在PackageInstallerActivity里。InstallStaging是为了处理7.0之后的场景,通过InstallStaging.StagingAsyncTask将content协议的Uri转换为File协议,再跳转PackageInstallerActivity。

    PackageInstallerActivity在处理完pkgUri,并校验安装权限,与用户交互获取用户同意安装的操作后,启动InstallInstalling。

    InstallInstalling则通过PackageInstaller.Session与PMS进行会话,调用session.commit(),最终触发installStage.installStage()发送INIT_COPY信号,进行PMS驱动APK安装过程。

    ADB 安装

    通过ADB命令

    adb install packagePath

    可将APK安装入手机。首先需要简单了解PM。PM全名为 package manager,是包管理工具。可以使用PM来执行应用程序的安装和查询应用包的信息、系统权限、控制应用等。PM接收到的各种各样的操作命令,大多通过PMS完成。以上的ABD命令会由PM接收。见PM.run()。

    关键代码不多,就一次性贴出来了。PM与PMS运行在不同的进程,需要IPC,借助PackageInstaller.Session进行,调用commit()。其中关系如下图。

    形成上述图的模版代码就不贴了。当前代码的执行路径为Session.commit()。PacakgeInstallerSession.mHandler发送 MSG_COMMIT 信号。PacakgeInstallerSession.mHandlerCallback 执行 commitLocked()。

    在 PMS.installStage() 里发送了INIT_COPY 信号, 将安装请求加入队列。因此,也是用了PMS驱动APK安装的方式。

    Martket安装

    从Martket安装就比较简单了。Martket应用程序在下载完APK后,与PMS进行对话,调用PMS.installPackageAsUser()。

    也是发送了INIT_COPY,通过PMS驱动APK安装方式进行。

    / 总结 /

    通过以上分析,实际上真正的APK安装方式为两大方式,第一种方式为系统启动时安装,第二种为PMS驱动安装。敲黑板,再做个总结。

    APK核心安装步骤

    系统启动时安装

    程序代码起点为PMS.PackageManagerService()。

    PMS驱动安装

    / 题外话 /

    取这个文章标题很符合学过程的心境。学习之初确实发现大家写APK安装原理的起始点均不同,并且写到如何解析APK,如何分配UID就没下文了,弄得我一脸懵,很是尴尬。因此萌生了写这一篇文章的想法。文章很长,看下来需要不少耐心。文章总体结构想了又想,省略了不少细节。

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    标签: apk科技新闻
    发表评论