0

尝试使用 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 的安装。

4

0 回答 0