總體而言, Android P 和 Android O相比,PackageInstaller模塊變化不大。因?yàn)锳ndroid O的時(shí)候沒來及總結(jié),所以一些 Android O上就出現(xiàn)的特性也會(huì)在這里記錄學(xué)習(xí)一下。
界面跳轉(zhuǎn)流程

Android P PackageInstaller.png
??從界面跳轉(zhuǎn)流程上看,與 Android O相比,多了一個(gè) DeleteStagedFileOnResult 頁面,主要就是把原來 跳轉(zhuǎn)PackageInstallerActivity 和刪除 臨時(shí)文件的動(dòng)作 拎到這里來了。
一些重要的點(diǎn)
- Android O開始,三方應(yīng)用如果想調(diào)起 系統(tǒng) PackageInstaller 安裝/卸載應(yīng)用,必須在AndroidManifest中聲明對應(yīng)的權(quán)限:
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.DELETE_PACKAGES"/>'
否則無法實(shí)現(xiàn)安裝會(huì)有類似 log打出來:
10-26 16:26:25.188 7457 7457 E InstallStart: Requesting uid 10106 needs to declare permission android.permission.REQUEST_INSTALL_PACKAGES
看一看這一部分的檢測邏輯,代碼位置在 InstallStart.java的 oncreate方法中
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
.............
.............
final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
final int originatingUid = getOriginatingUid(sourceInfo);
/**
* 下面是判定 未知來源 的邏輯
*/
boolean isTrustedSource = false;
//如果是 priv-app, 就去看Intent中 EXTRA_NOT_UNKNOWN_SOURCE 參數(shù),也就是說,如果調(diào)起安裝器的不是priv-app, 帶不帶這個(gè)參數(shù)都沒用
//如果是 priv-app, 再看 EXTRA_NOT_UNKNOWN_SOURCE 參數(shù)的值
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);
if (targetSdkVersion < 0) {
Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
// Invalid originating uid supplied. Abort install.
mAbortInstall = true;
} else if (targetSdkVersion >= Build.VERSION_CODES.O && !declaresAppOpPermission(
//在 Android O 及以上版本中,調(diào)起安裝器需要聲明REQUEST_INSTALL_PACKAGES 權(quán)限,否則直接放棄安裝
originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ Manifest.permission.REQUEST_INSTALL_PACKAGES);
mAbortInstall = true;
}
}
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
return;
}
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
// The the installation source as the nextActivity thinks this activity is the source, hence
// set the originating UID and sourceInfo explicitly
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
/*
幾種類型的Uri, file,content,package, 不同的Uri跳轉(zhuǎn)到不同的Activity
*/
if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
|| packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
// Copy file to prevent it from being changed underneath this process
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
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) {
startActivity(nextActivity);
}
finish();
}
-
Android O上去除了 未知來源 總開關(guān),改為單個(gè)渠道控制 未知來源的 安裝請求。
9833687_5.png
這一部分檢測邏輯如下,代碼位置在 PackageInstallerActivity.java
private void handleUnknownSources() {
if (mOriginatingPackage == null) {
Log.i(TAG, "No source found for package " + mPkgInfo.packageName);
showDialogInner(DLG_ANONYMOUS_SOURCE);
return;
}
/**
* 檢查callingPackage是否具有REQUEST_INSTALL_PACKAGES權(quán)限,如果 權(quán)限標(biāo)志位 是 AppOpsManager.MODE_ERRORED,
* 就顯示 渠道未知來源對話框,可以進(jìn)入設(shè)置中設(shè)置開關(guān)
*/
// Shouldn't use static constant directly, see b/65534401.
final int appOpCode =
AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode,
mOriginatingUid, mOriginatingPackage);
switch (appOpMode) {
case AppOpsManager.MODE_DEFAULT:
try {
int result = mIpm.checkUidPermission(
Manifest.permission.REQUEST_INSTALL_PACKAGES, mOriginatingUid);
if (result == PackageManager.PERMISSION_GRANTED) {
initiateInstall();
break;
}
} catch (RemoteException exc) {
Log.e(TAG, "Unable to talk to package manager");
}
mAppOpsManager.setMode(appOpCode, mOriginatingUid,
mOriginatingPackage, AppOpsManager.MODE_ERRORED);
// fall through
//渠道未知來源對話框
case AppOpsManager.MODE_ERRORED:
showDialogInner(DLG_EXTERNAL_SOURCE_BLOCKED);
break;
case AppOpsManager.MODE_ALLOWED:
initiateInstall();
break;
default:
Log.e(TAG, "Invalid app op mode " + appOpMode
+ " for OP_REQUEST_INSTALL_PACKAGES found for uid " + mOriginatingUid);
finish();
break;
}
}
