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

640?wx_fmt=jpeg


/   今日科技快讯   /


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


/   作者简介   /


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


MxsQ的博客地址:
https://www.jianshu.com/u/9cf1f31e1d09

/   背景   /


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


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


640?wx_fmt=png


因此此文章的目的有两个:
  1. 了解APK的核心安装原理
  2. 了解各场景下,到达核心安装状态的应对过程原理


/   运行APP条件   /


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


  1. AMS从PMS获取要启动的Activity的启动信息
  2. AMS需要确认此Activity所运行的进程有没有启动,没有则需要请求Zygote孵化
  3. 以上两过程正常,执行Activity启动流程

实际上,PMS加载APK主要完成三件事情:
  1. 解析AndroidManifest.xml,拿到构成此APP的各组件信息,以及启动信息
  2. 为每个APP分配UID、GID,以此创建APP运行的进程,这涉及Android的沙箱模型,可以理解为应用程序资源归属问题的解决
  3. 更新应用程序权限

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


/   同归   /


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


 
 


第一步 解析AndroidManifest.xml
第一件事情,解析AndroidManifest.xml,直接定位 PackageParser.parsePackage()。
640?wx_fmt=png


Android中存在各种包、如APK、Jar、so等均以静态文件的形式存在,需要对各样的包进行包管理。但包管理在内存中进行,因此需要PackageParser将各种包转换为内存中的数据结构。
上面代码片段if()是针对是否使用分包策略的不同执行路径,但均通过此调用路径:
  • parseBaseApk()
  • parseBaseApk()
  • parseBaseApkCommon()


640?wx_fmt=png


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


640?wx_fmt=png


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


第二步,分配进程UID


定位PMS.scanPackageDirtyLI()。
640?wx_fmt=png
640?wx_fmt=png


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


  • Settings:存储各种重要的动态配置信息或索引,如package信息索引、package.xml文件(后续会说到)、UID信息集合等
  • PackageSetting: 存储APK包解析出的信息,包括第一步中解析出的数据结构Package信息,能从Setting通过pkgName找到PackageSetting


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


  1. 为pkg所描述的应用程序分配UID,并更新PackageSetting,因为APK的安装涉及到新应用安装、旧版本更新以及share user id场景。新版本情况下,要准备新的PackageSetting并分配UID;旧版本更新情况下,更新PackageSetting,UID已做过分配;share user id情况下则需考虑PackageSetting的可用性
  2. 将pkg所指向的Pacakge保存到PMS
  3. 将pkg描述的四大组件保存到PMS,以供AMS访问


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


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


 
 


第三步 权限更新


APP在运行过程中,需要不断地访问系统资源以及使用资源。在第一步解析AndroidManifest.xml时,uses-permission、permission 节点下申请的权限已做记录但还未授权,需要对权限状态进行更新。
常见的权限如网络权限、文件读写权限等;危险权限则如定位权限、摄像头权限等,也包括自定义权限。权限状态更新定位 PMS.grantPermissionsLPw()。
640?wx_fmt=png
640?wx_fmt=png
权限文件位于 /system/etc/permissions/platform.xml,而权限ID保存在PMS.mSetting.mPemissions中,默认全局可访问的权限在mGlobalGids中,有兴趣可自行查看。权限等级有三种:


  • PROTECTION_NORMAL:任何应用都可以申请,在安装应用时授权,无需用户操作
  • PROTECTION_DANGEROU:任何应用都可以申请,在安装应用时授权,需要用户操作
  • PROTECTION_SIGNATURE:只有于声明该授权的apk使用了相同的私钥签名的应用才可以申请该权限


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


  • GRANT_INSTALL:安装时授权,无需用户操作
  • GRANT_RUNTIME:运行时授权,在6.0后加入权限动态申请,需要用户操作
  • GRANT_UPGRADE:安装时提示用户授权,需要用户操作
  • GRANT_DENIED:未获得授权


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


  1. 从AndroidManifest中解析出应用信息、各组件信息、权限信息
  2. 为应用程序分配UID,并让PMS记录个组件信息,AMS启动四大组件时,需要这些信息
  3. 更新应用程序权限信息,授权应用程序资源访问权


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


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


System进程在启动时,会初始化系统运行时的各种环境参数、并启动各种辅助。在启动Boot服务时,激活PMS.main()创建PMS,并注册入ServiceManager中。
640?wx_fmt=png
640?wx_fmt=png
系统在每次启动时,都会重新安装所有应用程序,共有四种类型的应用程序,分别存于不同文件夹之下:
  • /data/app-private: 受DRM保护的程序
  • /system/app-private: 系统自带程序
  • /vendor/app: 手机厂商自己程序
  • /data/app: 用户自行安装的程序

而 /system/framework 则是资源性应用程序,是用来打包资源文件的,不包含有执行代码。
之前说过,每一用户安装的应用程序被分配的UID是不变的,因此系统通过 /data/system/package.xml 可以到达此目的。package.xml文件保存了上一次安装应用程序的信息,其中也包括了应用程序UID,因此可以在解析出package.xml 信息后,向系统申请分配各应用程序的UID 。
紧接着,在拿到各种程序的文件夹后,通过scanDirTracedLI()进行安装,再通过updatePermissionsLPw()触发grantPermissionsLPw()更新所有应用程序权限。最后将最新的pacakge.xml写入保存。这里也就将之前所有的安装APK的核心步骤串联了起来。
Package.xml 文件的解析
package.xml 文件格式如图:
640?wx_fmt=png
实际了解关键节点的作用,即可大致知道程序如何解析。
  • package 节点:描述了某一个应用程序的安装信息,比如子节点name有包名,子节点Perms有申请的权限,自身节点属性userId即关键的UID。具体解析步骤见 Setting.readLPw()和Setting.readPackageLPw()
  • shared-user 节点:有共享应用程序的信息。在package节点是可能解析出 share user id 信息的 (与 userId 互斥),当出现此场景时,会将分配进程ID的操作挂起,等解析shared-user此节点的时候验证此share user id 的有效性再进行分配,详细见Setting.readLPw().readSharedUserLPw()

在解析package节点时,解析出userId后,通过Setting.addPackageLPw() 和Setting.addUserIdLPw()让系统分配UID,并能拿到描述应用程序信息的的PackageSetting。
640?wx_fmt=png
640?wx_fmt=png
Package.xml文件保存
见Settings.writeLPr()。
640?wx_fmt=png
package.xml的保存,除了根据规则生成xml外,还做了备份处理,备份文件路径为/data/system/package-backup.xml。备份文件是为了防止package.xml在写入过程中防止意外中断而做的保障。


scanDirTracedLI()


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


第三方应用需要PMS向PackageHandler发送信息来驱动安装,见PMS.PackageHandler.doHandleMessage()。
640?wx_fmt=png
640?wx_fmt=png
上面的逻辑是这样的。
INIT_COPY
HandlerParams包含了安装APK必要的参数。在收到INIT_COPY信息后,会与DefaultContainerService进行连接,DefaultContainerService负责处理文件检查与拷贝等耗时操作,与PMS运行在不同进程。在与DefaultContainerService链接后,PMS获得可转为IMediaContainerService(AIDL)的Binder,可以用来与DefaultContainerService通信。如果链接已建立,将HandlerParams加入安装任务队列。连接操作见PackageHandler.connectToService。
640?wx_fmt=png
注意在连接成功后,向PackageHandler发送了MCS_BOUND信号,并将imcs作为参数对象。
MCS_BOUND
收到此信号有两种可能,一者是与DefaultContainerService建立了连接,在此接收通信用的AIDL;二者是接收安装请求。
在接收安装请求时,如果mContainerService不为空,但mBound标志位没有正确设置,说明出现了异常,将安装请求队列清空。如果一切正常,将安装请求HandlerParams加入请求队列,取出位于0位置的进行startCopy()操作。在处理完后,将HandlerParams移除请求队列。如果队列还有任务,发送MCS_BOUND信息号继续执行下一条,否则发送MCS_UNBIND信号解决服务。
startCopy()
HandlerParams为抽象类,子类需实现以下三个方法。
640?wx_fmt=png
作为包安装请求时,实际类为InstallParams。
HandlerParams.startCopy()
640?wx_fmt=png
HandlerParams实际操作步骤为:


  1. 检查尝试安装次数,超过限制则放弃安装请求,发送MCS_GIVE_UP信号,调用handleServiceError()
  2. 调用子类handleStartCopy()处理具体安装
  3. 调用子类handleReturnCode()处理安装结果


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


apk复制后,再由InstallParams处理结果,见InstallParams.handleStartCopyhandleReturnCode()和processPendingInstall()。
640?wx_fmt=png
直接跟进installPackageTracedLI()和installPackageLI()。
640?wx_fmt=png
这里就触发核心的安装过程。


第三方安装小结
640?wx_fmt=png
PMS驱动APK安装可以用上图表示:


  • 通过INIT_COPY信号与DefaultContainerService进行连接,连接成功后拿到可转为IMediaContainerService的Binder,发送MCS_BOUND信号
  • 通过MCS_BOUND信号接收IMediaContainerService(AIDL)
  • 通过INIT_COPY接收HandlerParams请求数据,加入请求队列
  • 通过MCS_BOUND新型号处理HandlerParams请求,通过startCopy()拿到合适的APK路径,通过DefaultContainerService进行复制,最后通过handleReturnCode()最终触发核心安装逻辑


第三方安装交互过程 (这小节跳过不看也没关系)
前面说过第三方安装涉及到安装页面以及和用户交互,但目前为止未提及此类信息。原因是本质上是需要PMS来驱动安装,安装页面和用户交互是为了解决如何获取安装数据,并把安装请求发给PMS,这里做简单说明。
系统内置了应用程序PackageInstaller处理APK的安装和卸载,PackageInstaller涉及到的页面分别是InstallStart(入口)、InstallStaging、PackageInstallerActivity、InstallInstalling。
在7.0以下的版本能通过file:// Uri的 intent启动InstallStart。
640?wx_fmt=png
但在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命令



可将APK安装入手机。首先需要简单了解PM。PM全名为 package manager,是包管理工具。可以使用PM来执行应用程序的安装和查询应用包的信息、系统权限、控制应用等。PM接收到的各种各样的操作命令,大多通过PMS完成。以上的ABD命令会由PM接收。见PM.run()。
640?wx_fmt=png
640?wx_fmt=png
640?wx_fmt=png
关键代码不多,就一次性贴出来了。PM与PMS运行在不同的进程,需要IPC,借助PackageInstaller.Session进行,调用commit()。其中关系如下图。


640?wx_fmt=png


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


640?wx_fmt=png
在 PMS.installStage() 里发送了INIT_COPY 信号, 将安装请求加入队列。因此,也是用了PMS驱动APK安装的方式。
Martket安装
从Martket安装就比较简单了。Martket应用程序在下载完APK后,与PMS进行对话,调用PMS.installPackageAsUser()。
640?wx_fmt=png
也是发送了INIT_COPY,通过PMS驱动APK安装方式进行。


/   总结   /


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


  • 从AndroidManifest中解析出应用信息、各组件信息、权限信息,代码索引为PackageParser.parseBaseApkCommon()
  • 为应用程序分配UID,并让PMS记录个组件信息,代码索引为PMS.scanPackageDirtyLI()、Setting.addUserIdLPw()
  • 更新应用程序权限信息,授权应用程序资源访问权,代码索引为PMS.grantPermissionsLPw()


系统启动时安装
程序代码起点为PMS.PackageManagerService()。


  • 获取应用程序文件夹,system/framework、/data/app、data/app-private、/system/app、/vendor/app
  • 根据pacakge.xml文件恢复上一次保存的应用程序信息,为应用程序分配UID, 代码见Setting.ReadLPw()
  • 逐步扫描程序文件夹找出各个APK并安装
  • 更新所有应用程序权限


PMS驱动安装


  • 通过INIT_COPY信号与DefaultContainerService进行连接,连接成功后发送MCS_BOUND信号
  • 通过MCS_BOUND信号接收Binder,转为AILD接口(IMediaContainerService)
  • 通过INIT_COPY接收HandlerParams请求数据,加入请求队列
  • 通过MCS_BOUND信号处理HandlerParams请求,通过startCopy()拿到合适的APK路径,通过DefaultContainerService进行复制,最后通过handleReturnCode()最终触发核心安装逻辑


/   题外话  /
取这个文章标题很符合学过程的心境。学习之初确实发现大家写APK安装原理的起始点均不同,并且写到如何解析APK,如何分配UID就没下文了,弄得我一脸懵,很是尴尬。因此萌生了写这一篇文章的想法。文章很长,看下来需要不少耐心。文章总体结构想了又想,省略了不少细节。
推荐阅读: 使用Flutter中实现画中画效果,这操作66的
是的,Retrofit想用得好就得这么死磕
原来这样可以优雅地解决小米手机后台弹窗权限问题

欢迎关注我的公众号 学习技术或投稿


640.png?


640?wx_fmt=jpeg 长按上图,识别图中二维码即可关注
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要快速理解并掌握PFC(Power Factor Correction,功率因数校正)电源原理分析的技巧,可以按照以下4个步骤进行: 第一步是了解基本概念。PFC电源的目标是改善电源系统的功率因数,减小谐波失真,提高能源利用率。了解基本的PFC电源概念,如谐波、功率因数、能源利用等,是理解其原理分析的基础。 第二步是学习PFC电源的工作原理。PFC电源的核心组成是PFC控制器和PFC电路。通过学习PFC电源的工作原理,了解其如何校正和改善功率因数,以及各个元件和电路的作用和相互关系。 第三步是深入研究PFC电源的拓扑结构和控制方式。了解不同的PFC电路拓扑结构(如Boost型PFC、Bridgeless PFC等)和控制方式,对于理解PFC电源原理分析非常重要。研究拓扑结构和控制方式的优劣势、特点和适用范围,能够帮助快速抓住重点和关键问题。 最后一步是实践和案例分析。通过实际的项目实践和相关案例分析,深入理解PFC电源的原理和实际应用。可以通过仿真软件进行PFC电源的设计和验证,或者参考相关的研究论文和技术报告,从实践中获取经验和知识。 通过以上4个步骤,可以快速吃透PFC电源原理分析的技巧。但需要注意,PFC电源的原理和分析涉及一定的专业知识和技巧,需要较强的电力电子背景和电路分析能力。建议在了解基础知识后,多与领域专家和同行交流,不断学习和实践,进一步提升自己的能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值