PackageInstaller代码参考:android 11。
aosp的PackageInstaller所在位置:frameworks/base/packages/PackageInstaller
一、InstallStart
从AndroidManifest.xml了解到InstallStart为入口Activity
<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>
这里启动InstallStart主要分三种:
-
android.intent.action.VIEW
常规的三方应用安装设置的action -
android.intent.action.INSTALL_PACKAGE
系统应用才拥有的权限 -
android.content.pm.action.CONFIRM_INSTALL
session install
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
//Activity中的getCallingPackage需要呼起源按startActivityForResult方式启动才能获取到callingpakcage,否则返回null
String callingPackage = getCallingPackage();
...
//通过callingpackage获取ApplicationInfo
final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
final int originatingUid = getOriginatingUid(sourceInfo);
...
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
if (packageUri != null && packageUri.getScheme().equals(
ContentResolver.SCHEME_CONTENT)) {
// [IMPORTANT] This path is deprecated, but should still work. Only necessary
// features should be added.
// 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();
}
跳转页面规则:
action 是 sessioninstall:直接跳转PackageInstallerActivity;
action 不是 sessioninstall:
- uri为content scheme跳转 InstallStaging
- uri为pacakge scheme跳转 PackageInstallerActivity
- uri为其他scheme 这里应该是指file,则结束安装
莫非7.0之后除了通过FileUriExposedException来强制使用content方式之外,Installer也有逻辑限制么?扫下7.0-11.0的Installer:发现从8.1开始,原生Installer对file uri做了如上限制,直接结束安装。当然Installer各厂商都会做定制,而且厂商直接的差异也是非常大的。
另外,从前面InstallStart的intent-filter了解到,PackageInstallerActivity.SCHEME_PACKAGE 对应的是android.intent.action.INSTALL_PACKAGE action,非三方使用的
<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>
那么如果正常的是走action:android.intent.action.VIEW + uri conent,那么会进入到InstallStaging页面。
二、InstallStaging
frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
protected void onResume() {
super.onResume();
...
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
跳转InstallStaging 核心功能在这个异步任务。接下来看看这个任务是做了什么:
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)) {
// Despite the comments in ContentResolver#openInputStream the returned stream can
// be null.
if (in == null) {
return false;
}
//将待安装的apk copy一份到当前installer data/data对应的目录下
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) {
// Now start the installation again from a file
Intent installIntent = new Intent(getIntent());
//基于copy后的apk路径,将uri从content切为file来进行后续的安装操作
installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
installIntent.setData(Uri.fromFile(mStagedFile));
if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
}
installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(installIntent);
InstallStaging.this.finish();
} else {
showError();
}
}
}
InstallStaging通过三方app提供的content路径,将待安装的apk copy一份到当前installer data/data对应的目录下,然后将uri从content转为file,跳转到DeleteStagedFileOnResult页面。
三、DeleteStagedFileOnResult
public class DeleteStagedFileOnResult extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//跳转到PackageInstallerActivity
if (savedInstanceState == null) {
Intent installIntent = new Intent(getIntent());
installIntent.setClass(this, PackageInstallerActivity.class);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(installIntent, 0);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//删除copy的apk文件
File sourceFile = new File(getIntent().getData().getPath());
sourceFile.delete();
setResult(resultCode, data);
finish();
}
}
DeleteStagedFileOnResult做的事情很简单,就是跳转PackageInstallerActivity,然后startActivity回调之后删除copy的apk文件。
四、PackageInstallerActivity
在前面InstallStart跳转逻辑,如果是sessionInstall 或者匹配uri scheme为package,则直接跳转PackageInstallerActivity,如果是uri scheme为content,则要通过InstallStaging->DeleteStagedFileOnResult然后再跳到PackageInstallerActivity。绕了远路,但也只是做了apk的copy,最终殊途同归,下面来详细看一下PackageInstallerActivity:
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);
if (icicle != null) {
mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
}
mPm = getPackageManager();
mIpm = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
final Intent intent = getIntent();
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
PackageInstaller.SessionParams.UID_UNKNOWN);
mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
? getPackageNameForUid(mOriginatingUid) : null;
final Uri packageUri;
//确认sessionInstall方式的sessionInfo
if (PackageInstaller.ACTION_CONFIRM_INSTALL.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 {
mSessionId = -1;
packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
// if there's nothing to do, quietly slip into the ether
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;
}
//1 解析uri
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
// load dummy layout with OK button disabled until we override this layout in
// startInstallConfirm
bindUi();
//2 安装前检查
checkIfAllowedAndInitiateInstall();
}
先看1,解析uri
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
//因为copy apk后,content会转为file,因此到PackageInstallerActivity中就没有content的scheme类型了
switch (scheme) {
case SCHEME_PACKAGE: {
try {
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: {
File sourceFile = new File(packageUri.getPath());
mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile,
PackageManager.GET_PERMISSIONS);
// Check for parse errors
if (mPkgInfo == null) {
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
//通过application info获取应用的icon和label,并包装为PackageUtil.AppSnippet
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
default: {
throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
}
}
return true;
}
再看2安装前检查
/**
* Check if it is allowed to install the package and initiate install if allowed. If not allowed
* show the appropriate dialog.
*/
private void checkIfAllowedAndInitiateInstall() {
// Check for install apps user restriction first.
//系统对多用户管理会设置不同的权限,这里是做用户权限校验
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;
}
//用户手动设置同意未知来源安装权限
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
initiateInstall();
} else {
// Check for unknown sources restrictions.
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 {
handleUnknownSources();
}
}
}
这里主要是安装一系列权限限制检查,在允许未知来源安装的条件下,接着看initiateInstall
private void initiateInstall() {
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
//检查设备上是否已经有此名称的软件包, 但是它已经被重命名为其他东西。
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 {
// This is a little convoluted because we want to get all uninstalled
// apps, but this may include apps with just data, and if it is just
// data we still want to count it as "installed”.
//检查当前包是否已经被安装
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}
startInstallConfirm();
}
private void startInstallConfirm() {
View viewToEnable;
//已经安装的app升级
if (mAppInfo != null) {
viewToEnable = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? requireViewById(R.id.install_confirm_question_update_system)
: requireViewById(R.id.install_confirm_question_update);
} else {
//新安装app
// This is a new application with no permissions.
viewToEnable = requireViewById(R.id.install_confirm_question);
}
viewToEnable.setVisibility(View.VISIBLE);
mEnableOk = true;
mOk.setEnabled(true);
mOk.setFilterTouchesWhenObscured(true);
}
这里设置mOk,对应的安装按钮,设置可点击安装。按钮初始化是在前面bindUi的时候:
private void bindUi() {
mAlert.setIcon(mAppSnippet.icon);
mAlert.setTitle(mAppSnippet.label);
mAlert.setView(R.layout.install_content_view);
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
(ignored, ignored2) -> {
if (mOk.isEnabled()) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
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);
if (!mOk.isInTouchMode()) {
mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).requestFocus();
}
}
如果是正常用户安装这里直接就走startInstall()
private void startInstall() {
// Start subactivity to actually install the application
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallInstalling.class);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
}
if (mReferrerURI != null) {
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
}
if (installerPackageName != null) {
newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
installerPackageName);
}
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
}
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
startActivity(newIntent);
finish();
}
跳转到InstallInstalling来处理
五、InstallInstalling
protected void onCreate(@Nullable Bundle savedInstanceState) {
…
//通过PackageInstallerService来创建session
mSessionId = getPackageManager().getPackageInstaller().createSession(params);
...
}
frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java
private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
throws IOException {
...
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
installSource, params, createdMillis,
stageDir, stageCid, null, false, false, false, false, null, SessionInfo.INVALID_ID,
false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
synchronized (mSessions) {
mSessions.put(sessionId, session);
}
...
return sessionId;
}
创建了一个PackageInstallerSession,这里通过PackageInstallerSession做进程间通信,最终将安装apk任务交给PackageManagerService
再接着往下看InstallInstalling的onResume
protected void onResume() {
super.onResume();
// This is the first onResume in a single life of the activity
if (mInstallingTask == null) {
PackageInstaller installer = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
if (sessionInfo != null && !sessionInfo.isActive()) {
mInstallingTask = new InstallingAsyncTask();
mInstallingTask.execute();
} else {
// we will receive a broadcast when the install is finished
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
}
}
}
启动InstallingAsyncTask异步任务
/**
* Send the package to the package installer and then register a event result observer that
* will call {@link #launchFinishBasedOnResult(int, int, String)}
*/
private final class InstallingAsyncTask extends AsyncTask<Void, Void,
PackageInstaller.Session> {
volatile boolean isDone;
@Override
protected PackageInstaller.Session doInBackground(Void... params) {
PackageInstaller.Session session;
try {
//openSession就是获取onCreate创建的PackageInstaller.Session,并将PackageInstallerSession赋给当前session
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
} catch (IOException e) {
return null;
}
session.setStagingProgress(0);
//将apk文件写到session中
try {
File file = new File(mPackageURI.getPath());
try (InputStream in = new FileInputStream(file)) {
long sizeBytes = file.length();
try (OutputStream out = session
.openWrite("PackageInstaller", 0, sizeBytes)) {
byte[] buffer = new byte[1024 * 1024];
while (true) {
int numRead = in.read(buffer);
if (numRead == -1) {
session.fsync(out);
break;
}
if (isCancelled()) {
session.close();
break;
}
out.write(buffer, 0, numRead);
if (sizeBytes > 0) {
float fraction = ((float) numRead / (float) sizeBytes);
session.addProgress(fraction);
}
}
}
}
return session;
} catch (IOException | SecurityException e) {
Log.e(LOG_TAG, "Could not write package", e);
session.close();
return null;
} finally {
synchronized (this) {
isDone = true;
notifyAll();
}
}
}
@Override
protected void onPostExecute(PackageInstaller.Session session) {
if (session != null) {
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntent.setPackage(getPackageName());
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
//session commit
session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
getPackageManager().getPackageInstaller().abandonSession(mSessionId);
if (!isCancelled()) {
launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
}
}
}
}
InstallInstalling主要干三件事情:
- 创建与PackageManagerService进程间通信的PackageInstallerSession;
- 将apk通过io流写入到PackageInstallerSession中;
- 调用PackageInstallerSession的commit方法,将apk交给PackageManagerService来执行安装。
六、PackageInstallerSession
session.commit最终是PackageInstallerSession执行commit
frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
...
dispatchStreamValidateAndCommit();
}
private void dispatchStreamValidateAndCommit() {
mHandler.obtainMessage(MSG_STREAM_VALIDATE_AND_COMMIT).sendToTarget();
}
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_STREAM_VALIDATE_AND_COMMIT:
handleStreamValidateAndCommit();
break;
case MSG_INSTALL:
handleInstall();
break;
case MSG_ON_PACKAGE_INSTALLED:
final SomeArgs args = (SomeArgs) msg.obj;
final String packageName = (String) args.arg1;
final String message = (String) args.arg2;
final Bundle extras = (Bundle) args.arg3;
final IntentSender statusReceiver = (IntentSender) args.arg4;
final int returnCode = args.argi1;
args.recycle();
sendOnPackageInstalled(mContext, statusReceiver, sessionId,
isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(), userId,
packageName, returnCode, message, extras);
break;
case MSG_SESSION_VERIFICATION_FAILURE:
final int error = msg.arg1;
final String detailMessage = (String) msg.obj;
onSessionVerificationFailure(error, detailMessage);
break;
}
return true;
}
};
private void handleStreamValidateAndCommit() {
...
mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
}
private void handleInstall() {
...
synchronized (mLock) {
installNonStagedLocked(childSessions);
}
...
}
private void installNonStagedLocked(List<PackageInstallerSession> childSessions)
throws PackageManagerException {
final PackageManagerService.ActiveInstallSession installingSession =
makeSessionActiveLocked();
if (installingSession == null) {
return;
}
if (isMultiPackage()) {
List<PackageManagerService.ActiveInstallSession> installingChildSessions =
new ArrayList<>(childSessions.size());
boolean success = true;
PackageManagerException failure = null;
for (int i = 0; i < childSessions.size(); ++i) {
final PackageInstallerSession session = childSessions.get(i);
try {
final PackageManagerService.ActiveInstallSession installingChildSession =
session.makeSessionActiveLocked();
if (installingChildSession != null) {
installingChildSessions.add(installingChildSession);
}
} catch (PackageManagerException e) {
failure = e;
success = false;
}
}
if (!success) {
sendOnPackageInstalled(mContext, mRemoteStatusReceiver, sessionId,
isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(), userId, null,
failure.error, failure.getLocalizedMessage(), null);
return;
}
mPm.installStage(installingChildSessions);
} else {
mPm.installStage(installingSession);
}
}
这里mPm是PackageManagerService,通过installStage来执行安装。
这里补充一个分支:
private PackageManagerService.ActiveInstallSession makeSessionActiveLocked()
throws PackageManagerException {
if (mRelinquished) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Session relinquished");
}
if (mDestroyed) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
}
if (!mSealed) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
}
final IPackageInstallObserver2 localObserver;
if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
localObserver = null;
} else {
if (!params.isMultiPackage) {
Preconditions.checkNotNull(mPackageName);
Preconditions.checkNotNull(mSigningDetails);
Preconditions.checkNotNull(mResolvedBaseFile);
if (needToAskForPermissionsLocked()) {
// User needs to confirm installation;
// give installer an intent they can use to involve
// user.
// 如果权限校验不满足,则走PackageInstallObserverAdapter回调
final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
intent.setPackage(mPm.getPackageInstallerPackageName());
intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
try {
mRemoteObserver.onUserActionRequired(intent);
} catch (RemoteException ignored) {
}
// Commit was keeping session marked as active until now; release
// that extra refcount so session appears idle.
closeInternal(false);
return null;
}
...
return new PackageManagerService.ActiveInstallSession(mPackageName, stageDir,
localObserver, params, mInstallerPackageName, mInstallerUid, user,
mSigningDetails);
}
这里needToAskForPermissionsLocked()会校验系统权限:
private boolean needToAskForPermissionsLocked() {
if (mPermissionsManuallyAccepted) {
return false;
}
final boolean isInstallPermissionGranted =
(mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES,
mInstallerUid) == PackageManager.PERMISSION_GRANTED);
final boolean isSelfUpdatePermissionGranted =
(mPm.checkUidPermission(android.Manifest.permission.INSTALL_SELF_UPDATES,
mInstallerUid) == PackageManager.PERMISSION_GRANTED);
final boolean isUpdatePermissionGranted =
(mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGE_UPDATES,
mInstallerUid) == PackageManager.PERMISSION_GRANTED);
final int targetPackageUid = mPm.getPackageUid(mPackageName, 0, userId);
final boolean isPermissionGranted = isInstallPermissionGranted
|| (isUpdatePermissionGranted && targetPackageUid != -1)
|| (isSelfUpdatePermissionGranted && targetPackageUid == mInstallerUid);
final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID);
final boolean isInstallerSystem = (mInstallerUid == Process.SYSTEM_UID);
final boolean forcePermissionPrompt =
(params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
// Device owners and affiliated profile owners are allowed to silently install packages, so
// the permission check is waived if the installer is the device owner.
return forcePermissionPrompt || !(isPermissionGranted || isInstallerRoot
|| isInstallerSystem || isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked());
}
如果权限校验不满足,则走PackageInstallObserverAdapter回调
final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
intent.setPackage(mPm.getPackageInstallerPackageName());
intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
mRemoteObserver.onUserActionRequired(intent);
这里会拉起session.commit(pendingIntent.getIntentSender());中对应的组件的回调
static class PackageInstallObserverAdapter extends PackageInstallObserver {
999 private final Context mContext;
1000 private final IntentSender mTarget;
1001 private final int mSessionId;
1002 private final boolean mShowNotification;
1003 private final int mUserId;
1004
1005 public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId,
1006 boolean showNotification, int userId) {
1007 mContext = context;
1008 mTarget = target;
1009 mSessionId = sessionId;
1010 mShowNotification = showNotification;
1011 mUserId = userId;
1012 }
1013
1014 @Override
1015 public void onUserActionRequired(Intent intent) {
1016 final Intent fillIn = new Intent();
1017 fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
1018 fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
1019 PackageInstaller.STATUS_PENDING_USER_ACTION);
1020 fillIn.putExtra(Intent.EXTRA_INTENT, intent);
1021 try {
1022 mTarget.sendIntent(mContext, 0, fillIn, null, null);
1023 } catch (SendIntentException ignored) {
1024 }
1025 }
三方应用可以通过session install这种方式拉起PackageInstaller安装。
七、总结
整体流程时序:
不同阶段的任务梳理:
1)InstallStart
主要是根据不同的uri scheme执行不同页面的跳转:
跳转页面规则:
action 是 sessioninstall:直接跳转PackageInstallerActivity;
action 不是 sessioninstall:
- uri为content scheme跳转 InstallStaging
- uri为pacakge scheme跳转 PackageInstallerActivity
- uri为其他scheme 这里应该是指file,则结束安装
2)InstallStaging
通过三方app提供的content路径,将待安装的apk copy一份到当前installer data/data对应的目录下,然后将uri scheme从content转为file。
3)DeleteStagedFileOnResult
跳转PackageInstallerActivity,然后startActivity回调之后删除copy的apk文件。
4)PackageInstallerActivity
Uri解析和安装前权限检查(用户权限检查、安装来源权限检查)。
5)InstallInstalling
建立与PackageManagerService进程间通信的通道,将apk传递过去
- 创建与PackageManagerService进程间通信的PackageInstallerSession;
- 将apk通过io流写入到PackageInstallerSession中;
- 调用PackageInstallerSession的commit方法,将apk交给PackageManagerService来执行安装。
6)PackageInstallerSession
触发PackageManagerService对apk进行安装。
本篇文章简单梳理了原生的PackageInstaller的中转安装流程,目前国内厂商基于原生PackageInstaller都进行了深度定制,且策略各不相同,与原生差异还是相当大的。
八、厂商定制研究
- huawei: 基于原生PackageInstaller改的,整体页面转换与原生高度一致,对不同页面的逻辑有自己的定制;
- vivo:在9.0及其以下的低版本中,新加了一个VivoPackageInstallerActivity,定制逻辑在这个activity中,主流程也基本与原生保持一致;
- xiaomi:不是基于原生PackageInstaller改的,逻辑集中在PackageInstallerAcitivty中;
- oppo:不是基于原生PackageInstaller改的,逻辑集中在OppoPackageInstallerActivity中;