Android 手機安裝apk流程分析

和你一起終身學(xué)習(xí)室抽,這里是程序員 Android

本篇文章主要介紹 Android 開發(fā)中的部分知識點辩棒,通過閱讀本篇文章贞绳,您將收獲以下內(nèi)容:

一膳殷、前言
二操骡、PackageInstaller介紹
三、App安裝過程中涉及類
四赚窃、App安裝流程分析
五册招、總結(jié)
六、參考文獻(xiàn)

一勒极、前言

首先本文不是做PackageManagerService學(xué)習(xí)總結(jié)是掰,PackageManagerService這貨有1萬2千多行代碼,學(xué)習(xí)起來頗費勁辱匿,并且這貨功能強大键痛,本文只會總結(jié)其中一個小小的功能
為何要做這個總結(jié)呢?說來話長匾七,鄙人菜鳥一枚絮短,接到一個安裝應(yīng)用過程中重啟的問題,原因找到昨忆,但不知如何解決丁频,無奈,只有硬著頭皮學(xué)習(xí)了下這部分內(nèi)容
OK扔嵌,廢話不多說限府,接下來直接上干貨,如果文中有問題或有質(zhì)疑的地方可以直接修改痢缎,不勝感激胁勺。

二、PackageInstaller介紹

PackageInstaller是個神馬東西呢独旷?
我們知道安裝app有很多中方式秃诵,諸如adb install,應(yīng)用助手(豌豆莢),開機安裝(開機啟動時)糜值,下載到手機存儲后點擊安裝早龟。PackageInstaller這哥就是給手動安裝app提供一個界面的apk祝沸。
當(dāng)我們點安裝應(yīng)用時會啟動這個應(yīng)用來顯示安裝過程,安裝的事情并不是他在做麻养,正在安裝是由PackageManagerService來完成褐啡,當(dāng)然幕后英雄確是Installer。
代碼位置
packages/apps/PackageInstaller
疑問【以下有幾個問題鳖昌,如果親都知道的話备畦,那么可以不用再看本文啦】
如何使用PackageInstaller來安裝應(yīng)用低飒?
應(yīng)用首選安裝位置是在何時確定的?是在設(shè)置里設(shè)置的懂盐,還是在app中定義的褥赊,還是PackageManagerService這貨說了算?
安裝應(yīng)用過程中莉恼,哪些服務(wù)和類會插手這件事拌喉?
安裝過程中首先會生成一個.tmp臨時文件,這個文件在何時被rename為apk的俐银?
應(yīng)用都有uid尿背,這個uid是在什么時候被賦值的?
packages.xml and packages.list有什么用捶惜?
安裝app主要做了哪些事残家?

三、App安裝過程中涉及類

先來盤類圖看看售躁,如對PackageManagerService不熟悉的話,先看后面的流程茴晋,看完再來看這個類圖

安裝過程時會插手的主要類如下

packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java
frameworks/base /core/java/android/app/ApplicationPackageManager.java
frameworks/base /core/java/android/content/pm/PackageParser.java
frameworks/base /core/java/android/content/res/AssetManager.java
frameworks/base /packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
frameworks/base /services/java/com/android/server/pm/Installer.java
frameworks/base /services/java/com/android/server/pm/PackageManagerService.java
frameworks/base /services/java/com/android/server/pm/Settings.java

簡單說明這些類作用

  • PackageInstallerActivity.java:
    在文件管理器里點擊apk后就會調(diào)用該類陪捷,主要用于顯示要安裝的apk的一些權(quán)限信息
  • InstallAppProgress.java:
    當(dāng)看完所有權(quán)限后,點安裝后就會調(diào)用該類诺擅,用于顯示安裝進(jìn)度市袖,這時候- PackageManagerService 就在默默的安裝應(yīng)用
  • ApplicationPackageManager.java:
    這是類是PackageManager的兒子,我們使用mContext.getPackageManager得到的其實就是ApplicationPackageManager的對象烁涌,它爹PackageManager是個抽象類苍碟,對外的方法都定義在里面
  • PackageParser.java:
    解析app,主要解析apk中的AndroidManifest.xml撮执,解析里面的四大組件以及權(quán)限信息放入內(nèi)存里微峰,最后寫到packages.xml和package.list(/data/system下)中
  • AssetManager.java:
    把AndroidManifest.xml從app中拿出來給PackageParser.java去解析
  • DefaultContainerService.java:
    這個服務(wù)用于檢查存儲狀態(tài),得到合適的安裝位置
  • Installer.java:
    PackageManagerService調(diào)用它去執(zhí)行安裝抒钱,他會把PackageManagerService傳過來的數(shù)據(jù)封裝成命令蜓肆,然后讓底層的Installer去執(zhí)行
  • PackageManagerService.java:
    管理app的大神,安裝谋币、移動仗扬、卸載、查詢等都由他管

四蕾额、App安裝流程分析

先來個時序圖--安裝成功的時序圖早芭。點擊兩次可看大圖

安裝流程

流程分析,當(dāng)然對著代碼看上面的時序圖也很明了
1.當(dāng)點擊文件管理器中的apk時诅蝶,會調(diào)用FolderFragment的openFile方法退个,該方法里會將應(yīng)用信息傳給PackageInstallerActivity募壕,并啟動PackageInstaller

代碼位置:vendor/qcom/proprietary/qrdplus/FileExplorer/src/com/android/qrdfileexplorer/FolderFragment.java

private void openFile(File f) {
        final Uri fileUri = Uri.fromFile(f);
        final Intent intent = new Intent();
        intent.setAction(android.content.Intent.ACTION_VIEW);
        intent.putExtra(Intent.EXTRA_TITLE, f.getName());
        intent.putExtra(EXTRA_ALL_VIDEO_FOLDER, true);
        Uri contentUri = null;
        String type = getMIMEType(f);
        ......
            if (contentUri != null) {
                intent.setDataAndType(contentUri, type);
            } else {
                intent.setDataAndType(fileUri, type);
            }
            try {
                startActivitySafely(intent);
            } 
        ......
    }

2.PackageInstaller啟動過后會檢查是否開啟未知來源,未開啟就需要先進(jìn)入設(shè)置設(shè)置后帜乞,方可繼續(xù)安裝司抱,之后會依次調(diào)用initiateInstall()->startInstallConfirm();
在initiateInstall中會檢查是否已經(jīng)安裝過,是否是系統(tǒng)應(yīng)用等黎烈,調(diào)用startInstallConfirm去初始化界面习柠,顯示權(quán)限信息,當(dāng)點擊安裝按鈕時照棋,啟動安裝资溃,切換界面到InstallAppProgress

代碼位置:packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
 @Override
    protected void onCreate(Bundle icicle) {
        ......
        mPm = getPackageManager();
        boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
        ......
        initiateInstall();
    }
    private void initiateInstall() {
        String pkgName = mPkgInfo.packageName;
        String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
        if (oldName != null && oldName.length > 0 && oldName[0] != null) {
            pkgName = oldName[0];
            mPkgInfo.packageName = pkgName;
            mPkgInfo.applicationInfo.packageName = pkgName;
        }
        // Check if package is already installed. display confirmation dialog if replacing pkg
        try {
            // This is a little convoluted because we want to get all uninstalled
            // apps, but this may include apps with just data, and if it is just
            // data we still want to count it as "installed".
            mAppInfo = mPm.getApplicationInfo(pkgName,
                    PackageManager.GET_UNINSTALLED_PACKAGES);
            if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
                mAppInfo = null;
            }
        } catch (NameNotFoundException e) {
            mAppInfo = null;
        }
        mInstallFlowAnalytics.setReplace(mAppInfo != null);
        mInstallFlowAnalytics.setSystemApp(
                (mAppInfo != null) && ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0));
        startInstallConfirm();
    }

3.在InstallAppProgress中會調(diào)用initView去初始化界面并調(diào)用ApplicationPackageManager的installPackageWithVerificationAndEncryption方法來安裝

代碼位置:packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java
  @Override
    public void onCreate(Bundle icicle) {
        ......
        initView();
    }
    public void initView() {
        ......
        if ("package".equals(mPackageURI.getScheme())) {
            try {
                pm.installExistingPackage(mAppInfo.packageName);
                observer.packageInstalled(mAppInfo.packageName,
                        PackageManager.INSTALL_SUCCEEDED);
            } catch (PackageManager.NameNotFoundException e) {
                observer.packageInstalled(mAppInfo.packageName,
                        PackageManager.INSTALL_FAILED_INVALID_APK);
            }
        } else {
            pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
                    installerPackageName, verificationParams, null);
        }
    }

4.ApplicationPackageManager的installPackageWithVerificationAndEncryption里也是調(diào)用PMS的installPackageWithVerificationAndEncryption方法

代碼位置:frameworks/base/core/java/android/app/ApplicationPackageManager.java
    @Override
    public void installPackageWithVerificationAndEncryption(Uri packageURI,
            IPackageInstallObserver observer, int flags, String installerPackageName,
            VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
        try {
            mPM.installPackageWithVerificationAndEncryption(packageURI, observer, flags,
                    installerPackageName, verificationParams, encryptionParams);
        } catch (RemoteException e) {
            // Should never happen!
        }
    }

5.installPackageWithVerificationAndEncryption方法里,首先會獲取設(shè)置中的用戶安裝位置烈炭,并且會把InstallParams對象和安裝位置flag封裝到Message里溶锭,然后發(fā)出一個消息后就撒手不管了。

代碼位置:frameworks/base/services/java/com/android/server/pm/PackageManagerService.java
public void installPackageWithVerificationAndEncryption(Uri packageURI,
            IPackageInstallObserver observer, int flags, String installerPackageName,
            VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
                null);
        final int uid = Binder.getCallingUid();
        if(getInstallLocation() == PackageHelper.APP_INSTALL_INTERNAL){
            userFilteredFlags = flags |PackageManager.INSTALL_INTERNAL;
        } else if(getInstallLocation() == PackageHelper.APP_INSTALL_EXTERNAL){
            userFilteredFlags = flags |PackageManager.INSTALL_EXTERNAL;
        } else{
            userFilteredFlags = filteredFlags;
        }
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        msg.obj = new InstallParams(packageURI, observer, userFilteredFlags, installerPackageName,
                verificationParams, encryptionParams, user);
        mHandler.sendMessage(msg);
    }

6.接下來就該PackageHandler上場了符隙,會依次處理INIT_COPY趴捅、MCS_BOUN消息,這里面會去連接DefaultContainerService服務(wù)霹疫,接著會InstallParams的startCopy方法

代碼位置:frameworks/base/services/java/com/android/server/pm/PackageManagerService.java ->內(nèi)部類:PackageHandler
 public void handleMessage(Message msg) {
        try {
            doHandleMessage(msg);
        } finally {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        }
    }
    void doHandleMessage(Message msg) {
        switch (msg.what) {
            case INIT_COPY: {
                HandlerParams params = (HandlerParams) msg.obj;
                if (!mBound) {
                    if (!connectToService()) {
                        params.serviceError();
                        return;
                    } else {
                        mPendingInstalls.add(idx, params);
                    }
                } else {
                    mPendingInstalls.add(idx, params);
                    if (idx == 0) {
                        mHandler.sendEmptyMessage(MCS_BOUND);
                    }
                }
                break;
            }
            case MCS_BOUN: {
                if (msg.obj != null) {
                    mContainerService = (IMediaContainerService) msg.obj;
                }
                if (mContainerService == null) {
                    for (HandlerParams params : mPendingInstalls) {
                        params.serviceError();
                    }
                    mPendingInstalls.clear();
                } else if (mPendingInstalls.size() > 0) {
                    HandlerParams params = mPendingInstalls.get(0);
                    if (params != null) {
                        if (params.startCopy()) {
                            ......
                        }
                    }
                } else {
                    Slog.w(TAG, "Empty queue");
                }
                break;
            }
            ......
            case POST_INSTALL: {
                ...
                if (data != null) {
                    InstallArgs args = data.args;
                    PackageInstalledInfo res = data.res;
                    if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                        ......
                        sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                                res.pkg.applicationInfo.packageName, extras, null, null, firstUsers);
                        final boolean update = res.removedInfo.removedPackage != null;
                        if (update) {
                            extras.putBoolean(Intent.EXTRA_REPLACING, true);
                        }
                        sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                                res.pkg.applicationInfo.packageName, extras, null, null, updateUsers);
                        if (update) {
                            sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
                                    res.pkg.applicationInfo.packageName, extras, null, null, updateUsers);
                            sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
                                    null, null, res.pkg.applicationInfo.packageName, null, updateUsers);
                            if (isForwardLocked(res.pkg) || isExternal(res.pkg)) {
                                ......
                                sendResourcesChangedBroadcast(true, true, pkgList,uidArray, null);
                            }
                        }
                        if (res.removedInfo.args != null) {
                            deleteOld = true;
                        }
                    }
                    ......
                    if (args.observer != null) {
                        try {
                            args.observer.packageInstalled(res.name, res.returnCode);
                        } catch (RemoteException e) {
                            Slog.i(TAG, "Observer no longer exists.");
                        }
                    }
                } else {
                    Slog.e(TAG, "Bogus post-install token " + msg.arg1);
                }
                break;
            }
        }
    }

7.InstallParams的startCopy方法里拱绑,會調(diào)用handleStartCopy方法

代碼位置:frameworks/base/services/java/com/android/server/pm/PackageManagerService.java ->內(nèi)部類:InstallParams 繼承于HandlerParams
    final boolean startCopy() {
        boolean res;
        try {
            if (++mRetries > MAX_RETRIES) {
                Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                mHandler.sendEmptyMessage(MCS_GIVE_UP);
                handleServiceError();
                return false;
            } else {
                handleStartCopy();
                res = true;
            }
        } catch (RemoteException e) {
            mHandler.sendEmptyMessage(MCS_RECONNECT);
            res = false;
        }
        handleReturnCode();
        return res;
    }

8.handleStartCopy方法中會檢查應(yīng)用是否能安裝,如不合法則返回FAILED的CODE丽蝎,接著會調(diào)用DefaultContainerService的getMinimalPackageInfo方法猎拨,該方法用于獲取存儲狀態(tài),返回合適的安裝位置
如果返回碼是INSTALL_SUCCEEDED屠阻,那接下來就會調(diào)用InstallParams的copyApk红省,如果安裝到內(nèi)置,調(diào)用的就是FileInstallArgs的copyApk方法国觉,如安裝到外置就調(diào)用AsecInstallArgs的copyApk方法
AsecInstallArgs和FileInstallArgs都是InstallParams的子類

代碼位置:frameworks/base/services/java/com/android/server/pm/PackageManagerService.java ->內(nèi)部類:FileInstallArgs 繼承于InstallParams
public void handleStartCopy() throws RemoteException {
        ......
        if (onInt && onSd) {
            ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
        } else {
            ......
            try {
                mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, mPackageURI,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
                ........
                if (packageFile != null) {
                    ......
                    pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags, lowThreshold);
                }
            }
        }
        final InstallArgs args = createInstallArgs(this);
        mArgs = args;
        ......
        if (ret == PackageManager.INSTALL_SUCCEEDED) {
            ......
                ret = args.copyApk(mContainerService, true);
            ......
        }
        mRet = ret;
    }

9.copyApk方法中會依次調(diào)用FileInstallArgs 的createCopyFile->PackageManagerService的createTempPackageFile方法去創(chuàng)建臨時文件吧恃。

代碼位置:frameworks/base/services/java/com/android/server/pm/PackageManagerService.java ->內(nèi)部類:FileInstallArgs 繼承于InstallParams
          frameworks/base/services/java/com/android/server/pm/PackageManagerService.java
vmdl*.tmp就是copy成的臨時文件
    void createCopyFile() {
        installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir;
        codeFileName = createTempPackageFile(installDir).getPath();
        resourceFileName = getResourcePathFromCodePath();
        libraryPath = getLibraryPathFromCodePath();
        created = true;
    }
    private File createTempPackageFile(File installDir) {
        File tmpPackageFile;
        try {
            tmpPackageFile = File.createTempFile("vmdl", ".tmp", installDir);
        } catch (IOException e) {
            Slog.e(TAG, "Couldn't create temp file for downloaded package file.");
            return null;
        }
        try {
            FileUtils.setPermissions(
                    tmpPackageFile.getCanonicalPath(), FileUtils.S_IRUSR|FileUtils.S_IWUSR,
                    -1, -1);
            if (!SELinux.restorecon(tmpPackageFile)) {
                return null;
            }
        } catch (IOException e) {
            Slog.e(TAG, "Trouble getting the canoncical path for a temp file.");
            return null;
        }
        return tmpPackageFile;
    }

10.臨時文件已經(jīng)有了,handleStartCopy方法走完蛉加,接著回到步驟7蚜枢,調(diào)用InstallParams的handleReturnCode方法,handleReturnCode中會執(zhí)行processPendingInstall针饥,在該方法中做了大量工作

    @Override
    void handleReturnCode() {
        if (mArgs != null) {
            processPendingInstall(mArgs, mRet);
            if (mTempPackage != null) {
                if (!mTempPackage.delete()) {
                    Slog.w(TAG, "Couldn't delete temporary file: " +
                            mTempPackage.getAbsolutePath());
                }
            }
        }
    }

11.來看看processPendingInstall到底做了什么厂抽?processPendingInstall中最關(guān)鍵方法--installPackageLI,主要的操作(驗證簽名丁眼,創(chuàng)建/data/data筷凤,分配UID,dexopt)都在這個方法中完成。

    private void processPendingInstall(final InstallArgs args, final int currentStatus) {
        mHandler.post(new Runnable() {
            public void run() {
                mHandler.removeCallbacks(this);
                PackageInstalledInfo res = new PackageInstalledInfo();
                res.returnCode = currentStatus;
                res.uid = -1;
                res.pkg = null;
                res.removedInfo = new PackageRemovedInfo();
                if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                    args.doPreInstall(res.returnCode);
                    synchronized (mInstallLock) {
                        installPackageLI(args, true, res);
                    }
                    args.doPostInstall(res.returnCode, res.uid);
                }
                ......
                if (!doRestore) {
                    Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
                    mHandler.sendMessage(msg);
                }
            }
        });
    }

12.我們來看一下installPackageLI方法,首選會讓parsePackage去解析apk里的AndroidManifest.xml,使用的是parsePackage方法藐守,把解析出來的內(nèi)容放到Package對象中
接著調(diào)用doRename去將之前的tmp文件重命名為apk挪丢。apk已經(jīng)在/data/app下了,apk的屬性也被解析出來放在內(nèi)存(Package對象)中了
那么現(xiàn)在還需要做什么呢卢厂?apk有了乾蓬,數(shù)據(jù)目錄(/data/data)還沒有,所以后面會進(jìn)行uid賦值慎恒,驗證簽名任内,創(chuàng)建相應(yīng)的/data/data目錄,dexopt操作融柬,這些工作是由installNewPackageLI來完成

    private void installPackageLI(InstallArgs args,
            boolean newInstall, PackageInstalledInfo res) {
        ......
        int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
                | (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
        PackageParser pp = new PackageParser(tmpPackageFile.getPath());
        pp.setSeparateProcesses(mSeparateProcesses);
        final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,
                null, mMetrics, parseFlags);
        ......
        if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {
            res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
            return;
        }
        ......
        if (replace) {
            replacePackageLI(pkg, parseFlags, scanMode, args.user,
                    installerPackageName, res);
        } else {
            installNewPackageLI(pkg, parseFlags, scanMode | SCAN_DELETE_DATA_ON_FAILURES, args.user,
                    installerPackageName, res);
        }
        synchronized (mPackages) {
            final PackageSetting ps = mSettings.mPackages.get(pkgName);
            if (ps != null) {
                res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
            }
        }
    }

13.讓我們來看看installNewPackageLI具體怎么完成這些工作的吧死嗦。

private void installNewPackageLI(PackageParser.Package pkg,
            int parseFlags, int scanMode, UserHandle user,
            String installerPackageName, PackageInstalledInfo res) {
        ......
        PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode, System.currentTimeMillis(), user);
        if (newPackage == null) {
            ......
        } else {
            updateSettingsLI(newPackage, installerPackageName, null, null, res);
            ......
            if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
                deletePackageLI(pkgName, UserHandle.ALL, false, null, null, dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0,
                        res.removedInfo, true);
            }
        }
    }

14.scanPackageLI方法中,先調(diào)用getPackageLPw->newUserIdLPw(Settings類方法)去設(shè)置uid粒氧,在調(diào)用verifySignaturesLP驗證簽名
然后調(diào)用createDataDirsLI創(chuàng)建/data/data數(shù)據(jù)目錄越除,最后調(diào)用performDexOptLI進(jìn)行dexopt操作
createDataDirsLI是靠調(diào)用mInstaller.install方法來完成目錄創(chuàng)建,framework中的Installer會和底層幕后Installer勾兌外盯,完成目錄創(chuàng)建工作
performDexOptLI操作最后也是通過mInstaller.dexopt來完成的

    private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
            int parseFlags, int scanMode, long currentTime, UserHandle user) {
        ......
        synchronized (mPackages) {
            ......
            pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
                    destResourceFile, pkg.applicationInfo.nativeLibraryDir,
                    pkg.applicationInfo.flags, user, false);
            if (pkgSetting == null) {
                mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                return null;
            }
            ......
            if (!verifySignaturesLP(pkgSetting, pkg)) {
                if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
                    return null;
                }
                // The signature has changed, but this package is in the system
                // image...  let's recover!
                pkgSetting.signatures.mSignatures = pkg.mSignatures;
                // However...  if this package is part of a shared user, but it
                // doesn't match the signature of the shared user, let's fail.
                // What this means is that you can't change the signatures
                // associated with an overall shared user, which doesn't seem all
                // that unreasonable.
                if (pkgSetting.sharedUser != null) {
                    if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
                            pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
                        Log.w(TAG, "Signature mismatch for shared user : " + pkgSetting.sharedUser);
                        mLastScanError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
                        return null;
                    }
                }
                // File a report about this.
                String msg = "System package " + pkg.packageName
                        + " signature changed; retaining data.";
                reportSettingsProblem(Log.WARN, msg);
            }
            ......
        }
        ......
        if (mPlatformPackage == pkg) {
            ......
        } else {
            ......
            if (dataPath.exists()) {
               ......
            } else {
                ......
                int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
                                           pkg.applicationInfo.seinfo);
                if (ret < 0) {
                    // Error from installer
                    mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                    return null;
                }
                if (dataPath.exists()) {
                    pkg.applicationInfo.dataDir = dataPath.getPath();
                } else {
                    Slog.w(TAG, "Unable to create data directory: " + dataPath);
                    pkg.applicationInfo.dataDir = null;
                }
            }
            /*
             * Set the data dir to the default "/data/data/<package name>/lib"
             * if we got here without anyone telling us different (e.g., apps
             * stored on SD card have their native libraries stored in the ASEC
             * container with the APK).
             *
             * This happens during an upgrade from a package settings file that
             * doesn't have a native library path attribute at all.
             */
            if (pkg.applicationInfo.nativeLibraryDir == null && pkg.applicationInfo.dataDir != null) {
                if (pkgSetting.nativeLibraryPathString == null) {
                    setInternalAppNativeLibraryPath(pkg, pkgSetting);
                } else {
                    pkg.applicationInfo.nativeLibraryDir = pkgSetting.nativeLibraryPathString;
                }
            }
            pkgSetting.uidError = uidError;
        }
        ......
        // We also need to dexopt any apps that are dependent on this library.  Note that
        // if these fail, we should abort the install since installing the library will
        // result in some apps being broken.
        if (clientLibPkgs != null) {
            if ((scanMode&SCAN_NO_DEX) == 0) {
                for (int i=0; i<clientLibPkgs.size(); i++) {
                    PackageParser.Package clientPkg = clientLibPkgs.get(i);
                    if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                            == DEX_OPT_FAILED) {
                        if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
                            removeDataDirsLI(pkg.packageName);
                        }
 
                        mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                        return null;
                    }
                }
            }
        }
        // Request the ActivityManager to kill the process(only for existing packages)
        // so that we do not end up in a confused state while the user is still using the older
        // version of the application while the new one gets installed.
        ......
        // Also need to kill any apps that are dependent on the library.
        ......
        return pkg;
    }
    private int createDataDirsLI(String packageName, int uid, String seinfo) {
        int[] users = sUserManager.getUserIds();
        int res = mInstaller.install(packageName, uid, uid, seinfo);
        if (res < 0) {
            return res;
        }
        for (int user : users) {
            if (user != 0) {
                res = mInstaller.createUserData(packageName,
                        UserHandle.getUid(user, uid), user);
                if (res < 0) {
                    return res;
                }
            }
        }
        return res;
    }
    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
            boolean inclDependencies) {
        ......
        return performDexOptLI(pkg, forceDex, defer, done);
    }
    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
            HashSet<String> done) {
        ......
        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
            ......
            try {
                if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
                    if (!forceDex && defer) {
                        if (mDeferredDexOpt == null) {
                            mDeferredDexOpt = new HashSet<PackageParser.Package>();
                        }
                        mDeferredDexOpt.add(pkg);
                        return DEX_OPT_DEFERRED;
                    } else {
                        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
                        ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg));
                        pkg.mDidDexOpt = true;
                        performed = true;
                    }
                }
            } catch (FileNotFoundException e) {
                ......
            } catch (IOException e) {
                ......
            } catch (dalvik.system.StaleDexCacheError e) {
                ......
            } catch (Exception e) {
                ......
            }
            if (ret < 0) {
                //error from installer
                return DEX_OPT_FAILED;
            }
        }
        return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
    }
performDexOptLI后生成的文件

15.到目前為止摘盆,scanPackageLI已經(jīng)走完了,接下來就該更新packages.list,packages.xml了
系統(tǒng)中所有app的信息都保存在這兩個文件中饱苟,當(dāng)有app安裝骡澈、卸載、更新時都會更新這兩個文件
回到步驟13掷空,當(dāng)installNewPackageLI中的scanPackageLI走完后,后面會調(diào)用updateSettingsLI去更新文件
mSettings.writeLPr()來完成往packages.list,packages.xml中更新數(shù)據(jù)

    private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
            int[] allUsers, boolean[] perUserInstalled,
            PackageInstalledInfo res) {
        String pkgName = newPackage.packageName;
        synchronized (mPackages) {
            ......
            mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_INCOMPLETE);
            mSettings.writeLPr();
        }
        ......
        synchronized (mPackages) {
            updatePermissionsLPw(newPackage.packageName, newPackage,
                    UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
                            ? UPDATE_PERMISSIONS_ALL : 0));
            ......
            mSettings.writeLPr();
        }
    }

packages.list文件部分截取

packages.xml部分截取囤锉,包含包名坦弟,安裝時間,簽名官地,權(quán)限酿傍,文件安裝路徑等信息

16.installPackageLI到這里已經(jīng)執(zhí)行完了,現(xiàn)在回到步驟11驱入,后續(xù)會執(zhí)行到Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); mHandler.sendMessage(msg);發(fā)送消息
消息發(fā)出后赤炒,回到步驟POST_INSTALL,這里面主要做了兩件事亏较,發(fā)生一條ACTION_PACKAGE_ADDED廣播告訴大家莺褒,又有新包了,這是launcher什么的趕緊把圖標(biāo)加上
然后回調(diào)args.observer.packageInstalled(res.name, res.returnCode);告訴PackageInstaller安裝結(jié)果
然后就顯示安裝完成界面雪情。歐拉遵岩,應(yīng)用安裝結(jié)束。

五、總結(jié)

小結(jié)一下安裝app到底主要做了哪些事情尘执?

1.驗證是否允許安裝未知來源應(yīng)用
2.得到用戶設(shè)置的首選安裝位置
3.檢驗app有效性舍哄,檢查存儲狀態(tài),得到最佳安裝位置
4.拷貝app到安裝位置誊锭,此時為.tmp臨時文件
5.解析AndroidManifest.xml
6.重命名tmp為apk
7.賦值UID表悬,驗證權(quán)限
8.創(chuàng)建/data/data/下數(shù)據(jù)目錄
9.執(zhí)行dexopt操作
10.更新packages.xml,packages.list
11.發(fā)送廣播,回調(diào)安裝狀態(tài)

原文鏈接:sgzy001/article/details/44857057

六丧靡、參考文獻(xiàn)

【騰訊文檔】Android Framework 知識庫
https://docs.qq.com/doc/DSXBmSG9VbEROUXF5

至此蟆沫,本篇已結(jié)束。轉(zhuǎn)載網(wǎng)絡(luò)的文章窘行,小編覺得很優(yōu)秀饥追,歡迎點擊閱讀原文,支持原創(chuàng)作者罐盔,如有侵權(quán)但绕,懇請聯(lián)系小編刪除,歡迎您的建議與指正惶看。同時期待您的關(guān)注捏顺,感謝您的閱讀,謝謝纬黎!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末幅骄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子本今,更是在濱河造成了極大的恐慌拆座,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冠息,死亡現(xiàn)場離奇詭異挪凑,居然都是意外死亡,警方通過查閱死者的電腦和手機逛艰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門躏碳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人散怖,你說我怎么就攤上這事菇绵。” “怎么了镇眷?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵咬最,是天一觀的道長。 經(jīng)常有香客問我欠动,道長丹诀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮铆遭,結(jié)果婚禮上硝桩,老公的妹妹穿的比我還像新娘。我一直安慰自己枚荣,他們只是感情好碗脊,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著橄妆,像睡著了一般衙伶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上害碾,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天矢劲,我揣著相機與錄音,去河邊找鬼慌随。 笑死芬沉,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的阁猜。 我是一名探鬼主播丸逸,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼剃袍!你這毒婦竟也來了黄刚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤民效,失蹤者是張志新(化名)和其女友劉穎憔维,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畏邢,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡埋同,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了棵红。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡咧栗,死狀恐怖逆甜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情致板,我是刑警寧澤交煞,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站斟或,受9級特大地震影響素征,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一御毅、第九天 我趴在偏房一處隱蔽的房頂上張望根欧。 院中可真熱鬧,春花似錦端蛆、人聲如沸凤粗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫌拣。三九已至,卻和暖如春呆躲,著一層夾襖步出監(jiān)牢的瞬間异逐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工插掂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灰瞻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓燥筷,卻偏偏與公主長得像箩祥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肆氓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容