从前面文章Android 安装过程一 界面跳转安装进程中InstallInstalling界面的处理中,我们知道安装进程需要与系统进程交互,调用getPackageManager().getPackageInstaller().createSession(params)得到一个整型id值,这个id值对应着系统进程中PackageInstallerSession对象。系统进程会创建该对象,并且将id值返回。这样安装进程就能通过该id值与系统进程后续继续交互。下面就看看它是怎么创建的。
getPackageManager()得到的是ApplicationPackageManager对象,getPackageManager().getPackageInstaller()这里得到的是PackageInstaller对象。PackageInstaller对象的成员变量mInstaller是一个Binder代理对象,它实际指向系统进程中PackageManagerService对象的成员mInstallerService,它是一个PackageInstallerService对象。在这里,PackageInstaller对象的成员变量mInstallerPackageName是安装进程应用的包名。成员mAttributionTag是来自Manifest里面的配置,Activity的attributionTags标签配置,它可以"|"分割,这里是取的第一个。成员mUserId是UserId。
PackageInstaller对象的createSession(params)方法调用的是PackageInstallerService对象的createSession()方法。它的代码在PackageInstallerService.java文件中,如下:
java
@Override
public int createSession(SessionParams params, String installerPackageName,
String callingAttributionTag, int userId) {
try {
return createSessionInternal(params, installerPackageName, callingAttributionTag,
userId);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
这里参数installerPackageName、callingAttributionTag、userId对应着PackageInstaller对象的上面三个成员变量。
接着看PackageInstallerService的createSessionInternal()方法,代码比较长,分段看一下,
代码分段一
分段一,如下:
java
private int createSessionInternal(SessionParams params, String installerPackageName,
String installerAttributionTag, int userId)
throws IOException {
final int callingUid = Binder.getCallingUid();
mPm.enforceCrossUserPermission(
callingUid, userId, true, true, "createSession");
if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
throw new SecurityException("User restriction prevents installing");
}
if (params.dataLoaderParams != null
&& mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("You need the "
+ "com.android.permission.USE_INSTALLER_V2 permission "
+ "to use a data loader");
}
// INSTALL_REASON_ROLLBACK allows an app to be rolled back without requiring the ROLLBACK
// capability; ensure if this is set as the install reason the app has one of the necessary
// signature permissions to perform the rollback.
if (params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS)
!= PackageManager.PERMISSION_GRANTED &&
mContext.checkCallingOrSelfPermission(Manifest.permission.TEST_MANAGE_ROLLBACKS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(
"INSTALL_REASON_ROLLBACK requires the MANAGE_ROLLBACKS permission or the "
+ "TEST_MANAGE_ROLLBACKS permission");
}
}
这块主要是处理权限。
用户不能有UserManager.DISALLOW_INSTALL_APPS的限制。
params.dataLoaderParams和应用的streaming方式安装有关,如果是这种安装方式,需要有相关权限Manifest.permission.USE_INSTALLER_V2。使用该种安装方式,需要V4的签名方式。
还看到如果设置params.installReason为INSTALL_REASON_ROLLBACK,也就是回退。需要权限MANAGE_ROLLBACKS和TEST_MANAGE_ROLLBACKS这两种的至少一种。
代码分段二
代码分段二如下:
java
// App package name and label length is restricted so that really long strings aren't
// written to disk.
if (params.appPackageName != null
&& params.appPackageName.length() > SessionParams.MAX_PACKAGE_NAME_LENGTH) {
params.appPackageName = null;
}
params.appLabel = TextUtils.trimToSize(params.appLabel,
PackageItemInfo.MAX_SAFE_LABEL_LENGTH);
String requestedInstallerPackageName = (params.installerPackageName != null
&& params.installerPackageName.length() < SessionParams.MAX_PACKAGE_NAME_LENGTH)
? params.installerPackageName : installerPackageName;
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
// adb installs can override the installingPackageName, but not the
// initiatingPackageName
installerPackageName = null;
} else {
if (callingUid != Process.SYSTEM_UID) {
// The supplied installerPackageName must always belong to the calling app.
mAppOps.checkPackage(callingUid, installerPackageName);
}
// Only apps with INSTALL_PACKAGES are allowed to set an installer that is not the
// caller.
if (!TextUtils.equals(requestedInstallerPackageName, installerPackageName)) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
!= PackageManager.PERMISSION_GRANTED) {
mAppOps.checkPackage(callingUid, requestedInstallerPackageName);
}
}
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;
}
if (mContext.checkCallingOrSelfPermission(
Manifest.permission.INSTALL_TEST_ONLY_PACKAGE)
!= PackageManager.PERMISSION_GRANTED) {
params.installFlags &= ~PackageManager.INSTALL_ALLOW_TEST;
}
}
String originatingPackageName = null;
if (params.originatingUid != SessionParams.UID_UNKNOWN
&& params.originatingUid != callingUid) {
String[] packages = mPm.getPackagesForUid(params.originatingUid);
if (packages != null && packages.length > 0) {
// Choose an arbitrary representative package in the case of a shared UID.
originatingPackageName = packages[0];
}
}
if (Build.IS_DEBUGGABLE || isCalledBySystemOrShell(callingUid)) {
params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else {
params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE;
}
if ((params.installFlags & ADB_DEV_MODE) != ADB_DEV_MODE) {
// Only tools under specific conditions (test app installed through ADB, and
// verification disabled flag specified) can disable verification.
params.installFlags &= ~PackageManager.INSTALL_DISABLE_VERIFICATION;
}
首先检查安装文件的包名的长度如果超出SessionParams.MAX_PACKAGE_NAME_LENGTH(255)个字符,将它设置为空。
接着APP的名称如果超过PackageItemInfo.MAX_SAFE_LABEL_LENGTH,将其裁剪到PackageItemInfo.MAX_SAFE_LABEL_LENGTH(1000)长度。
params.installerPackageName和installerPackageName的区别,params.installerPackageName是安装者的包名,installerPackageName则是安装程序的包名。params.installerPackageName的值需要通过各个Activity通过Intent传递,如果它为空,则将变量requestedInstallerPackageName设置为安装程序的包名。
如果调用安装的是Process.SHELL_UID或Process.ROOT_UID,会将params.installFlags加上PackageManager.INSTALL_FROM_ADB,并且将安装者的包名installerPackageName设为空。这种情况应该是通过adb直接安装的。
如果调用者不是Process.SYSTEM_UID(系统进程),会调用mAppOps.checkPackage(callingUid, installerPackageName)这个主要是检查这个installerPackageName是属于调用应用的。如果不属于,会throw出SecurityException。
接着如果requestedInstallerPackageName不等于installerPackageName,这个是上面params.installerPackageName存在值的情况。如果调用者还没授权Manifest.permission.INSTALL_PACKAGES,则需要检测requestedInstallerPackageName的包是否属于调用者。mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)就是用来检测调用者的Manifest.permission.INSTALL_PACKAGES权限。
接下来会将params.installFlags的标识去除INSTALL_FROM_ADB、INSTALL_ALL_USERS,加上INSTALL_REPLACE_EXISTING。
接下来是处理INSTALL_VIRTUAL_PRELOAD、INSTALL_ALLOW_TEST标识。
紧接着,是得到最开始安装应用的包名originatingPackageName。就是跳入安装进程之前的那个应用。它的应用uid在params.originatingUid,它是Activity跳转通过Intent之间数据传递过来的。
下面看到标识INSTALL_ALLOW_DOWNGRADE的设置,如果系统版本是IS_DEBUGGABLE版本,或者调用者是系统进程、root、或者adb,会设置允许降级标识。
如果params.installFlags标识不存在ADB_DEV_MODE,则将INSTALL_DISABLE_VERIFICATION去除。
代码分段三
代码分段三如下:
java
boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
if (isApex) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES)
== PackageManager.PERMISSION_DENIED
&& mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
== PackageManager.PERMISSION_DENIED) {
throw new SecurityException("Not allowed to perform APEX updates");
}
} else if (params.isStaged) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, TAG);
}
if (isApex) {
if (!mApexManager.isApexSupported()) {
throw new IllegalArgumentException(
"This device doesn't support the installation of APEX files");
}
if (params.isMultiPackage) {
throw new IllegalArgumentException("A multi-session can't be set as APEX.");
}
if (!params.isStaged
&& (params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
throw new IllegalArgumentException(
"Non-staged APEX session doesn't support INSTALL_ENABLE_ROLLBACK");
}
if (isCalledBySystemOrShell(callingUid) || mBypassNextAllowedApexUpdateCheck) {
params.installFlags |= PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK;
} else {
// Only specific APEX updates (installed through ADB, or for CTS tests) can disable
// allowed APEX update check.
params.installFlags &= ~PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK;
}
}
if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0
&& !isCalledBySystemOrShell(callingUid)
&& (mPm.getFlagsForUid(callingUid) & ApplicationInfo.FLAG_SYSTEM) == 0) {
throw new SecurityException(
"Only system apps could use the PackageManager.INSTALL_INSTANT_APP flag.");
}
if (params.isStaged && !isCalledBySystemOrShell(callingUid)) {
if (!mBypassNextStagedInstallerCheck
&& !isStagedInstallerAllowed(requestedInstallerPackageName)) {
throw new SecurityException("Installer not allowed to commit staged install");
}
}
if (isApex && !isCalledBySystemOrShell(callingUid)) {
if (!mBypassNextStagedInstallerCheck
&& !isStagedInstallerAllowed(requestedInstallerPackageName)) {
throw new SecurityException(
"Installer not allowed to commit non-staged APEX install");
}
}
mBypassNextStagedInstallerCheck = false;
mBypassNextAllowedApexUpdateCheck = false;
if (!params.isMultiPackage) {
// Only system components can circumvent runtime permissions when installing.
if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
&& mContext.checkCallingOrSelfPermission(Manifest.permission
.INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
throw new SecurityException("You need the "
+ "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+ "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
}
// 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);
}
}
switch (params.mode) {
case SessionParams.MODE_FULL_INSTALL:
case SessionParams.MODE_INHERIT_EXISTING:
break;
default:
throw new IllegalArgumentException("Invalid install mode: " + params.mode);
}
// If caller requested explicit location, validity check it, otherwise
// resolve the best internal or adopted location.
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
if (!PackageHelper.fitsOnInternal(mContext, params)) {
throw new IOException("No suitable internal storage available");
}
} else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
// For now, installs to adopted media are treated as internal from
// an install flag point-of-view.
params.installFlags |= PackageManager.INSTALL_INTERNAL;
} else {
params.installFlags |= PackageManager.INSTALL_INTERNAL;
// Resolve best location for install, based on combination of
// requested install flags, delta size, and manifest settings.
final long ident = Binder.clearCallingIdentity();
try {
params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
APEX文件格式安装是用于较低级别系统模块的安装流程,这里先不涉及。想了解的看这里。
params.isStaged则是这个Session会被安装在下一次重启时,这时要强制检查调用者Manifest.permission.INSTALL_PACKAGES的权限被授予。
PackageManager.INSTALL_INSTANT_APP是和免安装应用有关,也暂时略过。
在params.isStaged时,如果调用者不是系统、root、adb,并且没有设置绕过检查,这时要检查安装包是否允许安装,如果不允许,则会报异常。安装包是否允许安装,是通过SystemConfig.getInstance().getWhitelistedStagedInstallers()来控制的,它里面的值来自设置配置文件。
接着设置mBypassNextStagedInstallerCheck、mBypassNextAllowedApexUpdateCheck成员变量的值为false。
params.isMultiPackage代表是它可以通过id引用其他的Session。如果它的孩子安装失败,则它们安装失败。
在params.isMultiPackage为false的情况下,如果params的标识存在INSTALL_GRANT_RUNTIME_PERMISSIONS,则需要检查INSTALL_GRANT_RUNTIME_PERMISSIONS权限,如果是被拒绝的情况下,会抛出异常。
接着处理图标太大的情况,am.getLauncherLargeIconSize()生成的大小和配置属性大小app_icon_size和屏幕密度相关。如果太大会重新调整它的大小。
params.mode必定是MODE_FULL_INSTALL或MODE_INHERIT_EXISTING两者中的一个。
下面处理安装位置。如果明确指定要安装在内部存储,但是内部空间不够安装文件大小,这时会报IO异常。PackageHelper.fitsOnInternal(mContext, params)就是用来检测空间是否足够的。
如果params.installFlags存在INSTALL_FORCE_VOLUME_UUID标识,则为params.installFlags设置INSTALL_INTERNAL标识。
其他的情况也为其设置INSTALL_INTERNAL标识。并且为params设置volumeUuid。PackageHelper.resolveInstallVolume(mContext, params)就是得到得到硬盘的uuid。这里注意内部私有的盘的UUID为StorageManager.UUID_PRIVATE_INTERNAL,它是一个null值。
代码分段四
代码分段四如下:
java
final int sessionId;
final PackageInstallerSession session;
synchronized (mSessions) {
// Check that the installer does not have too many active sessions.
final int activeCount = getSessionCount(mSessions, callingUid);
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
if (activeCount >= MAX_ACTIVE_SESSIONS_WITH_PERMISSION) {
throw new IllegalStateException(
"Too many active sessions for UID " + callingUid);
}
} else if (activeCount >= MAX_ACTIVE_SESSIONS_NO_PERMISSION) {
throw new IllegalStateException(
"Too many active sessions for UID " + callingUid);
}
final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);
if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
throw new IllegalStateException(
"Too many historical sessions for UID " + callingUid);
}
sessionId = allocateSessionIdLocked();
}
final long createdMillis = System.currentTimeMillis();
// We're staging to exactly one location
File stageDir = null;
String stageCid = null;
if (!params.isMultiPackage) {
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
stageDir = buildSessionDir(sessionId, params);
} else {
stageCid = buildExternalStageCid(sessionId);
}
}
// reset the force queryable param if it's not called by an approved caller.
if (params.forceQueryableOverride) {
if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
params.forceQueryableOverride = false;
}
}
InstallSource installSource = InstallSource.create(installerPackageName,
originatingPackageName, requestedInstallerPackageName,
installerAttributionTag);
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
synchronized (mSessions) {
mSessions.put(sessionId, session);
}
mCallbacks.notifySessionCreated(session.sessionId, session.userId);
mSettingsWriteRequest.schedule();
return sessionId;
这块就是创建Session了。在创建之前,首先得到Session的Id值。
getSessionCount(mSessions, callingUid)得到的是调用应用正在安装应用的数量。这个是有限制的,并且这个限制和调用方获取到INSTALL_PACKAGES权限有关,如果获取到,限制数量是MAX_ACTIVE_SESSIONS_WITH_PERMISSION(1024);如果没有获取到,限制数量是MAX_ACTIVE_SESSIONS_NO_PERMISSION(50)。同时,调用应用的安装总数也是有限制的,MAX_HISTORICAL_SESSIONS(1048576)。如果这些限制存在超了,会报异常。
下面就调用allocateSessionIdLocked()分配SessionId。
看一下它的代码:
java
@GuardedBy("mSessions")
private int allocateSessionIdLocked() {
int n = 0;
int sessionId;
do {
sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
if (!mAllocatedSessions.get(sessionId, false)) {
mAllocatedSessions.put(sessionId, true);
return sessionId;
}
} while (n++ < 32);
throw new IllegalStateException("Failed to allocate session ID");
}
mAllocatedSessions中保存着之前分配的SessionId。如果分配过,就重新随机分配。生成的SessionId的值范围为1到Integer.MAX_VALUE。并且重新分配超过32次,还没成功,会报异常。
生成SessionId之后,会生成缓存文件的目录。
从上面知道,在params.isMultiPackage为false的情况下,params.installFlags都会设置INSTALL_INTERNAL标识。生成的Session目录是使用buildSessionDir(sessionId, params)方法实现,看一下它:
java
private File buildSessionDir(int sessionId, SessionParams params) {
if (params.isStaged || (params.installFlags & PackageManager.INSTALL_APEX) != 0) {
final File sessionStagingDir = Environment.getDataStagingDirectory(params.volumeUuid);
return new File(sessionStagingDir, "session_" + sessionId);
}
return buildTmpSessionDir(sessionId, params.volumeUuid);
}
在params.isStaged为true,或者是用APEX安装方式时,目录是根据params.volumeUuid得到的一个。这里分params.volumeUuid为null和具体值两种情况。params.volumeUuid为null是内部存储的默认格式,params.volumeUuid为null时生成的目录为"/data/app-staging/session_" + sessionId;params.volumeUuid为具体值生成的目录则为"/mnt/expand/" + volumeUuid + "/app-staging/session_" + sessionId。
除了上面两种情况,是普通APP安装时,生成的目录也分params.volumeUuid为null和具体值两种情况。params.volumeUuid为null时生成目录为"/data/app/vmdl" + sessionId + ".tmp",如果不为null,则生成的目录为"/mnt/expand/" + volumeUuid + "/app/vmdl" + sessionId + ".tmp"。
在params.installFlags未设置INSTALL_INTERNAL标识时,stageCid的值为"smdl" + sessionId + ".tmp"。
接下来处理params.forceQueryableOverride为true时,如果调用方不为SHELL_UID、ROOT_UID。需要将它设置为false。
生成一个InstallSource对象。
生成一个PackageInstallerSession对象。它的生成就是调用它的构造函数,对它的成员变量赋值。
将PackageInstallerSession对象对象以sessionId为key,放入mSessions中。
回调接口mCallbacks执行notifySessionCreated(session.sessionId, session.userId)方法。这个回调需要首先注册。
下面调用mSettingsWriteRequest.schedule()执行,将mSessions中的信息写入文件。这个文件为"/data/system/install_sessions.xml"。
最后将sessionId返回。