4、安装入口:PackageInstaller的创建与初始化
好,咱们继续往下走。上一章我们聊完了PackageManagerService的启动,说白了就是系统把安装服务搭好了架子。那问题来了——用户点了一个APK文件,系统是怎么知道要启动安装流程的?
答案就在PackageInstaller这个应用里。它不是一个Service,而是一个普通的Android应用,专门负责跟用户交互。我刚开始研究源码时,一直以为安装过程是PMS直接干的,后来才发现——PMS只管后台逻辑,前台界面全是PackageInstaller在管。
4.1 PackageInstaller是什么?
PackageInstaller,说白了就是系统预装的一个APK。它的包名是com.android.packageinstaller。你想想看,每次你从浏览器下载一个APK,点开它,弹出来的那个安装界面——就是它。
它的职责很清晰:
- 展示安装确认界面
- 处理用户点击“安装”或“取消”
- 调用PMS的接口执行实际安装
- 展示安装结果
嗯,这里要注意:PackageInstaller本身没有安装权限。它只是调用PMS暴露的API。真正的安装逻辑,还是在系统服务里。
4.2 启动入口:InstallStart Activity
当用户点击一个APK文件时,系统会通过Intent启动PackageInstaller。具体是哪个Activity呢?
看AndroidManifest.xml里的配置:
<activity
android:name=".InstallStart"
android:exported="true"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
<data android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
</activity>
关键点:application/vnd.android.package-archive 这个MIME类型。系统看到这个类型,就知道要启动安装流程了。
我个人习惯在分析这类入口时,先看IntentFilter。它能告诉你这个Activity在什么场景下被触发。
4.3 InstallStart的初始化流程
InstallStart的onCreate方法里,做了几件重要的事:
- 解析Intent——拿到APK文件的URI
- 检查来源——判断是来自浏览器、文件管理器还是ADB
- 权限检查——确认是否有安装未知应用的权限
- 跳转——根据情况跳转到具体的安装界面
我截取一段核心代码,咱们看看:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 获取Intent中的URI
Uri packageUri = getIntent().getData();
if (packageUri == null) {
finish();
return;
}
// 检查来源
String callingPackage = getCallingPackage();
boolean isTrustedSource = checkTrustedSource(callingPackage);
// 权限检查
if (!hasInstallPermission()) {
// 跳转到设置页面,让用户授权
startActivity(new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES));
finish();
return;
}
// 跳转到安装确认界面
Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
installIntent.setData(packageUri);
installIntent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, isTrustedSource);
startActivity(installIntent);
finish();
}
这段代码看起来简单,但坑不少。我曾经在一个项目里遇到过一个问题:用户从微信里点开APK,结果一直跳转到设置页面,就是不让装。查了半天,发现是getCallingPackage()返回了null,导致权限判断逻辑走错了分支。
getCallingPackage()在某些场景下会返回null。如果你的代码依赖这个值做判断,一定要加null检查。否则用户会看到莫名其妙的跳转。
4.4 PackageInstallerActivity:真正的安装界面
InstallStart只是一个中转站。真正的安装确认界面,是PackageInstallerActivity。它负责:
- 解析APK的包信息(包名、版本号、权限列表)
- 展示给用户看
- 等待用户点击“安装”按钮
它的onCreate里,会调用PMS的getPackageArchiveInfo方法:
PackageManager pm = getPackageManager();
PackageInfo pkgInfo = pm.getPackageArchiveInfo(
packageUri.getPath(),
PackageManager.GET_PERMISSIONS);
if (pkgInfo != null) {
// 展示应用名称、图标、版本
mAppName = pkgInfo.applicationInfo.loadLabel(pm);
mAppIcon = pkgInfo.applicationInfo.loadIcon(pm);
mVersionName = pkgInfo.versionName;
// 展示权限列表
mPermissions = pkgInfo.requestedPermissions;
}
这里有个细节:getPackageArchiveInfo是同步方法。如果APK文件很大,或者存储设备很慢,UI线程可能会卡住。我记得在Android 8.0之前,这个问题还挺常见的。后来Google加了一些优化,但如果你自己写安装器,还是建议放到子线程里做。
4.5 安装会话的创建
当用户点击“安装”按钮后,PackageInstallerActivity会调用PMS的installPackage方法。但这里有个中间层——PackageInstaller.Session。
Android从5.0开始引入了Session机制。说白了,就是把安装过程拆成多个阶段:
| 阶段 | 说明 |
|---|---|
| 创建Session | PMS返回一个Session ID |
| 写入APK数据 | 把APK文件写入临时目录 |
| 提交Session | 触发实际安装 |
| 等待结果 | PMS返回安装状态 |
为什么要这么设计?你想想看,如果APK是从网络下载的,或者需要解密,那安装过程就不能是原子操作了。Session机制允许你先准备好数据,再一次性提交。
创建Session的代码大致是这样的:
PackageInstaller installer = pm.getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(packageName);
int sessionId = installer.createSession(params);
// 拿到Session对象,开始写入数据
PackageInstaller.Session session = installer.openSession(sessionId);
嗯,这里要注意:createSession和openSession是两个不同的调用。前者只是分配一个ID,后者才是真正打开可读写的Session。我见过有同事把这两个搞混,结果一直拿不到正确的Session对象。
4.6 小结
这一章我们走通了PackageInstaller的创建和初始化流程。从InstallStart到PackageInstallerActivity,再到Session的创建,每一步都有它的设计意图。
下一章,我们会深入Session的写入和提交过程。说白了,就是APK文件是怎么从临时目录搬到系统目录的。那里才是真正的硬核部分。