Apk安裝的源碼分析(一)

  • 源頭

    我們要研究一個(gè)應(yīng)用的安裝過程,首先要找到這個(gè)過程的起點(diǎn)是什么。我們回憶一下應(yīng)用安裝的場(chǎng)景:

    1. 應(yīng)用市場(chǎng)直接下載安裝的,這個(gè)場(chǎng)景下我們看不到apk后綴的文件,應(yīng)用市場(chǎng)是個(gè)軟件,他只提供了一個(gè)安裝的按鈕,或者設(shè)置成下載完直接安裝,整個(gè)過程對(duì)使用者來說是透明的;
    2. 瀏覽器下載apk文件,對(duì)于一些沒有發(fā)布在當(dāng)前廠商應(yīng)用市場(chǎng)的應(yīng)用,我們就需要在官網(wǎng)或者搜索引擎中找,這個(gè)場(chǎng)景下載下來的apk我們可以在“下載”菜單中看到,需要安裝的時(shí)候可以直接點(diǎn)擊一下即可;

    通常會(huì)彈出一個(gè)應(yīng)用安裝的界面(安裝確認(rèn)頁)來進(jìn)行apk安裝(廠商應(yīng)用市場(chǎng)下載的可能直接就執(zhí)行安裝了不會(huì)彈出安裝確認(rèn)頁,因?yàn)榘l(fā)布在上面的都是已經(jīng)審核過的安全應(yīng)用),這個(gè)彈出的動(dòng)作很明顯是提供點(diǎn)擊apk文件動(dòng)作的這個(gè)軟件觸發(fā)的。

    安裝確認(rèn)頁上面有查看權(quán)限、安裝按鈕、應(yīng)用信息展示等功能,手機(jī)中應(yīng)用的所有手動(dòng)apk安裝,都會(huì)顯示相同的(可能部分信息有差異)安裝確認(rèn)頁,如果是某個(gè)應(yīng)用內(nèi)置的安裝功能的話它們不可能那么巧合地做成一樣的界面,很明顯它應(yīng)該是系統(tǒng)提供的安裝界面。

    Android中的界面無非是Activity,那么安裝確認(rèn)頁肯定依附于某個(gè)內(nèi)嵌的應(yīng)用,作為系統(tǒng)應(yīng)用,一般放在AOSP的/packages/apps/下,在其中我們找到了一個(gè)叫做PackageInstaller 的應(yīng)用,從名字上來看它大概就是我們要研究的主角。

    接下來我們就順著PackageInstaller的源碼來一探究竟。

  • PackageInstaller的AndroidManifest配置

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
             package="com.android.packageinstaller" coreApp="true">
       <original-package android:name="com.android.packageinstaller" />
       ...很多uses-permission...
        <application android:name=".PackageInstallerApplication"
                     ...>
              <!--系統(tǒng)重啟時(shí)刪除所有的安裝和卸載記錄-->
            <receiver android:name=".TemporaryFileManager"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.BOOT_COMPLETED" />
                </intent-filter>
            </receiver>
            <!--PackageInstallerApplication的邏輯入口,負(fù)責(zé)解析安裝請(qǐng)求的intent并決定之后的走向-->
            <activity android:name=".InstallStart"
                    android:exported="true"
                    android:excludeFromRecents="true">
                <intent-filter android:priority="1">
                    <action android:name="android.intent.action.VIEW" />
                    <action android:name="android.intent.action.INSTALL_PACKAGE" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <data android:scheme="file" />
                    <data android:scheme="content" />
                    <!--標(biāo)志文件類型是apk-->
                    <data android:mimeType="application/vnd.android.package-archive" />
                </intent-filter>
                <intent-filter android:priority="1">
                    <action android:name="android.intent.action.INSTALL_PACKAGE" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <data android:scheme="file" />
                    <data android:scheme="package" />
                    <data android:scheme="content" />
                </intent-filter>
                <intent-filter android:priority="1">
                    <action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </activity>
            <!-- 使用file或content的Uri安裝時(shí)用于把位于源應(yīng)用私有存儲(chǔ)位置的apk文件復(fù)制到公有目錄 -->
            <activity android:name=".InstallStaging"
                    android:exported="false" />
            <!-- 使用package的Uri安裝時(shí)用于最后刪除InstallStaging中創(chuàng)建的公有目錄的臨時(shí)文件 -->
            <activity android:name=".DeleteStagedFileOnResult"
                android:exported="false" />
            <!--安裝確認(rèn)頁-->
            <activity android:name=".PackageInstallerActivity"
                    android:exported="false" />
            <!-- 運(yùn)行安裝的頁(安裝進(jìn)行中,有進(jìn)度條)-->
            <activity android:name=".InstallInstalling"
                    android:theme="@style/DialogWhenLargeNoAnimation"
                    android:exported="false" />
            <!-- 安裝完成時(shí)IPackageInstaller會(huì)發(fā)送一條通知,InstallEventReceiver會(huì)捕獲然后觸發(fā)observer回調(diào),回調(diào)中決定是執(zhí)行安裝成功邏輯還是失敗邏輯 -->
            <receiver android:name=".InstallEventReceiver"
                    android:permission="android.permission.INSTALL_PACKAGES"
                    android:exported="true">
               <intent-filter android:priority="1">
                 <action android:name="com.android.packageinstaller.ACTION_INSTALL_COMMIT" />
               </intent-filter>
              </receiver>
                  <!--安裝成功頁-->
              <activity android:name=".InstallSuccess"
                   android:theme="@style/DialogWhenLargeNoAnimation"
                   android:exported="false" />
                  <!--安裝失敗頁-->
              <activity android:name=".InstallFailed"
                   android:theme="@style/DialogWhenLargeNoAnimation"
                   android:exported="false" />
                  <!--卸載確認(rèn)頁-->
            <activity android:name=".UninstallerActivity"
                    android:configChanges="orientation|keyboardHidden|screenSize"
                    android:excludeFromRecents="true"
                    android:theme="@style/AlertDialogActivity">
                <intent-filter android:priority="1">
                    <action android:name="android.intent.action.DELETE" />
                    <action android:name="android.intent.action.UNINSTALL_PACKAGE" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <data android:scheme="package" />
                </intent-filter>
            </activity>
            <!-- 卸載完成時(shí)會(huì)收到通知決定后面的卸載成功還是失敗邏輯 -->
            <receiver android:name=".UninstallEventReceiver"
                android:permission="android.permission.INSTALL_PACKAGES"
                android:exported="true">
                <intent-filter android:priority="1">
                    <action android:name="com.android.packageinstaller.ACTION_UNINSTALL_COMMIT" />
                </intent-filter>
            </receiver>
            <!-- 執(zhí)行卸載,進(jìn)行中的頁面 -->
            <activity android:name=".UninstallUninstalling"
                android:excludeFromRecents="true"
                android:theme="@style/AlertDialogActivity"
                android:exported="false" />
            <!-- 卸載成功頁面-->
            <receiver android:name=".UninstallFinish"
                    android:exported="false" />
            <!--權(quán)限授權(quán)頁-->
            <activity android:name=".permission.ui.GrantPermissionsActivity"
                    android:configChanges="orientation|keyboardHidden|screenSize"
                    android:excludeFromRecents="true"
                    android:theme="@style/GrantPermissions"
                    android:visibleToInstantApps="true">
                <intent-filter android:priority="1">
                    <action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </activity>
            <!--權(quán)限管理頁-->
            <activity android:name=".permission.ui.ManagePermissionsActivity"
                      android:configChanges="orientation|keyboardHidden|screenSize"
                      android:excludeFromRecents="true"
                      android:label="@string/app_permissions"
                      android:theme="@style/Settings"
                      android:permission="android.permission.GRANT_RUNTIME_PERMISSIONS">
                <intent-filter android:priority="1">
                    <action android:name="android.intent.action.MANAGE_PERMISSIONS" />
                    <action android:name="android.intent.action.MANAGE_APP_PERMISSIONS" />
                    <action android:name="android.intent.action.MANAGE_PERMISSION_APPS" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </activity>
            <!-- Wearable Components 可穿戴設(shè)備的應(yīng)用(手表等)安裝相關(guān) -->
            ...
        </application>
    
    </manifest>
    

    TemporaryFileManager會(huì)在系統(tǒng)啟動(dòng)時(shí)觸發(fā):

    @Override
    public void onReceive(Context context, Intent intent) {
        long systemBootTime = System.currentTimeMillis() - SystemClock.elapsedRealtime();
        File[] filesOnBoot = context.getNoBackupFilesDir().listFiles();
        if (filesOnBoot == null) {
            return;
        }
        for (int i = 0; i < filesOnBoot.length; i++) {
            File fileOnBoot = filesOnBoot[i];
              //重啟后刪除之前所有的記錄
            if (systemBootTime > fileOnBoot.lastModified()) {
                boolean wasDeleted = fileOnBoot.delete();
                if (!wasDeleted) {
                    Log.w(LOG_TAG, "Could not delete " + fileOnBoot.getName() + " onBoot");
                }
            } else {
                Log.w(LOG_TAG, fileOnBoot.getName() + " was created before onBoot broadcast was "
                        + "received");
            }
        }
    }
    

    getNoBackupFilesDir().listFiles方法會(huì)得到所有的安裝和卸載記錄文件,可見它的作用是系統(tǒng)重啟后刪除之前所有的安裝記錄,為什么這么說——后面會(huì)看到getNoBackupFilesDir中的文件是怎么來的。

    在可以安裝apk的應(yīng)用中,當(dāng)我們點(diǎn)擊“安裝”時(shí),背后其實(shí)就是調(diào)用了startActivity,它的intent的配置就是指向了InstallStart 這個(gè)activity,我們來看一下它的邏輯:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mIPackageManager = AppGlobals.getPackageManager();
        Intent intent = getIntent();
        String callingPackage = getCallingPackage();
    
        //這塊邏輯是判斷請(qǐng)求安裝的應(yīng)用是否是可信的,比如廠商的應(yīng)用市場(chǎng)就是可信的
        int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -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;
          //ApplicationInfo的privateFlags是由系統(tǒng)開發(fā)者(廠商)設(shè)置的,值為PRIVATE_FLAG_PRIVILEGED才會(huì)獲取Intent.EXTRA_NOT_UNKNOWN_SOURCE來判斷,所以如果你只是自定義intent這個(gè)屬性是沒用的
        if (sourceInfo != null
              && (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
            isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
        }
          //如果發(fā)送安裝請(qǐng)求的來源應(yīng)用不是系統(tǒng)可信的(不是一家人),則需要其他驗(yàn)證
        if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)         {
            final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
              //如果包信息中沒有targetSdkVersion則攔截
            if (targetSdkVersion < 0) {
                ...
                // Invalid originating uid supplied. Abort install.
                mAbortInstall = true;
            //targetSdkVersion版本不低于Build.VERSION_CODES.O(也就是8.0)則需要Manifest.permission.REQUEST_INSTALL_PACKAGES授權(quán)
            } else if (targetSdkVersion >= Build.VERSION_CODES.O 
                       //declaresAppOpPermission方法會(huì)查找所有的授權(quán)了Manifest.permission.REQUEST_INSTALL_PACKAGES的應(yīng)用信息,如果包含當(dāng)前這個(gè)來源應(yīng)用的話就不攔截
                       && !declaresAppOpPermission(
                    originatingUid, 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);
        nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
        nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
        nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
          //如果需要展示安裝確認(rèn)頁的話
        if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
            nextActivity.setClass(this, PackageInstallerActivity.class);
        } else {
              //其他的則按照Uri解析
                Uri packageUri = intent.getData();
              //設(shè)置了scheme為file或content的話則打開InstallStaging頁
                if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
                        || packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
                nextActivity.setClass(this, InstallStaging.class);
            //設(shè)置了scheme為package的話則打開PackageInstallerActivity頁
            } else if (packageUri != null && packageUri.getScheme().equals(
                    PackageInstallerActivity.SCHEME_PACKAGE)) {
                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();
    }
    

    可見,InstallStart的onCreate方法中首先會(huì)進(jìn)行一些安全性的驗(yàn)證,它是針對(duì)發(fā)送安裝請(qǐng)求的應(yīng)用的檢驗(yàn),比如應(yīng)用市場(chǎng)的安裝請(qǐng)求,沒問題的話會(huì)打開InstallStaging或者PackageInstallerActivity。

    PackageInstallerActivity是安裝確認(rèn)頁,InstallStaging是實(shí)際安裝頁嗎?這得需要我們分析完它倆的源碼才能確定。

  • InstallStaging

    InstallStaging的界面不重要,一個(gè)process view表示復(fù)制進(jìn)度而已。

    @Override
    protected void onResume() {
        super.onResume();
        if (mStagingTask == null) {
            if (mStagedFile == null) {
                try {
                    mStagedFile = TemporaryFileManager.getStagedFile(this);
                } catch (IOException e) {
                    showError();
                    return;
                }
            }
            mStagingTask = new StagingAsyncTask();
            mStagingTask.execute(getIntent().getData());
        }
    }
    

    TemporaryFileManager的getStagedFile方法如下:

    @NonNull
    public static File getStagedFile(@NonNull Context context) throws IOException {
        return File.createTempFile("package", ".apk", context.getNoBackupFilesDir());
    }
    

    context.getNoBackupFilesDir()實(shí)際上就是new File(getDataDir(), "no_backup")。

    getIntent().getData()就是要安裝的apk文件的URI,StagingAsyncTask的doInBackground方法中會(huì)把源apk文件復(fù)制到context.getDataDir()/no_backup/package.apk文件:

    @Override
    protected Boolean doInBackground(Uri... params) {
        if (params == null || params.length <= 0) {
            return false;
        }
          //源apk文件的Uri
        Uri packageUri = params[0];
          //通過ContentResolver來獲取apk文件
        try (InputStream in = getContentResolver().openInputStream(packageUri)) {
            if (in == null) {
                return false;
            }
              //復(fù)制到當(dāng)前PackageInstaller應(yīng)用的私有目錄下
            try (OutputStream out = new FileOutputStream(mStagedFile)) {
                byte[] buffer = new byte[1024 * 1024];
                int bytesRead;
                while ((bytesRead = in.read(buffer)) >= 0) {
                    if (isCancelled()) {
                        return false;
                    }
                    out.write(buffer, 0, bytesRead);
                }
            }
        } catch (IOException | SecurityException | IllegalStateException e) {
            return false;
        }
        return true;
    }
    

    復(fù)制結(jié)束后會(huì)finish并打開DeleteStagedFileOnResult這個(gè)Activity:

    @Override
    protected void onPostExecute(Boolean success) {
        if (success) {
            Intent installIntent = new Intent(getIntent());
            installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
              //注意傳入DeleteStagedFileOnResult的data此時(shí)已經(jīng)變成了臨時(shí)文件的Uri
            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();
        }
    }
    

    DeleteStagedFileOnResult并沒設(shè)置任何界面,在其onCreate中直接打開了PackageInstallerActivity,那為何多此一舉?

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        File sourceFile = new File(getIntent().getData().getPath());
        sourceFile.delete();
        setResult(resultCode, data);
        finish();
    }
    

    我們看到在它的onActivityResult方法中會(huì)刪除之前創(chuàng)建的臨時(shí)apk文件并finish掉自己。后續(xù)代碼分析中我們會(huì)看到,PackageInstallerActivity中在取消安裝時(shí)是會(huì)調(diào)用setResult方法的,所以會(huì)返回這里刪除掉臨時(shí)文件,所以它的作用就是干這個(gè)的,很巧妙是不是,PackageInstallerActivity完全感知不到臨時(shí)文件的存在。

  • PackageInstallerActivity

    PackageInstallerActivity的界面上會(huì)顯示圖標(biāo)、appName、有一個(gè)自定義的ScrollView(可設(shè)置一個(gè)滾動(dòng)到底部時(shí)的回調(diào),在回調(diào)中改變“安裝”按鈕為可點(diǎn)擊狀態(tài),以此來盡量使用戶知悉應(yīng)用的所需權(quán)限)來盛放權(quán)限內(nèi)容、安裝和取消按鈕等。

    onCreate中會(huì)調(diào)用checkIfAllowedAndInitiateInstall方法來驗(yàn)證當(dāng)前用戶的權(quán)限:

    private void checkIfAllowedAndInitiateInstall() {
        final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
        ...//用戶是否允許安裝應(yīng)用的權(quán)限驗(yàn)證
        if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
            initiateInstall();
        } else {
              if (...) {
                  ...//用戶是否允許安裝未知來源應(yīng)用的權(quán)限驗(yàn)證
              } else {
                  handleUnknownSources();
              }
        }
    }
    

    handleUnknownSources中會(huì)檢查Manifest.permission.REQUEST_INSTALL_PACKAGES權(quán)限,如果通過則也會(huì)調(diào)用initiateInstall方法:

    private void initiateInstall() {
          //更換新的包名
        String pkgName = mPkgInfo.packageName;
        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;
        }
        try {
            //如果已安裝則此次是更新安裝
            mAppInfo = mPm.getApplicationInfo(pkgName,
                    PackageManager.MATCH_UNINSTALLED_PACKAGES);
            if ((mAppInfo.flags& ApplicationInfo.FLAG_INSTALLED) == 0) {
                mAppInfo = null;
            }
        } catch (NameNotFoundException e) {
            mAppInfo = null;
        }
    
        startInstallConfirm();
    }
    
    private void startInstallConfirm() {
        // We might need to show permissions, load layout with permissions
        if (mAppInfo != null) {
            bindUi(R.layout.install_confirm_perm_update, true);
        } else {
            bindUi(R.layout.install_confirm_perm, true);
        }
    
        ((TextView) findViewById(R.id.install_confirm_question))
                .setText(R.string.install_confirm_question);
        TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
        tabHost.setup();
        ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
        TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
          //// If the app supports runtime permissions the new permissions will be requested at runtime, hence we do not show them at install.
        //即如果不支持運(yùn)行時(shí)權(quán)限的話(6.0以下)就在此列出所有有關(guān)安全的權(quán)限,支持運(yùn)行時(shí)權(quán)限的版本中,安全權(quán)限會(huì)在運(yùn)行時(shí)詢問授權(quán)
        boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion
                >= Build.VERSION_CODES.M;
        ...//列出安全權(quán)限
        if (mScrollView == null) {
            // 不需要展示權(quán)限信息
            mOk.setText(R.string.install);
            mOkCanInstall = true;
        } else {
              //需要展示權(quán)限信息的話設(shè)置一個(gè)滾動(dòng)到底部的監(jiān)聽,為了讓用戶往下滑動(dòng)以至于盡量使其知悉這些關(guān)鍵權(quán)限
            mScrollView.setFullScrollAction(new Runnable() {
                @Override
                public void run() {
                    mOk.setText(R.string.install);
                    mOkCanInstall = true;
                }
            });
        }
    }
    

    mOkCanInstall表示安裝按鈕可以觸發(fā)安裝操作:

    public void onClick(View v) {
        if (v == mOk) {
            if (mOk.isEnabled()) {
                if (mOkCanInstall || mScrollView == null) {
                    if (mSessionId != -1) {
                        mInstaller.setPermissionsResult(mSessionId, true);
                        finish();
                    } else {
                        startInstall();
                    }
                } else {
                    mScrollView.pageScroll(View.FOCUS_DOWN);
                }
            }
        } else if (v == mCancel) {
            setResult(RESULT_CANCELED);
            if (mSessionId != -1) {
                mInstaller.setPermissionsResult(mSessionId, false);
            }
            finish();
        }
    }
    

    startInstall方法如下:

    private void startInstall() {
        Intent newIntent = new Intent();
        newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                mPkgInfo.applicationInfo);
        newIntent.setData(mPackageURI);
        newIntent.setClass(this, InstallInstalling.class);
        ...
        startActivity(newIntent);
        finish();
    }
    

    可見,這里又打開了一個(gè)名為InstallInstalling的Activity。

    PackageInstallerActivity檢驗(yàn)的是用戶相關(guān)的權(quán)限和合法性。

  • InstallInstalling

    其onCreate方法如下:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.install_installing);
        ApplicationInfo appInfo = getIntent()
                .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
        mPackageURI = getIntent().getData();
          //如果是系統(tǒng)應(yīng)用的話,那它們的應(yīng)用資源都和系統(tǒng)捆綁在一起的,所以直接安裝
        if ("package".equals(mPackageURI.getScheme())) {
            try {
                getPackageManager().installExistingPackage(appInfo.packageName);
                launchSuccess();
            } catch (PackageManager.NameNotFoundException e) {
                launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
            }
        } else {
              //第三方apk安裝
            final File sourceFile = new File(mPackageURI.getPath());
              ...
            if (savedInstanceState != null) {
                ...//復(fù)用
            } else {
                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                params.installFlags = PackageManager.INSTALL_FULL_APP;
                params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
                params.originatingUri = getIntent()
                        .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
                params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
                        UID_UNKNOWN);
                params.installerPackageName =
                        getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
    
                File file = new File(mPackageURI.getPath());
                try {
                      //解析出包信息賦值給params
                    PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0);
                    params.setAppPackageName(pkg.packageName);
                    params.setInstallLocation(pkg.installLocation);
                    params.setSize(
                            PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
                } catch (PackageParser.PackageParserException e) {
                    params.setSize(file.length());
                } catch (IOException e) {
                    params.setSize(file.length());
                }
                try {
                      //設(shè)置一個(gè)Observer,安裝完成后會(huì)觸發(fā)launchFinishBasedOnResult方法
                    mInstallId = InstallEventReceiver
                            .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
                                    this::launchFinishBasedOnResult);
                } catch (EventResultPersister.OutOfIdsException e) {
                    launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
                }
                try {
                      //建立和PackageInstaller的session
                    mSessionId = getPackageManager().getPackageInstaller().createSession(params);
                } catch (IOException e) {
                    launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
                }
            }
            mCancelButton = (Button) findViewById(R.id.cancel_button);
            mCancelButton.setOnClickListener(view -> {
                  //取消安裝
                if (mInstallingTask != null) {
                    mInstallingTask.cancel(true);
                }
                if (mSessionId > 0) {
                    getPackageManager().getPackageInstaller().abandonSession(mSessionId);
                    mSessionId = 0;
                }
                setResult(RESULT_CANCELED);
                finish();
            });
              //這個(gè)是更新界面安裝進(jìn)度的
            mSessionCallback = new InstallSessionCallback();
        }
    }
    

    其中PackageParser.parsePackageLite方法流程中會(huì)用到ApkAssets加載apk文件,它是直接使用了apk文件的路徑來加載的,這也就是為什么我們要使用InstallStaging多此一舉的原因,因?yàn)?code>根據(jù)Android系統(tǒng)的存儲(chǔ)權(quán)限要求,無法直接通過文件路徑訪問其他應(yīng)用的私有目錄下的文件,只能通過ContentProvider共享后用ContentResolver獲取。

    然后在onResume中會(huì)啟動(dòng)一個(gè)后臺(tái)任務(wù)來執(zhí)行安裝:

    @Override
    protected void onResume() {
        super.onResume();
        if (mInstallingTask == null) {
            PackageInstaller installer = getPackageManager().getPackageInstaller();
            PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
            if (sessionInfo != null && !sessionInfo.isActive()) {
                mInstallingTask = new InstallingAsyncTask();
                mInstallingTask.execute();
            } else {
                mCancelButton.setEnabled(false);
                setFinishOnTouchOutside(false);
            }
        }
    }
    

    InstallingAsyncTask中:

    private final class InstallingAsyncTask extends AsyncTask<Void, Void,
            PackageInstaller.Session> {
        volatile boolean isDone;
        @Override
        protected PackageInstaller.Session doInBackground(Void... params) {
            PackageInstaller.Session session;
            try {
                  //根據(jù)之前創(chuàng)建的mSessionId建立通道
                session = getPackageManager().getPackageInstaller().openSession(mSessionId);
            } catch (IOException e) {
                return null;
            }
              //初始進(jìn)度
            session.setStagingProgress(0);
            try {
                File file = new File(mPackageURI.getPath());
                try (InputStream in = new FileInputStream(file)) {
                    long sizeBytes = file.length();
                      //看來是寫入一個(gè)PackageInstaller相關(guān)的文件
                    try (OutputStream out = session
                            .openWrite("PackageInstaller", 0, sizeBytes)) {
                        byte[] buffer = new byte[1024 * 1024];
                        while (true) {
                            int numRead = in.read(buffer);
                            if (numRead == -1) {
                                session.fsync(out);
                                break;
                            }
                            if (isCancelled()) {
                                session.close();
                                break;
                            }
                            out.write(buffer, 0, numRead);
                            if (sizeBytes > 0) {
                                float fraction = ((float) numRead / (float) sizeBytes);
                                  //更新進(jìn)度
                                session.addProgress(fraction);
                            }
                        }
                    }
                }
                return session;
            } catch (IOException | SecurityException e) {
                session.close();
                return null;
            } finally {
                synchronized (this) {
                    isDone = true;
                    notifyAll();
                }
            }
        }
    
        @Override
        protected void onPostExecute(PackageInstaller.Session session) {
            if (session != null) {
                Intent broadcastIntent = new Intent(BROADCAST_ACTION);
                broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
                broadcastIntent.setPackage(
                        getPackageManager().getPermissionControllerPackageName());
                broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
    
                PendingIntent pendingIntent = PendingIntent.getBroadcast(
                        InstallInstalling.this,
                        mInstallId,
                        broadcastIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT);
                          //安裝完成
                session.commit(pendingIntent.getIntentSender());
                mCancelButton.setEnabled(false);
                setFinishOnTouchOutside(false);
            } else {
                getPackageManager().getPackageInstaller().abandonSession(mSessionId);
                if (!isCancelled()) {
                    launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
                }
            }
        }
    }
    

    session其實(shí)持有了一個(gè)PackageInstallerSession,是通過它處理的,是一個(gè)AIDL文件。session.commit后如果驗(yàn)證成功會(huì)發(fā)送一個(gè)通知,InstallEventReceiver會(huì)接收到,然后觸發(fā)Observer,執(zhí)行l(wèi)aunchFinishBasedOnResult方法:

    private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
        if (statusCode == PackageInstaller.STATUS_SUCCESS) {
                launchSuccess();
            } else {
                launchFailure(legacyStatus, statusMessage);
            }
    }
    private void launchSuccess() {
        Intent successIntent = new Intent(getIntent());
        successIntent.setClass(this, InstallSuccess.class);
        successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
        startActivity(successIntent);
        finish();
    }
    
    private void launchFailure(int legacyStatus, String statusMessage) {
        Intent failureIntent = new Intent(getIntent());
        failureIntent.setClass(this, InstallFailed.class);
        failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
        failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus);
        failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage);
        startActivity(failureIntent);
        finish();
    }
    

    到這里我們已經(jīng)梳理完了安裝一個(gè)apk的大概流程,只知道安裝好像是把一個(gè)apk文件寫入到了某個(gè)地方,至于是什么地方,那就需要解析完P(guān)ackageInstallerSession之后才能知道了,限于篇幅,另起一篇博文。

  • 總結(jié)

    1. 源應(yīng)用(觸發(fā)安裝請(qǐng)求的應(yīng)用)通過startActivity(intent)的方式發(fā)起安裝應(yīng)用的請(qǐng)求,intent中設(shè)置目標(biāo)信息,就是PackageInstaller應(yīng)用下AndroidManifest.xml中InstallStart 這個(gè)activity配置的intent-filter中的對(duì)應(yīng)內(nèi)容,然后和正常的Activity啟動(dòng)一樣,InstallStart就被打開了。

    2. InstallStart的onCreate方法中,首先會(huì)判斷發(fā)送安裝請(qǐng)求的應(yīng)用是否是系統(tǒng)信任的應(yīng)用,比如廠商自帶的應(yīng)用市場(chǎng)對(duì)于系統(tǒng)來說就是可信任的。如果不是系統(tǒng)自帶應(yīng)用則需要判斷targetSdkVersion是否有效,其次如果targetSdkVersion不小于26(8.0)的話,則還需要驗(yàn)證是否有Manifest.permission.REQUEST_INSTALL_PACKAGES 權(quán)限,沒達(dá)到這些要求的話就會(huì)被攔截,直接取消安裝finish,也就是說,這個(gè)類是驗(yàn)證發(fā)起安裝請(qǐng)求的應(yīng)用是否被允許安裝別的應(yīng)用

      如果通過了驗(yàn)證,則會(huì)構(gòu)造nextActivity,nextActivity的目標(biāo)有兩種選擇,一種是跳到PackageInstallerActivity,一種是跳到InstallStaging。

      intent設(shè)置的uri如果是以package開頭的,說明安裝的apk是系統(tǒng)應(yīng)用包,則直接打開安裝確認(rèn)頁。

      intent設(shè)置的uri如果是以file或content開頭的,要安裝的apk文件肯定不在PackageInstaller應(yīng)用的私有存儲(chǔ)目錄中,根據(jù)Android系統(tǒng)的存儲(chǔ)權(quán)限要求,無法直接通過文件路徑訪問其他應(yīng)用的私有目錄下的文件,只能通過ContentProvider共享后用ContentResolver獲取,而在后面的apk解析過程中ApkAssets會(huì)直接用到apk文件的路徑來加載的,所以為了避免權(quán)限問題,這里會(huì)先跳轉(zhuǎn)到InstallStaging,而InstallStaging中會(huì)通過ContentResolver獲取源apk文件,然后復(fù)制到PackageInstaller應(yīng)用的私有存儲(chǔ)目錄下的一個(gè)臨時(shí)目錄中(臨時(shí)文件位于當(dāng)前發(fā)起應(yīng)用內(nèi)的context.getDataDir()/no_backup目錄下,名叫“package.apk”),這樣在后續(xù)使用文件路徑訪問的時(shí)候就不會(huì)有存儲(chǔ)權(quán)限問題了。`然后復(fù)制完成后會(huì)打開一個(gè)叫DeleteStagedFileOnResult的Activity(它的作用是在安裝結(jié)束后自動(dòng)刪除臨時(shí)文件),在其onCreate方法中又打開了PackageInstallerActivity,所以,殊途同歸,最終都是打開PackageInstallerActivity。

      PackageInstallerActivity是安裝確認(rèn)頁,在這個(gè)頁你可以查看要安裝應(yīng)用的權(quán)限等信息,同時(shí)在這個(gè)頁會(huì)驗(yàn)證用戶的權(quán)限和該安裝包是否合法 ,如果驗(yàn)證通過,則“安裝”按鈕就變?yōu)榭牲c(diǎn)擊狀態(tài),點(diǎn)擊后打開名為InstallInstalling的Activity。

    3. InstallInstalling中會(huì)通過PackageInstaller的createSession方法把安裝包的信息傳過去,并得到一個(gè)mSessionId,然后創(chuàng)建一個(gè)AsyncTask,在其doInBackground方法中通過PackageInstaller的openSession(mSessionId)得到一個(gè)PackageInstaller.Session對(duì)象,它的相關(guān)方法都是調(diào)用了一個(gè)IPackageInstallerSession(AIDL)類型的IBinder開啟寫入,這也就是安裝的過程,最后在onPostExecute方法中調(diào)用session的commit方法,這個(gè)方法流程中會(huì)發(fā)送一個(gè)通知,InstallEventReceiver會(huì)接收,InstallEventReceiverInstallInstalling的onCreate中注冊(cè)了一個(gè)Observer,這時(shí)這個(gè)Observer就會(huì)被回調(diào),在其中會(huì)判斷結(jié)果狀態(tài),如果安裝成功就打開InstallSuccess頁,如果失敗就打開InstallFailed頁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容