尝试使用 PackageInstaller 和多包方法安装多个 APK(非拆分)时遇到问题。
下面是安装包的代码:
private void installPackages(List<String> apkFilePaths) {
Context context = (Context) this;
int parentSessionId = 0;
PackageInstaller.Session parentSession = null;
try {
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
parentParams.setMultiPackage();
parentSessionId = packageInstaller.createSession(parentParams);
Log.d(TAG, "installPackages: created parent session id " + parentSessionId);
parentSession = packageInstaller.openSession(parentSessionId);
for (String filepath : apkFilePaths) {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
int sessionId = packageInstaller.createSession(params);
Log.d(TAG, "installPackages: created child session id " + sessionId);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
File apkfile = new File(filepath);
addApkToInstallSession(apkfile, session);
parentSession.addChildSessionId(sessionId);
}
// Commit the session (this will start the installation workflow).
Log.d(TAG, "installPackages: committing session with ID " + parentSessionId);
parentSession.commit(createIntentSender(context, this));
Log.d(TAG, "installPackages: session committed");
} catch (IOException e) {
throw new RuntimeException("Couldn't install packages", e);
} catch (RuntimeException e) {
if (parentSession != null) {
Log.d(TAG, "installPackages: abandoning parent session with id " + parentSessionId);
for (int id : parentSession.getChildSessionIds()) {
Log.d(TAG, "installPackages: abandoning child session with id " + id);
}
parentSession.abandon();
}
log_error("runtime error",e.getMessage(),e.getCause());
}
}
private void addApkToInstallSession(File apkFile, PackageInstaller.Session session)
throws IOException {
// It's recommended to pass the file size to openWrite(). Otherwise installation may fail
// if the disk is almost full.
try (OutputStream packageInSession = session.openWrite("package", 0, apkFile.length());
InputStream is = new FileInputStream(apkFile) ) {
byte[] buffer = new byte[16384];
int n;
while ((n = is.read(buffer)) >= 0) {
packageInSession.write(buffer, 0, n);
}
session.fsync(packageInSession); // this may not be necessary
}
}
我使用以下方法监听事件onNewIntent
并运行以下方法来处理安装操作:
private void processInstallStatus(Bundle extras) {
int status = extras.getInt(PackageInstaller.EXTRA_STATUS);
String message = extras.getString(PackageInstaller.EXTRA_STATUS_MESSAGE);
switch (status) {
case PackageInstaller.STATUS_PENDING_USER_ACTION:
// This test app isn't privileged, so the user has to confirm the install.
Intent confirmIntent = (Intent) extras.get(Intent.EXTRA_INTENT);
this.startActivity(confirmIntent);
break;
case PackageInstaller.STATUS_SUCCESS:
log_success(status);
break;
case PackageInstaller.STATUS_FAILURE:
case PackageInstaller.STATUS_FAILURE_ABORTED:
case PackageInstaller.STATUS_FAILURE_BLOCKED:
case PackageInstaller.STATUS_FAILURE_CONFLICT:
case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
case PackageInstaller.STATUS_FAILURE_INVALID:
case PackageInstaller.STATUS_FAILURE_STORAGE:
log_error("runtime error", "processInstallStatus: Install failed! " + status + ", " + message, null);
break;
default:
log_error("unknown","Unrecognized status received from installer: " + status, null);
}
}
这是调用时生成的日志installPackages(...)
:
2021-11-24 14:29:04.131 27663-28000/com.mycompany.installapksexample D/InstallActivity: installPackages: created parent session id 590264201
2021-11-24 14:29:04.179 27663-28000/com.mycompany.installapksexample D/InstallActivity: installPackages: created child session id 1544911069
2021-11-24 14:29:05.513 27663-28000/com.mycompany.installapksexample D/InstallActivity: installPackages: created child session id 1170663918
2021-11-24 14:29:06.806 27663-28000/com.mycompany.installapksexample D/InstallActivity: installPackages: committing session with ID 590264201
2021-11-24 14:29:06.955 27663-28000/com.mycompany.installapksexample D/InstallActivity: installPackages: session committed
2021-11-24 14:29:07.192 27663-27663/com.mycompany.installapksexample D/InstallActivity: onNewIntent: received new intent with action com.mycompany.installapksexample.SESSION_API_PACKAGE_INSTALL
2021-11-24 14:29:07.192 27663-27663/com.mycompany.installapksexample E/InstallActivity: log_error: type: runtime error msg: processInstallStatus: Install failed! 1, INSTALL_FAILED_INTERNAL_ERROR: No child sessions found! details: null
我已经通过调用验证parentSession
确实包含子会话parentSession.getChildSessionIds()
。我还检查了 AOSP 项目,似乎非系统应用程序能够在任何给定时间启动最多 50 个会话的多包安装会话:
/** Upper bound on number of active sessions for a UID without INSTALL_PACKAGES permission in manifest */
private static final long MAX_ACTIVE_SESSIONS_NO_PERMISSION = 50;
环境:
- 在 Android 11 设备上进行测试
- 项目 API 目标 29
类似功能的快速 Adb 测试
adb
作为一个快速测试,我能够使用以下命令实现多个非拆分 APK 的安装:
adb install-multi-package apks/work/app_one.apk apks/work/app_two.apk
Created parent session ID 917960054.
Created child session ID 2053061687.
Created child session ID 1924985944.
Success
对此问题的任何帮助或指导将不胜感激。
附加信息
我也在AOSP中做了一些挖掘,找到了错误的来源:
MultiPackageInstallParams(InstallParams parent, List<InstallParams> childParams)
throws PackageManagerException {
super(parent.getUser());
if (childParams.size() == 0) {
throw new PackageManagerException("No child sessions found!");
}
//...
}
为空时会出现此错误List<InstallParams> childParams
,在调用方法中如果makeInstallParams()
返回null会出现此错误,如下所示:
private void installNonStaged()
throws PackageManagerException {
final PackageManagerService.InstallParams installingSession = makeInstallParams();
if (installingSession == null) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Session should contain at least one apk session for installation");
}
if (isMultiPackage()) {
final List<PackageInstallerSession> childSessions;
synchronized (mLock) {
childSessions = getChildSessionsLocked();
}
List<PackageManagerService.InstallParams> 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.InstallParams installingChildSession =
session.makeInstallParams();
if (installingChildSession != null) {
installingChildSessions.add(installingChildSession);
}
} catch (PackageManagerException e) {
failure = e;
success = false;
}
}
if (!success) {
final IntentSender statusReceiver;
synchronized (mLock) {
statusReceiver = mRemoteStatusReceiver;
}
sendOnPackageInstalled(mContext, statusReceiver, sessionId,
isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId, null,
failure.error, failure.getLocalizedMessage(), null);
return;
}
mPm.installStage(installingSession, installingChildSessions);
} else {
mPm.installStage(installingSession);
}
}
但是,我无法追踪该方法在什么条件下makeInstallParams()
返回 null。该方法似乎没有抛出 PackageManagerException,而是会触发我的异常处理程序。此外,我的会话包含一个 APK 而不是 Apex。这是我找到的代码makeInstallParams()
和构造函数InstallParams
:
@Nullable
private PackageManagerService.InstallParams makeInstallParams()
throws PackageManagerException {
synchronized (mLock) {
if (mDestroyed) {
throw new PackageManagerException(
INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
}
if (!mSealed) {
throw new PackageManagerException(
INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
}
}
// Do not try to install staged apex session. Parent session will have at least one apk
// session.
if (!isMultiPackage() && isApexSession() && params.isStaged) {
sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED,
"Apex package should have been installed by apexd", null);
return null;
}
final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
@Override
public void onUserActionRequired(Intent intent) {
throw new IllegalStateException();
}
@Override
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) {
if (isStaged()) {
sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);
} else {
// We've reached point of no return; call into PMS to install the stage.
// Regardless of success or failure we always destroy session.
destroyInternal();
dispatchSessionFinished(returnCode, msg, extras);
}
}
};
final UserHandle user;
if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
user = UserHandle.ALL;
} else {
user = new UserHandle(userId);
}
if (params.isStaged) {
params.installFlags |= INSTALL_STAGED;
}
if (!isMultiPackage() && !isApexSession()) {
synchronized (mLock) {
// This shouldn't be null, but have this code path just in case.
if (mPackageLite == null) {
Slog.wtf(TAG, "Session: " + sessionId + ". Don't have a valid PackageLite.");
}
mPackageLite = getOrParsePackageLiteLocked(stageDir, /* flags */ 0);
}
}
synchronized (mLock) {
return mPm.new InstallParams(stageDir, localObserver, params, mInstallSource, user,
mSigningDetails, mInstallerUid, mPackageLite);
}
}
构造函数InstallParams
:
InstallParams(File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, InstallSource installSource,
UserHandle user, SigningDetails signingDetails, int installerUid,
PackageLite packageLite) {
super(user);
origin = OriginInfo.fromStagedFile(stagedDir);
move = null;
installReason = fixUpInstallReason(
installSource.installerPackageName, installerUid, sessionParams.installReason);
mInstallScenario = sessionParams.installScenario;
this.observer = observer;
installFlags = sessionParams.installFlags;
this.installSource = installSource;
volumeUuid = sessionParams.volumeUuid;
packageAbiOverride = sessionParams.abiOverride;
grantedRuntimePermissions = sessionParams.grantedRuntimePermissions;
whitelistedRestrictedPermissions = sessionParams.whitelistedRestrictedPermissions;
autoRevokePermissionsMode = sessionParams.autoRevokePermissionsMode;
this.signingDetails = signingDetails;
forceQueryableOverride = sessionParams.forceQueryableOverride;
mDataLoaderType = (sessionParams.dataLoaderParams != null)
? sessionParams.dataLoaderParams.getType() : DataLoaderType.NONE;
requiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
mPackageLite = packageLite;
}
更新:
我能够使用命令转储活动安装会话adb shell dumpsys install
Session 590264201:
userId=0 mOriginalInstallerUid=10314 installerPackageName=com.mycompany.installapksexample installInitiatingPackage
Name=com.mycompany.installapksexample installOriginatingPackageName=null mInstallerUid=10314 createdMillis=163776414413
0 updatedMillis=1637764144130 stageDir=null stageCid=null
mode=1 installFlags=0x400002 installLocation=1 sizeBytes=-1 appPackageName=null appIcon=false appLabel=null origin
atingUri=null originatingUid=-1 referrerUri=null abiOverride=null volumeUuid=null grantedRuntimePermissions=null white
listedRestrictedPermissions=null autoRevokePermissions=3 installerPackageName=null isMultiPackage=true isStaged=false
forceQueryable=false requiredInstalledVersionCode=-1 dataLoaderParams=null rollbackDataPolicy=0
mClientProgress=1.0 mProgress=0.8 mCommitted=true mSealed=true mPermissionsManuallyAccepted=false mRelinquished=tr
ue mDestroyed=true mFds=0 mBridges=0 mFinalStatus=-110 mFinalMessage=No child sessions found! params.isMultiPackage=tr
ue params.isStaged=false mParentSessionId=-1 mChildSessionIds={1170663918=0, 1544911069=0} mStagedSessionApplied=false
mStagedSessionFailed=false mStagedSessionReady=false mStagedSessionErrorCode=0 mStagedSessionErrorMessage=
Session 1170663918:
userId=0 mOriginalInstallerUid=10314 installerPackageName=com.mycompany.installapksexample
installInitiatingPackageName=com.mycompany.installapksexample installOriginatingPackageName=null
mInstallerUid=10314 createdMillis=1637764145512 updatedMillis=1637764145512 stageDir=/data/app/vmdl1170663918.tmp
stageCid=null
mode=1 installFlags=0x400012 installLocation=1 sizeBytes=-1 appPackageName=null appIcon=false appLabel=null
originatingUri=null originatingUid=-1 referrerUri=null abiOverride=null volumeUuid=null
grantedRuntimePermissions=null whitelistedRestrictedPermissions=null autoRevokePermissions=3
installerPackageName=null isMultiPackage=false isStaged=false forceQueryable=false requiredInstalledVersionCode=-1
dataLoaderParams=null rollbackDataPolicy=0
mClientProgress=1.0 mProgress=0.8 mCommitted=true mSealed=true mPermissionsManuallyAccepted=false
mRelinquished=false mDestroyed=false mFds=0 mBridges=1 mFinalStatus=0 mFinalMessage=null
params.isMultiPackage=false params.isStaged=false mParentSessionId=590264201 mChildSessionIds={}
mStagedSessionApplied=false mStagedSessionFailed=false mStagedSessionReady=false mStagedSessionErrorCode=0
mStagedSessionErrorMessage=
Session 1544911069:
userId=0 mOriginalInstallerUid=10314 installerPackageName=com.mycompany.installapksexample
installInitiatingPackageName=com.mycompany.installapksexample installOriginatingPackageName=null
mInstallerUid=10314 createdMillis=1637764144178 updatedMillis=1637764144178 stageDir=/data/app/vmdl1544911069.tmp
stageCid=null
mode=1 installFlags=0x400012 installLocation=1 sizeBytes=-1 appPackageName=null appIcon=false appLabel=null
originatingUri=null originatingUid=-1 referrerUri=null abiOverride=null volumeUuid=null
grantedRuntimePermissions=null whitelistedRestrictedPermissions=null autoRevokePermissions=3
installerPackageName=null isMultiPackage=false isStaged=false forceQueryable=false requiredInstalledVersionCode=-1
dataLoaderParams=null rollbackDataPolicy=0
mClientProgress=1.0 mProgress=0.8 mCommitted=true mSealed=true mPermissionsManuallyAccepted=false
mRelinquished=false mDestroyed=false mFds=0 mBridges=1 mFinalStatus=0 mFinalMessage=null
params.isMultiPackage=false params.isStaged=false mParentSessionId=590264201 mChildSessionIds={}
mStagedSessionApplied=false mStagedSessionFailed=false mStagedSessionReady=false mStagedSessionErrorCode=0
mStagedSessionErrorMessage=
我还发现No child sessions found!
PackageInstaller验证步骤也可能出现错误。但是,在这种情况下,PackageInstaller 还应该触发一个REQUEST_USER_ACTION
意图来验证这些 APK 的安装。