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

android-APK-banner-670x335-1

前言

上一篇文章介紹了 0xA01 Android 10 源碼分析:APK 是如何生成的,這篇文章接著介紹如何安裝 APK,需要說一下 Android 10 及更高版本中, 安裝器 PackageInstaller 源碼位置有所變動

PackageInstaller 源碼所在位置

PackageInstaller 是系統(tǒng)內(nèi)置的應(yīng)用程序,用于安裝和卸載應(yīng)用

在 Android 9 及更低版本中,軟件包安裝和權(quán)限控制功能包含在 PackageInstaller 軟件包 (//packages/apps/PackageInstaller) 中。在 Android 10 及更高版本中,權(quán)限控制功能位于單獨(dú)的軟件包 PermissionController (//packages/apps/PermissionController),這兩個軟件包在 Android 10 中的位置如下圖所示,更多信息點(diǎn)擊這里前往 Android 權(quán)限

package-instal

Android 9 及更低版本中 :

軟件包安裝和權(quán)限控制功能源碼路徑:packages/apps/PackageInstaller

Android 10 及更高版本:

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

在 Android 系統(tǒng)不同的目錄存放不同類型的應(yīng)用

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

查看 PackageInstaller 源碼方式

  • AOSP-PackageInstaller: 包含了安裝器 PackageInstaller(7.1.2、8.1.0、9.0.0、10.0.0) 的源碼,可以切換分之查看,跟隨 Android 版本更新,你永遠(yuǎn)可以看到最新的源代碼

    source

  • aospxref:這是一個在線查看 Android 源碼網(wǎng)站,服務(wù)器在阿里云訪問速度很快,文末有關(guān)這個網(wǎng)站的介紹

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

1. APK 的安裝方式

安裝 APK 主要分為以下三種場景

  • 安裝系統(tǒng)應(yīng)用:系統(tǒng)啟動后調(diào)用 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 參數(shù),調(diào)用 PM 的 runInstall 方法,進(jìn)入 PackageManagerService 安裝安裝工作
  • 通過系統(tǒng)安裝器 PackageInstaller 進(jìn)行安裝:先調(diào)用 InstallStart 進(jìn)行權(quán)限檢查之后啟動 PackageInstallActivity,調(diào)用 PackageInstallActivity 的 startInstall 方法,點(diǎn)擊 OK 按鈕后進(jìn)入 PackageManagerService 完成拷貝解析安裝工作

所有安裝方式大致相同,最終就是回到 PackageManagerService 中,安裝一個 APK 的大致流程如下:

image
  • 拷貝到 APK 文件到指定目錄
  • 解壓縮 APK,拷貝文件,創(chuàng)建應(yīng)用的數(shù)據(jù)目錄
  • 解析 APK 的 AndroidManifest.xml 文件
  • 向 Launcher 應(yīng)用申請?zhí)砑觿?chuàng)建快捷方式

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

2. PackageInstaller 的入口

下面代碼一定不會很陌生,這就是我們常用的安裝 APK 的代碼(PS: 關(guān)于靜默安裝我會后續(xù)分享在逆向開發(fā)相關(guān)的文章)

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

/*
* 自Android N開始,是通過FileProvider共享相關(guān)文件,但是Android Q對公
* 有目錄 File API進(jìn)行了限制,只能通過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添加權(quán)限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> 

通過 intent.setDataAndType 方法指定 Intent 的數(shù)據(jù)類型為 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. 判斷是否勾選“未知來源”選項(xiàng),若未勾選跳轉(zhuǎn)到設(shè)置安裝未知來源界面
  2. 對于大于等于 Android 8.0 版本,會先檢查是否申請安裝權(quán)限,若沒有則中斷安裝
  3. 判斷 Uri 的 Scheme 協(xié)議,若是 content 則調(diào)用 InstallStaging, 若是 package 則調(diào)用 PackageInstallerActivity

當(dāng)我們調(diào)用上面安裝代碼來安裝 APK 時。會跳轉(zhuǎn)到 InstallStart, 并調(diào)用它的 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;
    // 判斷是否勾選“未知來源”選項(xiàng)
    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權(quán)限中止安裝
        } else if (targetSdkVersion >= Build.VERSION_CODES.O && !declaresAppOpPermission(
                originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
            Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
                    + Manifest.permission.REQUEST_INSTALL_PACKAGES);
            mAbortInstall = true;
        }
    }

    ...

    // 如果設(shè)置了ACTION_CONFIRM_PERMISSIONS,則調(diào)用PackageInstallerActivity。
    if (isSessionInstall) {
        nextActivity.setClass(this, PackageInstallerActivity.class);
    } else {
        Uri packageUri = intent.getData();
        // 判斷Uri的Scheme協(xié)議是否是content
        if (packageUri != null && packageUri.getScheme().equals(
                ContentResolver.SCHEME_CONTENT)) {
            // [IMPORTANT] This path is deprecated, but should still work.
            // 這個路徑已經(jīng)被起用了,但是仍然可以工作

            // 調(diào)用InstallStaging來拷貝file/content,防止被修改
            nextActivity.setClass(this, InstallStaging.class);
        } else if (packageUri != null && packageUri.getScheme().equals(
                PackageInstallerActivity.SCHEME_PACKAGE)) {
            // 如果Uri中包含package,則調(diào)用PackageInstallerActivity
            nextActivity.setClass(this, PackageInstallerActivity.class);
        } else {
            // Uri不合法
            Intent result = new Intent();
            result.putExtra(Intent.EXTRA_INSTALL_RESULT,
                    PackageManager.INSTALL_FAILED_INVALID_URI);
            setResult(RESULT_FIRST_USER, result);
            nextActivity = null;
        }
    }
    if (nextActivity != null) {
        startActivity(nextActivity);
    }
    finish();
}

根據(jù) Uri 的 Scheme 協(xié)議,若是 content 則調(diào)用 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) {
            // 創(chuàng)建臨時文件 mStagedFile 用來存儲數(shù)據(jù)
            try {
                mStagedFile = TemporaryFileManager.getStagedFile(this);
            } catch (IOException e) {
                showError();
                return;
            }
        }
        // 啟動 StagingAsyncTask,并傳入了content協(xié)議的Uri
        mStagingTask = new StagingAsyncTask();
        mStagingTask.execute(getIntent().getData());
    }
}
  1. 創(chuàng)建臨時文件 mStagedFile 用來存儲數(shù)據(jù)
  2. 啟動 StagingAsyncTask,并傳入了 content 協(xié)議的 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協(xié)議的Uri)的內(nèi)容寫入到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) {
            // 如果寫入成功,調(diào)用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 協(xié)議的 Uri)的內(nèi)容寫入到 mStagedFile 中
  2. 如果寫入成功,調(diào)用 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);
    }
}

經(jīng)過分析 InstallStaging 主要起了中轉(zhuǎn)作用,將 content 協(xié)議的 Uri 轉(zhuǎn)換為 File 協(xié)議,最后跳轉(zhuǎn)到 PackageInstallerActivity

3.2 PackageInstallerActivity

主要工作:

  1. 顯示安裝界面
  2. 初始化安裝需要用的各種對象,比如 PackageManager、IPackageManager、AppOpsManager、UserManager、PackageInstaller 等等
  3. 根據(jù)傳遞過來的 Scheme 協(xié)議做不同的處理
  4. 檢查是否允許、初始化安裝
  5. 在準(zhǔn)備安裝的之前,檢查應(yīng)用列表判斷該應(yīng)用是否已安裝,若已安裝則提示該應(yīng)用已安裝,由用戶決定是否替換
  6. 在安裝界面,提取出 APK 中權(quán)限信息并展示出來
  7. 點(diǎn)擊 OK 按鈕確認(rèn)安裝后,會調(diào)用 startInstall 開始安裝工作

PackageInstallerActivity 才是應(yīng)用安裝器 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);

    // 根據(jù)Uri的Scheme做不同的處理
    boolean wasSetUp = processPackageUri(packageUri);
    if (!wasSetUp) {
        return;
    }
    // 顯示安裝界面
    bindUi();
    // 檢查是否允許安裝包,如果允許則啟動安裝。如果不允許顯示適當(dāng)?shù)膶υ捒?    checkIfAllowedAndInitiateInstall();
}

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

private boolean processPackageUri(final Uri packageUri) {
    mPackageURI = packageUri;
    final String scheme = packageUri.getScheme();
    // 根據(jù)這個Scheme協(xié)議分別對package協(xié)議和file協(xié)議進(jìn)行處理
    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: {
            // 根據(jù)packageUri創(chuàng)建一個新的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;
            }
            // 根據(jù)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 協(xié)議分別對 package 協(xié)議和 file 協(xié)議進(jìn)行處理

SCHEME_PACKAGE:

  • 在 package 協(xié)議中調(diào)用了 PackageManager.getPackageInfo 方法生成 PackageInfo,PackageInfo 是跨進(jìn)程傳遞的包數(shù)據(jù)(activities、receivers、services、providers、permissions等等)包含 APK 的所有信息

SCHEME_FILE:

  • 在 file 協(xié)議的處理中調(diào)用了 PackageUtil.getPackageInfo 方法,方法內(nèi)部調(diào)用了 PackageParser.parsePackage() 把 APK 文件的 manifest 和簽名信息都解析完成并保存在了 Package,Package 包含了該 APK 的所有信息
  • 調(diào)用 PackageParser.generatePackageInfo 生成 PackageInfo

接著往下走,都解析完成之后,回到 onCreate 方法,繼續(xù)調(diào)用 checkIfAllowedAndInitiateInstall 方法

private void checkIfAllowedAndInitiateInstall() {
    // 首先檢查安裝應(yīng)用程序的用戶限制,如果有限制并彈出彈出提示Dialog或者跳轉(zhuǎn)到設(shè)置界面
    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;
    }

    // 判斷如果允許安裝未知來源或者根據(jù)Intent判斷得出該APK不是未知來源
    if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
        initiateInstall();
    } else {
        // 檢查未知安裝源限制,如果有限制彈出Dialog,顯示相應(yīng)的信息
        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();
        }
    }
}

主要檢查安裝應(yīng)用程序的用戶限制,當(dāng) APK 文件不對或者安裝有限制則調(diào)用 showDialogInner 方法,彈出 dialog 提示用戶,顯示相應(yīng)的錯誤信息,來看一下都有那些錯誤信息

// 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;
// 安裝限制用戶使用的應(yīng)用程序
private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 9;

如果用戶允許安裝未知來源,會調(diào)用 initiateInstall 方法

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

根據(jù)包名獲取應(yīng)用程序的信息,調(diào)用 startInstallConfirm 方法初始化安裝確認(rèn)界面后,當(dāng)用戶點(diǎn)擊確認(rèn)按鈕之后發(fā)生了什么,接著查看確認(rèn)按鈕點(diǎn)擊事件

private void bindUi() {
   ...
    // 點(diǎn)擊確認(rèn)按鈕,安裝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來完成應(yīng)用的安裝
                        startInstall();
                    }
                }
            }, null);
   // 點(diǎn)擊取消按鈕,取消此次安裝
    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);
}

當(dāng)用戶點(diǎn)擊確認(rèn)按鈕調(diào)用了 startInstall 方法,啟動子 Activity 完成 APK 的安裝

private void startInstall() {
    // 啟動子Activity來完成應(yīng)用的安
    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 方法用來跳轉(zhuǎn)到 InstallInstalling,并關(guān)閉掉當(dāng)前的 PackageInstallerActivity

3.3 InstallInstalling

主要工作:

  1. 向包管理器發(fā)送包的信息,然后等待包管理器處理結(jié)果
  2. 注冊一個觀察者 InstallEventReceiver,并接受安裝成功和失敗的回調(diào)
  3. 在方法 onResume 中創(chuàng)建同步棧,打開安裝 session,設(shè)置安裝進(jìn)度條

InstallInstalling 首先向包管理器發(fā)送包的信息,然后等待包管理器處理結(jié)果,并在方法 InstallSuccess 和方法 InstallFailed 進(jìn)行成功和失敗的處理,查看 InstallInstalling 的 onCreate 方法:
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java

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

    // 判斷安裝的應(yīng)用是否已經(jīng)存在
    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 不為空,獲取已經(jīng)存在mSessionId 和mInstallId 重新注冊
            mSessionId = savedInstanceState.getInt(SESSION_ID);
            mInstallId = savedInstanceState.getInt(INSTALL_ID);
            try {
                // 根據(jù)mInstallId向InstallEventReceiver注冊一個觀察者,launchFinishBasedOnResult會接收到安裝事件的回調(diào)
                InstallEventReceiver.addObserver(this, mInstallId,
                        this::launchFinishBasedOnResult);
            } catch (EventResultPersister.OutOfIdsException e) {
            }
        } else {
            // 如果為空創(chuàng)建SessionParams,代表安裝會話的參數(shù)
            // 解析APK, 并將解析的參數(shù)賦值給SessionParams
            PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
            ...

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

            try {
                // createSession 內(nèi)部通過IPackageInstaller與PackageInstallerService進(jìn)行進(jìn)程間通信,
                // 最終調(diào)用的是PackageInstallerService的createSession方法來創(chuàng)建并返回mSessionId
                mSessionId = getPackageManager().getPackageInstaller().createSession(params);
            } catch (IOException e) {
                launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
            }
        }
            ...
    }
}
  • 最終都會注冊一個觀察者 InstallEventReceiver,并在 launchFinishBasedOnResult 會接收到安裝事件的回調(diào),其中 InstallEventReceiver 繼承自 BroadcastReceiver,用于接收安裝事件并回調(diào)給 EventResultPersister
  • createSession 內(nèi)部通過 IPackageInstaller 與 PackageInstallerService 進(jìn)行進(jìn)程間通信,最終調(diào)用的是 PackageInstallerService的createSession 方法來創(chuàng)建并返回 mSessionId
  • 接下來在 onResume 方法創(chuàng)建 InstallingAsyncTask 用來執(zhí)行 APK 的安裝,接著查看 onResume 方法
protected void onResume() {
    super.onResume();
    if (mInstallingTask == null) {
        PackageInstaller installer = getPackageManager().getPackageInstaller();
        // 根據(jù)mSessionId 獲取SessionInfo, 代表安裝會話的詳細(xì)信息
        PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
        if (sessionInfo != null && !sessionInfo.isActive()) {
            mInstallingTask = new InstallingAsyncTask();
            mInstallingTask.execute();
        } else {
            // 安裝完成后會收到廣播
            mCancelButton.setEnabled(false);
            setFinishOnTouchOutside(false);
        }
    }
}

得到 SessionInfo 創(chuàng)建并創(chuàng)建 InstallingAsyncTask,InstallingAsyncTask 的 doInBackground 方法設(shè)置安裝進(jìn)度條,并將 APK 信息寫入 PackageInstaller.Session,寫入完成之后,在 InstallingAsyncTask 的 onPostExecute 進(jìn)行成功與失敗的處理,接著查看 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);
        }
    }
}

創(chuàng)建了 broadcastIntent,并通過 PackageInstaller.Session 的 commit 方法發(fā)送出去,通過 broadcastIntent 構(gòu)造方法指定的 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>

安裝結(jié)束之后,會在觀察者 InstallEventReceiver 注冊的回調(diào)方法 launchFinishBasedOnResult 處理安裝事件的結(jié)果,接著查看 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)將結(jié)果展示給用戶,然后 finish 掉 InstallInstalling

4. 總結(jié)

總結(jié)一下 PackageInstaller 安裝APK的過程:

  1. 根據(jù)根據(jù) Uri 的 Scheme 找到入口 InstallStart
  2. InstallStart 根據(jù) Uri 的 Scheme 協(xié)議不同做不同的處理
  3. 都會調(diào)用 PackageInstallerActivity, 然后分別對package協(xié)議和 file 協(xié)議的 Uri 進(jìn)行處理
  4. PackageInstallerActivity 檢查未知安裝源限制,如果安裝源限制彈出提示 Dialog
  5. 點(diǎn)擊 OK 按鈕確認(rèn)安裝后,會調(diào)用 startInstall 開始安裝工作
  6. 如果用戶允許安裝,然后跳轉(zhuǎn)到 InstallInstalling,進(jìn)行 APK 的安裝工作
  7. 在 InstallInstalling 中,向包管理器發(fā)送包的信息,然后注冊一個觀察者 InstallEventReceiver,并接受安裝成功和失敗的回調(diào)

5. 關(guān)于 packages.xml

在 Andorid 系統(tǒng)目錄 “/data/system” 下保存很多系統(tǒng)文件,主要介紹 packages.xml 文件

  • packages.xml:記錄了系統(tǒng)中所有安裝的應(yīng)用信息,包括基本信息、簽名和權(quán)限、APK 文件的路徑、native 庫的存儲路徑

系統(tǒng)啟動的時候會通過 PackageManagerServcie 讀取這個文件加載系統(tǒng)中所有安裝的應(yīng)用,這個文件在開發(fā)中也是非常有幫助的,不同廠商會對 Android 源碼有不同的修改,如果我們需要分析系統(tǒng) App 的源碼,就通過這個 packages.xml 找到目標(biāo) APK,dump 出來分析源碼

以下是 packages.xml 文件部分內(nèi)容

<?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 表示應(yīng)用的包名
  • codePath 表示的是 APK 文件的路徑
  • nativeLibraryPath 表示應(yīng)用的 native 庫的存儲路徑
  • it 表示應(yīng)用安裝的時間
  • ut 表示應(yīng)用最后一次修改的時間
  • version 表示應(yīng)用的版本號
  • userId 表示所屬于的 id
5.2. sign 表示應(yīng)用的簽名
  • count 表示標(biāo)簽中包含有多少個證書
  • cert 表示具體的證書的值
5.3. perms 表示應(yīng)用聲明使用的權(quán)限,每一個子標(biāo)簽代表一項(xiàng)權(quán)限

6. 安利一個在線查看 Android 源碼網(wǎng)站

aospxrefweishu 大神搭建一個在線查看在線查看 Android源碼網(wǎng)站, 訪問速度非常快

在這之前我常用的在線查看 Android 源碼的網(wǎng)站 androidxref,訪問速度不僅慢,而且更新也不及時,現(xiàn)在 Android 10 發(fā)布了,這個網(wǎng)站到現(xiàn)在提供的最新的代碼還是 Andorid 9

aospxref 提供了與 androidxref 完全一樣的源碼瀏覽和交叉索引功能;除此之外,它還有一些別的優(yōu)點(diǎn):

  • 跟隨 Android 版本更新,你永遠(yuǎn)可以看到最新的源代碼。
  • 服務(wù)器在阿里云,國內(nèi)訪問速度賊快。
  • opengrok 版本較高,查閱代碼時會有自動提示。
  • 對頁面做過部分優(yōu)化,使用更便捷;比如可以在任意界面跳轉(zhuǎn)到首頁。

參考

結(jié)語

致力于分享一系列 Android 系統(tǒng)源碼、逆向分析、算法相關(guān)的文章,如果你同我一樣喜歡研究Android源碼,可以關(guān)注我,一起來學(xué)習(xí),期待與你一起成長

文章列表

Android 10 源碼系列

工具系列

逆向系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,702評論 2 370