【Android 系統開發】_「核心服務」篇 -- PMS(4)- PackageInstaller

開篇

PackageManagerService 系列文章以下(基於 Android 9.0 源碼)

         🍁   Framework 核心服務之 PackageManagerService 鑽研(1)- 啓動流程                          
         🍁   Framework 核心服務之 PackageManagerService 鑽研(2)- 構造函數
         🍁   Framework 核心服務之 PackageManagerService 鑽研(3)- PackageManager
         🍁   Framework 核心服務之 PackageManagerService 鑽研(4)- PackageInstaller
         🍁   Framework 核心服務之 PackageManagerService 鑽研(5)- APK 安裝流程(PackageInstaller)
         🍁   Framework 核心服務之 PackageManagerService 鑽研(6)- APK 安裝流程(PMS)
         🍁   Framework 核心服務之 PackageManagerService 鑽研(7)- PackageParserhtml

核心源碼

關鍵類 路徑
PackageInstaller.java frameworks/base/core/java/android/content/pm/PackageInstaller.java
InstallStart.java packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
InstallStaging.java packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
PackageInstallerActivity.java packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

PackageManager 簡介

在詳細講解包管理機制如何安裝 APK 以前,咱們須要瞭解 PackageManagerjava

與 ActivityManagerService(AMS) 和 ActivityManager 的關係相似,PackageManagerService(PMS) 也有一個對應的管理類 PackageManager ,用於嚮應用程序進程提供一些功能。android

PackageManager 是一個抽象類,它的具體實現類爲 ApplicationPackageManager ,ApplicationPackageManager 中的方法會經過 IPackageManager 與 PMS 進行進程間通訊,所以 PackageManager 所提供的功能最終是由 PMS 來實現的,這麼設計的主要用意是爲了不繫統服務 PMS 直接被訪問。segmentfault

/**
 * Class for retrieving various kinds of information related to the application
 * packages that are currently installed on the device.
 *
 * You can find this class through {@link Context#getPackageManager}.
 */
// PackageManager 是一個抽象類
public abstract class PackageManager {}     

/** @hide */
// ApplicationPackageManager 繼承自 PackageManager
public class ApplicationPackageManager extends PackageManager {}

抽象類 PackageManager 提供了的功能,主要有如下幾點:微信

        ✨ 一、獲取一個應用程序的全部信息(ApplicationInfo)
        ✨ 二、獲取四大組件的信息
        ✨ 三、查詢 permission 相關信息
        ✨ 四、獲取包的信息
        ✨ 五、安裝、卸載 APKsession

APK 文件結構和安裝方式

APK 是 AndroidPackage 的縮寫,即 Android 安裝包,它其實是 zip 格式的壓縮文件,通常狀況下,解壓後的文件結構以下表所示。app

目錄/文件 描述
assert 存放的原生資源文件,經過 AssetManager 類訪問。
lib 存放庫文件。
META-INF 保存應用的簽名信息,簽名信息能夠驗證 APK 文件的完整性。
res 存放資源文件。res 中除了 raw 子目錄,其餘的子目錄都參與編譯,這些子目錄下的資源是經過編譯出的 R 類在代碼中訪問。
AndroidManifest.xml 用來聲明應用程序的包名稱、版本、組件和權限等數據。 apk 中的 AndroidManifest.xml 通過壓縮,能夠經過 AXMLPrinter2 工具解開。
classes.dex Java 源碼編譯後生成的 Java 字節碼文件。
resources.arsc 編譯後的二進制資源文件。

APK 的安裝場景

目前,咱們常見的安裝 APK 的場景主要分爲如下 四種ide

✨ 一、經過 adb 命令安裝:adb 命令包括 adb push/install。
✨ 二、用戶下載的 Apk,經過系統安裝器 packageinstaller(系統內置的應用程序,用於安裝和卸載應用程序)安裝該 Apk。
✨ 三、系統開機時安裝系統應用。
✨ 四、電腦或者手機上的應用商店自動安裝。函數

其實,這4種方式最終都是由 PMS 來進行處理,只是在此以前的調用鏈是不一樣的。咱們在接下來的分析中,會選擇第二種方式,由於對於開發者來講,這是調用鏈比較長的安裝方式(利於咱們分析源碼)。工具

咱們看下這種安裝方式的 實際操做圖 :(後面咱們分析的代碼流程會緊密聯繫着這幾張圖,你們能夠對比理解!)
微信截圖_20181129131739.png

APK 安裝相關目錄

目錄/文件 描述
/system/app 系統自帶的應用程序,得到 adb root 權限才能刪除。
/data/app 用戶程序安裝的目錄,安裝時把 apk 文件複製到此目錄。
/data/data 存放應用程序的數據。
/data/dalvik-cache 將 apk 中的 dex 文件安裝到 dalvik-cache 目錄下(dex 文件是 dalvik 虛擬機的可執行文件,固然,ART-Android Runtime 的可執行文件格式爲 .oat ,啓動 ART 時,系統會執行 dex 文件轉換至 oat 文件)。
/data/system 該目錄下的 packages.xml 文件相似於 Window 的註冊表,這個文件是解析 apk 時由 writeLP() 建立的,裏面記錄了系統的 permissons ,以及每一個 apk 的 name,codePath,flag,ts,version ,userid 等信息,這些信息主要經過 apk 的 AndroidManifest 解析獲取,解析完 apk 後將更新信息寫入這個文件並保存到 flash ,下次開機的時候直接從裏面讀取相關信息並添加到內存相關列表中。當有 apk 升級,安裝或刪除時會更新這個文件。
/data/system/package.xml 包含了該應用申請的權限、簽名和代碼所在的位置等信息系,而且二者都有同一個userld。之因此每一個應用都要一個userId,是由於Android在系統設計上把每一個應用當作Linux系統上的一個用戶對待,這樣就能夠利用已有的Linux用戶管理機制來設計Android應用,好比應用目錄,應用權限,應用進程管理等。
/data/system/package.list 指定了應用的默認存儲位置/data/data/com.xxx.xxx。

PackageInstaller 初始化

首先,咱們須要指出的是:從 Android 8.0 開始系統是經過以下代碼安裝指定路徑中的 APK:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" 
                      + apkfile.toString()), "application/vnd.android.package-archive");

Intent 的 Action 屬性爲 ACTION_VIEW,Type 屬性指定 Intent 的數據類型爲 application/vnd.android.package-archive。

"application/vnd.android.package-archive" 是什麼?

final String[][] MIME_MapTable={ 
            //{後綴名,MIME類型} 
            {".3gp",    "video/3gpp"}, 
            {".apk",    "application/vnd.android.package-archive"}, 
            {".asf",    "video/x-ms-asf"}, 
            {".avi",    "video/x-msvideo"}, 
            ... ...

咱們發現 "application/vnd.android.package-archive" 其實就是文件類型,具體對應 apk 類型。

那麼這個 Intent 隱式匹配的 Activity 是什麼?這邊咱們直接告訴你:InstallStart!(其實,7.0能隱式匹配的 Activity 爲 PackageInstallerActivity ,二者的區別咱們後面會講解到!)。

// packages/apps/PackageInstaller/AndroidManifest.xml
<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" />
        <data android:mimeType="application/vnd.android.package-archive" />
    </intent-filter>
    ... ...
</activity>

InstallStart

其實,InstallStart 是 PackageInstaller 中的入口 Activity。當咱們調用 PackageInstaller 來安裝應用時會跳轉到 InstallStart,並調用它的 onCreate 方法,咱們來看看:

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mIPackageManager = AppGlobals.getPackageManager();
        Intent intent = getIntent();
        String callingPackage = getCallingPackage();

        ... ...

        /**
         * public static final String 
         *          ACTION_CONFIRM_PERMISSIONS = "android.content.pm.action.CONFIRM_PERMISSIONS";
         * 判斷 Intent 的 Action 是否爲 CONFIRM_PERMISSIONS ;
         */
        if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
            nextActivity.setClass(this, PackageInstallerActivity.class);
        } else {
            // 咱們此時的情景會走這邊
            Uri packageUri = intent.getData();

            // 保證 packageUri 不爲 null,判斷 packageUri 的 Scheme 協議是不是 content
            if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
                    || packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
                // 跳轉到 InstallStaging (Android 8.0)
                nextActivity.setClass(this, InstallStaging.class);
            } else if (packageUri != null && packageUri.getScheme().equals(
                    PackageInstallerActivity.SCHEME_PACKAGE)) {
                // 跳轉到 PackageInstallerActivity(Android 7.0)
                nextActivity.setClass(this, PackageInstallerActivity.class);
            } else {
                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) {
            // 啓動相應的 Activity(InstallStaging、PackageInstallerActivity)
            startActivity(nextActivity);
        }
        finish();
    }

InstallStaging

咱們是基於 Android 8.0 的代碼進行的分析,因此會走到 InstallStaging 分支,咱們繼續看源碼:

@Override
    protected void onResume() {
        super.onResume();

        // This is the first onResume in a single life of the activity
        if (mStagingTask == null) {
            if (mStagedFile == null) {
                // Create file delayed to be able to show error
                try {
                    // 若是 File 類型的 mStagedFile 爲 null,
                    // 則建立 mStagedFile,mStagedFile 用於存儲臨時數據
                    mStagedFile = TemporaryFileManager.getStagedFile(this);
                } catch (IOException e) {
                    showError();
                    return;
                }
            }

            mStagingTask = new StagingAsyncTask();
            // 啓動 StagingAsyncTask 線程,並傳入了 content 協議的 Uri
            mStagingTask.execute(getIntent().getData());
        }
    }

咱們能夠看到, InstallStaging 主要作了兩部分工做

        ✨ 一、判斷 mStagingTask 是否爲空,主要用於存儲臨時數據;

        ✨ 二、建立並啓動 StagingAsyncTask 線程。

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)) {
                if (in == null) {
                    return false;
                }
                /*
                 * 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥
                 * 將 packageUri(content協議的Uri)的內容寫入到 mStagedFile 中
                 * 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥
                 */                 
                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 e) {
                Log.w(LOG_TAG, "Error staging apk from content URI", e);
                return false;
            }
            return true;
        }

        @Override
        protected void onPostExecute(Boolean success) {
            if (success) {
                Intent installIntent = new Intent(getIntent());

                /*
                 * 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥
                 * 若是寫入成功,跳轉到 PackageInstallerActivity
                 * 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥 💥
                 */                
                installIntent.setClass(InstallStaging.this, PackageInstallerActivity.class);
                
                // 並將 mStagedFile 傳進去
                installIntent.setData(Uri.fromFile(mStagedFile));
                installIntent
                        .setFlags(installIntent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
                installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
                startActivityForResult(installIntent, 0);
            } else {
                showError();
            }
        }
    }

【總結】:doInBackground 方法中將 packageUri(content 協議的 Uri)的內容寫入到 mStagedFile 中,若是寫入成功,onPostExecute 方法中會跳轉到 PackageInstallerActivity 中,並將 mStagedFile 傳進去。

繞了一圈又回到了 PackageInstallerActivity,這裏能夠看出 InstallStaging 主要起了轉換的做用,將 content 協議的 Uri 轉換爲 File 協議,而後跳轉到 PackageInstallerActivity,接下來的安裝流程就和 Android 7.0 同樣了。

PackageInstallerActivity

接下來,咱們就要重點分析 PackageInstallerActivity !從功能上來講, PackageInstallerActivity 纔是應用安裝器 PackageInstaller 真正的入口 Activity

咱們看下官方對於這個類的說明:

/**
 * This activity is launched when a new application is installed via side loading
 * The package is first parsed and the user is notified of parse errors via a dialog.
 * If the package is successfully parsed, the user is notified to turn on the install unknown
 * applications setting. A memory check is made at this point and the user is notified of out
 * of memory conditions if any. If the package is already existing on the device,
 * a confirmation dialog (to replace the existing package) is presented to the user.
 * Based on the user response the package is then installed by launching InstallAppConfirm
 * sub activity. All state transitions are handled in this activity
 */
 
/**
 * 當經過渠道安裝一個應用程序的時候,會啓動這個 Activity。
 * 若是在首次解析這個安裝包的時候出現解析錯誤,會經過對話框的形式告訴用戶。
 * 若是首次解析安裝包的時候,成功解析了,則會通知用戶去打開"安裝未知應用程序設置"。
 * 在啓動 Activity 的時候會進行內存檢查,若是內存不足會通知用戶。
 * 若是這個應用程序已經在這個設備安裝過了,則會向用戶彈出一個對話框詢問用戶是否"替換現有應用程序的安裝包"。
 * 基於用戶的迴應,而後經過 InstallAppConfirm 的子類來安裝應用程序。
 * 全部狀態的轉換都是在這 Activity 中處理。
 */

public class PackageInstallerActivity extends OverlayTouchActivity implements OnClickListener {
    ... ...
}

瞭解完了官方說明,接下來咱們查看它的 onCreate 方法:

onCreate

public class PackageInstallerActivity extends OverlayTouchActivity implements OnClickListener {
    ... ...

    PackageManager mPm;
    IPackageManager mIpm;
    AppOpsManager mAppOpsManager;
    PackageInstaller mInstaller;
    UserManager mUserManager;
    
    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        if (icicle != null) {
            mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
        }

        /**
         * 初始話 PackageManager 對象:具體用來執行安裝操做,最終的功能是由 PMS 來實現的;
         */
        mPm = getPackageManager();
        
        /**
         * 初始話 IPackageManager 對象:一個AIDL的接口,用於和 PMS 進行進程間通訊;
         */
        mIpm = AppGlobals.getPackageManager();
        
        /**
         * 初始化 AppOpsManager 對象:用於權限動態檢測,在,Android 4.3 中被引入;
         */
        mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
        
        /**
         * 初始化 PackageInstaller 對象:在該對象中包含了安裝 APK 的基本信息;
         */
        mInstaller = mPm.getPackageInstaller();
        
        /**
         * 初始化 UserManager 對象:用於多用戶管理;
         */
        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
        
        final Intent intent = getIntent();
        
        ... ...

        final Uri packageUri;

        if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.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 {
            // 若是是用戶本身拉起來的安裝,則默認 sessionId 爲 -1,而且獲取 packageUri
            mSessionId = -1;
            packageUri = intent.getData();
            mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
        }

        // 返回 URI 解析錯誤
        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;
        }

        // 根據 Uri 的 Scheme 進行預處理
        boolean wasSetUp = processPackageUri(packageUri);      // 💥 💥 💥 重點方法 1 💥 💥 💥 
        if (!wasSetUp) {
            return;
        }

        bindUi(R.layout.install_confirm, false);

        // 判斷是不是未知來源的應用,若是開啓容許安裝未知來源選項則直接初始化安裝
        checkIfAllowedAndInitiateInstall();                    // 💥 💥 💥 重點方法 2 💥 💥 💥 
    }
    ... ...
}

processPackageUri

咱們首先來看看 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;

        // 獲得 packageUri 的 Scheme 協議
        final String scheme = packageUri.getScheme();

        // 根據這個 Scheme 協議分別對 package 協議和 file 協議進行處理
        switch (scheme) {
            // 處理 scheme 爲 package 的狀況
            case SCHEME_PACKAGE: {
                try {
                    /**
                      *     PackageInfo mPkgInfo;
                      *
                      * 獲取 package 對應的 Android 應用信息 PackageInfo 如:應用名稱,權限列表等...
                      */
                    mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
                            PackageManager.GET_PERMISSIONS | PackageManager.MATCH_UNINSTALLED_PACKAGES);
                } catch (NameNotFoundException e) {
                }
                
                // 若是沒法獲取 PackageInfo ,彈出一個錯誤的對話框,而後直接退出安裝
                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;
                }
                // 建立 AppSnipet 對象,該對象封裝了待安裝 Android 應用的標題和圖標
                mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo), 
                                  mPm.getApplicationIcon(mPkgInfo.applicationInfo));
            } break;

            // 處理 scheme 爲 file 的狀況
            case ContentResolver.SCHEME_FILE: {
                // 根據 packageUri 建立一個新的 File
                File sourceFile = new File(packageUri.getPath());
                // 建立 APK 文件的分析器 parsed
                // 💥 💥 💥 重點方法 💥 💥 💥 
                PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);      

                // 說明解析錯誤,則彈出對話框,並退出安裝
                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;
                }
                
                /**
                 * 走到這邊,說明解析成功
                 * 對 parsed 進行進一步處理獲得包信息 PackageInfo,獲取權限部分
                 * 這裏麪包含APK文件的相關信息
                 *
                 *     PackageInfo mPkgInfo;
                 */
                mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
                        PackageManager.GET_PERMISSIONS, 0, 0, null, new PackageUserState());
                        
                /*
                 *     private PackageUtil.AppSnippet mAppSnippet;
                 *
                 * 獲取 PackageUtil.AppSnippet,AppSnippet 是 PackageUtil 的靜態內部類
                 * 內部封裝了 icon 和 label ;
                 * AppSnippet 中只有兩個屬性:lable(應用名稱)、icon(應用圖標)
                 *
                 *     // getAppSnippet 返回的是 label 和 icon
                 *     return new PackageUtil.AppSnippet(label, icon);  
                 */
                mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
            } break;

            // 若是不是這兩個協議就會拋出異常
            default: {
                throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
            }
        }

        return true;
    }

PackageUtil.getPackageInfo

/**
     * Utility method to get package information for a given {@link File}
     */
    public static PackageParser.Package getPackageInfo(Context context, File sourceFile) {
        // new 了一個 PackageParser 對象
        final PackageParser parser = new PackageParser();
        parser.setCallback(new PackageParser.CallbackImpl(context.getPackageManager()));
        try {
            return parser.parsePackage(sourceFile, 0);
        } catch (PackageParserException e) {
            return null;
        }
    }

checkIfAllowedAndInitiateInstall

接下來咱們看看 checkIfAllowedAndInitiateInstall 作所工做:

private void checkIfAllowedAndInitiateInstall() {
        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;
        }

        /**
          * isInstallRequestFromUnknownSource    // 安裝請求是否來自一個未知的源
          *
          * 判斷若是容許安裝未知來源或者根據 Intent 判斷得出該 APK 不是未知來源
          */
        if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
            // 初始化安裝
            initiateInstall();      // 💥 💥 💥 重點方法 1 💥 💥 💥 
        } else {
            final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
            // 若是管理員限制來自未知源的安裝, 就彈出提示 Dialog
            if ((unknownSourcesRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
                showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
            } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
                startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
                finish();
            } else {
                handleUnknownSources();      // 💥 💥 💥 重點方法 2 💥 💥 💥 
            }
        }
    }

initiateInstall

// 判斷若是容許安裝未知來源或者根據Intent判斷得出該APK不是未知來源
    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;
        }

        // Check if package is already installed. display confirmation dialog if replacing pkg
        // 檢查這個包是否真的被安裝,若是要替換,則顯示替換對話框
        try {
            // 獲取設備上的殘存數據,而且標記爲 「installed」 的,實際上已經被卸載的應用
            mAppInfo = mPm.getApplicationInfo(pkgName,
                    PackageManager.MATCH_UNINSTALLED_PACKAGES);
            if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
                // 若是應用是被卸載的,可是又是被標識成安裝過的,則認爲是新安裝
                mAppInfo = null;
            }
        } catch (NameNotFoundException e) {
            mAppInfo = null;
        }

        // 列出權限列表,等待用戶確認安裝
        startInstallConfirm();
    }

咱們能夠看到, initiateInstall 主要作了三件事

        ✨ 一、檢查設備是不是同名安裝,若是是則後續是替換安裝。

        ✨ 二、檢查設備上是否已經安裝了這個安裝包,若是是,後面是替換安裝。

        ✨ 三、調用 startInstallConfirm() 這個方法是安裝的核心代碼。

startInstallConfirm

下面咱們就來看下 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.
        // 根據 sdk 版原本判斷 app 是否支持運行時權限
        // 若是app支持運行時權限,這裏會顯示新的運行時權限
        boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion
                >= Build.VERSION_CODES.M;
                
        // 顯示權限列表的變量,true:顯示權限列表,false:未顯示權限列表
        boolean permVisible = false;
        mScrollView = null;
        mOkCanInstall = false;
        int msg = 0;

        // perms 這個對象包括了該應用的用戶的 uid 以及相應的一些權限,以及權限組信息
        AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);
        
        // 獲取隱私相關權限的數量
        final int N = perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL);

        // 判斷是否爲已經安裝過的應用
        if (mAppInfo != null) {
            // 若是已經安裝過則繼續判斷是否爲系統應用
            msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                    ? R.string.install_confirm_question_update_system
                    : R.string.install_confirm_question_update;
            // 用來顯示權限列表的 scrollview
            mScrollView = new CaffeinatedScrollView(this);
            // 若是顯示的內容超過了 mScrollView ,則就會摺疊能夠滾動
            mScrollView.setFillViewport(true);
            boolean newPermissionsFound = false;
            if (!supportsRuntimePermissions) {
                // 針對更新應用程序相對於舊版本而判斷是否加入新的權限
                newPermissionsFound =
                        (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
                if (newPermissionsFound) {
                    // 將新的權限列表視頻添加到滾動視圖中
                    permVisible = true;
                    // 調用 AppSecurityPermissions 的 getPermissionsView方法來獲取
                    // PermissionItemView,並將 PermissionItemView
                    // 添加到 CaffeinatedScrollView 中,
                    // 這樣安裝該 APK 須要訪問的系統權限就能夠所有的展現出來了
                    mScrollView.addView(perms.getPermissionsView(
                            AppSecurityPermissions.WHICH_NEW));
                }
            }
            if (!supportsRuntimePermissions && !newPermissionsFound) {
                // 若是既不支持可運行權限項也沒有新權限發現,
                // 則提示沒有新權限(沒有設置任何權限,只顯示應用程序名稱和圖標)
                LayoutInflater inflater = (LayoutInflater)getSystemService(
                        Context.LAYOUT_INFLATER_SERVICE);
                TextView label = (TextView)inflater.inflate(R.layout.label, null);
                label.setText(R.string.no_new_perms);
                mScrollView.addView(label);
            }
            adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
                    getText(R.string.newPerms)), mScrollView);
        }
        // 若是至少設置了一個權限
        if (!supportsRuntimePermissions && N > 0) {
            permVisible = true;
            LayoutInflater inflater = (LayoutInflater)getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
            // 解析權限列表的視圖
            View root = inflater.inflate(R.layout.permissions_list, null);
            if (mScrollView == null) {
                mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview);
            }
            // 添加到權限列表的視圖
            ((ViewGroup)root.findViewById(R.id.permission_list)).addView(
                        perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL));
            adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
                    getText(R.string.allPerms)), root);
        }
        if (!permVisible) {
            // 若是不須要任何權限
            if (mAppInfo != null) {
                // 若是是更新安裝包,而且沒有任何權限要求
                // 判斷是不是系統應用來設置佈局文件
                msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                        ? R.string.install_confirm_question_update_system_no_perms
                        : R.string.install_confirm_question_update_no_perms;
            } else {
                // 是新安裝的 app 而且沒有權限列表
                msg = R.string.install_confirm_question_no_perms;
            }

            // We do not need to show any permissions, load layout without permissions
            // 設置相應的 UI
            bindUi(R.layout.install_confirm, true);
            mScrollView = null;
        }
        if (msg != 0) {
            ((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
        }
        if (mScrollView == null) {
            // There is nothing to scroll view, so the ok button is immediately
            // set to install.
            mOk.setText(R.string.install);
            mOkCanInstall = true;
        } else {
            mScrollView.setFullScrollAction(new Runnable() {
                @Override
                public void run() {
                    mOk.setText(R.string.install);
                    mOkCanInstall = true;
                }
            });
        }
    }

這個方法其實主要是根據不一樣的狀況來設置相應的 UI,主要是將安裝包分爲新安裝和更新安裝,在更新安裝裏面又分爲系統應用和非系統應用,而後根據不一樣的狀況來顯示不一樣的 UI,UI這塊主要是經過 getPermissionsView 方法來獲取不一樣的權限 View。

總結

PackageInstaller 初始化的過程:

        ✨ 一、根據 Uri 的 Scheme 協議不一樣,跳轉到不一樣的界面,content 協議跳轉到 InstallStart,其餘的跳轉到 PackageInstallerActivity。本文應用場景中,若是是 Android7.0 以及更高版本會跳轉到 InstallStart。
        ✨ 二、InstallStart 將 content 協議的 Uri 轉換爲 File 協議,而後跳轉到 PackageInstallerActivity。
        ✨ 三、PackageInstallerActivity 會分別對 package 協議和 file 協議的 Uri 進行處理,若是是 file 協議會解析 APK 文件獲得包信息 PackageInfo。
        ✨ 四、PackageInstallerActivity 中會對未知來源進行處理,若是容許安裝未知來源或者根據 Intent 判斷得出該 APK 不是未知來源,就會初始化安裝確認界面,若是管理員限制來自未知源的安裝, 就彈出提示 Dialog 或者跳轉到設置界面。

後做

Android 9.0 源碼_核心篇 -- 深刻研究 PMS 系列(5)之 APK 安裝流程(PI)

參考

 01. http://liuwangshu.cn/framewor...
 02. https://www.cnblogs.com/ouyan...

相關文章
相關標籤/搜索