0xA02 Android 10 源碼分析:Apk的安裝流程

前言

  • 這是 Android 10 源碼分析系列的第 2 篇
  • 分支:android-10.0.0_r14
  • 全文閱讀大概 5 分鐘

上一篇文章介紹了0xA01 ASOP應用框架:Apk是如何生成的,這篇文章接着介紹如何安裝Apk,須要說一下Android 10 及更高版本中, 安裝器PackageInstaller源碼位置有所變更
java

PackageInstaller源碼所在位置

PackageInstaller是系統內置的應用程序,用於安裝和卸載應用
android

在 Android 9 及更低版本中,軟件包安裝和權限控制功能包含在 PackageInstaller 軟件包 (//packages/apps/PackageInstaller) 中。在 Android 10 及更高版本中,權限控制功能位於單獨的軟件包 PermissionController (//packages/apps/PermissionController),這兩個軟件包在 Android 10 中的位置以下圖所示,更多信息點擊這裏前往Android 權限git

Android 9 及更低版本中 :github

軟件包安裝和權限控制功能源碼路徑:packages/apps/PackageInstaller算法

Android 10 及更高版本:緩存

  • 權限控制功能PermissionController源碼路徑:packages/apps/PermissionController/
  • 安裝器PackageInstaller源碼路徑:frameworks/base/packages/PackageInstaller/

在Android系統不一樣的目錄存放不一樣類型的應用

  • /system/framwork:保存的是資源型的應用程序,它們用來打包資源文件
  • /system/app:保存系統自帶的應用程序
  • /data/app:保存用戶安裝的應用程序
  • /data/data:應用數據目錄
  • /data/app-private:保存受DRM保護的私有應用程序
  • /vendor/app:保存設備廠商提供的應用程序

查看PackageInstaller源碼方式

  • AOSP-PackageInstaller: 包含了安裝器PackageInstaller(7.1.二、8.1.0、9.0.0、10.0.0)的源碼,能夠切換分之查看,跟隨 Android 版本更新,你永遠能夠看到最新的源代碼 bash

    source

  • aospxref:這是一個在線查看Android源碼網站,服務器在阿里雲訪問速度很快,文末有關這個網站的介紹服務器

  • googlesource-PackageInstaller:這是安裝器PackageInstaller在googlesource上的地址session

1. Apk的安裝方式

安裝Apk主要分爲如下三種場景數據結構

  • 安裝系統應用:系統啓動後調用PackageManagerService.main()初始化註冊解析安裝工做
public static PackageManagerService main(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    // Self-check for initial settings.
    PackageManagerServiceCompilerMapping.checkProperties();

    PackageManagerService m = new PackageManagerService(context, installer,
            factoryTest, onlyCore);
    m.enableSystemUserPackages();
    ServiceManager.addService("package", m);
    final PackageManagerNative pmn = m.new PackageManagerNative();
    ServiceManager.addService("package_native", pmn);
    return m;
}
複製代碼
  • 經過adb安裝:經過pm參數,調用PM的runInstall方法,進入PackageManagerService安裝安裝工做
  • 經過系統安裝器PackageInstaller進行安裝:先調用InstallStart進行權限檢查以後啓動PackageInstallActivity,調用PackageInstallActivity的startInstall方法,點擊OK按鈕後進入PackageManagerService完成拷貝解析安裝工做

全部安裝方式大體相同,最終就是回到PackageManagerService中,安裝一個APK的大體流程以下:

  • 拷貝到apk文件到指定目錄
  • 解壓縮apk,拷貝文件,建立應用的數據目錄
  • 解析apk的AndroidManifest.xml文件
  • 向Launcher應用申請添加建立快捷方式

本文主要來分析經過安裝器PackageInstaller安裝Apk,這是用戶最經常使用的一種方式

2. PackageInstaller的入口

下面代碼必定不會很陌生,這就是咱們經常使用的安裝Apk的代碼(PS: 關於靜默安裝我會後續分享在逆向開發相關的文章)

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

/*
* 自Android N開始,是經過FileProvider共享相關文件,可是Android Q對公
* 有目錄 File API進行了限制,只能經過Uri來操做
*/
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
    // filePath是經過ContentResolver獲得的
    intent.setDataAndType(Uri.parse(filePath) ,"application/vnd.android.package-archive");
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    Uri contentUri = FileProvider.getUriForFile(mContext, "com.dhl.file.fileProvider", file);
    intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
    intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
startActivity(intent);

// 須要在AndroidManifest添加權限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> 
複製代碼

經過intent.setDataAndType方法指定Intent的數據類型爲application/vnd.android.package-archive,隱式匹配的Activity爲InstallStart: 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>
複製代碼
  • 本文分析的是10.0的源碼,在8.0、9.0、10.0等等版本中隱式匹配的Activity是InstallStart,7.0隱式匹配的Activity是PackageInstallerActivity
  • 安裝器PackageInstaller的入口Activity是InstallStart,定義了兩個scheme:content和package

3. Apk的安裝流程

經過上面方式找到了入口Activity,下面咱們來查看一下Apk是如何安裝的

3.1 InstallStart

主要工做:

  1. 判斷是否勾選「未知來源」選項,若未勾選跳轉到設置安裝未知來源界面
  2. 對於大於等於Android 8.0版本,會先檢查是否申請安裝權限,若沒有則中斷安裝
  3. 判斷Uri的Scheme協議,如果content則調用InstallStaging, 如果package則調用PackageInstallerActivity

當咱們調用上面安裝代碼來安裝Apk時。會跳轉到InstallStart, 並調用它的onCreate方法: frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...

    final boolean isSessionInstall =
            PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
    ...

    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);
            mAbortInstall = true;

            // 若是targetSdkVersion大於等於26(8.0), 且獲取不到REQUEST_INSTALL_PACKAGES權限停止安裝
        } 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;
        }
    }

    ...

    // 若是設置了ACTION_CONFIRM_PERMISSIONS,則調用PackageInstallerActivity。
    if (isSessionInstall) {
        nextActivity.setClass(this, PackageInstallerActivity.class);
    } else {
        Uri packageUri = intent.getData();
        // 判斷Uri的Scheme協議是不是content
        if (packageUri != null && packageUri.getScheme().equals(
                ContentResolver.SCHEME_CONTENT)) {
            // [IMPORTANT] This path is deprecated, but should still work.
            // 這個路徑已經被起用了,可是仍然能夠工做

            // 調用InstallStaging來拷貝file/content,防止被修改
            nextActivity.setClass(this, InstallStaging.class);
        } else if (packageUri != null && packageUri.getScheme().equals(
                PackageInstallerActivity.SCHEME_PACKAGE)) {
            // 若是Uri中包含package,則調用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();
}
複製代碼

根據Uri的Scheme協議,如果content則調用InstallStaging,查看InstallStaging的onResume方法: frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java

@Override
protected void onResume() {
    super.onResume();
    if (mStagingTask == null) {
        if (mStagedFile == null) {
            // 建立臨時文件 mStagedFile 用來存儲數據
            try {
                mStagedFile = TemporaryFileManager.getStagedFile(this);
            } catch (IOException e) {
                showError();
                return;
            }
        }
        // 啓動 StagingAsyncTask,並傳入了content協議的Uri
        mStagingTask = new StagingAsyncTask();
        mStagingTask.execute(getIntent().getData());
    }
}
複製代碼
  1. 建立臨時文件 mStagedFile 用來存儲數據
  2. 啓動 StagingAsyncTask,並傳入了content協議的Uri
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
    @Override
    protected Boolean doInBackground(Uri... params) {
        ...
        Uri packageUri = params[0];
        try (InputStream in = getContentResolver().openInputStream(packageUri)) {
            ...
            // 將packageUri(content協議的Uri)的內容寫入到mStagedFile中
            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) {
            // 若是寫入成功,調用DeleteStagedFileOnResult
            Intent installIntent = new Intent(getIntent());
            installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
            installIntent.setData(Uri.fromFile(mStagedFile));
            ...
            startActivity(installIntent);
            InstallStaging.this.finish();
        } else {
            showError();
        }
    }
}
複製代碼
  1. doInBackground方法中將packageUri(content協議的Uri)的內容寫入到mStagedFile中
  2. 若是寫入成功,調用DeleteStagedFileOnResult的OnCreate方法: frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState == null) {
        // 啓動PackageInstallerActivity
        Intent installIntent = new Intent(getIntent());
        installIntent.setClass(this, PackageInstallerActivity.class);
        installIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
        startActivityForResult(installIntent, 0);
    }
}
複製代碼

通過分析InstallStaging主要起了中轉做用,將content協議的Uri轉換爲File協議,最後跳轉到PackageInstallerActivity

3.2 PackageInstallerActivity

主要工做:

  1. 顯示安裝界面
  2. 初始化安裝須要用的各類對象,好比PackageManager、IPackageManager、AppOpsManager、UserManager、PackageInstaller等等
  3. 根據傳遞過來的Scheme協議作不一樣的處理
  4. 檢查是否容許、初始化安裝
  5. 在準備安裝的以前,檢查應用列表判斷該應用是否已安裝,若已安裝則提示該應用已安裝,由用戶決定是否替換
  6. 在安裝界面,提取出APK中權限信息並展現出來
  7. 點擊OK按鈕確認安裝後,會調用startInstall開始安裝工做

PackageInstallerActivity纔是應用安裝器PackageInstaller真正的入口Activity,查看它的onCreate方法: frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

protected void onCreate(Bundle icicle) {
    getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
    super.onCreate(null);
    // 初始化安裝須要用到的對象
    mPm = getPackageManager();
    mIpm = AppGlobals.getPackageManager();
    mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
    mInstaller = mPm.getPackageInstaller();
    mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

    // 根據Uri的Scheme作不一樣的處理
    boolean wasSetUp = processPackageUri(packageUri);
    if (!wasSetUp) {
        return;
    }
    // 顯示安裝界面
    bindUi();
    // 檢查是否容許安裝包,若是容許則啓動安裝。若是不容許顯示適當的對話框
    checkIfAllowedAndInitiateInstall();
}
複製代碼

主要作了對象的初始化,解析Uri的Scheme,初始化界面,安裝包檢查等等工做,接着查看一下processPackageUri方法

private boolean processPackageUri(final Uri packageUri) {
    mPackageURI = packageUri;
    final String scheme = packageUri.getScheme();
    // 根據這個Scheme協議分別對package協議和file協議進行處理
    switch (scheme) {
        case SCHEME_PACKAGE: {
            try {
                // 經過PackageManager對象獲取指定包名的包信息
                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: {
            // 根據packageUri建立一個新的File
            File sourceFile = new File(packageUri.getPath());
            // 解析Apk獲得Apk的信息,PackageParser.Package存儲了Apk的全部信息
            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;
            }
            // 根據PackageParser.Package獲得的Apk信息,生成PackageInfo
            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;
}
複製代碼

主要對Scheme協議分別對package協議和file協議進行處理

SCHEME_PACKAGE:

  • 在package協議中調用了PackageManager.getPackageInfo方法生成PackageInfo,PackageInfo是跨進程傳遞的包數據(activities、receivers、services、providers、permissions等等)包含Apk的全部信息

SCHEME_FILE:

  • 在file協議的處理中調用了PackageUtil.getPackageInfo方法,方法內部調用了PackageParser.parsePackage()把Apk文件的manifest和簽名信息都解析完成並保存在了Package,Package包含了該APK的全部信息
  • 調用PackageParser.generatePackageInfo生成PackageInfo

接着往下走,都解析完成以後,回到onCreate方法,繼續調用checkIfAllowedAndInitiateInstall方法

private void checkIfAllowedAndInitiateInstall() {
    // 首先檢查安裝應用程序的用戶限制,若是有限制並彈出彈出提示Dialog或者跳轉到設置界面
    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;
    }

    // 判斷若是容許安裝未知來源或者根據Intent判斷得出該APK不是未知來源
    if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
        initiateInstall();
    } else {
        // 檢查未知安裝源限制,若是有限制彈出Dialog,顯示相應的信息
        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 {
            // 處理未知來源的APK
            handleUnknownSources();
        }
    }
}
複製代碼

主要檢查安裝應用程序的用戶限制,當Apk文件不對或者安裝有限制則調用showDialogInner方法,彈出dialog提示用戶,顯示相應的錯誤信息,來看一下都有那些錯誤信息

// Dialog identifiers used in showDialog
private static final int DLG_BASE = 0;
// package信息錯誤
private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2;
// 存儲空間不夠
private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3;
// 安裝錯誤
private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
// 用戶限制的未知來源
private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = DLG_BASE + 5;
private static final int DLG_ANONYMOUS_SOURCE = DLG_BASE + 6;
// 在wear上不支持
private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7;
private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 8;
// 安裝限制用戶使用的應用程序
private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 9;
複製代碼

若是用戶容許安裝未知來源,會調用initiateInstall方法

private void initiateInstall() {
    String pkgName = mPkgInfo.packageName;
    // 檢查設備上是否存在相同包名的Apk
    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;
    }
    // 檢查package是否已安裝, 若是已經安裝則顯示對話框提示用戶是否替換。
    try {
        mAppInfo = mPm.getApplicationInfo(pkgName,
                PackageManager.MATCH_UNINSTALLED_PACKAGES);
        if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
            mAppInfo = null;
        }
    } catch (NameNotFoundException e) {
        mAppInfo = null;
    }
    // 初始化確認安裝界面
    startInstallConfirm();
}
複製代碼

根據包名獲取應用程序的信息,調用startInstallConfirm方法初始化安裝確認界面後,當用戶點擊確認按鈕以後發生了什麼,接着查看確認按鈕點擊事件

private void bindUi() {
   ...
    // 點擊確認按鈕,安裝Apk
    mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
            (ignored, ignored2) -> {
                if (mOk.isEnabled()) {
                    if (mSessionId != -1) {
                        mInstaller.setPermissionsResult(mSessionId, true);
                        finish();
                    } else {
                        // 啓動Activity來完成應用的安裝
                        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);
}
複製代碼

當用戶點擊確認按鈕調用了startInstall方法,啓動子Activity完成Apk的安裝

private void startInstall() {
    // 啓動子Activity來完成應用的安
    Intent newIntent = new Intent();
    newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
            mPkgInfo.applicationInfo);
    newIntent.setData(mPackageURI);
    newIntent.setClass(this, InstallInstalling.class);
    ...
    if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
    startActivity(newIntent);
    finish();
}
複製代碼

startInstall方法用來跳轉到InstallInstalling,並關閉掉當前的PackageInstallerActivity

3.3 InstallInstalling

主要工做:

  1. 向包管理器發送包的信息,而後等待包管理器處理結果
  2. 註冊一個觀察者InstallEventReceiver,並接受安裝成功和失敗的回調
  3. 在方法onResume中建立同步棧,打開安裝session,設置安裝進度條

InstallInstalling首先向包管理器發送包的信息,而後等待包管理器處理結果,並在方法InstallSuccess 和 方法InstallFailed 進行成功和失敗的處理,查看InstallInstalling的onCreate方法: frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java

protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...

    // 判斷安裝的應用是否已經存在
    if ("package".equals(mPackageURI.getScheme())) {
        try {
            getPackageManager().installExistingPackage(appInfo.packageName);
            launchSuccess();
        } catch (PackageManager.NameNotFoundException e) {
            launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
        }
    } else {
        final File sourceFile = new File(mPackageURI.getPath());
        PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
        ...

        if (savedInstanceState != null) {
            // 若是savedInstanceState 不爲空,獲取已經存在mSessionId 和mInstallId 從新註冊
            mSessionId = savedInstanceState.getInt(SESSION_ID);
            mInstallId = savedInstanceState.getInt(INSTALL_ID);
            try {
                // 根據mInstallId向InstallEventReceiver註冊一個觀察者,launchFinishBasedOnResult會接收到安裝事件的回調
                InstallEventReceiver.addObserver(this, mInstallId,
                        this::launchFinishBasedOnResult);
            } catch (EventResultPersister.OutOfIdsException e) {
            }
        } else {
            // 若是爲空建立SessionParams,表明安裝會話的參數
            // 解析Apk, 並將解析的參數賦值給SessionParams
            PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
            ...

            try {
                // 註冊InstallEventReceiver,並在launchFinishBasedOnResult會接收到安裝事件的回調
                mInstallId = InstallEventReceiver
                        .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
                                this::launchFinishBasedOnResult);
            } catch (EventResultPersister.OutOfIdsException e) {
                launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
            }

            try {
                // createSession 內部經過IPackageInstaller與PackageInstallerService進行進程間通訊,
                // 最終調用的是PackageInstallerService的createSession方法來建立並返回mSessionId
                mSessionId = getPackageManager().getPackageInstaller().createSession(params);
            } catch (IOException e) {
                launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
            }
        }
            ...
    }
}
複製代碼
  • 最終都會註冊一個觀察者InstallEventReceiver,並在launchFinishBasedOnResult會接收到安裝事件的回調,其中InstallEventReceiver繼承自BroadcastReceiver,用於接收安裝事件並回調給EventResultPersister
  • createSession 內部經過IPackageInstaller與PackageInstallerService進行進程間通訊,最終調用的是PackageInstallerService的createSession方法來建立並返回mSessionId
  • 接下來在onResume方法建立InstallingAsyncTask用來執行Apk的安裝,接着查看onResume方法
protected void onResume() {
    super.onResume();
    if (mInstallingTask == null) {
        PackageInstaller installer = getPackageManager().getPackageInstaller();
        // 根據mSessionId 獲取SessionInfo, 表明安裝會話的詳細信息
        PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
        if (sessionInfo != null && !sessionInfo.isActive()) {
            mInstallingTask = new InstallingAsyncTask();
            mInstallingTask.execute();
        } else {
            // 安裝完成後會收到廣播
            mCancelButton.setEnabled(false);
            setFinishOnTouchOutside(false);
        }
    }
}
複製代碼

獲得SessionInfo建立並建立InstallingAsyncTask,InstallingAsyncTask的doInBackground方法設置安裝進度條,並將Apk信息寫入PackageInstaller.Session,寫入完成以後,在InstallingAsyncTask的onPostExecute進行成功與失敗的處理,接着查看onPostExecute方法

protected void onPostExecute(PackageInstaller.Session session) {
    if (session != null) {
        Intent broadcastIntent = new Intent(BROADCAST_ACTION);
        broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        broadcastIntent.setPackage(getPackageName());
        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);
        }
    }
}
複製代碼

建立了broadcastIntent,並經過PackageInstaller.Session的commit方法發送出去,經過broadcastIntent構造方法指定的Intent的Action爲BROADCAST_ACTION,而BROADCAST_ACTION是一個常量值

private static final String BROADCAST_ACTION =
            "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
複製代碼

回到InstallInstalling.OnCreate方法,在OnCreate方法註冊InstallEventReceiver,而InstallEventReceiver繼承自BroadcastReceiver,而使用BroadcastReceiver須要在AndroidManifest.xml註冊,接着查看AndroidManifest.xml: /frameworks/base/packages/PackageInstaller/AndroidManifest.xml

<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>
複製代碼

安裝結束以後,會在觀察者InstallEventReceiver註冊的回調方法launchFinishBasedOnResult處理安裝事件的結果,接着查看launchFinishBasedOnResult

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();
}
複製代碼

安裝成功和失敗,都會啓動一個新的Activity(InstallSuccess、InstallFailed)將結果展現給用戶,而後finish掉InstallInstalling

4. 總結

總結一下PackageInstaller安裝APK的過程:

  1. 根據根據Uri的Scheme找到入口InstallStart
  2. InstallStart根據Uri的Scheme協議不一樣作不一樣的處理
  3. 都會調用PackageInstallerActivity, 而後分別對package協議和file協議的Uri進行處理
  4. PackageInstallerActivity檢查未知安裝源限制,若是安裝源限制彈出提示Dialog
  5. 點擊OK按鈕確認安裝後,會調用startInstall開始安裝工做
  6. 若是用戶容許安裝,而後跳轉到InstallInstalling,進行Apk的安裝工做
  7. 在InstallInstalling中,向包管理器發送包的信息,而後註冊一個觀察者InstallEventReceiver,並接受安裝成功和失敗的回調

4.1 PackageInstaller 裏的數據結構與職能

在包android.content.pm下

  • PackageParser:包解析器
    • PackageParser.Package :解析獲得的包信息
      • PackageParser.Component :組件的基類,其子類對應到AndroidManifest.xml中定義的不一樣組件
      • PackageParser.Activity 對應AndroidManifest.xml中定義和標籤
      • PackageParser.Service :對應AndroidManifest.xml中定義標籤
      • PackageParser.Provider :對應AndroidManifest.xml中定義 標籤
      • PackageParser.Instrumentation :對應AndroidManifest.xml中定義 標籤
      • PackageParser.Permission :對應AndroidManifest.xml中定義 標籤
      • PackageParser.PermissionGroup :對應AndroidManifest.xml中定義 標籤
      • PackageInfoLite :輕量的包信息
      • ApkLite :輕量級的APK信息
      • IntentInfo :組件所定義的信息,保存了每一個節點的信息,是基類,它的子類是ActivityIntentInfo、ServiceIntentInfo和ProviderIntentInfo
      • ActivityIntentInfo :保存和節點下的節點
      • ServiceIntentInfo :保存節點下的節點
      • ProviderIntentInfo:保存 節點下的節點

在包android.content.pm下

  • PackageInfo :跨進程傳遞的包數據,包解析時生成
  • PackageItemInfo :一個應用包內全部組件項和通用信息的基類。提供最基本的屬性集,如:label、icon、meta-data等。
  • ApplicationInfo:表明一個特定應用的基本信息,對應AndroidManifest裏面的
  • InstrumentationInfo:用做進行instrumentation的測試的片斷,對應AndroidManifest裏面的
  • PermissionInfo:表明一個特定的權限,對應AndroidManifest裏面的
  • PermissionGroupInfo :一個特定的權限組,對應AndroidManifest裏面的
  • ComponentInfo:表明一個應用內組件(如activityInfo、serviceInfo、ProviderInfo)通用信息的基類。通常不會直接使用該類,它設計爲了避免同應用的組件共享統一的定義。
  • ActivityInfo :對應AndroidManifest.xml裏面的註冊的標籤和標籤。表明一個Activity或者receiver
  • ServiceInfo :對應AndroidManifest.xml裏面的註冊的標籤。表明一個service
  • ProviderInfo :對應AndroidManifest.xml裏面的註冊的標籤。表明一個Provider

在包android.content下

  • Intent:根據特定的條件找到匹配的組件
  • IntentFilter :Intent過濾器
  • ResolveInfo:經過解析一個與IntentFilter相對應的intent獲得的信息,它部分地對應於從AndroidManifest.xml的標籤收集到的信息
  • IntentResolver :Intent解析器,其子類用於不一樣組件的Intent解析保存了全部或者節點信息。(Activity或者BroadcastReceiver信息就是用該自定義類保存的)
  • ActivityIntentResolver :保存全部和節點信息。(Activity或者BroadcastReceiver信息就是用該自定義類保存的)保存了全部節點信息。(Service信息就是用該自定義類保存的)。
  • ServiceIntentResolver :保存了全部 節點信息。(Service信息就是用該自定義類保存的)
  • ProviderIntentReslover:保存了全部 節點信息

在包com.android.server.pm下

  • PackageManagerService:在 SystemServer 進程中啓動的,用來管理全部的package信息,包括安裝、卸載、更新以及解析AndroidManifest.xml以組織相應的數據結構
    • PackageHandler :包管理的消息處理器
      • HandlerParams :消息的數據載體
        • InstallParams : 用於APK的安裝
        • MeasureParams:用於查詢某個已安裝的APK佔據存儲空間的大小(例如在設置程序中獲得某個APK的緩存文件大小)
        • MoveParams :用於已安裝APK的位置移動
      • InstallArgs :APK的安裝參數
        • FileInstallArgs :針對是安裝在內部存儲的APK
        • AsecInstallArgs :針對安裝在SD卡上的APK
        • MoveInfoArgs : 移動APK

到了這裏整個Apk的安裝過程結束了,另外在系統目錄 「/data/system」下有個packages.xml,記錄了系統中全部安裝的應用信息,瞭解這個文件對咱們後續在開發中會有很大的幫助

5. 關於 packages.xml

在Andorid系統目錄 「/data/system」 下保存不少系統文件,主要介紹packages.xml文件

  • packages.xml:記錄了系統中全部安裝的應用信息,包括基本信息、簽名和權限、Apk文件的路徑、native庫的存儲路徑

系統啓動的時候會經過PackageManagerServcie讀取這個文件加載系統中全部安裝的應用,這個文件在開發中也是很是有幫助的,不一樣廠商會對Android源碼有不一樣的修改,若是咱們須要分析系統App的源碼,就經過這個packages.xml找到目標Apk,dump出來分析源碼

如下是packages.xml文件部份內容

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
    <version sdkVersion="27" databaseVersion="3" fingerprint="Meizu/meizu_M1822_CN/M1822:8.1.0/OPM1.171019.026/1539943691:user/release-keys" />
    <version volumeUuid="primary_physical" sdkVersion="27" databaseVersion="27" fingerprint="Meizu/meizu_M1822_CN/M1822:8.1.0/OPM1.171019.026/1539943691:user/release-keys" />
    <meizu_version meizu_fingerprint="8.1.0-1541573178_stable" />
    <permission-trees />
    <permissions>
        <item name="com.meizu.voiceassistant.push.permission.MESSAGE" package="com.meizu.voiceassistant" protection="2" />
        <item name="com.meizu.safe.alphame.permission.DATA" package="com.meizu.safe" protection="18" />
        <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
        
        ......

        <item name="android.permission.MODIFY_PHONE_STATE" granted="true" flags="0" />
        <item name="com.android.launcher.permission.INSTALL_SHORTCUT" granted="true" flags="0" />
        <item name="android.permission.WAKE_LOCK" granted="true" flags="0" />
        </perms>
        <proper-signing-keyset identifier="1" />
    </package>
    
    <package name="com.android.providers.telephony" codePath="/system/priv-app/TelephonyProvider" nativeLibraryPath="/system/priv-app/TelephonyProvider/lib" primaryCpuAbi="arm64-v8a" publicFlags="1007402501" privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="27" sharedUserId="1001" isOrphaned="true" forceFull="true">
        <sigs count="1">
            <cert index="0" />
        </sigs>
        <perms>
            <item name="android.permission.SEND_RECEIVE_STK_INTENT" granted="true" flags="0" />
            <item name="android.permission.BIND_INCALL_SERVICE" granted="true" flags="0" />
            
            ......

            <item name="android.permission.UPDATE_APP_OPS_STATS" granted="true" flags="0" />
        </perms>
        <proper-signing-keyset identifier="1" />
    </package>
複製代碼
5.1. package 表示包信息
  • name表示應用的包名
  • codePath表示的是apk文件的路徑
  • nativeLibraryPath表示應用的native庫的存儲路徑
  • it表示應用安裝的時間
  • ut表示應用最後一次修改的時間
  • version表示應用的版本號
  • userId表示所屬於的id
5.2. sign 表示應用的簽名
  • count表示標籤中包含有多少個證書
  • cert表示具體的證書的值
5.3. perms 表示應用聲明使用的權限,每個子標籤表明一項權限

6. 安利一個在線查看Android源碼網站

aospxrefweishu大神搭建一個在線查看在線查看Android源碼網站, 訪問速度很是快

在這以前我經常使用的在線查看Android源碼的網站androidxref,訪問速度不只慢,並且更新也不及時,如今Android 10發佈了,這個網站到如今提供的最新的代碼仍是Andorid 9

aospxref提供了與 androidxref 徹底同樣的源碼瀏覽和交叉索引功能;除此以外,它還有一些別的優勢:

  • 跟隨 Android 版本更新,你永遠能夠看到最新的源代碼。
  • 服務器在阿里雲,國內訪問速度賊快。
  • opengrok 版本較高,查閱代碼時會有自動提示。
  • 對頁面作過部分優化,使用更便捷;好比能夠在任意界面跳轉到首頁。

關於更多詳細的信息能夠點擊這裏閱讀原文

結語

致力於分享一系列的Android系統源碼、逆向分析、算法相關的文章,每篇文章都會反覆檢查以後纔會發表出來,若是你同我同樣喜歡研究Android源碼,一塊兒來學習,期待與你一塊兒成長

參考

相關文章
相關標籤/搜索