应用安装Apk的代码如下:
val intent = Intent(Intent.ACTION_VIEW)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val apk = File(filesDir,"ff/ss.apk")
val uri = FileProvider.getUriForFile(this, "$packageName.fileProvider", apk)
intent.setDataAndType(uri, "application/vnd.android.package-archive")
startActivity(intent)
AndroidManifest中provider配置路径的xml文件如下:
<?xml version="1.0" encoding="UTF-8" ?>
<paths>
<external-path
name="path1"
path="/data/dir1"/>
<files-path
name="path2"
path="/ff" />
</paths>
这里uri的String为content://com.example.testapplication.fileProvider/path2/ss.apk。
下面它会跳入另外一个进程"com.android.packageinstaller"(这里叫它包安装进程)中,首先会进入InstallStart。它是包安装进程中的一个Activity,它在AndroidManifest.xml指那个的配置 action为"android.intent.action.VIEW",所以能通过该action找到它。首先进入InstallStart的onCreate()生命周期方法:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPackageManager = getPackageManager();
mUserManager = getSystemService(UserManager.class);
Intent intent = getIntent();
String callingPackage = getCallingPackage();
String callingAttributionTag = null;
final boolean isSessionInstall =
PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
// If the activity was started via a PackageInstaller session, we retrieve the calling
// package from that session
final int sessionId = (isSessionInstall
? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
: -1);
if (callingPackage == null && sessionId != -1) {
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
callingAttributionTag =
(sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : 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) {
Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
// Invalid originating uid supplied. Abort install.
mAbortInstall = true;
} else if (targetSdkVersion >= Build.VERSION_CODES.O && !isUidRequestingPermission(
originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ 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
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
// The the installation source as the nextActivity thinks this activity is the source, hence
// set the originating UID and sourceInfo explicitly
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_ATTRIBUTION_TAG,
callingAttributionTag);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
if (packageUri != null && packageUri.getScheme().equals(
ContentResolver.SCHEME_CONTENT)) {
// [IMPORTANT] This path is deprecated, but should still work. Only necessary
// features should be added.
// Copy file to prevent it from being changed underneath this process
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();
}
首先通过getCallingPackage()得到调用方的包名,放到变量callingPackage中。
接着通过intent的action是否等于PackageInstaller.ACTION_CONFIRM_INSTALL来判断是否是PackageInstaller session安装,如果是,会去intent中取出SESSION_ID,并且得到SessionInfo信息,并从中得到callingPackage、callingAttributionTag。
接着再通过getSourceInfo(callingPackage)得到调用方的ApplicationInfo,getOriginatingUid(sourceInfo)得到调用方的uid。
接着判断是否是信任的安装源,在sourceInfo.privateFlags存在ApplicationInfo.PRIVATE_FLAG_PRIVILEGED时,如果传过来的intent中有Intent.EXTRA_NOT_UNKNOWN_SOURCE的值为true时,就是信任的。
如果是信任的,就不用去检查Manifest.permission.REQUEST_INSTALL_PACKAGES权限,如果是不信任的,需要检查应用是否有Manifest.permission.REQUEST_INSTALL_PACKAGES权限,这个权限是要在安装APP的应用中声明的。
Manifest.permission.REQUEST_INSTALL_PACKAGES权限是在Build.VERSION_CODES.O版本及之后才有的,所以需要先找到安装应用的目标Sdk,如果没有也会报错;如果目标Sdk版本比Build.VERSION_CODES.O版本之后,就需要检查Manifest.permission.REQUEST_INSTALL_PACKAGES权限。
如果没得到目标sdk版本及没声明Manifest.permission.REQUEST_INSTALL_PACKAGES权限都会报错返回。
接着会复制intent,得到nextActivity,然后给nextActivity设置Intent.FLAG_ACTIVITY_FORWARD_RESULT|Intent.FLAG_GRANT_READ_URI_PERMISSION标签。
接着向新生成的Intent对象里面添加数据,包括调用者包名callingPackage、调用者AttributionTag、调用者的应用信息、调用者的uid。
若是是PackageInstaller session安装,直接就跳转到PackageInstallerActivity中去;如果得到的URI的getScheme()是ContentResolver.SCHEME_CONTENT,则会跳转到InstallStaging中,我们的例子就是属于这种;如果得到的URI的getScheme()是PackageInstallerActivity.SCHEME_PACKAGE,则会跳转到PackageInstallerActivity中;如果不是以上情况,则返回PackageManager.INSTALL_FAILED_INVALID_URI给调用者Activity。
最后就是跳转,并且关闭InstallStart。
InstallStaging 在InstallStart 中根据Uri的scheme是Content,跳转到该Activity中。 该Activity是用来缓存APK文件的。
它是继承AlertActivity,界面是以对框的形式展示的。看下它的onCreate()方法:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.setIcon(R.drawable.ic_file_download);
mAlert.setTitle(getString(R.string.app_name_unknown));
mAlert.setView(R.layout.install_content_view);
mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
(ignored, ignored2) -> {
if (mStagingTask != null) {
mStagingTask.cancel(true);
}
setResult(RESULT_CANCELED);
finish();
}, null);
setupAlert();
requireViewById(R.id.staging).setVisibility(View.VISIBLE);
if (savedInstanceState != null) {
mStagedFile = new File(savedInstanceState.getString(STAGED_FILE));
if (!mStagedFile.exists()) {
mStagedFile = null;
}
}
}
对话框的布局是install_content_view。这里是显示的id为staging的控件,它是LinearLayout控件,里面有个文本,文本下面是水平进度条。文本内容显示为“Staging app…”。在复制APK文件完成之前,跳转到DeleteStagedFileOnResult之前,一直显示这个界面。
它有一个成员变量StagingAsyncTask,它继承自AsyncTask,用来执行异步任务。在异步任务过程中,它会将安装的APK文件重新保存一份,保存的文件位于成员变量File 类型mStagedFile中。
看一下文件的生成代码,它是由TemporaryFileManager.getStagedFile(this)生成的文件:
@NonNull
public static File getStagedFile(@NonNull Context context) throws IOException {
return File.createTempFile("package", ".apk", context.getNoBackupFilesDir());
}
这时创建临时文件以package作为开头,".apk"作为结尾的文件,目录为context.getNoBackupFilesDir()。
这里context为Activity,它是通过代理模式最终会调用到ContextImpl类的getNoBackupFilesDir(),如下代码:
@Override
public File getNoBackupFilesDir() {
synchronized (mSync) {
if (mNoBackupFilesDir == null) {
mNoBackupFilesDir = new File(getDataDir(), "no_backup");
}
return ensurePrivateDirExists(mNoBackupFilesDir);
}
}
这里如果mNoBackupFilesDir不存在,会给他声明。并且ensurePrivateDirExists(mNoBackupFilesDir)确保该目录存在。
再看一下getDataDir()的实现:
@Override
public File getDataDir() {
if (mPackageInfo != null) {
File res = null;
if (isCredentialProtectedStorage()) {
res = mPackageInfo.getCredentialProtectedDataDirFile();
} else if (isDeviceProtectedStorage()) {
res = mPackageInfo.getDeviceProtectedDataDirFile();
} else {
res = mPackageInfo.getDataDirFile();
}
if (res != null) {
if (!res.exists() && android.os.Process.myUid() == android.os.Process.SYSTEM_UID) {
Log.wtf(TAG, "Data directory doesn't exist for package " + getPackageName(),
new Throwable());
}
return res;
} else {
throw new RuntimeException(
"No data directory found for package " + getPackageName());
}
} else {
throw new RuntimeException(
"No package details found for package " + getPackageName());
}
}
可以看到分了好几种情况,并且它的值是来自成员变量mPackageInfo。
isCredentialProtectedStorage()、isDeviceProtectedStorage()是和当前ContextImpl类的flags(Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE、Context.CONTEXT_DEVICE_PROTECTED_STORAGE)有关,而这两个flag又和mPackageInfo的值相关。
mPackageInfo是LoadedApk类,它用来描述目前加载apk的本地状态。它的生成是在ActivityThread的performLaunchActivity()中。
在哪里生成的ApplicationInfo,在PackageInfoUtils的generateApplicationInfo()中。 PackageInfoWithoutStateUtils.generateApplicationInfoUnchecked()里会调用assignUserFields(),assignUserFields(ParsingPackageRead pkg, ApplicationInfo info, int userId)有为ApplicationInfo中的dataDir赋值。
private static void assignUserFields(ParsingPackageRead pkg, ApplicationInfo info, int userId) {
// This behavior is undefined for no-state ApplicationInfos when called by a public API,
// since the uid is never assigned by the system. It will always effectively be appId 0.
info.uid = UserHandle.getUid(userId, UserHandle.getAppId(info.uid));
String pkgName = pkg.getPackageName();
if ("android".equals(pkgName)) {
info.dataDir = SYSTEM_DATA_PATH;
return;
}
// For performance reasons, all these paths are built as strings
String baseDataDirPrefix =
Environment.getDataDirectoryPath(pkg.getVolumeUuid()) + File.separator;
String userIdPkgSuffix = File.separator + userId + File.separator + pkgName;
info.credentialProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_CE
+ userIdPkgSuffix;
info.deviceProtectedDataDir = baseDataDirPrefix + Environment.DIR_USER_DE + userIdPkgSuffix;
if (pkg.isDefaultToDeviceProtectedStorage()
&& PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
info.dataDir = info.deviceProtectedDataDir;
} else {
info.dataDir = info.credentialProtectedDataDir;
}
}
这里baseDataDirPrefix 的值为"/data"或者"/mnt/expand/" + volumeUuid,如果是系统用户,则userId 为0。并且如果是内部存储,则info.credentialProtectedDataDir=“/data/user/0/” + 包名,info.deviceProtectedDataDir=“/data/user_de/0/” + 包名。
接着为info.dataDir赋值,pkg.isDefaultToDeviceProtectedStorage()是由Manifest文件中的标签defaultToDeviceProtectedStorage来控制的。如果没配置则默认为false。
TemporaryFileManager.getStagedFile(this)得到的文件名字还会有随机数,因为当前安装应用是配置了android:defaultToDeviceProtectedStorage=“true”,所以TemporaryFileManager.getStagedFile(this)得到的文件的生成名字为"/data/user_de/0/com.android.packageinstaller/no_backup/package" + 随机数 + “.apk”。package7515820555458262274.apk就是一个例子。
这里实现的异步任务为AsyncTask,代码如下:
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)) {
// 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);
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();
}
}
}
这里可以看到在doInBackground(Uri… params)中实现了,文件的复制。
在onPostExecute(Boolean success)中,根据复制结果来做下一步。如果复制成功,会跳入DeleteStagedFileOnResult这个Activity中。在这里会设置跳转Intent的mData成员变量
这个Activity是为了跳入PackageInstallerActivity,然后等待返回结果,收到返回结果之后,然后删除前面生成的安装文件。
首先进入它的onCreate()里面看:
…………
final Intent intent = getIntent();
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG);
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_INSTALL.equals(intent.getAction())) {
…………
} else {
mSessionId = -1;
packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
…………
boolean wasSetUp = processPackageUri(packageUri);
……
取到Intent里面传递过来的相对应的数据,这里的数据对应InstallStart这个Activity里面赋的对应的值。mOriginatingUid 、mOriginatingPackage都是调用安装APP应用的uid和包名。
接着就取得intent里面的mData的值,然后进入processPackageUri(packageUri)里处理。
看下processPackageUri(packageUri)方法:
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
if (mLocalLOGV) Log.i(TAG, "processPackageUri(): uri=" + packageUri + ", scheme=" + scheme);
switch (scheme) {
case SCHEME_PACKAGE: {
…………
} break;
case ContentResolver.SCHEME_FILE: {
File sourceFile = new File(packageUri.getPath());
mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile,
PackageManager.GET_PERMISSIONS);
// Check for parse errors
if (mPkgInfo == null) {
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
if (mLocalLOGV) Log.i(TAG, "creating snippet for local file " + sourceFile);
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
…………
}
return true;
}
在前面InstallStaging中,我们知道,intent的mData是File的。所以这里代码会走ContentResolver.SCHEME_FILE分支。
这里得到的文件sourceFile 就是我们在InstallStaging中生成的APK文件。
接着会调用PackageUtil.getPackageInfo()方法得到成员变量mPkgInfo。再通过它的applicationInfo,调用PackageUtil.getAppSnippet()得到mAppSnippet 。
mPkgInfo是PackageInfo类型,它是关于一个安装包的所有的信息,对应着从AndroidManifest.xml文件里收集到的所有信息。mAppSnippet 是AppSnippet类型,它包括应用的标签名和icon标识。
得到安装包的所有的信息,可以参考下Android 得到安装包的信息。
接着进入PackageInstallerActivity的onResume()方法里面看看,
@Override
protected void onResume() {
super.onResume();
if (mLocalLOGV) Log.i(TAG, "onResume(): mAppSnippet=" + mAppSnippet);
if (mAppSnippet != null) {
// load dummy layout with OK button disabled until we override this layout in
// startInstallConfirm
bindUi();
checkIfAllowedAndInitiateInstall();
}
if (mOk != null) {
mOk.setEnabled(mEnableOk);
}
}
mAppSnippet 不为null,代表前面解析的APK文件信息存在,接着调用bindUi()和checkIfAllowedAndInitiateInstall()来处理提示对话框。mOk则是提示对话框中的确定按钮,mEnableOk为true代表可以安装,则按钮可以点击。
在用户自己安装APK的时候,系统不知道安装源的安全、受信任情况。所以需要用户自己确认安装源之后,才能安装。在用户确认安装源之后,mEnableOk即设置为true。之后安装按钮就能点击了。
先看一下bindUi()的代码:
private void bindUi() {
mAlert.setIcon(mAppSnippet.icon);
mAlert.setTitle(mAppSnippet.label);
mAlert.setView(R.layout.install_content_view);
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
(ignored, ignored2) -> {
if (mOk.isEnabled()) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
startInstall();
}
}
}, null);
mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
(ignored, ignored2) -> {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
finish();
}, null);
setupAlert();
mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
mOk.setEnabled(false);
if (!mOk.isInTouchMode()) {
mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).requestFocus();
}
}
这里是主要设置对话框界面,将mAppSnippet.icon和mAppSnippet.label设置给对话框的icon和标题。然后将内容设置成布局文件install_content_view,然后设置安装按钮和取消按钮,现在安装按钮是不能点击,所以将它的enabled设置为false。
接着看看checkIfAllowedAndInitiateInstall():
private void checkIfAllowedAndInitiateInstall() {
// Check for install apps user restriction first.
final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
if (mLocalLOGV) Log.i(TAG, "install not allowed: " + UserManager.DISALLOW_INSTALL_APPS);
showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
return;
} else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
if (mLocalLOGV) {
Log.i(TAG, "install not allowed by admin; showing "
+ Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
}
startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
finish();
return;
}
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
if (mLocalLOGV) Log.i(TAG, "install allowed");
initiateInstall();
} else {
// Check for unknown sources restrictions.
final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
& (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
if (systemRestriction != 0) {
if (mLocalLOGV) Log.i(TAG, "Showing DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER");
showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
} else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
} else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startAdminSupportDetailsActivity(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
} else {
handleUnknownSources();
}
}
}
Process.myUserHandle()是得到用户标识,当前用户是一般是UserHandle.USER_SYSTEM,关于用户的相关,可以参考一下Android UserManagerService初始化,首先调用mUserManager.getUserRestrictionSource()检查用户安装APP的限制标识。如果得到的结果有UserManager.RESTRICTION_SOURCE_SYSTEM标识,说明不允许该用户安装APP,接着会调用showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER),显示“This user is not allowed to install apps”对话框。
如果结果不为UserManager.RESTRICTION_NOT_SET,则会跳到设置界面,并且关闭当前界面。
USER_SYSTEM用户是不属于这两种情况,所以它会继续向下执行,下面是处理未知来源的APP的情况。mAllowUnknownSources为true是允许安装未知来源的APP,刚进入该界面时是为false。isInstallRequestFromUnknownSource(getIntent())是通过传递过来的intent,来判断是否是未知安装源。看一下它的代码:
private boolean isInstallRequestFromUnknownSource(Intent intent) {
if (mCallingPackage != null && intent.getBooleanExtra(
Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
if (mSourceInfo != null) {
if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
!= 0) {
// Privileged apps can bypass unknown sources check if they want.
return false;
}
}
}
return true;
}
可以看到,不仅要设置Intent.EXTRA_NOT_UNKNOWN_SOURCE的值为true,并且安装源得是特权APP。
所以checkIfAllowedAndInitiateInstall()会接着检查对于用户UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES和UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY的限制。
对于UserHandle.USER_SYSTEM,这些限制都不存在,所以会走最后的else分支,进行handleUnknownSources()处理。看一下它的代码:
private void handleUnknownSources() {
if (mOriginatingPackage == null) {
Log.i(TAG, "No source found for package " + mPkgInfo.packageName);
showDialogInner(DLG_ANONYMOUS_SOURCE);
return;
}
// Shouldn't use static constant directly, see b/65534401.
final int appOpCode =
AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid,
mOriginatingPackage, mCallingAttributionTag,
"Started package installation activity");
if (mLocalLOGV) Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode);
switch (appOpMode) {
case AppOpsManager.MODE_DEFAULT:
mAppOpsManager.setMode(appOpCode, mOriginatingUid,
mOriginatingPackage, AppOpsManager.MODE_ERRORED);
// fall through
case AppOpsManager.MODE_ERRORED:
showDialogInner(DLG_EXTERNAL_SOURCE_BLOCKED);
break;
case AppOpsManager.MODE_ALLOWED:
initiateInstall();
break;
default:
Log.e(TAG, "Invalid app op mode " + appOpMode
+ " for OP_REQUEST_INSTALL_PACKAGES found for uid " + mOriginatingUid);
finish();
break;
}
}
mOriginatingPackage是调用安装APP的应用包名,所以不为null。
这里接下来会走到switch的AppOpsManager.MODE_ALLOWED case。接着调用initiateInstall()。
private void initiateInstall() {
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
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;
}
// Check if package is already installed. display confirmation dialog if replacing pkg
try {
// This is a little convoluted because we want to get all uninstalled
// apps, but this may include apps with just data, and if it is just
// data we still want to count it as "installed".
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}
startInstallConfirm();
}
在这里由于pkgName包还未安装,所以mAppInfo 为null。
private void startInstallConfirm() {
View viewToEnable;
if (mAppInfo != null) {
viewToEnable = requireViewById(R.id.install_confirm_question_update);
mOk.setText(R.string.update);
} else {
// This is a new application with no permissions.
viewToEnable = requireViewById(R.id.install_confirm_question);
}
viewToEnable.setVisibility(View.VISIBLE);
mEnableOk = true;
mOk.setEnabled(true);
mOk.setFilterTouchesWhenObscured(true);
}
在这里会走else分支,viewToEnable 显示的内容“Do you want to install this app?”,来询问用户是否安装。这个时候,安装按钮就可以点击了。用户如果同意,就会走安装按钮点击的逻辑,看一下。
它的点击事件逻辑在bindUi()方法中:
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
(ignored, ignored2) -> {
if (mOk.isEnabled()) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
startInstall();
}
}
}, null);
这里会走到startInstall(),看它的代码:
private void startInstall() {
// Start subactivity to actually install the application
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);
if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI);
startActivity(newIntent);
finish();
}
这是为了要跳到InstallInstalling中做的一些设置。在目前的安装例子中,installerPackageName、mOriginatingURI、mReferrerURI都为null。intent中Intent.EXTRA_RETURN_RESULT的值也为false,这里会加上Intent.FLAG_ACTIVITY_FORWARD_RESULT标识。接下来就是跳入InstallInstalling中,关闭当前界面。
先看看它的onCreate()方法:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ApplicationInfo appInfo = getIntent()
.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
mPackageURI = getIntent().getData();
if ("package".equals(mPackageURI.getScheme())) {
…………
} else {
final File sourceFile = new File(mPackageURI.getPath());
PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
mAlert.setIcon(as.icon);
mAlert.setTitle(as.label);
mAlert.setView(R.layout.install_content_view);
mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
(ignored, ignored2) -> {
if (mInstallingTask != null) {
mInstallingTask.cancel(true);
}
if (mSessionId > 0) {
getPackageManager().getPackageInstaller().abandonSession(mSessionId);
mSessionId = 0;
}
setResult(RESULT_CANCELED);
finish();
}, null);
setupAlert();
requireViewById(R.id.installing).setVisibility(View.VISIBLE);
if (savedInstanceState != null) {
…………
} else {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setInstallAsInstantApp(false);
params.setReferrerUri(getIntent().getParcelableExtra(Intent.EXTRA_REFERRER));
params.setOriginatingUri(getIntent()
.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
UID_UNKNOWN));
params.setInstallerPackageName(getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME));
params.setInstallReason(PackageManager.INSTALL_REASON_USER);
File file = new File(mPackageURI.getPath());
try {
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
input.reset(), file, /* flags */ 0);
if (result.isError()) {
Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
Log.e(LOG_TAG,
"Cannot calculate installed size " + file + ". Try only apk size.");
params.setSize(file.length());
} else {
final PackageLite pkg = result.getResult();
params.setAppPackageName(pkg.getPackageName());
params.setInstallLocation(pkg.getInstallLocation());
params.setSize(
PackageHelper.calculateInstalledSize(pkg, params.abiOverride));
}
} catch (IOException e) {
Log.e(LOG_TAG,
"Cannot calculate installed size " + file + ". Try only apk size.");
params.setSize(file.length());
}
try {
mInstallId = InstallEventReceiver
.addObserver(this, EventResultPersister.GENERATE_NEW_ID,
this::launchFinishBasedOnResult);
} catch (EventResultPersister.OutOfIdsException e) {
launchFailure(PackageInstaller.STATUS_FAILURE,
PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
try {
mSessionId = getPackageManager().getPackageInstaller().createSession(params);
} catch (IOException e) {
launchFailure(PackageInstaller.STATUS_FAILURE,
PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
}
mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE);
}
}
这里先得到安装文件的ApplicationInfo对象appInfo,mPackageURI。接着就得到安装文件sourceFile、还有AppSnippet对象。
再往下就是设置对话框的显示内容。这个对话框采用的布局和PackageInstallerActivity界面采用的布局文件是同一个install_content_view。不过显示内容是不同,这里直接让id为installing的控件显示出来。它是一个LinearLayout控件,看一下它的布局结构:
<LinearLayout
android:id="@+id/installing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="invisible">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@android:style/TextAppearance.Material.Subhead"
android:text="@string/installing" />
<ProgressBar
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
style="?android:attr/progressBarStyleHorizontal"
android:indeterminate="true" />
</LinearLayout>
一个文本提示安装中,一个水平进度条提示进度。
接着会生成一个SessionParams对象。将intent中的值取出来,设置给SessionParams对象。然后调用ApkLiteParseUtils.parsePackageLite( input.reset(), file, /* flags */ 0)得到安装包的轻量级对象PackageLite对象。接着用PackageLite对象的信息设置到SessionParams对象的AppPackageName、InstallLocation、Size。
再接着就是生成一个安装id,一个SessionId。
生成安装id是通过InstallEventReceiver的静态方法addObserver()方法实现,其中第二个参数EventResultPersister.GENERATE_NEW_ID代表生成一个新的、而this::launchFinishBasedOnResult则是一个回调。安装结果出来之后,就是通过这个回调通知的。
在这里getPackageManager()得到的是ApplicationPackageManager对象,它的类成员mContext是ContextImpl实例,mPM是 PackageManagerService 在应用进程中的代理对象。ApplicationPackageManager的getPackageInstaller()代码为:
@Override
public PackageInstaller getPackageInstaller() {
synchronized (mLock) {
if (mInstaller == null) {
try {
mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
mContext.getPackageName(), mContext.getAttributionTag(), getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return mInstaller;
}
}
mPM.getPackageInstaller()得到的是系统进程中的PackageInstallerService对象(它是Binder对象)的Binder代理对象,现在将它封装到PackageInstaller对象中。
接着会调用PackageInstaller对象的createSession(params)方法,它会在系统进程中创建一个PackageInstallerSession对象。在创建过程中会生成PackageInstallerSession唯一id返回到应用进程,这里mSessionId就是。在系统进程创建PackageInstallerSession对象时,还会生成一个目录,如果是APK安装在内部,则为“/data/app/” + “/vmdl” + sessionId + “.tmp”或者指定volumeUuid时,则为"/mnt/expand/" + volumeUuid + “/app” + “/vmdl” + sessionId + “.tmp”。
接着看一下onResume()方法:
@Override
protected void onResume() {
super.onResume();
// This is the first onResume in a single life of the activity
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);
}
}
}
这里在异步任务未创建的情况下,调用PackageInstaller对象的getSessionInfo(mSessionId),这个主要是为了生成SessionInfo。然后开始调用执行异步任务mInstallingTask 。
看一下异步任务mInstallingTask 的doInBackground()方法:
@Override
protected PackageInstaller.Session doInBackground(Void... params) {
PackageInstaller.Session session;
try {
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
} catch (IOException e) {
synchronized (this) {
isDone = true;
notifyAll();
}
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) {
Log.e(LOG_TAG, "Could not write package", e);
session.close();
return null;
} finally {
synchronized (this) {
isDone = true;
notifyAll();
}
}
}
这里主要调用PackageInstaller对象的openSession(mSessionId)来打开系统进程中的PackageInstallerSession,这个步骤主要是为了创建上面说的的目录。因为下面马上要复制APK文件了。
下面就是接着就调用session.openWrite(“PackageInstaller”, 0, sizeBytes)打开之前创建目录下的"PackageInstaller"文件,然后将APK文件拷贝到这个文件中。还是以上面的目录为例子,未指定volumeUuid时,最后生成的文件则为“/data/app/” + “/vmdl” + sessionId + “.tmp/PackageInstaller”。
拷贝完毕之后,会走异步任务mInstallingTask 的onPostExecute()方法,看一下它的代码:
@Override
protected void onPostExecute(PackageInstaller.Session session) {
if (session != null) {
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntent.setPackage(getPackageName());
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
getPackageManager().getPackageInstaller().abandonSession(mSessionId);
if (!isCancelled()) {
launchFailure(PackageInstaller.STATUS_FAILURE,
PackageManager.INSTALL_FAILED_INVALID_APK, null);
}
}
}
这里将接受安装结果的通知Intent和mInstallId封装到PendingIntent 中,接着调用session.commit(pendingIntent.getIntentSender())去系统进程中封装PackageInstallerSession对象,然后系统进程开始执行安装操作。
之后界面会一直在进度条走动那儿等待安装结果。
在安装成功之后,会回调InstallEventReceiver.addObserver(this, EventResultPersister.GENERATE_NEW_ID, this::launchFinishBasedOnResult)注册的launchFinishBasedOnResult()方法,看一下它:
private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
if (statusCode == PackageInstaller.STATUS_SUCCESS) {
launchSuccess();
} else {
launchFailure(statusCode, legacyStatus, statusMessage);
}
}
private void launchSuccess() {
Intent successIntent = new Intent(getIntent());
successIntent.setClass(this, InstallSuccess.class);
successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
startActivity(successIntent);
finish();
}
成功之后,会走launchSuccess(),它会继续跳转到InstallSuccess这个Activity中,并且添加FLAG_ACTIVITY_FORWARD_RESULT标识,跳转之后,会将自己关闭。
InstallSuccess主要用来提示用户安装成功了,后续是否运行安装的应用。看一下它的onCreate()方法:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
// Return result if requested
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED);
setResult(Activity.RESULT_OK, result);
finish();
} else {
Intent intent = getIntent();
ApplicationInfo appInfo =
intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
mAppPackageName = appInfo.packageName;
Uri packageURI = intent.getData();
// Set header icon and title
PackageManager pm = getPackageManager();
if ("package".equals(packageURI.getScheme())) {
mAppSnippet = new PackageUtil.AppSnippet(pm.getApplicationLabel(appInfo),
pm.getApplicationIcon(appInfo));
} else {
File sourceFile = new File(packageURI.getPath());
mAppSnippet = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
}
mLaunchIntent = getPackageManager().getLaunchIntentForPackage(mAppPackageName);
bindUi();
}
}
如果intent中设置了Intent.EXTRA_RETURN_RESULT,会直接返回了,不会提示安装成功界面。
如果没有设置该值,会得到ApplicationInfo对象,包名、还有安装文件,接着查询安装应用中的主Activity 所需要的mLaunchIntent。
接着调用bindUi(),看一下它的界面提示:
private void bindUi() {
if (mAppSnippet == null) {
return;
}
mAlert.setIcon(mAppSnippet.icon);
mAlert.setTitle(mAppSnippet.label);
mAlert.setView(R.layout.install_content_view);
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.launch), null,
null);
mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.done),
(ignored, ignored2) -> {
if (mAppPackageName != null) {
Log.i(LOG_TAG, "Finished installing " + mAppPackageName);
}
finish();
}, null);
setupAlert();
requireViewById(R.id.install_success).setVisibility(View.VISIBLE);
// Enable or disable "launch" button
boolean enabled = false;
if (mLaunchIntent != null) {
List<ResolveInfo> list = getPackageManager().queryIntentActivities(mLaunchIntent,
0);
if (list != null && list.size() > 0) {
enabled = true;
}
}
Button launchButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
if (enabled) {
launchButton.setOnClickListener(view -> {
try {
startActivity(mLaunchIntent);
} catch (ActivityNotFoundException | SecurityException e) {
Log.e(LOG_TAG, "Could not start activity", e);
}
finish();
});
} else {
launchButton.setEnabled(false);
}
}
这个对话框两个按钮一个是“Open”,一个是“Done”。“Done”按钮点击之后,直接关闭该界面,主要起到提示用户安装完成的作用。“Open”按钮则是跳转到安装的应用中,关闭该界面。
install_content_view布局中显示的控件为id为install_success的,它是一个文本控件,内容为“App installed.”。
该篇文章说了安装应用时,相关界面的显示,以及涉及到的Activity的跳转和其作用。
现在进行了两次APK的复制,第一次将它复制到包安装进程的私有存储空间中,第二次拷贝到的则是在系统进程中创建的安装APK文件。包安装进程中的APK文件会在DeleteStagedFileOnResult收到应答之后,执行删除,系统进程中的会在之后进行安装的时候,继续处理。
现在我们知道了大概在APK安装过程中的界面显示,实际的安装工作都是在系统进程中进行,后续我们继续分析系统进程进行的安装行为。