天天看點

深入Android系統(十)PMS-3-應用安裝過程PackageInstallerSession安裝第一階段:複制檔案安裝第二階段:裝載應用

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

Android

中,通過發送

Intent

就可以啟動應用的安裝過程,比如:

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

的界面。

國内的廠商基本上會在

InstallStart

這裡進行修改,替換為自己的安裝界面

對于

PackageInstallerActivity

來說,它的主要作用是

  • 解析

    Uri

    協定進行解析,包括

    file

    package

    兩種
    • 如果是

      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

界面進行安裝,我們看看這個

Activity

@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()

    :主要是對已經并掃描到

    Settings

    mPackages

    集合中的應用進行操作
    • 根據目前的

      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);
                ......
        }
    }
               
    • 任務中做的第一個事情是:根據

      APK

      Uri

      ,将

      APK

      的資訊通過

      IO

      流的形式寫入到

      PackageInstaller.Session

    • 第二個事情就是

      PackageInstallerSession.commit()

      函數

session

這個東西看來很重要,那麼我們先來看看

PackageInstallerSession

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

服務

PackageInstallerSession

IBinder

對象。在

PackageInstallerService

mSessions

數組儲存了所有

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()

函數最後會調用到

PMS

installStage()

方法,到這裡就觸發了安裝的第一階段:檔案複制

安裝第一階段:複制檔案

我們已經知道

PackageInstallerSession

通過調用

PMS

installStage()

方法開啟了安裝第一階段,不過整個安裝過程比較複雜,我們先看看這個過程的序列圖:

深入Android系統(十)PMS-3-應用安裝過程PackageInstallerSession安裝第一階段:複制檔案安裝第二階段:裝載應用

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.what

      INIT_COPY

    • msg.obj

      InstallParams

      對象

先來簡單了解下

InstallParams

的類:

深入Android系統(十)PMS-3-應用安裝過程PackageInstallerSession安裝第一階段:複制檔案安裝第二階段:裝載應用
上圖中的類都是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

    消息
  • 直到所有應用都處理完畢,然後發送一個延時

    10s

    MCS_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()

    方法中會對

    InstallParams

    move

    成員變量進行判斷
    • move

      不為空使用

      MoveInstallArgs

    • move

      為空使用

      FileInstallArgs

      ,我們重點來看這個
  • 再接下來對

    apk

    進行校驗,這個校驗過程是通過發送

    Intent.ACTION_PACKAGE_NEEDS_VERIFICATION)

    廣播給系統中所有可以接收該

    Intent

    的應用來完成的
  • 無需校驗,執行

    InstallArgs

    copyApk()

    方法

InstallArgs

copyApk()

方法如下:

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()

最後調用了

DefaultContainerService

copyPackage()

方法将應用的檔案複制到了

/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()

再次吐槽一下

PMS

package

解析部分的調用是真滴複雜啊,各種條件判斷

我們繼續看下

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

      中需要增加應用的圖示等
  • 調用

    IPackageInstallObserver2

    onPackageInstalled()

    方法通知觀察者
    • 我們最初介紹的

      PackageInstaller

      在安裝應用時就是通過注冊觀察者來實作結果監聽的
關于

PMS

的軟體解除安裝流程,大家同樣可以從

PackageInstaller

入手,關鍵類是

UninstallUninstalling

。這裡就先跳過了,

PMS

篇幅占用時間太久了。。。。。