前言
- android系統(tǒng)里app有哪些類型及其安裝涉及目錄宋梧、所需權(quán)限是什么缀踪?
- apk安裝有幾種方式忿薇?
- apk安裝流程會涉及到哪些android系統(tǒng)知識?
- apk安裝的過程大體上分哪幾步岳链?
- apk安裝的過程中涉及到比較重要的類有哪些花竞?分別用來做什么?
- 了解apk安裝流程有什么用掸哑?
Android系統(tǒng)里app類型约急、安裝涉及目錄、所需權(quán)限
1./system/framwork:
- 保存的是資源型的應(yīng)用程序苗分,它們用來打包資源文件
2./system/app:
- 系統(tǒng)自帶的應(yīng)用程序烤宙,獲得adb root 權(quán)限才能刪除。
- 如果想修改該目錄下的app俭嘁,必須手動push新的apk進去躺枕,該新app文件不會自動被安裝,需要系統(tǒng)重啟時供填,系統(tǒng)檢查到apk被更新拐云,才會去安裝。
- 系統(tǒng)app升級時近她,實際上是在/data/app里重新安裝了一個app叉瘩,只是這個路徑會重新注冊到系統(tǒng)那里去,當打開該系統(tǒng)app時粘捎,會指向新app的地址薇缅。
- 如果你卸載該更新后的系統(tǒng)app,系統(tǒng)只是卸載/data/app里的app攒磨,然后恢復(fù)/system/app里的app給用戶使用泳桦。
3./system/priv-app:
- 這里放的是權(quán)限更高的系統(tǒng)核心應(yīng)用,例如系統(tǒng)設(shè)置娩缰、系統(tǒng)UI灸撰、開機launcher等,這個目錄非常重要,一般情況下不建議改動浮毯。
4./vendor/app:
- 保存設(shè)備廠商提供的應(yīng)用程序
5./data/app:
- 用戶程序安裝的目錄完疫,安裝時把apk文件復(fù)制到此目錄。因為Android機有內(nèi)部存儲和SD卡兩部分债蓝,很多Android機為了節(jié)省內(nèi)存空間壳鹤,會把apk安裝到SD卡上。如此以來饰迹,/data/app在大部分Android機上會有2個器虾,1個在內(nèi)部存儲1個在SD卡里。
6./data/app-private:
- 保存受DRM保護的私有應(yīng)用程序蹦锋,例如一個受保護的歌曲或受保護的視頻是使用 DRM保護的文件兆沙。
7./data/data:
- 存放應(yīng)用程序數(shù)據(jù)的目錄
8./data/dalvik-cache:
- 存放apk中的dex文件。dex文件是dalvik虛擬機的可執(zhí)行文件莉掂,其大小約為原始apk文件大小的四分之一葛圃,但是ART-Android Runtime的可執(zhí)行文件格式為.oat,所以啟動ART時憎妙,系統(tǒng)會執(zhí)行dex文件轉(zhuǎn)換至oat文件
9./data/system:
該目錄主要是存放一些系統(tǒng)文件
- packages.xml文件:類似于Window的注冊表库正,這個文件是解析apk時由writeLP()創(chuàng)建的,里面記錄了系統(tǒng)的permissons厘唾,以及每個apk的name褥符,codePath,flag抚垃,ts喷楣,version,userid鹤树,native 庫的存儲路徑等信息铣焊,這些信息主要通過apk的AndroidManifest解析獲取,解析完apk后將更新信息寫入這個文件并保存到flash罕伯,下次開機的時候直接從里面讀取相關(guān)信息并添加到內(nèi)存相關(guān)列表中曲伊。當有apk升級,安裝或刪除時會更新這個文件追他。
- pakcages-back.xml:packages.xml文件的備份坟募。
- pakcages-stoped.xml:記錄系統(tǒng)中被強制停止的運行的應(yīng)用信息,系統(tǒng)在強制停止某個應(yīng)用的時候邑狸,會將應(yīng)用的信息記錄在該文件中懈糯。
- pakcages-stoped-backup.xml:pakcages-stoped.xml文件的備份。
- package.list:packages.list指定了應(yīng)用的默認存儲位置/data/data/com.xxx.xxx推溃;
- 這5個文件中pakcages-back.xml和pakcages-stoped-backup.xml是備份文件昂利。當Android對文件packages.xml和pakcages-stoped.xml寫之前届腐,會先把它們備份铁坎,如果寫文件成功了蜂奸,再把備份文件刪除。如果寫的時候硬萍,系統(tǒng)出問題了扩所,重啟后在需要讀取這兩個文件時,如果發(fā)現(xiàn)備份文件存在朴乖,會使用備份文件的內(nèi)容祖屏,因為源文件可能已經(jīng)損壞了。其中packages.xml是PackageManagerServcie啟動時买羞,需要用到的文件袁勺。
APK安裝主體流程
1.復(fù)制APK到/data/app目錄下,解壓并掃描安裝包
2.將APP的dex文件拷貝到/data/dalvik-cache目錄畜普,再在/data/data/目錄下創(chuàng)建應(yīng)用程序的數(shù)據(jù)目錄(以應(yīng)用包名命令)期丰,用來存放應(yīng)用的數(shù)據(jù)庫、xml文件吃挑、cache钝荡、二進制的so動態(tài)庫等
3.解析apk的AndroidManifest.xml文件,注冊四大組件舶衬,將apk的權(quán)限埠通、應(yīng)用包名、apk的安裝位置逛犹、版本凌埂、userID等重要信息保存在/data/system/packages.xml文件中磅废。這些操作都是在PackageManagerService中完成。
4.資源管理器解析APK里的資源文件。
5.dex2oat操作,對dex文件進行優(yōu)化灸拍,并保存在dalvik-cache目錄下轩性。
6.更新權(quán)限信息脯厨。
7.安裝完成后稼跳,發(fā)送Intent.ACTION_PACKAGE_ADDED廣播卸伞。
8.顯示icon圖標,應(yīng)用程序經(jīng)過PMS中的邏輯處理后绍豁,相當于已經(jīng)注冊好了芬位,如果想要在Android桌面上看到icon圖標,則需要Launcher將系統(tǒng)中已經(jīng)安裝的程序展現(xiàn)在桌面上箭养。
總體說來就兩件事情拷貝APK和解析APK,解析APK主要是解析APK的應(yīng)用配置文件AndroidManifest.xml蛙奖,以便獲得它的安裝信息缸兔。在安裝的過程中還會這個應(yīng)用分配Linux用戶ID和Linux用戶組ID(以便它可以在系統(tǒng)中獲取合適的運行權(quán)限)鼻听。
Tips财著,dexopt/dex2oat 操作在不同版本下的區(qū)別:
- Dalvik虛擬機-dexopt: 該時機在第一次執(zhí)行時app時 非安裝時(詳情見 Android運行流程)
- ART虛擬機-dex2oat:AOT (Ahead-Of-Time) 運行前編譯
- Android O(8.0)前,將dex字節(jié)碼翻譯成本地字節(jié)碼oat(非全量)
- Android O(8.0)開始:開始新增vdex概念精算,不是為了提升性能瓢宦,而是為了避免不必要的驗證Dex 文件合法性的過程,例如首次安裝時進行dex2oat時會校驗Dex 文件各個section的合法性灰羽,這時候使用的compiler filter 為了照顧安裝速度等方面驮履,并沒有采用全量編譯鱼辙,當app盤啟動后,運行一段時間后玫镐,收集了足夠多的jit 熱點方法信息倒戏,Android會在后臺重新進行dex2oat,將熱點方法編譯成機器代碼恐似,這時候就不用再重復(fù)做驗證Dex文件的過程了杜跷。
APK安裝的幾種方式
-
1、系統(tǒng)安裝
系統(tǒng)啟動后調(diào)用 PackageManagerService.main() 初始化注冊解析安裝工作矫夷。PackageManagerService處理各種應(yīng)用的安裝葛闷,卸載,管理等工作双藕,開機時由systemServer啟動此服務(wù)淑趾。
第1步:PackageManagerService.main()初始化注冊
第2步:建立Java層的installer與C層的intalld的socket聯(lián)接
第3步:建立PackageHandler消息循環(huán)
第4步:調(diào)用成員變量mSettings的readLPw()方法恢復(fù)上一次的安裝信息
第5步:.jar文件的detopt優(yōu)化
第6步:scanDirLI函數(shù)掃描特定目錄的APK文件解析
第7步:updatePermissionsLPw()函數(shù)分配權(quán)限
第8步:調(diào)用mSettings.writeLPr()保存安裝信息
-
2、adb 命令安裝
通過 pm 參數(shù)忧陪,調(diào)用 PM 的 runInstall 方法扣泊,進入 PackageManagerService 進行安裝工作。
第1步:pm.java的runInstall()方法
第2步:參數(shù)不對會調(diào)用showUsage方法嘶摊,彈出使用說明
第3步:正常情況runInstall會調(diào)用mPm變量的installPackageWithVerification方法
第4步:由于pm.java中的變量mPm是PackageManagerService的實例延蟹,所以實際上是調(diào)用PackageManagerService的installPackageWithVerfication()方法
第5步:進入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第6步:成功綁定了com.android.defcontainer.DefaultContainerService服務(wù),進入MCS_BOUND分支
第7步:里面調(diào)用PackageManagerService中內(nèi)部抽象類HandlerParams的子類InstallParams的startCopy方法叶堆。
第8步:抽象類的HandlerParams的startCopy方法調(diào)用了HandlerParams子類的handleStartCopy和handlerReturnCode兩個方法
第9步:handlesStartCopy方法調(diào)用了InstallArgs的子類copyApk阱飘,它負責將下載的APK文件copy到/data/app
第10步:handleReturnCode調(diào)用handleReturnCode方法
第11步:調(diào)用PackageManagerService服務(wù)的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法進行APK掃描。
第12步:上面的方法判斷是否APP應(yīng)安裝蹂空,調(diào)用installNewPackageLI或replacePackageLI方法
第13步:調(diào)用updateSettingsLI方法進行更新PackageManagerService的Settings
第14步:發(fā)送what值為POST_INSTALL的Message給PackageHandler進行處理
第15步:發(fā)送what值為MCS_UNBIND的Message給PackageHandler俯萌,進而調(diào)用PackageHandler.disconnectService()中斷連接
-
3、應(yīng)用市場安裝
這個要視應(yīng)用的權(quán)限上枕,有系統(tǒng)的權(quán)限無安裝界面(例如MiUI的小米應(yīng)用商店)咐熙。
第1步:調(diào)用PackageManagerService的installPackage方法
第2步:上面的方法調(diào)用installPackageWithVerfication(),進行權(quán)限校驗辨萍,發(fā)送INIT_COPY的msg
第3步:進入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第4步:成功綁定了com.android.defcontainer.DefaultContainerService服務(wù)棋恼,進入MCS_BOUND分支
第5步:里面調(diào)用PackageManagerService中內(nèi)部抽象類HandlerParams的子類InstallParams的startCopy方法。
第6步:抽象類的HandlerParams的startCopy方法調(diào)用了HandlerParams子類的handleStartCopy和handlerReturnCode兩個方法
第7步:handlesStartCopy方法調(diào)用了InstallArgs的子類copyApk锈玉,它負責將下載的APK文件copy到/data/app
第8步:handleReturnCode調(diào)用handleReturnCode方法
第9步:調(diào)用PackageManagerService服務(wù)的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法進行APK掃描爪飘。
第10步:上面的方法判斷是否APP應(yīng)安裝,調(diào)用installNewPackageLI或replacePackageLI方法
第11步:調(diào)用updateSettingsLI方法進行更新PackageManagerService的Settings
第12步:發(fā)送what值為POST_INSTALL的Message給PackageHandler進行處理
第13步:發(fā)送what值為MCS_UNBIND的Message給PackageHandler拉背,進而調(diào)用PackageHandler.disconnectService()中斷連接
-
4师崎、第三方安裝
有安裝界面,通過PackageInstaller.apk來處理椅棺,安裝及卸載的過程的界面先調(diào)用 InstallStart(是一個Activity) 進行權(quán)限檢查之后啟動 PackageInstallActivity犁罩,調(diào)用 PackageInstallActivity 的 startInstall 方法齐蔽,點擊 OK 按鈕后進入 PackageManagerService 完成拷貝解析安裝工作。
第1步:通過隱式跳轉(zhuǎn)啟動InstallStart
第2步:在InstallStart的onCreate生命周期里判斷如果不允許未知來源的則阻止安裝床估、targetSdkVersion<0阻止安裝含滴,如果targetSdkVersion大于等于26(8.0), 且獲取不到REQUEST_INSTALL_PACKAGES權(quán)限也中止安裝,然后啟動PackageInstallerActivity
第3步:調(diào)用PackageInstallerActivity的onCreate方法初始化安裝界面
第4步:初始化界面以后調(diào)用initiateInstall方法
第5步:上面的方法調(diào)用startInstallConfirm方法丐巫,彈出確認和取消安裝的按鈕
第6步:點擊確認按鈕谈况,打開新的activity:InstallAppProgress
第7步:InstallAppProgress類初始化帶有進度條的界面之后,調(diào)用PackageManager的installPackage方法
第8步:PackageManager是PackageManagerService實例递胧,所以就是調(diào)用PackageManagerService的installPackage方法
第9步:調(diào)用PackageManagerService的installPackage方法
第10步:上面的方法調(diào)用installPackageWithVerfication()碑韵,進行權(quán)限校驗,發(fā)送INIT_COPY的msg
第11步:進入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第12步:成功綁定了com.android.defcontainer.DefaultContainerService服務(wù)谓着,進入MCS_BOUND分支
第13步:里面調(diào)用PackageManagerService中內(nèi)部抽象類HandlerParams的子類InstallParams的startCopy方法泼诱。
第14步:抽象類的HandlerParams的startCopy方法調(diào)用了HandlerParams子類的handleStartCopy和handlerReturnCode兩個方法
第15步:handlesStartCopy方法調(diào)用了InstallArgs的子類copyApk坛掠,它負責將下載的APK文件copy到/data/app
第16步:handleReturnCode調(diào)用handleReturnCode方法
第17步:調(diào)用PackageManagerService服務(wù)的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法進行APK掃描赊锚。
第18步:上面的方法判斷是否APP應(yīng)安裝,調(diào)用installNewPackageLI或replacePackageLI方法
第19步:調(diào)用updateSettingsLI方法進行更新PackageManagerService的Settings
第20步:發(fā)送what值為POST_INSTALL的Message給PackageHandler進行處理
第21步:發(fā)送what值為MCS_UNBIND的Message給PackageHandler屉栓,進而調(diào)用PackageHandler.disconnectService()中斷連接
以上4種安裝apk方式最終都會走到PackageManagerService舷蒲,其中第4種方式走的流程最長最完整,所以下面我們會用第4種方式的流程進行代碼詳細分析友多。
APK安裝詳細代碼流程(android-10.0.0_r14)
-
第1步 通過隱式跳轉(zhuǎn)啟動InstallStart
InstallStart是從Android8.0開始PackageInstaller.apk的入口Activity牲平,7.0 隱式匹配的 Activity 是 PackageInstallerActivity。
platform/frameworks/base/packages/PackageInstaller/AndroidManifest.xml
...
<activity android:name=".InstallStart"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
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="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="package" />
<data android:scheme="content" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.content.pm.action.CONFIRM_INSTALL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
...
從上面的Manifest文件我們可以看出除了通過android:mimeType=application/vnd.android.package-archive
能啟動入口Activity-InstallStart域滥,還能通過action為android.intent.action.INSTALL_PACKAGE
的其他Scheme以及"android.content.pm.action.CONFIRM_INSTALL
啟動纵柿,其中定義了兩個 scheme:content 和 package,這2種scheme數(shù)據(jù)會在后面流程里的PackageInstallerActivity里解析通過不同的方式獲取到安裝包信息启绰,最后在InstallInstalling再通過不同的安裝方式來完成安裝動作(后續(xù)會有詳細分析)昂儒。
content: 在 file 協(xié)議的處理中調(diào)用了
PackageUtil.getPackageInfo
方法,方法內(nèi)部調(diào)用了PackageParser.parsePackage()
把 APK 文件的 manifest 和簽名信息都解析完成并保存在了 Package委可,Package 包含了該 APK 的所有信息渊跋,調(diào)用 PackageParser.generatePackageInfo 生成 PackageInfo。package: 在 package 協(xié)議中調(diào)用了
PackageManager.getPackageInfo
方法生成 PackageInfo着倾,PackageInfo 是跨進程傳遞的包數(shù)據(jù)(activities拾酝、receivers、services卡者、providers蒿囤、permissions等等)包含 APK 的所有信息。-
第2步 InstallStart啟動崇决,進入其onCreate()生命周期
其實onCreate()生命周期里主要做了如下事情:
1.判斷當前的apk包是否是可信任來源材诽,如果是不可信任來源镶摘,且沒有申請不可信任來源包安裝權(quán)限,則會強制結(jié)束當前的Activity岳守。
2.此時就會從data從獲取到我們傳遞進來的apk包的地址uri凄敢,并且設(shè)置下一個啟動的Activity為InstallStaging。
3.啟動InstallStaging 這個Activity湿痢。
源碼如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIPackageManager = AppGlobals.getPackageManager();
Intent intent = getIntent();
String callingPackage = getCallingPackage();
final boolean isSessionInstall =
PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
// If the activity was started via a PackageInstaller session, we retrieve the calling
// package from that session
final int sessionId = (isSessionInstall
? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
: -1);
if (callingPackage == null && sessionId != -1) {
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
}
final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
final int originatingUid = getOriginatingUid(sourceInfo);
boolean isTrustedSource = false;
// 判斷是否勾選“未知來源”選項
if (sourceInfo != null
&& (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
}
if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
// 如果targetSdkVerison小于0中止安裝
if (targetSdkVersion < 0) {
Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
// Invalid originating uid supplied. Abort install.
mAbortInstall = true;
// 如果targetSdkVersion大于等于26(8.0), 且獲取不到REQUEST_INSTALL_PACKAGES權(quán)限中止安裝
} else if (targetSdkVersion >= Build.VERSION_CODES.O && !declaresAppOpPermission(
originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ Manifest.permission.REQUEST_INSTALL_PACKAGES);
mAbortInstall = true;
}
}
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
return;
}
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
// The the installation source as the nextActivity thinks this activity is the source, hence
// set the originating UID and sourceInfo explicitly
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
// 如果設(shè)置了ACTION_CONFIRM_INSTALL涝缝,則調(diào)用PackageInstallerActivity
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
// 判斷Uri的Scheme協(xié)議是否是content
if (packageUri != null && packageUri.getScheme().equals(
ContentResolver.SCHEME_CONTENT)) {
// 這個路徑已經(jīng)被起用了,但是仍然可以工作
// [IMPORTANT] This path is deprecated, but should still work. Only necessary
// features should be added.
// Copy file to prevent it from being changed underneath this process
// 調(diào)用InstallStaging來拷貝file/content譬重,防止被修改
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
// 如果Uri中包含package拒逮,則調(diào)用PackageInstallerActivity
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
// Uri不合法
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT,
PackageManager.INSTALL_FAILED_INVALID_URI);
setResult(RESULT_FIRST_USER, result);
nextActivity = null;
}
}
if (nextActivity != null) {
startActivity(nextActivity);
}
finish();
}
從以上源碼我們可以看出:
1.如果我們使用另一種安裝方式,設(shè)置Intent的action為PackageInstaller.ACTION_CONFIRM_INSTALL
(也就是android.content.pm.action.CONFIRM_INSTALL
)臀规,則會直接啟動PackageInstallerActivity
2.如果Scheme是package
協(xié)議也是直接啟動PackageInstallerActivity
3.只有當Scheme是content
時才會跳轉(zhuǎn)到InstallStaging(即使跳轉(zhuǎn)到InstallStaging滩援,最后還是會跳轉(zhuǎn)到PackageInstallerActivity,所以第3步我們會分析跳轉(zhuǎn)到InstallStaging的代碼)
-
第3步 啟動InstallStaging塔嬉,進入其onResume()生命周期
看其onResume()方法(核心實現(xiàn))
@Override
protected void onResume() {
super.onResume();
// This is the first onResume in a single life of the activity
if (mStagingTask == null) {
// File does not exist, or became invalid
if (mStagedFile == null) {
// Create file delayed to be able to show error
try {
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
}
在這里通過TemporaryFileManager.getStagedFile(this)
方法構(gòu)建了一個臨時文件:
/**
* Create a new file to hold a staged file.
*
* @param context The context of the caller
*
* @return A new file
*/
@NonNull
public static File getStagedFile(@NonNull Context context) throws IOException {
return File.createTempFile("package", ".apk", context.getNoBackupFilesDir());
}
這個文件建立在當前應(yīng)用私有目錄下的no_backup文件夾上玩徊。也就是/data/no_backup/packagexxx.apk 這個臨時文件。
這有一個StagingAsyncTask類:
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected Boolean doInBackground(Uri... params) {
if (params == null || params.length <= 0) {
return false;
}
Uri packageUri = params[0];
try (InputStream in = getContentResolver().openInputStream(packageUri)) {
// Despite the comments in ContentResolver#openInputStream the returned stream can
// be null.
if (in == null) {
return false;
}
try (OutputStream out = new FileOutputStream(mStagedFile)) {
byte[] buffer = new byte[1024 * 1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
// Be nice and respond to a cancellation
if (isCancelled()) {
return false;
}
out.write(buffer, 0, bytesRead);
}
}
} catch (IOException | SecurityException | IllegalStateException e) {
Log.w(LOG_TAG, "Error staging apk from content URI", e);
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
// Now start the installation again from a file
Intent installIntent = new Intent(getIntent());
installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
installIntent.setData(Uri.fromFile(mStagedFile));
if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
}
installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(installIntent);
InstallStaging.this.finish();
} else {
showError();
}
}
}
StagingAsyncTask主要工作:
1.doInBackground 是指在異步線程池中處理的事務(wù)谨究。doInBackground中實際上就是把uri中需要安裝的apk拷貝到臨時文件中(上文的/data/no_backup/packagexxx.apk)恩袱。
2.onPostExecute 當拷貝任務(wù)處理完之后,就會把當前的臨時文件Uri作為Intent的參數(shù)(這個時候會把Uri的類型設(shè)置為file
)胶哲,跳轉(zhuǎn)到DeleteStagedFileOnResult中畔塔。
-
第4步 啟動DeleteStagedFileOnResult
看其源碼:
/**
* Trampoline activity. Calls PackageInstallerActivity and deletes staged install file onResult.
*/
public class DeleteStagedFileOnResult extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Intent installIntent = new Intent(getIntent());
installIntent.setClass(this, PackageInstallerActivity.class);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(installIntent, 0);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
File sourceFile = new File(getIntent().getData().getPath());
sourceFile.delete();
setResult(resultCode, data);
finish();
}
}
這個Activity就非常簡單了,只是作為一個過渡鸯屿,啟動真正大頭戲份PackageInstallerActivity澈吨。如果PackageInstallerActivity安裝失敗了,就會退出PackageInstallerActivity界面返回到DeleteStagedFileOnResult的onActivityResult
中刪除這個臨時文件寄摆。
-
第5步 啟動PackageInstallerActivity
這個類是真正的安裝界面谅辣,我們先來看其onCreate()方法:
@Override
protected void onCreate(Bundle icicle) {
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
super.onCreate(null);
if (icicle != null) {
mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
}
mPm = getPackageManager();
mIpm = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
final Intent intent = getIntent();
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
PackageInstaller.SessionParams.UID_UNKNOWN);
mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
? getPackageNameForUid(mOriginatingUid) : null;
final Uri packageUri;
if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
}
mSessionId = sessionId;
packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessionId = -1;
packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
// if there's nothing to do, quietly slip into the ether
if (packageUri == null) {
Log.w(TAG, "Unspecified source");
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
finish();
return;
}
if (DeviceUtils.isWear(this)) {
showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
return;
}
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
// load dummy layout with OK button disabled until we override this layout in
// startInstallConfirm
bindUi();
checkIfAllowedAndInitiateInstall();
}
從上面的代碼我們可以看出:
1.如果使用PackageInstaller.ACTION_CONFIRM_INSTALL(也就是第1步里InstallStart接收到的action一直傳下來的)模式進行安裝,那么就會獲取保存在Intent中的ApplicationInfo對象冰肴,獲取其中的resolvedBaseCodePath也就是代碼文件路徑屈藐。這種處理文件的方式會通過Uri.fromFile(new File(info.resolvedBaseCodePath))
將Uri的Scheme設(shè)置為file
類型(這個file類型會在下個Activity里用到)。
2.如果是使用android.intent.action.INSTALL_PACKAGE
熙尉,也就是帶有Scheme的情況联逻,則通過Intent的getData獲取到保存在其中的臨時文件(就是上一步中說的/data/no_backup/packagexxx.apk)。通過這種方式獲取到的PackageUri的Scheme是package
類型检痰。
3.上面2步主要是為了獲取代碼文件包归,形成PackageUri,然后通過方法processPackageUri()
掃描路徑對應(yīng)的文件包铅歼,實例化PackageInfo對象mPkgInfo(安裝的必要屬性)公壤。
4.bindUi()
初始化安裝UI
5.checkIfAllowedAndInitiateInstall()
啟動安裝
下面是processPackageUri()
方法源碼:
/**
* Parse the Uri and set up the installer for this package.
*
* @param packageUri The URI to parse
*
* @return {@code true} iff the installer could be set up
*/
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
switch (scheme) {
case SCHEME_PACKAGE: {
try {
mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS
| PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
if (mPkgInfo == null) {
Log.w(TAG, "Requested package " + packageUri.getScheme()
+ " not available. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} break;
case ContentResolver.SCHEME_FILE: {
File sourceFile = new File(packageUri.getPath());
PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
// Check for parse errors
if (parsed == null) {
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
default: {
throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
}
}
return true;
}
從前面幾個Activity的處理邏輯中我們可以發(fā)現(xiàn)换可,直接通過ACTION_CONFIRM_INSTALL跳轉(zhuǎn)到PackageInstallerActivity的Uri的Scheme會被轉(zhuǎn)換成file
類型;原來Scheme類型為content
的在InstallStaging的StagingAsyncTask處理完畢后其Uri的Scheme也會轉(zhuǎn)成file
厦幅;只有原來Scheme類型為package
的是從InstallStart直接跳轉(zhuǎn)到PackageInstallerActivity的沾鳄,其Scheme才是package
,所以最終的Scheme只剩下package
和file
确憨,而processPackageUri()
方法也只會解析這兩種uri:
1.package
協(xié)議開頭的uri:這種uri會通過PackageManager的getPackageInfo()
通過getSchemeSpecificPart獲取對應(yīng)apk文件對應(yīng)的PackageInfo信息
2.file
協(xié)議開頭的uri译荞,則使用PackageParser的generatePackageInfo()
方法獲取apk文件中的package信息。最終實現(xiàn)mPkgInfo的屬性初始化休弃。
3.這個方法里會引入PackageManager和PackageParser這2個類吞歼,后續(xù)會講訴這倆類和PMS之間的關(guān)系,暫不多表塔猾。
我們看一下PackageParser的generatePackageInfo()
方法:
@UnsupportedAppUsage
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
Set<String> grantedPermissions, PackageUserState state, int userId) {
if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) {
return null;
}
PackageInfo pi = new PackageInfo();
pi.packageName = p.packageName;
pi.splitNames = p.splitNames;
pi.versionCode = p.mVersionCode;
pi.versionCodeMajor = p.mVersionCodeMajor;
pi.baseRevisionCode = p.baseRevisionCode;
pi.splitRevisionCodes = p.splitRevisionCodes;
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
pi.installLocation = p.installLocation;
pi.isStub = p.isStub;
pi.coreApp = p.coreApp;
if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0
|| (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
pi.requiredForAllUsers = p.mRequiredForAllUsers;
}
pi.restrictedAccountType = p.mRestrictedAccountType;
pi.requiredAccountType = p.mRequiredAccountType;
pi.overlayTarget = p.mOverlayTarget;
pi.targetOverlayableName = p.mOverlayTargetName;
pi.overlayCategory = p.mOverlayCategory;
pi.overlayPriority = p.mOverlayPriority;
pi.mOverlayIsStatic = p.mOverlayIsStatic;
pi.compileSdkVersion = p.mCompileSdkVersion;
pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
if ((flags&PackageManager.GET_GIDS) != 0) {
pi.gids = gids;
}
if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) {
int N = p.configPreferences != null ? p.configPreferences.size() : 0;
if (N > 0) {
pi.configPreferences = new ConfigurationInfo[N];
p.configPreferences.toArray(pi.configPreferences);
}
N = p.reqFeatures != null ? p.reqFeatures.size() : 0;
if (N > 0) {
pi.reqFeatures = new FeatureInfo[N];
p.reqFeatures.toArray(pi.reqFeatures);
}
N = p.featureGroups != null ? p.featureGroups.size() : 0;
if (N > 0) {
pi.featureGroups = new FeatureGroupInfo[N];
p.featureGroups.toArray(pi.featureGroups);
}
}
if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
final int N = p.activities.size();
if (N > 0) {
int num = 0;
final ActivityInfo[] res = new ActivityInfo[N];
for (int i = 0; i < N; i++) {
final Activity a = p.activities.get(i);
if (state.isMatch(a.info, flags)) {
if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) {
continue;
}
res[num++] = generateActivityInfo(a, flags, state, userId);
}
}
pi.activities = ArrayUtils.trimToSize(res, num);
}
}
if ((flags & PackageManager.GET_RECEIVERS) != 0) {
final int N = p.receivers.size();
if (N > 0) {
int num = 0;
final ActivityInfo[] res = new ActivityInfo[N];
for (int i = 0; i < N; i++) {
final Activity a = p.receivers.get(i);
if (state.isMatch(a.info, flags)) {
res[num++] = generateActivityInfo(a, flags, state, userId);
}
}
pi.receivers = ArrayUtils.trimToSize(res, num);
}
}
if ((flags & PackageManager.GET_SERVICES) != 0) {
final int N = p.services.size();
if (N > 0) {
int num = 0;
final ServiceInfo[] res = new ServiceInfo[N];
for (int i = 0; i < N; i++) {
final Service s = p.services.get(i);
if (state.isMatch(s.info, flags)) {
res[num++] = generateServiceInfo(s, flags, state, userId);
}
}
pi.services = ArrayUtils.trimToSize(res, num);
}
}
if ((flags & PackageManager.GET_PROVIDERS) != 0) {
final int N = p.providers.size();
if (N > 0) {
int num = 0;
final ProviderInfo[] res = new ProviderInfo[N];
for (int i = 0; i < N; i++) {
final Provider pr = p.providers.get(i);
if (state.isMatch(pr.info, flags)) {
res[num++] = generateProviderInfo(pr, flags, state, userId);
}
}
pi.providers = ArrayUtils.trimToSize(res, num);
}
}
if ((flags&PackageManager.GET_INSTRUMENTATION) != 0) {
int N = p.instrumentation.size();
if (N > 0) {
pi.instrumentation = new InstrumentationInfo[N];
for (int i=0; i<N; i++) {
pi.instrumentation[i] = generateInstrumentationInfo(
p.instrumentation.get(i), flags);
}
}
}
if ((flags&PackageManager.GET_PERMISSIONS) != 0) {
int N = p.permissions.size();
if (N > 0) {
pi.permissions = new PermissionInfo[N];
for (int i=0; i<N; i++) {
pi.permissions[i] = generatePermissionInfo(p.permissions.get(i), flags);
}
}
N = p.requestedPermissions.size();
if (N > 0) {
pi.requestedPermissions = new String[N];
pi.requestedPermissionsFlags = new int[N];
for (int i=0; i<N; i++) {
final String perm = p.requestedPermissions.get(i);
pi.requestedPermissions[i] = perm;
// The notion of required permissions is deprecated but for compatibility.
pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED;
if (grantedPermissions != null && grantedPermissions.contains(perm)) {
pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_GRANTED;
}
}
}
}
// deprecated method of getting signing certificates
if ((flags&PackageManager.GET_SIGNATURES) != 0) {
if (p.mSigningDetails.hasPastSigningCertificates()) {
// Package has included signing certificate rotation information. Return the oldest
// cert so that programmatic checks keep working even if unaware of key rotation.
pi.signatures = new Signature[1];
pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0];
} else if (p.mSigningDetails.hasSignatures()) {
// otherwise keep old behavior
int numberOfSigs = p.mSigningDetails.signatures.length;
pi.signatures = new Signature[numberOfSigs];
System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs);
}
}
// replacement for GET_SIGNATURES
if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
if (p.mSigningDetails != SigningDetails.UNKNOWN) {
// only return a valid SigningInfo if there is signing information to report
pi.signingInfo = new SigningInfo(p.mSigningDetails);
} else {
pi.signingInfo = null;
}
}
return pi;
}
1.這個方法看上去又臭又長篙骡,不做詳細分析,其核心只是解析了四大組件的內(nèi)容以及權(quán)限相關(guān)的標簽丈甸,拿到了apk包所有運行需要的基礎(chǔ)信息糯俗。
2.這個方法我們要留意2個類:PackageInfo和PackageParser.Package,后續(xù)我們會做分析老虫。
bindUi()
方法源碼:
private void bindUi() {
mAlert.setIcon(mAppSnippet.icon);
mAlert.setTitle(mAppSnippet.label);
mAlert.setView(R.layout.install_content_view);
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
(ignored, ignored2) -> {
if (mOk.isEnabled()) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
startInstall();
}
}
}, null);
mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
(ignored, ignored2) -> {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
finish();
}, null);
setupAlert();
mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
mOk.setEnabled(false);
}
該方法是對安裝界面UI的初始化叶骨,默認會把“安裝”按鈕(mOK
)隱藏掉,點擊BUTTON_POSITIVE會執(zhí)行startInstall()
祈匙,待我們分析完onCreate()里所有方法后再回過頭來看一下這個方法。
我們再來看onCreate()里的最后一個方法:checkIfAllowedAndInitiateInstall()
/**
* Check if it is allowed to install the package and initiate install if allowed. If not allowed
* show the appropriate dialog.
*/
private void checkIfAllowedAndInitiateInstall() {
// Check for install apps user restriction first.
final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
//允許安裝未知來源的文件判斷
if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
return;
} else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
finish();
return;
}
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
//核心代碼
initiateInstall();
} else {
// Check for unknown sources restrictions.
final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
& (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
if (systemRestriction != 0) {
showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
} else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
} else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startAdminSupportDetailsActivity(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
} else {
handleUnknownSources();
}
}
}
繼續(xù)看其核心方法:initiateInstall()
private void initiateInstall() {
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
if (oldName != null && oldName.length > 0 && oldName[0] != null) {
pkgName = oldName[0];
mPkgInfo.packageName = pkgName;
mPkgInfo.applicationInfo.packageName = pkgName;
}
// Check if package is already installed. display confirmation dialog if replacing pkg
try {
// This is a little convoluted because we want to get all uninstalled
// apps, but this may include apps with just data, and if it is just
// data we still want to count it as "installed".
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}
startInstallConfirm();
}
通過PackageManager的getApplicationInfo方法天揖,獲取當前Android系統(tǒng)中是否已經(jīng)安裝了當前的app夺欲,如果能找到mAppInfo對象,說明是安裝了今膊,調(diào)用startInstallConfirm刷新按鈕的顯示的是更新些阅,否則就是沒有安裝按鈕展示的是安裝。需要注意的是這里又用到了PackageManager這個類斑唬,大家請多留意市埋。
繼續(xù)看方法startInstallConfirm()
:
private void startInstallConfirm() {
View viewToEnable;
if (mAppInfo != null) {
viewToEnable = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? requireViewById(R.id.install_confirm_question_update_system)
: requireViewById(R.id.install_confirm_question_update);
} else {
// This is a new application with no permissions.
viewToEnable = requireViewById(R.id.install_confirm_question);
}
viewToEnable.setVisibility(View.VISIBLE);
mEnableOk = true;
mOk.setEnabled(true);
}
該方法很簡單,其實就是把上文中bindUi()
方法里隱藏的mOK
按鈕展示出來恕刘,用來點擊進行更新/安裝缤谎,所以我們可以回頭看一下bindUi()
的"mOK"按鈕點擊執(zhí)行的方法startInstall()
private void startInstall() {
// Start subactivity to actually install the application
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallInstalling.class);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
}
if (mReferrerURI != null) {
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
}
if (installerPackageName != null) {
newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
installerPackageName);
}
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
}
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
startActivity(newIntent);
finish();
}
這個方法的核心就是把mPackageURI
這個字段放入Intent,然后啟動InstallInstalling這個Activity褐着。
后續(xù)流程請看Android APK安裝流程(2)