Android 安装过程五 MSG_INSTALL消息的处理 安装
现在马上进入正式的安装流程。
从前面文章 Android 安装过程四 MSG_INSTALL消息的处理 安装之前的验证知道,在验证之后没有什么问题的情况下,会回调onVerificationComplete()方法,它位于PackageInstallerSession类中。
private void onVerificationComplete() {// Staged sessions will be installed later during bootif (isStaged()) {// TODO(b/136257624): Remove this once all verification logic has been transferred out// of StagingManager.mStagingManager.notifyPreRebootVerification_Apk_Complete(mStagedSession);// TODO(b/136257624): We also need to destroy internals for verified staged session,// otherwise file descriptors are never closed for verified staged session until rebootreturn;}install();}……private void install() {try {installNonStaged();} catch (PackageManagerException e) {final String completeMsg = ExceptionUtils.getCompleteMessage(e);onSessionInstallationFailure(e.error, completeMsg);}}
看到不是在缓存下来进行安装的情况下,会继续调用installNonStaged()方法。
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);}}
构造安装参数InstallParams对象
和验证那块的代码有些像,先调用makeInstallParams()构造安装参数InstallParams对象installingSession。暂时忽略多包安装的情况,接着就是到PackageManagerService对象中调用installStage(installingSession)方法,执行安装。
先看一下安装参数的构成方法makeInstallParams():
@Nullableprivate 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() {@Overridepublic void onUserActionRequired(Intent intent) {throw new IllegalStateException();}@Overridepublic 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);}}
首先检查Session对象的状态,如果是mDestroyed或!mSealed,都会抛出PackageManagerException。
接着会生成一个回调对象localObserver。等待后面安装完毕之后,会调用它的onPackageInstalled回调接口。
如果不是多包安装和不是apex安装的情况下,如果成员变量为null,会调用getOrParsePackageLiteLocked(stageDir, /* flags */ 0)再次生成它。
最后就是调用InstallParams 的构造函数来生成InstallParams 对象了。
进入PackageManagerService中执行安装
PackageManagerService类中的installStage(InstallParams params)如下:
void installStage(InstallParams params) {final Message msg = mHandler.obtainMessage(INIT_COPY);params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));msg.obj = params;Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installStage",System.identityHashCode(msg.obj));Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",System.identityHashCode(msg.obj));mHandler.sendMessage(msg);}
看下INIT_COPY消息的处理,
void doHandleMessage(Message msg) {switch (msg.what) {case INIT_COPY: {HandlerParams params = (HandlerParams) msg.obj;if (params != null) {if (DEBUG_INSTALL) Slog.i(TAG, "init_copy: " + params);Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",System.identityHashCode(params));Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");params.startCopy();Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}break;}…………
msg.obj就是前面构造的InstallParams 对象,这里调用它的startCopy()方法。InstallParams 是继承自HandlerParams类,它的startCopy(),就是调用handleStartCopy()和handleReturnCode()。这俩方法是实现在InstallParams 类中的。
InstallParams 类的handleStartCopy()
先看InstallParams 类的handleStartCopy():
public void handleStartCopy() {if ((installFlags & PackageManager.INSTALL_APEX) != 0) {mRet = INSTALL_SUCCEEDED;return;}PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,mPackageLite, origin.resolvedPath, installFlags, packageAbiOverride);// For staged session, there is a delay between its verification and install. Device// state can change within this delay and hence we need to re-verify certain conditions.boolean isStaged = (installFlags & INSTALL_STAGED) != 0;if (isStaged) {mRet = verifyReplacingVersionCode(pkgLite, requiredInstalledVersionCode, installFlags);if (mRet != INSTALL_SUCCEEDED) {return;}}mRet = overrideInstallLocation(pkgLite);}
通过PackageManagerServiceUtils.getMinimalPackageInfo()得到包信息对象pkgLite。接着就调用overrideInstallLocation(pkgLite)重写安装位置,主要是可能改变安装标识。
这两个方法咱们主要关注一下关于安装位置方面的东西。
先看下PackageManagerServiceUtils.getMinimalPackageInfo()方法:
public static PackageInfoLite getMinimalPackageInfo(Context context, PackageLite pkg,String packagePath, int flags, String abiOverride) {final PackageInfoLite ret = new PackageInfoLite();if (packagePath == null || pkg == null) {Slog.i(TAG, "Invalid package file " + packagePath);ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;return ret;}final File packageFile = new File(packagePath);final long sizeBytes;try {sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);} catch (IOException e) {if (!packageFile.exists()) {ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;} else {ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;}return ret;}final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,pkg.getPackageName(), pkg.getInstallLocation(), sizeBytes, flags);ret.packageName = pkg.getPackageName();ret.splitNames = pkg.getSplitNames();ret.versionCode = pkg.getVersionCode();ret.versionCodeMajor = pkg.getVersionCodeMajor();ret.baseRevisionCode = pkg.getBaseRevisionCode();ret.splitRevisionCodes = pkg.getSplitRevisionCodes();ret.installLocation = pkg.getInstallLocation();ret.verifiers = pkg.getVerifiers();ret.recommendedInstallLocation = recommendedInstallLocation;ret.multiArch = pkg.isMultiArch();ret.debuggable = pkg.isDebuggable();return ret;}
这里需要关注的是PackageInfoLite对象的recommendedInstallLocation和installLocation成员变量。
这里看到recommendedInstallLocation的意思是推荐的安装位置,如果结果如果是以PackageHelper.RECOMMEND_FAILED 开头的,代表失败,出问题了。
像这里计算安装大小sizeBytes时,出现异常时,RECOMMEND_FAILED_INVALID_URI代表文件不存在,RECOMMEND_FAILED_INVALID_APK代表不是有效的APK文件。
installLocation是来自安装包的解析属性,它是配置在Manifest中的"installLocation"属性,如果没有配置,默认为PackageInfo.INSTALL_LOCATION_UNSPECIFIED。
推荐安装位置,主要是通过PackageHelper.resolveInstallLocation()方法来得到。再看一下它:
public static int resolveInstallLocation(Context context, SessionParams params)throws IOException {ApplicationInfo existingInfo = null;try {existingInfo = context.getPackageManager().getApplicationInfo(params.appPackageName,PackageManager.MATCH_ANY_USER);} catch (NameNotFoundException ignored) {}final int prefer;final boolean checkBoth;boolean ephemeral = false;if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {prefer = RECOMMEND_INSTALL_INTERNAL;ephemeral = true;checkBoth = false;} else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {prefer = RECOMMEND_INSTALL_INTERNAL;checkBoth = false;} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {prefer = RECOMMEND_INSTALL_INTERNAL;checkBoth = false;} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {prefer = RECOMMEND_INSTALL_EXTERNAL;checkBoth = true;} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {// When app is already installed, prefer same mediumif (existingInfo != null) {// TODO: distinguish if this is external ASECif ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {prefer = RECOMMEND_INSTALL_EXTERNAL;} else {prefer = RECOMMEND_INSTALL_INTERNAL;}} else {prefer = RECOMMEND_INSTALL_INTERNAL;}checkBoth = true;} else {prefer = RECOMMEND_INSTALL_INTERNAL;checkBoth = false;}boolean fitsOnInternal = false;if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {fitsOnInternal = fitsOnInternal(context, params);}boolean fitsOnExternal = false;if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {fitsOnExternal = fitsOnExternal(context, params);}if (prefer == RECOMMEND_INSTALL_INTERNAL) {// The ephemeral case will either fit and return EPHEMERAL, or will not fit// and will fall through to return INSUFFICIENT_STORAGEif (fitsOnInternal) {return (ephemeral)? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL: PackageHelper.RECOMMEND_INSTALL_INTERNAL;}} else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {if (fitsOnExternal) {return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;}}if (checkBoth) {if (fitsOnInternal) {return PackageHelper.RECOMMEND_INSTALL_INTERNAL;} else if (fitsOnExternal) {return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;}}return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;}
从这里可以看到推荐安装的值。
这里定义变量prefer是比较推荐安装的地方。checkBoth是检查内部存储和外部存储空间是否满足。
优先判断的是标识installFlags。如果它不满足,会去判断installLocation中的值。
如果明确推荐位置,checkBoth为false,它之后不会内部和外部存储空间两个都做判断。
像满足安装位置为INSTALL_LOCATION_PREFER_EXTERNAL或PackageInfo.INSTALL_LOCATION_AUTO的情况下,将checkBoth = true。像INSTALL_LOCATION_INTERNAL_ONLY明确就在内部安装,就将checkBoth = false。
fitsOnInternal()和fitsOnExternal()两个方法就是去判断内部空间和外部空间是否满足安装大小。如果满足,返回true。
如果推荐内部安装,并且内部大小充足,就返回RECOMMEND_INSTALL_INTERNAL(Instant安装返回RECOMMEND_INSTALL_EPHEMERAL)。推荐外部安装,外部空间满足,就返回RECOMMEND_INSTALL_EXTERNAL。
如果前面两个都不是,并且满足需要检查两个空间的情况,如果空间足够,优先内部安装,返回RECOMMEND_INSTALL_INTERNAL,再外部安装。
如果以上都不满足,返回RECOMMEND_FAILED_INSUFFICIENT_STORAGE,代表空间不足。
handleStartCopy()接下来执行overrideInstallLocation(pkgLite),看一下它:
private int overrideInstallLocation(PackageInfoLite pkgLite) {final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;if (DEBUG_INSTANT && ephemeral) {Slog.v(TAG, "pkgLite for install: " + pkgLite);}if (origin.staged) {// If we're already staged, we've firmly committed to an install locationif (origin.file != null) {installFlags |= PackageManager.INSTALL_INTERNAL;} else {throw new IllegalStateException("Invalid stage location");}} else if (pkgLite.recommendedInstallLocation== PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {/** If we are not staged and have too little free space, try to free cache* before giving up.*/// TODO: focus freeing disk space on the target devicefinal StorageManager storage = StorageManager.from(mContext);final long lowThreshold = storage.getStorageLowBytes(Environment.getDataDirectory());final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(origin.resolvedPath, packageAbiOverride);if (sizeBytes >= 0) {try {mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,mPackageLite, origin.resolvedPath, installFlags,packageAbiOverride);} catch (InstallerException e) {Slog.w(TAG, "Failed to free cache", e);}}/** The cache free must have deleted the file we downloaded to install.** TODO: fix the "freeCache" call to not delete the file we care about.*/if (pkgLite.recommendedInstallLocation== PackageHelper.RECOMMEND_FAILED_INVALID_URI) {pkgLite.recommendedInstallLocation= PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;}}int ret = INSTALL_SUCCEEDED;int loc = pkgLite.recommendedInstallLocation;if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;} else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;} else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {ret = PackageManager.INSTALL_FAILED_INVALID_APK;} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {ret = PackageManager.INSTALL_FAILED_INVALID_URI;} else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;} else {// Override with defaults if needed.loc = installLocationPolicy(pkgLite);final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;if (!onInt) {// Override install location with flagsif (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {// Set the flag to install on external media.installFlags &= ~PackageManager.INSTALL_INTERNAL;} else {// Make sure the flag for installing on external// media is unsetinstallFlags |= PackageManager.INSTALL_INTERNAL;}}}return ret;}
可以看到的是,如果pkgLite的recommendedInstallLocation为RECOMMEND_FAILED_INSUFFICIENT_STORAGE时,从上面我们知道,这是由于存储空间不足了。在这种情况下,它会尝试去释放一些空间,调用的是mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0)。在释放一些空间之后,接着再去执行一遍PackageManagerServiceUtils.getMinimalPackageInfo(),看看能不能后续得到满足的结果。
接下来,判断pkgLite.recommendedInstallLocation的值,如果是以PackageManager.INSTALL_FAILED开头的结果,都直接设置返回结果。
如果不是失败的结果,会接着调用installLocationPolicy(pkgLite)。该方法是又去根据该应用之前的安装包信息来再次做判断。例如,安装包是系统应用包,则返回PackageHelper.RECOMMEND_INSTALL_INTERNAL。在配置文件没有明确指定位置的情况下,安装包在外部存储空间,则返回PackageHelper.RECOMMEND_INSTALL_EXTERNAL。如果没有之前的安装包,则返回它自己的推荐值。
如果installFlags里面有INSTALL_INTERNAL标识,则结果返回INSTALL_SUCCEEDED。如果没有INSTALL_INTERNAL标识,假如loc为不等于PackageHelper.RECOMMEND_INSTALL_EXTERNAL,则将installFlags里加上INSTALL_INTERNAL标识。
InstallParams 类的handleReturnCode()
它执行processPendingInstall()方法:
private void processPendingInstall() {InstallArgs args = createInstallArgs(this);if (mRet == PackageManager.INSTALL_SUCCEEDED) {mRet = args.copyApk();}if (mRet == PackageManager.INSTALL_SUCCEEDED) {F2fsUtils.releaseCompressedBlocks(mContext.getContentResolver(), new File(args.getCodePath()));}if (mParentInstallParams != null) {mParentInstallParams.tryProcessInstallRequest(args, mRet);} else {PackageInstalledInfo res = createPackageInstalledInfo(mRet);processInstallRequestsAsync(res.returnCode == PackageManager.INSTALL_SUCCEEDED,Collections.singletonList(new InstallRequest(args, res)));}}}
首先通过createInstallArgs(this)生成一个InstallArgs 对象。将InstallParams 里的信息,转到InstallArgs 类对象中。在这里,对于我们的例子,它是FileInstallArgs对象。
接着mRet == PackageManager.INSTALL_SUCCEEDED的情况下,这里会执行FileInstallArgs对象的copyApk()。FileInstallArgs对象的copyApk(),如果文件还没有缓存到对应的文件夹下面,需要执行复制。如果文件已经复制过,则不需要再次复制,直接返回INSTALL_SUCCEEDED结果。像例子中的,目前已经复制过,所以直接返回INSTALL_SUCCEEDED结果。
下面mRet ==PackageManager.INSTALL_SUCCEEDED的情况下,如果文件系统是F2Fs,启动压缩情况下,需要先释放压缩的数据块,以备下面读取使用,所以调用F2fsUtils.releaseCompressedBlocks()方法。
如果mParentInstallParams不为null,它是一个MultiPackageInstallParams对象,用来在多包安装时使用,目前的InstallParams对象是多包安装中的一个。需要调用MultiPackageInstallParams类的tryProcessInstallRequest()方法。
如果是单文件安装包,则先生成一个PackageInstalledInfo对象res。然后将InstallArgs 对象和res封装到InstallRequest对象中。最后是调用processInstallRequestsAsync()方法来执行后续安装。
处理安装请求
看下processInstallRequestsAsync()代码:
// Queue up an async operation since the package installation may take a little while.private void processInstallRequestsAsync(boolean success,List<InstallRequest> installRequests) {mHandler.post(() -> {List<InstallRequest> apexInstallRequests = new ArrayList<>();List<InstallRequest> apkInstallRequests = new ArrayList<>();for (InstallRequest request : installRequests) {if ((request.args.installFlags & PackageManager.INSTALL_APEX) != 0) {apexInstallRequests.add(request);} else {apkInstallRequests.add(request);}}// Note: supporting multi package install of both APEXes and APKs might requir some// thinking to ensure atomicity of the install.if (!apexInstallRequests.isEmpty() && !apkInstallRequests.isEmpty()) {// This should've been caught at the validation step, but for some reason wasn't.throw new IllegalStateException("Attempted to do a multi package install of both APEXes and APKs");}if (!apexInstallRequests.isEmpty()) {if (success) {// Since installApexPackages requires talking to external service (apexd), we// schedule to run it async. Once it finishes, it will resume the install.Thread t = new Thread(() -> installApexPackagesTraced(apexInstallRequests),"installApexPackages");t.start();} else {// Non-staged APEX installation failed somewhere before// processInstallRequestAsync. In that case just notify the observer about the// failure.InstallRequest request = apexInstallRequests.get(0);notifyInstallObserver(request.installResult, request.args.observer);}return;}if (success) {for (InstallRequest request : apkInstallRequests) {request.args.doPreInstall(request.installResult.returnCode);}synchronized (mInstallLock) {installPackagesTracedLI(apkInstallRequests);}for (InstallRequest request : apkInstallRequests) {request.args.doPostInstall(request.installResult.returnCode, request.installResult.uid);}}for (InstallRequest request : apkInstallRequests) {restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,new PostInstallData(request.args, request.installResult, null));}});}
参数installRequests是List格式,因为多包安装,也是调用的它。
显然是将操作封装成消息了,mHandler发送的消息是在叫"PackageManager"的线程中执行的。
这里有Apex安装方式的处理。可以看到,多包装时,是不能既用Apex,又用Apk安装方式的。咱们这里主要看Apk的安装方式。
在目前没错误的情况下,会调用installPackagesTracedLI(apkInstallRequests)执行安装。在安装之前和之后,都会执行对应InstallArgs对象的doPreInstall和doPostInstall方法。从名字能看出来,是安装之前的操作和安装之后的操作。由于这里是FileInstallArgs类对象,所以执行的是FileInstallArgs类的doPreInstall和doPostInstall方法。FileInstallArgs类的doPreInstall和doPostInstall方法都是根据参数状态,如果不是INSTALL_SUCCEEDED,则执行清理操作。具体看下面。咱们这里按照还没发生错误的情况下,继续往下看。
restoreAndPostInstall()方法在不是升级包的情况下(首次安装属于这种情况),会向备份管理服务要求执行回复数据,当然是在合适的情况下(备份服务可能不会启动,得配置PackageManager.FEATURE_BACKUP Feature,才会启动),才会具体执行。如果它是替换升级的情况,它还可能会向RollbackManager请求回退数据,得配置安装参数中的PackageManager.INSTALL_ENABLE_ROLLBACK或者PackageManager.INSTALL_REQUEST_DOWNGRADE标识。如果这两个都没满足条件,它会做安装之后的工作。在这里它是向"PackageManager"线程发送POST_INSTALL消息。它的处理实现在PackageHandler的handleMessage(Message msg)方法中,在处理POST_INSTALL消息时,会调用handlePackagePostInstall()方法,详见下面。
安装
接下来继续查看installPackagesTracedLI(List requests)的实现:
@GuardedBy("mInstallLock")private void installPackagesTracedLI(List<InstallRequest> requests) {try {Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");installPackagesLI(requests);} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}}
主要是调用installPackagesLI(requests),继续看它的实现:
/*** Installs one or more packages atomically. This operation is broken up into four phases:* <ul>* <li><b>Prepare</b>* <br/>Analyzes any current install state, parses the package and does initial* validation on it.</li>* <li><b>Scan</b>* <br/>Interrogates the parsed packages given the context collected in prepare.</li>* <li><b>Reconcile</b>* <br/>Validates scanned packages in the context of each other and the current system* state to ensure that the install will be successful.* <li><b>Commit</b>* <br/>Commits all scanned packages and updates system state. This is the only place* that system state may be modified in the install flow and all predictable errors* must be determined before this phase.</li>* </ul>** Failure at any phase will result in a full failure to install all packages.*/@GuardedBy("mInstallLock")private void installPackagesLI(List<InstallRequest> requests) {final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size());final Map<String, InstallArgs> installArgs = new ArrayMap<>(requests.size());final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size());final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size());final Map<String, VersionInfo> versionInfos = new ArrayMap<>(requests.size());final Map<String, PackageSetting> lastStaticSharedLibSettings =new ArrayMap<>(requests.size());final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());boolean success = false;try {Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");for (InstallRequest request : requests) {// TODO(b/109941548): remove this once we've pulled everything from it and into// scan, reconcile or commit.final PrepareResult prepareResult;try {Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");prepareResult =preparePackageLI(request.args, request.installResult);} catch (PrepareFailure prepareFailure) {request.installResult.setError(prepareFailure.error,prepareFailure.getMessage());request.installResult.origPackage = prepareFailure.conflictingPackage;request.installResult.origPermission = prepareFailure.conflictingPermission;return;} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}request.installResult.setReturnCode(PackageManager.INSTALL_SUCCEEDED);request.installResult.installerPackageName =request.args.installSource.installerPackageName;final String packageName = prepareResult.packageToScan.getPackageName();prepareResults.put(packageName, prepareResult);installResults.put(packageName, request.installResult);installArgs.put(packageName, request.args);try {final ScanResult result = scanPackageTracedLI(prepareResult.packageToScan, prepareResult.parseFlags,prepareResult.scanFlags, System.currentTimeMillis(),request.args.user, request.args.abiOverride);if (null != preparedScans.put(result.pkgSetting.pkg.getPackageName(), result)) {request.installResult.setError(PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,"Duplicate package " + result.pkgSetting.pkg.getPackageName()+ " in multi-package install request.");return;}createdAppId.put(packageName, optimisticallyRegisterAppId(result));versionInfos.put(result.pkgSetting.pkg.getPackageName(),getSettingsVersionForPackage(result.pkgSetting.pkg));if (result.staticSharedLibraryInfo != null) {final PackageSetting sharedLibLatestVersionSetting =getSharedLibLatestVersionSetting(result);if (sharedLibLatestVersionSetting != null) {lastStaticSharedLibSettings.put(result.pkgSetting.pkg.getPackageName(),sharedLibLatestVersionSetting);}}} catch (PackageManagerException e) {request.installResult.setError("Scanning Failed.", e);return;}}ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,installResults,prepareResults,mSharedLibraries,Collections.unmodifiableMap(mPackages), versionInfos,lastStaticSharedLibSettings);CommitRequest commitRequest = null;synchronized (mLock) {Map<String, ReconciledPackage> reconciledPackages;try {Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");reconciledPackages = reconcilePackagesLocked(reconcileRequest, mSettings.getKeySetManagerService(), mInjector);} catch (ReconcileFailure e) {for (InstallRequest request : requests) {request.installResult.setError("Reconciliation failed...", e);}return;} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}try {Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "commitPackages");commitRequest = new CommitRequest(reconciledPackages,mUserManager.getUserIds());commitPackagesLocked(commitRequest);success = true;} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}}executePostCommitSteps(commitRequest);} finally {if (success) {for (InstallRequest request : requests) {final InstallArgs args = request.args;if (args.mDataLoaderType != DataLoaderType.INCREMENTAL) {continue;}if (args.signingDetails.signatureSchemeVersion != SIGNING_BLOCK_V4) {continue;}// For incremental installs, we bypass the verifier prior to install. Now// that we know the package is valid, send a notice to the verifier with// the root hash of the base.apk.final String baseCodePath = request.installResult.pkg.getBaseApkPath();final String[] splitCodePaths = request.installResult.pkg.getSplitCodePaths();final Uri originUri = Uri.fromFile(args.origin.resolvedFile);final int verificationId = mPendingVerificationToken++;final String rootHashString = PackageManagerServiceUtils.buildVerificationRootHashString(baseCodePath, splitCodePaths);broadcastPackageVerified(verificationId, originUri,PackageManager.VERIFICATION_ALLOW, rootHashString,args.mDataLoaderType, args.getUser());}} else {for (ScanResult result : preparedScans.values()) {if (createdAppId.getOrDefault(result.request.parsedPackage.getPackageName(),false)) {cleanUpAppIdCreation(result);}}// TODO(patb): create a more descriptive reason than unknown in future release// mark all non-failure installs as UNKNOWN so we do not treat them as successfor (InstallRequest request : requests) {if (request.installResult.freezer != null) {request.installResult.freezer.close();}if (request.installResult.returnCode == PackageManager.INSTALL_SUCCEEDED) {request.installResult.returnCode = PackageManager.INSTALL_UNKNOWN;}}}Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}}
参数requests是List类型,是个集合,代表它可以安装1个或者多个应用,每个应用安装对应一个InstallRequest。通过注释可以看到,将安装分成了4个阶段:准备、浏览、协调、提交。当然翻译不一定准确,这里就这样称呼它们。
开始时按照参数requests的数量生成了好几个ArrayMap对象。prepareResults里的是准备阶段返回的结果,preparedScans里面是浏览阶段返回的结果,installArgs则是安装参数,installResults是安装结果,versionInfos里面放的是和存储卷相关的版本信息。lastStaticSharedLibSettings存放的则是最新版本的静态库对应的PackageSetting对象。createdAppId则是存放的对应应用的AppId是否创建。
下面根据请求数组,进行遍历。
preparePackageLI(request.args, request.installResult)是准备阶段的实现,具体见 Android 安装应用-准备阶段。如果准备阶段抛出异常PrepareFailure,将失败信息收集到request.installResult中。然后就直接整个返回,不再向下进行。如果没有抛出异常,将安装成功PackageManager.INSTALL_SUCCEEDED设置到request.installResult的返回代码中。然后收集准备结果,安装结果,安装参数,都是以包名为key。
scanPackageTracedLI()则是浏览阶段的实现,具体见文章 Android 安装应用-浏览阶段。然后将浏览结果对象result收集到集合preparedScans中,也是以包名作为key。继续将appId,存储卷的版本信息添加到createdAppId、versionInfos中。
如果应用是静态库,则得到最新版本的PackageSetting对象sharedLibLatestVersionSetting,如果它不为空,则将它放到lastStaticSharedLibSettings中。
下面就是退出循环了。接着使用之前的结果,构造ReconcileRequest对象reconcileRequest。
下面reconcilePackagesLocked()就是协商阶段的实现,详见 Android 应用安装-协调阶段。
继续构造CommitRequest对象commitRequest,调用commitPackagesLocked(commitRequest),它是提交阶段的实现,详见文章 Android 应用安装-提交阶段。并且将变量success = true,代表提交成功。
executePostCommitSteps(commitRequest)则是提交之后的操作步骤,详见 Android 安装应用-提交阶段之后剩下的操作。
继续往下,如果success为true,则代表安装成功。继续遍历安装请求,如果是增量安装,则跳出循环。如果不是增量安装,发送一个广播给验证者(注册接收Intent.ACTION_PACKAGE_VERIFIED的广播)。
如果success为false,则遍历浏览结果集合,如果在之前没有创建成功AppId,则这里调用cleanUpAppIdCreation(result)清理Settings对象中维持的appId。
在success为false的情况下,它接着遍历安装请求。将该应用对应的freezer关闭,其实就是将该包从PackageManagerService对象的成员mFrozenPackages中删除。对于安装结果为PackageManager.INSTALL_SUCCEEDED的,也将返回代码设置为PackageManager.INSTALL_UNKNOWN。
安装之后的处理
看下handlePackagePostInstall()的实现代码如下:
private void handlePackagePostInstall(PackageInstalledInfo res, boolean killApp,boolean virtualPreload, boolean launchedForRestore, String installerPackage,IPackageInstallObserver2 installObserver, int dataLoaderType) {boolean succeeded = res.returnCode == PackageManager.INSTALL_SUCCEEDED;final boolean update = res.removedInfo != null && res.removedInfo.removedPackage != null;final String packageName = res.name;final PackageSetting pkgSetting = succeeded ? getPackageSetting(packageName) : null;final boolean removedBeforeUpdate = (pkgSetting == null)|| (pkgSetting.isSystem() && !pkgSetting.getPathString().equals(res.pkg.getPath()));if (succeeded && removedBeforeUpdate) {Slog.e(TAG, packageName + " was removed before handlePackagePostInstall "+ "could be executed");res.returnCode = INSTALL_FAILED_PACKAGE_CHANGED;res.returnMsg = "Package was removed before install could complete.";// Remove the update failed package's older resources safely nowInstallArgs args = res.removedInfo != null ? res.removedInfo.args : null;if (args != null) {synchronized (mInstallLock) {args.doPostDeleteLI(true);}}notifyInstallObserver(res, installObserver);return;}if (succeeded) {// Clear the uid cache after we installed a new package.mPerUidReadTimeoutsCache = null;// Send the removed broadcastsif (res.removedInfo != null) {res.removedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/);}final String installerPackageName =res.installerPackageName != null? res.installerPackageName: res.removedInfo != null? res.removedInfo.installerPackageName: null;synchronized (mLock) {mInstantAppRegistry.onPackageInstalledLPw(res.pkg, res.newUsers);}// Determine the set of users who are adding this package for// the first time vs. those who are seeing an update.int[] firstUserIds = EMPTY_INT_ARRAY;int[] firstInstantUserIds = EMPTY_INT_ARRAY;int[] updateUserIds = EMPTY_INT_ARRAY;int[] instantUserIds = EMPTY_INT_ARRAY;final boolean allNewUsers = res.origUsers == null || res.origUsers.length == 0;final PackageSetting ps = pkgSetting;for (int newUser : res.newUsers) {final boolean isInstantApp = ps.getInstantApp(newUser);if (allNewUsers) {if (isInstantApp) {firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser);} else {firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser);}continue;}boolean isNew = true;for (int origUser : res.origUsers) {if (origUser == newUser) {isNew = false;break;}}if (isNew) {if (isInstantApp) {firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser);} else {firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser);}} else {if (isInstantApp) {instantUserIds = ArrayUtils.appendInt(instantUserIds, newUser);} else {updateUserIds = ArrayUtils.appendInt(updateUserIds, newUser);}}}// Send installed broadcasts if the package is not a static shared lib.if (res.pkg.getStaticSharedLibName() == null) {mProcessLoggingHandler.invalidateBaseApkHash(res.pkg.getBaseApkPath());// Send added for users that see the package for the first time// sendPackageAddedForNewUsers also deals with system appsint appId = UserHandle.getAppId(res.uid);boolean isSystem = res.pkg.isSystem();sendPackageAddedForNewUsers(packageName, isSystem || virtualPreload,virtualPreload /*startReceiver*/, appId, firstUserIds, firstInstantUserIds,dataLoaderType);// Send added for users that don't see the package for the first timeBundle extras = new Bundle(1);extras.putInt(Intent.EXTRA_UID, res.uid);if (update) {extras.putBoolean(Intent.EXTRA_REPLACING, true);}extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);// Send to all running apps.final SparseArray<int[]> newBroadcastAllowList;synchronized (mLock) {newBroadcastAllowList = mAppsFilter.getVisibilityAllowList(getPackageSettingInternal(res.name, Process.SYSTEM_UID),updateUserIds, mSettings.getPackagesLocked());}sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,extras, 0 /*flags*/,null /*targetPackage*/, null /*finishedReceiver*/,updateUserIds, instantUserIds, newBroadcastAllowList, null);if (installerPackageName != null) {// Send to the installer, even if it's not running.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,extras, 0 /*flags*/,installerPackageName, null /*finishedReceiver*/,updateUserIds, instantUserIds, null /* broadcastAllowList */, null);}// if the required verifier is defined, but, is not the installer of record// for the package, it gets notifiedfinal boolean notifyVerifier = mRequiredVerifierPackage != null&& !mRequiredVerifierPackage.equals(installerPackageName);if (notifyVerifier) {sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,extras, 0 /*flags*/,mRequiredVerifierPackage, null /*finishedReceiver*/,updateUserIds, instantUserIds, null /* broadcastAllowList */, null);}// If package installer is defined, notify package installer about new// app installedif (mRequiredInstallerPackage != null) {sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/,mRequiredInstallerPackage, null /*finishedReceiver*/,firstUserIds, instantUserIds, null /* broadcastAllowList */, null);}// Send replaced for users that don't see the package for the first timeif (update) {sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,packageName, extras, 0 /*flags*/,null /*targetPackage*/, null /*finishedReceiver*/,updateUserIds, instantUserIds, res.removedInfo.broadcastAllowList,null);if (installerPackageName != null) {sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,extras, 0 /*flags*/,installerPackageName, null /*finishedReceiver*/,updateUserIds, instantUserIds, null /*broadcastAllowList*/, null);}if (notifyVerifier) {sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,extras, 0 /*flags*/,mRequiredVerifierPackage, null /*finishedReceiver*/,updateUserIds, instantUserIds, null /*broadcastAllowList*/, null);}sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,null /*package*/, null /*extras*/, 0 /*flags*/,packageName /*targetPackage*/,null /*finishedReceiver*/, updateUserIds, instantUserIds,null /*broadcastAllowList*/,getTemporaryAppAllowlistBroadcastOptions(REASON_PACKAGE_REPLACED).toBundle());} else if (launchedForRestore && !res.pkg.isSystem()) {// First-install and we did a restore, so we're responsible for the// first-launch broadcast.if (DEBUG_BACKUP) {Slog.i(TAG, "Post-restore of " + packageName+ " sending FIRST_LAUNCH in " + Arrays.toString(firstUserIds));}sendFirstLaunchBroadcast(packageName, installerPackage,firstUserIds, firstInstantUserIds);}// Send broadcast package appeared if external for all usersif (res.pkg.isExternalStorage()) {if (!update) {final StorageManager storage = mInjector.getSystemService(StorageManager.class);VolumeInfo volume =storage.findVolumeByUuid(res.pkg.getStorageUuid().toString());int packageExternalStorageType =getPackageExternalStorageType(volume, res.pkg.isExternalStorage());// If the package was installed externally, log it.if (packageExternalStorageType != StorageEnums.UNKNOWN) {FrameworkStatsLog.write(FrameworkStatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED,packageExternalStorageType, packageName);}}if (DEBUG_INSTALL) {Slog.i(TAG, "upgrading pkg " + res.pkg + " is external");}final int[] uidArray = new int[]{res.pkg.getUid()};ArrayList<String> pkgList = new ArrayList<>(1);pkgList.add(packageName);sendResourcesChangedBroadcast(true, true, pkgList, uidArray, null);}} else if (!ArrayUtils.isEmpty(res.libraryConsumers)) { // if static shared libint[] allUsers = mInjector.getUserManagerService().getUserIds();for (int i = 0; i < res.libraryConsumers.size(); i++) {AndroidPackage pkg = res.libraryConsumers.get(i);// send broadcast that all consumers of the static shared library have changedsendPackageChangedBroadcast(pkg.getPackageName(), false /* dontKillApp */,new ArrayList<>(Collections.singletonList(pkg.getPackageName())),pkg.getUid(), null);}}// Work that needs to happen on first install within each userif (firstUserIds != null && firstUserIds.length > 0) {for (int userId : firstUserIds) {restorePermissionsAndUpdateRolesForNewUserInstall(packageName,pkgSetting.getInstallReason(userId), userId);}}if (allNewUsers && !update) {notifyPackageAdded(packageName, res.uid);} else {notifyPackageChanged(packageName, res.uid);}// Log current value of "unknown sources" settingEventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,getUnknownSourcesSettings());// Remove the replaced package's older resources safely nowInstallArgs args = res.removedInfo != null ? res.removedInfo.args : null;if (args != null) {if (!killApp) {// If we didn't kill the app, defer the deletion of code/resource files, since// they may still be in use by the running application. This mitigates problems// in cases where resources or code is loaded by a new Activity before// ApplicationInfo changes have propagated to all application threads.scheduleDeferredNoKillPostDelete(args);} else {synchronized (mInstallLock) {args.doPostDeleteLI(true);}}} else {// Force a gc to clear up things. Ask for a background one, it's fine to go on// and not block here.VMRuntime.getRuntime().requestConcurrentGC();}// Notify DexManager that the package was installed for new users.// The updated users should already be indexed and the package code paths// should not change.// Don't notify the manager for ephemeral apps as they are not expected to// survive long enough to benefit of background optimizations.for (int userId : firstUserIds) {PackageInfo info = getPackageInfo(packageName, /*flags*/ 0, userId);// There's a race currently where some install events may interleave with an// uninstall. This can lead to package info being null (b/36642664).if (info != null) {mDexManager.notifyPackageInstalled(info, userId);}}}final boolean deferInstallObserver = succeeded && update && !killApp;if (deferInstallObserver) {scheduleDeferredNoKillInstallObserver(res, installObserver);} else {notifyInstallObserver(res, installObserver);}}
succeeded为true代表安装成功,update为true代表是更新升级。removedBeforeUpdate代表更新失败或者是系统应用包更新,但是PackageSetting对象的文件路径和文件安装路径不一致,代表更新之前需要删除。
如果succeeded为true并且removedBeforeUpdate也为true的情况,会将res.returnCode = INSTALL_FAILED_PACKAGE_CHANGED,将安装成功的返回码改成安装失败。并且如果res.removedInfo.args不为null的情况下,会执行删除旧包路径及其包下面内容。接着调用notifyInstallObserver(res, installObserver)执行回调。
接下来succeeded如果为true,代表着安装成功的情况。在res.removedInfo不为null的情况下,将调用它的sendPackageRemovedBroadcasts(killApp, false)发送包被删除的广播。在前面 Android 安装应用-准备阶段 中,会在更新安装的情况下,初始化res.removedInfo,更新安装涉及到旧包的删除,这里会发送包被删除的广播。res.removedInfo是PackageRemovedInfo对象,所以会调用它的sendPackageRemovedBroadcasts(killApp, false)方法,它会向能看到它的普通应用发送Intent.ACTION_PACKAGE_REMOVED广播,向安装该应用的应用发送Intent.ACTION_PACKAGE_REMOVED广播。向平台包应用(包名为"android")发送Intent.ACTION_PACKAGE_REMOVED_INTERNAL广播。如果数据都被删除,并且不是系统应用包更新的情况,它会向能看到它的普通应用发送Intent.ACTION_PACKAGE_FULLY_REMOVED广播。它还会向能看到它的普通应用发送Intent.ACTION_UID_REMOVED广播。
下面的四个数组变量firstUserIds、firstInstantUserIds、updateUserIds、instantUserIds是和用户有关,前面两个是首次添加该包的用户(分普通应用安装和Instant安装),后面两个是之前就添加过该包的用户,现在更新安装依然存在的用户。res.origUsers就是之前添加过该包的用户,res.newUsers则是目前更新添加该包的用户。这块处理的逻辑就是res.origUsers不存在,res.newUsers存在的就放入firstUserIds或firstInstantUserIds中;res.origUsers存在,res.newUsers存在的就放入updateUserIds或instantUserIds中。
res.pkg.getStaticSharedLibName() == null代表安装应用不是静态分享包,在该种情况下,sendPackageAddedForNewUsers()方法会向firstUserIds或firstInstantUserIds对应的能看到的普通应用发送Intent.ACTION_PACKAGE_ADDED广播。对于这些首次添加该包的用户,如果应用是系统应用或者虚拟预加载的,如果这些用户是运行状态,它会该用户的应用发送Intent.ACTION_LOCKED_BOOT_COMPLETED广播,如果该用户没有被锁定,它还会发送Intent.ACTION_BOOT_COMPLETED广播。
下面还会接着处理应用不是静态分享包时,还会向updateUserIds或instantUserIds中的普通应用发送Intent.ACTION_PACKAGE_ADDED广播。如果安装者应用不为null,向它发送Intent.ACTION_PACKAGE_ADDED广播。如果mRequiredVerifierPackage不为null,并且它不为安装者应用,则它向mRequiredVerifierPackage发送Intent.ACTION_PACKAGE_ADDED广播。如果包安装者定义了(mRequiredInstallerPackage != null),会向它发送Intent.ACTION_PACKAGE_ADDED广播。
如果是在更新安装的情况下,它会向updateUserIds或instantUserIds应用中的普通应用发送Intent.ACTION_PACKAGE_REPLACED广播,如果安装者应用不为null,向它发送Intent.ACTION_PACKAGE_REPLACED广播,如果mRequiredVerifierPackage不为null,并且它不为安装者应用,则它向mRequiredVerifierPackage发送Intent.ACTION_PACKAGE_REPLACED广播。它还会向自己发送Intent.ACTION_MY_PACKAGE_REPLACED广播。
如果不是更新安装,是恢复数据的普通应用,它将向第一次添加该包的用户的安装它的应用包发送Intent.ACTION_PACKAGE_FIRST_LAUNCH广播。
接下来如果包是在外部存储空间中,它将发送Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE广播,将该应用发生了变化进行通知。
下面进入else if (!ArrayUtils.isEmpty(res.libraryConsumers))分支,意味着它是一个库,被别的应用引用,res.libraryConsumers就是引用它的应用。它会发送Intent.ACTION_PACKAGE_CHANGED广播,通知其他能看到引用库的应用的其他普通应用发生了变化。
在安装的过程中,如果有新增的用户,会执行恢复新增用户的运行时权限,更新对应用户的更喜欢的Activity。
在allNewUsers为true,update为false的情况下,代表第一次安装,会通过notifyPackageAdded(packageName, res.uid),调用成员变量mPackageListObservers中的onPackageAdded接口。其他情况下,会调用成员变量mPackageListObservers中的onPackageChanged接口。
如果res.removedInfo.args不为null,会调用它的doPostDeleteLI(true)执行删除资源和代码。不过如果设置了不杀死应用的情况,会调用scheduleDeferredNoKillPostDelete(args)延迟删除。
对于新增的用户,调用mDexManager.notifyPackageInstalled(info, userId)通知包安装了。
最后就是处理返回结果。不过也分是否杀死应用两种情况,如果是不杀死应用的情况,也是延迟消息处理。最后都是通过调用notifyInstallObserver(res, installObserver)来处理。
将处理结果返回给应用
notifyInstallObserver(res, installObserver)的代码如下:
private void notifyInstallObserver(PackageInstalledInfo info,IPackageInstallObserver2 installObserver) {if (installObserver != null) {try {Bundle extras = extrasForInstallResult(info);installObserver.onPackageInstalled(info.name, info.returnCode,info.returnMsg, extras);} catch (RemoteException e) {Slog.i(TAG, "Observer no longer exists.");}}}
可见是通过参数IPackageInstallObserver2 类型的onPackageInstalled接口返回。这个回调接口是来自安装参数FileInstallArgs类对象的成员变量observer。它又来自InstallParams类对象的observer。InstallParams类对象的创建来自PackageInstallerSession的makeInstallParams()。
@Nullableprivate PackageManagerService.InstallParams makeInstallParams()throws PackageManagerException {……final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {@Overridepublic void onUserActionRequired(Intent intent) {throw new IllegalStateException();}@Overridepublic 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);}}};……synchronized (mLock) {return mPm.new InstallParams(stageDir, localObserver, params, mInstallSource, user,mSigningDetails, mInstallerUid, mPackageLite);}}
IPackageInstallObserver2 对象是通过构造InstallParams对象时作为参数传递过去的。
isStaged()代表这个安装是需要重启才执行安装。普通应用的安装是走else分支。
destroyInternal()是用来销毁session,因为目前已经安装完毕,有结果了。
dispatchSessionFinished(returnCode, msg, extras)用来派发结果。看一下它的代码:
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);synchronized (mLock) {mFinalStatus = returnCode;mFinalMessage = msg;}final boolean success = (returnCode == INSTALL_SUCCEEDED);// Send broadcast to default launcher only if it's a new install// TODO(b/144270665): Secure the usage of this broadcast.final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING);if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()) {mPm.sendSessionCommitBroadcast(generateInfoScrubbed(true /*icon*/), userId);}mCallback.onSessionFinished(this, success);if (isDataLoaderInstallation()) {logDataLoaderInstallationSession(returnCode);}}
sendUpdateToRemoteStatusReceiver(returnCode, msg, extras)是向应用端发送广播安装结果。
给mFinalStatus、mFinalMessage赋值,存放结果。
如果是新安装,就是不是更新安装,并且安装成功的情况下,mPm.sendSessionCommitBroadcast(generateInfoScrubbed(true /icon/), userId)会发送对应的广播。
mCallback.onSessionFinished(this, success)主要是Session已经完成了,所以需要将它去除。
咱们这里主要看看sendUpdateToRemoteStatusReceiver(returnCode, msg, extras),代码如下:
private void sendUpdateToRemoteStatusReceiver(int returnCode, String msg, Bundle extras) {final IntentSender statusReceiver;final String packageName;synchronized (mLock) {statusReceiver = mRemoteStatusReceiver;packageName = mPackageName;}if (statusReceiver != null) {// Execute observer.onPackageInstalled on different thread as we don't want callers// inside the system server have to worry about catching the callbacks while they are// calling into the sessionfinal SomeArgs args = SomeArgs.obtain();args.arg1 = packageName;args.arg2 = msg;args.arg3 = extras;args.arg4 = statusReceiver;args.argi1 = returnCode;mHandler.obtainMessage(MSG_ON_PACKAGE_INSTALLED, args).sendToTarget();}}
这里的mRemoteStatusReceiver就是在 Android 安装过程一 界面跳转 中InstallInstalling Activity中session.commit(pendingIntent.getIntentSender())里的pendingIntent.getIntentSender()。pendingIntent是待发送的BROADCAST_ACTION广播(详见Android 安装过程一 界面跳转),它的接收是在安装进程中的InstallEventReceiver中,它是配置在Manifest文件中。
在这里是通过消息MSG_ON_PACKAGE_INSTALLED的处理,它的处理是在sendOnPackageInstalled()中,看一下:
private static void sendOnPackageInstalled(Context context, IntentSender target, int sessionId,boolean showNotification, int userId, String basePackageName, int returnCode,String msg, Bundle extras) {if (INSTALL_SUCCEEDED == returnCode && showNotification) {boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING);Notification notification = PackageInstallerService.buildSuccessNotification(context,context.getResources().getString(update ? R.string.package_updated_device_owner :R.string.package_installed_device_owner),basePackageName,userId);if (notification != null) {NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);notificationManager.notify(basePackageName,SystemMessageProto.SystemMessage.NOTE_PACKAGE_STATE,notification);}}final Intent fillIn = new Intent();fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, basePackageName);fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);fillIn.putExtra(PackageInstaller.EXTRA_STATUS,PackageManager.installStatusToPublicStatus(returnCode));fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,PackageManager.installStatusToString(returnCode, msg));fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);if (extras != null) {final String existing = extras.getString(PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);if (!TextUtils.isEmpty(existing)) {fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing);}}try {target.sendIntent(context, 0, fillIn, null, null);} catch (IntentSender.SendIntentException ignored) {}}
这里就是发送了BROADCAST_ACTION广播,咱们再看看安装进程中的接收者的处理。
安装进程中的接收者的处理是在EventResultPersister中的onEventReceived()中,代码如下:
void onEventReceived(@NonNull Context context, @NonNull Intent intent) {int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT));return;}int id = intent.getIntExtra(EXTRA_ID, 0);String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0);EventResultObserver observerToCall = null;synchronized (mLock) {int numObservers = mObservers.size();for (int i = 0; i < numObservers; i++) {if (mObservers.keyAt(i) == id) {observerToCall = mObservers.valueAt(i);mObservers.removeAt(i);break;}}if (observerToCall != null) {observerToCall.onResult(status, legacyStatus, statusMessage);} else {mResults.put(id, new EventResult(status, legacyStatus, statusMessage));writeState();}}}
注册的回调在成员变量mObservers,它是SparseArray类型。接收到广播之后,通过参数id来找到对应的回调函数,就可以回调对应的函数了。
再看一下它的注册回调也是在InstallInstalling Activity中,如下:
mInstallId = InstallEventReceiver.addObserver(this, EventResultPersister.GENERATE_NEW_ID,this::launchFinishBasedOnResult)
所以最后会调用launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage)方法,再显示对应的界面。(详见Android 安装过程一 界面跳转)。
总结
安装这个过程还是挺复杂的,最主要的是它将之分成4个阶段:准备、浏览、协调、提交。每个步骤中都有好多代码,并且代码中处理不是只针对普通应用的安装,它是包括其他形式安装的处理,该文章主要是有关普通应用的安装,还是结合了前面文章的例子,将所有的流程过了一遍,其中的相关细节,还没有深究,留待后续继续琢磨。
相关文章:

Android 安装过程五 MSG_INSTALL消息的处理 安装
现在马上进入正式的安装流程。 从前面文章 Android 安装过程四 MSG_INSTALL消息的处理 安装之前的验证知道,在验证之后没有什么问题的情况下,会回调onVerificationComplete()方法,它位于PackageInstallerSession类中。 private void onVe…...

大数据开发--1.3 Linux的常用命令大全
目录 一. 终端命令格式 命令格式 说明: 二. 显示文件列表命令 -ls 作用 格式 ls常用选项 案例 三. 目录操作命令 -pwd 作用 格式 案例 四. 目录操作命令 -cd 作用 格式 案例 五. 目录操作命令 -mkdir 作用 格式 案…...

使用PuTTY连接到Amazon Linux实例
PuTTY 是一款免费的 SSH 客户端,广泛用于从 Windows 系统连接到 Linux 实例。如果你使用的是 Windows Server 2019 或更高版本,可以考虑使用内置的 OpenSSH 工具,但 PuTTY 依然是一个非常受欢迎的选择。 一、先决条件 在使用 PuTTY 连接到 …...

Nexus搭建maven私有仓库
内网访问,内网团队使用一个服务缓存节省外网宽带。 微服务开发中加速 Maven 项目构建,加快团队合作,提高工作效率 允许上传和下载私有库,并且不被外部访问,安全 稳定。 方便内部项目服务的依赖引用,而不需要…...

留存率的定义与SQL实现
1.什么是留存率 留存率是指在特定时间段内,仍然继续使用某项产品或服务的用户占用户总数的百分比。 通常,留存率会以日,周,或月为单位进行统计和分析。 2.SQL留存率常见问题 1.计算新用户登录的日期的次日留存率以及3日留存率 …...

Java的锁机制详解
在并发编程中,锁 是用于控制多个线程对共享资源进行访问的工具。Java提供了多种锁机制,从最基础的 synchronized 到高级的 ReentrantLock,这些锁帮助我们确保线程安全,并能有效避免数据竞争和死锁问题。 1. synchronized 关键字…...

用户登录与信息管理:实现小程序登录与用户信息存储
用户登录与信息管理:实现小程序登录与用户信息存储 在现代的移动应用中,用户登录与信息管理是构建个性化用户体验的基础。小程序作为轻量级的应用形式,在简化开发流程的同时,也需要我们妥善管理用户的登录状态与用户信息。本文将…...

Java如何调用构造函数和方法以及使用
调用构造函数的格式 构造函数在创建新对象时被调用。调用格式如下: ClassName objectName new ClassName(parameters); ClassName:你需要创建其实例的类的名称。 objectName:你将创建的对象的名称。 parameters:如果你使用的是…...

TFBoys谁最重
题目 使用go语言设计一个程序计算TFBoys谁最重,要求使用结构体表示TFBoys三个成员,设计函数计算三个重量的最大值。 程序 package main import ("fmt") type Person struct {Name stringWeight float64} func (p Person) GetWeigh…...

scp 通过中间机器进行远程拷贝
有时候,我们想要通过 scp将一台机器上的文件拷贝至另外一台机器,但这两台机器可能没有直接联通,需要通过中间机器进行跳转才能访问,一个麻烦的办法就是,先将文件拷贝至中间机器,然后再从中间机器拷贝至另外…...

探索 Python 高精度计算的奥秘:mpmath 库全解析
文章目录 探索 Python 高精度计算的奥秘:mpmath 库全解析背景:为何选择 mpmath?第二部分:mpmath 是什么?第三部分:如何安装 mpmath?第四部分:mpmath 函数使用示例第五部分࿱…...

<<迷雾>> 第10章 用机器做一连串的加法(1)--使用两排开关分别给出被加数和加数 示例电路
info::操作说明 鼠标单击逻辑输入切换 0|1 状态 primary::在线交互操作链接 https://cc.xiaogd.net/?startCircuitLinkhttps://book.xiaogd.net/cyjsjdmw-examples/assets/circuit/cyjsjdmw-ch10-01-5-bit-adder.txt 原图...

Stable Diffusion最新版nowebui的api使用详解
最近在使用stable diffusion最新版的Stable Diffusion WebUI Forge进行api调用,下面来一步一步的进行展开吧!!! 1、下载lllyasviel/stable-diffusion-webui-forge GitHub - lllyasviel/stable-diffusion-webui-forgeContribute to lllyasviel/stable-diffusion-webui-for…...

云服务器架构详解:X86计算_ARM_GPU/FPGA/ASIC_裸金属_超级计算集群
阿里云服务器架构有什么区别?X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器、超级计算集群有什么区别?阿里云服务器网aliyunfuwuqi.com分享云服务器ECS架构详细说明: 阿里云服务器ECS架构说明 阿里云服务器ECS架构 X86计算 X86计算架…...

高级java每日一道面试题-2024年10月4日-数据库篇-MySQL索引底层结构为什么使用B+树?
如果有遗漏,评论区告诉我进行补充 面试官: MySQL索引底层结构为什么使用B树? 我回答: 该面试题本质还是在考察B树的数据结构和在数据库系统中的应用,下边是详细的回答。 B树的基本特性 B 树的结构特点 非叶子节点只存储键值信息,不存储…...

【JVM】内存分析工具JConsole/Visual VM
1 缘起 日常补充JVM调优,调优实践前需要学习一些理论做支撑, JVM调优三步:理论>GC分析>JVM调优, 我们会有一些玩笑话说,做了这么久Java开发,做过JVM调优吗? 做过,面试时。当然…...

一静 、二平 、三忍 、四让、五淡
一静 、二平 、三忍 、四让、五淡。 作者:儒风君 来源:儒风大家(ID: rufengdajia) 古人为人、处事、修身,都有独特的章法。 一静、二平、三忍、四让、五淡。 说透中国人的大智慧。 1 静 《道德经》里讲:“清静为天下正。”…...

js 深入理解函数(一):函数的本质
目录 概述1. 箭头函数2. 函数名 :指向函数的指针3. 理解参数3.1 arguments 对象的作用3.2 arguments 的注意点3.3 箭头函数中的参数 4. 没有重载5. 默认参数值5.1 ES 6 支持显示定义默认参数5.2 传 undefined 等于没有传值5.3 arguments 不反映参数默认值5.4 默认值…...

MySql表结构设计
创建 create table 表名(字段1 字段类型 [约束] [comment 字段1注释],...) [comment 表注释];约束是作用于表中字段上的规则,用于限制存储在表中的数据。它的目的是保证数据库中数据的正确性、有效性和完整性。 约束描述关键字非空约束限制该字段不能为nullnot nu…...

java:pdfbox 3.0 去除扫描版PDF中文本水印
官网下载 https://pdfbox.apache.org/download.html下载 pdfbox-app-3.0.3.jar cd D:\pdfbox 运行 java -jar pdfbox-app-3.0.3.jar java -jar pdfbox-app-3.0.3.jar Usage: pdfbox [COMMAND] [OPTIONS] Commands:debug Analyzes and inspects the internal structu…...

python知识点100篇系列(17)-替换requests的python库httpx
Requests 是使用 Apache2 Licensed 许可证的 基于Python开发的HTTP 库,其在Python内置模块的基础上进行了高度的封装,使用Requests可以轻而易举的完成浏览器可有的任何操作。 但是在python3.6之后,出现了一个requests的替代选项; httpx httpx是Python新一代的网络请求库…...

python 实现graph list图列算法
graph list图列算法介绍 图列(Graph List)算法通常指的是在图的表示中,使用列表(List)或更具体地说,邻接表(Adjacency List)来表示图的一种算法。邻接表是图的一种常见表示方法&…...

LFU算法 初始频率 动态频率
LFU(Least Frequently Used)算法是一种缓存淘汰策略,其核心思想是根据数据的访问频率来决定淘汰哪些数据。具体来说, LFU算法认为如果一个数据在过去一段时间内被访问的次数很少,那么它在未来被再次访问的概率也…...

Spring Boot 进阶-详解SpringBoot的复杂数据校验规则
在之前的文章中,我们介绍了SpringBoot整合JSR-303规则来完成数据校验操作。接下来我们来聊一聊关于数据校验的具体用法。 之前的文章中举过一个简单的例子通过学生信息提交的例子来介绍了关于数据校验如何去做。那么接下来这篇文章,我们就来看看对于一些复杂的数据校验如何完…...

wsl环境下安装Ubuntu,并下载MySQL5.7
安装操作需root权限,切换root用户有两种方式: 1-通过 sudo su - ,切换到root用户(登录后长期有效)。 2-在每一个命令前加上sudo,临时提升权限(仅对一条命令有效)。 1、下载apt仓库…...

倪师学习笔记-天纪-01
一、概要 介绍课程内容,介绍部分概念 二、具体内容 1、天纪内容 天机道:看象,使用斗数等工具人间道:看卦,使用易经地脉道:看风水地理 2、神 神与形对应,形是神的实例,神是形的…...

深入理解缓存穿透、缓存击穿和缓存雪崩
在现代分布式系统中,缓存是提升系统性能和减轻数据库负载的重要组件。然而,在实际应用中,我们可能会遇到一些缓存问题,如缓存穿透、缓存击穿和缓存雪崩。本文将详细探讨这三种缓存问题的原理、影响以及解决方案。 一,…...

【玩转动态规划专题】70. 爬楼梯【简单】
【玩转动态规划专题】70. 爬楼梯【简单】 1、力扣链接 https://leetcode.cn/problems/climbing-stairs/description/ 2、题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 示例 1&…...

前端开发设计模式——组合模式
目录 一、组合模式的定义和特点 1.定义 2.特点: 二、组合模式的实现方式 1.定义抽象组件类 2.创建叶节点类 3.创建组合类: 三、组合模式的应用场景 1.界面布局管理 2.菜单系统构建 3.组件库开发 四、组合模式的优点 1.简化客户端代码 2.增…...

初探OceanBase 4.x单机环境下如何进行主备架构搭建
本文来自OceanBase 用户的体验分享 (以下简称 OB),已经开源了3年左右,其间从3.x版本演进至4.x版本,发生了许多变化。对一个DBer而言,最为关切的是如何高效运用OB,以及是否能实现如同应用MySQL般…...