Android包管理機制(三)PMS處理APK的安裝

相關(guān)文章
Android包管理機制系列

前言

在上一篇文章Android包管理機制(二)PackageInstaller安裝APK中,我們學(xué)習(xí)了PackageInstaller是如何安裝APK的笋额,最后會將APK的信息交由PMS處理兄猩。那么PMS是如何處理的呢援岩?這篇文章會給你答案。

1.PackageHandler處理安裝消息

APK的信息交由PMS后趟咆,PMS通過向PackageHandler發(fā)送消息來驅(qū)動APK的復(fù)制和安裝工作。
先來查看PackageHandler處理安裝消息的調(diào)用時序圖虐唠。

接著上一篇文章的代碼邏輯來查看PMS的installStage方法。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

 void installStage(String packageName, File stagedDir, String stagedCid,
            IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
            String installerPackageName, int installerUid, UserHandle user,
            Certificate[][] certificates) {
        ...
        final Message msg = mHandler.obtainMessage(INIT_COPY);//1
        final int installReason = fixUpInstallReason(installerPackageName, installerUid,
                sessionParams.installReason);
        final InstallParams params = new InstallParams(origin, null, observer,
                sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
                verificationInfo, user, sessionParams.abiOverride,
                sessionParams.grantedRuntimePermissions, certificates, installReason);//2
        params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
        msg.obj = params;
        ...
        mHandler.sendMessage(msg);//3
    }

注釋2處創(chuàng)建InstallParams杆故,它對應(yīng)于包的安裝數(shù)據(jù)。注釋1處創(chuàng)建了類型為INIT_COPY的消息撤蟆,在注釋3處將InstallParams通過消息發(fā)送出去家肯。

1.1 對INIT_COPY的消息的處理

處理INIT_COPY類型的消息的代碼如下所示。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#PackageHandler

        void doHandleMessage(Message msg) {
            switch (msg.what) {
                case INIT_COPY: {
                    HandlerParams params = (HandlerParams) msg.obj;
                    int idx = mPendingInstalls.size();
                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
                    //mBound用于標(biāo)識是否綁定了服務(wù)值依,默認值為false
                    if (!mBound) {//1
                        Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                                System.identityHashCode(mHandler));
                        //如果沒有綁定服務(wù),重新綁定,connectToService方法內(nèi)部如果綁定成功會將mBound置為true
                        if (!connectToService()) {//2
                            Slog.e(TAG, "Failed to bind to media container service");
                            params.serviceError();
                            Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                                    System.identityHashCode(mHandler));
                            if (params.traceMethod != null) {
                                Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, params.traceMethod,
                                        params.traceCookie);
                            }
                            //綁定服務(wù)失敗則return
                            return;
                        } else {
                            //綁定服務(wù)成功辆亏,將請求添加到ArrayList類型的mPendingInstalls中风秤,等待處理
                            mPendingInstalls.add(idx, params);
                        }
                    } else {
                    //已經(jīng)綁定服務(wù)
                        mPendingInstalls.add(idx, params);
                        if (idx == 0) {
                            mHandler.sendEmptyMessage(MCS_BOUND);//3
                        }
                    }
                    break;
                }
                ....
                }
    }
 }

PackageHandler繼承自Handler,它被定義在PMS中扮叨,doHandleMessage方法用于處理各個類型的消息缤弦,來查看對INIT_COPY類型消息的處理。注釋1處的mBound用于標(biāo)識是否綁定了DefaultContainerService彻磁,默認值為false。DefaultContainerService是用于檢查和復(fù)制可移動文件的服務(wù)衷蜓,這是一個比較耗時的操作累提,因此DefaultContainerService沒有和PMS運行在同一進程中,它運行在com.android.defcontainer進程磁浇,通過IMediaContainerService和PMS進行IPC通信斋陪,如下圖所示。


注釋2處的connectToService方法用來綁定DefaultContainerService置吓,注釋3處發(fā)送MCS_BOUND類型的消息无虚,觸發(fā)處理第一個安裝請求。
查看注釋2處的connectToService方法:
**frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#PackageHandler **

  private boolean connectToService() {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
                    " DefaultContainerService");
            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
            if (mContext.bindServiceAsUser(service, mDefContainerConn,
                    Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) {//1
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                mBound = true;//2
                return true;
            }
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return false;
        }

注釋2處如果綁定DefaultContainerService成功交洗,mBound會置為ture 骑科。注釋1處的bindServiceAsUser方法會傳入mDefContainerConn,bindServiceAsUser方法的處理邏輯和我們調(diào)用bindService是類似的构拳,服務(wù)建立連接后咆爽,會調(diào)用onServiceConnected方法:
**frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java **

  class DefaultContainerConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName name, IBinder service) {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
            final IMediaContainerService imcs = IMediaContainerService.Stub
                    .asInterface(Binder.allowBlocking(service));
            mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, Object));//1
        }
        public void onServiceDisconnected(ComponentName name) {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected");
        }
    }

注釋1處發(fā)送了MCS_BOUND類型的消息,與PackageHandler.doHandleMessage方法的注釋3處不同的是置森,這里發(fā)送消息帶了Object類型的參數(shù)斗埂,這里會對這兩種情況來進行講解,一種是消息不帶Object類型的參數(shù)凫海,一種是消息帶Object類型的參數(shù)呛凶。

1.2 對MCS_BOUND類型的消息的處理

消息不帶Object類型的參數(shù)
查看對MCS_BOUND類型消息的處理:

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

case MCS_BOUND: {
            if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
            if (msg.obj != null) {//1
                mContainerService = (IMediaContainerService) msg.obj;//2
                Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                        System.identityHashCode(mHandler));
            }
            if (mContainerService == null) {//3
                if (!mBound) {//4
                      Slog.e(TAG, "Cannot bind to media container service");
                      for (HandlerParams params : mPendingInstalls) {
                          params.serviceError();//5
                          Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                                        System.identityHashCode(params));
                          if (params.traceMethod != null) {
                          Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER,
                           params.traceMethod, params.traceCookie);
                          }
                          return;
                      }   
                          //綁定失敗,清空安裝請求隊列
                          mPendingInstalls.clear();
                   } else {
                          //繼續(xù)等待綁定服務(wù)
                          Slog.w(TAG, "Waiting to connect to media container service");
                   }
            } else if (mPendingInstalls.size() > 0) {
              ...
              else {
                   Slog.w(TAG, "Empty queue");
                   }
            break;
        }

如果消息不帶Object類型的參數(shù)行贪,就無法滿足注釋1處的條件漾稀,注釋2處的IMediaContainerService類型的mContainerService也無法被賦值,這樣就滿足了注釋3處的條件建瘫。
如果滿足注釋4處的條件崭捍,說明還沒有綁定服務(wù),而此前已經(jīng)在PackageHandler.doHandleMessage方法的注釋2處調(diào)用綁定服務(wù)的方法了啰脚,這顯然是不正常的殷蛇,因此在注釋5處負責(zé)處理服務(wù)發(fā)生錯誤的情況。如果不滿足注釋4處的條件,說明已經(jīng)綁定服務(wù)了粒梦,就會打印出系統(tǒng)log亮航,告知用戶等待系統(tǒng)綁定服務(wù)。

消息帶Object類型的參數(shù)
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

case MCS_BOUND: {
            if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
            if (msg.obj != null) {
            ...
            }
            if (mContainerService == null) {//1
             ...
            } else if (mPendingInstalls.size() > 0) {//2
                          HandlerParams params = mPendingInstalls.get(0);//3
                        if (params != null) {
                            Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                                    System.identityHashCode(params));
                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");
                            if (params.startCopy()) {//4
                                if (DEBUG_SD_INSTALL) Log.i(TAG,
                                        "Checking for more work or unbind...");
                                 //如果APK安裝成功匀们,刪除本次安裝請求
                                if (mPendingInstalls.size() > 0) {
                                    mPendingInstalls.remove(0);
                                }
                                if (mPendingInstalls.size() == 0) {
                                    if (mBound) {
                                    //如果沒有安裝請求了缴淋,發(fā)送解綁服務(wù)的請求
                                        if (DEBUG_SD_INSTALL) Log.i(TAG,
                                                "Posting delayed MCS_UNBIND");
                                        removeMessages(MCS_UNBIND);
                                        Message ubmsg = obtainMessage(MCS_UNBIND);
                                        sendMessageDelayed(ubmsg, 10000);
                                    }
                                } else {
                                    if (DEBUG_SD_INSTALL) Log.i(TAG,
                                            "Posting MCS_BOUND for next work");
                                   //如果還有其他的安裝請求,接著發(fā)送MCS_BOUND消息繼續(xù)處理剩余的安裝請求       
                                    mHandler.sendEmptyMessage(MCS_BOUND);//5
                                }
                            }
                            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                        }else {
                        Slog.w(TAG, "Empty queue");//6
                    }
            break;
        }

如果MCS_BOUND類型消息帶Object類型的參數(shù)就不會滿足注釋1處的條件昼蛀,就會調(diào)用注釋2處的判斷宴猾,如果安裝請求數(shù)不大于0就會打印出注釋6處的log,說明安裝請求隊列是空的叼旋。安裝完一個APK后,就會在注釋5處發(fā)出MSC_BOUND消息沦辙,繼續(xù)處理剩下的安裝請求直到安裝請求隊列為空夫植。
注釋3處得到安裝請求隊列第一個請求HandlerParams ,如果HandlerParams 不為null就會調(diào)用注釋4處的HandlerParams的startCopy方法油讯,用于開始復(fù)制APK的流程详民。

2.復(fù)制APK

先來查看復(fù)制APK的時序圖。

HandlerParams是PMS中的抽象類陌兑,它的實現(xiàn)類為PMS的內(nèi)部類InstallParams沈跨。HandlerParams的startCopy方法如下所示。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#HandlerParams

 final boolean startCopy() {
            boolean res;
            try {
                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
                //startCopy方法嘗試的次數(shù)兔综,超過了4次饿凛,就放棄這個安裝請求
                if (++mRetries > MAX_RETRIES) {//1
                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                    mHandler.sendEmptyMessage(MCS_GIVE_UP);//2
                    handleServiceError();
                    return false;
                } else {
                    handleStartCopy();//3
                    res = true;
                }
            } catch (RemoteException e) {
                if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
                mHandler.sendEmptyMessage(MCS_RECONNECT);
                res = false;
            }
            handleReturnCode();//4
            return res;
        }

注釋1處的mRetries用于記錄startCopy方法調(diào)用的次數(shù),調(diào)用startCopy方法時會先自動加1软驰,如果次數(shù)大于4次就放棄這個安裝請求:在注釋2處發(fā)送MCS_GIVE_UP類型消息涧窒,將第一個安裝請求(本次安裝請求)從安裝請求隊列mPendingInstalls中移除掉。注釋4處用于處理復(fù)制APK后的安裝APK邏輯锭亏,第3小節(jié)中會再次提到它纠吴。注釋3處調(diào)用了抽象方法handleStartCopy,它的實現(xiàn)在InstallParams中慧瘤,如下所示戴已。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#InstallParams

     public void handleStartCopy() throws RemoteException {
            ...
            //確定APK的安裝位置。onSd:安裝到SD卡锅减, onInt:內(nèi)部存儲即Data分區(qū)糖儡,ephemeral:安裝到臨時存儲(Instant Apps安裝)            
            final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
            final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
            final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
            PackageInfoLite pkgLite = null;
            if (onInt && onSd) {
              // APK不能同時安裝在SD卡和Data分區(qū)
                Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
              //安裝標(biāo)志沖突,Instant Apps不能安裝到SD卡中
            } else if (onSd && ephemeral) {
                Slog.w(TAG,  "Conflicting flags specified for installing ephemeral on external");
                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
            } else {
                 //獲取APK的少量的信息
                pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
                        packageAbiOverride);//1
                if (DEBUG_EPHEMERAL && ephemeral) {
                    Slog.v(TAG, "pkgLite for install: " + pkgLite);
                }
            ...
            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                 //判斷安裝的位置
                int loc = pkgLite.recommendedInstallLocation;
                if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
                    ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
                } 
                ...
                }else{
                  loc = installLocationPolicy(pkgLite);//2
                  ...
                }
            }
            //根據(jù)InstallParams創(chuàng)建InstallArgs對象
            final InstallArgs args = createInstallArgs(this);//3
            mArgs = args;
            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                   ...
                if (!origin.existing && requiredUid != -1
                        && isVerificationEnabled(
                              verifierUser.getIdentifier(), installFlags, installerUid)) {
                      ...
                } else{
                    ret = args.copyApk(mContainerService, true);//4
                }
            }
            mRet = ret;
        }

handleStartCopy方法的代碼很多上煤,這里截取關(guān)鍵的部分休玩。
注釋1處通過IMediaContainerService跨進程調(diào)用DefaultContainerService的getMinimalPackageInfo方法,該方法輕量解析APK并得到APK的少量信息,輕量解析的原因是這里不需要得到APK的全部信息拴疤,APK的少量信息會封裝到PackageInfoLite中永部。接著在注釋2處確定APK的安裝位置。注釋3處創(chuàng)建了InstallArgs呐矾,InstallArgs 是一個抽象類苔埋,定義了APK的安裝邏輯,比如復(fù)制和重命名APK等蜒犯,它有3個子類组橄,都被定義在PMS中,如下圖所示罚随。


其中FileInstallArgs用于處理安裝到非ASEC的存儲空間的APK玉工,也就是內(nèi)部存儲空間(Data分區(qū)),AsecInstallArgs用于處理安裝到ASEC中(mnt/asec)即SD卡中的APK淘菩。MoveInstallArgs用于處理已安裝APK的移動的邏輯遵班。
對APK進行檢查后就會在注釋4處調(diào)用InstallArgs的copyApk方法進行安裝。
不同的InstallArgs子類會有著不同的處理潮改,這里以FileInstallArgs為例狭郑。FileInstallArgs的copyApk方法中會直接return FileInstallArgs的doCopyApk方法:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#FileInstallArgs

   private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
           ...
            try {
                final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
                //創(chuàng)建臨時文件存儲目錄
                final File tempDir =
                        mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);//1
                codeFile = tempDir;
                resourceFile = tempDir;
            } catch (IOException e) {
                Slog.w(TAG, "Failed to create copy file: " + e);
                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
            }
            ...
            int ret = PackageManager.INSTALL_SUCCEEDED;
            ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);//2
            ...
            return ret;
        }

注釋1處用于創(chuàng)建臨時存儲目錄,比如/data/app/vmdl18300388.tmp汇在,其中18300388是安裝的sessionId翰萨。注釋2處通過IMediaContainerService跨進程調(diào)用DefaultContainerService的copyPackage方法,這個方法會在DefaultContainerService所在的進程中將APK復(fù)制到臨時存儲目錄糕殉,比如/data/app/vmdl18300388.tmp/base.apk亩鬼。目前為止APK的復(fù)制工作就完成了,接著就是APK的安裝過程了糙麦。

3.安裝APK

照例先來查看安裝APK的時序圖辛孵。

安裝APK_副本.png

我們回到APK的復(fù)制調(diào)用鏈的頭部方法:HandlerParams的startCopy方法,在注釋4處會調(diào)用handleReturnCode方法赡磅,它的實現(xiàn)在InstallParams中魄缚,如下所示。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

 void handleReturnCode() {
    if (mArgs != null) {
        processPendingInstall(mArgs, mRet);
    }
}

    private void processPendingInstall(final InstallArgs args, final int currentStatus) {
        mHandler.post(new Runnable() {
            public void run() {
                mHandler.removeCallbacks(this);
                PackageInstalledInfo res = new PackageInstalledInfo();
                res.setReturnCode(currentStatus);
                res.uid = -1;
                res.pkg = null;
                res.removedInfo = null;
                if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                    //安裝前處理
                    args.doPreInstall(res.returnCode);//1
                    synchronized (mInstallLock) {
                        installPackageTracedLI(args, res);//2
                    }
                    //安裝后收尾
                    args.doPostInstall(res.returnCode, res.uid);//3
                }
              ...
            }
        });
    }

handleReturnCode方法中只調(diào)用了processPendingInstall方法焚廊,注釋1處用于檢查APK的狀態(tài)的冶匹,在安裝前確保安裝環(huán)境的可靠,如果不可靠會清除復(fù)制的APK文件咆瘟,注釋3處用于處理安裝后的收尾操作嚼隘,如果安裝不成功,刪除掉安裝相關(guān)的目錄與文件袒餐。主要來看注釋2處的installPackageTracedLI方法飞蛹,其內(nèi)部會調(diào)用PMS的installPackageLI方法谤狡。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
    ...
    PackageParser pp = new PackageParser();
    pp.setSeparateProcesses(mSeparateProcesses);
    pp.setDisplayMetrics(mMetrics);
    pp.setCallback(mPackageParserCallback);
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
    final PackageParser.Package pkg;
    try {
        //解析APK
        pkg = pp.parsePackage(tmpPackageFile, parseFlags);//1
    } catch (PackageParserException e) {
        res.setError("Failed parse during installPackageLI", e);
        return;
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
    ...
    pp = null;
    String oldCodePath = null;
    boolean systemApp = false;
    synchronized (mPackages) {
        // 檢查APK是否存在
        if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
            String oldName = mSettings.getRenamedPackageLPr(pkgName);//獲取沒被改名前的包名
            if (pkg.mOriginalPackages != null
                    && pkg.mOriginalPackages.contains(oldName)
                    && mPackages.containsKey(oldName)) {
                pkg.setPackageName(oldName);//2
                pkgName = pkg.packageName;
                replace = true;//設(shè)置標(biāo)志位表示是替換安裝
                if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName="
                        + oldName + " pkgName=" + pkgName);
            } 
            ...
        }
        PackageSetting ps = mSettings.mPackages.get(pkgName);
        //查看Settings中是否存有要安裝的APK的信息,如果有就獲取簽名信息
        if (ps != null) {//3
            if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
            PackageSetting signatureCheckPs = ps;
            if (pkg.applicationInfo.isStaticSharedLibrary()) {
                SharedLibraryEntry libraryEntry = getLatestSharedLibraVersionLPr(pkg);
                if (libraryEntry != null) {
                    signatureCheckPs = mSettings.getPackageLPr(libraryEntry.apk);
                }
            }
            //檢查簽名的正確性
            if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
                if (!checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
                    res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
                            + pkg.packageName + " upgrade keys do not match the "
                            + "previously installed version");
                    return;
                }
            } 
            ...
        }

        int N = pkg.permissions.size();
        for (int i = N-1; i >= 0; i--) {
           //遍歷每個權(quán)限卧檐,對權(quán)限進行處理
            PackageParser.Permission perm = pkg.permissions.get(i);
            BasePermission bp = mSettings.mPermissions.get(perm.info.name);
         
            }
        }
    }
    if (systemApp) {
        if (onExternal) {
            //系統(tǒng)APP不能在SD卡上替換安裝
            res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
                    "Cannot install updates to system apps on sdcard");
            return;
        } else if (instantApp) {
            //系統(tǒng)APP不能被Instant App替換
            res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
                    "Cannot update a system app with an instant app");
            return;
        }
    }
    ...
    //重命名臨時文件
    if (!args.doRename(res.returnCode, pkg, oldCodePath)) {//4
        res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
        return;
    }

    startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);

    try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
            "installPackageLI")) {
       
        if (replace) {//5
         //替換安裝   
           ...
            replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
                    installerPackageName, res, args.installReason);
        } else {
        //安裝新的APK
            installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
                    args.user, installerPackageName, volumeUuid, res, args.installReason);
        }
    }

    synchronized (mPackages) {
        final PackageSetting ps = mSettings.mPackages.get(pkgName);
        if (ps != null) {
            //更新應(yīng)用程序所屬的用戶
            res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
            ps.setUpdateAvailable(false /*updateAvailable*/);
        }
        ...
    }
}

installPackageLI方法的代碼有將近500行墓懂,這里截取主要的部分,主要做了幾件事:

  1. 創(chuàng)建PackageParser解析APK霉囚。
  2. 檢查APK是否存在捕仔,如果存在就獲取此前沒被改名前的包名并在注釋1處賦值給PackageParser.Package類型的pkg,在注釋3處將標(biāo)志位replace置為true表示是替換安裝盈罐。
  3. 注釋3處榜跌,如果Settings中保存有要安裝的APK的信息,說明此前安裝過該APK盅粪,則需要校驗APK的簽名信息钓葫,確保安全的進行替換。
  4. 在注釋4處將臨時文件重新命名票顾,比如前面提到的/data/app/vmdl18300388.tmp/base.apk瓤逼,重命名為/data/app/包名-1/base.apk。這個新命名的包名會帶上一個數(shù)字后綴1库物,每次升級一個已有的App,這個數(shù)字會不斷的累加贷帮。
  5. 系統(tǒng)APP的更新安裝會有兩個限制戚揭,一個是系統(tǒng)APP不能在SD卡上替換安裝,另一個是系統(tǒng)APP不能被Instant App替換撵枢。
  6. 注釋5處根據(jù)replace來做區(qū)分民晒,如果是替換安裝就會調(diào)用replacePackageLIF方法,其方法內(nèi)部還會對系統(tǒng)APP和非系統(tǒng)APP進行區(qū)分處理锄禽,如果是新安裝APK會調(diào)用installNewPackageLIF方法潜必。

這里我們以新安裝APK為例,會調(diào)用PMS的installNewPackageLIF方法沃但。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

 private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
            int scanFlags, UserHandle user, String installerPackageName, String volumeUuid,
            PackageInstalledInfo res, int installReason) {
        ...
        try {
            //掃描APK
            PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags,
                    System.currentTimeMillis(), user);
            //更新Settings信息
            updateSettingsLI(newPackage, installerPackageName, null, res, user, installReason);
            if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                //安裝成功后磁滚,為新安裝的應(yīng)用程序準(zhǔn)備數(shù)據(jù)
                prepareAppDataAfterInstallLIF(newPackage);

            } else {
                //安裝失敗則刪除APK
                deletePackageLIF(pkgName, UserHandle.ALL, false, null,
                        PackageManager.DELETE_KEEP_DATA, res.removedInfo, true, null);
            }
        } catch (PackageManagerException e) {
            res.setError("Package couldn't be installed in " + pkg.codePath, e);
        }
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }

installNewPackageLIF主要做了以下3件事:

  1. 掃描APK,將APK的信息存儲在PackageParser.Package類型的newPackage中宵晚,一個Package的信息包含了1個base APK以及0個或者多個split APK垂攘。
  2. 更新該APK對應(yīng)的Settings信息,Settings用于保存所有包的動態(tài)設(shè)置淤刃。
  3. 如果安裝成功就為新安裝的應(yīng)用程序準(zhǔn)備數(shù)據(jù)晒他,安裝失敗就刪除APK。

安裝APK的過程就講到這里逸贾,就不再往下分析下去陨仅,有興趣的同學(xué)可以接著深挖津滞。

4. 總結(jié)

本文主要講解了PMS是如何處理APK安裝的,主要有幾個步驟:

  1. PackageInstaller安裝APK時會將APK的信息交由PMS處理灼伤,PMS通過向PackageHandler發(fā)送消息來驅(qū)動APK的復(fù)制和安裝工作触徐。
  2. PMS發(fā)送INIT_COPY和MCS_BOUND類型的消息,控制PackageHandler來綁定DefaultContainerService饺蔑,完成復(fù)制APK等工作锌介。
  3. 復(fù)制APK完成后,會開始進行安裝APK的流程猾警,包括安裝前的檢查孔祸、安裝APK和安裝后的收尾工作。

參考資料
Android包管理機制
Android app安裝過程分析(基于Nougat)
應(yīng)用程序安裝流程

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末发皿,一起剝皮案震驚了整個濱河市崔慧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌穴墅,老刑警劉巖惶室,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異玄货,居然都是意外死亡皇钞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門松捉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夹界,“玉大人,你說我怎么就攤上這事隘世】墒粒” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵丙者,是天一觀的道長复斥。 經(jīng)常有香客問我,道長械媒,這世上最難降的妖魔是什么目锭? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮滥沫,結(jié)果婚禮上侣集,老公的妹妹穿的比我還像新娘。我一直安慰自己兰绣,他們只是感情好世分,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缀辩,像睡著了一般臭埋。 火紅的嫁衣襯著肌膚如雪踪央。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天瓢阴,我揣著相機與錄音畅蹂,去河邊找鬼。 笑死荣恐,一個胖子當(dāng)著我的面吹牛液斜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叠穆,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼少漆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了硼被?” 一聲冷哼從身側(cè)響起示损,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嚷硫,沒想到半個月后检访,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡仔掸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年脆贵,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片起暮。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡丹禀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鞋怀,到底是詐尸還是另有隱情,我是刑警寧澤持搜,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布密似,位于F島的核電站,受9級特大地震影響葫盼,放射性物質(zhì)發(fā)生泄漏残腌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一贫导、第九天 我趴在偏房一處隱蔽的房頂上張望抛猫。 院中可真熱鬧,春花似錦孩灯、人聲如沸闺金。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽败匹。三九已至寨昙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掀亩,已是汗流浹背舔哪。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留槽棍,地道東北人捉蚤。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像炼七,于是被迫代替她去往敵國和親缆巧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

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