Android APK安裝流程(1)

前言

  • 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安裝主體流程

apk_install_structure.png

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只剩下packagefile确憨,而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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坷澡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子含蓉,更是在濱河造成了極大的恐慌频敛,老刑警劉巖项郊,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異斟赚,居然都是意外死亡着降,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門拗军,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹊碍,“玉大人,你說我怎么就攤上這事食绿〕薰荆” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵器紧,是天一觀的道長耀销。 經(jīng)常有香客問我,道長铲汪,這世上最難降的妖魔是什么熊尉? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮掌腰,結(jié)果婚禮上狰住,老公的妹妹穿的比我還像新娘。我一直安慰自己齿梁,他們只是感情好催植,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著勺择,像睡著了一般创南。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上省核,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天稿辙,我揣著相機與錄音,去河邊找鬼气忠。 笑死邻储,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的旧噪。 我是一名探鬼主播吨娜,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼舌菜!你這毒婦竟也來了萌壳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袱瓮,沒想到半個月后缤骨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡尺借,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年绊起,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片燎斩。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡虱歪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出栅表,到底是詐尸還是另有隱情笋鄙,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布怪瓶,位于F島的核電站萧落,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏洗贰。R本人自食惡果不足惜找岖,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望敛滋。 院中可真熱鬧许布,春花似錦、人聲如沸绎晃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽箕昭。三九已至灵妨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間落竹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工货抄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留述召,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓蟹地,卻偏偏與公主長得像积暖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子怪与,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容