PMS是Android系统服务中的包管理机制,服务于APK的完整生命周期,主要包括APK的解析,安装,运行,卸载。
PMS整个生命周期比较庞大,可以先从安装和卸载入手,看PMS是如何工作。
APK的安装
下面这段代码估计大家都很熟悉,就是用做在应用中做自更新或者安装其他应用使用
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri apkUri = FileProvider.getUriForFile(context, UIUtils.getContext().getPackageName(), file);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else {
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
InstallStart
application/vnd.android.package-archive
上面这段主要做隐试匹配,会匹配到 com.android.packageInstaller
进程中InstallStart(InstallStart是一个Activity)
InstallStart其实也没干什么正事,一般来说就三点
- 对TargetSDKVersion进行校验
- 对
Manifest.permission.REQUEST_INSTALL_PACKAGES
进行校验 - 对传递进来的uri进行校验,如果以Schema以Content开头,就跳转到InstallStaging中。
断点看一下吧
我们传递进来uri的值
content://com.android.externalstorage.documents/document/primary%3Aapp-release.apk
InstallStaging
先来看看这个Activity长什么样子
InstallStaging中主要逻辑在onResume()方法中开启异步任务
@Override
protected void onResume() {
super.onResume();
// This is the first onResume in a single life of the activity
if (mStagingTask == null) {
// File does not exist, or became invalid
if (mStagedFile == null) {
// Create file delayed to be able to show error
try {
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
}
具体看下StagingAsyncTask的任务
private final class StagingAsyncTask extends AsyncTask {
@Override
protected Boolean doInBackground(Uri... params) {
if (params == null || params.length <= 0) {
return false;
}
Uri packageUri = params[0];
// 这里的packageUri 是 content://com.android.externalstorage.documents/document/primary%3Aapp-release.apk
try (InputStream in = getContentResolver().openInputStream(packageUri)) {
// Despite the comments in ContentResolver#openInputStream the returned stream can
// be null.
if (in == null) {
return false;
}
try (OutputStream out = new FileOutputStream(mStagedFile)) {
byte[] buffer = new byte[1024 * 1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
// Be nice and respond to a cancellation
if (isCancelled()) {
return false;
}
out.write(buffer, 0, bytesRead);
}
}
} catch (IOException | SecurityException | IllegalStateException e) {
Log.w(LOG_TAG, "Error staging apk from content URI", e);
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
// Now start the installation again from a file
Intent installIntent = new Intent(getIntent());
installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
// 这里需要注意,已经将content开头的地址转换成了file开头的文件地址
installIntent.setData(Uri.fromFile(mStagedFile));
if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
}
installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(installIntent);
InstallStaging.this.finish();
} else {
// 这里如果出错的话会出现一个比较熟悉的界面
showError();
}
}
}
其实就是将APK文件拷贝一份,具体拷贝到哪里呢
拷贝到这里 /data/user/com.android.packageInstaller/no_backup/
拷贝完了之后跳转到DeleteStagedFileOnResult
ps 这里如果拷贝错误的话会出现一个比较熟悉界面
DeleteStagedFileOnResult
这个类貌似是android 10新增的,在android9还没看到这个类,内容比较简单
public class DeleteStagedFileOnResult extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Intent installIntent = new Intent(getIntent());
// 跳转到PackageInstallerActivity,这个页面很熟悉了
installIntent.setClass(this, PackageInstallerActivity.class);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(installIntent, 0);
}
}
// 这里是重点,主要是用来接收安装的成功或失败,不管咋样,都把刚复制的文件干掉
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
File sourceFile = new File(getIntent().getData().getPath());
sourceFile.delete();
setResult(resultCode, data);
finish();
}
}
PackageInstallerActivity
还是先看看这个页面长什么样子
PackageInstallerActivity中逻辑比较多
先看看在onCreate方法中初始化的主要变量
// 这个是向应用的进程提供功能,但是功能最终都有PMS提供,当前的进程还是处在com.android.packageInstaller中
mPm = getPackageManager();
// AIDL接口,用于和PMS进行通信
mIpm = AppGlobals.getPackageManager();
// 动态权限检测
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
// 提供安装,卸载的功能
mInstaller = mPm.getPackageInstaller();
// 谁唤起了这次安装,如果是从应用安装,那么就是应用相关的信息,如果是从文件管理器点击安装,那么都是空的
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
PackageInstaller.SessionParams.UID_UNKNOWN);
看一下传过来的apk文件地址
file:///data/user_de/0/com.android.packageinstaller/no_backup/package5541502234249167159.apk
得到地址后,对APK文件开始解析,具体实现在processPackageUri方法中
ApkAssets apkAssets = ApkAssets.loadFromPath("/data/user_de/0/com.android.packageinstaller/no_backup/package5541502234249167159.apk")
XmlResourceParser parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME);
构造资源路径
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
// 这里的assets资源路径总共有两个
// ApkAssets{path=/system/framework/framework-res.apk},应用中有很多使用系统资源
// ApkAssets{path=/data/user_de/0/com.android.packageinstaller/no_backup/package4780013443826073855.apk}
final Resources res = new Resources(assets, mMetrics, null);
最后看下解析的结果
activitys // 数量和类名
applicationInfo
baseCodePath
mCompileSdkVersion
mVersionName
mVersionCode
manifestPackageName
packageName
permissionGroups
permissions
use32bitAbi = false
codePath = /data/user_de/0/com.android.packageinstaller/no_backup/package4780013443826073855.apk
providers
receivers
.....
基本上得到在清单文件注册的所有信息
点击继续之后会出现确认安装界面
在看看点击安装之后的逻辑
private void startInstall() {
// Start subactivity to actually install the application
Intent newIntent = new Intent();
// 应用的ApplicationInfo
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
// mPackageURI = file:///data/user_de/0/com.android.packageinstaller/no_backup/package479667155487808554.apk
newIntent.setData(mPackageURI);
// 跳转到InstallInstalling中
newIntent.setClass(this, InstallInstalling.class);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
}
if (mReferrerURI != null) {
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
}
if (installerPackageName != null) {
newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
installerPackageName);
}
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
}
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
startActivity(newIntent);
finish();
}
逻辑比较简单,就是将解析出来的ApplicationInfo和之前传递过来的PackageURI继续传递到InstallInstalling中.
这里应该会跟现在网上已经有的描述不一样,android 10在应用安装过程不会展示需要系统权限
InstallInstalling
这里差不多是在应用安装在com.android.packageinstaller进程的最后一步。
首先注册了注册一个接受应用安装成功或者失败的广播
try {
mInstallId = InstallEventReceiver
.addObserver(this, EventResultPersister.GENERATE_NEW_ID,
this::launchFinishBasedOnResult);
} catch (EventResultPersister.OutOfIdsException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
这里需要重点看一下这个广播
@NonNull private static EventResultPersister getReceiver(@NonNull Context context) {
synchronized (sLock) {
if (sReceiver == null) {
sReceiver = new EventResultPersister(
// 这里返回了一个File 具体路径是
// /data/user_de/0/com.android.packageinstaller/no_backup/install_results.xml
// install_results.xml中记录的每次安装的installId,这次installId每次安装的时候递增
TemporaryFileManager.getInstallStateFile(context));
}
}
return sReceiver;
}
我们安装应用的时候通常会有进度呢,到现在还没看到进度的回调呢?
进度的回调肯定是PMS回调给InstallInstalling来展示的,接着往下看,果然在后面建立了于PMS通信
try {
// 这种SessionID的系统应用和SystemServer的服务中很常见,因为系统服务要服务于所有应用,所以每个应用调用系统的服务的时候,在每个系统服务那里都会有一个sessionID的列表,标识着与各种应用的连接
mSessionId = getPackageManager().getPackageInstaller().createSession(params);
} catch (IOException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
mSessionCallback = new InstallSessionCallback();
// InstallSessionCallback这里就是PMS的进度回调
@Override
public void onProgressChanged(int sessionId, float progress) {
if (sessionId == mSessionId) {
ProgressBar progressBar = requireViewById(R.id.progress);
progressBar.setMax(Integer.MAX_VALUE);
progressBar.setProgress((int) (Integer.MAX_VALUE * progress));
}
}
接着就是将APK传递给PMS,怎么传递呢,直接传递路径?NO
PackageInstaller.Session session;
try {
// 通过上一步创建的sessionID,创建一个安装的会话
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
} catch (IOException e) {
return null;
}
session.setStagingProgress(0);
try {
File file = new File(mPackageURI.getPath());
try (InputStream in = new FileInputStream(file)) {
long sizeBytes = file.length();
// 将字节流写入PMS中,具体写到什么位置了呢?下文有介绍
try (OutputStream out = session
.openWrite("PackageInstaller", 0, sizeBytes)) {
byte[] buffer = new byte[1024 * 1024];
while (true) {
int numRead = in.read(buffer);
if (numRead == -1) {
session.fsync(out);
break;
}
if (isCancelled()) {
session.close();
break;
}
out.write(buffer, 0, numRead);
if (sizeBytes > 0) {
float fraction = ((float) numRead / (float) sizeBytes);
session.addProgress(fraction);
}
}
}
}
return session;
} catch (IOException | SecurityException e) {
Log.e(LOG_TAG, "Could not write package", e);
session.close();
return null;
} finally {
synchronized (this) {
isDone = true;
notifyAll();
}
}
APK传输给PMS之后下一步肯定是通知PMS我传递完了,你可以开始安装了,接着看下怎么通知了
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntent.setPackage(getPackageName());
// 还记得这个mInstallId之前在哪里创建的吗,忘记了往前翻翻,这里将mInstallId也传递给了PMS,PMS拿到这个mInstallID,在应用安装完成后就可以发送一个广播,在把这个mInstallID回写,packageinstaller进程就可以单独去更新这个被安装应用的状态
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
// PendingIntent 延时意图,满足某个条件的时候才触发
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
说了半天其实和PMS还没有半毛钱关系,只是说安装前的准备工作,收拢总结一下在com.android.packageinstaller中流程的工作
PMS中的安装流程
在介绍PMS之前,先介绍PNS
接着上面IPC调用开始分析
session.commit(pendingIntent.getIntentSender());
对应的远程调用在 PackageInstallerSession中的commit方法
@Override
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
if (hasParentSessionId()) {
throw new IllegalStateException(
"Session " + sessionId + " is a child of multi-package session "
+ mParentSessionId + " and may not be committed directly.");
}
if (!markAsCommitted(statusReceiver, forTransfer)) {
return;
}
... // 省略部分代码
...
// 发送了一个MSG_COMMIT
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
这个Handler通过Callback的方式处理消息,在SystemServer进程断点看一下
在handleCommit方法中终于调用PMS中的installStage方法
// 安装信息都在ActiveInstallSession中,ActiveInstallSession具体有哪些值呢
// mInstallPackageName = com.android.packageinstaller
// mInstallUid = 10048
// mPackageName = com.android.sourcecodedebug
// mStageDir = /data/app/vmdl1397850738.tmp
// .....
// 之前说过跨进程将APK写入到PMS中,其实就是写入到 /data/app/vmdl1397850738.tmp,它是个文件夹,里面有两个文件,后面的数字是随机生成的,不同的应用不一样
// /data/app/vmdl1397850738.tmp/lib 估计能猜出有啥作用
// /data/app/vmdl1397850738.tmp/base.apk
void installStage(ActiveInstallSession activeInstallSession) {
final Message msg = mHandler.obtainMessage(INIT_COPY);
final InstallParams params = new InstallParams(activeInstallSession);
msg.obj = params;
// 这个mHandler是PMS中的核心,下文会细说
// 发送了一个INIT_COPY消息
mHandler.sendMessage(msg);
}
PackageHandler是处理PMS中的所有逻辑的核心,应用的安装,卸载都会触发到这个Handler,消息的种类太多,先接着上文的INIC_COPY消息来看
// 看着好像比较简单,直接调用了InstallParams的startCopy方法,看名字像是做了一次拷贝,其实不完全是
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
if (params != null) {
params.startCopy();
}
break;
}
InstallParams.startCopy中就两个方法调用
- handleStartCopy
- handleReturnCode
先看handleStartCopy
/*
*
* 方法很长,省略了部分代码,关键其实就做了两件事
1 轻量解析 /data/app/vmdl1397850738.tmp/base.apk文件,将各种信息包装成PackageInfoLite
2 确定应用的安装位置,三方应用,系统应用,InstantApp都有对应的规则
* {@link PackageHelper#RECOMMEND_INSTALL_INTERNAL} to install on internal storage, 等于1
* {@link PackageHelper#RECOMMEND_INSTALL_EXTERNAL} to install on external media, 等于2
* {@link PackageHelper#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors, 等于-1
*/
public void handleStartCopy() {
int ret = PackageManager.INSTALL_SUCCEEDED;
....
....
PackageInfoLite pkgLite = null;
pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
origin.resolvedPath, installFlags, packageAbiOverride);
/*
* 判断空间够不够,不光是APK文件,还有从APK解压释放的so文件
*
*/
if (!origin.staged && pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
// TODO: focus freeing disk space on the target device
final 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,
origin.resolvedPath, installFlags, packageAbiOverride);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to free cache", e);
}
}
}
if (ret == PackageManager.INSTALL_SUCCEEDED) {
// 解析完就可以知道应用的类型,获取PMS推荐的安装目录,一般都是1 内置存储
int loc = pkgLite.recommendedInstallLocation;
.....
.....
}
mRet = ret;
}
接着看handlerReturnCode
@Override
void handleReturnCode() {
if (mVerificationCompleted && mEnableRollbackCompleted) {
.....
.....
if (mRet == PackageManager.INSTALL_SUCCEEDED) {
// copyAPK并没有开始复制,
mRet = mArgs.copyApk();
}
processPendingInstall(mArgs, mRet);
}
}
copyApk()并没有真正的复制,只是将FileInstallArgs对象中的两个成员变量复制,两个变量值都是一样的
/data/app/vmdl424643763.tmp
最终的实现在processPendingInstall方法中,通过PackageHandler post一个Runnable
private void processInstallRequestsAsync(boolean success,
List installRequests) {
mHandler.post(() -> {
if (success) {
for (InstallRequest request : installRequests) {
// 对安装目录做一下清理
request.args.doPreInstall(request.installResult.returnCode);
}
synchronized (mInstallLock) {
// 重点,安装过程中最核心的方法
installPackagesTracedLI(installRequests);
}
for (InstallRequest request : installRequests) {
// 安装完成之后的工作
request.args.doPostInstall(
request.installResult.returnCode, request.installResult.uid);
}
}
});
}
installPackagesTracedLi方法代码比较多,主要执行了下面四个逻辑
1 全量解析APK文件
2 校验APK签名,如果之前有安装过会和之前的APK签名做比对
3 释放apk文件中的so库,释放到/data/app/vmdl1397850738.tmp/lib**
4 对目录和文件充新命名,将/data/app/vmdl903343092.tmp重新命名为/data/app/包名/,将/data/app/vmdl1397850738.tmp/base.apk重新命名为/data/app/包名/base.apk**5 重新命名后的地址赋值给PackageParser.Package
6 更新mPackages和mSettings中的应用信息
最后一个目录比较熟悉吧!
这里描述的安装流程和现有的很多不一致的地方,主要是android 10的源代码在PMS安装这一块改动挺大,摒弃了PackageHandler中的MCS_BOUND消息。