APK安裝流程系列文章整體內容如下:
- APK安裝流程詳解0——前言
- APK安裝流程詳解1——有關"安裝ing"的實體類概述
- APK安裝流程詳解2——PackageManager簡介
- APK安裝流程詳解3——PackageManager與PackageManagerService
- APK安裝流程詳解4——安裝中關于so庫的哪些事
- APK安裝流程詳解5——PackageInstallerService和Installer
- APK安裝流程詳解6——PackageManagerService啟動前奏
- APK安裝流程詳解7——PackageManagerService的啟動流程(上)
- APK安裝流程詳解8——PackageManagerService的啟動流程(下)
- APK安裝流程詳解9——PackageParser解析APK(上)
- APK安裝流程詳解10——PackageParser解析APK(下)
- APK安裝流程詳解11——普通應用安裝簡介
- APK安裝流程詳解12——PackageManagerService中的新安裝流程上(拷貝)
- APK安裝流程詳解13——PackageManagerService中的新安裝流程下(裝載)
- APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充
- APK安裝流程詳解15——PMS中的新安裝流程下(裝載)補充
- APK安裝流程詳解16——Android包管理總結(尚未完結請期待)
本片文章的主要內容如下:
- 1、ApplicationPackageManager中相關方法的跟蹤
- 1.1 、installPackageWithVerification(Uri,PackageInstallObserver, int, String,Uri, ManifestDigest,ContainerEncryptionParams)方法
- 1.2、installCommon(Uri,PackageInstallObserver, int, String,int)方法解析
- 2、PackageManagerService中相關方法的跟蹤
- 2.1 installPackage(String, IPackageInstallObserver2, int, String,VerificationParams,String)方法解析
- 2.2 installPackageAsUser(String, IPackageInstallObserver2, int, String, VerificationParams,String, int)方法解析
- 2.3 PackageHandler的HandlerMesaage方法中what值為INIT_COPY的情況:
- 2.4 PackageHandler的connectToService方法解析
- 2.5 PackageHandler的HandlerMesaage方法中what值為MCS_BOUND的情況:
- 2.6 HandlerParams的startCopy方法
- 2.7 InstallParams的handleStartCopy方法
從上面一片文章我們知道InstallAppProgress里面最后更新的代碼是調用到PackageManager#installPackageWithVerificationAndEncryption方法,那我們就從這個方法開始進行跟蹤分析
總體流程大致如下:
涉及到類的流程如下:
我將上面整個安裝流程分為兩大步驟
- 1、第一步:拷貝安裝包
- 2、第二步:裝載代碼
本片文章主要講解"拷貝",即將安裝包拷貝到/data目錄下,同時為了保證本篇文章的流程性, 本片文章只講主流程,在主流程涉及到的復雜問題,或者小分支,我會提出問題,但由于簡書的篇幅問題,我就不在本篇文章深入了,我會在這篇文章APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充去深入講解,但我會在本篇文章中提出問題,并給出答案的索引。
一、ApplicationPackageManager中相關方法的跟蹤
通過前面的研究我們知道PackageManager是個抽象類,具體的實現類是ApplicationPackageManager,那我們就來看下ApplicationPackageManager類的installExistingPackage(String)方法
(一)、installPackageWithVerification(Uri,PackageInstallObserver, int, String,Uri, ManifestDigest,ContainerEncryptionParams)方法解析
代碼在ApplicationPackageManager.java 1370行
@Override
public void installPackageWithVerification(Uri packageURI,
PackageInstallObserver observer, int flags, String installerPackageName,
Uri verificationURI, ManifestDigest manifestDigest,
ContainerEncryptionParams encryptionParams) {
final VerificationParams verificationParams = new VerificationParams(verificationURI, null,
null, VerificationParams.NO_UID, manifestDigest);
installCommon(packageURI, observer, flags, installerPackageName, verificationParams,
encryptionParams);
}
這個方法內部很簡單,就是調用了installCommon方法。那我們繼續跟蹤下
(二)、installCommon(Uri,PackageInstallObserver, int, String,int)方法解析
代碼在ApplicationPackageManager.java 1370行
private void installCommon(Uri packageURI,
PackageInstallObserver observer, int flags, String installerPackageName,
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
if (!"file".equals(packageURI.getScheme())) {
throw new UnsupportedOperationException("Only file:// URIs are supported");
}
if (encryptionParams != null) {
throw new UnsupportedOperationException("ContainerEncryptionParams not supported");
}
final String originPath = packageURI.getPath();
try {
mPM.installPackage(originPath, observer.getBinder(), flags, installerPackageName,
verificationParams, null);
} catch (RemoteException ignored) {
}
}
這個方法內部做了兩次判斷,然后調用的mPM的installPackage方法。我們知道IPackageManager僅僅是AIDL的Binder通道,其實真正的調用方是PackageManagerService的installPackage(String, IPackageInstallObserver2, int, String, VerificationParams,String)方法
二、PackageManagerService中相關方法的跟蹤
(一) installPackage(String, IPackageInstallObserver2, int, String,VerificationParams,String)方法解析
代碼在PackageManagerService.java 9513行
@Override
public void installPackage(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, VerificationParams verificationParams,
String packageAbiOverride) {
installPackageAsUser(originPath, observer, installFlags, installerPackageName,
verificationParams, packageAbiOverride, UserHandle.getCallingUserId());
}
看下這6個參數:
- originPath:安裝包的位置,它必須是File類型在content的URI類型。這里傳遞過來的是toString的一個字符串。
- observer:是IPackageInstallObserver2類型的回調。通知調用者安裝完成
- installFlags:它的值可能是如下的值中的一個
- INSTALL_FORWARD_LOCK:表示安裝過程中是否鎖定
- INSTALL_REPLACE_EXISTING:表示是否替換包
- INSTALL_ALLOW_TEST:表示是否是測安裝包
比如在AndoridManifestL里面配置了"android:testOnly",則這里的標志就是INSTALL_ALLOW_TEST- installerPackageName:安裝包的包名
- verificationParams:代表驗證參數用于驗證包安裝
- packageAbiOverride:一般傳null
我們看到PackageManagerService的installPackage方法什么都沒做,直接調用了PackageManagerService的installPackageAsUser方法
(二) installPackageAsUser(String, IPackageInstallObserver2, int, String, VerificationParams,String, int)方法解析
代碼在PackageManagerService.java 9521行
@Override
public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, VerificationParams verificationParams,
String packageAbiOverride, int userId) {
// 第一步
// 檢查是否具有install權限
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId, true, true, "installPackageAsUser");
// 判斷當前用戶是否被Restricted,回調onPackageInstalled方法,安裝失敗
if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
try {
if (observer != null) {
observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null);
}
} catch (RemoteException re) {
}
return;
}
// 第二步
// 判斷是安裝來源是 ADB、shell和all_user
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
// 如果發起端進程是shell或者root,則添加flag:INSTALL_FROM_ADB
installFlags |= PackageManager.INSTALL_FROM_ADB;
} else {
// Caller holds INSTALL_PACKAGES permission, so we're less strict
// about installerPackageName.
// 如果不是則從flags中去掉INSTALL_FROM_ADB和INSTALL_ALL_USERS
installFlags &= ~PackageManager.INSTALL_FROM_ADB;
installFlags &= ~PackageManager.INSTALL_ALL_USERS;
}
UserHandle user;
if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
user = UserHandle.ALL;
} else {
user = new UserHandle(userId);
}
// Only system components can circumvent runtime permissions when installing.
// Android 6.0 時,當權限屬于運行時權限時,需要彈出框,讓用戶授權,對于system app,應該取消彈框授權,而是直接授權
if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
&& 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");
}
verificationParams.setInstallerUid(callingUid);
final File originFile = new File(originPath);
final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);
// 第三步
// 發送"INIT_COPY"消息,構造InstallParams參數
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(origin, null, observer, installFlags, installerPackageName,
null, verificationParams, user, packageAbiOverride, null);
mHandler.sendMessage(msg);
}
這里面涉及到一個mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);關于這個方法的詳解請參考APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充中的 "一、在PackageManagerService的installPackageAsUser方法里面的代碼" 部分
就像這個方法的名字叫"installPackageAsUser"一樣,這段代碼主要是對用戶是否有權限安裝進行檢查,以及安裝app是 僅僅給當前用戶安裝,還是給所有用戶安裝。通過上面代碼可以看出,當安裝進程是shell或者root時,否則大多數情況下,僅僅安裝給當前用戶。
這里主要是對當前用戶是否有權限安裝app進行檢查,以及安裝的app是僅僅為當前用戶安裝,還是給所有的用戶安裝。從以上代碼可以得出,當安裝進程是shell或者root時,flags中又包含了INSTALL_ALL_USERS時,才會給所有用戶安裝,否則大多數情況下,僅僅安裝給當前的用戶。當我們使用pm命令安裝的時候,可以選擇安裝給哪個用戶,也可以是全部用戶,就是這個原因。
該函數主要做了以下操作:
- 第一步:獲取權限,如果被拒絕,則退出執行
- 第二步:設置installFlags參數,即判斷安裝來源
- 第三步:發送了一個what值為INIT_COPY的message
PS:這里注意這個msg的obj變量對應的InstallParams對象,這個InstallParams對象后面會多次用到;在構造InstallParams的時候注意他的兩個參數為null
該方法的主要流程如下:
PackageHandler來執行的,PackageHandler繼續字Handler,那我們來具體看看執行代碼:
(三) PackageHandler的HandlerMesaage方法中what值為INIT_COPY的情況:
因為PackageHandler繼承Handler,所以我們來看下PackageHandler的HandlerMessage方法
代碼在PackageManagerService.java 1131行
public void handleMessage(Message msg) {
try {
doHandleMessage(msg);
} finally {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
}
前面的文章我們提及了PackageHandler是PackageServiceManager的內部類,并且繼承Handler,所以我們直接看PackageHandler的HandlerMesaage方法,我們看到PackageHandler的HandlerMesaage方法其實是調用doHandleMessage(Message),然后在finally代碼塊里面設置了線程的優先級為后臺線程。
那我們就來看下PackageHandler的doHandleMessage(Message)方法
1、PackageHandler的doHandleMessage方法
因為我們知道Message的what值是INIT_COPY,所以為了節省空間,我們就不粘貼其他的case了。
代碼在PackageManagerService.java 1139行
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
// 第一步
// 取出InstallParams
HandlerParams params = (HandlerParams) msg.obj;
// idx為當前需要安裝的APK個數,mPendingInstalls里面保存所有需要安裝的APK解析出來的HandlerParams參數
int idx = mPendingInstalls.size();
if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
// If a bind was already initiated we dont really
// need to do anything. The pending install
// will be processed later on.
// 第二步 分兩種情況
// 判斷綁定的連接是否已經存在,如果已經綁定了,則mBound為true。如果是第一次調用mBound為false。
if (!mBound) {
// If this is the only one pending we might
// have to bind to the service again.
// 第三步
// 連接安裝Service
if (!connectToService()) {
Slog.e(TAG, "Failed to bind to media container service");
params.serviceError();
return;
} else {
// Once we bind to the service, the first
// pending request will be processed.
// 如果成功綁定Service后,將新的安裝請求放入到mPendingIntalls中,等待處理
mPendingInstalls.add(idx, params);
}
} else {
// 如果之前已經綁定過服務,同樣將新的請求到mPendingIntalls中,等待處理
mPendingInstalls.add(idx, params);
// Already bound to the service. Just make
// sure we trigger off processing the first request.
if (idx == 0) {
// 如果是第一個安裝請求,則直接發送事件MCS_BOUND觸發處理流程
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
break;
}
...
}
我將這個方法分為三個部分:
- 第一步,取出參數params,這個params就是之前傳入的InstallParams
- 第二步,獲取等待安裝隊列的個數,并根據mBound的值進行不同的處理。mBound為true,表示已經綁定,mBound為false表示未綁定。第一次調用則mBound為默認值為false。
- 第三步,如果是第一次調用,則調用connectToService()方法,如果不是第一次調用,且已經綁定則將params添加到mPendingInstalls(等待安裝隊列)的最后一個位置。如果是第一個安裝請求,則發送MCS_BOUND事件觸發接下來的流程
這個方法整體流程如下圖:
如果是第一次走這個方法,則方法里面主要有兩個流程
- 調用connectToService()方法
- 然后調用mPendingInstalls.add(idx, params);
如果不是第一次走這個方法,則方法里面的主要流程
- 先調用mPendingInstalls.add(idx, params);
- 發送一個what值是MCS_BOUND 的Message
我們假設第一次,則先調用connectToService()方法
(四) PackageHandler的connectToService方法解析
代碼在PackageManagerService.java 1104行
private boolean connectToService() {
if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
" DefaultContainerService");
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
if (mContext.bindServiceAsUser(service, mDefContainerConn,
Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mBound = true;
return true;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return false;
}
這里可以看到bind到了一個service,這個service的ComponentName是"DEFAULT_CONTAINER_COMPONENT"這個常量,那我們就來看下這個ComponentName
代碼在PackageManagerService.java 377行
static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
DEFAULT_CONTAINER_PACKAGE,
"com.android.defcontainer.DefaultContainerService");
所以我們知道bind的service是DefaultContainerService。綁定DefaultContainerService之后,設定進程的優先級為THREAD_PRIORITY_DEFAULT。然后等bindServiceAsUser這個方法執行完則又把線程的優先級設為THREAD_PRIORITY_BACKGROUND。
這里面涉及到一個變量mDefContainerConn
1、mDefContainerConn變量
要想研究mDefContainerConn,我們先來看下它的類型
代碼在PackageManagerService.java 921行
final private DefaultContainerConnection mDefContainerConn =
new DefaultContainerConnection();
我們知道了mDefContainerConn的類型是DefaultContainerConnection,那我們來看下DefaultContainerConnection這個類。
代碼在PackageManagerService.java 923行
class DefaultContainerConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
IMediaContainerService imcs =
IMediaContainerService.Stub.asInterface(service);
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
}
public void onServiceDisconnected(ComponentName name) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected");
}
}
通過上面代碼我們知道DefaultContainerConnection實現了ServiceConnection。所以在連接成功的時候會調用onServiceConnected方法,我們看到當連接成功的時候會向mHandler發送一個Message去,這個Message的what值為MCS_BOUND。
2、mDefContainerConn變量與DefaultContainerService
上文我們提及到mContext.bindServiceAsUser(service, mDefContainerConn,Context.BIND_AUTO_CREATE, UserHandle.OWNER)方法,其實就是"綁定"DefaultContainerService。我們知道bind一個Service,其中負責通信的ServiceConnection,而本方法中負責通信的就是mDefContainerConn。所以一旦綁定成功會執行mDefContainerConn的onServiceConnected方法。而現實是當綁定成功后在onServiceConnected中將一個IBinder轉換成了一個IMediaContainerService。這個就是onServiceConnected回調函數中根據參數傳進來的IMediaContainerService.Stub的對象引用創建的一個遠程代理對象,后面PacakgeManagerServic通過該代理對象訪問DefaultContainerService服務
關于DefaultContainerService這里我就不詳細講解了,我會在下一篇文章APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充中的二、DefaultContainerService詳解,進行講解
關于mContext.bindServiceAsUser(service, mDefContainerConn,
Context.BIND_AUTO_CREATE, UserHandle.OWNER)為什么等于bindService這里也不詳細講解了,我們會在后面的文章APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充中的 四、為什么說mContext.bindServiceAsUser等于mContext.bindService 中詳細講解
(五) PackageHandler的HandlerMesaage方法中what值為MCS_BOUND的情況:
void doHandleMessage(Message msg) {
switch (msg.what) {
...
case MCS_BOUND: {
if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
// 第一步
if (msg.obj != null) {
mContainerService = (IMediaContainerService) msg.obj;
}
// 第二步
// 如果綁定DefaultContainerService服務失敗,則不能安裝程序
if (mContainerService == null) {
// 第二步的情況A
if (!mBound) {
// Something seriously wrong since we are not bound and we are not
// waiting for connection. Bail out.
Slog.e(TAG, "Cannot bind to media container service");
for (HandlerParams params : mPendingInstalls) {
// Indicate service bind error
params.serviceError();
}
mPendingInstalls.clear();
} else {
Slog.w(TAG, "Waiting to connect to media container service");
}
// 如果安裝請求隊列不為空
} else if (mPendingInstalls.size() > 0) {
// 第二步的情況B
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
// 調動startCopy函數處理安裝請求
if (params.startCopy()) {
// We are done... look for more work or to
// go idle.
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Checking for more work or unbind...");
// Delete pending install
if (mPendingInstalls.size() > 0) {
// 刪除請求安裝隊列的頭元素,即下標為0的元素
mPendingInstalls.remove(0);
}
// 第二步的情況B下的分支甲
if (mPendingInstalls.size() == 0) {
// 如果安裝請求都處理完了
if (mBound) {
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Posting delayed MCS_UNBIND");
// 通過發送MCS_UNBIND消息處理斷開綁定請求
removeMessages(MCS_UNBIND);
Message ubmsg = obtainMessage(MCS_UNBIND);
// Unbind after a little delay, to avoid
// continual thrashing.
sendMessageDelayed(ubmsg, 10000);
}
} else {
// 第二步的情況B下的分支乙
// There are more pending requests in queue.
// Just post MCS_BOUND message to trigger processing
// of next pending install.
// 如果還有未處理的請求,則繼續發送MCS_BOUND消息
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Posting MCS_BOUND for next work");
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
}
} else {
// Should never happen ideally.
Slog.w(TAG, "Empty queue");
}
break;
}
}
我將上面整體流程分為兩個步驟
- 第一步,獲取mContainerService對象
- 第二步,根據上面的mContainerService是否為空,進入下面兩個分支
- 情況A:如果mContainerService為null,則綁定DefaultContainerService失敗,不能安裝,則調用HandlerParams的serviceError方法,并清空mPendingInstalls列表
- 情況B:如果mContainerService不為null。則獲取安裝等待隊列mPendingInstalls的第一個元素,其實也就是我們最開始添加進去的InstallParams對象。并執行startCopy()方法,執行startCopy()方法后,刪除我們我們獲取的mPendingInstalls第一個元素。這時候又分甲乙兩種情況,甲情況下是沒有元素了,即mPendingInstalls.size=0;乙情況下還有元素,那我們就依次來看下
- 分支甲:mPendingInstalls為空,且當前的綁定狀態還是"綁定"(mBound為true),則發送一個what為MCS_UNBIND的Message消息解除綁定
- 分支乙:mPendingInstalls不為空,繼續發送MCS_BOUND消息,繼續處理下一個,只到隊列為空
上面這個方法涉及到一個核心方法即startCopy()來處理安裝請求,通過方法名叫"startCopy"我們猜測是"開始拷貝"的意思,那我們就一起來看下
(六) HandlerParams的startCopy方法
代碼在PackageManagerService.java 10253行
final boolean startCopy() {
boolean res;
// 第一步
try {
if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
//MAX_RETIRES目前為4,表示嘗試4次安裝,如果還不成功,則認為安裝失敗
if (++mRetries > MAX_RETRIES) {
Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
mHandler.sendEmptyMessage(MCS_GIVE_UP);
handleServiceError();
return false;
} else {
// 調用handleStartCopy抽象方法
handleStartCopy();
res = true;
}
} catch (RemoteException e) {
if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
mHandler.sendEmptyMessage(MCS_RECONNECT);
res = false;
}
// 第二步
// 調用handleReturnCode抽象方法,這個方法會在handleStartCopy執行完拷貝相關行為之后,根據handleStartCopy做進一步的處理,主要返回狀態碼
handleReturnCode();
return res;
}
這里會涉及到一個HandlerParams和InstallParams的關系請參考APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充中的五、HandlerParams與InstallParams簡介
我將上面方法內部劃分為兩個部分
- 第一部分,即try-catch塊內部,判斷是錯次數,是否大于四次,這里有兩種情況:
- 如果超過4次,即mRetries>=4則,表示已經嘗試了4次(4次是極限),則發送一個what值為MCS_GIVE_UP的Message放棄本次安裝。
- 如果沒超過4次, 則調用handleStartCopy()方法,如果在這個方法中出現異常。其中handleStartCopy()為真正的核心方法。如果在handleStartCopy()方法調用的時候產生了異常則發送一個what為MCS_RECONNECT的Message
- 上面完成之后,調用handleReturnCode()
這個方法涉及了到兩個Message的what值的處理邏輯,這兩個what值分別是MCS_GIVE_UP和MCS_RECONNECT,按我們就來一起來看下:
MCS_RECONNECT代碼在PackageManagerService.java 1226行
MCS_GIVE_UP代碼在PackageManagerService.java 1262行
為了方便我直接把他們合在一起了。
void doHandleMessage(Message msg) {
switch (msg.what) {
...
case MCS_RECONNECT: {
if (DEBUG_INSTALL) Slog.i(TAG, "mcs_reconnect");
if (mPendingInstalls.size() > 0) {
if (mBound) {
disconnectService();
}
if (!connectToService()) {
Slog.e(TAG, "Failed to bind to media container service");
for (HandlerParams params : mPendingInstalls) {
// Indicate service bind error
params.serviceError();
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(params));
}
mPendingInstalls.clear();
}
}
break;
}
case MCS_GIVE_UP: {
if (DEBUG_INSTALL) Slog.i(TAG, "mcs_giveup too many retries");
HandlerParams params = mPendingInstalls.remove(0);
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(params));
break;
}
...
}
}
- MCS_RECONNECT:判斷安裝請求隊列mPendingInstalls是否還有元素,如果有元素先斷開綁定,則再次重新調用connectToService方法,我們知道connectToService()內部會再次執行綁定DefaultContainerService,而在綁定成功后會再次發送一個what值為MCS_BOUND的Message,從而又回到了startCopy里面。
- MCS_GIVE_UP:直接刪除了安裝請求隊列mPendingInstalls里面下標為0的元素。
通過上文我們知道這個的HandlerParams的具體實現類是InstallParams,即handleStartCopy方法的具體實現是在InstallParams的handleStartCopy里面。
綜上所述:
startCopy()方法調用其子類的InstallParams的handleStartCopy()來完成拷貝工作的,考慮到安裝過程的不去誒的那個性,startCopy主要工作是進行錯誤處理,當捕獲到handleStartCopy跑出的異常時,startCopy將發送MCS_RECONNECT,在MCS_RECONNECT消息處理中,將會重新綁定
DefaultContainerService,如果綁定成功,那么安裝過程將會重新開始。startCopy也就將會再次調用,重試的次數記錄在mRetries中,當累計重試超過4次時,安裝將失敗,如果安裝失敗,startCopy將會調用handleReturnCode()方法來處理。
下面我們就來看下handleStartCopy的具體實現
(七) InstallParams的handleStartCopy方法
代碼在PackageManagerService.java 10307行
/*
* Invoke remote method to get package information and install
* location values. Override install location based on default
* policy if needed and then create install arguments based
* on the install location.
*/
public void handleStartCopy() throws RemoteException {
int ret = PackageManager.INSTALL_SUCCEEDED;
// 第一步
// If we're already staged, we've firmly committed to an install location
// 是安裝在手機內部存儲空間還是sdcard中,設置對應標志位
// 新安裝的情況下stage為false
if (origin.staged) {
if (origin.file != null) {
installFlags |= PackageManager.INSTALL_INTERNAL;
installFlags &= ~PackageManager.INSTALL_EXTERNAL;
} else if (origin.cid != null) {
installFlags |= PackageManager.INSTALL_EXTERNAL;
installFlags &= ~PackageManager.INSTALL_INTERNAL;
} else {
throw new IllegalStateException("Invalid stage location");
}
}
// 是否安裝在SD卡上
final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
// 是否安裝在內部空間上
final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
PackageInfoLite pkgLite = null;
// 檢查APK的安裝位置是否正確
if (onInt && onSd) {
// 如果既要安裝到SD卡上也要安裝上內部空間,則由沖突安裝失敗
// Check if both bits are set.
Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else {
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
packageAbiOverride);
/*
* If we have too little free space, try to free cache
* before giving up.
*/
// 釋放存儲空間
if (!origin.staged && pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
// TODO: focus freeing disk space on the target device
final StorageManager storage = StorageManager.from(mContext);
final long lowThreshold = storage.getStorageLowBytes(
Environment.getDataDirectory());
final long sizeBytes = mContainerService.calculateInstalledSize(
origin.resolvedPath, isForwardLocked(), packageAbiOverride);
if (mInstaller.freeCache(null, sizeBytes + lowThreshold) >= 0) {
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath,
installFlags, packageAbiOverride);
}
/*
* The cache free must have deleted the file we
* downloaded to install.
*
* TODO: fix the "freeCache" call to not delete
* the file we care about.
*/
if (pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
pkgLite.recommendedInstallLocation
= PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
}
}
// 第二步
if (ret == PackageManager.INSTALL_SUCCEEDED) {
int loc = pkgLite.recommendedInstallLocation;
if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
ret = PackageManager.INSTALL_FAILED_INVALID_APK;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
ret = PackageManager.INSTALL_FAILED_INVALID_URI;
} else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
} else {
// Override with defaults if needed.
loc = installLocationPolicy(pkgLite);
if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {
ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
} else if (!onSd && !onInt) {
// Override install location with flags
if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
// Set the flag to install on external media.
installFlags |= PackageManager.INSTALL_EXTERNAL;
installFlags &= ~PackageManager.INSTALL_INTERNAL;
} else {
// Make sure the flag for installing on external
// media is unset
installFlags |= PackageManager.INSTALL_INTERNAL;
installFlags &= ~PackageManager.INSTALL_EXTERNAL;
}
}
}
}
// 第三步
//createInstallArgs 用于創建一個安裝參數對象
final InstallArgs args = createInstallArgs(this);
mArgs = args;
// 第四步
if (ret == PackageManager.INSTALL_SUCCEEDED) {
/*
* ADB installs appear as UserHandle.USER_ALL, and can only be performed by
* UserHandle.USER_OWNER, so use the package verifier for UserHandle.USER_OWNER.
*/
int userIdentifier = getUser().getIdentifier();
if (userIdentifier == UserHandle.USER_ALL
&& ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0)) {
userIdentifier = UserHandle.USER_OWNER;
}
/*
* Determine if we have any installed package verifiers. If we
* do, then we'll defer to them to verify the packages.
*/
final int requiredUid = mRequiredVerifierPackage == null ? -1
: getPackageUid(mRequiredVerifierPackage, userIdentifier);
if (!origin.existing && requiredUid != -1
&& isVerificationEnabled(userIdentifier, installFlags)) {
// 對當前包做驗證操作
final Intent verification = new Intent(
Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
verification.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)),
PACKAGE_MIME_TYPE);
verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
final List<ResolveInfo> receivers = queryIntentReceivers(verification,
PACKAGE_MIME_TYPE, PackageManager.GET_DISABLED_COMPONENTS,
0 /* TODO: Which userId? */);
if (DEBUG_VERIFY) {
Slog.d(TAG, "Found " + receivers.size() + " verifiers for intent "
+ verification.toString() + " with " + pkgLite.verifiers.length
+ " optional verifiers");
}
final int verificationId = mPendingVerificationToken++;
verification.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId);
verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE,
installerPackageName);
verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALL_FLAGS,
installFlags);
verification.putExtra(PackageManager.EXTRA_VERIFICATION_PACKAGE_NAME,
pkgLite.packageName);
verification.putExtra(PackageManager.EXTRA_VERIFICATION_VERSION_CODE,
pkgLite.versionCode);
if (verificationParams != null) {
if (verificationParams.getVerificationURI() != null) {
verification.putExtra(PackageManager.EXTRA_VERIFICATION_URI,
verificationParams.getVerificationURI());
}
if (verificationParams.getOriginatingURI() != null) {
verification.putExtra(Intent.EXTRA_ORIGINATING_URI,
verificationParams.getOriginatingURI());
}
if (verificationParams.getReferrer() != null) {
verification.putExtra(Intent.EXTRA_REFERRER,
verificationParams.getReferrer());
}
if (verificationParams.getOriginatingUid() >= 0) {
verification.putExtra(Intent.EXTRA_ORIGINATING_UID,
verificationParams.getOriginatingUid());
}
if (verificationParams.getInstallerUid() >= 0) {
verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID,
verificationParams.getInstallerUid());
}
}
final PackageVerificationState verificationState = new PackageVerificationState(
requiredUid, args);
mPendingVerification.append(verificationId, verificationState);
final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
receivers, verificationState);
// Apps installed for "all" users use the device owner to verify the app
UserHandle verifierUser = getUser();
if (verifierUser == UserHandle.ALL) {
verifierUser = UserHandle.OWNER;
}
/*
* If any sufficient verifiers were listed in the package
* manifest, attempt to ask them.
*/
if (sufficientVerifiers != null) {
final int N = sufficientVerifiers.size();
if (N == 0) {
Slog.i(TAG, "Additional verifiers required, but none installed.");
ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
} else {
for (int i = 0; i < N; i++) {
final ComponentName verifierComponent = sufficientVerifiers.get(i);
final Intent sufficientIntent = new Intent(verification);
sufficientIntent.setComponent(verifierComponent);
mContext.sendBroadcastAsUser(sufficientIntent, verifierUser);
}
}
}
final ComponentName requiredVerifierComponent = matchComponentForVerifier(
mRequiredVerifierPackage, receivers);
if (ret == PackageManager.INSTALL_SUCCEEDED
&& mRequiredVerifierPackage != null) {
/*
* Send the intent to the required verification agent,
* but only start the verification timeout after the
* target BroadcastReceivers have run.
*/
verification.setComponent(requiredVerifierComponent);
mContext.sendOrderedBroadcastAsUser(verification, verifierUser,
android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final Message msg = mHandler
.obtainMessage(CHECK_PENDING_VERIFICATION);
msg.arg1 = verificationId;
mHandler.sendMessageDelayed(msg, getVerificationTimeout());
}
}, null, 0, null, null);
/*
* We don't want the copy to proceed until verification
* succeeds, so null out this field.
*/
mArgs = null;
}
} else {
// 不需要做驗證
/*
* No package verification is enabled, so immediately start
* the remote call to initiate copy using temporary file.
*/
// 調用InstallArgs的copyApk函數
ret = args.copyApk(mContainerService, true);
}
}
mRet = ret;
}
- 第一步:判斷origin.staged的值,要判斷origin.staged的值,這里origin.staged為false,關于為什么origin.staged為false,請查看APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充中的七、為什么新安裝的情況下 origin.staged等于false
- 第二步:設置ret參數
- 第三步:創建安裝參數對象args
- 第四步:這里根絕是否需要驗證分為兩種情況
- 情況1——需要驗證:需要構建一個Intent對象verifaction,并設置相應的參數,然后發送一個廣播進行包驗證。在廣播的onReceive中,則發送了一個what值為CHECK_PENDING_VERIFICATION的message,這個message的arg1的值為verificationId,
- 情況2——不需要驗證:直接調用InstallArgs的copyApk方法進行包拷貝
整體流程圖下圖:
這方面里面涉及到的幾個比較難理解的地方,我就不一一解答了,我會下一篇依次解答,如下:
- 1、mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride)方法:
- 2、mContainerService.calculateInstalledSize(origin.resolvedPath, isForwardLocked(), packageAbiOverride)方法:
請參考APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充中的 三、mContainerService.getMinimalPackageInfo(String.int,String)方法與calculateInstalledSize(String,boolean,String)方法的講解 里面會想詳細講解- 3、createInstallArgs(InstallParams)方法:
請參考APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充中的九、createInstallArgs(InstallParams)方法解答- 4、isVerificationEnabled(int userId, int installFlags) 的理解:
請參考APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充中的十、sVerificationEnabled(int userId, int installFlags) 的理解方法解答- 6、Context.sendBroadcast(Intent intent)的功能是否和Context.sendBroadcastAsUser一樣:
請參考APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充中的十一、Context.sendBroadcast(Intent intent)的功能是和Context.sendBroadcastAsUser(Intent,UserHandle)一樣的解答
這里面會涉及兩個問題如下:
- 1、what值為CHECK_PENDING_VERIFICATION的Message的處理內容
- 2、InstallArgs的copyApk(IMediaContainerService, boolean)方法的具體實現
我在這里說下
1、what值為CHECK_PENDING_VERIFICATION的Message的處理內容
代碼在PackageManagerService.java 1506行
case CHECK_PENDING_VERIFICATION: {
// 獲取verificationId
final int verificationId = msg.arg1;
// mPendingVerification 是一個SparseArray,鍵值對是verificationId和其對應的PackageVerificationState
// PackageVerificationState 可以理解為安裝包驗證的狀態
final PackageVerificationState state = mPendingVerification.get(verificationId);
// 如果包的驗證狀態不為空,并且驗證沒有超時
if ((state != null) && !state.timeoutExtended()) {
final InstallArgs args = state.getInstallArgs();
final Uri originUri = Uri.fromFile(args.origin.resolvedFile);
Slog.i(TAG, "Verification timed out for " + originUri);
// 移除verificationId對應的PackageVerificationState對象
mPendingVerification.remove(verificationId);
// 初始化ret值為 驗證失敗導致安裝失敗
int ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
// 如果默認的包驗證器允許繼續安裝
if (getDefaultVerificationResponse() == PackageManager.VERIFICATION_ALLOW) {
Slog.i(TAG, "Continuing with installation of " + originUri);
// 設置驗證跟蹤類別,PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT表示:允許在沒有驗證者的情況下進行安裝
state.setVerifierResponse(Binder.getCallingUid(),
PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT);
//發送廣播,對應的ACTION為ACTION_PACKAGE_VERIFIED
broadcastPackageVerified(verificationId, originUri,
PackageManager.VERIFICATION_ALLOW,
state.getInstallArgs().getUser());
try {
// 調用args的copyApk(IMediaContainerService,boolean)方法
ret = args.copyApk(mContainerService, true);
} catch (RemoteException e) {
Slog.e(TAG, "Could not contact the ContainerService");
}
} else {
// 如果默認的包驗證器,拒絕驗證,則發送廣播告訴之
broadcastPackageVerified(verificationId, originUri,
PackageManager.VERIFICATION_REJECT,
state.getInstallArgs().getUser());
}
// 調用processPendingInstall方法
processPendingInstall(args, ret);
// 發送一個what值為MCS_UNBIND的Message
mHandler.sendEmptyMessage(MCS_UNBIND);
}
break;
}
我們發現無論是否需要驗證,最后都是調用InstallArgs的copyApk(IMediaContainerService,boolean)方法,而且這兩個入參都是固定的,第一個參數為mContainerService,第二個參數為true。所以最后的拷貝肯定是通過copyApk來執行的,通過上面的代碼我們知道這個InstallArgs其實是FileInstallArgs。所以我們只需要關注FileInstallArgs的copyApk(IMediaContainerService,boolean)方法即可
2、FileInstallArgs的copyApk(IMediaContainerService,boolean)方法
代碼在PackageManagerService.java 11050行
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
// 上面我們已經說了,在新安裝的情況下origin.staged=false
if (origin.staged) {
if (DEBUG_INSTALL) Slog.d(TAG, origin.file + " already staged; skipping copy");
codeFile = origin.file;
resourceFile = origin.file;
return PackageManager.INSTALL_SUCCEEDED;
}
// 第一步
// 獲取目錄
try {
// 創建目錄
final File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid);
codeFile = tempDir;
resourceFile = tempDir;
} catch (IOException e) {
Slog.w(TAG, "Failed to create copy file: " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
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;
// 第二步
// 真正的文件拷貝
ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
if (ret != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Failed to copy package");
return ret;
}
// 第三步
// 獲取庫的跟目錄
final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
NativeLibraryHelper.Handle handle = null;
// 拷貝 Native代碼 即so文件
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;
}
我將上面的內容大體上分為三個步驟
- 第一步:創建目錄文件
- 第二步:進行代碼拷貝
- 第三步:進行Native代碼拷貝
這里重點說下這行代碼ret = imcs.copyPackage(origin.file.getAbsolutePath(), target)。我們知道imc其實是一個遠程的代理,實際的調用方式DefaultContainerService的成員變量mBinder。那我們就一起來看下DefaultContainerService的成員變量mBinder的copyPackage方法的執行
3、DefaultContainerService#mBinder#copyPackage方法
代碼在DefaultContainerService.java 128行
/**
* Copy package to the target location.
*
* @param packagePath absolute path to the package to be copied. Can be
* a single monolithic APK file or a cluster directory
* containing one or more APKs.
* @return returns status code according to those in
* {@link PackageManager}
*/
@Override
public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
// 第一步:
if (packagePath == null || target == null) {
return PackageManager.INSTALL_FAILED_INVALID_URI;
}
// 第二步:
PackageLite pkg = null;
try {
final File packageFile = new File(packagePath);
pkg = PackageParser.parsePackageLite(packageFile, 0);
// 第三步
return copyPackageInner(pkg, target);
} catch (PackageParserException | IOException | RemoteException e) {
Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
}
太好了,居然有注釋,那我們就來看下注釋
將包復制到目標位置
- 入參:packagePath:要復制包的絕對路徑,可以是一個APK文件,也可以是多個APK文件
- 出參:安裝的狀態碼
我將這方法內部整體流程分為三步
- 第一步,判斷入參packagePath和target是否為空,如果為空則直接返回安裝失敗,失敗原因不正確的地址路徑
- 第二步,通過PackageParser的PackageParser方法解析出"輕量級"的安裝包內容。
- 第三步,調用copyPackageInner(PackageLite, IParcelFileDescriptorFactory)方法
PS:在第二步和第三步中有個try-catch塊,如果其中出現異常,則直接返回安裝失敗,失敗原因是空間不足。
所以我們看出這個copyPackage方法內部,其實沒有真正的實現包拷貝,而是類似于"預加載"的作用,先獲取一個"輕量級"的安裝包。
那我們來看下copyPackageInner方法
4、DefaultContainerService#copyPackageInner方法
代碼在DefaultContainerService.java 367行
private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
throws IOException, RemoteException {
// 第一部分
copyFile(pkg.baseCodePath, target, "base.apk");
// 第二部分
if (!ArrayUtils.isEmpty(pkg.splitNames)) {
for (int i = 0; i < pkg.splitNames.length; i++) {
copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk");
}
}
return PackageManager.INSTALL_SUCCEEDED;
}
我將這個方法的內部執行分為兩個部分
- 第一部分:調用copyFile,注意這里傳入兩個的參數是baseCodePath和"base.apk"
- 第二部分:如果PackageLite有APK拆分,則遍歷所有的分APK,再依次調用copyFile
我們看到這里調用了copyFile(String, IParcelFileDescriptorFactory, String)方法來進行的代碼拷貝,那我們就來看下copyFile里面是怎么執行的
5、DefaultContainerService#copyFile(String, IParcelFileDescriptorFactory, String)方法
代碼在DefaultContainerService.java 379行
private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName)
throws IOException, RemoteException {
Slog.d(TAG, "Copying " + sourcePath + " to " + targetName);
InputStream in = null;
OutputStream out = null;
try {
// 輸入流
in = new FileInputStream(sourcePath);
// 輸出流
out = new ParcelFileDescriptor.AutoCloseOutputStream(
target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE));
// 進行拷貝
Streams.copy(in, out);
} finally {
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(in);
}
}
我們看到這個方法很簡單,就是構建輸入流,構建輸出流,然后調用Streams的方法進行拷貝。
這里順帶說下Streams.java這個類,這個類是libcore/io/Streams.java。copy方法如下:
/**
* Copies all of the bytes from {@code in} to {@code out}. Neither stream is closed.
* Returns the total number of bytes transferred.
*/
public static int copy(InputStream in, OutputStream out) throws IOException {
int total = 0;
byte[] buffer = new byte[8192];
int c;
while ((c = in.read(buffer)) != -1) {
total += c;
out.write(buffer, 0, c);
}
return total;
}
通過這個方法完成了安裝包的拷貝功能
至此 PMS中的新安裝流程上(拷貝)已經全部講解完成
上一篇文章 APK安裝流程詳解11——普通應用安裝簡介
下一篇文章 APK安裝流程詳解13——PackageManagerService中的新安裝流程下(裝載)