Android 10 PMS安裝流程


轉載
https://juejin.cn/post/7003590026776969253#heading-4


Android 是如何完成apk的安裝的?安裝時它又做了哪些事情,保存了哪些信息?存儲的這些信息又有什么作用?

這篇文章,讓我們帶着以上問題來一起探討一下android系統的apk安裝流程。


讓我們先從apk安裝的調用代碼開始追溯:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri apkUri =FileProvider.getUriForFile(context, "你的包名.fileProvider", file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent)
復制代碼

代碼內容很簡單,啟動一個Action為Intent.ACTION_VIEW,Flag為Intent.FLAG_GRANT_READ_URI_PERMISSION,Type為application/vnd.android.package-archive的Activity。

這個Activity在哪里呢?

Android 10之前,你可以在/packages/apps/PackageInstaller/AndroidManifest.xml中找到它:

<activity android:name=".InstallStart"
                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>
            <intent-filter android:priority="1">
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:scheme="package" />
                <data android:scheme="content" />
            </intent-filter>
            <intent-filter android:priority="1">
                <action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
復制代碼

但是,在Android 10的源碼里,谷歌已經將PackageInstaller 這個app刪掉了,這意味着使用之前的安裝方式會存在一定的風險(ps:國產手機系統在Android 10中都自己添加了PackageInstaller這個app)。Android 10推薦采用一個叫PackageInstaller的類來專門負責apk的安裝與卸載工作,你可以在 /samples/ApiDemos/src/com/example/android/apis/content/InstallApkSessionApi.java 中找到此Api的使用示例:

Code 1

PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( 
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
int sessionId = packageInstaller.createSession(params);
session = packageInstaller.openSession(sessionId);

addApkToInstallSession("HelloActivity.apk", session);

// Create an install status receiver.
Context context = InstallApkSessionApi.this;
Intent intent = new Intent(context, InstallApkSessionApi.class);
intent.setAction(PACKAGE_INSTALLED_ACTION);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
IntentSender statusReceiver = pendingIntent.getIntentSender();

// Commit the session (this will start the installation workflow).
session.commit(statusReceiver);
復制代碼


這篇文章,我們探討一下采用PackageInstaller類來安裝Apk的方式,采用PackageInstaller app來安裝Apk的方式留個各位讀者自行去探討挖掘。

PackageInstaller

我們還是先從PackageInstaller的定義開始看起,先上官方的說明:

Offers the ability to install, upgrade, and remove applications on the device. This
includes support for apps packaged either as a single "monolithic" APK, or apps packaged
as multiple "split" APKs.

An app is delivered for installation through a PackageInstaller.Session, which any app
can create. Once the session is created, the installer can stream one or more APKs into
place until it decides to either commit or destroy the session. Committing may require
user intervention to complete the installation, unless the caller falls into one of the
following categories, in which case the installation will complete automatically.

  • the device owner
  • the affiliated profile owner

Sessions can install brand new apps, upgrade existing apps, or add new splits into an existing app.

Apps packaged as multiple split APKs always consist of a single "base" APK (with
null split name) and zero or more "split" APKs (with unique split names). Any subset
of these APKs can be installed together, as long as the following constraints are met:

  • All APKs must have the exact same package name, version code, and signing certificates.
  • All APKs must have unique split names.
  • All installations must contain a single base APK.
    復制代碼

我來做一下翻譯工作,由於最后一段講的是拆分包的安裝,這篇文章不對拆分包的安裝做討論,故直接略過了:

  • 提供在設備上安裝、升級和刪除應用程序的功能,應用程序既可以是整包也可以是拆分包。
  • 應用程序通過PackageInstaller.Session安裝,任何應用程序都可以創建該Session。創建Session后,installer可以將一個或多個APK以流的形式傳輸到適當的位置,直到它決定提交或銷毀Session。提交可能需要用戶干預才能完成安裝,除非調用方屬於設備所有者或文件所有者,在這種情況下,安裝將自動完成。
  • Session可以安裝全新的應用程序,升級現有應用程序,或將新拆分添加到現有應用程序中。

接下來,我們結合上文Code 1處的PackageInstallApi調用代碼來對它進行探討。

PackageInstaller.SessionParams

PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( 
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
復制代碼

SessionParams 有以下兩個值得關注的參數:

mode

Type Description
MODE_FULL_INSTALL 安裝的Apk完全替換目標程序現有的Apk
MODE_INHERIT_EXISTING 繼承目標應用程序的任何現有APK,除非它們已被會話顯式覆蓋(基於拆分名稱)

installLocation

Type Description
INSTALL_LOCATION_AUTO 讓系統決定理想的安裝位置。
INSTALL_LOCATION_INTERNAL_ONLY 默認值,明確要求僅安裝在手機內部存儲上。
INSTALL_LOCATION_PREFER_EXTERNAL 更傾向安裝在SD卡上。不能保證系統會遵守這一要求。如果外部存儲不可用或太滿,應用程序可能會安裝在內部存儲上。

isStaged

標識此Session是否在重啟時安裝。


讓我們回到PackageInstaller中,繼續往下看。

packageInstaller.createSession

public int createSession(@NonNull SessionParams params) throws IOException {
    try {
        final String installerPackage;
        if (params.installerPackageName == null) {
            //這里的mInstallerPackageName指的是安裝apk的應用程序包名,而不是目標apk的包名
            installerPackage = mInstallerPackageName;
        } else {
            installerPackage = params.installerPackageName;
        }
        //調用PackageInstallerService里的createSession方法
        return mInstaller.createSession(params, installerPackage, mUserId);
    } catch (RuntimeException e) {
        ExceptionUtils.maybeUnwrapIOException(e);
        throw e;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
復制代碼

可以看到,packageInstaller.createSession方法最后將實際調用轉交給服務端PackageInstallerService去執行。


PackageInstallerService.createSession

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

createSessionInternal

private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
        throws IOException {
    final int callingUid = Binder.getCallingUid();
    mPermissionManager.enforceCrossUserPermission(
            callingUid, userId, true, true, "createSession");
//檢查此用戶是否被禁止安裝程序
if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
    throw new SecurityException("User restriction prevents installing");
}

//如果安裝來自adb 或者 root用戶,param里增加 INSTALL_FROM_ADB 的FLAG
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
    params.installFlags |= PackageManager.INSTALL_FROM_ADB;

} else {
    // 只有具有INSTALL_PACKAGES權限的APP才允許以非調用者的身份設置為安裝器
    if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) !=
            PackageManager.PERMISSION_GRANTED) {
        mAppOps.checkPackage(callingUid, installerPackageName);
    }
    //移除以下三個FLAG
    params.installFlags &amp;= ~PackageManager.INSTALL_FROM_ADB;
    params.installFlags &amp;= ~PackageManager.INSTALL_ALL_USERS;
    params.installFlags &amp;= ~PackageManager.INSTALL_ALLOW_TEST;
    //如果APP已存在,則替換它
    params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
    if ((params.installFlags &amp; PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0
            &amp;&amp; !mPm.isCallerVerifier(callingUid)) {
        params.installFlags &amp;= ~PackageManager.INSTALL_VIRTUAL_PRELOAD;
    }
}

if (Build.IS_DEBUGGABLE || isDowngradeAllowedForCaller(callingUid)) {
    //允許降級
    params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else {
    //不允許降級,不允許向低版本升級
    params.installFlags &amp;= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
    params.installFlags &amp;= ~PackageManager.INSTALL_REQUEST_DOWNGRADE;
}

...

if (!params.isMultiPackage) {
    //安裝時,只有系統組件可以繞過運行時權限。
    if ((params.installFlags &amp; PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
            &amp;&amp; mContext.checkCallingOrSelfPermission(Manifest.permission
            .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
        throw new SecurityException("You need the "
                + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
                + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
    }

    //如果AppIcon過大,則對它進行調整
    if (params.appIcon != null) {
        final ActivityManager am = (ActivityManager) mContext.getSystemService(
                Context.ACTIVITY_SERVICE);
        final int iconSize = am.getLauncherLargeIconSize();
        if ((params.appIcon.getWidth() &gt; iconSize * 2)
                || (params.appIcon.getHeight() &gt; iconSize * 2)) {
            params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize,
                    true);
        }
    }

    switch (params.mode) {
        case SessionParams.MODE_FULL_INSTALL:
        case SessionParams.MODE_INHERIT_EXISTING:
            break;
        default:
            throw new IllegalArgumentException("Invalid install mode: " + params.mode);
    }

    // If caller requested explicit location, sanity check it, otherwise
    // resolve the best internal or adopted location.
    if ((params.installFlags &amp; PackageManager.INSTALL_INTERNAL) != 0) {
        if (!PackageHelper.fitsOnInternal(mContext, params)) {
            throw new IOException("No suitable internal storage available");
        }
    } else if ((params.installFlags &amp; PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
        // For now, installs to adopted media are treated as internal from
        // an install flag point-of-view.
        params.installFlags |= PackageManager.INSTALL_INTERNAL;
    } else {
        params.installFlags |= PackageManager.INSTALL_INTERNAL;

        // Resolve best location for install, based on combination of
        // requested install flags, delta size, and manifest settings.
        final long ident = Binder.clearCallingIdentity();
        try {
        //根據給定的installLocation計算安裝需要的大小,選擇要安裝應用程
        //序的實際卷。只考慮內部卷和專用卷,並且更偏向將現有包保留在其當前卷上。
        //volumeUuid 指的是 安裝所在的卷的fsUuid,如果安裝在內部存儲上,則返回null
            params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
}

final int sessionId;
final PackageInstallerSession session;
synchronized (mSessions) {
    // 檢查安裝程序是否正常運行,打開的Installer Session不能超過1024個
    final int activeCount = getSessionCount(mSessions, callingUid);
    if (activeCount &gt;= MAX_ACTIVE_SESSIONS) {
        throw new IllegalStateException(
                "Too many active sessions for UID " + callingUid);
    }
    //歷史Session不能超過1048576個
    final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);
    if (historicalCount &gt;= MAX_HISTORICAL_SESSIONS) {
        throw new IllegalStateException(
                "Too many historical sessions for UID " + callingUid);
    }
    //隨機生成一個不超過Integer.MAX_VALUE的sessionId,並保存在mAllocatedSessions中
    sessionId = allocateSessionIdLocked();
}

final long createdMillis = System.currentTimeMillis();
// We're staging to exactly one location
File stageDir = null;
String stageCid = null;
if (!params.isMultiPackage) {
        if ((params.installFlags &amp; PackageManager.INSTALL_INTERNAL) != 0) {
         //安裝在內部存儲上,volumeUuid 為null
        //復制路徑為/data/app/vmdl${sessionId}.tmp
        stageDir = buildSessionDir(sessionId, params);
    } else {
        stageCid = buildExternalStageCid(sessionId);
    }
}
//存儲了安裝相關的所有信息
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
        mInstallThread.getLooper(), mStagingManager, sessionId, userId,
        installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
        false, false, null, SessionInfo.INVALID_ID, false, false, false,
        SessionInfo.STAGED_SESSION_NO_ERROR, "");

//將sessionId和session綁定起來
synchronized (mSessions) {
    mSessions.put(sessionId, session);
}
if (params.isStaged) {
     //處理分階段安裝會話,即僅在重新啟動后才需要安裝的會話。
    mStagingManager.createSession(session);
}

if ((session.params.installFlags &amp; PackageManager.INSTALL_DRY_RUN) == 0) {
    //回調通知Session已創建成功
    mCallbacks.notifySessionCreated(session.sessionId, session.userId);
}
//異步將session信息寫入install_sessions.xml文件中
writeSessionsAsync();
return sessionId;

}
復制代碼

createSessionInternal方法主要是對 初始化apk的安裝信息及環境,並創建一個sessionId,將安裝Session與sessionId 進行綁定。


packageInstaller.openSession

public @NonNull Session openSession(int sessionId) throws IOException {
    try {
        try {
            return new Session(mInstaller.openSession(sessionId));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    } catch (RuntimeException e) {
        ExceptionUtils.maybeUnwrapIOException(e);
        throw e;
    }
}
復制代碼

我們繼續追蹤PackageInstallerService中的openSession方法:

PackageInstallerService.openSession

public IPackageInstallerSession openSession(int sessionId) {
    try {
        return openSessionInternal(sessionId);
    } catch (IOException e) {
        throw ExceptionUtils.wrap(e);
    }
}
復制代碼

openSessionInternal

private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException {
    synchronized (mSessions) {
        final PackageInstallerSession session = mSessions.get(sessionId);
        if (session == null || !isCallingUidOwner(session)) {
            throw new SecurityException("Caller has no access to session " + sessionId);
        }
        //如果StageDir不為空,則創建此文件夾
        //stageDir是寫入客戶端數據的暫存位置
        session.open();
        return session;
    }
}
復制代碼

可以看到,openSessionInternal方法只是根據sessionId查找在上一階段通過createSession創建的PackageInstallerSession對象並將其返回。


安裝代碼接下來調用了addApkToInstallSession("HelloActivity.apk", session);,這個方法的詳細實現如下:

private void addApkToInstallSession(String assetName, PackageInstaller.Session session)
    throws IOException {
        // It's recommended to pass the file size to openWrite(). Otherwise installation may fail
        // if the disk is almost full.
        try (OutputStream packageInSession = session.openWrite("package", 0, -1);
            InputStream is = getAssets().open(assetName)) {
            byte[] buffer = new byte[16384];
            int n;
            while ((n = is.read(buffer)) >= 0) {
                packageInSession.write(buffer, 0, n);
            }
        }
}
復制代碼

session.openWrite

openWrite方法的主要作用是打開一個流,將APK文件寫入Session。返回的流將開始在文件中請求的偏移量處寫入數據,該偏移量可用於恢復部分寫入的文件。如果指定了有效的文件長度,系統將預先分配底層磁盤空間,以優化磁盤上的位置。強烈建議在已知的情況下提供有效的文件長度。

您可以將數據寫入返回的流,也可以根據需要調用fsync(OutputStream),以確保字節已持久化到磁盤,然后在完成時關閉。在調用commit(IntentSender)之前,必須關閉所有流。

openWrite的源碼如下所示:

public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes,
        long lengthBytes) throws IOException {
    try {
        if (ENABLE_REVOCABLE_FD) {
            return new ParcelFileDescriptor.AutoCloseOutputStream(
                    mSession.openWrite(name, offsetBytes, lengthBytes));
        } else {
            //ENABLE_REVOCABLE_FD默認為false
            final ParcelFileDescriptor clientSocket = mSession.openWrite(name,
                    offsetBytes, lengthBytes);
            return new FileBridge.FileBridgeOutputStream(clientSocket);
        }
    } catch (RuntimeException e) {
        ExceptionUtils.maybeUnwrapIOException(e);
        throw e;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
復制代碼

這個方法里我們重點關注這句話 mSession.openWrite(name, offsetBytes, lengthBytes),它意味者最終由 PackageInstallerSession類里的openWrite方法繼續來處理。


PackageInstallerSession.openWrite

@Override
public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
    try {
        return doWriteInternal(name, offsetBytes, lengthBytes, null);
    } catch (IOException e) {
        throw ExceptionUtils.wrap(e);
    }
}
復制代碼

繼續看doWriteInternal方法:


doWriteInternal

private ParcelFileDescriptor doWriteInternal(String name, long offsetBytes, long lengthBytes,
                                              ParcelFileDescriptor incomingFd) throws IOException {
    //快速檢查狀態是否正常,並為自己分配一個Pipe。
    //然后,我們在鎖之外進行大量的磁盤分配,但是這個打開的Pipe將阻止任何嘗試的安裝轉換
    final RevocableFileDescriptor fd;
    final FileBridge bridge;
    final File stageDir;
    synchronized (mLock) {
        if (PackageInstaller.ENABLE_REVOCABLE_FD) {
            fd = new RevocableFileDescriptor();
            bridge = null;
            mFds.add(fd);
        } else {
            //走這里
            fd = null;
            bridge = new FileBridge();
            mBridges.add(bridge);
        }
         //解析應寫入暫存數據的實際位置。
        stageDir = resolveStageDirLocked();
    }
try {
    //先使用安裝程序提供的名稱;我們之后會重命名
    if (!FileUtils.isValidExtFilename(name)) {
    //Check if given filename is valid for an ext4 filesystem.
        throw new IllegalArgumentException("Invalid name: " + name);
    }
    final File target;
    final long identity = Binder.clearCallingIdentity();
    try {
        target = new File(stageDir, name);
    } finally {
        Binder.restoreCallingIdentity(identity);
    }

    // TODO: this should delegate to DCS so the system process avoids
    // holding open FDs into containers.
    //打開文件並設置權限為644
    final FileDescriptor targetFd = Os.open(target.getAbsolutePath(),
            O_CREAT | O_WRONLY, 0644);
    Os.chmod(target.getAbsolutePath(), 0644);

    // 如果指定了APK大小,先在磁盤中分配空間
    if (stageDir != null &amp;&amp; lengthBytes &gt; 0) {
        mContext.getSystemService(StorageManager.class).allocateBytes(targetFd, lengthBytes,
                PackageHelper.translateAllocateFlags(params.installFlags));
    }

    if (offsetBytes &gt; 0) {
        Os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
    }

   if (incomingFd != null) {
   //如果調用了session.write方法,incomingFd不為null,將會進入此分支
            switch (Binder.getCallingUid()) {
                case android.os.Process.SHELL_UID:
                case android.os.Process.ROOT_UID:
                case android.os.Process.SYSTEM_UID:
                    break;
                default:
                    throw new SecurityException(
                            "Reverse mode only supported from shell or system");
            }

            // In "reverse" mode, we're streaming data ourselves from the
            // incoming FD, which means we never have to hand out our
            // sensitive internal FD. We still rely on a "bridge" being
            // inserted above to hold the session active.
            try {
                final Int64Ref last = new Int64Ref(0);
                //將數據寫入暫存文件
                FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, lengthBytes, null,
                        Runnable::run, (long progress) -&gt; {
                            if (params.sizeBytes &gt; 0) {
                                final long delta = progress - last.value;
                                last.value = progress;
                                addClientProgress((float) delta / (float) params.sizeBytes);
                            }
                        });
            } finally {
                IoUtils.closeQuietly(targetFd);
                IoUtils.closeQuietly(incomingFd);

                //完成傳輸,關閉file bridge
                synchronized (mLock) {
                    if (PackageInstaller.ENABLE_REVOCABLE_FD) {
                        mFds.remove(fd);
                    } else {
                        bridge.forceClose();
                        mBridges.remove(bridge);
                    }
                }
            }
            return null;
   } 

   
   bridge.setTargetFile(targetFd);
   bridge.start();
   return new ParcelFileDescriptor(bridge.getClientSocket());
   
} catch (ErrnoException e) {
    throw e.rethrowAsIOException();
}

}
復制代碼

doWriteInternal這個方法主要是初始化要安裝的APK文件的磁盤存儲空間(如果指定了大小的話),並創建了一個可以跨進程寫文件的FileBridge對象,通過ParcelFileDescriptor類包裝稱文件輸出流提供給應用層寫入安裝的APK數據。

需要注意的是,后續的session.write方法調用的也是doWriteInternal,不同的是傳入的參數incomingFd不為null,這意味着doWriteInternal會將數據寫入到incomingFd這個文件描述符所對應的文件,它也是我們在調用openWrite方法時創建的文件。

到這一步為止,我們完成了要安裝的Apk的復制工作。


session.commit

現在到了我們最后一步了,將Installer Session提交,去真正執行安裝Apk的工作。我們來看一下commit方法:

public void commit(@NonNull IntentSender statusReceiver) {
    try {
        mSession.commit(statusReceiver, false);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
復制代碼

同樣的,它會把此方法委托給PackageInstallerSession類去執行。需要注意的是,commit方法里有一個IntentSender參數,這個參數的作用是用來通知安裝時Session狀態的改變,比如 安裝需要用戶進一步確認、安裝成功、安裝失敗等事件。


PackageInstallerSession.commit

public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
    if (hasParentSessionId()) {
        throw new IllegalStateException(
                "Session " + sessionId + " is a child of multi-package session "
                        + mParentSessionId +  " and may not be committed directly.");
    }
    if (!markAsCommitted(statusReceiver, forTransfer)) {
        return;
    }

...

mHandler.obtainMessage(MSG_COMMIT).sendToTarget();

}
復制代碼

commit方法最終向mHandler中發送了一條MSG_COMMIT的消息,而在這個消息里,實際執行的是handleCommit方法。


handleCommit

private void handleCommit() {
    ...
// 返回子Session列表,如果Session不是multipackage,則返回null
List&lt;PackageInstallerSession&gt; childSessions = getChildSessions();

try {
    synchronized (mLock) {
        commitNonStagedLocked(childSessions);
    }
} catch (PackageManagerException e) {
    final String completeMsg = ExceptionUtils.getCompleteMessage(e);
    Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
    destroyInternal();
    dispatchSessionFinished(e.error, completeMsg, null);
}

}
復制代碼


commitNonStagedLocked

private void commitNonStagedLocked(List<PackageInstallerSession> childSessions)
        throws PackageManagerException {
        //返回一個ActiveInstallSession對象
    final PackageManagerService.ActiveInstallSession committingSession =
            makeSessionActiveLocked();
    if (committingSession == null) {
        return;
    }
    if (isMultiPackage()) {
        List<PackageManagerService.ActiveInstallSession> activeChildSessions =
                new ArrayList<>(childSessions.size());
        boolean success = true;
        PackageManagerException failure = null;
        for (int i = 0; i < childSessions.size(); ++i) {
            final PackageInstallerSession session = childSessions.get(i);
            try {
                final PackageManagerService.ActiveInstallSession activeSession =
                        session.makeSessionActiveLocked();
                if (activeSession != null) {
                    activeChildSessions.add(activeSession);
                }
            } catch (PackageManagerException e) {
                failure = e;
                success = false;
            }
        }
        if (!success) {
            try {
                mRemoteObserver.onPackageInstalled(
                        null, failure.error, failure.getLocalizedMessage(), null);
            } catch (RemoteException ignored) {
            }
            return;
        }
        mPm.installStage(activeChildSessions);
    } else {
       //我們通常執行的安裝走這個分支
        mPm.installStage(committingSession);
    }
}
復制代碼

commitNonStagedLocked方法的最后,調用PMS的installStage方法,這樣代碼邏輯就進入了PMS中,讓PMS去對要安裝的APK文件做內容的解析,完成真正的安裝工作。

實際上,PackageInstaller只是負責安裝前期的准備工作,它維持了一個安裝會話,管理安裝的參數,並提供將安裝包臨時復制到特定路徑的功能,但真正實現安裝還是得依靠PMS。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM