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方法里,做了几件重要的事:

  1. 解析Intent——拿到APK文件的URI
  2. 检查来源——判断是来自浏览器、文件管理器还是ADB
  3. 权限检查——确认是否有安装未知应用的权限
  4. 跳转——根据情况跳转到具体的安装界面

我截取一段核心代码,咱们看看:

@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,导致权限判断逻辑走错了分支。

避坑指南: 我曾经在适配Android 12时发现,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);

嗯,这里要注意:createSessionopenSession是两个不同的调用。前者只是分配一个ID,后者才是真正打开可读写的Session。我见过有同事把这两个搞混,结果一直拿不到正确的Session对象。

个人经验: 我在做系统应用预装时,经常用Session机制来批量安装APK。先创建多个Session,然后并行写入数据,最后统一提交。这样能大幅提升安装速度。但要注意,Session数量有限制,默认是30个左右,超过会报错。

4.6 小结

这一章我们走通了PackageInstaller的创建和初始化流程。从InstallStart到PackageInstallerActivity,再到Session的创建,每一步都有它的设计意图。

下一章,我们会深入Session的写入和提交过程。说白了,就是APK文件是怎么从临时目录搬到系统目录的。那里才是真正的硬核部分。