前言
- 这是 Android 10 源码分析系列的第 2 篇
- 分支:android-10.0.0_r14
- 全文阅读大概 10 分钟
上一篇文章介绍了 0xA01 Android 10 源码分析:APK 是如何生成的,这篇文章接着介绍如何安装 APK,需要说一下 Android 10 及更高版本中, 安装器 PackageInstaller 源码位置有所变动
PackageInstaller 源码所在位置
PackageInstaller 是系统内置的应用程序,用于安装和卸载应用
在 Android 9 及更低版本中,软件包安装和权限控制功能包含在 PackageInstaller 软件包 (//packages/apps/PackageInstaller) 中。在 Android 10 及更高版本中,权限控制功能位于单独的软件包 PermissionController (//packages/apps/PermissionController),这两个软件包在 Android 10 中的位置如下图所示,更多信息点击这里前往 Android 权限
Android 9 及更低版本中 :
软件包安装和权限控制功能源码路径:packages/apps/PackageInstaller
Android 10 及更高版本:
- 权限控制功能 PermissionController 源码路径:packages/apps/PermissionController/
- 安装器 PackageInstaller 源码路径:frameworks/base/packages/PackageInstaller/
在 Android 系统不同的目录存放不同类型的应用
- /system/framwork:保存的是资源型的应用程序,它们用来打包资源文件
- /system/app:保存系统自带的应用程序
- /data/app:保存用户安装的应用程序
- /data/data:应用数据目录
- /data/app-private:保存受DRM保护的私有应用程序
- /vendor/app:保存设备厂商提供的应用程序
查看 PackageInstaller 源码方式
- AOSP-PackageInstaller: 包含了安装器 PackageInstaller(7.1.2、8.1.0、9.0.0、10.0.0) 的源码,可以切换分之查看,跟随 Android 版本更新,你永远可以看到最新的源代码
- aospxref:这是一个在线查看 Android 源码网站,服务器在阿里云访问速度很快,文末有关这个网站的介绍
- googlesource-PackageInstaller:这是安装器 PackageInstaller 在 googlesource 上的地址
- APK 的安装方式
安装 APK 主要分为以下三种场景
- 安装系统应用:系统启动后调用 PackageManagerService.main() 初始化注册解析安装工作
1 | java复制代码public static PackageManagerService main(Context context, Installer installer, |
- 通过 adb 安装:通过 pm 参数,调用 PM 的 runInstall 方法,进入 PackageManagerService 安装安装工作
- 通过系统安装器 PackageInstaller 进行安装:先调用 InstallStart 进行权限检查之后启动 PackageInstallActivity,调用 PackageInstallActivity 的 startInstall 方法,点击 OK 按钮后进入 PackageManagerService 完成拷贝解析安装工作
所有安装方式大致相同,最终就是回到 PackageManagerService 中,安装一个 APK 的大致流程如下:
- 拷贝到 APK 文件到指定目录
- 解压缩 APK,拷贝文件,创建应用的数据目录
- 解析 APK 的 AndroidManifest.xml 文件
- 向 Launcher 应用申请添加创建快捷方式
本文主要来分析通过安装器 PackageInstaller 安装 APK,这是用户最常用的一种方式
- PackageInstaller 的入口
下面代码一定不会很陌生,这就是我们常用的安装 APK 的代码(PS: 关于静默安装我会后续分享在逆向开发相关的文章)
1 | scss复制代码Intent intent = new Intent(Intent.ACTION_VIEW); |
通过 intent.setDataAndType 方法指定 Intent 的数据类型为 application/vnd.android.package-archive,隐式匹配的 Activity 为 InstallStart:
frameworks/base/packages/PackageInstaller/AndroidManifest.xml
1 | ini复制代码<activity android:name=".InstallStart" |
- 本文分析的是 10.0 的源码,在 8.0、9.0、10.0 等等版本中隐式匹配的 Activity 是 InstallStart,7.0 隐式匹配的 Activity 是 PackageInstallerActivity
- 安装器 PackageInstaller 的入口 Activity 是 InstallStart,定义了两个 scheme:content 和 package
- APK 的安装流程
通过上面方式找到了入口 Activity,下面我们来查看一下 APK 是如何安装的
3.1 InstallStart
主要工作:
- 判断是否勾选“未知来源”选项,若未勾选跳转到设置安装未知来源界面
- 对于大于等于 Android 8.0 版本,会先检查是否申请安装权限,若没有则中断安装
- 判断 Uri 的 Scheme 协议,若是 content 则调用 InstallStaging, 若是 package 则调用 PackageInstallerActivity
当我们调用上面安装代码来安装 APK 时。会跳转到 InstallStart, 并调用它的 onCreate 方法:
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
1 | java复制代码@Override |
根据 Uri 的 Scheme 协议,若是 content 则调用 InstallStaging,查看 InstallStaging 的 onResume方法:
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
1 | scss复制代码@Override |
- 创建临时文件 mStagedFile 用来存储数据
- 启动 StagingAsyncTask,并传入了 content 协议的 Uri
1 | scala复制代码private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> { |
- doInBackground 方法中将 packageUri(content 协议的 Uri)的内容写入到 mStagedFile 中
- 如果写入成功,调用 DeleteStagedFileOnResult 的 OnCreate 方法:
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
1 | less复制代码protected void onCreate(@Nullable Bundle savedInstanceState) { |
经过分析 InstallStaging 主要起了中转作用,将 content 协议的 Uri 转换为 File 协议,最后跳转到 PackageInstallerActivity
3.2 PackageInstallerActivity
主要工作:
- 显示安装界面
- 初始化安装需要用的各种对象,比如 PackageManager、IPackageManager、AppOpsManager、UserManager、PackageInstaller 等等
- 根据传递过来的 Scheme 协议做不同的处理
- 检查是否允许、初始化安装
- 在准备安装的之前,检查应用列表判断该应用是否已安装,若已安装则提示该应用已安装,由用户决定是否替换
- 在安装界面,提取出 APK 中权限信息并展示出来
- 点击 OK 按钮确认安装后,会调用 startInstall 开始安装工作
PackageInstallerActivity 才是应用安装器 PackageInstaller 真正的入口 Activity,查看它的 onCreate 方法:
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
1 | scss复制代码protected void onCreate(Bundle icicle) { |
主要做了对象的初始化,解析 Uri 的 Scheme,初始化界面,安装包检查等等工作,接着查看一下 processPackageUri 方法
1 | java复制代码private boolean processPackageUri(final Uri packageUri) { |
主要对 Scheme 协议分别对 package 协议和 file 协议进行处理
SCHEME_PACKAGE:
- 在 package 协议中调用了 PackageManager.getPackageInfo 方法生成 PackageInfo,PackageInfo 是跨进程传递的包数据(activities、receivers、services、providers、permissions等等)包含 APK 的所有信息
SCHEME_FILE:
- 在 file 协议的处理中调用了 PackageUtil.getPackageInfo 方法,方法内部调用了 PackageParser.parsePackage() 把 APK 文件的 manifest 和签名信息都解析完成并保存在了 Package,Package 包含了该 APK 的所有信息
- 调用 PackageParser.generatePackageInfo 生成 PackageInfo
接着往下走,都解析完成之后,回到 onCreate 方法,继续调用 checkIfAllowedAndInitiateInstall 方法
1 | scss复制代码private void checkIfAllowedAndInitiateInstall() { |
主要检查安装应用程序的用户限制,当 APK 文件不对或者安装有限制则调用 showDialogInner 方法,弹出 dialog 提示用户,显示相应的错误信息,来看一下都有那些错误信息
1 | java复制代码// Dialog identifiers used in showDialog |
如果用户允许安装未知来源,会调用 initiateInstall 方法
1 | ini复制代码private void initiateInstall() { |
根据包名获取应用程序的信息,调用 startInstallConfirm 方法初始化安装确认界面后,当用户点击确认按钮之后发生了什么,接着查看确认按钮点击事件
1 | scss复制代码private void bindUi() { |
当用户点击确认按钮调用了 startInstall 方法,启动子 Activity 完成 APK 的安装
1 | scss复制代码private void startInstall() { |
startInstall 方法用来跳转到 InstallInstalling,并关闭掉当前的 PackageInstallerActivity
3.3 InstallInstalling
主要工作:
- 向包管理器发送包的信息,然后等待包管理器处理结果
- 注册一个观察者 InstallEventReceiver,并接受安装成功和失败的回调
- 在方法 onResume 中创建同步栈,打开安装 session,设置安装进度条
InstallInstalling 首先向包管理器发送包的信息,然后等待包管理器处理结果,并在方法 InstallSuccess 和方法 InstallFailed 进行成功和失败的处理,查看 InstallInstalling 的 onCreate 方法:
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
1 | scss复制代码protected void onCreate(@Nullable Bundle savedInstanceState) { |
- 最终都会注册一个观察者 InstallEventReceiver,并在 launchFinishBasedOnResult 会接收到安装事件的回调,其中 InstallEventReceiver 继承自 BroadcastReceiver,用于接收安装事件并回调给 EventResultPersister
- createSession 内部通过 IPackageInstaller 与 PackageInstallerService 进行进程间通信,最终调用的是 PackageInstallerService的createSession 方法来创建并返回 mSessionId
- 接下来在 onResume 方法创建 InstallingAsyncTask 用来执行 APK 的安装,接着查看 onResume 方法
1 | scss复制代码protected void onResume() { |
得到 SessionInfo 创建并创建 InstallingAsyncTask,InstallingAsyncTask 的 doInBackground 方法设置安装进度条,并将 APK 信息写入 PackageInstaller.Session,写入完成之后,在 InstallingAsyncTask 的 onPostExecute 进行成功与失败的处理,接着查看 onPostExecute 方法
1 | scss复制代码protected void onPostExecute(PackageInstaller.Session session) { |
创建了 broadcastIntent,并通过 PackageInstaller.Session 的 commit 方法发送出去,通过 broadcastIntent 构造方法指定的 Intent 的 Action 为 BROADCAST_ACTION,而 BROADCAST_ACTION 是一个常量值
1 | arduino复制代码 private static final String BROADCAST_ACTION = |
回到 InstallInstalling.OnCreate 方法,在 OnCreate 方法注册 InstallEventReceiver,而 InstallEventReceiver 继承自 BroadcastReceiver,而使用 BroadcastReceiver 需要在 AndroidManifest.xml注册,接着查看 AndroidManifest.xml:
/frameworks/base/packages/PackageInstaller/AndroidManifest.xml
1 | ini复制代码<receiver android:name=".InstallEventReceiver" |
安装结束之后,会在观察者 InstallEventReceiver 注册的回调方法 launchFinishBasedOnResult 处理安装事件的结果,接着查看 launchFinishBasedOnResult
1 | scss复制代码private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) { |
安装成功和失败,都会启动一个新的 Activity(InstallSuccess、InstallFailed)将结果展示给用户,然后 finish 掉 InstallInstalling
- 总结
总结一下 PackageInstaller 安装APK的过程:
- 根据根据 Uri 的 Scheme 找到入口 InstallStart
- InstallStart 根据 Uri 的 Scheme 协议不同做不同的处理
- 都会调用 PackageInstallerActivity, 然后分别对package协议和 file 协议的 Uri 进行处理
- PackageInstallerActivity 检查未知安装源限制,如果安装源限制弹出提示 Dialog
- 点击 OK 按钮确认安装后,会调用 startInstall 开始安装工作
- 如果用户允许安装,然后跳转到 InstallInstalling,进行 APK 的安装工作
- 在 InstallInstalling 中,向包管理器发送包的信息,然后注册一个观察者 InstallEventReceiver,并接受安装成功和失败的回调
- 关于 packages.xml
在 Andorid 系统目录 “/data/system” 下保存很多系统文件,主要介绍 packages.xml 文件
- packages.xml:记录了系统中所有安装的应用信息,包括基本信息、签名和权限、APK 文件的路径、native 库的存储路径
系统启动的时候会通过 PackageManagerServcie 读取这个文件加载系统中所有安装的应用,这个文件在开发中也是非常有帮助的,不同厂商会对 Android 源码有不同的修改,如果我们需要分析系统 App 的源码,就通过这个 packages.xml 找到目标 APK,dump 出来分析源码
以下是 packages.xml 文件部分内容
1 | ini复制代码<?xml version='1.0' encoding='utf-8' standalone='yes' ?> |
5.1. package 表示包信息
- name 表示应用的包名
- codePath 表示的是 APK 文件的路径
- nativeLibraryPath 表示应用的 native 库的存储路径
- it 表示应用安装的时间
- ut 表示应用最后一次修改的时间
- version 表示应用的版本号
- userId 表示所属于的 id
5.2. sign 表示应用的签名
- count 表示标签中包含有多少个证书
- cert 表示具体的证书的值
5.3. perms 表示应用声明使用的权限,每一个子标签代表一项权限
- 安利一个在线查看 Android 源码网站
aospxref 是 weishu 大神搭建一个在线查看在线查看 Android源码网站, 访问速度非常快
在这之前我常用的在线查看 Android 源码的网站 androidxref,访问速度不仅慢,而且更新也不及时,现在 Android 10 发布了,这个网站到现在提供的最新的代码还是 Andorid 9
aospxref 提供了与 androidxref 完全一样的源码浏览和交叉索引功能;除此之外,它还有一些别的优点:
- 跟随 Android 版本更新,你永远可以看到最新的源代码。
- 服务器在阿里云,国内访问速度贼快。
- opengrok 版本较高,查阅代码时会有自动提示。
- 对页面做过部分优化,使用更便捷;比如可以在任意界面跳转到首页。
参考
结语
致力于分享一系列 Android 系统源码、逆向分析、算法、翻译、Jetpack 源码相关的文章,正在努力写出更好的文章,如果这篇文章对你有帮助给个 star,文章中有什么没有写明白的地方,或者有什么更好的建议欢迎留言,欢迎一起来学习,在技术的道路上一起前进。
计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,可以前去查看:AndroidX-Jetpack-Practice, 如果这个仓库对你有帮助,请帮我点个赞,我会陆续完成更多 Jetpack 新成员的项目实践。
算法
由于 LeetCode 的题库庞大,每个分类都能筛选出数百道题,由于每个人的精力有限,不可能刷完所有题目,因此我按照经典类型题目去分类、和题目的难易程度去排序。
- 数据结构: 数组、栈、队列、字符串、链表、树……
- 算法: 查找算法、搜索算法、位运算、排序、数学、……
每道题目都会用 Java 和 kotlin 去实现,并且每道题目都有解题思路、时间复杂度和空间复杂度,如果你同我一样喜欢算法、LeetCode,可以关注我 GitHub 上的 LeetCode 题解:Leetcode-Solutions-with-Java-And-Kotlin,一起来学习,期待与你一起成长。
Android 10 源码系列
正在写一系列的 Android 10 源码分析的文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,如果你同我一样喜欢研究 Android 源码,可以关注我 GitHub 上的 Android10-Source-Analysis,文章都会同步到这个仓库。
- 0xA01 Android 10 源码分析:APK 是如何生成的
- 0xA02 Android 10 源码分析:APK 的安装流程
- 0xA03 Android 10 源码分析:APK 加载流程之资源加载
- 0xA04 Android 10 源码分析:APK 加载流程之资源加载(二)
- 0xA05 Android 10 源码分析:Dialog 加载绘制流程以及在 Kotlin、DataBinding 中的使用
- 更多……
工具系列
- 为数不多的人知道的 AndroidStudio 快捷键(一)
- 为数不多的人知道的 AndroidStudio 快捷键(二)
- 关于 adb 命令你所需要知道的
- 如何高效获取视频截图
- 10分钟入门 Shell 脚本编程
- 如何在项目中封装 Kotlin + Android Databinding
逆向系列
本文转载自: 掘金