深刻Android系統(十)PMS-3-應用安裝過程

研究應用的安裝過程,老樣子,咱們仍是先從使用入手。java

Android中,經過發送Intent就能夠啓動應用的安裝過程,好比:android

Uri uri = Uri.fromFile(new File(apkFilePath));
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(uri,"application/vnd.android.package-archive");
    startActivity(intent);
複製代碼

而在Android的系統應用PackageInstaller中有一個InstallStart會響應這個Intent數組

<activity android:name=".InstallStart" android:theme="@style/Installer" android:exported="true" android:excludeFromRecents="true">
            <intent-filter android:priority="1">
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:scheme="content" />
                <data android:mimeType="application/vnd.android.package-archive" />
            </intent-filter>
            ......
        </activity>
複製代碼

InstallStart中會進行各類Uri的判斷,最終會跳轉到一個叫作PackageInstallerActivity的界面。markdown

國內的廠商基本上會在InstallStart這裏進行修改,替換爲本身的安裝界面session

對於PackageInstallerActivity來講,它的主要做用是數據結構

  • 解析Uri協議進行解析,包括filepackage兩種
    • 若是是file協議會解析APK文件獲得包信息PackageInfo
  • 對未知來源進行處理
    • 若是容許安裝未知來源或者根據Intent判斷得出該APK不是未知來源,就會初始化安裝確認界面
    • 若是管理員限制來自未知源的安裝, 就彈出提示Dialog或者跳轉到設置界面
  • 提取並在界面展現應用對應的權限

當點擊確認安裝後,其實會執行到startInstall()方法,相關內容以下:併發

private void startInstall() {
        // Start subactivity to actually install the application
        Intent newIntent = new Intent();
        ......
        newIntent.setData(mPackageURI);
        newIntent.setClass(this, InstallInstalling.class);
        ......
        startActivity(newIntent);
        finish();
    }
複製代碼

上面的邏輯是跳轉到InstallInstalling界面進行安裝,咱們看看這個Activityapp

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ......
        if ("package".equals(mPackageURI.getScheme())) {
            // 直接安裝 package
            try {
                getPackageManager().installExistingPackage(appInfo.packageName);
                launchSuccess();
            } catch (PackageManager.NameNotFoundException e) {
                launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
            }
        } else {
            // 對於 file 安裝,按照session的邏輯去走
            // 這部分在onResume中開始
            final File sourceFile = new File(mPackageURI.getPath());
            ......
            if (savedInstanceState != null) {
                mSessionId = savedInstanceState.getInt(SESSION_ID);
                mInstallId = savedInstanceState.getInt(INSTALL_ID);
                ......
            } else {
                ......
                mSessionId = getPackageManager().getPackageInstaller().createSession(params);
                ......
                // 建立應用安裝狀態的Observer
                // 真正註冊是在後面 Session 的 commitLocked 中
                // InstallEventReceiver 這裏是作了二次封裝,方便進行持久化操做
                InstallEventReceiver.addObserver(this, mInstallId,
                            this::launchFinishBasedOnResult);
            }
            ......
        }
    }
    @Override
    protected void onResume() {
        super.onResume();
        // This is the first onResume in a single life of the activity
        if (mInstallingTask == null) {
            PackageInstaller installer = getPackageManager().getPackageInstaller();
            PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
            // 啓動異步安裝任務 InstallingAsyncTask 進行一系列的session操做
            if (sessionInfo != null && !sessionInfo.isActive()) {
                mInstallingTask = new InstallingAsyncTask();
                mInstallingTask.execute();
            }
            ......
        }
    }
複製代碼

從上面的方法咱們能夠看到安裝操做主要分爲兩個接口:異步

  • getPackageManager().installExistingPackage():主要是對已經並掃描到SettingsmPackages集合中的應用進行操做
    • 根據當前的userID從集合中查詢應用狀態,若是存在而且應用沒有安裝,就進行一些簡單的配置工做
    • 最後經過prepareAppDataAfterInstallLIF方法,建立特定userID下的應用數據
  • InstallingAsyncTask任務中的Session系列操做:一個應用完整的安裝過程從這裏開始
    private final class InstallingAsyncTask extends AsyncTask<Void, Void, PackageInstaller.Session> {
        volatile boolean isDone;
        @Override
        protected PackageInstaller.Session doInBackground(Void... params) {
            PackageInstaller.Session session;
            ......
            session = getPackageManager().getPackageInstaller().openSession(mSessionId);
            try {
                File file = new File(mPackageURI.getPath());
                try (InputStream in = new FileInputStream(file)) {
                    long sizeBytes = file.length();
                    try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
                        byte[] buffer = new byte[1024 * 1024];
                        while (true) {
                            ......
                            out.write(buffer, 0, numRead);
                            ......
                        }
                    }
                }
                return session;
            }
            ......
        }
        @Override
        protected void onPostExecute(PackageInstaller.Session session) {
            if (session != null) {
                ......
                session.commit(pendingIntent.getIntentSender());
                ......
            } else {
                getPackageManager().getPackageInstaller().abandonSession(mSessionId);
                ......
        }
    }
    複製代碼
    • 任務中作的第一個事情是:根據APKUri,將APK的信息經過IO流的形式寫入到PackageInstaller.Session
    • 第二個事情就是PackageInstallerSession.commit()函數

session這個東西看來很重要,那麼咱們先來看看PackageInstallerSessionasync

PackageInstallerSession

Android經過PackageInstallerSession來表示一次安裝過程,一個PackageInstallerSession包含一個系統中惟一的一個SessionId,若是一個應用的安裝必須分幾個階段來完成,即便設備重啓了,也能夠經過這個ID來繼續安裝過程

PackageInstallerSession中保存了應用安裝的相關數據,例如,安裝包的路徑、安裝的進度、中間數據保存的目錄等。

PackageInstallerService提供了接口createSession來建立一個Session對象:

@Override
    public int createSession(SessionParams params, String installerPackageName, int userId) {
        try {
            return createSessionInternal(params, installerPackageName, userId);
        } catch (IOException e) {
            throw ExceptionUtils.wrap(e);
        }
    }
複製代碼

createSession()方法將返回一個系統惟一值做爲Session ID。若是但願再次使用這個Session,能夠經過接口openSession()打開它。openSession()方法的代碼以下所示:

@Override
    public IPackageInstallerSession openSession(int sessionId) {
        try {
            return openSessionInternal(sessionId);
        } catch (IOException e) {
            throw ExceptionUtils.wrap(e);
        }
    }
複製代碼

openSession()方法將返回一個IPackageInstallerSession對象,它是Binder服務PackageInstallerSessionIBinder對象。在PackageInstallerServicemSessions數組保存了全部PackageInstallerSession對象,定義以下:

@GuardedBy("mSessions")
    private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
複製代碼

咱們知道,當PackageManagerService初始化時會建立PackageInstallerService服務,在這個服務的初始化函數中會讀取/data/system目錄下的install_sessions.xml文件,這個文件中保存了系統中未完成的Install Session。而後PackageInstallerService會根據文件的內容建立PackageInstallerSession對象並插入到mSessions中。

而對於上面提到的commit()函數,是真正觸發PMS安裝的函數,定義以下:

@Override
    public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
        ......
        mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
        ......
    }
    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                ......
                case MSG_COMMIT:
                    synchronized (mLock) {
                        ......
                        commitLocked();
                        ......
                    }

                    break;
                ......
            }
            return true;
        }
    };
    private void commitLocked() throws PackageManagerException {
        ......
        mRemoteObserver.onUserActionRequired(intent);
        ......
        mPm.installStage(mPackageName, stageDir, localObserver, params,
                mInstallerPackageName, mInstallerUid, user, mSigningDetails);
    }
複製代碼

commit()函數最後會調用到PMSinstallStage()方法,到這裏就觸發了安裝的第一階段:文件複製

安裝第一階段:複製文件

咱們已經知道PackageInstallerSession經過調用PMSinstallStage()方法開啓了安裝第一階段,不過整個安裝過程比較複雜,咱們先看看這個過程的序列圖:

image

installStage()

咱們繼續從installStage()來分析,代碼以下:

void installStage(String packageName, File stagedDir, IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, String installerPackageName, int installerUid, UserHandle user, PackageParser.SigningDetails signingDetails) {
        ......
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        ......
        final InstallParams params = new InstallParams(origin, null, observer,
                sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
                verificationInfo, user, sessionParams.abiOverride,
                sessionParams.grantedRuntimePermissions, signingDetails, installReason);
        ......
        msg.obj = params;
        ......
        mHandler.sendMessage(msg);
    }
複製代碼

installStage()方法主要進行了:

  • 建立了一個InstallParams對象
    • InstallParams是安裝過程當中的主要數據結構
    • 通常狀況,安裝應用的時間一般比較長,所以Android把安裝過程拆分,把調用過程的參數數據保存到InstallParams
  • 建立併發送了一個Message消息
    • msg.whatINIT_COPY
    • msg.objInstallParams對象

先來簡單瞭解下InstallParams的類:

image

上圖中的類都是PMS的內部類

  • InstallParams繼承自HandlerParams,用來記錄安裝應用的參數。
  • InstallParams中有一個成員變量mArgs,是一個抽象類型InstallArgs,主要是用來執行APK的複製,真正的實現類包括:
    • FileInstallArgs:用來完成非ASEC應用的安裝
      • asec全稱是Android Secure External Cache
    • MoveInstallArgs:用來完成已安裝應用的移動安裝

簡單瞭解完InstallParams,咱們繼續看INIT_COPY消息的處理過程:

case INIT_COPY: {
    HandlerParams params = (HandlerParams) msg.obj;
    int idx = mPendingInstalls.size();
    ......
    if (!mBound) {
        // 綁定 DefaultContainerService 
        if (!connectToService()) {
            params.serviceError();
            ......
            return;
        } else {
            // 鏈接成功,將安裝信息保存到 mPendingInstalls 的尾部
            mPendingInstalls.add(idx, params);
        }
    } else {
        // 若是已經綁定服務,直接插入到集合尾部
        mPendingInstalls.add(idx, params);
        if (idx == 0) {
            // 若是插入前的集合爲空
            // 直接發送 MCS_BOUND 消息
            mHandler.sendEmptyMessage(MCS_BOUND);
        }
    }
    break;
}
複製代碼

整個過程比較簡潔:

  • 首先是connectToService()去綁定和啓動DefaultContainerService服務。
    • 服務綁定是一個異步的過程,須要等待綁定結果經過onServiceConnected()返回
    • DefaultContainerConnection中能夠看到,當服務鏈接成功後,會發送MSC_BOUNF消息
    class DefaultContainerConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName name, IBinder service) {
            final IMediaContainerService imcs = IMediaContainerService.Stub
                    .asInterface(Binder.allowBlocking(service));
            mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
        }
    }
    複製代碼
  • 而後將安裝的參數信息放到了mPendingInstalls列表中。
    • 當有多個安裝請求到達時,能夠經過mPendingInstalls進行排隊
  • 若是添加前的列表爲空,說明沒有其餘的安裝請求,此時直接發送MSC_BOUND消息

那麼,重點又轉移到了MCS_BOUND的消息處理,代碼以下:

case MCS_BOUND: {
    if (msg.obj != null) {
        mContainerService = (IMediaContainerService) msg.obj;
    }
    if (mContainerService == null) {
        if (!mBound) {
            // DefaultContainerService bind失敗的狀況
            for (HandlerParams params : mPendingInstalls) {
                params.serviceError();
                ......
            }
            mPendingInstalls.clear();
        } else {
            Slog.w(TAG, "Waiting to connect to media container service");
        }
    } else if (mPendingInstalls.size() > 0) {
        // 取出頭部安裝參數
        HandlerParams params = mPendingInstalls.get(0);
        if (params != null) {
            ......
            if (params.startCopy()) {// 執行安裝
                // 已執行成功,移除頭部元素
                if (mPendingInstalls.size() > 0) {
                    mPendingInstalls.remove(0);
                }
                if (mPendingInstalls.size() == 0) {
                    if (mBound) {
                        ......
                        // 若是集合中沒有須要安裝的信息了,發送MCS_UNBIND延時消息進行解綁
                        removeMessages(MCS_UNBIND);
                        Message ubmsg = obtainMessage(MCS_UNBIND);
                        sendMessageDelayed(ubmsg, 10000);
                    }
                } else {
                    // 若是集合中還存在安裝信息,發送 MCS_BOUND 消息,繼續處理
                    mHandler.sendEmptyMessage(MCS_BOUND);
                }
            }
        }
    }
    break;
}
複製代碼

MCS_BOUND消息的處理過程就是:

  • 調用InstallParams類的startCopy()方法來執行安裝
    • 處理邏輯確定在這個方法裏面,下面細講
  • 只要mPendingInstalls中還有安裝信息,就會重複發送MCS_BOUND消息
  • 直到全部應用都處理完畢,而後發送一個延時10sMCS_UNBIND消息
    • MCS_UNBIND消息的處理很簡單,收到消息後若是發現mPendingInstalls中有數據了,則發送MCS_BOUND消息繼續安裝。
    • 不然斷開DefaultContainerService鏈接,安裝過程到此結束
    case MCS_UNBIND: {
        if (mPendingInstalls.size() == 0 && mPendingVerification.size() == 0) {
            if (mBound) {
                // 斷開 DefaultContainerService 鏈接
                disconnectService();
            }
        } else if (mPendingInstalls.size() > 0) {
            // 繼續發送執行安裝操做的消息
            mHandler.sendEmptyMessage(MCS_BOUND);
        }
        break;
    }
    複製代碼

針對startCopy()方法的處理過程,代碼以下:

private static final int MAX_RETRIES = 4;
final boolean startCopy() {
    boolean res;
    try {
        // 重試超過4次,則推出
        if (++mRetries > MAX_RETRIES) {
            // 發送 MCS_GIVE_UP 消息取消安裝
            mHandler.sendEmptyMessage(MCS_GIVE_UP);
            handleServiceError();
            return false;
        } else {
            // 核心方法,執行 copy
            handleStartCopy();
            res = true;
        }
    } catch (RemoteException e) {
        // 出現異常,發送 MCS_RECONNECT 消息,進行重連
        mHandler.sendEmptyMessage(MCS_RECONNECT);
        res = false;
    }
    // 開始進入第二階段
    handleReturnCode();
    return res;
}
複製代碼

startCopy()方法經過調用handleStartCopy()來完成安裝過程。考慮到安裝過程的不肯定性,startCopy()主要是進行錯誤處理:

  • MCS_RECONNECT消息處理中,會從新綁定DefaultContainerService
    • 若是綁定成功,安裝過程就會從新開始,startCopy()會被再次調用。
    • 重試的次數記錄在變量mRetries中,超過4次,安裝失敗
  • handleStartCopy()執行成功,startCopy()會調用handleReturnCode()來繼續處理

handleStartCopy()方法比較長,簡要代碼以下:

public void handleStartCopy() throws RemoteException {
    int ret = PackageManager.INSTALL_SUCCEEDED;
    ......
    if (onInt && onSd) {
        // 設置安裝路徑異常標誌
        ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
    } else if (onSd && ephemeral) {
        // 設置安裝路徑異常標誌
        ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
    } else {
        // 獲取安裝包中的 PackageInfoLite 對象
        pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride);
        ......
        if (!origin.staged && pkgLite.recommendedInstallLocation
                == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
            // TODO: focus freeing disk space on the target device
            // 若是安裝空間不夠,嘗試釋放cache空間
            ......
            try {
                mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
                ......
            } catch (InstallerException e) {
                Slog.w(TAG, "Failed to free cache", e);
            }
            ......
            if (pkgLite.recommendedInstallLocation
                    == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
                pkgLite.recommendedInstallLocation
                    = PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
            }
        }
    }
    ......
    final InstallArgs args = createInstallArgs(this);
    mArgs = args;
    if (ret == PackageManager.INSTALL_SUCCEEDED) {
        ......
        if (!origin.existing && requiredUid != -1 && isVerificationEnabled(verifierUser.getIdentifier(), installFlags, installerUid)) {
            // 此部分是在執行應用的校驗
            // 主要經過向待校驗功能的組件發送Intent來完成
            ......
            /* * We don't want the copy to proceed until verification * succeeds, so null out this field. */
            mArgs = null;
        } else {
            // 無需校驗,調用 InstallArgs 的 copyAPK 方法繼續處理
            ret = args.copyApk(mContainerService, true);
        }
    }
    mRet = ret;
}
複製代碼

上面handleStartCopy()方法的主要邏輯是:

  • 首先經過getMinimalPackageInfo()方法確認是否還有足夠的安裝空間,若是安裝空間不夠,會經過mInstaller.freeCache()來釋放一部分
  • 接下來經過createInstallArgs()建立InstallArgs對象,createInstallArgs()方法中會對InstallParamsmove成員變量進行判斷
    • move不爲空使用MoveInstallArgs
    • move爲空使用FileInstallArgs,咱們重點來看這個
  • 再接下來對apk進行校驗,這個校驗過程是經過發送Intent.ACTION_PACKAGE_NEEDS_VERIFICATION)廣播給系統中全部能夠接收該Intent的應用來完成的
  • 無需校驗,執行InstallArgscopyApk()方法

InstallArgscopyApk()方法以下:

int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
    ......
    return doCopyApk(imcs, temp);
}
private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
    if (origin.staged) {
        codeFile = origin.file;
        resourceFile = origin.file;
        return PackageManager.INSTALL_SUCCEEDED;
    }
    try {
        final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
        // 在/data/app路徑下生成臨時文件
        final File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
        codeFile = tempDir;
        resourceFile = tempDir;
    } catch (IOException e) {
        Slog.w(TAG, "Failed to create copy file: " + e);
        return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
    }
    // 爲臨時文件建立文件描述符 ParcelFileDescriptor
    // 主要是爲了將文件序列化,方便經過binder傳輸
    final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() {
        @Override
        public ParcelFileDescriptor open(String name, int mode) throws RemoteException {
            if (!FileUtils.isValidExtFilename(name)) {
                throw new IllegalArgumentException("Invalid filename: " + name);
            }
            try {
                final File file = new File(codeFile, name);
                final FileDescriptor fd = Os.open(file.getAbsolutePath(),
                        O_RDWR | O_CREAT, 0644);
                Os.chmod(file.getAbsolutePath(), 0644);
                return new ParcelFileDescriptor(fd);
            } catch (ErrnoException e) {
                throw new RemoteException("Failed to open: " + e.getMessage());
            }
        }
    };
    int ret = PackageManager.INSTALL_SUCCEEDED;
    // 使用服務 DefaultContainerService 的 copyPackage 方法複製文件
    ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
    if (ret != PackageManager.INSTALL_SUCCEEDED) {
        Slog.e(TAG, "Failed to copy package");
        return ret;
    }
    // 安裝應用中自帶的 native 動態庫
    final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
    NativeLibraryHelper.Handle handle = null;
    try {
        handle = NativeLibraryHelper.Handle.create(codeFile);
        ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
                abiOverride);
    } catch (IOException e) {
        Slog.e(TAG, "Copying native libraries failed", e);
        ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
    } finally {
        IoUtils.closeQuietly(handle);
    }
    return ret;
}
複製代碼

copyApk()最後調用了DefaultContainerServicecopyPackage()方法將應用的文件複製到了/data/app目錄下。若是應用中還有native的動態庫,也會把apk中的庫文件提取出來。

執行完copyApk()方法後,安裝第一階段的工做就完成了,應用安裝到了/data/app目錄下。咱們接下來看看handleReturnCode()的內容,也就是第二階段。

安裝第二階段:裝載應用

第二階段的工做主要是

  • 把應用的格式轉換成oat格式
  • 爲應用建立數據目錄
  • 把應用的信息裝載進PMS的數據結構中

handleReturnCode()方法代碼以下:

void handleReturnCode() {
    if (mArgs != null) {
        processPendingInstall(mArgs, mRet);
    }
}
複製代碼

handleReturnCode()只是調用了processPendingInstall()方法繼續處理,這個方法的代碼以下:

private void processPendingInstall(final InstallArgs args, final int currentStatus) {
    // Queue up an async operation since the package installation may take a little while.
    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);
                synchronized (mInstallLock) {
                    installPackageTracedLI(args, res);
                }
                args.doPostInstall(res.returnCode, res.uid);
            }
            ......
            // 將相關數據記錄到 mRunningInstalls 集合中
            PostInstallData data = new PostInstallData(args, res);
            mRunningInstalls.put(token, data);
            ...... // 省略備份部分
            if (!doRestore) {
                Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
                mHandler.sendMessage(msg);
            }
        }
    });
}
複製代碼

processPendingInstall()方法中post了一個任務:

  • 任務中的核心是經過installPackageTracedLI()來裝載應用
  • 接下來的部分是經過IBackupManager服務執行備份相關的操做,這部分就不先深刻了,篇幅已通過大。。。
  • 備份完成後發送POST_INSTALL消息繼續處理

咱們仍是先看下核心方法installPackageTracedLI()的內容,代碼以下:

private void installPackageTracedLI(InstallArgs args, PackageInstalledInfo res) {
    installPackageLI(args, res);
}
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
    ......
    // Result object to be returned
    res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
    res.installerPackageName = installerPackageName;
    ......
    // 建立 APK 解析對象 PackageParser
    PackageParser pp = new PackageParser();
    pp.setSeparateProcesses(mSeparateProcesses);
    ......
    final PackageParser.Package pkg;
    try {
        // 解析 APK
        pkg = pp.parsePackage(tmpPackageFile, parseFlags);
    } catch (PackageParserException e) {
        res.setError("Failed parse during installPackageLI", e);
        return;
    }
    ......
    // 收集應用的簽名信息
    if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) {
        pkg.setSigningDetails(args.signingDetails);
    } else {
        PackageParser.collectCertificates(pkg, false /* skipVerify */);
    }
    ......
    pp = null;
    String oldCodePath = null;
    boolean systemApp = false;
    synchronized (mPackages) {
        // Check if installing already existing package
        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);
                pkgName = pkg.packageName;
                replace = true;
            } 
            ......// 省略部分新舊應用的屬性比對
        }
        // 檢查已安裝的應用集合中是否存在該應用的記錄
        PackageSetting ps = mSettings.mPackages.get(pkgName);
        if (ps != null) {
            // 存在同名應用
            ......// 省略一些本地庫的簽名判斷
            // 檢查是否爲系統應用
            oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
            if (ps.pkg != null && ps.pkg.applicationInfo != null) {
                systemApp = (ps.pkg.applicationInfo.flags &
                        ApplicationInfo.FLAG_SYSTEM) != 0;
            }
            res.origUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
        }
        // 檢查應用中定義的Permission是否被重複定義
        int N = pkg.permissions.size();
        for (int i = N-1; i >= 0; i--) {
            final PackageParser.Permission perm = pkg.permissions.get(i);
            final BasePermission bp =
                    (BasePermission) mPermissionManager.getPermissionTEMP(perm.info.name);
            ...... 
            // 省略部分只要是對系統定義權限和非系統定義權限的區分
            // 若是是系統應用定義的權限,則忽略本應用中的定義,而後繼續
            // 若是是非系統應用定義的權限,則本次安裝失敗
            ......
        }
    }
    ......
    try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
            "installPackageLI")) {
        if (replace) {
            ......
            replacePackageLIF(pkg, parseFlags, scanFlags, args.user,
                    installerPackageName, res, args.installReason);
        } else {
            installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
                    args.user, installerPackageName, volumeUuid, res, args.installReason);
        }
    }
    ...... // 省略 dexopt 相關操做
    }
}
複製代碼

這部分的代碼邏輯和前面介紹的scanDirLI()很像,不詳細介紹了,總體流程是:

  • 先經過PackageParser解析了安裝的應用文件
    • 解析過程和scanDirLI()中的parsePackage()同樣
  • 獲得解析結果後,主要是判斷新安裝的應用是否和已安裝的應用構成升級關係
    • 若是是,則調用replacePackageLIF()繼續處理
    • 不然調用installNewPackageLIF()處理

這裏不深刻進行分析了哈,跟蹤完調用過程後其實都會走到addForInitLI()

再次吐槽一下PMSpackage解析部分的調用是真滴複雜啊,各類條件判斷

咱們繼續看下POST_INSTALL消息的處理過程:

case POST_INSTALL: {
    PostInstallData data = mRunningInstalls.get(msg.arg1);
    final boolean didRestore = (msg.arg2 != 0);
    mRunningInstalls.delete(msg.arg1);
    if (data != null) {
        InstallArgs args = data.args;
        PackageInstalledInfo parentRes = data.res;
        ......
        // Handle the parent package
        handlePackagePostInstall(parentRes, grantPermissions, killApp,
                virtualPreload, grantedPermissions, didRestore,
                args.installerPackageName, args.observer);
        // Handle the child packages
        final int childCount = (parentRes.addedChildPackages != null)
                ? parentRes.addedChildPackages.size() : 0;
        for (int i = 0; i < childCount; i++) {
            PackageInstalledInfo childRes = parentRes.addedChildPackages.valueAt(i);
            handlePackagePostInstall(childRes, grantPermissions, killApp,
                    virtualPreload, grantedPermissions, false /*didRestore*/,
                    args.installerPackageName, args.observer);
        }
        ......
    }
    ......
} break;
複製代碼

POST_INSTALL的消息處理基本上都是在執行handlePackagePostInstall()方法,handlePackagePostInstall()方法主要工做包括兩個:

  • 發廣播,廣播包括:
    • Intent.ACTION_PACKAGE_ADDED:新應用安裝成功
    • Intent.ACTION_PACKAGE_REPLACED:應用更新成功
    • 發廣播的目的是通知系統中的其餘應用APK安裝完成。例如,Launcher中須要增長應用的圖標等
  • 調用IPackageInstallObserver2onPackageInstalled()方法通知觀察者
    • 咱們最初介紹的PackageInstaller在安裝應用時就是經過註冊觀察者來實現結果監聽的

關於PMS的軟件卸載流程,你們一樣能夠從PackageInstaller入手,關鍵類是UninstallUninstalling。這裏就先跳過了,PMS篇幅佔用時間過久了。。。。。

相關文章
相關標籤/搜索