提示:通过安装前输入密码的需求,来熟悉了解PMS 基本的安装流程
文章目录
- 一、需求
- 实现需求原因
- 提醒
- 二、UML图-类图
- 三、参考资料
- 四、实现效果
- 五、需求修改点
- 修改文件及路径
- 具体修改内容
- 六、源码流程分析
- PMS的复杂性
- 代码量
- 实现aidl 接口
- PackageManagerService 内部类
- 七、PMSAnZhuangLiuCheng.html" title=PMS安装流程>PMS安装流程源码分析
- installStage
- HandlerParams
- handleStartCopy
- PackageInfoLite
- verifyReplacingVersionCode
- handleReturnCode ->processPendingInstall();
- processInstallRequestsAsync()
- 阶段性总结
- doPreInstall
- cleanUp();
- removeCodePathLI
- installPackagesTracedLI ->installPackagesLI
- preparePackageLI
- doRename
- scanPackageTracedLI
- reconcilePackagesLocked
- commitPackagesLocked
- executePostCommitSteps
- prepareAppDataAfterInstallLIF
- doPostInstall
- restoreAndPostInstall
- handlePackagePostInstall
- 总结
一、需求
在安装应用时候需要加一个锁,用户输入密码才能安装应用
无论是 adb 安装;应用市场下载应用安装;apk 放到本地文件夹点击apk 文件安装都需要输入密码才能安装。
实现需求原因
- 之前有客户提出过相关类似需求,自己来实现以下 ;
- 体制内政府银行机关相关需求偏多,通过简单demo 实现看看;
- 通过这样的一个小需求 简单实现,来熟悉PMS 相关内容
提醒
- 如果仅仅是想实现需求,按照思路和部分代码 copy 即可
- 如果想通过此需求了解PMS相关联知识点,建议多看看,也多看看相关资料推荐和相关文章,并实际验证
- 爬不完的坑,建议多实际操作 验证,别人的不一定好使,需要多验证下。 本文仅仅针对MTK Android12 上面验证,不同平台不同版本待验证,思路肯定一致
二、UML图-类图
这个UML图相当重要,站在别人的肩膀上,方便梳理思路
类图,可参考借鉴
├── PMS.installPackage()
└── PMS.installPackageAsUser()
|传递 InstallParams 参数
PackageHandler.doHandleMessage().INIT_COPY
|
PackageHandler.doHandleMessage().MCS_BOUND
├── HandlerParams.startCopy()
│ ├── InstallParams.handleStartCopy()
│ │ └──InstallArgs.copyApk()
│ └── InstallParams.handleReturnCode()
│ └── PMS.processPendingInstall()
│ ├── InstallArgs.doPreInstall()
│ ├── PMS.installPackageLI()
│ │ ├── PackageParser.parsePackage()
│ │ ├── PackageParser.collectCertificates()
│ │ ├── PackageParser.collectManifestDigest()
│ │ ├── PackageDexOptimizer.performDexOpt()
│ │ ├── InstallArgs.doRename()
│ │ │ └── InstallArgs.getNextCodePath()
│ │ ├── replacePackageLI()
│ │ │ ├── shouldCheckUpgradeKeySetLP()
│ │ │ ├── compareSignatures()
│ │ │ ├── replaceSystemPackageLI()
│ │ │ │ ├── killApplication()
│ │ │ │ ├── removePackageLI()
│ │ │ │ ├── Settings.disableSystemPackageLPw()
│ │ │ │ ├── createInstallArgsForExisting()
│ │ │ │ ├── deleteCodeCacheDirsLI()
│ │ │ │ ├── scanPackageLI()
│ │ │ │ └── updateSettingsLI()
│ │ │ └── replaceNonSystemPackageLI()
│ │ │ ├── deletePackageLI()
│ │ │ ├── deleteCodeCacheDirsLI()
│ │ │ ├── scanPackageLI()
│ │ │ └── updateSettingsLI()
│ │ └── installNewPackageLI()
│ │ ├── scanPackageLI()
│ │ └── updateSettingsLI()
│ ├── InstallArgs.doPostInstall()
│ ├── BackupManager.restoreAtInstall()
│ └── sendMessage(POST_INSTALL)
│ |
│ PackageHandler.doHandleMessage().POST_INSTALL
│ ├── grantRequestedRuntimePermissions()
│ ├── sendPackageBroadcast()
│ └── IPackageInstallObserver.onPackageInstalled()
└── PackageHandler.doHandleMessage().MCS_UNBIND
└── PackageHandler.disconnectService()
三、参考资料
Android10.0 PMS安装第三方app时添加密码锁限制安装
应用安装源码阅读指南
Android PackageManagerService总结(四) APK安装流程
Android PackageManager相关源码分析之安装应用
Android PMS APP安装流程
PMS 处理APK安装
Android PMS应用安装流程源码分析下篇-安装包校验及安装
也可以参考我之前的关联笔记有助于熟悉相关内容和加深PMS理解,每个子模块一点一点 慢慢的就串联起来了。
Android13-包安装器PackageInstaller-之apk安装流程
MTK-Android13-包安装器PackageInstaller 静默安装实现
通过 pm 指令分析 Install 过程
四、实现效果
演示了从adb 安装的一个流程(应用市场下载安装和本地安装包点击安装效果是一样的)
- copy 文件 到/data/app/**.tmp 拷贝的安装包文件零时文件 .tmp 结尾
- 弹出密码输入框,准备安装。 安装流程阻塞等待状态
- 输入密码验证。验证失败 安装结束;验证成功则进行安装,安装流程结束。 删除零时文件 .tmp 的零时安装文件
五、需求修改点
修改文件及路径
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
具体修改内容
引入包
+ import android.content.Context;
+ import android.graphics.Color;
+ import android.graphics.Typeface;
+ import android.text.InputType;
+ import android.view.Gravity;
+ import android.view.View;
+ import android.view.WindowManager;
+ import android.widget.Button;
+ import android.widget.EditText;
+ import android.widget.LinearLayout;
+ import android.widget.TextView;
+ import android.widget.Toast;
+ import android.widget.RelativeLayout;
+ import android.view.ViewGroup;
+ import android.graphics.PixelFormat;
+
在 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));}*/ final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();mLayoutParams.width = 1000;mLayoutParams.height =500;mLayoutParams.dimAmount =0.5f;mLayoutParams.format = PixelFormat.TRANSLUCENT;mLayoutParams.type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);final LinearLayout parentLayout = new LinearLayout(mContext);parentLayout.setOrientation(LinearLayout.VERTICAL);parentLayout.setBackgroundColor(Color.WHITE);LinearLayout.LayoutParams layoutParams= new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);parentLayout.setLayoutParams(layoutParams);TextView titleText = new TextView(mContext);LinearLayout.LayoutParams contentParams= new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);titleText.setLayoutParams(contentParams);titleText.setText("check password");titleText.setTextColor(Color.BLACK);titleText.setTypeface(Typeface.create(titleText.getTypeface(), Typeface.NORMAL), Typeface.BOLD);titleText.setPadding(10, 10, 0, 0);parentLayout.addView(titleText);EditText passEdtTxt = new EditText(mContext);passEdtTxt.setLayoutParams(contentParams);passEdtTxt.setHint("Please input password");passEdtTxt.setTextSize(14);passEdtTxt.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);passEdtTxt.setTextColor(Color.BLACK);parentLayout.addView(passEdtTxt);RelativeLayout reLayout = new RelativeLayout(mContext);RelativeLayout.LayoutParams rightReal = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);rightReal.addRule(RelativeLayout.ALIGN_PARENT_TOP);rightReal.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);rightReal.setMargins(0,10,15,0);Button confirmBtn = new Button(mContext);LinearLayout.LayoutParams btnParams= new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);confirmBtn.setLayoutParams(btnParams);confirmBtn.setText("ok");confirmBtn.setTextColor(Color.parseColor("#BEBEBE"));confirmBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {String password = passEdtTxt.getText().toString();if ("123456".equals(password)) {if (parentLayout!=null){mWindowManager.removeViewImmediate(parentLayout);//parentLayout = null;}// when password is right -->to deal preparePackageLI(args,res)mIsCanInstall = true;Log.d(TAG,"1111111111 confirm 111:mIsCanInstall:"+mIsCanInstall);/*try {Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");preparePackageLI(args,res);} catch (PrepareFailure prepareFailure) {Log.d(TAG,"============== zhixing preparePackageLI exception return ");Log.d(TAG,"000000000:mIsCanInstall:"+mIsCanInstall);} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}*/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));}// mIsCanInstall = false;Log.d(TAG,"222222222222:mIsCanInstall:"+mIsCanInstall);}else {Toast.makeText(mContext,"PassWorld is Error !",Toast.LENGTH_SHORT).show();}}});reLayout.addView(confirmBtn, rightReal);RelativeLayout.LayoutParams leftReal = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);leftReal.addRule(RelativeLayout.ALIGN_PARENT_TOP);leftReal.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);leftReal.setMargins(15,10,0,0);Button cancelBtn = new Button(mContext);LinearLayout.LayoutParams cancelbtnParams= new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);cancelBtn.setLayoutParams(cancelbtnParams);cancelBtn.setText("cancel");cancelBtn.setTextColor(Color.parseColor("#BEBEBE"));cancelBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (parentLayout!=null){mWindowManager.removeViewImmediate(parentLayout);Log.d(TAG," cancelBtn...");for (InstallRequest request : apkInstallRequests) {restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,new PostInstallData(request.args, request.installResult, null));}}});reLayout.addView(cancelBtn, leftReal);parentLayout.addView(reLayout);try {mWindowManager.addView(parentLayout, mLayoutParams);} catch (WindowManager.BadTokenException e) {e.printStackTrace();}});}
核心思路:
在 核心安装的方法 放到对话框确认的功能下面:
doPreInstall installPackagesTracedLI doPostInstall restoreAndPostInstall
将 恢复状态的功能放到取消对话框取消按钮逻辑里面
restoreAndPostInstall
六、源码流程分析
PMS_342">PMS的复杂性
PMS 本身是核心功能,但特别复杂,一时半会根本无法吃透,但实现相关的需求必须了解需求相关的部分代码。
通过接口、内部类、 代码量直观感受PMS的复杂性。所以我们本身是无法短时间内来真正了解它的,但还是需要继续理解、认知。
大量的类和方法,都不清除具体逻辑,代码量大,涉及范围 管(拷贝、解析、校验、权限、设置、安装… 每部分单独拎出来都需要好好理解下的),导致不知无从下手,肯定懵逼状态。
代码量
PackageManagerService 类的代码量 接近3万行
实现aidl 接口
PackageManagerService extends IPackageManager.Stub
核查代码,你会发现 IPackageManager 文件有199 个方法,也就是 PackageManagerService 光实现这个接口的方法就有199个左右。
PackageManagerService 内部类
PMS_368">七、PMSAnZhuangLiuCheng.html" title=PMS安装流程>PMS安装流程源码分析
可以借鉴 UML 图来看
installStage
- 这个方法是整个流程的入口,关注异步线程 信号 INIT_COPY,其实准备IO copy 操作了
- installStage 方法,其中有一个参数 InstallParams,有InstallParams 和 MultiPackageInstallParams,这里关注下InstallParams ,以它为突破了往下了解
void installStage(InstallParams params) {final Message msg = mHandler.obtainMessage(INIT_COPY);params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));msg.obj = params;...mHandler.sendMessage(msg);}void installStage(InstallParams parent, List<InstallParams> children)throws PackageManagerException {final Message msg = mHandler.obtainMessage(INIT_COPY);final MultiPackageInstallParams params =new MultiPackageInstallParams(parent, children);params.setTraceMethod("installStageMultiPackage").setTraceCookie(System.identityHashCode(params));msg.obj = params;...mHandler.sendMessage(msg);}
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;}。。。。。。。。。。。。。。
HandlerParams
从 installStage 方法中关联到这个 类 HandlerParams。 它是一个抽象类,主要是copy 操作的封装接口,抽象类。它有几个子类,我们这里还是以InstallParams 为例,来分析。
private abstract class HandlerParams {/** User handle for the user requesting the information or installation. */private final UserHandle mUser;String traceMethod;int traceCookie;HandlerParams(UserHandle user) {mUser = user;}UserHandle getUser() {return mUser;}......final void startCopy() {if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);handleStartCopy();handleReturnCode();}abstract void handleStartCopy();abstract void handleReturnCode();}
所以接下来要追的是 InstallParams 的handleStartCopy 方法,也就是 handleStartCopy handleReturnCode 两个方法的具体实现
handleStartCopy
这里方法注释说:
调用远程方法获取包相关信息和安装位置,如果需要 创建安装参数基于安装位置。
/** Invoke remote method to get package information and install* location values. Override install location based on default* policy if needed and then create install arguments based* on the install location.*/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);}
PackageInfoLite
看构造方法,其实就是apk 基本信息: 包名、版本号、版本名称、安装位置 等
private PackageInfoLite(Parcel source) {packageName = source.readString();splitNames = source.createStringArray();versionCode = source.readInt();versionCodeMajor = source.readInt();baseRevisionCode = source.readInt();splitRevisionCodes = source.createIntArray();recommendedInstallLocation = source.readInt();installLocation = source.readInt();multiArch = (source.readInt() != 0);debuggable = (source.readInt() != 0);final int verifiersLength = source.readInt();if (verifiersLength == 0) {verifiers = new VerifierInfo[0];} else {verifiers = new VerifierInfo[verifiersLength];source.readTypedArray(verifiers, VerifierInfo.CREATOR);}}
verifyReplacingVersionCode
其实就是对apk 进行一次验证,验证版本
源码及部分标注如下:
private int verifyReplacingVersionCode(PackageInfoLite pkgLite,long requiredInstalledVersionCode, int installFlags) {String packageName = pkgLite.packageName;synchronized (mLock) {// Package which currently owns the data that the new package will own if installed.// If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg// will be null whereas dataOwnerPkg will contain information about the package// which was uninstalled while keeping its data.AndroidPackage dataOwnerPkg = mPackages.get(packageName);PackageSetting dataOwnerPs = mSettings.getPackageLPr(packageName);if (dataOwnerPkg == null) {if (dataOwnerPs != null) {dataOwnerPkg = dataOwnerPs.getPkg();}}//如果要求安装包对应包名应用之前安装过的版本不为-1,但是dataOwnerPkg为空,说明没有安装过;因此不满足安装要求if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {if (dataOwnerPkg == null) {Slog.w(TAG, "Required installed version code was "+ requiredInstalledVersionCode+ " but package is not installed");return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;}//此前安装过的版本与要求的版本不一致,也不满足要求if (dataOwnerPkg.getLongVersionCode() != requiredInstalledVersionCode) {Slog.w(TAG, "Required installed version code was "+ requiredInstalledVersionCode+ " but actual installed version is "+ dataOwnerPkg.getLongVersionCode());return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;}}//是否允许降级if (dataOwnerPkg != null) {if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,dataOwnerPkg.isDebuggable())) {// Downgrade is not permitted; a lower version of the app will not be allowedtry {checkDowngrade(dataOwnerPkg, pkgLite);} catch (PackageManagerException e) {Slog.w(TAG, "Downgrade detected: " + e.getMessage());return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;}} else if (dataOwnerPs.isSystem()) {// Downgrade is permitted, but system apps can't be downgraded below// the version preloaded onto the system imagefinal PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(dataOwnerPs);if (disabledPs != null) {dataOwnerPkg = disabledPs.getPkg();}if (!Build.IS_DEBUGGABLE && !dataOwnerPkg.isDebuggable()) {// Only restrict non-debuggable builds and non-debuggable version of the apptry {checkDowngrade(dataOwnerPkg, pkgLite);} catch (PackageManagerException e) {String errorMsg = "System app: " + packageName+ " cannot be downgraded to"+ " older than its preloaded version on the system image. "+ e.getMessage();Slog.w(TAG, errorMsg);return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;}}}}}return PackageManager.INSTALL_SUCCEEDED;}
handleReturnCode ->processPendingInstall();
这里做了几个核心工作:
- createInstallArgs :创建安装参数,然后copy 安装文件到零时目录,创建零时文件 ,通过FileInstallArgs
类,来copy apk 文件到 /data/app 目录下面去- releaseCompressedBlocks 释放安装过程中的压缩模块,最终走到 nativeReleaseCompressedBlocks 这个native 方法
- 准备安装,根据 mParentInstallParams是否为空,准备安装 调用 tryProcessInstallRequest 或者 processInstallRequestsAsync 方法
@Overridevoid handleReturnCode() {processPendingInstall();}
看看 processPendingInstall 源码
private void processPendingInstall() {InstallArgs args = createInstallArgs(this);if (mRet == PackageManager.INSTALL_SUCCEEDED) {//copy apk 文件到 /data/app 目录下面去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)));}}
processInstallRequestsAsync()
如上分析,走到 PMS 中的 processInstallRequestsAsync() 方法,准备安装
主要做了四个方面工作:
- doPreInstall : -> cleanUp() /如果前置条件不满足,则移除临时apk相关文件目录
- installPackagesTracedLI :
最终会调用到该函数,该函数会对安装包中所定义的权限、权限组、签名、包名、provider声明等进行校验以及对存放临时apk文件路径进行重命名,如果其中一步不满足则应用会安装失败。如下只保留了大致的函数调用流程。 - doPostInstall -> cleanUp() //如果应用安装失败,则移除零时文件
- restoreAndPostInstall
-> performBackupManagerRestore 备份;
-> mHandler.obtainMessage(POST_INSTALL
//如果没有备份恢复则直接发送安装成功msg进行各种资源回收、安装成功广播发送、observer回调等操作
//如果存在备份恢复,在恢复成功之后会回调到finishPackageInstall函数做上述的处理
具体源码如下,看方法注释 :一个包安装过程的队列操作,会花一点时间。
// 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));}});}
阶段性总结
上面我们从整体大概流程分析下来,已经分析到了 方法:processInstallRequestsAsync ,准备安装了。
在回过头来看看,我们的需求实现方法不就是在这个方法里面,开了一个dialog 密码输入框吗,如果密码输入成功,则进行上面processInstallRequestsAsync 里面的四步方法:doPreInstall ->installPackagesTracedLI->doPostInstall->restoreAndPostInstall 。 也就是在copy apk 文件方法完成后,显示输入密码弹框,进行密码验证。
如果点击取消安装,则执行 restoreAndPostInstall ,清理并进行回调。
doPreInstall
在processInstallRequestsAsync 方法中,
for (InstallRequest request : apkInstallRequests) {request.args.doPreInstall(request.installResult.returnCode);
}
cleanUp();
做得就是 清除操作,如果以前安装失败,清除相关
private boolean cleanUp() {if (codeFile == null || !codeFile.exists()) {return false;}removeCodePathLI(codeFile);return true;}
removeCodePathLI
清除文件
void removeCodePathLI(File codePath) {if (codePath.isDirectory()) {final File codePathParent = codePath.getParentFile();final boolean needRemoveParent = codePathParent.getName().startsWith(RANDOM_DIR_PREFIX);try {final boolean isIncremental = (mIncrementalManager != null && isIncrementalPath(codePath.getAbsolutePath()));if (isIncremental) {if (needRemoveParent) {mIncrementalManager.rmPackageDir(codePathParent);} else {mIncrementalManager.rmPackageDir(codePath);}}mInstaller.rmPackageDir(codePath.getAbsolutePath());if (needRemoveParent) {mInstaller.rmPackageDir(codePathParent.getAbsolutePath());removeCachedResult(codePathParent);}} catch (InstallerException e) {Slog.w(TAG, "Failed to remove code path", e);}} else {codePath.delete();}}
installPackagesTracedLI ->installPackagesLI
installPackagesTracedLI最终会调用到该函数,该函数会对安装包中所定义的权限、权限组、签名、包名、provider声明等进行校验以及对存放临时apk文件路径进行重命名,如果其中一步不满足则应用会安装失败。如下只保留了大致的函数调用流程,这个流程提炼几个核心的方法
方法 | 作用 |
---|---|
preparePackageLI | Prepare 准备:分析任何当前安装状态,分析包并对其进行初始验证 |
scanPackageTracedLI | Scan 扫描:扫描分析准备阶段拿到的包 |
reconcilePackagesLocked | Reconcile 协调:包的扫描结果,用于协调可能向系统中添加的一个或多个包 |
commitPackagesLocked | 提交:提交所有扫描的包并更新系统状态。这是安装流程中唯一可以修改系统状态的地方, |
executePostCommitSteps | executePostCommitSteps 安装APK,并为新的代码路径准备应用程序配置文件,并在此检查是否需要dex优化。 |
private void installPackagesLI(List<InstallRequest> requests) {......boolean success = false;try {for (InstallRequest request : requests) {final PrepareResult prepareResult;try {prepareResult = preparePackageLI(request.args, request.installResult);} catch (PrepareFailure prepareFailure) {......return;} finally {}......try {final ScanResult result = scanPackageTracedLI(prepareResult.packageToScan, prepareResult.parseFlags,prepareResult.scanFlags, System.currentTimeMillis(),request.args.user, request.args.abiOverride);......} catch (PackageManagerException e) {return;}}......synchronized (mLock) {Map<String, ReconciledPackage> reconciledPackages;try {reconciledPackages = reconcilePackagesLocked(reconcileRequest, mSettings.getKeySetManagerService(), mInjector);} catch (ReconcileFailure e) {......return;} finally {}try {commitRequest = new CommitRequest(reconciledPackages,mUserManager.getUserIds());//如果是覆盖安装非系统应用,则卸载旧的应用(移除系统中存储的相关信息)commitPackagesLocked(commitRequest);//到这一步说明应用已经安装成功了success = true;} finally {}}executePostCommitSteps(commitRequest);} finally {......}}
preparePackageLI
- 安装包解析;
- 安装包中所定义的权限、权限组是否合法判断;
- 重命名apk临时文件目录;
- 安装包完整性校验等;
private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)throws PrepareFailure {//一些变量初始化......//scanFlags相关赋值......//其他相关代码......//解析安装包final ParsedPackage parsedPackage;try (PackageParser2 pp = mInjector.getPreparingPackageParser()) {parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);//验证APK包中的Dex Metadata有效性//Dex Metadata位于APK包的Dex文件中,包含了一些关键信息,例如应用的类结构、方法和字段的信息,以及其他与Dalvik虚拟机相关的元数据。系统可以使用Dex Metadata 提前加载类或方法,以加快应用的启动速度和执行性能AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);} catch (PackageParserException e) {} finally {}//临时应用相关......//安装包是共享库相关......//安装包是否在AndroidManifest.xml中配置了仅仅作为测试......//安装包签名设置......//instantApp相关......synchronized (mLock) {//覆盖安装相关......//权限相关判断:不允许定义的权限、权限组已经存在于不同签名的应用包内......}//系统应用相关......//重命名apk临时文件路径if (!args.doRename(res.returnCode, parsedPackage)) {throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");}try {//也属于安装包完成性校验相关,使用的是fs-verity。当启用 fs-verity后,文件系统会对特定文件进行加密和校验,只有在校验通过的情况下,才能读取和执行该文件setUpFsVerityIfPossible(parsedPackage);} catch (InstallerException | IOException | DigestException | NoSuchAlgorithmException e) {}//对应用进行冻结final PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags, "installPackageLI");boolean shouldCloseFreezerBeforeReturn = true;try {......if (replace) {//覆盖安装相关......} else { ......String pkgName1 = parsedPackage.getPackageName();synchronized (mLock) {//判断是否是重复安装......}}shouldCloseFreezerBeforeReturn = false;return new PrepareResult(replace, targetScanFlags, targetParseFlags,existingPackage, parsedPackage, replace, sysPkg,ps, disabledPs);} finally {......}}
doRename
boolean doRename(int status, ParsedPackage parsedPackage) {//判断前置条件是否满足,不满足则清除临时apk相关文件路径......//获取临时apk文件夹父级目录(data/app)final File targetDir = resolveTargetDir();//临时apk文件夹目录final File beforeCodeFile = codeFile;//获取最终存储apk的文件路径final File afterCodeFile = getNextCodePath(targetDir, parsedPackage.getPackageName());//apk对应文件路径是否为增量安装文件路径final boolean onIncremental = mIncrementalManager != null && isIncrementalPath(beforeCodeFile.getAbsolutePath());try {//目标文件路径创建makeDirRecursive(afterCodeFile.getParentFile(), 0775);//如果是增量安装文件路径,那么只做文件link操作if (onIncremental) {mIncrementalManager.linkCodePath(beforeCodeFile, afterCodeFile);//将此前的apk文件路径重命名为新的文件路径名} else {Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());}} catch (IOException | ErrnoException e) {return false;}if (!onIncremental && !SELinux.restoreconRecursive(afterCodeFile)) {return false;}//parsedPackage中apk路径设置为最新的......return true;}//获取存储apk的真正文件路径private File getNextCodePath(File targetDir, String packageName) {SecureRandom random = new SecureRandom();byte[] bytes = new byte[16];File firstLevelDir;do {random.nextBytes(bytes);//最终名类似于:~~AKJbp1qvUKuNjzi_UMpsqQ==String dirName = RANDOM_DIR_PREFIX + Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);//即data/app/~~AKJbp1qvUKuNjzi_UMpsqQ==firstLevelDir = new File(targetDir, dirName);} while (firstLevelDir.exists());random.nextBytes(bytes);String suffix = Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);//文件路径类似于:data/app/~~AKJbp1qvUKuNjzi_UMpsqQ==/包名-Mc2kon90VNr66M5RUQ9R_w==return new File(firstLevelDir, packageName + "-" + suffix);}
scanPackageTracedLI
扫描:扫描分析准备阶段拿到的包
- 对解析的安装包对象中部分属性进行设置,比如非系统应用无法声明受保护广播、非系统应用不能声明为系统核心应用、将未声明exported值的Activity声明为未导出状态等
- 判断应用签名是否有效
- 判断安装包包名是否与其他应用冲突
- 判断安装包中声明的provider是否与其他应用冲突
- 应用进程名合法性判断
- 如果应用是非Privileged类型应用,但其共享uid与某个Privileged类型应用一致,则要求其签名与Android平台签名一致。
private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage,final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,@Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {......scanFlags = adjustScanFlags(scanFlags, pkgSetting, disabledPkgSetting, user, parsedPackage);synchronized (mLock) {......applyPolicy(parsedPackage, parseFlags, scanFlags, mPlatformPackage, isUpdatedSystemApp);assertPackageIsValid(parsedPackage, parseFlags, scanFlags);......}}private static void applyPolicy(ParsedPackage parsedPackage, final int parseFlags,final int scanFlags, AndroidPackage platformPkg, boolean isUpdatedSystemApp) {//安装包扫描为系统应用类型,这里当然不是if ((scanFlags & SCAN_AS_SYSTEM) != 0) {......} else {parsedPackage// 非系统应用无法声明受保护广播.clearProtectedBroadcasts()//非系统应用不能声明为系统核心应用.setCoreApp(false)//非系统应用不能声明常驻.setPersistent(false)//非系统应用无法设置应用的默认安装路径为设备受保护存储.setDefaultToDeviceProtectedStorage(false)//非系统应用无法设置允许应用在设备处于未解锁状态启动.setDirectBootAware(false)//非系统应用无法设置权限的优先级.capPermissionPriorities();}//将未声明exported值的Activity声明为未导出状态if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {parsedPackage.markNotActivitiesAsNotExportedIfSingleUser();}//设置应用是否是如下几种类型parsedPackage.setPrivileged((scanFlags & SCAN_AS_PRIVILEGED) != 0).setOem((scanFlags & SCAN_AS_OEM) != 0).setVendor((scanFlags & SCAN_AS_VENDOR) != 0).setProduct((scanFlags & SCAN_AS_PRODUCT) != 0).setSystemExt((scanFlags & SCAN_AS_SYSTEM_EXT) != 0).setOdm((scanFlags & SCAN_AS_ODM) != 0);//校验应用签名是否于Android签名一致parsedPackage.setSignedWithPlatformKey((PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())|| (platformPkg != null && compareSignatures(platformPkg.getSigningDetails().signatures,parsedPackage.getSigningDetails().signatures) == PackageManager.SIGNATURE_MATCH)));//如果当前是非系统应用则移除如下几个值if (!parsedPackage.isSystem()) {parsedPackage.clearOriginalPackages().setRealPackage(null).clearAdoptPermissions();}PackageBackwardCompatibility.modifySharedLibraries(parsedPackage, isUpdatedSystemApp);}private void assertPackageIsValid(AndroidPackage pkg, final int parseFlags,final int scanFlags)throws PackageManagerException {//其他一些合法性判断 ......//apex不能通过apk的形式进行安装......//判断安装包签名是否有效final KeySetManagerService ksms = mSettings.getKeySetManagerService();ksms.assertScannedPackageValid(pkg);synchronized (mLock) {//判断安装包包名是否是android,如果是则抛出异常......//如果是新安装(非覆盖安装)的应用,但是系统中存在相同包名的应用,则抛出异常......//当前安装包是共享库相关......//判断是否安装现有的安装包,如果是则要求现有的安装包路径与待安装包路径相同,不然则抛出异常......//检测安装包中声明的provider是否与系统即系统中应用已经声明的provider冲突if ((scanFlags & SCAN_NEW_INSTALL) != 0) {mComponentResolver.assertProvidersNotDefined(pkg);}//校验安装包中是否包含主进程,以及其他子进程命名是否符合要求......//一个应用不是Privileged类型应用,但其共享uid与某个Privileged类型应用一致,但是其签名又与Android平台签名不一致,则会直接抛出异常......//其他代码......}}
reconcilePackagesLocked
- 将当前正在安装应用信息合并到存储所有应用基本信息的map中;
- 如果当前正在覆盖安装非系统应用则需要删除原有的应用,这里只是构造了对应的action对象;
- 如果是覆盖安装,则判断新安装的应用签名与原有应用签名是否一致;如果不是覆盖安装且如果当前应用与其他应用共享uid则合并签名;
private static Map<String, ReconciledPackage> reconcilePackagesLocked(final ReconcileRequest request, KeySetManagerService ksms, Injector injector)throws ReconcileFailure {//当前正在安装应用信息,key为对应包名final Map<String, ScanResult> scannedPackages = request.scannedPackages;final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());//request.allPackages表示当前系统中已经安装的应用(key为包名),这里包含了当前正在安装包final ArrayMap<String, AndroidPackage> combinedPackages = new ArrayMap<>(request.allPackages.size() + scannedPackages.size());combinedPackages.putAll(request.allPackages);final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries = new ArrayMap<>();for (String installPackageName : scannedPackages.keySet()) {final ScanResult scanResult = scannedPackages.get(installPackageName);//将正在安装的应用信息替换map中的原有信息combinedPackages.put(scanResult.pkgSetting.name, scanResult.request.parsedPackage);//当前正在安装的应用是否存在被共享的so库,只有系统应用才有......//获取前面应用安装过程中存储的信息final InstallArgs installArgs = request.installArgs.get(installPackageName);final PackageInstalledInfo res = request.installResults.get(installPackageName);final PrepareResult prepareResult = request.preparedPackages.get(installPackageName);final boolean isInstall = installArgs != null;//如果当前正在安装应用,但是存储信息为空,则说明某个步骤存在问题if (isInstall && (res == null || prepareResult == null)) {throw new ReconcileFailure("Reconcile arguments are not balanced for " + installPackageName + "!");}final DeletePackageAction deletePackageAction;// 如果是覆盖安装并且是非系统应用,则需要卸载原有的应用if (isInstall && prepareResult.replace && !prepareResult.system) {final boolean killApp = (scanResult.request.scanFlags & SCAN_DONT_KILL_APP) == 0;final int deleteFlags = PackageManager.DELETE_KEEP_DATA | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);deletePackageAction = mayDeletePackageLocked(res.removedInfo, prepareResult.originalPs, prepareResult.disabledPs,deleteFlags, null);if (deletePackageAction == null) {throw new ReconcileFailure(PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,"May not delete " + installPackageName + " to replace");}} else {deletePackageAction = null;}//临时变量赋值......//如果是应用升级判断新安装应用签名是与原有的应用签名一致if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {} else {//签名不一致则抛出异常......}signingDetails = parsedPackage.getSigningDetails();} else {//如果正在安装应用签名与某些应用共享uid,则合并他们的签名信息......}result.put(installPackageName, new ReconciledPackage(request, installArgs, scanResult.pkgSetting, res, request.preparedPackages.get(installPackageName), scanResult, deletePackageAction, allowedSharedLibInfos, signingDetails, sharedUserSignaturesChanged, removeAppKeySetData));}//共享库相关......return result;}
commitPackagesLocked
Commit 提交:提交所有扫描的包并更新系统状态。这是安装流程中唯一可以修改系统状态的地方;
必须在此阶段之前确定所有的可预测的错误;会根据安装的包名确定如果是系统app的更新操作,则会对旧的进行替换
@GuardedBy("mLock")private void commitPackagesLocked(final CommitRequest request) {// TODO: remove any expected failures from this method; this should only be able to fail due// to unavoidable errors (I/O, etc.)for (ReconciledPackage reconciledPkg : request.reconciledPackages.values()) {final ScanResult scanResult = reconciledPkg.scanResult;final ScanRequest scanRequest = scanResult.request;final ParsedPackage parsedPackage = scanRequest.parsedPackage;final String packageName = parsedPackage.getPackageName();final PackageInstalledInfo res = reconciledPkg.installResult;if (reconciledPkg.prepareResult.replace) {AndroidPackage oldPackage = mPackages.get(packageName);// Set the update and install timesPackageSetting deletedPkgSetting = getPackageSetting(oldPackage.getPackageName());reconciledPkg.pkgSetting.firstInstallTime = deletedPkgSetting.firstInstallTime;reconciledPkg.pkgSetting.lastUpdateTime = System.currentTimeMillis();res.removedInfo.broadcastAllowList = mAppsFilter.getVisibilityAllowList(reconciledPkg.pkgSetting, request.mAllUsers, mSettings.getPackagesLocked());if (reconciledPkg.prepareResult.system) {// Remove existing system packageremovePackageLI(oldPackage, true);if (!disableSystemPackageLPw(oldPackage)) {// We didn't need to disable the .apk as a current system package,// which means we are replacing another update that is already// installed. We need to make sure to delete the older one's .apk.res.removedInfo.args = createInstallArgsForExisting(oldPackage.getPath(),getAppDexInstructionSets(AndroidPackageUtils.getPrimaryCpuAbi(oldPackage,deletedPkgSetting),AndroidPackageUtils.getSecondaryCpuAbi(oldPackage,deletedPkgSetting)));} else {res.removedInfo.args = null;}} else {try {// Settings will be written during the call to updateSettingsLI().executeDeletePackageLIF(reconciledPkg.deletePackageAction, packageName,true, request.mAllUsers, false, parsedPackage);} catch (SystemDeleteException e) {if (mIsEngBuild) {throw new RuntimeException("Unexpected failure", e);// ignore; not possible for non-system app}}// Successfully deleted the old package; proceed with replace.// If deleted package lived in a container, give users a chance to// relinquish resources before killing.if (oldPackage.isExternalStorage()) {if (DEBUG_INSTALL) {Slog.i(TAG, "upgrading pkg " + oldPackage+ " is ASEC-hosted -> UNAVAILABLE");}final int[] uidArray = new int[]{oldPackage.getUid()};final ArrayList<String> pkgList = new ArrayList<>(1);pkgList.add(oldPackage.getPackageName());sendResourcesChangedBroadcast(false, true, pkgList, uidArray, null);}// Update the in-memory copy of the previous code paths.PackageSetting ps1 = mSettings.getPackageLPr(reconciledPkg.prepareResult.existingPackage.getPackageName());if ((reconciledPkg.installArgs.installFlags & PackageManager.DONT_KILL_APP)== 0) {if (ps1.mOldCodePaths == null) {ps1.mOldCodePaths = new ArraySet<>();}Collections.addAll(ps1.mOldCodePaths, oldPackage.getBaseApkPath());if (oldPackage.getSplitCodePaths() != null) {Collections.addAll(ps1.mOldCodePaths, oldPackage.getSplitCodePaths());}} else {ps1.mOldCodePaths = null;}if (reconciledPkg.installResult.returnCode== PackageManager.INSTALL_SUCCEEDED) {PackageSetting ps2 = mSettings.getPackageLPr(parsedPackage.getPackageName());if (ps2 != null) {res.removedInfo.removedForAllUsers = mPackages.get(ps2.name) == null;}}}}AndroidPackage pkg = commitReconciledScanResultLocked(reconciledPkg, request.mAllUsers);updateSettingsLI(pkg, reconciledPkg.installArgs, request.mAllUsers, res);final PackageSetting ps = mSettings.getPackageLPr(packageName);if (ps != null) {res.newUsers = ps.queryInstalledUsers(mUserManager.getUserIds(), true);ps.setUpdateAvailable(false /*updateAvailable*/);}if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {updateSequenceNumberLP(ps, res.newUsers);updateInstantAppInstallerLocked(packageName);}}ApplicationPackageManager.invalidateGetPackagesForUidCache();}
executePostCommitSteps
如果是直接安装新包,会为新的代码路径准备应用程序配置文件;如果是替换安装,其主要过程为更新设置,清除原有的某些APP数据,重新生成相关的app数据目录等步骤,同时要区分系统应用替换和非系统应用替换。而安装新包,则直接更新设置,生成APP数据即可。
当调用到该函数,说明应用已经安装成功,后续则需要为该应用创建私有目录,以及进行dex优化。
private void executePostCommitSteps(CommitRequest commitRequest) {final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) {final boolean instantApp = ((reconciledPkg.scanResult.request.scanFlags & PackageManagerService.SCAN_AS_INSTANT_APP) != 0);final AndroidPackage pkg = reconciledPkg.pkgSetting.pkg;final String packageName = pkg.getPackageName();final String codePath = pkg.getPath();final boolean onIncremental = mIncrementalManager != null && isIncrementalPath(codePath);if (onIncremental) {......incrementalStorages.add(storage);}//为应用创建私有目录,data/data(data/user/0)目录下创建prepareAppDataAfterInstallLIF(pkg);//判断是否需要清除应用数据if (reconciledPkg.prepareResult.clearCodeCache) {clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE| FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);}//覆盖安装相关if (reconciledPkg.prepareResult.replace) {mDexManager.notifyPackageUpdated(pkg.getPackageName(),pkg.getBaseApkPath(), pkg.getSplitCodePaths());}//准备正在安装应用配置文件,用于后续dex优化mArtManagerService.prepareAppProfiles(pkg,resolveUserIds(reconciledPkg.installArgs.user.getIdentifier()),true);//根据安装场景选择dex优化策略,比如单个应用安装、多个应用同时安装场景final int compilationReason = mDexManager.getCompilationReasonForInstallScenario(reconciledPkg.installArgs.mInstallScenario);//判断应用是否是通过其他设备迁移或者备份恢复进行安装final boolean isBackupOrRestore = reconciledPkg.installArgs.installReason == INSTALL_REASON_DEVICE_RESTORE || reconciledPkg.installArgs.installReason == INSTALL_REASON_DEVICE_SETUP;//dex优化相关flagfinal int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);DexoptOptions dexoptOptions = new DexoptOptions(packageName, compilationReason, dexoptFlags);//判断是否需要做dex优化,需要满足如下条件//(1)非instant类型应用或者instant类型应用dex优化开关开启//(2)非debug类型应用//(3)非增量安装//(4)系统支持dex优化final boolean performDexopt = (!instantApp || Global.getInt(mContext.getContentResolver(),Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) && !pkg.isDebuggable() && (!onIncremental) && dexoptOptions.isCompilationEnabled();if (performDexopt) {//开始dex优化......}//如果前面该包dex优化失败,则从失败列表中移除,以再次进行dex优化BackgroundDexOptService.notifyPackageChanged(packageName);notifyPackageChangeObserversOnUpdate(reconciledPkg);}//增量安装相关waitForNativeBinariesExtraction(incrementalStorages);}
prepareAppDataAfterInstallLIF
private void prepareAppDataAfterInstallLIF(AndroidPackage pkg) {..... for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) {......if (ps.getInstalled(user.id)) {// TODO: when user data is locked, mark that we're still dirtyprepareAppDataLIF(pkg, user.id, flags);}}
}private void prepareAppDataLIF(AndroidPackage pkg, int userId, int flags) {if (pkg == null) {Slog.wtf(TAG, "Package was null!", new Throwable());return;}// 调用prepareAppDataLeafLIF方法prepareAppDataLeafLIF(pkg, userId, flags);
}private void prepareAppDataLeafLIF(AndroidPackage pkg, int userId, int flags) {......try {// 调用Install守护进程的入口ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,appId, seInfo, pkg.getTargetSdkVersion());} catch (InstallerException e) {if (pkg.isSystem()) {destroyAppDataLeafLIF(pkg, userId, flags);try {ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,appId, seInfo, pkg.getTargetSdkVersion());} catch (InstallerException e2) {......}}}
}
doPostInstall
其实就是收尾工作,成功或者失败都需要删除以前的零时文件,如下源码
int doPostInstall(int status, int uid) {if (status == PackageManager.INSTALL_SUCCEEDED) {cleanUp(move.fromUuid);} else {cleanUp(move.toUuid);}return status;}private boolean cleanUp(String volumeUuid) {final String toPathName = new File(move.fromCodePath).getName();final File codeFile = new File(Environment.getDataAppDirectory(volumeUuid),toPathName);Slog.d(TAG, "Cleaning up " + move.packageName + " on " + volumeUuid);final int[] userIds = mUserManager.getUserIds();synchronized (mInstallLock) {// Clean up both app data and code// All package moves are frozen until finished// We purposefully exclude FLAG_STORAGE_EXTERNAL here, since// this task was only focused on moving data on internal storage.// We don't want ART profiles cleared, because they don't move,// so we would be deleting the only copy (b/149200535).final int flags = FLAG_STORAGE_DE | FLAG_STORAGE_CE| Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES;for (int userId : userIds) {try {mInstaller.destroyAppData(volumeUuid, move.packageName, userId, flags, 0);} catch (InstallerException e) {Slog.w(TAG, String.valueOf(e));}}removeCodePathLI(codeFile);}return true;}
restoreAndPostInstall
如下代码判断是否需要对应用数据进行备份恢复(之前系统备份过该应用数据),如果需要则首先进行备份恢复;如果不需要则进行最后的资源回收、安装成功广播发送等操作。
private void restoreAndPostInstall(int userId, PackageInstalledInfo res, @Nullable PostInstallData data) {final boolean update = res.removedInfo != null && res.removedInfo.removedPackage != null;boolean doRestore = !update && res.pkg != null;int token;if (mNextInstallToken < 0) mNextInstallToken = 1;token = mNextInstallToken++;if (data != null) {mRunningInstalls.put(token, data);}//备份恢复if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {if (res.freezer != null) {res.freezer.close();}doRestore = performBackupManagerRestore(userId, token, res);}if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {doRestore = performRollbackManagerRestore(userId, token, res, data);}//如果没有备份恢复则直接发送安装成功msg进行各种资源回收、安装成功广播发送、observer回调等操作//如果存在备份恢复,在恢复成功之后会回调到finishPackageInstall函数做上述的处理if (!doRestore) {Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);mHandler.sendMessage(msg);}}我们来关注下 POST_INSTALL```javacase POST_INSTALL: {....if (data != null && data.mPostInstallRunnable != null) {data.mPostInstallRunnable.run();} else if (data != null && data.args != null) {InstallArgs args = data.args;PackageInstalledInfo parentRes = data.res;final boolean killApp = (args.installFlags& PackageManager.INSTALL_DONT_KILL_APP) == 0;final boolean virtualPreload = ((args.installFlags& PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);handlePackagePostInstall(parentRes, killApp, virtualPreload,didRestore, args.installSource.installerPackageName, args.observer,args.mDataLoaderType);} else if (DEBUG_INSTALL) {// No post-install when we run restore from installExistingPackageForUserSlog.i(TAG, "Nothing to do for post-install token " + msg.arg1);}Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "postInstall", msg.arg1);} break;
handlePackagePostInstall
private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,boolean killApp, String[] grantedPermissions,boolean launchedForRestore, String installerPackage,IPackageInstallObserver2 installObserver) {//【1】当安装结果为 success 后,会进入后续的处理!if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {//【1.1】如果是 move pacakge,那么发送 removed 广播!if (res.removedInfo != null) {res.removedInfo.sendPackageRemovedBroadcasts(killApp);}//【1.2】如果安装时指定了授予运行时权限,并且应用的目标 sdk 支持运行时权限,那就授予运行时权限!if (grantPermissions && res.pkg.applicationInfo.targetSdkVersion>= Build.VERSION_CODES.M) {grantRequestedRuntimePermissions(res.pkg, res.newUsers, grantedPermissions);}//【1.3】判断安装方式,是更新安装,还是全新安装!// 我们知道,如果 res.removedInfo 不为 null 的话,一定是覆盖更新!final boolean update = res.removedInfo != null&& res.removedInfo.removedPackage != null;//【1.4】如果被 disable 的特权应用之前没有子包,这是第一次有子包,那么我们会授予新的子包// 运行时权限,如果旧的特权应用之前已经授予!if (res.pkg.parentPackage != null) {synchronized (mPackages) {grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw(res.pkg);}}synchronized (mPackages) {mEphemeralApplicationRegistry.onPackageInstalledLPw(res.pkg);}final String packageName = res.pkg.applicationInfo.packageName;Bundle extras = new Bundle(1);extras.putInt(Intent.EXTRA_UID, res.uid);//【1.5】决定在那些 user 下是第一次安装,那些用户下是覆盖更新!int[] firstUsers = EMPTY_INT_ARRAY;int[] updateUsers = EMPTY_INT_ARRAY;if (res.origUsers == null || res.origUsers.length == 0) {firstUsers = res.newUsers;} else {// res.newUsers 表示本次安装新增加的目标 user!// res.origUsers 标志之前安装的目标 user!for (int newUser : res.newUsers) {boolean isNew = true;for (int origUser : res.origUsers) {if (origUser == newUser) {isNew = false;break;}}if (isNew) {firstUsers = ArrayUtils.appendInt(firstUsers, newUser);} else {updateUsers = ArrayUtils.appendInt(updateUsers, newUser);}}}//【1.5】发送 ADD 和 REPLACE 广播,如果不是 Ephemeral app!if (!isEphemeral(res.pkg)) {mProcessLoggingHandler.invalidateProcessLoggingBaseApkHash(res.pkg.baseCodePath);//【1.5.1】给第一次安装的用户发送 ACTION_PACKAGE_ADDED 广播,不带 EXTRA_REPLACING 属性!sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,extras, 0 /*flags*/, null /*targetPackage*/,null /*finishedReceiver*/, firstUsers);//【1.5.2】给覆盖更新的用户发送 ACTION_PACKAGE_ADDED 广播,带 EXTRA_REPLACING 属性!if (update) {extras.putBoolean(Intent.EXTRA_REPLACING, true);}sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,extras, 0 /*flags*/, null /*targetPackage*/,null /*finishedReceiver*/, updateUsers);//【1.5.3】给覆盖更新的用户发送 ACTION_PACKAGE_REPLACED / ACTION_MY_PACKAGE_REPLACED 广播!if (update) {sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,packageName, extras, 0 /*flags*/,null /*targetPackage*/, null /*finishedReceiver*/,updateUsers);sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,null /*package*/, null /*extras*/, 0 /*flags*/,packageName /*targetPackage*/,null /*finishedReceiver*/, updateUsers);} else if (launchedForRestore && !isSystemApp(res.pkg)) {//【1.5.4】如果是第一次安装,同时我们要做一个恢复操作,并且 apk 不是系统应用// 那么我们会发送 ACTION_PACKAGE_FIRST_LAUNCH 广播!if (DEBUG_BACKUP) {Slog.i(TAG, "Post-restore of " + packageName+ " sending FIRST_LAUNCH in " + Arrays.toString(firstUsers));}sendFirstLaunchBroadcast(packageName, installerPackage, firstUsers);}//【1.5.5】如果该 apk 处于 forward locked 或者处于外置存储中,那么会给所有的用户发送// 资源变化的广播!if (res.pkg.isForwardLocked() || isExternal(res.pkg)) {if (DEBUG_INSTALL) {Slog.i(TAG, "upgrading pkg " + res.pkg+ " is ASEC-hosted -> AVAILABLE");}final int[] uidArray = new int[]{res.pkg.applicationInfo.uid};ArrayList<String> pkgList = new ArrayList<>(1);pkgList.add(packageName);sendResourcesChangedBroadcast(true, true, pkgList, uidArray, null);}}//【1.6】针对 firstUsers 做一些权限恢复和默认浏览器设置!if (firstUsers != null && firstUsers.length > 0) {synchronized (mPackages) {for (int userId : firstUsers) {//【1.6.1】默认浏览器设置!!if (packageIsBrowser(packageName, userId)) {mSettings.setDefaultBrowserPackageNameLPw(null, userId);}//【1.6.2】处理那些正在等待或者需要恢复的运行时权限授予!mSettings.applyPendingPermissionGrantsLPw(packageName, userId);}}}EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,getUnknownSourcesSettings());// 触发 GC 回收资源!Runtime.getRuntime().gc();//【5.8.1.1】移除掉更新后的旧 apk!if (res.removedInfo != null && res.removedInfo.args != null) {synchronized (mInstallLock) {res.removedInfo.args.doPostDeleteLI(true);}}}//【*5.8.2】通知观察者安装的结果,这里的 installObserver 是我们之前创建的 localObsever!!if (installObserver != null) {try {Bundle extras = extrasForInstallResult(res);installObserver.onPackageInstalled(res.name, res.returnCode,res.returnMsg, extras);} catch (RemoteException e) {Slog.i(TAG, "Observer no longer exists.");}}
}
总结
- 从一个实际的安装加锁输入密码功能作为切入点,熟悉了安装流程。
- 上面还是仅仅从大方向,走了一遍安装流程,实际上细节太多。
- 回过头来,实现这个需求,这个方案还是比较好的。
其它有专家说在扫描或者准备阶段实现输入框,其实很不可取,极易出错。锁机制和各种零时文件删除状态,会导致各种问题。 最好还是在processInstallRequestsAsync 作为点,进行功能需求实现。