研究應用的安裝過程,老樣子,我們還是先從使用入手。
在
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
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()
方法開啟了安裝第一階段,不過整個安裝過程比較複雜,我們先看看這個過程的序列圖:
installStage()
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
的類:
上圖中的類都是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()
- 重試的次數記錄在變量
中,超過4次,安裝失敗mRetries
- 如果綁定成功,安裝過程就會重新開始,
-
執行成功,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