Related articles Android package management mechanism series
preface
PackageInstaller installs APK and sends the APK information to the PMS. So how does PMS handle this? This article will give you the answer.
1.PackageHandler processes installation messages
After APK information is handed over to the PMS, the PMS sends messages to the PackageHandler to drive APK replication and installation. Let’s start by looking at the sequence diagram of how the PackageHandler calls the installation messages.
Continue with the code logic from the previous article to look at PMS’s installStage methods. frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
void installStage(String packageName, File stagedDir, String stagedCid,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
String installerPackageName, int installerUid, UserHandle user,
Certificate[][] certificates) {...final Message msg = mHandler.obtainMessage(INIT_COPY);/ / 1
final int installReason = fixUpInstallReason(installerPackageName, installerUid,
sessionParams.installReason);
final InstallParams params = new InstallParams(origin, null, observer,
sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
verificationInfo, user, sessionParams.abiOverride,
sessionParams.grantedRuntimePermissions, certificates, installReason);/ / 2
params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params)); msg.obj = params; . mHandler.sendMessage(msg);/ / 3
}
Copy the code
Create InstallParams in comment 2, which corresponds to the package’s installation data. A message of type INIT_COPY is created in comment 1, and InstallParams is sent via message in comment 3.
1.1 Handling of INIT_COPY messages
The code for handling messages of type INIT_COPY is shown below. frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#PackageHandler
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ":" + params);
//mBound is used to identify whether a service is bound. The default value is false
if(! mBound) {/ / 1
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
System.identityHashCode(mHandler));
// If the service is not bound, rebind it. Inside the connectToService method mBound is set to true if the binding succeeds
if(! connectToService()) {/ / 2
Slog.e(TAG, "Failed to bind to media container service");
params.serviceError();
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
System.identityHashCode(mHandler));
if(params.traceMethod ! =null) {
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, params.traceMethod,
params.traceCookie);
}
// If the binding fails, return
return;
} else {
// Add the request to mpendingMouse of type ArrayList. Waits for processingmPendingInstalls.add(idx, params); }}else {
// The service has been bound
mPendingInstalls.add(idx, params);
if (idx == 0) {
mHandler.sendEmptyMessage(MCS_BOUND);/ / 3}}break; }... }}}Copy the code
PackageHandler inherits from Handler, which is defined in the PMS. The doHandleMessage method is used to handle messages of various types to see how messages of type INIT_COPY are handled. MBound in comment 1 is used to indicate whether DefaultContainerService is bound. The default value is false. DefaultContainerService is a service that checks and copies removable files. This is a time-consuming operation, so DefaultContainerService does not run in the same process as PMS. It runs in com.android. defContainer and communicates with PMS via IMediaContainerService, as shown in the figure below.
The connectToService method at note 2 is used to bind DefaultContainerService, and the MCS_BOUND message at note 3 triggers processing of the first installation request. Look at the connectToService method in comment 2: **frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#PackageHandler **
private boolean connectToService(a) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
" DefaultContainerService");
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
if (mContext.bindServiceAsUser(service, mDefContainerConn,
Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) {/ / 1
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mBound = true;/ / 2
return true;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return false;
}
Copy the code
MBound is set to true in comment 2 if DefaultContainerService is successfully bound. The bindServiceAsUser method in comment 1 passes in mDefContainerConn. The logic of the bindServiceAsUser method is similar to that of calling bindService. Once the service has established a connection, Will call onServiceConnected method: * * frameworks/base/services/core/Java/com/android/server/PM/PackageManagerService. Java * *
class DefaultContainerConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
final IMediaContainerService imcs = IMediaContainerService.Stub
.asInterface(Binder.allowBlocking(service));
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, Object));/ / 1
}
public void onServiceDisconnected(ComponentName name) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected"); }}Copy the code
Comments sent 1 MCS_BOUND types of messages, and PackageHandler. DoHandleMessage method comments 3, send the message here is with the parameters of the Object types, there will be for the two cases to explain, is a kind of news do not bring the parameters of the Object types, One is a message with a parameter of type Object.
1.2 Handling of MCS_BOUND messages
MCS_BOUND messages with no Object parameters
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
case MCS_BOUND: {
if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
if(msg.obj ! =null) {/ / 1
mContainerService = (IMediaContainerService) msg.obj;/ / 2
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
System.identityHashCode(mHandler));
}
if (mContainerService == null) {/ / 3
if(! mBound) {/ / 4
Slog.e(TAG, "Cannot bind to media container service");
for (HandlerParams params : mPendingInstalls) {
params.serviceError();/ / 5
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(params));
if(params.traceMethod ! =null) {
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER,
params.traceMethod, params.traceCookie);
}
return;
}
// Failed to bind, empty the installation request queue
mPendingInstalls.clear();
} else {
// Continue to wait for the binding service
Slog.w(TAG, "Waiting to connect to media container service"); }}else if (mPendingInstalls.size() > 0) {...else {
Slog.w(TAG, "Empty queue");
}
break;
}
Copy the code
If the message does not take an Object parameter, the conditions at comment 1 cannot be met. The mContainerService of type IMediaContainerService at comment 2 cannot be assigned, thus satisfying the conditions at Comment 3. If meet the comment 4 condition, that is not binding, and had been in PackageHandler. DoHandleMessage method 2 place call the binding service method, this is obviously not normal, so there was an error in note 5 is responsible for handling service. If the conditions in comment 4 are not met, the service is bound and the system log is printed to tell the user to wait for the system to bind the service.
Message with the parameters of the Object types frameworks/base/services/core/Java/com/android/server/PM/PackageManagerService. Java
case MCS_BOUND: {
if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
if(msg.obj ! =null) {... }if (mContainerService == null) {/ / 1. }else if (mPendingInstalls.size() > 0) {/ / 2
HandlerParams params = mPendingInstalls.get(0);/ / 3
if(params ! =null) {
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(params));
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");
if (params.startCopy()) {/ / 4
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Checking for more work or unbind...");
// If APK is installed successfully, delete the installation request
if (mPendingInstalls.size() > 0) {
mPendingInstalls.remove(0);
}
if (mPendingInstalls.size() == 0) {
if (mBound) {
// If no more requests are installed, send a request to unbind the service
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Posting delayed MCS_UNBIND");
removeMessages(MCS_UNBIND);
Message ubmsg = obtainMessage(MCS_UNBIND);
sendMessageDelayed(ubmsg, 10000); }}else {
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Posting MCS_BOUND for next work");
If there are additional installation requests, then send an MCS_BOUND message to continue processing the remaining installation requests
mHandler.sendEmptyMessage(MCS_BOUND);/ / 5
}
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}else {
Slog.w(TAG, "Empty queue");/ / 6
}
break;
}
Copy the code
If an MCS_BOUND message with an Object parameter does not satisfy the conditions in comment 1, the judgment in comment 2 is invoked. If the number of install requests does not exceed zero, the log in comment 6 is printed, indicating that the install request queue is empty. Once an APK is installed, an MSC_BOUND message is sent at comment 5 to continue processing the remaining install requests until the install request queue is empty. If HandlerParams is not null, the startCopy method of HandlerParams at comment 4 is called to start the process of copying APK.
2. Copy the APK
Let’s start by looking at the sequence diagram of the copied APK.
HandlerParams is an abstract class in PMS. Its implementation class is InstallParams, an internal class of PMS. The startCopy method for HandlerParams is shown below. frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#HandlerParams
final boolean startCopy(a) {
boolean res;
try {
if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ":" + this);
// The startCopy method is aborted after four attempts
if (++mRetries > MAX_RETRIES) {/ / 1
Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
mHandler.sendEmptyMessage(MCS_GIVE_UP);/ / 2
handleServiceError();
return false;
} else {
handleStartCopy();/ / 3
res = true; }}catch (RemoteException e) {
if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
mHandler.sendEmptyMessage(MCS_RECONNECT);
res = false;
}
handleReturnCode();/ / 4
return res;
}
Copy the code
The mRetries in comment 1 is used to record the number of times the startCopy method is called. The startCopy method is automatically incremented and the request is aborted if it is called more than four times: Delete the first install request (this one) from the Install queue mpendingMouse by sending a message of type MCS_GIVE_UP at comment 2. Note 4 deals with the logic of installing APK after copying APK, as mentioned again in section 3. Note 3 calls the abstract method handleStartCopy, which is implemented in InstallParams as shown below. frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#InstallParams
public void handleStartCopy(a) throws RemoteException {...// Determine the APK installation location. OnSd: Install to SD card, onInt: internal storage i.e. Data partition, ephemeral: install to temporary storage (Instant Apps installation)
final booleanonSd = (installFlags & PackageManager.INSTALL_EXTERNAL) ! =0;
final booleanonInt = (installFlags & PackageManager.INSTALL_INTERNAL) ! =0;
final booleanephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) ! =0;
PackageInfoLite pkgLite = null;
if (onInt && onSd) {
// APK cannot be installed on both SD card and Data partition
Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
Instant Apps cannot be installed on the SD card
} else if (onSd && ephemeral) {
Slog.w(TAG, "Conflicting flags specified for installing ephemeral on external");
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else {
// Get a small amount of information about APK
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
packageAbiOverride);/ / 1
if (DEBUG_EPHEMERAL && ephemeral) {
Slog.v(TAG, "pkgLite for install: "+ pkgLite); }...if (ret == PackageManager.INSTALL_SUCCEEDED) {
// Determine the installation position
int loc = pkgLite.recommendedInstallLocation;
if(loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) { ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; }else if(loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) { ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS; }... }else{
loc = installLocationPolicy(pkgLite);/ / 2. }}// Create InstallArgs object according to InstallParams
final InstallArgs args = createInstallArgs(this);/ / 3
mArgs = args;
if (ret == PackageManager.INSTALL_SUCCEEDED) {
...
if(! origin.existing && requiredUid ! = -1
&& isVerificationEnabled(
verifierUser.getIdentifier(), installFlags, installerUid)) {
...
} else{
ret = args.copyApk(mContainerService, true);/ / 4
}
}
mRet = ret;
}
Copy the code
The handleStartCopy method has a lot of code, and here are the key sections. Note 1 calls the getMinimalPackageInfo method of DefaultContainerService across processes through IMediaContainerService, which parses APK lightly and gets a small amount of APK information. The reason for light parsing is that you don’t need to get all the APK information; the small amount of APK information is encapsulated in PackageInfoLite. Next determine the location of the APK installation in comment 2. Note 3 creates InstallArgs, an abstract class that defines the installation logic of APK, such as copying and renaming APK. InstallArgs has three subclasses, all defined in PMS, as shown in the figure below.
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#FileInstallArgs
private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {...try {
final booleanisEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) ! =0;
// Create a temporary file storage directory
final File tempDir =
mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);/ / 1
codeFile = tempDir;
resourceFile = tempDir;
} catch (IOException e) {
Slog.w(TAG, "Failed to create copy file: " + e);
returnPackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; }...int ret = PackageManager.INSTALL_SUCCEEDED;
ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);/ / 2.return ret;
}
Copy the code
Note 1 is used to create temporary storage directory, such as/data/app/vmdl18300388. TMP, 18300388 of them were installed sessionId. Note 2 calls the copyPackage method of DefaultContainerService across processes through IMediaContainerService. This method copies the APK to the temporary storage directory in the process where the DefaultContainerService is located. Such as the/data/app/vmdl18300388. TMP/base. Apk. Now that the APK copy is complete, the APK installation process is ready.
3. Install the APK
As usual, we’ll start with a sequence diagram of the APK installation.
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
void handleReturnCode(a) {
if(mArgs ! =null) { processPendingInstall(mArgs, mRet); }}private void processPendingInstall(final InstallArgs args, final int currentStatus) {
mHandler.post(new Runnable() {
public void run(a) {
mHandler.removeCallbacks(this);
PackageInstalledInfo res = new PackageInstalledInfo();
res.setReturnCode(currentStatus);
res.uid = -1;
res.pkg = null;
res.removedInfo = null;
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
// Pre-installation processing
args.doPreInstall(res.returnCode);/ / 1
synchronized (mInstallLock) {
installPackageTracedLI(args, res);/ / 2
}
// Finish after installation
args.doPostInstall(res.returnCode, res.uid);/ / 3}... }}); }Copy the code
In the handleReturnCode method, only the processPendingInstall method is called. Note 1 is used to check the status of APK and ensure that the installation environment is reliable before installation. If not, the copied APK files will be cleared. Delete the installation related directories and files. Focus on the installPackageTracedLI method in comment 2, which internally calls the installPackageLI method of PMS. frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {... PackageParser pp =new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setDisplayMetrics(mMetrics);
pp.setCallback(mPackageParserCallback);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final PackageParser.Package pkg;
try {
/ / parsing APK
pkg = pp.parsePackage(tmpPackageFile, parseFlags);/ / 1
} catch (PackageParserException e) {
res.setError("Failed parse during installPackageLI", e);
return;
} finally{ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); }... pp =null;
String oldCodePath = null;
boolean systemApp = false;
synchronized (mPackages) {
// Check whether the APK exists
if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) ! =0) {
String oldName = mSettings.getRenamedPackageLPr(pkgName);// Get the package name before it was renamed
if(pkg.mOriginalPackages ! =null
&& pkg.mOriginalPackages.contains(oldName)
&& mPackages.containsKey(oldName)) {
pkg.setPackageName(oldName);/ / 2
pkgName = pkg.packageName;
replace = true;// Set the flag bit to indicate a replacement installation
if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName="
+ oldName + " pkgName="+ pkgName); }... } PackageSetting ps = mSettings.mPackages.get(pkgName);// Check whether the Settings contains information about the APK to install, and if so, get the signature information
if(ps ! =null) {/ / 3
if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
PackageSetting signatureCheckPs = ps;
if (pkg.applicationInfo.isStaticSharedLibrary()) {
SharedLibraryEntry libraryEntry = getLatestSharedLibraVersionLPr(pkg);
if(libraryEntry ! =null) { signatureCheckPs = mSettings.getPackageLPr(libraryEntry.apk); }}// Check whether the signature is correct
if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
if(! checkUpgradeKeySetLP(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--) {
// Iterate over each permission to process the permissionPackageParser.Permission perm = pkg.permissions.get(i); BasePermission bp = mSettings.mPermissions.get(perm.info.name); }}}if (systemApp) {
if (onExternal) {
// System APP cannot be replaced on SD card
res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
"Cannot install updates to system apps on sdcard");
return;
} else if (instantApp) {
// System APP cannot be replaced by Instant APP
res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
"Cannot update a system app with an instant app");
return; }}...// Rename temporary files
if(! args.doRename(res.returnCode, pkg, oldCodePath)) {/ / 4
res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
return;
}
startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
"installPackageLI")) {
if (replace) {/ / 5
// Replace the installation. replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user, installerPackageName, res, args.installReason); }else {
// Install new APKinstallNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES, args.user, installerPackageName, volumeUuid, res, args.installReason); }}synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(pkgName);
if(ps ! =null) {
// Update the user to which the application belongs
res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
ps.setUpdateAvailable(false /*updateAvailable*/); }... }}Copy the code
The installPackageLI method has nearly 500 lines of code, and here is the main excerpt that does a few things:
- Create PackageParser to parse the APK.
- Check if the APK exists. If it does, get the Package name that was not previously renamed and assign it to a PKG of type PackageParser.Package at comment 1.
- In comment 3, if the Settings contains information about the APK to be installed, it indicates that the APK has been installed before. Therefore, you need to verify the APK signature information to ensure safe replacement.
- In April to a temporary file renaming, such as the aforementioned/data/app/vmdl18300388. TMP/base. The apk, renamed/data/app/package name – 1 / base. Apk. The newly named package name will have a suffix 1, which will add up every time you upgrade an existing App.
- There are two limitations in the update installation of system APP. One is that system APP cannot be replaced and installed on the SD card, and the other is that system APP cannot be replaced by Instant APP.
- ReplacePackageLIF is called in the case of a replacement installation. It also distinguishes between system APP and non-system APP. InstallNewPackageLIF is called in the case of a newly installed APK.
Here we use the newly installed APK as an example, calling the installNewPackageLIF method of PMS. frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
int scanFlags, UserHandle user, String installerPackageName, String volumeUuid,
PackageInstalledInfo res, int installReason) {...try {
/ / scan the APK
PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags,
System.currentTimeMillis(), user);
// Update Settings information
updateSettingsLI(newPackage, installerPackageName, null, res, user, installReason);
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
// After successful installation, prepare data for the newly installed application
prepareAppDataAfterInstallLIF(newPackage);
} else {
// If the installation fails, delete the APK
deletePackageLIF(pkgName, UserHandle.ALL, false.null,
PackageManager.DELETE_KEEP_DATA, res.removedInfo, true.null); }}catch (PackageManagerException e) {
res.setError("Package couldn't be installed in " + pkg.codePath, e);
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
Copy the code
InstallNewPackageLIF does three things:
- Scan the APK and store the APK information in newPackage of type PackageParser.Package. The information of a Package contains 1 Base APK and 0 or more split APKs.
- Update the Settings of the APK. Settings is used to save the dynamic Settings of all packages.
- Prepare the data for the newly installed application on success, and remove the APK on failure.
This is the APK installation process, no further analysis, interested students can continue to dig.
4. To summarize
This article mainly explains how PMS handles APK installation. There are several steps:
- When the PackageInstaller installs the APK, the APK information is processed by the PMS. The PMS sends messages to the PackageHandler to drive the APK replication and installation.
- The PMS sends messages of type INIT_COPY and MCS_BOUND, and controls PackageHandler to bind DefaultContainerService and copy APK.
- After the APK is copied, the APK installation process starts, including pre-installation check, APK installation, and post-installation finishing work.
Android Package Management Mechanism Android App Installation process analysis (based on Nougat) Application installation process
Here we share not only Android, Java and mobile front-end technologies, but also industry trends, technical information, experience and personal insights. Every month will strive to send book benefits to everyone.