Today we’ll take a look at how the system installs apps on your phone.

The entrance

When we click on an APK to install, the following screen pops up

This is a package installation program provided by the Android Framework. The page is PackageInstallerActivity

package com.android.packageinstaller; public class PackageInstallerActivity extends Activity { public void onClick(View v) { if (v == mOk) { if (mOk.isEnabled()) { //... Omit some details startInstall(); Else if (v == mCancel) {// Cancel and finish}} private void startInstall() {// Start subactivity to actually install the application Intent newIntent = new Intent(); newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo); newIntent.setData(mPackageURI); newIntent.setClass(this, InstallInstalling.class); . // newIntent. PutExtra other arguments startActivity(newIntent); finish(); }}Copy the code

When you click install on this page, the installation package information is passed to the InstallInstalling Activity through the Intent. InstallInstalling mainly sends the package information to the PMS and handles the callback.

InstallInstalling.onCreate

So if I go to a new page, of course I’m going to start with onCreate

protected void onCreate(@Nullable Bundle savedInstanceState) { ApplicationInfo appInfo = getIntent() .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); mPackageURI = getIntent().getData(); . // Create a corresponding File according to mPackageURI final File sourceFile = new File(mpackageuri.getPath ()); / / display application icon, the application name, or the package name PackageUtil. InitSnippetForNewApp (this, PackageUtil getAppSnippet (this, appInfo, sourceFile), R.id.app_snippet); // create SessionParams, . It is used to carry the parameters of the session PackageInstaller SessionParams params = new PackageInstaller. SessionParams ( PackageInstaller.SessionParams.MODE_FULL_INSTALL); params.installFlags = PackageManager.INSTALL_FULL_APP; . params.installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME); File file = new File(mPackageURI.getPath()); // Lightweight parsing of APK, And the analytic results assigned to PackageParser SessionParams related field. PackageLite PKG = PackageParser. ParsePackageLite (file, 0); params.setAppPackageName(pkg.packageName); params.setInstallLocation(pkg.installLocation); params.setSize( PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride)); // InstallEventReceiver is a BroadcastReceiver, Can be installed by EventResultPersister to receive all the events / / here will give this callback: : launchFinishBasedOnResult mInstallId = InstallEventReceiver .addObserver(this, EventResultPersister.GENERATE_NEW_ID, this::launchFinishBasedOnResult); Try {// PackageInstaller createSession // the IPackageInstaller communicates with PackageInstallerService. // The PackageInstallerService createSession method is called to create and return mSessionId mSessionId = getPackageManager().getPackageInstaller().createSession(params); } catch (IOException e) { launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); }}Copy the code

InstallInstalling.onResume

Next up is onResume, which does some asynchronous work with InstallingAsyncTask

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); if (sessionInfo ! = null && ! sessionInfo.isActive()) { mInstallingTask = new InstallingAsyncTask(); mInstallingTask.execute(); } else { // we will receive a broadcast when the install is finished mCancelButton.setEnabled(false); setFinishOnTouchOutside(false); }}}Copy the code

InstallingAsyncTask

private final class InstallingAsyncTask extends AsyncTask<Void, Void, PackageInstaller.Session> { @Override protected PackageInstaller.Session doInBackground(Void... params) { PackageInstaller.Session session; session = getPackageManager().getPackageInstaller().openSession(mSessionId); session.setStagingProgress(0); File file = new File(mPackageURI.getPath()); OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes) InputStream in = new FileInputStream(file) long sizeBytes = file.length(); byte[] buffer = new byte[1024 * 1024]; while (true) { int numRead = in.read(buffer); if (numRead == -1) { session.fsync(out); break; Out. write(buffer, 0, numRead); if (sizeBytes > 0) { float fraction = ((float) numRead / (float) sizeBytes); session.addProgress(fraction); } } return session; } @Override protected void onPostExecute(PackageInstaller.Session session) { Intent broadcastIntent = new Intent(BROADCAST_ACTION); broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); broadcastIntent.setPackage( getPackageManager().getPermissionControllerPackageName()); broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId); PendingIntent pendingIntent = PendingIntent.getBroadcast( InstallInstalling.this, mInstallId, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT); / / call PackageInstaller Session commit method, install session.com MIT (pendingIntent. GetIntentSender ()); }}Copy the code

Take a look at the implementation in PackageInstaller.Session

public static class Session implements Closeable { private IPackageInstallerSession mSession; public void commit(@NonNull IntentSender statusReceiver) { try { mSession.commit(statusReceiver, false); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); }}}Copy the code

The mSession type is IPackageInstallerSession, which means that IPackageInstallerSession is used to communicate between processes. The PackageInstallerSession commit method is called. After executing this class, you will enter the famous PMS to perform the installation:

public class PackageInstallerSession extends IPackageInstallerSession.Stub { public void commit(@NonNull IntentSender statusReceiver, Boolean forTransfer) {/ / package information encapsulation for PackageInstallObserverAdapter final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter( mContext, statusReceiver, sessionId, isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(), userId); mRemoteObserver = adapter.getBinder(); 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: commitLocked(); break; }}}; private final PackageManagerService mPm; private void commitLocked() throws PackageManagerException { mPm.installStage(mPackageName, stageDir, ...) ; }}Copy the code

MPm is the system service PackageManagerService. The installStage method officially begins the apK installation process. This process involves two steps: copying the installation package and loading the code

Copying the Installation Package

Continue with the installStage code

// PackageManagerService.java void installStage(String packageName, File stagedDir,...) { final Message msg = mHandler.obtainMessage(INIT_COPY); // add the sessionParams installation information passed in earlier, Final InstallParams Params = new InstallParams(Origin, NULL, observer, sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid, verificationInfo, user, sessionParams.abiOverride, sessionParams.grantedRuntimePermissions, signingDetails, installReason); mHandler.sendMessage(msg); }Copy the code

The message sent INIT_COPY is known by name to initialize the copy

class PackageHandler extends Handler { void doHandleMessage(Message msg) { switch (msg.what) { case INIT_COPY: { HandlerParams params = (HandlerParams) msg.obj; // Call the connectToService method to connect to the apK installation Service. if (! connectToService()) { return; } else { // Once we bind to the service, the first // pending request will be processed. mPendingInstalls.add(idx, params); }}}} private Boolean connectToService() {// Bind Service with an implicit Intent, DefaultContainerService Intent Service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); if (mContext.bindServiceAsUser(service, mDefContainerConn, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) { mBound = true; return true; } return false; }}Copy the code

When the binding Service succeeds, a Message for the binding operation is sent to mDefContainerConn’s onServiceConnection method, as shown below:

class DefaultContainerConnection implements ServiceConnection { public void onServiceConnected(ComponentName name, IBinder service) { final IMediaContainerService imcs = IMediaContainerService.Stub .asInterface(Binder.allowBlocking(service)); mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs)); + + + + + + + + + + + + + + + if (params.startCopy()) { if (mPendingInstalls.size() > 0) { mPendingInstalls.remove(0); }}}Copy the code

Mpendingmouse is a waiting queue that stores all the HandlerParams parameters (add) that you will install with apK. Extract the first HandlerMouse object to be installed from MpendingMouse and call its startCopy method. From the startCopy method, an abstract method handleStartCopy will be used to handle the install request. From our previous analysis, we know that HandlerParams is actually of type InstallParams, so we end up calling InstallParams’ handlerStartCopy method, which is the core of the installation package copy.

class InstallParams extends HandlerParams { public void handleStartCopy() throws RemoteException { if (origin.staged) { // If (origine.file! = null) { installFlags |= PackageManager.INSTALL_INTERNAL; installFlags &= ~PackageManager.INSTALL_EXTERNAL; Final Boolean onSd = (installFlags & Packagemanager.install_external)! = 0; final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) ! = 0; final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) ! = 0; final InstallArgs args = createInstallArgs(this); / /... ret = args.copyApk(mContainerService, true); } private InstallArgs createInstallArgs(InstallParams params) { if (params.move ! = null) { return new MoveInstallArgs(params); } else { return new FileInstallArgs(params); }}}Copy the code

Normally, createInstallArgs returns the FileInstallArgs object

FileInstallArgs copyApk method

int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException { return doCopyApk(imcs, temp); } private int doCopyApk(IMediaContainerService IMCS, Boolean temp) throws RemoteException {// Create the target path to store the installation package. Is actually the/data/app/final application package name directory File tempDir. = mInstallerService allocateStageDirLegacy (volumeUuid isEphemeral); final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() { @Override public ParcelFileDescriptor open(String name, int mode) throws RemoteException { final File file = new File(codeFile, name); final FileDescriptor fd = Os.open(file.getAbsolutePath(), O_RDWR | O_CREAT, 0644); Os.chmod(file.getAbsolutePath(), 0644); return new ParcelFileDescriptor(fd); }}; // Call the copyPackage method of the service to copy the installation package apk to the target path; ret = imcs.copyPackage(origin.file.getAbsolutePath(), target); // Copy the dynamic library in apk to the target path as well. ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot, abiOverride); }Copy the code

The IMediaContainerService imCS is the DefaultContainerService that was connected earlier

DefaultContainerService

The copyPackage method is essentially an IO stream operation, as follows:

// new IMediaContainerService.Stub()
public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
    PackageLite pkg = null;
    final File packageFile = new File(packagePath);
    pkg = PackageParser.parsePackageLite(packageFile, 0);
    return copyPackageInner(pkg, target);
}

// DefaultContainerService
private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target){
    copyFile(pkg.baseCodePath, target, "base.apk");
    if (!ArrayUtils.isEmpty(pkg.splitNames)) {
        for (int i = 0; i < pkg.splitNames.length; i++) {
            copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk");
        }
    }

    return PackageManager.INSTALL_SUCCEEDED;
}

private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName){
    InputStream in = null;
    OutputStream out = null;
    try {
        in = new FileInputStream(sourcePath);
        out = new ParcelFileDescriptor.AutoCloseOutputStream(
                target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE));
        FileUtils.copy(in, out);
    } finally {
        IoUtils.closeQuietly(out);
        IoUtils.closeQuietly(in);
    }
}
Copy the code

The final installation package is saved as base.apk in the data/app directory, and the installation package copy is complete.

Loading code

With the installation package copied, the installation is ready to begin. The code returns to the startCopy method in the above HandlerParams:

private abstract class HandlerParams { final boolean startCopy() { ... handleStartCopy(); handleReturnCode(); } } class InstallParams extends HandlerParams { @Override void handleReturnCode() { // If mArgs is null, then MCS couldn't be reached. When it // reconnects, it will try again to install. At that point, this // will succeed. if (mArgs ! = null) { processPendingInstall(mArgs, mRet); } } private void processPendingInstall(final InstallArgs args, final int currentStatus) { mHandler.post(new Runnable() { public void run() { PackageInstalledInfo res = new PackageInstalledInfo(); If (res.returnCode == Packagemanage. INSTALL_SUCCEEDED) {// The pre-installation operation checks the status of the installation package to ensure that the installation environment is normal. Clean up the copy file args.dopscandisk (res.returnCode) if the installation environment is faulty. Synchronized (mInstallLock) {// installPackageTracedLI(args, res); } args.doPostInstall(res.returnCode, res.uid); }... }}}}Copy the code

installPackageLI

Add Trace Trace to the installPackageTracedLI method, and then install by calling the installPackageLI method. This method is 600 lines long and takes some of the key code:

private void installPackageLI(InstallArgs args, PackageInstalledInfo res) { ... PackageParser pp = new PackageParser(); final PackageParser.Package pkg; // 1. parsePackage pkg = pp.parsePackage(tmpPackageFile, parseFlags); / / 2. Check the installation package signed final KeySetManagerService KSMS. = mSettings mKeySetManagerService; if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) { if (! ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) { res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + pkg.packageName + " upgrade keys do not match the " + "previously installed version"); return; Int N = pkg.permissions. Size (); for (int i = N-1; i >= 0; i--) { final PackageParser.Permission perm = pkg.permissions.get(i); . } // 4. Generate installation package Abi(Application binary interface, Try {String abiOverride = (textutils.isempty (pkg.cpuabiOverride)? args.abiOverride : pkg.cpuAbiOverride); final boolean extractNativeLibs = ! pkg.isLibrary(); derivePackageAbi(pkg, abiOverride, extractNativeLibs); } catch (PackageManagerException pme) { res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI"); return; } // 5. Freeze the APK, perform a replacement installation or a new installation,  try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags, "installPackageLI")) { if (replace) { replacePackageLIF(pkg, parseFlags, scanFlags, args.user, installerPackageName, res, args.installReason); } else { private void installNewPackageLIF((pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES, args.user, installerPackageName, volumeUuid, res, args.installReason); }} // 5. Optimize dex file (actual dex2OAT operation, Used to transform the apk dex file for oat file) if (performDexopt) {mPackageDexOptimizer. PerformDexopt (PKG, PKG. UsesLibraryFiles, null /* instructionSets */, getOrCreateCompilerPackageStats(pkg), mDexManager.getPackageUseInfoOrDefault(pkg.packageName), dexoptOptions); }... }Copy the code

Finally, let’s look at installNewPackage ElIf

private void installNewPackageLIF(PackageParser.Package pkg, final @ParseFlags int parseFlags, final @ScanFlags int scanFlags,...) {// Continue to scan and parse the APK installation package file, save the APK related information to the PMS, and create apK data directory, Packageparser. Package newPackage = scanPackageTracedLI(PKG, parseFlags, scanFlags, System.currentTimeMillis(), user); UpdateSettingsLI (newPackage, installerPackageName, NULL, res, user, installReason); If (res) returnCode = = PackageManager) INSTALL_SUCCEEDED) {/ / install then prepare APP data prepareAppDataAfterInstallLIF (newPackage); } else {// If the installation fails, DeletePackageLIF (pkgName, userHandle. ALL, false, null, Packagemanager.delete_keep_data, res.removedInfo, true, null); }}Copy the code

PrepareAppDataAfterInstallLIF there will be a series of calls

prepareAppDataAfterInstallLIF() -> prepareAppDataLIF() -> prepareAppDataLeafLIF() -> mInstaller.createAppData(...) final Installer mInstaller; private void prepareAppDataLeafLIF(...) CeDataInode = mInstaller. CreateAppData (volumeUuid, packageName, userId, Flags, appId, seInfo, app.targetSdkVersion); } public class Installer extends SystemService { ... }Copy the code

At this point, the entire APK installation process is over. In fact, after the successful installation, a broadcast ACTION_PACKAGE_ADDED will be sent indicating the successful installation of the App. The mobile desktop application registers this broadcast, and when it receives a successful application installation, it displays the APK startup icon on the desktop.

conclusion

Just click the install button on the mobile phone, but there is such a tedious process behind, I believe that through today’s learning we should be able to have a complete understanding of the system application installation process. Review the installation process as follows:

  • Click the APK installation, and the PackageInstallerActivity will start, and then the InstallInstalling two activities will show the application information
  • Click Install on the page and save the APK information to PackageInstaller.Session and upload it to PMS
  • The PMS does two things: copy the installation package and load the code
  • In the process of copying the installation package, the Service will be enabled to copyAPK, check the apK installation path and package status
  • After the copy is complete, the package name of /data/app is stored in base.apk
  • As the code is loaded, the APK continues to be parsed and the manifest file contents are stored in the PMS
  • Verify the apK signature
  • After the installation is successful, update the application setting permissions and send broadcast notifications to display APP ICONS on the desktop. If the installation fails, delete the installation package and various cache files
  • Perform dex2OAT optimization

“This article is based on Android 28”