PMS-APK的安装

156 阅读9分钟

前言

要研究APK的安装过程,还是从我们平时的使用开始说起。一般来说我们安装APK有3种方式:

  • 下载APK到手机,点击APK文件安装
  • 在手机自带的应用市场安装
  • 使用adb命令安装

其实这几种方式大体的流程都是一样的,区别仅仅在前期对APK的一些检查项和UI上的区别。我们这里研究一下手动点击APK的安装流程即可,因为它的流程相对完整,更符合一般用户的使用习惯。

1.安装的准备工作

在Android中,通过发送Intent就可以启动应用的安装过程,比如:

Uri uri = Uri.fromFile(new File(apkFilePath));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri,"application/vnd.android.package-archive");
startActivity(intent);

在Android的系统应用PackageInstaller中有一个InstallStart.java会响应这个Intent:

<activity android:name=".InstallStart"
    android:theme="@style/Installer"
        android:exported="true"
        android:excludeFromRecents="true">
    <intent-filter android:priority="1">
        <action android:name="android.intent.action.VIEW" />
        <action android:name="android.intent.action.INSTALL_PACKAGE" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="file" />
        <data android:scheme="content" />
        <data android:mimeType="application/vnd.android.package-archive" />
    </intent-filter>
    ......
</activity>

紧接着在InstallStart的onCreate方法中会进行各种Uri的判断,最终会跳转到一个叫做PackageInstallerActivity的界面。

Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);

// The the installation source as the nextActivity thinks this activity is the source, hence
// set the originating UID and sourceInfo explicitly
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);

if (isSessionInstall) {
    nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
    ...
}

if (nextActivity != null) {
    startActivity(nextActivity);
}

对于PackageInstallerActivity界面,主要做以下几件事:

  • 解析Uri协议进行解析,包括file和package两种

  • 对未知来源进行处理,并进行相应的UI提示

  • 提取并在界面展示应用对应的权限
    如果一切顺利,点击确认弹框之后,会执行startInstall()方法,如下:

    private void startInstall() {
      // Start subactivity to actually install the application
      Intent newIntent = new Intent();
      ......
      newIntent.setData(mPackageURI);
      newIntent.setClass(this, InstallInstalling.class);
      ......
      startActivity(newIntent);
      finish();
    }
    

上面的逻辑是跳转到InstallInstalling界面进行安装,我们看看这个Activity:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    ......
    if ("package".equals(mPackageURI.getScheme())) {
        // 直接安装 package
        try {
            getPackageManager().installExistingPackage(appInfo.packageName);
            launchSuccess();
        } catch (PackageManager.NameNotFoundException e) {
            launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
        }
    } else {
        // 对于 file 安装,按照session的逻辑去走
        // 这部分在onResume中开始
        final File sourceFile = new File(mPackageURI.getPath());
        ......
        if (savedInstanceState != null) {
            mSessionId = savedInstanceState.getInt(SESSION_ID);
            mInstallId = savedInstanceState.getInt(INSTALL_ID);
            ......
        } else {
            ......
            mSessionId = getPackageManager().getPackageInstaller().createSession(params);
            ......
            // 创建应用安装状态的Observer
            // 真正注册是在后面 Session 的 commitLocked 中
            // InstallEventReceiver 这里是做了二次封装,方便进行持久化操作
            InstallEventReceiver.addObserver(this, mInstallId,
                        this::launchFinishBasedOnResult);
        }
        ......
    }
}
@Override
protected void onResume() {
    super.onResume();
    // This is the first onResume in a single life of the activity
    if (mInstallingTask == null) {
        PackageInstaller installer = getPackageManager().getPackageInstaller();
        PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
        // 启动异步安装任务 InstallingAsyncTask 进行一系列的session操作
        if (sessionInfo != null && !sessionInfo.isActive()) {
            mInstallingTask = new InstallingAsyncTask();
            mInstallingTask.execute();
        }
        ......
    }
}

整体过程如下:

  • onCreate 中通过 PackageInstaller 创建Session 并返回 mSessionId
  • onResume 中开启InstallingAsyncTask,把包信息写入mSessionId对应的session,然后提交。
  • onCreate 中添加了 安装结果的监听,在收到安装结果的广播后 会调用此 跳转到对应结果页面。

其中核心步骤就是InstallingAsyncTask任务中的Session系列操作:一个应用完整的安装过程从这里开始

private final class InstallingAsyncTask extends AsyncTask<Void, Void,
        PackageInstaller.Session> {
    volatile boolean isDone;
    @Override
    protected PackageInstaller.Session doInBackground(Void... params) {
        PackageInstaller.Session session;
        ......
        session = getPackageManager().getPackageInstaller().openSession(mSessionId);
        try {
            File file = new File(mPackageURI.getPath());
            try (InputStream in = new FileInputStream(file)) {
                long sizeBytes = file.length();
                try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
                    byte[] buffer = new byte[1024 * 1024];
                    while (true) {
                        ......
                        out.write(buffer, 0, numRead);
                        ......
                    }
                }
            }
            return session;
        }
        ......
    }
    @Override
    protected void onPostExecute(PackageInstaller.Session session) {
        if (session != null) {
            ......
            session.commit(pendingIntent.getIntentSender());
            ......
        } else {
            getPackageManager().getPackageInstaller().abandonSession(mSessionId);
            ......
    }
}
  • 任务中做的第一个事情是:根据APK的Uri,将APK的信息通过IO流的形式写入到PackageInstaller.Session中
  • 第二个事情就是PackageInstallerSession.commit()函数

session这个东西看来很重要,那么我们先来看看PackageInstallerSession

Android通过PackageInstallerSession来表示一次安装过程,一个PackageInstallerSession包含一个系统中唯一的一个SessionId,如果一个应用的安装必须分几个阶段来完成,即使设备重启了,也可以通过这个ID来继续安装过程

PackageInstallerSession中保存了应用安装的相关数据,例如,安装包的路径、安装的进度、中间数据保存的目录等。

PackageInstallerService提供了接口createSession来创建一个Session对象:

@Override
public int createSession(SessionParams params, String installerPackageName, int userId) {
    try {
        return createSessionInternal(params, installerPackageName, userId);
    } catch (IOException e) {
        throw ExceptionUtils.wrap(e);
    }
}

createSession()方法将返回一个系统唯一值作为Session ID。如果希望再次使用这个Session,可以通过接口openSession()打开它。openSession()方法的代码如下所示:

@Override
public IPackageInstallerSession openSession(int sessionId) {
    try {
        return openSessionInternal(sessionId);
    } catch (IOException e) {
        throw ExceptionUtils.wrap(e);
    }
}

openSession()方法将返回一个IPackageInstallerSession对象,它是Binder服务PackageInstallerSession的IBinder对象。在PackageInstallerService中mSessions数组保存了所有PackageInstallerSession对象,定义如下:

@GuardedBy("mSessions")
private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();

当PackageManagerService初始化时会创建PackageInstallerService服务,在这个服务的初始化函数中会读取/data/system目录下的install_sessions.xml文件,这个文件中保存了系统中未完成的Install Session。然后PackageInstallerService会根据文件的内容创建PackageInstallerSession对象并插入到mSessions中。
而对于上面提到的commit()函数,是真正触发PMS安装的函数,定义如下:

@Override
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
    ......
    mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
    ......
}
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            ......
            case MSG_COMMIT:
                synchronized (mLock) {
                    ......
                    commitLocked();
                    ......
                }

                break;
            ......
        }
        return true;
    }
};
private void commitLocked()
        throws PackageManagerException {
    ......
    mRemoteObserver.onUserActionRequired(intent);
    ......
    mPm.installStage(mPackageName, stageDir, localObserver, params,
            mInstallerPackageName, mInstallerUid, user, mSigningDetails);
}

最后,安装过程走到了 PKMS 的 installStage()。

目前为止,只是通过 PackageInstaller 维持了安装 Session,把安装包写入到 Session中,真正的安装过程是 PMS 来执行。

2. 安装开始

2.1 复制安装文件

我们已经知道PackageInstallerSession通过调用PMS的installStage()方法开启了安装第一阶段,不过整个安装过程比较复杂,大致流程如图(高版本的系统源码没有DefaultContainerService这个类,拷贝操作均在FileInstallArgs中完成):

image.png

installStage()做了如下几件事情:

  • 创建了一个InstallParams对象

    • InstallParams是安装过程中的主要数据结构
    • 一般情况,安装应用的时间通常比较长,因此Android把安装过程拆分,把调用过程的参数数据保存到InstallParams中
  • 创建并发送了一个Message消息

    • msg.what为INIT_COPY
    • msg.obj为InstallParams对象

代码如下:

    void installStage(ActiveInstallSession activeInstallSession) {
        if (DEBUG_INSTANT) {
            if ((activeInstallSession.getSessionParams().installFlags
                    & PackageManager.INSTALL_INSTANT_APP) != 0) {
                Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName());
            }
        }
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        final InstallParams params = new InstallParams(activeInstallSession);
        params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
        msg.obj = params;

        Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installStage",
                System.identityHashCode(msg.obj));
        Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                System.identityHashCode(msg.obj));

        mHandler.sendMessage(msg);
    }

经过一系列的调用,最终会调用到InstallParams的handleStartCopy(),这里面代码很长,这里主要做了如下几件事:

  • 首先通过getMinimalPackageInfo()方法确认是否还有足够的安装空间,如果安装空间不够,会通过mInstaller.freeCache()来释放一部分
  • 接下来通过createInstallArgs()创建InstallArgs对象,createInstallArgs()方法中会对InstallParams的move成员变量进行判断,决定后续调用FileInstallArgs还是MoveInstallArgs,我们这里的流程是调用FileInstallArgs。
  • 接下来对apk进行校验,这个校验过程是通过发送Intent.ACTION_PACKAGE_NEEDS_VERIFICATION)广播给系统中所有可以接收该Intent的应用来完成的

接下来调用handleReturnCode,这里面会调用InstallArgs的copyApk(),其具体实现在FileInstallArgs里面。最终会调用到doCopyApk(),完成APK的拷贝操作,代码如下:

private int doCopyApk() {
    if (origin.staged) {
        if (DEBUG_INSTALL) Slog.d(TAG, origin.file + " already staged; skipping copy");
        codeFile = origin.file;
        resourceFile = origin.file;
        return PackageManager.INSTALL_SUCCEEDED;
    }

    try {
        final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
        final File tempDir =
                mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
        codeFile = tempDir;
        resourceFile = tempDir;
    } catch (IOException e) {
        Slog.w(TAG, "Failed to create copy file: " + e);
        return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
    }

    //这里拷贝APK文件到data/app下
    int ret = PackageManagerServiceUtils.copyPackage(
            origin.file.getAbsolutePath(), codeFile);
    if (ret != PackageManager.INSTALL_SUCCEEDED) {
        Slog.e(TAG, "Failed to copy package");
        return ret;
    }

    final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
    NativeLibraryHelper.Handle handle = null;
    try {
        //这里APK里面的so库
        handle = NativeLibraryHelper.Handle.create(codeFile);
        ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
                abiOverride);
    } catch (IOException e) {
        Slog.e(TAG, "Copying native libraries failed", e);
        ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
    } finally {
        IoUtils.closeQuietly(handle);
    }

    return ret;
}

执行完copyApk()方法后,安装第一阶段的工作就完成了,应用拷贝到了/data/app目录下。我们接下来看看handleReturnCode()中processPendingInstall()的内容,也就是第二阶段。

2.2 装载应用

装载应用从handleReturnCode()中的processPendingInstall()方法开始,代码如下:

private void processInstallRequestsAsync(boolean success,
        List<InstallRequest> installRequests) {
    mHandler.post(() -> {
        if (success) {
            for (InstallRequest request : installRequests) {
                request.args.doPreInstall(request.installResult.returnCode);
            }
                        //这里开始解析APK
            synchronized (mInstallLock) {
                installPackagesTracedLI(installRequests);
            }
            for (InstallRequest request : installRequests) {
                request.args.doPostInstall(
                        request.installResult.returnCode, request.installResult.uid);
            }
        }
        for (InstallRequest request : installRequests) {
            restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,
                    new PostInstallData(request.args, request.installResult, null));
        }
    });
}

核心方法为installPackagesTracedLI(),它最终会调用到installPackagesLI(),代码如下:

private void installPackagesLI(List<InstallRequest> requests) {
    ...
    try {
        for (InstallRequest request : requests) {
            final PrepareResult prepareResult;
            //1.准备:分析当前安装状态,解析包并初始验证
            prepareResult = preparePackageLI(request.args, request.installResult);
            ...
            try {
                //2.扫描:根据准备阶段解析的包信息上下文 进一步解析
                final ScanResult result = scanPackageTracedLI(prepareResult.packageToScan, prepareResult.parseFlags,prepareResult.scanFlags, System.currentTimeMillis(),request.args.user, request.args.abiOverride);
                ...
                //注册appId
                createdAppId.put(packageName, optimisticallyRegisterAppId(result));
               //保存version信息
                versionInfos.put(result.pkgSetting.pkg.getPackageName(),getSettingsVersionForPackage(result.pkgSetting.pkg));
            }
            ...
        }
        ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,installResults,prepareResults,mSharedLibraries,Collections.unmodifiableMap(mPackages), versionInfos,lastStaticSharedLibSettings);
        CommitRequest commitRequest = null;
        synchronized (mLock) {
            Map<String, ReconciledPackage> reconciledPackages;
            try {
                //3.核对:验证扫描后的包信息和系统状态,确保安装成功
                reconciledPackages = reconcilePackagesLocked(
                        reconcileRequest, mSettings.mKeySetManagerService);
            } catch (ReconcileFailure e) {
                for (InstallRequest request : requests) {
                    request.installResult.setError("Reconciliation failed...", e);
                }
                return;
            } 
            try {
                //4.提交:提交扫描的包、更新系统状态。这是唯一可以修改系统状态的地方,并且要对所有可预测的错误进行检测。
                commitRequest = new CommitRequest(reconciledPackages,
                        mUserManager.getUserIds());
                commitPackagesLocked(commitRequest);
                success = true;
            }
        }
        //安装成功的后续才做:准备app数据、编译布局资源、执行dex优化
        executePostCommitSteps(commitRequest);
    } ...
}

安装过程分四个阶段:

  • 准备,分析当前安装状态,解析包 并初始校验: 在 preparePackageLI() 内使用 PackageParser.parsePackage() 解析AndroidManifest.xml,获取四大组件等信息;使用ParsingPackageUtils.getSigningDetails() 解析签名信息;重命名包最终路径 等。这里的代码量特别大,尤其是解析AndroidManifest.xml文件,涉及到大量的标签解析,所以就不展开了
  • 扫描,根据准备阶段解析的包信息上下文调用scanPackageTracedLI() 进一步解析, 确认包名真实;根据解析出的信息校验包有效性(是否有签名信息等);搜集apk信息——PackageSetting、apk的静态库/动态库信息等。
  • 核对,验证扫描后的包信息,确保安装成功:主要就是覆盖安装的签名匹配验证。
  • 提交,提交扫描的包、更新系统状态:添加 PackageSetting 到 PMS 的 mSettings、添加 AndroidPackage 到 PMS 的 mPackages 、添加 秘钥集 到系统、应用的权限添加到 mPermissionManager、四大组件信息添加到 mComponentResolver 。这是唯一可以修改系统状态的地方,并且要对所有可预测的错误进行检测。

前三步主要是 解析和校验,第四步是把 包信息 提交到 PKMS 内存数据结构中。 其中解析和提交在之前的PMS初始化中 扫描apk目录后也是同样的过程。

将APK信息提交到PKMS后,调用了 executePostCommitSteps() 准备app数据、执行dex优化:

private void executePostCommitSteps(CommitRequest commitRequest) {
    ...
    for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) {
        ...
        //提供目录结构/data/user/用户ID/包名/cache(/data/user/用户ID/包名/code_cache)
        prepareAppDataAfterInstallLIF(pkg);
        ...
        //是否要dex opt
        final boolean performDexopt =
                (!instantApp || Global.getInt(mContext.getContentResolver(),
                Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
                && !pkg.isDebuggable()
                && (!onIncremental);

        if (performDexopt) {
            ...
            DexoptOptions dexoptOptions = new DexoptOptions(packageName,REASON_INSTALL,flags);
            ...
            //执行dex优化:dexopt 是对 dex 文件 进行 verification 和 optimization 的操作,其对 dex 文件的优化结果变成了 odex 文件,这个文件和 dex 文件很像,只是使用了一些优化操作码(譬如优化调用虚拟指令等)。
            mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
                    null /* instructionSets */,
                    getOrCreateCompilerPackageStats(pkg),
                    mDexManager.getPackageUseInfoOrDefault(packageName),
                    dexoptOptions);
        }...
    }
}

这里的prepareAppDataAfterInstallLIF和performDexOpt操作均由 Installer 对应的方法来操作,而 Installer 继承自 SystemService 也是一个系统服务,它就是installd。这两个操作需要installd来完成是因为它们需要root权限,而PKMS只有System权限。

到这里安装完成,再回到 PMS 的 processInstallRequestsAsync(),最后调用restoreAndPostInstall()进行 备份、可能的回滚、发送安装完成相关广播,更新Launcher的UI。