Installation of Android APK

Posted by adamlacombe on Fri, 17 May 2019 14:17:55 +0200

Brief description of APK installation process, before the formal start of the article, we need to do some knowledge supplement

1: How to invoke the installation interface, taking the installation of sd card as an example

Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.setData(Uri.parse("file:/sdcard/qq.apk"));
startActivity(intent);

2: In setData, the composition of Uri
The format of Uri is "file://<absolute path>", such as: xl://goods:8888/goodsDetail?goodsId=10011002

xl represents the Scheme protocol name
Which address domain does goods represent Scheme act on
goodsDetail represents the page specified by Scheme
goodsId represents the passed parameters
8888 represents the port number of the path

With Uri, we can access this resource in other applications

Official introduction to the installation of Apk
When we click on an APK on the SD card, the filesystem will initiate an action similar to implicit Intent for us, which will wake up the apk-installing application in the framework with the following interface

The code location of the interface is in the: packages.apps.PackageInstaller directory, and its entry class is PackageInstallerActivity.java.

    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        //Classes for retrieving application-related information from packages installed on current devices
        mPm = getPackageManager();
        mInstaller = mPm.getPackageInstaller();
        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

        final Intent intent = getIntent();
        mOriginatingUid = getOriginatingUid(intent);

        final Uri packageUri;

        if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
            final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
            final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
            if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                finish();
                return;
            }

            mSessionId = sessionId;
            packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
            mOriginatingURI = null;
            mReferrerURI = null;
        } else {
            mSessionId = -1;
            packageUri = intent.getData();
            mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
        }

        // if there's nothing to do, quietly slip into the ether
        if (packageUri == null) {
            Log.w(TAG, "Unspecified source");
            setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
            finish();
            return;
        }

        if (DeviceUtils.isWear(this)) {
            showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
            return;
        }

        //set view
        setContentView(R.layout.install_start);
        mInstallConfirm = findViewById(R.id.install_confirm_panel);
        mInstallConfirm.setVisibility(View.INVISIBLE);
        mOk = (Button)findViewById(R.id.ok_button);
        mCancel = (Button)findViewById(R.id.cancel_button);
        mOk.setOnClickListener(this);
        mCancel.setOnClickListener(this);

        boolean wasSetUp = processPackageUri(packageUri);
        if (!wasSetUp) {
            return;
        }

        checkIfAllowedAndInitiateInstall(false);
    }

Line 6, get the PackageManager object, which is mainly used to retrieve all kinds of information related to the application in the package installed by the current device, mainly from the Manifest.xml file of Apk.
In line 7, the PackageInstaller object is obtained through the getPackageInstaller method.
The main role of this class is to provide the ability to install, upgrade and delete applications, including applications that support single packaged applications or packaged into multiple "split" APK s.
Question 1:
The PackageManager class is an abstract class. How does he get the PackageInstaller object through the getPackageInstaller method?

The way to get a PackageManager object is usually to call the getPackageManager method in Activity, or through Context#getPackageManager, where the Context may come from Application, Activity or Service.
Let's first look at the inheritance relationships of these components.

Context.java, as an abstract class, provides global information about the application environment. Its implementation is provided by Android system.
It allows access to application-specific resources and classes and calls up application-level operations, such as start-up activities, broadcast and receive intentions, etc.

Thus, obtaining PackageManager through Context getPackageManager is implemented by the ContextWrapper class.

    Context mBase;

    @Override
    public PackageManager getPackageManager() {
        return mBase.getPackageManager();
    }

Question 2:
Who is the context implementer?

To answer this question, let's go back to the ActivityThread.java class, which manages the main thread, activities, broadcasting, etc. in the application process. As the first class to execute the application, let's see what the main function does.

    public static void main(String[] args) {

        //...Ellipsis code...

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

Line 6 binds the Application, creates context, Application, etc.
Line 9 creates handler for the main thread
Running a message queue in the main thread

The implementation of the abstract class Context is implemented in thread.attach(false) method.

    private void attach(boolean system) {
       // Ellipsis code 
            try {
                mInstrumentation = new Instrumentation();
                ContextImpl context = ContextImpl.createAppContext(
                        this, getSystemContext().mPackageInfo);
                mInitialApplication = context.mPackageInfo.makeApplication(true, null);
                mInitialApplication.onCreate();
            } catch (Exception e) {
                throw new RuntimeException(
                        "Unable to instantiate Application():" + e.toString(), e);
            }
         // Ellipsis code 
    }

In line 5, we can see that context is implemented as ContextImpl. Java
Line 6, Create Application Objects
Line 7, onCreate completes one of the life cycles of the Application object

Back in the onCreate method of PackageInstallerActivity.java, we continue to analyze the process of Apk installation.
Lines 15 to 28 are not normally executed
Lines 29 to 32, get Uri type packageUri, mOriginating URI, mReferrer URI from intent, where the value of packageUri is through intent.setData(Uri.parse("file:/sdcard/qqq.apk");
Lines 49 to 55 to complete the UI layout
Line 57 completes the Apk parsing. Let's look at the code in detail.

    /**
     * Parse the Uri and set up the installer for this package.
     * Parse Uri and set up the installer for this package
     *
     * @param packageUri The URI to parse
     *
     * @return {@code true} iff the installer could be set up
     */
    private boolean processPackageUri(final Uri packageUri) {
        mPackageURI = packageUri;
        //Get Scheme from the value in intent.setData
        final String scheme = packageUri.getScheme();
        //In our example, Scheme is file, which runs the case SCHEME_FILE branch
        final PackageUtil.AppSnippet as;

        switch (scheme) {
            case SCHEME_PACKAGE: {
                try {
                    mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
                            PackageManager.GET_PERMISSIONS
                                    | PackageManager.GET_UNINSTALLED_PACKAGES);
                } catch (NameNotFoundException e) {
                }
                if (mPkgInfo == null) {
                    Log.w(TAG, "Requested package " + packageUri.getScheme()
                            + " not available. Discontinuing installation");
                    showDialogInner(DLG_PACKAGE_ERROR);
                    setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                    return false;
                }
                as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
                        mPm.getApplicationIcon(mPkgInfo.applicationInfo));
            } break;

            case SCHEME_FILE: {
                File sourceFile = new File(packageUri.getPath());
                PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);

                // Check for parse errors
                if (parsed == null) {
                    Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
                    showDialogInner(DLG_PACKAGE_ERROR);
                    setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                    return false;
                }
                mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
                        PackageManager.GET_PERMISSIONS, 0, 0, null,
                        new PackageUserState());
                as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
            } break;

            case SCHEME_CONTENT: {
                mStagingAsynTask = new StagingAsyncTask();
                mStagingAsynTask.execute(packageUri);
                return false;
            }

            default: {
                Log.w(TAG, "Unsupported scheme " + scheme);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
                clearCachedApkIfNeededAndFinish();
                return false;
            }
        }

        //initSnippetForNewApp mainly displays layout files in pictures and text.
        PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);

        return true;
    }

Line 36. Get file power through Url and create File objects
Line 37 calls the parsePackage method of PackageParser.java to get information about the package according to the path of the generated target file; the main function of the PackageParser.java class is to parse the apk on the hard disk, which supports parsing a single apk and how fast it is split into.

//PackageParser.java
    public static PackageLite parsePackageLite(File packageFile, int flags)
            throws PackageParserException {
        if (packageFile.isDirectory()) {
            return parseClusterPackageLite(packageFile, flags);
        } else {
            return parseMonolithicPackageLite(packageFile, flags);
        }
    }

The first parameter of this method is the file to parse the apk, and the second parameter is 0; because there is only one APK file in the packageFile, consider jumping directly to the parseMonolithic PackageLite method in line 7.

    //Monolithic monolithic
    private static PackageLite parseMonolithicPackageLite(File packageFile, int flags)
            throws PackageParserException {
        final ApkLite baseApk = parseApkLite(packageFile, flags);
        final String packagePath = packageFile.getAbsolutePath();
        return new PackageLite(packagePath, baseApk, null, null, null);
    }

Line 4 calls the parseApkLite method to get the object of ApkLite. ApkLite is a lightweight object describing the apk, which contains the package name, version number, certificate, signature, installation location and other information of the apk.
Let's look at the parseApkLite method, which I explained in detail.

    public static ApkLite parseApkLite(File apkFile, int flags)
            throws PackageParserException {
        final String apkPath = apkFile.getAbsolutePath();

        AssetManager assets = null;
        XmlResourceParser parser = null;
        try {
            assets = new AssetManager();
            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    Build.VERSION.RESOURCES_SDK_INT);

            int cookie = assets.addAssetPath(apkPath);
            if (cookie == 0) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                        "Failed to parse " + apkPath);
            }

            final DisplayMetrics metrics = new DisplayMetrics();
            metrics.setToDefaults();

            final Resources res = new Resources(assets, metrics, null);
            //Return the XmlResourceParser object through assets, which is mainly used to parse the AndroidManifest.xml file
            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);

            //Signature is mainly used to store signatures
            final Signature[] signatures;
            //Certificate is mainly used to store certificates
            final Certificate[][] certificates;
            if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
                // TODO: factor signature related items out of Package object
                //Build an empty Package object and pass it to the collectCertificates method.
                //When the collectCertificates method is completed, the tempPkg object members are assigned values.
                //Package represents a complete apk information, or how fast it is split into
                final Package tempPkg = new Package(null);
                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
                try {
                //The collectCertificates method first confirms whether the apk uses V2 signature, and if it is a V2 signature, builds a StrictJarFile object.
                //The object change is a base class of parsing jar. The parsing of apk is completed by ZipEntry. The apk itself is a zip file, so it can be used.
                //Use the ZipEntry class; after parsing, assign tempPkg members, such as certificates, signatures
                    collectCertificates(tempPkg, apkFile, 0 /*parseFlags*/);
                } finally {
                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                }
                signatures = tempPkg.mSignatures;
                certificates = tempPkg.mCertificates;
            } else {
                signatures = null;
                certificates = null;
            }

            final AttributeSet attrs = parser;
            //Call the parseApkLite method to return the ApkLite object, which represents a lightweight description of an apk
            //Such as package name, installation location, etc.
            return parseApkLite(apkPath, res, parser, attrs, flags, signatures, certificates);

        } catch (XmlPullParserException | IOException | RuntimeException e) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                    "Failed to parse " + apkPath, e);
        } finally {
            IoUtils.closeQuietly(parser);
            IoUtils.closeQuietly(assets);
        }
    }

After the parseMonolithic PackageLite method completes the construction of ApkLite, line 6 completes the creation of PackageLite class. PackageLite mainly describes a lightweight description of the package, which is different from ApkLite. ApkLite describes apk, which contains package name, installation location, certificate, signature and other information, while PackageLite mainly includes package name, apk location, etc. Without making any specific distinction, PackageLite and APkLite can be understood as a simple description of apk, which contains some simple information about apk.

Let's look back at the onCreate method in PackageInstallerActivity.java
If the parsing apk is successful, the checkIfAllowedAndInitiateInstall method in line 62 is executed. This method is mainly used to check whether the apk is allowed to install or not. If it is allowed to install, it enters the installation process. If it is not allowed to install, a dialog box like apk that does not allow the installation of unknown sources will pop up.

    private void checkIfAllowedAndInitiateInstall(boolean ignoreUnknownSourcesSettings) {
        // Block the install attempt on the Unknown Sources setting if necessary.
        // Determine whether to install based on whether the setup module prevents unknown source settings
        // If installation is allowed, the initiateInstall method is called, which calls startInstallConfirm to initialize other interfaces.
        // For example, display permission list page, etc.
        // If installation is not allowed, a dialog box like apk from unknown sources is popped up
        final boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(getIntent());
        if (!requestFromUnknownSource) {
            initiateInstall();
            return;
        }

        // If the admin prohibits (prohibits) it, or we're running in a managed profile, just show error
        // and exit. Otherwise show an option to take the user to Settings to change the setting.
        final boolean isManagedProfile = mUserManager.isManagedProfile();
        if (isUnknownSourcesDisallowed()) {
            if ((mUserManager.getUserRestrictionSource(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
                    Process.myUserHandle()) & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
                if (ignoreUnknownSourcesSettings) {
                    initiateInstall();
                } else {
                    showDialogInner(DLG_UNKNOWN_SOURCES);
                }
            } else {
                startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
                clearCachedApkIfNeededAndFinish();
            }
        } else if (!isUnknownSourcesEnabled() && isManagedProfile) {
            showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
        } else if (!isUnknownSourcesEnabled()) {
            if (ignoreUnknownSourcesSettings) {
                initiateInstall();
            } else {
                // Ask user to enable setting first
                showDialogInner(DLG_UNKNOWN_SOURCES);
            }
        } else {
            initiateInstall();
        }
    }

You can see that the checkIfAllowedAndInitiateInstall method will eventually call the initiateInstall method, and then call the startInstallConfirm method to update the remaining UI content.

So far, we've looked at the first step of installing the apk in detail. In a nutshell, the steps are as follows
1: Parse the target apk and generate some lightweight information of the apk
2: Check whether the apk is allowed to install, and update the other interfaces of PackageInstallerActivity.java if it is allowed to install

Finally, let's look at the key steps to install the apk. Click Install and the code is still in the onClick method of PackageInstallerActivity.java

    public void onClick(View v) {
        if (v == mOk) {
            if (mOkCanInstall || mScrollView == null) {
                if (mSessionId != -1) {
                    mInstaller.setPermissionsResult(mSessionId, true);
                    clearCachedApkIfNeededAndFinish();
                } else {
                    startInstall();
                }
            } else {
                mScrollView.pageScroll(View.FOCUS_DOWN);
            }
        } else if (v == mCancel) {
            // Cancel and finish
            setResult(RESULT_CANCELED);
            if (mSessionId != -1) {
                mInstaller.setPermissionsResult(mSessionId, false);
            }
            clearCachedApkIfNeededAndFinish();
        }
    }

It finally calls the startInstall method to jump to the Installation Progress Activity

    private void startInstall() {
        // Start subactivity to actually install the application
        /*Building empty intent
        Intent newIntent = new Intent();
        //Input parameter applicationInfo information
        newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                mPkgInfo.applicationInfo);
        //Pass in the mPackageURI, which is the value passed in through intent.setDate
        newIntent.setData(mPackageURI);
        //Jump to the installation progress activity,InstallAppProgress.java 
        newIntent.setClass(this, InstallAppProgress.class);
        String installerPackageName = getIntent().getStringExtra(
                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
        if (mOriginatingURI != null) {
            newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
        }
        if (mReferrerURI != null) {
            newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
        }
        if (mOriginatingUid != VerificationParams.NO_UID) {
            newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
        }
        if (installerPackageName != null) {
            newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
                    installerPackageName);
        }
        if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
            newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
            newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
        }
        if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
        startActivity(newIntent);
        finish();
    }

Next, we analyze the second step of installing the apk, and go to InstallAppProgress.java, which has just entered the following interface

The figure above describes the icon and label of apk respectively, and a progress bar set to ambiguity.

First look at its onCreate method

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        Intent intent = getIntent();
        //Get the incoming Application Info
        mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
        //Get the url of the incoming apk
        mPackageURI = intent.getData();
        //scheme type for obtaining url of apk
        final String scheme = mPackageURI.getScheme();
        if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
            throw new IllegalArgumentException("unexpected scheme " + scheme);
        }
        //Constructing mInstallHandler to Complete Message Receiving and Sending
        mInstallThread = new HandlerThread("InstallThread");
        mInstallThread.start();
        mInstallHandler = new Handler(mInstallThread.getLooper());

        //Register the broadcasting receiver and listen for com. android. package installer. ACTION_INSTALL_COMMIT broadcasting
        //The broadcasting receiver indicates that only the broadcasting sender of android.permission.INSTALL_PACKAGES with installation authority can send
        //To trigger this broadcast receiver
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BROADCAST_ACTION);
        registerReceiver(
                mBroadcastReceiver, intentFilter, BROADCAST_SENDER_PERMISSION, null /*scheduler*/);
        //Show UI
        initView();
    }

In this method, handler s and broadcasting receivers are created to process messages. Their functions will be explained in the following analysis. Let's look at the initView method.

    void initView() {
        /*layout activity,That's the interface to display the installation progress bar.
        setContentView(R.layout.op_progress);
        //AppSnippet can be connected to a class that contains label and icon attributes describing App
        final PackageUtil.AppSnippet as;
        //PackageManager manages application packages, for example, through which we can obtain application information.
        final PackageManager pm = getPackageManager();
        //The getInstallFlags method obtains the PackageManager object through the getPackageManager method based on the incoming package name
        //Getting PackageInfo through the PackageManager object returns PackageManager.INSTALL_REPLACE_EXISTING if PackageInfo is not empty
        //Represents replacing an existing apk, or returning 0
        //The value of installing flag implies inconsistency of methods when calling installation
        final int installFlags = getInstallFlags(mAppInfo.packageName);

        if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) {
            Log.w(TAG, "Replacing package:" + mAppInfo.packageName);
        }
        //According to the sample code, the cheme value of the mPackageURI here is not "package" but "file".“
        if ("package".equals(mPackageURI.getScheme())) {
            as = new PackageUtil.AppSnippet(pm.getApplicationLabel(mAppInfo),
                    pm.getApplicationIcon(mAppInfo));
        } else {
            //Create the target file object sourceFile based on the address of the mPackageURI
            final File sourceFile = new File(mPackageURI.getPath());
            //getAppSnippet obtains label and icon of apk and assigns them to members of AppSnippet
            as = PackageUtil.getAppSnippet(this, mAppInfo, sourceFile);
        }
        mLabel = as.label;
        //The initSnippetForNewApp method mainly updates the contents of imageview and textview in the interface as icon and label obtained above.
        PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
        mStatusTextView = (TextView)findViewById(R.id.center_text);
        mExplanationTextView = (TextView) findViewById(R.id.explanation);
        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        //Setting progress bar in fuzzy mode, that is to say, it appears that progress bar is consistent in the
        mProgressBar.setIndeterminate(true);
        // Hide button till progress is being displayed
        //mOkPanel contains mDoneButton and mLaunchButton buttons, which are initially invisible until installation is complete.
        mOkPanel = findViewById(R.id.buttons_panel);
        mDoneButton = (Button)findViewById(R.id.done_button);
        mLaunchButton = (Button)findViewById(R.id.launch_button);
        mOkPanel.setVisibility(View.INVISIBLE);
        //The scheme here is "file"
        if ("package".equals(mPackageURI.getScheme())) {
            try {
                pm.installExistingPackage(mAppInfo.packageName);
                onPackageInstalled(PackageInstaller.STATUS_SUCCESS);
            } catch (PackageManager.NameNotFoundException e) {
                onPackageInstalled(PackageInstaller.STATUS_FAILURE_INVALID);
            }
        } else {
             //PackageInstaller: Provides the ability to install, update, and uninstall an app on a device
            //The installation of Apk is left to Session, and any application can create such Session.
            //Session Params is a parameter used to create a Session that stores some information about the apk, such as progress, icons, session Id, installation representations.
            //The parameters in the incoming Session Params indicate how Session will interact with the existing app
            //MODE_FULL_INSTALL means that the existing app is completely replaced and installed.
            //Session Params is a parameter used to create a Session that stores some information about the apk, such as progress, icons, session Id, installation representations.
            //The parameters in the incoming Session Params indicate how Session will interact with the existing app
            //MODE_FULL_INSTALL means that the existing app is completely replaced and installed.
            final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
            params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
            params.originatingUri = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
                    UID_UNKNOWN);
            //Building target files    
            File file = new File(mPackageURI.getPath());
            try {
                //As previously analyzed, calling the parsePackageLite method will do other processing based on whether the file is a directory or not?
                //This column of our test code is a single file, considering the parsePackageLite method call
                //The parseMonolithic PackageLite method returns PackageLite
                //PackageLite represents a condensed version of APK, which contains some package names, version numbers, and other information.
                PackageLite pkg = PackageParser.parsePackageLite(file, 0);
                //Setting the package name of the Sesion parameter params
                params.setAppPackageName(pkg.packageName);
                //Setting the installation location of the Sesion parameter params
                params.setInstallLocation(pkg.installLocation);
                //Setting Sesion parameter file size
                params.setSize(
                    PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
            } catch (PackageParser.PackageParserException e) {
                Log.e(TAG, "Cannot parse package " + file + ". Assuming defaults.");
                Log.e(TAG, "Cannot calculate installed size " + file + ". Try only apk size.");
                params.setSize(file.length());
            } catch (IOException e) {
                Log.e(TAG, "Cannot calculate installed size " + file + ". Try only apk size.");
                params.setSize(file.length());
            }

            mInstallHandler.post(new Runnable() {
                @Override
                public void run() {
                    doPackageStage(pm, params);
                }
            });
        }
    }

I've written a more detailed comment on this method, but it's easier to understand. Let's look at the getAppSnippet method in line 25. This method, like the comment, is mainly to get the icon and lab of apk.

    public static AppSnippet getAppSnippet(
            Activity pContext, ApplicationInfo appInfo, File sourceFile) {
        final String archiveFilePath = sourceFile.getAbsolutePath();
        Resources pRes = pContext.getResources();
        AssetManager assmgr = new ANQPRequestManager();
        assmgr.addAssetPath(archiveFilePath);
        Resources res = new Resources(assmgr, pRes.getDisplayMetrics(), pRes.getConfiguration());
        CharSequence label = null;
        // Try to load the label from the package's resources. If an app has not explicitly
        // specified any label, just use the package name.
        if (appInfo.labelRes != 0) {
            try {
                label = res.getText(appInfo.labelRes);
            } catch (Resources.NotFoundException e) {
            }
        }
        if (label == null) {
            label = (appInfo.nonLocalizedLabel != null) ?
                    appInfo.nonLocalizedLabel : appInfo.packageName;
        }
        Drawable icon = null;
        // Try to load the icon from the package's resources. If an app has not explicitly
        // specified any resource, just use the default icon for now.
        if (appInfo.icon != 0) {
            try {
                icon = res.getDrawable(appInfo.icon);
            } catch (Resources.NotFoundException e) {
            }
        }
        if (icon == null) {
            icon = pContext.getPackageManager().getDefaultActivityIcon();
        }
        return new PackageUtil.AppSnippet(label, icon);
    }

In line 17, if label is empty, that is, manifest.xml does not specify the android:label attribute, then app's lab defaults to the package name of the application
In line 30, if icon is empty, that is, manifest.xml does not specify the android: icon attribute, then icon defaults to the internal icon of the system. What exactly is this icon?
We can see line 31, which calls the getDefaultActivityIcon method to get the default icon. In the previous analysis, we know that the implementer of the Context is ContextImpl. The call to the getPackageManager method can be found according to the code to return the ApplicationPackageManager object. The ApplicationPackageManager inherits from the PackageManager, so now we know Context.getP. The ackage Manager is actually implemented by the Application Package Manager; tracing the getDefaultActivityIcon method shows what the default icon is.

    @Override public Drawable getDefaultActivityIcon() {
        return Resources.getSystem().getDrawable(
            com.android.internal.R.drawable.sym_def_app_icon);
    }

That's the following Icon

We return to the initView method of InstallAppProgress.java
In line 42, we get scheme as "file" and jump to line 49.
Lines 49 to 91, we built the Session Params object params for creating Session. With Session, we can install Apk and call it

doPackageStage(pm, params);

Complete installation action;

    private void doPackageStage(PackageManager pm, PackageInstaller.SessionParams params) {
        final PackageInstaller packageInstaller = pm.getPackageInstaller();
        //Session is a class that implements the Closeable interface
        //Closeable is a data source or target that can be turned off. Call the close method to release the resources saved by the object, such as opening a file.
        //Closeable API: void close() throws IOException
        //Close the stream and release all system resources associated with it. If the flow has been closed, the call to this method is invalid.
        //Its function indicates that an installation is under way, and any installed apk needs to have a unique package name, version number, and signature file.
        PackageInstaller.Session session = null;
        try {
            //Installation location
            final String packageLocation = mPackageURI.getPath();
            //Create files according to installation location
            final File file = new File(packageLocation);
            //The createSession method creates a session based on the incoming session parameter, params, and returns a unique ID to represent the session.
            //The Session is created, and the Session can be opened multiple times in devices that have been restarted multiple times. Simply speaking, the device restarts and the progress of the unfinished apk is cached.
            //It will not be reinstalled when it is installed
            final int sessionId = packageInstaller.createSession(params);
            final byte[] buffer = new byte[65536];

            session = packageInstaller.openSession(sessionId);

            final InputStream in = new FileInputStream(file);
            final long sizeBytes = file.length();
            final OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes);
            try {
                int c;
                while ((c = in.read(buffer)) != -1) {
                    out.write(buffer, 0, c);
                    if (sizeBytes > 0) {
                        final float fraction = ((float) c / (float) sizeBytes);
                        session.addProgress(fraction);
                    }
                }
                session.fsync(out);
            } finally {
                IoUtils.closeQuietly(in);
                IoUtils.closeQuietly(out);
            }

            // Create a PendingIntent and use it to generate the IntentSender
            Intent broadcastIntent = new Intent(BROADCAST_ACTION);
            PendingIntent pendingIntent = PendingIntent.getBroadcast(
                    InstallAppProgress.this /*context*/,
                    sessionId,
                    broadcastIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);
            session.commit(pendingIntent.getIntentSender());
        } catch (IOException e) {
            onPackageInstalled(PackageInstaller.STATUS_FAILURE);
        } finally {
            IoUtils.closeQuietly(session);
        }
    }

In line 2, the PackageInstaller object packageInstaller is obtained by pm.getPackageInstaller(), which provides the ability to install, update and uninstall App on the device; an attribute is maintained within PackageInstaller.java

 private final IPackageInstaller mInstaller;

It's this mInstaller that helps PackageInstaller.java to install, update, uninstall app s; when is this attribute assigned? Let's look at the getPackageInstaller method

    @Override
    public PackageInstaller getPackageInstaller() {
        synchronized (mLock) {
            if (mInstaller == null) {
                try {
                    mInstaller = new PackageInstaller(mContext, this, mPM.getPackageInstaller(),
                            mContext.getPackageName(), mContext.getUserId());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return mInstaller;
        }
    }

In line 6, in the second parameter mPM.getPackageInstaller(), which constructs the PackageInstaller object, it is this parameter that completes the assignment of the IPackageInstaller mInstaller object; let's see how to assign the. mPM object to.

private final IPackageManager mPM;

IPackage Manager is actually an Aidl file, so we can sort out the idea by maintaining an Aidl object mPM in Application Package Manager. java, calling getPackage Installer method through aidl, returning an object IPackage Installer representing aidl, and assigning this object to mPackageInstaller maintained by PackageInstaller.java, so as to make eInstaller. ja. VA can use mInstaller to implement the installation, update and other operations of app.

Next, let's look at how Application Package Manager interacts with PMS. Let's look at the constructor in Application Package Manager

    ApplicationPackageManager(ContextImpl context,
                              IPackageManager pm) {
        //IPackageManager mPM;
        mContext = context;
        mPM = pm;
    }

Application Package Manager is implemented in ActivityThread.java by the handleBind Application method, which contains a code like this.

try {
                ii = new ApplicationPackageManager(null, getPackageManager())
                        .getInstrumentationInfo(data.instrumentationName, 0);
            } catch (PackageManager.NameNotFoundException e) {
                throw new RuntimeException(
                        "Unable to find instrumentation info for: " + data.instrumentationName);
            }

By calling getPackageManager method, the assignment of mPM is completed.

    public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
            return sPackageManager;
        }
        //Get the BinderProxy object of package manager service
        IBinder b = ServiceManager.getService("package");
        //Convert the BinderProxy object into the actual business agent object sPackageManager
        sPackageManager = IPackageManager.Stub.asInterface(b);
        //Slog.v("PackageManager", "default service = " + sPackageManager);
        return sPackageManager;
    }

Within the android system, many classes of app layer and framework layer interact through this binder way, such as package manager, whose implementer is application manager. When the client accesses the package manager object through context, its implementer is application manager, which maintains mPM as an aidl member in application manager. Interacting with package manager service through binder, the client's task is effectively communicated to PKMS, which inherits from IPackage Manager. Stub and is also a Binder-based server.

Here's a brief look at how android uses aidl for process interaction
1: First, just like defining a java class, create a new aidl file with the suffix aidl
2: After compiling, an aidl java file will be generated in the gen directory. Looking at the Java file, we can see that the system creates an abstract class Stub for us, which inherits from the android.os.Binder class and implements the interface in the aidl file.
3: Define the stub object of aidl file in Service and implement the method of aidl

public class AidlService extends Service {
        private final forService.Stub mBinder = new forService.Stub() {
            @Override
            public void invokCallBack() throws RemoteException {

            }
        };
    }

4: Binding services in activity and getting objects of aidl class in onService Connected method

    forService mService;  

    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

    private ServiceConnection mConnection = new ServiceConnection() {  
        public void onServiceConnected(ComponentName className,IBinder service) {  
           mService = forService.Stub.asInterface(service);          
           mService.invokCallBack();  
        } 
    };

OnService Connected () is called by the system when connecting to Service, and its value comes from the object returned by the onBind method in AidlService.java, which is forService.Stub.

summary
In the process of communication between android client and server, the binder mechanism is often used. service realizes the stub object of creating aidl file and returns it in its onbind method.
When the client is in bindserivce, it calls back the onService Connected method, the second parameter in onService Connected and the onbin parameter returned by service.
The class object generated by the aidl file can be returned by the asInterface return variable of the stub class of aidl
In this way, the client can call the method in server by using the returned object.

Let's jump out of the above lead and go back to the apk installation, the getPackageManager method.
Line 7, get the IBind object, where you get the IBind in the form of the client passing in the second parameter through the onService Connected method
Line 9. Get the class object generated by the aidl file through the asInterface and interact with the method in Package Manager Service

Back to InstallAppProgress.java's doPackageStage method, we continue to analyze the apk's follow-up process

Topics: Session Java Android xml