前言
之前和大家聊完了PMS的启动原理,本文继续介绍当进行安装的时候,PMS做了什么事情。
如果遇到什么问题欢迎来到本文https://www.jianshu.com/p/f2afa66f1547进行讨论。
正文
先来看看在Android 中如何吊起安装界面:
public static void installApk(Context context, String apkPath) {
if (context == null || TextUtils.isEmpty(apkPath)) {
return;
}
File file = new File(apkPath);
Intent intent = new Intent(Intent.ACTION_VIEW);
//判读版本是否在7.0以上
if (Build.VERSION.SDK_INT >= 24) {
Log.v(TAG,"7.0以上,正在安装apk...");
Uri apkUri = FileProvider.getUriForFile(context, "com.yjy.fileprovider", file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
Log.v(TAG,"7.0以下,正在安装apk...");
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
context.startActivity(intent);
}
能看到其实是启动了一个特殊的意图,设置了data和type:application/vnd.android.package-archive
.
我们可以看看AndroidManifest.xml
/packages/apps/PackageInstaller/AndroidManifest.xml
<activity android:name=".InstallStart"
android:exported="true"
android:excludeFromRecents="true">
<intent-filter android:priority="1">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="package" />
<data android:scheme="content" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
其实除了通过application/vnd.android.package-archive
能打开之外,还能通过action为android.intent.action.INSTALL_PACKAGE
以及"android.content.pm.action.CONFIRM_PERMISSIONS
能打开安装的Activity对象InstallStart
。
InstallStart 获取需要安装的包
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIPackageManager = AppGlobals.getPackageManager();
Intent intent = getIntent();
String callingPackage = getCallingPackage();
// If the activity was started via a PackageInstaller session, we retrieve the calling
// package from that session
int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
if (callingPackage == null && sessionId != -1) {
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
}
final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
final int originatingUid = getOriginatingUid(sourceInfo);
boolean isTrustedSource = false;
if (sourceInfo != null
&& (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
}
if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
if (targetSdkVersion < 0) {
mAbortInstall = true;
} else if (targetSdkVersion >= Build.VERSION_CODES.O && !declaresAppOpPermission(
originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
...
mAbortInstall = true;
}
}
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
return;
}
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
|| packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT,
PackageManager.INSTALL_FAILED_INVALID_URI);
setResult(RESULT_FIRST_USER, result);
nextActivity = null;
}
}
if (nextActivity != null) {
startActivity(nextActivity);
}
finish();
}
很简单,这个过程做了如下事情:
- 1.判断当前的apk包是否是可信任来源,如果是不可信任来源,且没有申请不可信任来源包安装权限,则会强制结束当前的Activity。
- 2.此时就会从data从获取到我们传递进来的apk包的地址uri,并且设置下一个启动的Activity为InstallStaging
- 3.启动InstallStaging 这个Activity
值得注意的是:
- 1.如果我们使用另一种安装方式,设置的是Intent的action为
ACTION_CONFIRM_PERMISSIONS
(也就是android.content.pm.action.CONFIRM_PERMISSIONS
),则会直接启动PackageInstallerActivity - 1.如果此时我们的uri不是
file
和content
开头的协议,但是是package
协议也是直接启动PackageInstallerActivity
InstallStaging 包安装确认界面
文件:/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
这是一个带着旋转进度条和一个取消按钮的页面,核心方法在onResume
protected void onResume() {
super.onResume();
if (mStagingTask == null) {
if (mStagedFile == null) {
try {
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
}
在这里构建了一个临时文件:
public static File getStagedFile(@NonNull Context context) throws IOException {
return File.createTempFile("package", ".apk", context.getNoBackupFilesDir());
}
这个文件建立在当前应用私有目录下的no_backup文件夹上。也就是/data/no_backup/packagexxx.apk
这个临时文件。
并启动StagingAsyncTask这个AysncTask任务。这个AysncTask就没必要聊了,都是老生常谈的东西了,现在都是用LoaderManager了。如果实在好奇可以阅读我很早以前写过的AsyncTask与LoaderManager。
StagingAsyncTask
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected Boolean doInBackground(Uri... params) {
if (params == null || params.length <= 0) {
return false;
}
Uri packageUri = params[0];
try (InputStream in = getContentResolver().openInputStream(packageUri)) {
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) {
if (isCancelled()) {
return false;
}
out.write(buffer, 0, bytesRead);
}
}
} catch (IOException | SecurityException | IllegalStateException e) {
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
Intent installIntent = new Intent(getIntent());
installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
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();
}
}
}
1.doInBackground 是指在异步线程池中处理的事务。doInBackground中实际上就是把uri中需要安装的apk拷贝到临时文件中。
2.onPostExecute 当拷贝任务处理完之后,就会把当前的临时文件作为Intent的参数,跳转到DeleteStagedFileOnResult中。
DeleteStagedFileOnResult
public class DeleteStagedFileOnResult extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Intent installIntent = new Intent(getIntent());
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安装失败了,就会退出PackageInstallerActivity界面在DeleteStagedFileOnResult的onActivityResult中删除这个临时文件。
PackageInstallerActivity 安装界面
先来看看onCreate
protected void onCreate(Bundle icicle) {
super.onCreate(null);
if (icicle != null) {
mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
}
mPm = getPackageManager();
mIpm = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
final Intent intent = getIntent();
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
PackageInstaller.SessionParams.UID_UNKNOWN);
mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
? getPackageNameForUid(mOriginatingUid) : null;
final Uri packageUri;
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
finish();
return;
}
mSessionId = sessionId;
packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessionId = -1;
packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
...
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
...
checkIfAllowedAndInitiateInstall();
}
1.能看到如果使用
application/vnd.android.package-archive
另外两种模式进行安装,那么就会获取保存在Intent中的ApplicationInfo对象,获取其中的resolvedBaseCodePath也就是代码文件路径2.如果是使用
application/vnd.android.package-archive
则通过Intent的getData获取到保存在其中的临时文件。3.processPackageUri 扫描路径对应的包
4.checkIfAllowedAndInitiateInstall 启动安装
processPackageUri
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
switch (scheme) {
case SCHEME_PACKAGE: {
try {
mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS
| PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
if (mPkgInfo == null) {
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} break;
case ContentResolver.SCHEME_FILE: {
File sourceFile = new File(packageUri.getPath());
PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
if (parsed == null) {
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
default: {
throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
}
}
return true;
}
先把uri缓存到全局的mPackageURI
在这个过程中,会解析两种uri:
- 1.
package
协议开头的uri:这种uri会通过PMS的getPackageInfo通过getSchemeSpecificPart获取对应apk文件对应的PackageInfo信息 - 2.
file
协议开头的uri,则使用PMS的generatePackageInfo方法获取apk文件中的package信息
PackageParser generatePackageInfo
PMS的generatePackageInfo,最终会调用PackageParser的generatePackageInfo
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
Set<String> grantedPermissions, PackageUserState state, int userId) {
if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) {
return null;
}
PackageInfo pi = new PackageInfo();
pi.packageName = p.packageName;
pi.splitNames = p.splitNames;
pi.versionCode = p.mVersionCode;
pi.versionCodeMajor = p.mVersionCodeMajor;
pi.baseRevisionCode = p.baseRevisionCode;
pi.splitRevisionCodes = p.splitRevisionCodes;
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
pi.installLocation = p.installLocation;
pi.isStub = p.isStub;
pi.coreApp = p.coreApp;
if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0
|| (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
pi.requiredForAllUsers = p.mRequiredForAllUsers;
}
pi.restrictedAccountType = p.mRestrictedAccountType;
pi.requiredAccountType = p.mRequiredAccountType;
pi.overlayTarget = p.mOverlayTarget;
pi.overlayCategory = p.mOverlayCategory;
pi.overlayPriority = p.mOverlayPriority;
pi.mOverlayIsStatic = p.mOverlayIsStatic;
pi.compileSdkVersion = p.mCompileSdkVersion;
pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
if ((flags&PackageManager.GET_GIDS) != 0) {
pi.gids = gids;
}
if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) {
int N = p.configPreferences != null ? p.configPreferences.size() : 0;
if (N > 0) {
pi.configPreferences = new ConfigurationInfo[N];
p.configPreferences.toArray(pi.configPreferences);
}
N = p.reqFeatures != null ? p.reqFeatures.size() : 0;
if (N > 0) {
pi.reqFeatures = new FeatureInfo[N];
p.reqFeatures.toArray(pi.reqFeatures);
}
N = p.featureGroups != null ? p.featureGroups.size() : 0;
if (N > 0) {
pi.featureGroups = new FeatureGroupInfo[N];
p.featureGroups.toArray(pi.featureGroups);
}
}
if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
final int N = p.activities.size();
if (N > 0) {
int num = 0;
final ActivityInfo[] res = new ActivityInfo[N];
for (int i = 0; i < N; i++) {
final Activity a = p.activities.get(i);
if (state.isMatch(a.info, flags)) {
res[num++] = generateActivityInfo(a, flags, state, userId);
}
}
pi.activities = ArrayUtils.trimToSize(res, num);
}
}
if ((flags & PackageManager.GET_RECEIVERS) != 0) {
final int N = p.receivers.size();
if (N > 0) {
int num = 0;
final ActivityInfo[] res = new ActivityInfo[N];
for (int i = 0; i < N; i++) {
final Activity a = p.receivers.get(i);
if (state.isMatch(a.info, flags)) {
res[num++] = generateActivityInfo(a, flags, state, userId);
}
}
pi.receivers = ArrayUtils.trimToSize(res, num);
}
}
if ((flags & PackageManager.GET_SERVICES) != 0) {
final int N = p.services.size();
if (N > 0) {
int num = 0;
final ServiceInfo[] res = new ServiceInfo[N];
for (int i = 0; i < N; i++) {
final Service s = p.services.get(i);
if (state.isMatch(s.info, flags)) {
res[num++] = generateServiceInfo(s, flags, state, userId);
}
}
pi.services = ArrayUtils.trimToSize(res, num);
}
}
if ((flags & PackageManager.GET_PROVIDERS) != 0) {
final int N = p.providers.size();
if (N > 0) {
int num = 0;
final ProviderInfo[] res = new ProviderInfo[N];
for (int i = 0; i < N; i++) {
final Provider pr = p.providers.get(i);
if (state.isMatch(pr.info, flags)) {
res[num++] = generateProviderInfo(pr, flags, state, userId);
}
}
pi.providers = ArrayUtils.trimToSize(res, num);
}
}
if ((flags&PackageManager.GET_INSTRUMENTATION) != 0) {
int N = p.instrumentation.size();
if (N > 0) {
pi.instrumentation = new InstrumentationInfo[N];
for (int i=0; i<N; i++) {
pi.instrumentation[i] = generateInstrumentationInfo(
p.instrumentation.get(i), flags);
}
}
}
if ((flags&PackageManager.GET_PERMISSIONS) != 0) {
int N = p.permissions.size();
if (N > 0) {
pi.permissions = new PermissionInfo[N];
for (int i=0; i<N; i++) {
pi.permissions[i] = generatePermissionInfo(p.permissions.get(i), flags);
}
}
N = p.requestedPermissions.size();
if (N > 0) {
pi.requestedPermissions = new String[N];
pi.requestedPermissionsFlags = new int[N];
for (int i=0; i<N; i++) {
final String perm = p.requestedPermissions.get(i);
pi.requestedPermissions[i] = perm;
pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED;
if (grantedPermissions != null && grantedPermissions.contains(perm)) {
pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_GRANTED;
}
}
}
}
if ((flags&PackageManager.GET_SIGNATURES) != 0) {
if (p.mSigningDetails.hasPastSigningCertificates()) {
pi.signatures = new Signature[1];
pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0];
} else if (p.mSigningDetails.hasSignatures()) {
int numberOfSigs = p.mSigningDetails.signatures.length;
pi.signatures = new Signature[numberOfSigs];
System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs);
}
}
if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
if (p.mSigningDetails != SigningDetails.UNKNOWN) {
pi.signingInfo = new SigningInfo(p.mSigningDetails);
} else {
pi.signingInfo = null;
}
}
return pi;
}
能看到generatePackageInfo的方法实际上只解析了四大组件的内容以及权限相关的标签。还没有上一篇文章中扫描apk做的事情齐全。不过倒是拿到了apk包所有运行需要的基础信息。
checkIfAllowedAndInitiateInstall
private void checkIfAllowedAndInitiateInstall() {
...
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
initiateInstall();
} else {
....
}
}
核心就是这样一行,允许安装未知来源的文件判断,initiateInstall
private void initiateInstall() {
String pkgName = mPkgInfo.packageName;
String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
if (oldName != null && oldName.length > 0 && oldName[0] != null) {
pkgName = oldName[0];
mPkgInfo.packageName = pkgName;
mPkgInfo.applicationInfo.packageName = pkgName;
}
try {
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}
startInstallConfirm();
}
通过PMS的getApplicationInfo方法,获取当前Android系统中是否已经安装了当前的app,如果能找到mAppInfo对象,说明是安装了,则调用startInstallConfirm刷新按钮的内容,是安装
还是更新
.
private void startInstallConfirm() {
// We might need to show permissions, load layout with permissions
if (mAppInfo != null) {
bindUi(R.layout.install_confirm_perm_update, true);
} else {
bindUi(R.layout.install_confirm_perm, true);
}
...
}
PackageInstallerActivity 点击安装按钮
public void onClick(View v) {
if (v == mOk) {
if (mOk.isEnabled()) {
if (mOkCanInstall || mScrollView == null) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
startInstall();
}
} else {
mScrollView.pageScroll(View.FOCUS_DOWN);
}
}
} else if (v == mCancel) {
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
finish();
}
}
这个过程就很简单了,如果点击了安装的按钮,则调用startInstall启动安装等待界面。
private void startInstall() {
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
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);
startActivity(newIntent);
finish();
}
从这一段代码,就能看到把mPackageURI放入Intent的Data中,并且启动了InstallInstalling 这个Activity。
InstallInstalling 安装等待界面
文件:/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
先来看看InstallInstalling的onCreate方法
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.install_installing);
ApplicationInfo appInfo = getIntent()
.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
mPackageURI = getIntent().getData();
if ("package".equals(mPackageURI.getScheme())) {
try {
getPackageManager().installExistingPackage(appInfo.packageName);
launchSuccess();
} catch (PackageManager.NameNotFoundException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
} else {
final File sourceFile = new File(mPackageURI.getPath());
PackageUtil.initSnippetForNewApp(this, PackageUtil.getAppSnippet(this, appInfo,
sourceFile), R.id.app_snippet);
if (savedInstanceState != null) {
...
} else {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.installFlags = PackageManager.INSTALL_FULL_APP;
params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
params.originatingUri = getIntent()
.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
UID_UNKNOWN);
params.installerPackageName =
getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
File file = new File(mPackageURI.getPath());
try {
PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0);
params.setAppPackageName(pkg.packageName);
params.setInstallLocation(pkg.installLocation);
params.setSize(
PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
} catch (PackageParser.PackageParserException e) {
params.setSize(file.length());
} catch (IOException e) {
params.setSize(file.length());
}
try {
mInstallId = InstallEventReceiver
.addObserver(this, EventResultPersister.GENERATE_NEW_ID,
this::launchFinishBasedOnResult);
} catch (EventResultPersister.OutOfIdsException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
try {
mSessionId = getPackageManager().getPackageInstaller().createSession(params);
} catch (IOException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
}
...
mSessionCallback = new InstallSessionCallback();
}
}
这个过程分为两种:
1.如果是
package
协议,则调用PMS的installExistingPackage方法,并且调用launchSuccess,告诉用户这个apk已经安装成功了2.剩下只可能是
file
协议,则获取PackageUri对应的临时文件,先通过PackageParser.parsePackageLite 解析出Package包的中apk的AndroidManifest的versionCode,安装路径等包含一个apk包中基础数据的PackageLite。3.注册InstallEventReceiver 一个安装完成的广播监听。
4.调用PMS的getPackageInstaller.createSession 创建一个Session,并获得对应的sessionID。
核心的后面的2两点,我们依次看看都做了什么
InstallEventReceiver.addObserver
public class InstallEventReceiver extends BroadcastReceiver {
private static final Object sLock = new Object();
private static EventResultPersister sReceiver;
@NonNull private static EventResultPersister getReceiver(@NonNull Context context) {
synchronized (sLock) {
if (sReceiver == null) {
sReceiver = new EventResultPersister(
TemporaryFileManager.getInstallStateFile(context));
}
}
return sReceiver;
}
@Override
public void onReceive(Context context, Intent intent) {
getReceiver(context).onEventReceived(context, intent);
}
static int addObserver(@NonNull Context context, int id,
@NonNull EventResultPersister.EventResultObserver observer)
throws EventResultPersister.OutOfIdsException {
return getReceiver(context).addObserver(id, observer);
}
}
这个类很简答,实际上就是实例化一个静态EventResultPersister对象后,并且调用EventResultPersister的addObserver,把监听事件注册到里面。注意InstallEventReceiver接受的广播是com.android.packageinstaller.ACTION_INSTALL_COMMIT
EventResultPersister 实例化
EventResultPersister(@NonNull File resultFile) {
mResultsFile = new AtomicFile(resultFile);
mCounter = GENERATE_NEW_ID + 1;
try (FileInputStream stream = mResultsFile.openRead()) {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, StandardCharsets.UTF_8.name());
nextElement(parser);
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
String tagName = parser.getName();
if ("results".equals(tagName)) {
mCounter = readIntAttribute(parser, "counter");
} else if ("result".equals(tagName)) {
int id = readIntAttribute(parser, "id");
int status = readIntAttribute(parser, "status");
int legacyStatus = readIntAttribute(parser, "legacyStatus");
String statusMessage = readStringAttribute(parser, "statusMessage");
mResults.put(id, new EventResult(status, legacyStatus, statusMessage));
} else {
throw new Exception("unexpected tag");
}
nextElement(parser);
}
} catch (Exception e) {
mResults.clear();
writeState();
}
}
这个过程也很简单,就是读取no_backup
文件夹下的install_results.xml
xml文件
public static File getInstallStateFile(@NonNull Context context) {
return new File(context.getNoBackupFilesDir(), "install_results.xml");
}
install_results.xml
实际上可以获取到Android每一次安装后记录,每新增一个apk的安装都会递增id。并且该id就对应上不同安装的结果,如status
,legacyStatus
,statusMessage
都会记录在这个文件中。
当安装完后,分发的消息就会同步到这个文件中,这样就能保证需要分发的安装完成的事件可以保证分发出去。
EventResultPersister addObserver
int addObserver(int id, @NonNull EventResultObserver observer)
throws OutOfIdsException {
synchronized (mLock) {
int resultIndex = -1;
if (id == GENERATE_NEW_ID) {
id = getNewId();
} else {
resultIndex = mResults.indexOfKey(id);
}
// Check if we can instantly call back
if (resultIndex >= 0) {
EventResult result = mResults.valueAt(resultIndex);
observer.onResult(result.status, result.legacyStatus, result.message);
mResults.removeAt(resultIndex);
writeState();
} else {
mObservers.put(id, observer);
}
}
return id;
}
此时的id是GENERATE_NEW_ID,因此会通过getNewId对应的id会从0依次递增,并且把EventResultObserver记录id和observer,等到后面安装完成后,在通过id获取observer回调到外面的Activity。
如果id不为GENERATE_NEW_ID,则尝试从mResults中获取,如果能找到,说明需要告诉监听者这个apk已经安装好了,则回调EventResultObserver的onResult方法。
ContextImpl getPackageManager 获取ApplicationPackageManager
在继续聊getPackageInstaller之前,先来看看getPackageManager在App进程端实际上访问的是哪个对象?
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
能看到实际上App进程是不会直接访问PMS的,而是通过一层ApplicationPackageManager对PMS进行方法。
ApplicationPackageManager getPackageInstaller
public PackageInstaller getPackageInstaller() {
synchronized (mLock) {
if (mInstaller == null) {
try {
mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
mContext.getPackageName(), mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return mInstaller;
}
}
能看到这里实际上拿的是PMS的Binder方法,通过getPackageInstaller获取到PackageInstallerService后,再包装到PackageInstaller。App进程正是通过PackageInstaller进一步的访问SystemServer端的PackageInstallerService对象。
PMS的getPackageInstaller.createSession
文件:/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public IPackageInstaller getPackageInstaller() {
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
return null;
}
return mInstallerService;
}
能看到实际上是获取了mInstallerService,而这个Service在上一篇文章和大家聊过了但是并没有重点聊,实际上就是PackageInstallerService对象。
PackageInstallerService的初始化
public PackageInstallerService(Context context, PackageManagerService pm) {
mContext = context;
mPm = pm;
mPermissionManager = LocalServices.getService(PermissionManagerInternal.class);
mInstallThread = new HandlerThread(TAG);
mInstallThread.start();
mInstallHandler = new Handler(mInstallThread.getLooper());
mCallbacks = new Callbacks(mInstallThread.getLooper());
mSessionsFile = new AtomicFile(
new File(Environment.getDataSystemDirectory(), "install_sessions.xml"),
"package-session");
mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
mSessionsDir.mkdirs();
}
public void systemReady() {
mAppOps = mContext.getSystemService(AppOpsManager.class);
synchronized (mSessions) {
readSessionsLocked();
reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, false /*isInstant*/);
reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL, true /*isInstant*/);
final ArraySet<File> unclaimedIcons = newArraySet(
mSessionsDir.listFiles());
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
unclaimedIcons.remove(buildAppIconFile(session.sessionId));
}
for (File icon : unclaimedIcons) {
icon.delete();
}
}
}
- 1.在构造函数中,先初始化了如下2个文件:
/data/system/install_sessions.xml
-
/data/system/install_sessions/
目录
其次就是PMS的systemReady会调用PackageInstallerService的systemReady。
- 2.调用readSessionsLocked方法,读取缓存在xml中的数据,并且依据这些数据生成PackageInstallerSession对象
private void readSessionsLocked() {
mSessions.clear();
FileInputStream fis = null;
try {
fis = mSessionsFile.openRead();
final XmlPullParser in = Xml.newPullParser();
in.setInput(fis, StandardCharsets.UTF_8.name());
int type;
while ((type = in.next()) != END_DOCUMENT) {
if (type == START_TAG) {
final String tag = in.getName();
if (PackageInstallerSession.TAG_SESSION.equals(tag)) {
final PackageInstallerSession session;
try {
session = PackageInstallerSession.readFromXml(in, mInternalCallback,
mContext, mPm, mInstallThread.getLooper(), mSessionsDir);
} catch (Exception e) {
continue;
}
final long age = System.currentTimeMillis() - session.createdMillis;
final boolean valid;
if (age >= MAX_AGE_MILLIS) {
valid = false;
} else {
valid = true;
}
if (valid) {
mSessions.put(session.sessionId, session);
} else {
addHistoricalSessionLocked(session);
}
mAllocatedSessions.put(session.sessionId, true);
}
}
}
} catch (FileNotFoundException e) {
// Missing sessions are okay, probably first boot
} catch (IOException | XmlPullParserException e) {
Slog.wtf(TAG, "Failed reading install sessions", e);
} finally {
IoUtils.closeQuietly(fis);
}
}
能看到这个过程通过解析xml的数据后,就能通过这些数据生成一个个PackageInstallerSession对象,如果还有效则保存在mSessions。接着不管是否有效都会保存在mAllocatedSessions,告诉Android系统已经实例化了这么多的Seesion对象。
- 3.reconcileStagesLocked 清除安装阶段性的临时文件
private void reconcileStagesLocked(String volumeUuid, boolean isEphemeral) {
final File stagingDir = buildStagingDir(volumeUuid, isEphemeral);
final ArraySet<File> unclaimedStages = newArraySet(
stagingDir.listFiles(sStageFilter));
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
unclaimedStages.remove(session.stageDir);
}
for (File stage : unclaimedStages) {
synchronized (mPm.mInstallLock) {
mPm.removeCodePathLI(stage);
}
}
}
private static final FilenameFilter sStageFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return isStageName(name);
}
};
public static boolean isStageName(String name) {
final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
final boolean isLegacyContainer = name.startsWith("smdl2tmp");
return isFile || isContainer || isLegacyContainer;
}
private File buildStagingDir(String volumeUuid, boolean isEphemeral) {
return Environment.getDataAppDirectory(volumeUuid);
}
实际上在volumeUuid对应的参数就是
StorageManager.UUID_PRIVATE_INTERNAL
而他是一个为null的static final的成员变量。因此实际上就是构建了一个/data/app
的目录。拿到该目录下所有的数据临时数据,这些临时数据一般为(
vmdlxx.tmp
,smdlxxx.tmp
,smdl2tmpxxx
),从这个集合中挑选出没有session对应的临时文件并删除。遍历这些临时数据,调用PMS的removeCodePathLI,移除缓存在PMS这些临时文件对应的扫描结果
4.遍历
/data/system/install_sessions/
中所有的文件,并且从unclaimedIcons集合移除每一个Session对象的app_icon.sessionId.png
也就是临时的安装应用图标,最后删除剩下哪些没有sessionId对应的临时应用图标文件。
private File buildAppIconFile(int sessionId) {
return new File(mSessionsDir, "app_icon." + sessionId + ".png");
}
PackageInstaller createSession
/frameworks/base/core/java/android/content/pm/PackageInstaller.java
public int createSession(@NonNull SessionParams params) throws IOException {
try {
final String installerPackage;
if (params.installerPackageName == null) {
installerPackage = mInstallerPackageName;
} else {
installerPackage = params.installerPackageName;
}
return mInstaller.createSession(params, installerPackage, mUserId);
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
这个过程很简单,就是调用了PackageInstallerService的createSession,而这个方法最终会调用到createSessionInternal中
PackageInstallerService createSessionInternal
private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
throws IOException {
...
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
} else {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) !=
PackageManager.PERMISSION_GRANTED) {
mAppOps.checkPackage(callingUid, installerPackageName);
}
params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0
&& !mPm.isCallerVerifier(callingUid)) {
params.installFlags &= ~PackageManager.INSTALL_VIRTUAL_PRELOAD;
}
}
...
// Defensively resize giant app icons
if (params.appIcon != null) {
final ActivityManager am = (ActivityManager) mContext.getSystemService(
Context.ACTIVITY_SERVICE);
final int iconSize = am.getLauncherLargeIconSize();
if ((params.appIcon.getWidth() > iconSize * 2)
|| (params.appIcon.getHeight() > iconSize * 2)) {
params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize,
true);
}
}
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
....
} else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
....
} else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
....
} else {
params.setInstallFlagsInternal();
final long ident = Binder.clearCallingIdentity();
try {
params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
...
final int sessionId;
final PackageInstallerSession session;
synchronized (mSessions) {
final int activeCount = getSessionCount(mSessions, callingUid);
...
final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);
...
sessionId = allocateSessionIdLocked();
}
final long createdMillis = System.currentTimeMillis();
// We're staging to exactly one location
File stageDir = null;
String stageCid = null;
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
final boolean isInstant =
(params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant);
} else {
stageCid = buildExternalStageCid(sessionId);
}
session = new PackageInstallerSession(mInternalCallback, mContext, mPm,
mInstallThread.getLooper(), sessionId, userId, installerPackageName, callingUid,
params, createdMillis, stageDir, stageCid, false, false);
synchronized (mSessions) {
mSessions.put(sessionId, session);
}
mCallbacks.notifySessionCreated(session.sessionId, session.userId);
writeSessionsAsync();
return sessionId;
}
1.从ActivityManager 获取全局配置下,当前配置下的dp数值以及图标大小,从而计算出应用图标对应的bitmap大小,并提前申请出内存出来.
2.PackageHelper.resolveInstallVolume 拿到磁盘中最合适安装的扇区的uuid,保存到SessionParams的volumeUuid中。setInstallFlagsInternal设置installFlags带上INSTALL_INTERNAL
public void setInstallFlagsInternal() {
installFlags |= PackageManager.INSTALL_INTERNAL;
installFlags &= ~PackageManager.INSTALL_EXTERNAL;
}
3.从Int的最大数值内挑选一个没有使用过的随机数,成为新的PackageInstallerSession的id。
4.如果PackageManager.INSTALL_INTERNAL不为空,则会通过buildStageDir方法生成一个特殊文件:
private File buildStageDir(String volumeUuid, int sessionId, boolean isEphemeral) {
final File stagingDir = buildStagingDir(volumeUuid, isEphemeral);
return new File(stagingDir, "vmdl" + sessionId + ".tmp");
}
private File buildStagingDir(String volumeUuid, boolean isEphemeral) {
return Environment.getDataAppDirectory(volumeUuid);
}
public static File getDataAppDirectory(String volumeUuid) {
return new File(getDataDirectory(volumeUuid), "app");
}
public static File getDataDirectory(String volumeUuid) {
if (TextUtils.isEmpty(volumeUuid)) {
return DIR_ANDROID_DATA;
} else {
return new File("/mnt/expand/" + volumeUuid);
}
}
当不传入需要安装的扇区uuid时候则为/data/app/vmdlsessionId.tmp
文件,但是如果传入了,整个文件就变成了/data/app/volumeUuid/vmdlsessionId.tmp
注意此时如果能在私有磁盘(也就是内置存储器)内找到剩余空间进行安装/data/app/vmdlsessionId.tmp
,如果找不到则安装到/mnt/expand/volumeUuid/vmdlsessionId.tmp
(也就是类似U盘的外部存储器)
5.否则则生成一个新的名字
smdlsessionid.tmp
名字保存到PackageInstallerSession中。6.生成一个PackageInstallerSession对象,并保存到mSessions这个Map中,唤醒监听者notifySessionCreated的Session创建成功的监听
7.writeSessionsAsync 通过IO线程,调用PackageInstallerSession的write方法,把数据全部写入到PackageInstallerSession的
private void writeSessionsLocked() {
FileOutputStream fos = null;
try {
fos = mSessionsFile.startWrite();
XmlSerializer out = new FastXmlSerializer();
out.setOutput(fos, StandardCharsets.UTF_8.name());
out.startDocument(null, true);
out.startTag(null, TAG_SESSIONS);
final int size = mSessions.size();
for (int i = 0; i < size; i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
session.write(out, mSessionsDir);
}
out.endTag(null, TAG_SESSIONS);
out.endDocument();
mSessionsFile.finishWrite(fos);
} catch (IOException e) {
if (fos != null) {
mSessionsFile.failWrite(fos);
}
}
}
其实这个过程就很简单了,就是往/data/system/install_sessions.xml
中写入当前PackageInstallerSession的配置数据。
InstallInstalling onResume
onCreate考察完了,我们来看看onResume.
protected void onResume() {
super.onResume();
if (mInstallingTask == null) {
PackageInstaller installer = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
if (sessionInfo != null && !sessionInfo.isActive()) {
mInstallingTask = new InstallingAsyncTask();
mInstallingTask.execute();
} else {
// we will receive a broadcast when the install is finished
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
}
}
}
在onCreate的过程通过PackageInstallerService的CreateSession方法获取到每一个Session对应的id,此时通过id反过来找到每一个SessionInfo,并开始执行这个InstallingAsyncTask这个AsyncTask。
InstallingAsyncTask的异步执行
@Override
protected PackageInstaller.Session doInBackground(Void... params) {
PackageInstaller.Session session;
try {
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();
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) {
session.close();
return null;
} finally {
synchronized (this) {
isDone = true;
notifyAll();
}
}
}
实际上,InstallingAsyncTask的异步线程的工作就是在通过PackageInstallerService.openSession方法打开一个session,并通过FileInputStream读取临时apk文件的数据流,并通过session.openWrite ,把临时apk数据往session的OutputSream中写入。
依次看看openSession和openWrite都做了什么?
PackageInstaller openSession
public @NonNull Session openSession(int sessionId) throws IOException {
try {
return new Session(mInstaller.openSession(sessionId));
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
很简单,调用了PackageInstallerService的openSession,获得IPackageInstallerSession这个Binder对象,并且包装成一个Session对象返回。
PackageInstallerService openSession
private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException {
synchronized (mSessions) {
final PackageInstallerSession session = mSessions.get(sessionId);
...
session.open();
return session;
}
}
核心还是PacakgeInstallerSession的open方法。
public void open() throws IOException {
if (mActiveCount.getAndIncrement() == 0) {
mCallback.onSessionActiveChanged(this, true);
}
boolean wasPrepared;
synchronized (mLock) {
wasPrepared = mPrepared;
if (!mPrepared) {
if (stageDir != null) {
prepareStageDir(stageDir);
} else {
throw new IllegalArgumentException("stageDir must be set");
}
mPrepared = true;
}
}
if (!wasPrepared) {
mCallback.onSessionPrepared(this);
}
}
static void prepareStageDir(File stageDir) throws IOException {
try {
Os.mkdir(stageDir.getAbsolutePath(), 0755);
Os.chmod(stageDir.getAbsolutePath(), 0755);
} catch (ErrnoException e) {
throw new IOException("Failed to prepare session dir: " + stageDir, e);
}
if (!SELinux.restorecon(stageDir)) {
throw new IOException("Failed to restorecon session dir: " + stageDir);
}
}
这里实际上就是给安装的目录做了准备的工作,把/mnt/expand/volumeUuid/vmdlsessionId.tmp
这个文件设置权限为0755,也就是当前用户允许操作所有权限,同用户组或者其他用户的权限只能读执行。
PackageInstaller.Session openWrite
public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes,
long lengthBytes) throws IOException {
try {
if (ENABLE_REVOCABLE_FD) {
return new ParcelFileDescriptor.AutoCloseOutputStream(
mSession.openWrite(name, offsetBytes, lengthBytes));
} else {
final ParcelFileDescriptor clientSocket = mSession.openWrite(name,
offsetBytes, lengthBytes);
return new FileBridge.FileBridgeOutputStream(clientSocket);
}
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
核心还是调用PackageInstallerSession的openWrite后,获取到ParcelFileDescriptor对象通过ParcelFileDescriptor.AutoCloseOutputStream或者FileBridge.FileBridgeOutputStream封装成OutputStream。
而PackageInstallerSession的openWrite核心会调用到doWriteInternal。这里我们先只关注ENABLE_REVOCABLE_FD为false的情况。
PackageInstallerSession doWriteInternal
private ParcelFileDescriptor doWriteInternal(String name, long offsetBytes, long lengthBytes,
ParcelFileDescriptor incomingFd) throws IOException {
final RevocableFileDescriptor fd;
final FileBridge bridge;
final File stageDir;
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotSealedLocked("openWrite");
if (PackageInstaller.ENABLE_REVOCABLE_FD) {
fd = new RevocableFileDescriptor();
bridge = null;
mFds.add(fd);
} else {
fd = null;
bridge = new FileBridge();
mBridges.add(bridge);
}
stageDir = resolveStageDirLocked();
}
try {
if (!FileUtils.isValidExtFilename(name)) {
throw new IllegalArgumentException("Invalid name: " + name);
}
final File target;
final long identity = Binder.clearCallingIdentity();
try {
target = new File(stageDir, name);
} finally {
Binder.restoreCallingIdentity(identity);
}
final FileDescriptor targetFd = Os.open(target.getAbsolutePath(),
O_CREAT | O_WRONLY, 0644);
Os.chmod(target.getAbsolutePath(), 0644);
...
if (offsetBytes > 0) {
Os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
}
...
if (incomingFd != null) {
...
} else if (PackageInstaller.ENABLE_REVOCABLE_FD) {
...
} else {
bridge.setTargetFile(targetFd);
bridge.start();
return new ParcelFileDescriptor(bridge.getClientSocket());
}
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
}
1.就是根据stageDir目录下生成一个特殊的文件,其实就是
/data/app/vmdlsessionId.tmp/PackageInstaller
.并调用Os.open创建这个文件,这个文件的权限是0644,也就是本用户能读写,其他和同一个用户组只能读。2.创建一个新的FileBridge对象,把需要写入的对象设置到FileBridge中,并调用start方法,最后获取FileBridge的getClientSocket,设置到ParcelFileDescriptor。
FileBridge 原理
public class FileBridge extends Thread {
private static final String TAG = "FileBridge";
// TODO: consider extending to support bidirectional IO
private static final int MSG_LENGTH = 8;
/** CMD_WRITE [len] [data] */
private static final int CMD_WRITE = 1;
/** CMD_FSYNC */
private static final int CMD_FSYNC = 2;
/** CMD_CLOSE */
private static final int CMD_CLOSE = 3;
private FileDescriptor mTarget;
private final FileDescriptor mServer = new FileDescriptor();
private final FileDescriptor mClient = new FileDescriptor();
private volatile boolean mClosed;
public FileBridge() {
try {
Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mServer, mClient);
} catch (ErrnoException e) {
...
}
}
public boolean isClosed() {
return mClosed;
}
public void forceClose() {
IoUtils.closeQuietly(mTarget);
IoUtils.closeQuietly(mServer);
IoUtils.closeQuietly(mClient);
mClosed = true;
}
public void setTargetFile(FileDescriptor target) {
mTarget = target;
}
public FileDescriptor getClientSocket() {
return mClient;
}
@Override
public void run() {
final byte[] temp = new byte[8192];
try {
while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) {
final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN);
if (cmd == CMD_WRITE) {
// Shuttle data into local file
int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN);
while (len > 0) {
int n = IoBridge.read(mServer, temp, 0, Math.min(temp.length, len));
...
IoBridge.write(mTarget, temp, 0, n);
len -= n;
}
} else if (cmd == CMD_FSYNC) {
Os.fsync(mTarget);
IoBridge.write(mServer, temp, 0, MSG_LENGTH);
} else if (cmd == CMD_CLOSE) {
Os.fsync(mTarget);
Os.close(mTarget);
mClosed = true;
IoBridge.write(mServer, temp, 0, MSG_LENGTH);
break;
}
}
} catch (ErrnoException | IOException e) {
} finally {
forceClose();
}
}
}
FileBridge在构造函数中,通过 Os.socketpair 生成一对socket。一旦启动了线程,FileBridge就会开始监听mServer 服务端的socket是否有数据到来,有数据到来,则开始分析当前的命令是什么并执行相应的操作,命令结构如下:
socket数据 | 头8位高4位 | 头8位低4位 | 头8位的决定的长度内容 |
---|---|---|---|
意义 | 命令 | 长度 | 传递过来的数据内容 |
先从mServer的服务端socket读取当前数据的命令和命令中对应数据的长度
CMD_WRITE 说明需要开始写入数据,从socket中读取长度对应的内容,并把当前的数据写入到设置到FileBridge中的mTarget中
CMD_FSYNC 则把mTarget对应缓存数据刷到mTarget这个文件中
CMD_CLOSE 则关闭服务端socket的监听和需要写入的文件fd。
FileBridgeOutputStream 写入原理
public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount);
Memory.pokeInt(mTemp, 0, CMD_WRITE, ByteOrder.BIG_ENDIAN);
Memory.pokeInt(mTemp, 4, byteCount, ByteOrder.BIG_ENDIAN);
IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
IoBridge.write(mClient, buffer, byteOffset, byteCount);
}
整个流程就是不断的往mClient 的socket端口写入数据,先写入命令CMD_WRITE,然后byteCount但是这个过程是以BIG_ENDIAN写入,因此两个顺序是颠倒的。最后是apk包数据内容。
当写完数据之后,就会调用FileBridgeOutputStream的fsync方法。
public void fsync() throws IOException {
writeCommandAndBlock(CMD_FSYNC, "fsync()");
}
private void writeCommandAndBlock(int cmd, String cmdString) throws IOException {
Memory.pokeInt(mTemp, 0, cmd, ByteOrder.BIG_ENDIAN);
IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
if (IoBridge.read(mClient, mTemp, 0, MSG_LENGTH) == MSG_LENGTH) {
if (Memory.peekInt(mTemp, 0, ByteOrder.BIG_ENDIAN) == cmd) {
return;
}
}
throw new IOException("Failed to execute " + cmdString + " across bridge");
}
这个过程很简单就是写入了一个CMD_FSYNC命令交给正在监听的FileBridge处理,调用Os.fsync从缓存刷入磁盘。
通过这种方式,就把临时文件/data/no_backup/packagexxx.apk
拷贝到/data/app/vmdlsessionId.tmp/PackageInstaller
中。
InstallingAsyncTask onPostExecute
当拷贝app的事件完成之后,就会回到主线程回调InstallingAsyncTask的onPostExecute。
protected void onPostExecute(PackageInstaller.Session session) {
if (session != null) {
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntent.setPackage(
getPackageManager().getPermissionControllerPackageName());
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
...
}
}
构造一个BROADCAST_ACTION(com.android.packageinstaller.ACTION_INSTALL_COMMIT
)的PendingIntent广播,调用session的commit方法,进行发送。
PackageInstaller.Session commit
public void commit(@NonNull IntentSender statusReceiver) {
try {
mSession.commit(statusReceiver, false);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
实际上就是调用了PackageInstallerSession的commit方法。
PackageInstallerSession commit
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
Preconditions.checkNotNull(statusReceiver);
final boolean wasSealed;
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotDestroyedLocked("commit");
final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(
mContext, statusReceiver, sessionId,
isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(), userId);
mRemoteObserver = adapter.getBinder();
...
wasSealed = mSealed;
if (!mSealed) {
try {
sealAndValidateLocked();
} catch (IOException e) {
...
} catch (PackageManagerException e) {
...
return;
}
}
mClientProgress = 1f;
computeProgressLocked(true);
mActiveCount.incrementAndGet();
mCommitted = true;
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
if (!wasSealed) {
mCallback.onSessionSealedBlocking(this);
}
}
- 1.构建一个PackageInstallObserverAdapter对象,并获取其中的Binder对象也就是IPackageInstallObserver2.
private final IPackageInstallObserver2.Stub mBinder = new IPackageInstallObserver2.Stub() {
@Override
public void onUserActionRequired(Intent intent) {
PackageInstallObserver.this.onUserActionRequired(intent);
}
@Override
public void onPackageInstalled(String basePackageName, int returnCode,
String msg, Bundle extras) {
PackageInstallObserver.this.onPackageInstalled(basePackageName, returnCode, msg,
extras);
}
};
这个Binder将会监听从远程端发送过来onPackageInstalled安装完毕的消息。
2.sealAndValidateLocked 把当前拷贝过来的文件进行一次重命名,并且清除该文件夹下可能安装过的临时文件
3.computeProgressLocked 计算进度并且回调
4.发送MSG_COMMIT 的Handler消息
核心是来看看后面2点。
sealAndValidateLocked
private void sealAndValidateLocked() throws PackageManagerException, IOException {
assertNoWriteFileTransfersOpenLocked();
assertPreparedAndNotDestroyedLocked("sealing of session");
final PackageInfo pkgInfo = mPm.getPackageInfo(
params.appPackageName, PackageManager.GET_SIGNATURES
| PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
resolveStageDirLocked();
mSealed = true;
try {
validateInstallLocked(pkgInfo);
} catch (PackageManagerException e) {
throw e;
} catch (Throwable e) {
throw new PackageManagerException(e);
}
}
能看到这个过程先通过PMS的getPackageInfo获取包内容,接着调用validateInstallLocked根据包内容进一步的处理事务。
private File resolveStageDirLocked() throws IOException {
if (mResolvedStageDir == null) {
if (stageDir != null) {
mResolvedStageDir = stageDir;
} else {
throw new IOException("Missing stageDir");
}
}
return mResolvedStageDir;
}
设置了mResolvedStageDir为stageDir,也就是/data/app/vmdlsessionId.tmp/
private void validateInstallLocked(@Nullable PackageInfo pkgInfo)
throws PackageManagerException {
mPackageName = null;
mVersionCode = -1;
mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
mResolvedBaseFile = null;
mResolvedStagedFiles.clear();
mResolvedInheritedFiles.clear();
try {
resolveStageDirLocked();
} catch (IOException e) {
...
}
final File[] removedFiles = mResolvedStageDir.listFiles(sRemovedFilter);
final List<String> removeSplitList = new ArrayList<>();
if (!ArrayUtils.isEmpty(removedFiles)) {
for (File removedFile : removedFiles) {
final String fileName = removedFile.getName();
final String splitName = fileName.substring(
0, fileName.length() - REMOVE_SPLIT_MARKER_EXTENSION.length());
removeSplitList.add(splitName);
}
}
final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter);
...
final ArraySet<String> stagedSplits = new ArraySet<>();
for (File addedFile : addedFiles) {
final ApkLite apk;
try {
apk = PackageParser.parseApkLite(
addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
}
...
if (mPackageName == null) {
mPackageName = apk.packageName;
mVersionCode = apk.getLongVersionCode();
}
if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
mSigningDetails = apk.signingDetails;
}
...
final String targetName;
if (apk.splitName == null) {
targetName = "base" + APK_FILE_EXTENSION;
} else {
targetName = "split_" + apk.splitName + APK_FILE_EXTENSION;
}
...
final File targetFile = new File(mResolvedStageDir, targetName);
maybeRenameFile(addedFile, targetFile);
// Base is coming from session
if (apk.splitName == null) {
mResolvedBaseFile = targetFile;
}
mResolvedStagedFiles.add(targetFile);
final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(addedFile);
if (dexMetadataFile != null) {
...
final File targetDexMetadataFile = new File(mResolvedStageDir,
DexMetadataHelper.buildDexMetadataPathForApk(targetName));
mResolvedStagedFiles.add(targetDexMetadataFile);
maybeRenameFile(dexMetadataFile, targetDexMetadataFile);
}
}
...
if (params.mode == SessionParams.MODE_FULL_INSTALL) {
...
} else {
...
}
}
1.resolveStageDirLocked 做的事情其实就是把stageDir赋值给mResolvedStageDir中,也就是
/data/app/vmdlsessionId.tmp/
2.遍历mResolvedStageDir文件夹中
/data/app/vmdlsessionId.tmp/
所有的文件,能通过sRemovedFilter和sAddedFilter识别出哪些文件需要删除,哪些文件是本次需要添加到安装流程中。
private static final FileFilter sAddedFilter = new FileFilter() {
@Override
public boolean accept(File file) {
if (file.isDirectory()) return false;
if (file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false;
if (DexMetadataHelper.isDexMetadataFile(file)) return false;
return true;
}
};
private static final String REMOVE_SPLIT_MARKER_EXTENSION = ".removed";
private static final FileFilter sRemovedFilter = new FileFilter() {
@Override
public boolean accept(File file) {
if (file.isDirectory()) return false;
if (!file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false;
return true;
}
};
对于需要删除的都是带了.remove
后缀的文件,需要添加到安装的除了目录文件,.dm
,.remove
的文件。
- 3.拿到这些需要添加到安装的流程的文件后,则对每一个apk包通过PackageParser.parseApkLite解析出AndroidManifest最顶层的xml内容出来。获得其版本号,包名,签名等数据,并以包名为基础转化为两种形式的名字:
- 1.如果apk没有进行分割,则名字为
base.apk
- 2.如果分割,则为
split_splitname.apk
并在/data/app/vmdlsessionId.tmp/
中生成一个该文件名的实例File(也就是/data/app/vmdlsessionId.tmp/base.apk
),最后添加到mResolvedStagedFiles集合中。接着校验这个apk文件在有没有对应的包名.dm
文件,存在则生成一个/data/app/vmdlsessionId.tmp/base.dm
文件.
- 1.如果apk没有进行分割,则名字为
PackageInstallSession计算安装进度原理
private void computeProgressLocked(boolean forcePublish) {
mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
+ MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) {
mReportedProgress = mProgress;
mCallback.onSessionProgressChanged(this, mProgress);
}
}
决定当前安装进度的有两个,是mClientProgress和mInternalProgress。mClientProgress是指来自PackageInstallerSession之外的PMS的安装进度,另一方面就是mInternalProgress也就是PackageInstallSession本身的进度.
mClientProgress的进度要乘以0.8,并且把这个结果约束到0~0.8之间。
mInternalProgress的进度需要乘以0.5,并且把结果约束到0~0.2之间
PackageInstallSession 发送MSG_COMMIT 的Handler消息
在PackageInstallerService的mInstallThread中接收到MSG_COMMIT,就会执行commitLocked方法:
private void commitLocked()
throws PackageManagerException {
...
if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
try {
final List<File> fromFiles = mResolvedInheritedFiles;
final File toDir = resolveStageDirLocked();
if (isLinkPossible(fromFiles, toDir)) {
if (!mResolvedInstructionSets.isEmpty()) {
final File oatDir = new File(toDir, "oat");
createOatDirs(mResolvedInstructionSets, oatDir);
}
if (!mResolvedNativeLibPaths.isEmpty()) {
for (String libPath : mResolvedNativeLibPaths) {
final int splitIndex = libPath.lastIndexOf('/');
if (splitIndex < 0 || splitIndex >= libPath.length() - 1) {
continue;
}
final String libDirPath = libPath.substring(1, splitIndex);
final File libDir = new File(toDir, libDirPath);
if (!libDir.exists()) {
NativeLibraryHelper.createNativeLibrarySubdir(libDir);
}
final String archDirPath = libPath.substring(splitIndex + 1);
NativeLibraryHelper.createNativeLibrarySubdir(
new File(libDir, archDirPath));
}
}
linkFiles(fromFiles, toDir, mInheritedFilesBase);
} else {
copyFiles(fromFiles, toDir);
}
} catch (IOException e) {
...
}
}
mInternalProgress = 0.5f;
computeProgressLocked(true);
extractNativeLibraries(mResolvedStageDir, params.abiOverride, mayInheritNativeLibs());
final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
@Override
public void onUserActionRequired(Intent intent) {
throw new IllegalStateException();
}
@Override
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) {
destroyInternal();
dispatchSessionFinished(returnCode, msg, extras);
}
};
final UserHandle user;
if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
user = UserHandle.ALL;
} else {
user = new UserHandle(userId);
}
mRelinquished = true;
mPm.installStage(mPackageName, stageDir, localObserver, params,
mInstallerPackageName, mInstallerUid, user, mSigningDetails);
}
1.如果是SessionParams.MODE_INHERIT_EXISTING安装模式,那么说明是进行安装覆盖,则会遍历该目录下所有的文件。通过isLinkPossible来检查安装原有的文件和当前的原有的文件,mResolvedInheritedFiles中的文件是否可以进行覆盖,如果文件信息一致说明可以覆盖,不能则直接拷贝到
/data/app/vmdlsessionId.tmp/
目录下。2.mInternalProgress 设置为0.5,并且更新进度条回调给正在监听的Activity
3.extractNativeLibraries
private static void extractNativeLibraries(File packageDir, String abiOverride, boolean inherit)
throws PackageManagerException {
final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
if (!inherit) {
NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
}
NativeLibraryHelper.Handle handle = null;
try {
handle = NativeLibraryHelper.Handle.create(packageDir);
final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
abiOverride);
...
} catch (IOException e) {
...
} finally {
IoUtils.closeQuietly(handle);
}
}
创建/data/app/vmdlsessionId.tmp/lib
,copyNativeBinariesWithOverride并在这个文件下创建该系统支持的一系列的如64位,32位的arm-v7的so库保存目录
- 4.把localObserver这个本地Binder作为参数调用PMS的installStage方法,此时安装步骤将会转移到PMS中。之后等到PMS到达了某个阶段就会回调到onPackageInstalled,从而得知PMS那边安装完成了。