The background,
Recently, I am learning about plug-in technology: integrate the uninstalled plug-in APK into the host App to achieve the advantages of reducing the volume of the host APK package. That is, a complete APK can be used in the host using plug-in technology without installation.
So what does the complete installation process of an APK look like? There are two types of installation that users typically experience:
- Select the target App in the mobile phone manufacturer’s application market for automatic installation;
APK is downloaded first, followed by the installation process, there is no jump to other pages during the process, can be said to be a one-click installation.
- Download APK package from browser, QQ, Douyin and other applications, click and the user confirms the installation:
This is usually the user downloads an APK file, and then manually click, jump to the installation page, prompting the download information of the APK, and then the installation package details page, showing the version number, required permissions and other information, and finally the installation page. I am using a Xiaomi mobile phone, and the corresponding Activity information is as follows, in which PermissionInfoActivity is the permission details page of clicking “Application Permission”.
com.miui.packageinstaller/com.miui.packageInstaller.NewPackageInstallerActivity: +220ms
com.miui.packageinstaller/com.miui.packageInstaller.NormalInstallReadyActivity: +97ms
com.miui.packageinstaller/com.android.packageinstaller.miui.PermissionInfoActivity: +97ms
com.miui.packageinstaller/com.miui.packageInstaller.InstallProgressActivity: +99ms
Copy the code
Found the whole installation process is performed in millet system beautiful miui “com. Beautiful miui. Packageinstaller”, so it is conceivable in native Android system also has the corresponding Activity.
The larger the APK package, the longer the installation time, because the more you have to parse and validate. Either way, the installation is notified to the desktop application Launcher, which adds an App icon to the desktop. This is how the user feels about the installation process.
Ok, the APK installation process is over!
— there is no 😄, what is the installation process of APK? We are Android app developers, read the Source Code!
Reading source code is a pain in the ass and generally requires understanding in conjunction with other sources. And, focus on mastering the overall process.
2. Know about PackageManagerService
Let’s start with a class: PackageManagerService (PMS), the name of which may remind you of ActivityManagerService (AMS), is closely related to the four components, both of which run in the SystemServer process, The App side interacts with AMS and PMS through Binder across processes. AMS is responsible for the management of four components such as Activity, while PMS is responsible for the management of package. The full name of APK is Android Package, that is, THE role of PMS is to manage APK.
In addition to the PMS here, ActivityManagerService (AMS) and WindowManagerService (WMS) were introduced in previous articles.
2.1 Use of PMS
In normal development, we would need to obtain information about the currently installed package, such as the list of installed applications, etc., in the Activity:
PackageManager packageManager = getPackageManager();
// Get package information for all installed programs
List<PackageInfo> packages = packageManager.getInstalledPackages(0);
Copy the code
GetPackageManager () is the Context method, which is implemented in ContextImpl:
@Override
public PackageManager getPackageManager(a) {
if(mPackageManager ! =null) {
return mPackageManager;
}
final IPackageManager pm = ActivityThread.getPackageManager();
final IPermissionManager permissionManager = ActivityThread.getPermissionManager();
if(pm ! =null&& permissionManager ! =null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm, permissionManager));
}
return null;
}
Copy the code
Note that access to the PackageManager actually ApplicationPackageManager, passed through in the constructor IPackageManager, IPermissionManager ActivityThread acquired. And check ApplicationPackageManager, will find that the basic all methods are packaged for the incoming PM:
public List<PackageInfo> getInstalledPackagesAsUser(int flags, Int userId) {try {ParceledListSlice<PackageInfo> parceledList = mPM.getInstalledPackages(updateFlagsForPackage(flags, userId), userId); if (parceledList == null) { return Collections.emptyList(); } return parceledList.getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); }}Copy the code
We’ll look at ActivityThread. GetPackageManager () how to acquire IPackageManager:
public static IPackageManager getPackageManager() { if (sPackageManager ! = null) { return sPackageManager; } final IBinder b = ServiceManager.getService("package"); sPackageManager = IPackageManager.Stub.asInterface(b); return sPackageManager; }Copy the code
If you are familiar with AMS cross-process (IPC), you will know that this is also for IPC, that is, to get the IPackageManager on the App side of the PMS in SystemServer. In other words, the Activity gets the list of installed applications through getPackageManager(), and finally the IPC goes to the system process and is executed in the PMS. The class diagram is as follows:
2.2 Initialization of PMS
So when is the PMS initialized? Since all apps need these system services to run, the initialization of PMS and AMS must be completed when the system is started up:
//SystemServer.java /** * The main entry point from zygote. */ public static void main(String[] args) { new SystemServer().run(); } private void run() { ... // Here we go! Slog.i(TAG, "Entered the Android system server!" ); . // Prepare the main looper thread (this thread). Looper.prepareMainLooper(); . // Start services. startBootstrapServices(t); StartCoreServices (t); // startOtherServices(t) without complex dependencies; // Other services... // Loop forever. Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }Copy the code
When the system starts up, zygote forks a SystemServer process and executes the main method above, which does a lot of initialization, including starting various services. StartBootstrapServices ():
private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) { ... // Installer Installer installer = mSystemServiceManager.startService(Installer.class); . //AMS mActivityManagerService = ActivityManagerService.Lifecycle.startService(mSystemServiceManager, atm); mActivityManagerService.setInstaller(installer); . //PMS mPackageManagerService = PackageManagerService.main(mSystemContext, installer,mFactoryTestMode ! = FactoryTest.FACTORY_TEST_OFF, mOnlyCore); . }Copy the code
AMS and PMS are enabled and Installer is used. Installer sounds like an Installer, more on that later. The main method of the PMS creates an instance of the PMS using a constructor. The constructor of the PMS is very large.
/ data/app * * and * / installed app directory private static final File sAppInstallDir = new File (Environment. GetDataDirectory (), "app"); public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest){ ... // Read the last package information in packes. XML and store the data in mSettings. mSettings.readLPw(mInjector.getUserManagerInternal().getUsers(false)); . / / scan data/app scanDirTracedLI (sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0, packageParser, the executorService); . // Update the new mSettings content to packes.xml msettings.writelpr (); . }Copy the code
packages.xml
: Records the systemInformation about all installed applications, including basic information, signatures, and permissions.mSettings
: saves PMS related Settings that will be used to parse the application.
XML file is first read, parsed and stored in mSettings, which represents the application package information of the last startup. Then scan all APK directories and parse THE APK, and finally update the packes.xml file. The packs.xml file is created in the Settings constructor.
Data /app is the directory where the user has installed the APP. In addition, system/app stores the system APP. The PMS constructor scans multiple directories including these two, which we assume is an initialization of all installed apps at boot time. ScanDirTracedLI () goes to scanDirLI().
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime, PackageParser2 packageParser, ExecutorService executorService) { ... ParallelPackageParser parallelPackageParser = new ParallelPackageParser(packageParser, executorService); / / parallel parsing all package for (the File File: files) {parallelPackageParser. Submit (File, parseFlags); }... // parse the result for (; fileCount > 0; fileCount--) { ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take(); addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags, currentTime, null); }... }Copy the code
Parses all apKs in the directory using ParallelPackageParser and processes the parseResult with addForInitLI(). ParallelPackageParser encapsulates the use of PackageParser2. APK parses go to PackageParser2’s parsePackage():
//PackageParser2.java public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches) { ... ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags); ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed(); . return parsed; }Copy the code
Here is parsingUtils ParsingPackageUtils instance, continue watching parsingUtils… parsePackage () :
//ParsingPackageUtils.java public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile,int flags){parseBaseApk() if (packagefile.isdirectory ()) {// Is the directory return parseClusterPackage(input, packageFile, flags); } else { return parseMonolithicPackage(input, packageFile, flags); }}Copy the code
Both branches go to parseBaseApk() :
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,String codePath, AssetManager assets, int flags) { ... XmlResourceParser parser = assets.openXmlResourceParser(cookie,PackageParser.ANDROID_MANIFEST_FILENAME)) { final Resources res = new Resources(assets, mDisplayMetrics, null); ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,parser, flags); . }Copy the code
Here we create an instance of XmlResourceParser for parsing APK’s Androidmanifest.xml file, and then call the overloaded parseBaseApk() :
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath, String codePath, Resources res, XmlResourceParser parser, int flags){ ... / / create instances to undertake analytic results final ParsingPackage ParsingPackage PKG. = mCallback startParsingPackage (pkgName, apkPath, codePath. manifestArray, isCoreApp); // Parses apK results into PKG, Final ParseResult<ParsingPackage> result = parseBaseApkTags(input, PKG, res, manifestArray) parser, flags); return input.success(pkg); . }Copy the code
ParseBaseApkTags () internally uses XmlResourceParser to find the Application tag in Androidmanifest.xml. ParseBaseApplication () is then called to parse the base Application node tree in APK:
private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags){ ... // Parse the Application tag attributes: Theme, AllowBackup parseBaseAppBasicFlags(PKG, sa); . while ((type = parser.next()) ! = XmlPullParser.END_DOCUMENT && (type ! = XmlPullParser.END_TAG || parser.getDepth() > depth)) { ... final ParseResult result; String tagName = parser.getName(); boolean isActivity = false; switch (tagName) { case "activity": isActivity = true; case "receiver": ParseResult<ParsedActivity> activityResult = ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg, res, parser, flags, PackageParser.sUseRoundIcon, input); if (activityResult.isSuccess()) { ParsedActivity activity = activityResult.getResult(); if (isActivity) { hasActivityOrder |= (activity.getOrder() ! = 0); pkg.addActivity(activity); / / add the Activity} else {hasReceiverOrder | = (Activity. The getOrder ()! = 0); pkg.addReceiver(activity); // Add Receiver}} result = activityResult; break; case "service": ParseResult<ParsedService> serviceResult = ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser, flags, PackageParser.sUseRoundIcon, input); if (serviceResult.isSuccess()) { ParsedService service = serviceResult.getResult(); hasServiceOrder |= (service.getOrder() ! = 0); pkg.addService(service); // Add Service} result = serviceResult; break; case "provider": ParseResult<ParsedProvider> providerResult = ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser, flags, PackageParser.sUseRoundIcon, input); if (providerResult.isSuccess()) { pkg.addProvider(providerResult.getResult()); // add Provider} result = providerResult; break; . }... }... return input.success(pkg); }Copy the code
Here is the analytical Application of child nodes, four major components, and store the information in the PKG ParsingPackage instance, finally into ParallelPackageParser. ParseResult. So back to the PMS constructor, which parses and calls addForInitLI(), this method is logically complex and has a lot of validation applied to the system (package name change, version upgrade, signer collection, and validation), but let’s just focus on the following:
//PMS.java private AndroidPackage addForInitLI(ParsedPackage parsedPackage,@ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,@Nullable UserHandle user){ ... / / continue to scan the final ScanResult ScanResult = scanPackageNewLI (parsedPackage parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user, null); if (scanResult.success) { synchronized (mLock) { boolean appIdCreated = false; try { final String pkgName = scanResult.pkgSetting.name; final Map<String, ReconciledPackage> reconcileResult = reconcilePackagesLocked(new ReconcileRequest( Collections.singletonMap(pkgName, scanResult), mSharedLibraries, mPackages, Collections.singletonMap(pkgName, getSettingsVersionForPackage(parsedPackage)), Collections.singletonMap(pkgName, getSharedLibLatestVersionSetting(scanResult))),mSettings.mKeySetManagerService); appIdCreated = optimisticallyRegisterAppId(scanResult); / / submit scan results, update the system state commitReconciledScanResultLocked (reconcileResult. Get (pkgName), mUserManager getUserIds ()); }... }}... return scanResult.pkgSetting.pkg; }Copy the code
ScanPackageNewLI () is called to continue the scanning, mainly to update the packageSetting (packageSetting mainly contains the basic information of an APP, such as installation location, lib location, etc.). Is consistent, then call reconcilePackagesLocked () call commitReconciledScanResultLocked () submit scan results, update the system state, internal calls again commitPackageSettings () :
// Add the scanned package to the system. Then the package becomes available to the system. private void commitPackageSettings(AndroidPackage pkg,@Nullable AndroidPackage oldPkg, PackageSetting pkgSetting,inal @ScanFlags int scanFlags, booleanchatty,ReconciledPackage reconciledPkg) { final String pkgName = pkg.getPackageName(); . Synchronized (mLock) {/ / added to mSettings mSettings. InsertPackageSettingLPw (pkgSetting, PKG); // Add to mPackages mpackages.put (pkg.getPackagename (), PKG); / / add the component to mComponentResolver: all four major components of the information added to the internal data structures mComponentResolver. AddAllComponents (PKG, chatty); . / / record permissions to PermissionManagerService mPermissionManager addAllPermissions (PKG, chatty); . // Add mInstrumentation for (I = 0; i < collectionSize; i++) { ParsedInstrumentation a = pkg.getInstrumentations().get(i); a.setPackageName(pkg.getPackageName()); mInstrumentation.put(a.getComponentName(), a); . }... }Copy the code
That is, the package information is recorded in the properties of the PMS. That is, after the system is started, the package information is recorded in memory managed by the PMS.
To summarize, the PMS is created and started after the system is started, and the PMS completes the scanning of all the directories with APK, parses the androidmanifest.xml of all APKS, and then further scans the APK and submits the package scanning results to the attributes of THE PMS.
The above is a simple understanding of the APK installation process at boot time. So, let’s take a look at the general APK installation process.
APK installation process
Mentioned above, the application of ordinary installation process is performed in millet system beautiful miui “com. Beautiful miui. Packageinstaller”, the installation process involves several Activity, let’s take a look at how to use the code to install the APK and And what activities are native to Android.
3.1 a
The code for installing APK is as follows:
File file = new File(getExternalFilesDir(null), "xxx.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri apkUri =FileProvider.getUriForFile(this."com.xxx.xxx.fileprovider", file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent);
Copy the code
Action is Intent. The ACTION_VIEW, type is “application/VND. Android. Package – archive”, this time using the API 30 native simulator, the Activity information is as follows:
com.google.android.packageinstaller/com.android.packageinstaller.InstallStaging: +349ms
com.google.android.packageinstaller/com.android.packageinstaller.PackageInstallerActivity: +1s516ms
com.android.settings/.Settings$ManageAppExternalSourcesActivity: +385ms
com.google.android.packageinstaller/com.android.packageinstaller.InstallInstalling: +296ms
com.google.android.packageinstaller/com.android.packageinstaller.InstallSuccess: +375ms
Copy the code
Process screenshots are as follows:
- InstallStaging, corresponding to Figure 1: Reading apK files from the Content URI
- The PackageInstallerActivity, corresponding to Figures 2 and 4, resolves Settings where APK prompts for installation from unknown sources, and displays apK icon and name and prompts for installation confirmation.
- ManageAppExternalSourcesActivity, figure 3, Settings – allows the unknown sources
- InstallInstalling, Figure 5
- InstallSuccess, Figure 6, installation complete.
Let’s start with InstallStaging:
public class InstallStaging extends AlertActivity { private @Nullable StagingAsyncTask mStagingTask; private @Nullable File mStagedFile; protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); . setupAlert(); // Display UI... } protected void onResume() {... mStagingTask = new StagingAsyncTask(); mStagingTask.execute(getIntent().getData()); }} protected void onSaveInstanceState(Bundle outState) {outState. PutString (STAGED_FILE) mStagedFile.getPath()); }... private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> { protected Boolean doInBackground(Uri... params) {... try (InputStream in = getContentResolver().openInputStream(packageUri)) { ... try (OutputStream out = new FileOutputStream(mStagedFile)) { byte[] buffer = new byte[1024 * 1024]; int bytesRead; while ((bytesRead = in.read(buffer)) >= 0) {... out.write(buffer, 0, bytesRead); . } protected void onPostExecute(Boolean success) { if (success) { // Now start the installation again from a file Intent installIntent = new Intent(getIntent()); installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class); installIntent.setData(Uri.fromFile(mStagedFile)); if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); } installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); startActivity(installIntent); / / read, after the completion of start-up DeleteStagedFileOnResult InstallStaging. Enclosing finish (); }... }}}Copy the code
The only thing it does is start AsyncTask and read the APK file, and then start the activity DeleteStagedFileOnResult, which is a springboard page that directly transacts the Intent and starts PackageInstallerActivity:
public class PackageInstallerActivity extends AlertActivity { ... private int mSessionId = -1; PackageManager mPm; IPackageManager mIpm; . PackageInstaller mInstaller; PackageInfo mPkgInfo; // PackageInfo... protected void onCreate(Bundle icicle) { ... mPm = getPackageManager(); //ApplicationPackageManager mInstaller = mPm.getPackageInstaller(); // Final Intent Intent = getIntent(); final Uri packageUri; . boolean wasSetUp = processPackageUri(packageUri); // parse APK if (! wasSetUp) { return; } // initialize UI bindUi(); / / check "unknown sources" installation, shows the installation confirmation checkIfAllowedAndInitiateInstall (); }... private boolean processPackageUri(final Uri packageUri) { mPackageURI = packageUri; final String scheme = packageUri.getScheme(); switch (scheme) { ... case ContentResolver.SCHEME_FILE: { File sourceFile = new File(packageUri.getPath()); / / parse the APK, pay attention to parameter is GET_PERMISSIONS mPkgInfo = PackageUtil. GetPackageInfo (this, sourceFile, PackageManager. GET_PERMISSIONS); / / get apk abstract: ICONS, name mAppSnippet = PackageUtil getAppSnippet (this, mPkgInfo applicationInfo, sourceFile); . } break; . } return true; } // Click install, 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); String installerPackageName = getIntent().getStringExtra( Intent.EXTRA_INSTALLER_PACKAGE_NAME); . if (installerPackageName ! = null) { newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName); } newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); startActivity(newIntent); finish(); }Copy the code
The overall logic is clear:
- Initialize mPm and mInstaller in PackageInstallerActivity onCreate(),
- Use PackageUtil. GetPackageInfo () parsing APK permission information (can be used to display to the user before installation), pay attention to parameter flags is PackageManager GET_PERMISSIONS; Using PackageUtil. GetAppSnippet () to obtain apk abstract: icon and name.
- Then use the checkIfAllowedAndInitiateInstall () is to check APK source, show “unknown sources APK installation” dialog box, after click “Settings” button to jump to the Settings page.
- Return to PackageInstallerActivity after enabling installation from unknown sources. In onActivityResult(), the prompt “Confirm installation” is displayed. Click “Install” to jump to installinstalling-start installation.
We look at the package information PackageInfo parsing process — PackageUtil. GetPackageInfo () :
//PackageUtil.java
public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) {
...
return context.getPackageManager().getPackageArchiveInfo(sourceFile.getAbsolutePath(),flags);
}
Copy the code
Continue looking at PackageManager’s getPackageArchiveInfo() :
//PackageManager.java public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,@PackageInfoFlags int flags) { ... ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset(); ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input, new File(archiveFilePath), 0, collectCertificates); . return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flags, 0, 0, null, new PackageUserState(), UserHandle.getCallingUserId()); }Copy the code
Call ParsingPackageUtils’s static method parseDefault(), which creates an instance of ParsingPackageUtils, and continue to call its parsePackage(). ParsingPackageUtils parsePackage() is also ParsingPackageUtils, except that the flags parameter is packagemanager.get_permissions, and only the permission information is parsed. There’s no more tracking in here.
Finally, the PackageInfo of all the apK information is resolved, and then click “Install” to jump to InstallInstalling.
3.2 Session: Sends packets to the PMS
InstallInstalling:
Public class InstallInstalling extends AlertActivity {... // Send package to Installer private InstallingAsyncTask mInstallingTask; Private int mSessionId; . protected void onCreate(@Nullable Bundle savedInstanceState) { ApplicationInfo appInfo = getIntent().getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); mPackageURI = getIntent().getData(); . PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL); . // Install result monitor. After receiving the installation results after the broadcast Will call this the Observer mInstallId = InstallEventReceiver. AddObserver (this, EventResultPersister.GENERATE_NEW_ID,this::launchFinishBasedOnResult); . / / the IPC: The PackageInstaller internally creates mSessionId = by going to the PackageInstallerService createSession method with IPackageInstaller getPackageManager().getPackageInstaller().createSession(params); . }}... protected void onResume() { super.onResume(); if (mInstallingTask == null) { PackageInstaller installer = getPackageManager().getPackageInstaller(); PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId); if (sessionInfo ! = null && ! Sessioninfo.isactive ()) {mInstallingTask = new InstallingAsyncTask(); mInstallingTask.execute(); }... }}... private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) { if (statusCode == PackageInstaller.STATUS_SUCCESS) { launchSuccess(); InstallSuccess} else {launchFailure(legacyStatus, statusMessage); InstallFailed}}... Installer 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); 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) { int numRead = in.read(buffer); . 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) { if (session ! = null) { Intent broadcastIntent = new Intent(BROADCAST_ACTION); broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); broadcastIntent.setPackage(getPackageName()); broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId); PendingIntent pendingIntent = PendingIntent.getBroadcast( InstallInstalling.this,mInstallId,broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT); / / package written after the session, commit session.com MIT (pendingIntent. GetIntentSender ()); mCancelButton.setEnabled(false); setFinishOnTouchOutside(false); . }}Copy the code
The overall process is as follows:
- OnCreate creates a Session with PackageInstaller and returns the mSessionId
- Start InstallingAsyncTask in onResume, write the package information to the session corresponding to mSessionId, and commit.
- OnCreate adds a listener for the installation result, which will be called to jump to the corresponding result page after receiving the broadcast of the installation result.
PackageInstaller, APK installer, which is in ApplicationPackageManager getPackageInstaller () created in:
/ / is in ApplicationPackageManager. Java public PackageInstaller getPackageInstaller () {synchronized (mLock) {if (mInstaller == null) { mInstaller = new PackageInstaller(mPM.getPackageInstaller(), mContext.getPackageName(), getUserId()); . } return mInstaller; }}Copy the code
It encapsulates the IPackageInstaller instance obtained by MPm.getPackageInstaller (), and the specific implementation of IPackageInstaller on the system server is PackageInstallerService.
And the PackageInstallerService instance is created in the PMS constructor, which reads the install_sessions. XML file in the /data/system directory. This file holds the incomplete Install Session on the system. The PMS creates the PackageInstallerSession object from the contents of the file and inserts it into the mSessions.
PackageInstaller provides the ability to install, update, and remove applications. Of course, the specific implementation is IPC in PackageInstallerService.
Session, which is an installation Session bound to mSessionId, represents an ongoing installation. Session classes is to IPackageInstaller openSession (sessionId) obtained PackageInstallerSession encapsulation (server) system. Session manages the parameters of the installation and provides the ability to temporarily copy the installation package to a specific path (data/app-staging).
The specific implementation of Session creation and opening is in PackageInstallerService, which mainly initializes the installation information and environment of APK, creates a sessionId, and binds the installation Session to the sessionId. (More code, not to play)
We focus on writing the package information into the session corresponding to mSessionId, and then commit it. When we go back to InstallingAsyncTask, we see that the APK package file is read and written in InstallingAsyncTask. The output stream is written to the Session, that is, the APK file is written to the Session. Session.mit () is implemented in PackageInstallerSession.
//PackageInstallerSession.java
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
...
dispatchStreamValidateAndCommit();
}
private void dispatchStreamValidateAndCommit() {
mHandler.obtainMessage(MSG_STREAM_VALIDATE_AND_COMMIT).sendToTarget();
}
Copy the code
Sent a handler, finally executed in handleStreamValidateAndCommit (), and then it sends the message MSG_INSTALL, the executive in handleInstall () :
private final Handler.Callback mHandlerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_STREAM_VALIDATE_AND_COMMIT: handleStreamValidateAndCommit(); // break; case MSG_INSTALL: handleInstall(); // break; . } return true; }}; private void handleInstall() { ... List<PackageInstallerSession> childSessions = getChildSessionsNotLocked(); installNonStagedLocked(childSessions); . } private void installNonStagedLocked(List<PackageInstallerSession> childSessions) { final PackageManagerService.ActiveInstallSession installingSession = makeSessionActiveLocked(); . InstallStage (installingSession); . }Copy the code
Finally, the installation process reaches the installStage() of the PMS.
So far, only the installation Session has been maintained with the PackageInstaller and the installation package has been written into the Session; the real installation process is performed by the PMS.
3.3 Installation in PMS
PMS installStage() :
//PMS
void installStage(ActiveInstallSession activeInstallSession) {
...
final Message msg = mHandler.obtainMessage(INIT_COPY);
final InstallParams params = new InstallParams(activeInstallSession);
msg.obj = params;
mHandler.sendMessage(msg);
}
Copy the code
INIT_COPY is params.startCopy():
//HandlerParams.java
final void startCopy() {
handleStartCopy();
handleReturnCode();
}
Copy the code
Two method implementations are called in InstallParams:
public void handleStartCopy() { int ret = PackageManager.INSTALL_SUCCEEDED; . PackageInfoLite pkgLite = null; // Parsing the package returns the smallest details :pkgName, versionCode, size of space required for installation, obtaining installation location, etc. PkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,origin.resolvedPath, installFlags, packageAbiOverride); . If (ret = = PackageManager. INSTALL_SUCCEEDED) {/ / here is failed to get installation location situation int loc = pkgLite. RecommendedInstallLocation; If (loc == packageHelper.shame_failed_invalid_location) {// Install position invalid ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else if (loc == packagehelper.shame_failed_already_exists) {// There was a package ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS; } else if (loc == packageHelper.shame_failed_Insufficient_storage) {// Insufficient space ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } else if (loc == packageHelper.shame_failed_invalid_apk) {//apk invalid ret = packagemanager.install_failed_invalid_apk; } else if (loc == packagehelper.shame_failed_invalid_uri) {//uri invalid ret = packagemanager.install_failed_invalid_uri; } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {// ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE; } else { ... } final InstallArgs args = createInstallArgs(this); // The installation parameter mVerificationCompleted = true; mIntegrityVerificationCompleted = true; mArgs = args; / / if the if (ret = = PackageManager INSTALL_SUCCEEDED) {final int verificationId = mPendingVerificationToken++; // if (! origin.existing) { PackageVerificationState verificationState =new PackageVerificationState(this); mPendingVerification.append(verificationId, verificationState); sendIntegrityVerificationRequest(verificationId, pkgLite, verificationState); ret = sendPackageVerificationRequest(verificationId, pkgLite, verificationState); . }... mRet = ret; // assign returnCode}Copy the code
The parse package returns the smallest details: pkgName, versionCode, size of space required for installation; Confirm the installation position of the package; Verify APK integrity. Get the result mRet. Then look at handler ReturnCode() :
void handleReturnCode() { if (mVerificationCompleted && mIntegrityVerificationCompleted && mEnableRollbackCompleted) { . If (mRet == packagemanager.install_Succeeded) {mRet = margs.copyapk (); } processPendingInstall(mArgs, mRet); }}}Copy the code
Determine the mRet, perform the APK copy, and then perform the install. Margs.copyapk () :
//FileInstallArgs.java int copyApk() { return doCopyApk(); } private int doCopyApk() { /data/app 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; } / / copy apk int ret. = PackageManagerServiceUtils copyPackage (origin. File. GetAbsolutePath (), codeFile); if (ret ! = PackageManager.INSTALL_SUCCEEDED) { Slog.e(TAG, "Failed to copy package"); return ret; } final boolean isIncremental = isIncrementalPath(codeFile.getAbsolutePath()); final File libraryRoot = new File(codeFile, LIB_DIR_NAME); NativeLibraryHelper.Handle handle = null; try { handle = NativeLibraryHelper.Handle.create(codeFile); / / copy the Native code. So file ret = NativeLibraryHelper. CopyNativeBinariesWithOverride (handle, libraryRoot abiOverride, isIncremental); } catch (IOException e) { Slog.e(TAG, "Copying native libraries failed", e); ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; }... return ret; }Copy the code
CopyApk() in FileInstallArgs goes to doCopyApk() and gets the copied file path first: . / data/app, use PackageManagerServiceUtils copyPackage APK copy (), then the. So a copy of the file. That is, copy the APK sent to the Session staging directory data/app-staging to /data/app.
ProcessPendingInstall (mArgs, mRet)
//PMS.java private void processPendingInstall(final InstallArgs args, final int currentStatus) { ... / / res, used to save the installation results PackageInstalledInfo res = createPackageInstalledInfo (currentStatus); processInstallRequestsAsync(res.returnCode == PackageManager.INSTALL_SUCCEEDED,Collections.singletonList(new InstallRequest(args, res))); . } / / install operation handler team private void processInstallRequestsAsync (Boolean success, List<InstallRequest>installRequests) { mHandler.post(() -> { if (success) { for (InstallRequest request : // installRequests) {// installRequests: ReturnCode not copy by removing INSTALL_SUCCEEDED apk request. Such as args. DoPreInstall (request. InstallResult. ReturnCode); } synchronized (mInstallLock) {// install apK installPackagesTracedLI(installRequests); } for (InstallRequest request : InstallRequests) {/ / with inspection request before installation. The args. DoPostInstall (request. InstallResult. ReturnCode, request. InstallResult. Uid); } } for (InstallRequest request : InstallRequests) {// installRequests: Backup, possible to rollback, send the installation is complete, first broadcast restoreAndPostInstall (request. The args. User. GetIdentifier (), request. InstallResult, new PostInstallData(request.args, request.installResult, null)); }}); }Copy the code
InstallPackagesTracedLI () installPackagesLI() installPackagesLI()
private void installPackagesLI(List<InstallRequest> requests) { ... try { for (InstallRequest request : requests) { final PrepareResult prepareResult; PrepareResult = preparePackageLI(Request.args, Request.installResult); // prepareResult = preparePackageLI(Request.args, Request.installResult); . Try {//2. According to the package information of preparation phase analytic context further resolve final ScanResult result = scanPackageTracedLI (prepareResult packageToScan, prepareResult.parseFlags,prepareResult.scanFlags, System.currentTimeMillis(),request.args.user, request.args.abiOverride); . / / registered appId createdAppId. Put (packageName, optimisticallyRegisterAppId (result); // Save the version information 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. Verify scan package information and the system state, to ensure the success of the installation reconciledPackages = reconcilePackagesLocked (reconcileRequest, mSettings. MKeySetManagerService); } catch (ReconcileFailure e) { for (InstallRequest request : requests) { request.installResult.setError("Reconciliation failed..." , e); } return; } try {//4. Submit: submit scanned packets and update the system status. This is the only place where you can modify the system state and detect all predictable errors. commitRequest = new CommitRequest(reconciledPackages, mUserManager.getUserIds()); commitPackagesLocked(commitRequest); success = true; }} // Prepare app data, compile layout resources, execute dex optimization executePostCommitSteps(commitRequest); }... }Copy the code
The installation process is divided into four stages:
-
Preparation, analysis of the current installation status, analytic package and initial check: in preparePackageLI (within) using PackageParser2, parsePackage () parsing AndroidManifest. XML, to obtain the information such as the four components; Using ParsingPackageUtils. GetSigningDetails () analytical signature information; Rename package final path, etc.
-
Scan and further parse according to the context of the packet information resolved in the preparation phase: Verify that the packet name is true. Verify the validity of the packet based on the parsed information (for example, whether the packet has a signature). Collect APK information — PackageSetting, apK static/dynamic library information, etc.
-
Check to verify the scanned package information to ensure the successful installation: mainly refers to the signature matching verification of overwriting the installation.
-
Submit, submit scanned packages, update system status: Add PackageSetting to mSettings of PMS, add AndroidPackage to mPackages of PMS, add secret key set to system, add application permissions to MPermissionManager and four major component information are added to mComponentResolver. This is the only place where you can modify the system state and detect all predictable errors.
The first three steps focus on parsing and validation, and the fourth step is to commit package information to the PMS in-memory data structure. The same goes for parsing and committing after scanning the APK directory in the PMS initialization above. I’m not going to do any tracing here.
After the installation is complete, executePostCommitSteps() is called to prepare app data and perform dex optimization:
private void executePostCommitSteps(CommitRequest commitRequest) { ... for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) { ... / / directory structure/data/user/user ID/package/cache (/ data/user/user ID/package/code_cache) prepareAppDataAfterInstallLIF (PKG); . Dexopt 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); . // Execute dex optimization: Dexopt is an operation to verify and optimize the DEX file. The optimization result of the dex file becomes the Odex file, which is similar to the DEX file but uses some optimization opcodes (such as optimization call virtual instructions). mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting, null /* instructionSets */, getOrCreateCompilerPackageStats(pkg), mDexManager.getPackageUseInfoOrDefault(packageName), dexoptOptions); }... }}Copy the code
- PrepareAppDataAfterInstallLIF () : provide a directory structure/data/user/user ID/package/cache (/ data/user/user ID/package/code_cache)
- MPackageDexOptimizer. PerformDexOpt () : Dexopt is an operation to verify and optimize the DEX file. The optimization result of the dex file becomes the Odex file, which is similar to the DEX file but uses some optimization opcodes (such as optimization call virtual instructions).
Both of these operations are ultimately performed using the Installer method. Installer, which inherits from SystemService, is also a SystemService. Here’s a look:
public class Installer extends SystemService { public void onStart() { if (mIsolated) { mInstalld = null; } else { connect(); } } private void connect() { IBinder binder = ServiceManager.getService("installd"); if (binder ! = null) { try { binder.linkToDeath(new DeathRecipient() { @Override public void binderDied() { Slog.w(TAG, "installd died; reconnecting"); connect(); }}, 0); } catch (RemoteException e) { binder = null; } } if (binder ! = null) { mInstalld = IInstalld.Stub.asInterface(binder); try { invalidateMounts(); } catch (InstallerException ignored) { } } else { Slog.w(TAG, "installd not found; trying again"); BackgroundThread.getHandler().postDelayed(() -> { connect(); }, DateUtils.SECOND_IN_MILLIS); } } public long createAppData(String uuid, String packageName, int userId, int flags, int appId, String seInfo, int targetSdkVersion) throws InstallerException { if (! checkBeforeRemote()) return -1; try { return mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo, targetSdkVersion); } catch (Exception e) { throw InstallerException.from(e); } } public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath ...) throws InstallerException { ... if (! checkBeforeRemote()) return; try { mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade, targetSdkVersion, profileName, dexMetadataPath, compilationReason); } catch (Exception e) { throw InstallerException.from(e); }}Copy the code
- The Installer onStart() method gets the mInstalld instance from Installd’s Binder object. This is an IPC operation, that is, the Installer in the System_Server process IPC to the root daemon. Directories like /data/user must be created with root privileges.
- Installer is a Java API provided by the Java layer. Installd is a Daemon with root permission started in the init process.
Here the installation is complete, and then back to the PMS processInstallRequestsAsync (), the last call restoreAndPostInstall () for backup, possible to rollback and send the installation is complete, first off radio:
private void restoreAndPostInstall( int userId, PackageInstalledInfo res, @Nullable PostInstallData data) { ... Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); mHandler.sendMessage(msg); . }Copy the code
MHandler handles POST_INSTALL using the PMS handlePackagePostInstall() method:
- According to the result of the installation, the desktop Launcher sends broadcasts such as intent.action_package_added. After receiving such broadcasts, the desktop Launcher adds the Icon of the App on the desktop
- Call the onPackageInstalled() method of the IPackageInstallObserver2 instance saved in PackageInstallSession, and finally send a notification of successful installation to display in the notification bar. The broadcast that is defined in InstallInstalling is sent through IntentSender, and the InstallInstalling page shows success/failure based on the result.
Now that the APK installation process is sorted out, let’s review:
- APK writes to the Session and the package information and APK installation operations are committed to the PMS;
- In PMS, APK is first copied to /data/app, and then the APK is parsed with PackageParser2 to obtain the four major components, collect signature, PackageSetting and other information, and verified to ensure successful installation.
- Then submit the information packet to update the system state and PMS memory data;
- Then use Installer to prepare user directory /data/user and dexOpt.
- Finally, the UI layer is notified of the installation results.
Four,
Starting from the user experience of APK installation, this paper combs the service startup and initial scanning process of PackageManagerService — PackageManagerService in detail. Then the installation process and page of corresponding native system are analyzed. Then, the installation process is analyzed in detail, including creating Session and sending APK, APK copy operation in PMS, including APK parsing verification and dexOpt and other specific installation processes. Finally, the installation results are sent.
It’s a lot to cover, but we just need to focus on the whole process of PMS initialization and APK installation, and don’t need to pay attention to the details.
Although I spent a lot of time reading and reading some articles, there were mistakes and omissions. Welcome to discuss ~
In addition, welcome to follow my public account Hu Feiyang, which has my wechat entrance, I will pull you into the Android discussion group.
Reference and thanks:
Detailed APK installation process
Android10 APK installation process analysis package agemanageservice
The Android 10.0 PackageManagerService
Android Relearn series PackageManagerService startup and installation
Deep into Android (X) PMS-3- Application installation process