Android 10 PMS安装流程
转载
https://juejin.cn/post/7003590026776969253#heading-4
Android 是如何完成apk的安装的?安装时它又做了哪些事情,保存了哪些信息?存储的这些信息又有什么作用?
这篇文章,让我们带着以上问题来一起探讨一下android系统的apk安装流程。
让我们先从apk安装的调用代码开始追溯:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri apkUri =FileProvider.getUriForFile(context, "你的包名.fileProvider", file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent)
复制代码
代码内容很简单,启动一个Action为Intent.ACTION_VIEW
,Flag为Intent.FLAG_GRANT_READ_URI_PERMISSION
,Type为application/vnd.android.package-archive
的Activity。
这个Activity在哪里呢?
Android 10之前,你可以在/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>
复制代码
但是,在Android 10的源码里,谷歌已经将PackageInstaller 这个app删掉了,这意味着使用之前的安装方式会存在一定的风险(ps:国产手机系统在Android 10中都自己添加了PackageInstaller这个app)。Android 10推荐采用一个叫PackageInstaller
的类来专门负责apk的安装与卸载工作,你可以在
/samples/ApiDemos/src/com/example/android/apis/content/InstallApkSessionApi.java
中找到此Api的使用示例:
Code 1
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
int sessionId = packageInstaller.createSession(params);
session = packageInstaller.openSession(sessionId);
addApkToInstallSession("HelloActivity.apk", session);
// Create an install status receiver.
Context context = InstallApkSessionApi.this;
Intent intent = new Intent(context, InstallApkSessionApi.class);
intent.setAction(PACKAGE_INSTALLED_ACTION);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
IntentSender statusReceiver = pendingIntent.getIntentSender();
// Commit the session (this will start the installation workflow).
session.commit(statusReceiver);
复制代码
这篇文章,我们探讨一下采用PackageInstaller
类来安装Apk的方式,采用PackageInstaller app来安装Apk的方式留个各位读者自行去探讨挖掘。
PackageInstaller
我们还是先从PackageInstaller
的定义开始看起,先上官方的说明:
Offers the ability to install, upgrade, and remove applications on the device. This
includes support for apps packaged either as a single "monolithic" APK, or apps packaged
as multiple "split" APKs.
An app is delivered for installation through a PackageInstaller.Session, which any app
can create. Once the session is created, the installer can stream one or more APKs into
place until it decides to either commit or destroy the session. Committing may require
user intervention to complete the installation, unless the caller falls into one of the
following categories, in which case the installation will complete automatically.
the device owner
the affiliated profile owner
Sessions can install brand new apps, upgrade existing apps, or add new splits into an existing app.
Apps packaged as multiple split APKs always consist of a single "base" APK (with
a null split name) and zero or more "split" APKs (with unique split names). Any subset
of these APKs can be installed together, as long as the following constraints are met:
PackageInstaller.Session
null
All APKs must have the exact same package name, version code, and signing certificates.
All APKs must have unique split names.
All installations must contain a single base APK.
复制代码
我来做一下翻译工作,由于最后一段讲的是拆分包的安装,这篇文章不对拆分包的安装做讨论,故直接略过了:
- 提供在设备上安装、升级和删除应用程序的功能,应用程序既可以是整包也可以是拆分包。
- 应用程序通过
PackageInstaller.Session
安装,任何应用程序都可以创建该Session。创建Session后,installer可以将一个或多个APK以流的形式传输到适当的位置,直到它决定提交或销毁Session。提交可能需要用户干预才能完成安装,除非调用方属于设备所有者或文件所有者,在这种情况下,安装将自动完成。 - Session可以安装全新的应用程序,升级现有应用程序,或将新拆分添加到现有应用程序中。
接下来,我们结合上文Code 1处的PackageInstall
Api调用代码来对它进行探讨。
PackageInstaller.SessionParams
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
复制代码
SessionParams
有以下两个值得关注的参数:
mode
Type | Description |
---|---|
MODE_FULL_INSTALL | 安装的Apk完全替换目标程序现有的Apk |
MODE_INHERIT_EXISTING | 继承目标应用程序的任何现有APK,除非它们已被会话显式覆盖(基于拆分名称) |
installLocation
Type | Description |
---|---|
INSTALL_LOCATION_AUTO | 让系统决定理想的安装位置。 |
INSTALL_LOCATION_INTERNAL_ONLY | 默认值,明确要求仅安装在手机内部存储上。 |
INSTALL_LOCATION_PREFER_EXTERNAL | 更倾向安装在SD卡上。不能保证系统会遵守这一要求。如果外部存储不可用或太满,应用程序可能会安装在内部存储上。 |
isStaged
标识此Session是否在重启时安装。
让我们回到PackageInstaller中,继续往下看。
packageInstaller.createSession
public int createSession(@NonNull SessionParams params) throws IOException {
try {
final String installerPackage;
if (params.installerPackageName == null) {
//这里的mInstallerPackageName指的是安装apk的应用程序包名,而不是目标apk的包名
installerPackage = mInstallerPackageName;
} else {
installerPackage = params.installerPackageName;
}
//调用PackageInstallerService里的createSession方法
return mInstaller.createSession(params, installerPackage, mUserId);
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
复制代码
可以看到,packageInstaller.createSession
方法最后将实际调用转交给服务端PackageInstallerService
去执行。
PackageInstallerService.createSession
@Override
public int createSession(SessionParams params, String installerPackageName, int userId) {
try {
return createSessionInternal(params, installerPackageName, userId);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
复制代码
createSessionInternal
private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
throws IOException {
final int callingUid = Binder.getCallingUid();
mPermissionManager.enforceCrossUserPermission(
callingUid, userId, true, true, "createSession");
//检查此用户是否被禁止安装程序
if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
throw new SecurityException("User restriction prevents installing");
}
//如果安装来自adb 或者 root用户,param里增加 INSTALL_FROM_ADB 的FLAG
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
} else {
// 只有具有INSTALL_PACKAGES权限的APP才允许以非调用者的身份设置为安装器
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) !=
PackageManager.PERMISSION_GRANTED) {
mAppOps.checkPackage(callingUid, installerPackageName);
}
//移除以下三个FLAG
params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
params.installFlags &= ~PackageManager.INSTALL_ALLOW_TEST;
//如果APP已存在,则替换它
params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0
&& !mPm.isCallerVerifier(callingUid)) {
params.installFlags &= ~PackageManager.INSTALL_VIRTUAL_PRELOAD;
}
}
if (Build.IS_DEBUGGABLE || isDowngradeAllowedForCaller(callingUid)) {
//允许降级
params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else {
//不允许降级,不允许向低版本升级
params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE;
}
...
if (!params.isMultiPackage) {
//安装时,只有系统组件可以绕过运行时权限。
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");
}
//如果AppIcon过大,则对它进行调整
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, sanity 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 {
//根据给定的installLocation计算安装需要的大小,选择要安装应用程
//序的实际卷。只考虑内部卷和专用卷,并且更偏向将现有包保留在其当前卷上。
//volumeUuid 指的是 安装所在的卷的fsUuid,如果安装在内部存储上,则返回null
params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
final int sessionId;
final PackageInstallerSession session;
synchronized (mSessions) {
// 检查安装程序是否正常运行,打开的Installer Session不能超过1024个
final int activeCount = getSessionCount(mSessions, callingUid);
if (activeCount >= MAX_ACTIVE_SESSIONS) {
throw new IllegalStateException(
"Too many active sessions for UID " + callingUid);
}
//历史Session不能超过1048576个
final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);
if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
throw new IllegalStateException(
"Too many historical sessions for UID " + callingUid);
}
//随机生成一个不超过Integer.MAX_VALUE的sessionId,并保存在mAllocatedSessions中
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) {
//安装在内部存储上,volumeUuid 为null
//复制路径为/data/app/vmdl${sessionId}.tmp
stageDir = buildSessionDir(sessionId, params);
} else {
stageCid = buildExternalStageCid(sessionId);
}
}
//存储了安装相关的所有信息
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId,
installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
false, false, null, SessionInfo.INVALID_ID, false, false, false,
SessionInfo.STAGED_SESSION_NO_ERROR, "");
//将sessionId和session绑定起来
synchronized (mSessions) {
mSessions.put(sessionId, session);
}
if (params.isStaged) {
//处理分阶段安装会话,即仅在重新启动后才需要安装的会话。
mStagingManager.createSession(session);
}
if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) {
//回调通知Session已创建成功
mCallbacks.notifySessionCreated(session.sessionId, session.userId);
}
//异步将session信息写入install_sessions.xml文件中
writeSessionsAsync();
return sessionId;
//检查此用户是否被禁止安装程序
if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
throw new SecurityException("User restriction prevents installing");
}
//如果安装来自adb 或者 root用户,param里增加 INSTALL_FROM_ADB 的FLAG
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
} else {
// 只有具有INSTALL_PACKAGES权限的APP才允许以非调用者的身份设置为安装器
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) !=
PackageManager.PERMISSION_GRANTED) {
mAppOps.checkPackage(callingUid, installerPackageName);
}
//移除以下三个FLAG
params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
params.installFlags &= ~PackageManager.INSTALL_ALLOW_TEST;
//如果APP已存在,则替换它
params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0
&& !mPm.isCallerVerifier(callingUid)) {
params.installFlags &= ~PackageManager.INSTALL_VIRTUAL_PRELOAD;
}
}
if (Build.IS_DEBUGGABLE || isDowngradeAllowedForCaller(callingUid)) {
//允许降级
params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else {
//不允许降级,不允许向低版本升级
params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE;
}
if (!params.isMultiPackage) {
//安装时,只有系统组件可以绕过运行时权限。
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");
}
//如果AppIcon过大,则对它进行调整
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, sanity 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 {
//根据给定的installLocation计算安装需要的大小,选择要安装应用程
//序的实际卷。只考虑内部卷和专用卷,并且更偏向将现有包保留在其当前卷上。
//volumeUuid 指的是 安装所在的卷的fsUuid,如果安装在内部存储上,则返回null
params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
final int sessionId;
final PackageInstallerSession session;
synchronized (mSessions) {
// 检查安装程序是否正常运行,打开的Installer Session不能超过1024个
final int activeCount = getSessionCount(mSessions, callingUid);
if (activeCount >= MAX_ACTIVE_SESSIONS) {
throw new IllegalStateException(
"Too many active sessions for UID " + callingUid);
}
//历史Session不能超过1048576个
final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);
if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
throw new IllegalStateException(
"Too many historical sessions for UID " + callingUid);
}
//随机生成一个不超过Integer.MAX_VALUE的sessionId,并保存在mAllocatedSessions中
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) {
//安装在内部存储上,volumeUuid 为null
//复制路径为/data/app/vmdl${sessionId}.tmp
stageDir = buildSessionDir(sessionId, params);
} else {
stageCid = buildExternalStageCid(sessionId);
}
}
//存储了安装相关的所有信息
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId,
installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
false, false, null, SessionInfo.INVALID_ID, false, false, false,
SessionInfo.STAGED_SESSION_NO_ERROR, "");
//将sessionId和session绑定起来
synchronized (mSessions) {
mSessions.put(sessionId, session);
}
if (params.isStaged) {
//处理分阶段安装会话,即仅在重新启动后才需要安装的会话。
mStagingManager.createSession(session);
}
if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) {
//回调通知Session已创建成功
mCallbacks.notifySessionCreated(session.sessionId, session.userId);
}
//异步将session信息写入install_sessions.xml文件中
writeSessionsAsync();
return sessionId;
}
复制代码
createSessionInternal
方法主要是对 初始化apk的安装信息及环境,并创建一个sessionId,将安装Session与sessionId 进行绑定。
packageInstaller.openSession
public @NonNull Session openSession(int sessionId) throws IOException {
try {
try {
return new Session(mInstaller.openSession(sessionId));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
}
}
复制代码
我们继续追踪PackageInstallerService
中的openSession
方法:
PackageInstallerService.openSession
public IPackageInstallerSession openSession(int sessionId) {
try {
return openSessionInternal(sessionId);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
复制代码
openSessionInternal
private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException {
synchronized (mSessions) {
final PackageInstallerSession session = mSessions.get(sessionId);
if (session == null || !isCallingUidOwner(session)) {
throw new SecurityException("Caller has no access to session " + sessionId);
}
//如果StageDir不为空,则创建此文件夹
//stageDir是写入客户端数据的暂存位置
session.open();
return session;
}
}
复制代码
可以看到,openSessionInternal
方法只是根据sessionId查找在上一阶段通过createSession
创建的PackageInstallerSession
对象并将其返回。
安装代码接下来调用了addApkToInstallSession("HelloActivity.apk", session);
,这个方法的详细实现如下:
private void addApkToInstallSession(String assetName, PackageInstaller.Session session)
throws IOException {
// It's recommended to pass the file size to openWrite(). Otherwise installation may fail
// if the disk is almost full.
try (OutputStream packageInSession = session.openWrite("package", 0, -1);
InputStream is = getAssets().open(assetName)) {
byte[] buffer = new byte[16384];
int n;
while ((n = is.read(buffer)) >= 0) {
packageInSession.write(buffer, 0, n);
}
}
}
复制代码
session.openWrite
openWrite
方法的主要作用是打开一个流,将APK文件写入Session。返回的流将开始在文件中请求的偏移量处写入数据,该偏移量可用于恢复部分写入的文件。如果指定了有效的文件长度,系统将预先分配底层磁盘空间,以优化磁盘上的位置。强烈建议在已知的情况下提供有效的文件长度。
您可以将数据写入返回的流,也可以根据需要调用fsync(OutputStream)
,以确保字节已持久化到磁盘,然后在完成时关闭。在调用commit(IntentSender)
之前,必须关闭所有流。
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 {
//ENABLE_REVOCABLE_FD默认为false
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();
}
}
复制代码
这个方法里我们重点关注这句话 mSession.openWrite(name, offsetBytes, lengthBytes)
,它意味者最终由 PackageInstallerSession
类里的openWrite
方法继续来处理。
PackageInstallerSession.openWrite
@Override
public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
try {
return doWriteInternal(name, offsetBytes, lengthBytes, null);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
复制代码
继续看doWriteInternal
方法:
doWriteInternal
private ParcelFileDescriptor doWriteInternal(String name, long offsetBytes, long lengthBytes,
ParcelFileDescriptor incomingFd) throws IOException {
//快速检查状态是否正常,并为自己分配一个Pipe。
//然后,我们在锁之外进行大量的磁盘分配,但是这个打开的Pipe将阻止任何尝试的安装转换
final RevocableFileDescriptor fd;
final FileBridge bridge;
final File stageDir;
synchronized (mLock) {
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)) {
//Check if given filename is valid for an ext4 filesystem.
throw new IllegalArgumentException("Invalid name: " + name);
}
final File target;
final long identity = Binder.clearCallingIdentity();
try {
target = new File(stageDir, name);
} finally {
Binder.restoreCallingIdentity(identity);
}
// TODO: this should delegate to DCS so the system process avoids
// holding open FDs into containers.
//打开文件并设置权限为644
final FileDescriptor targetFd = Os.open(target.getAbsolutePath(),
O_CREAT | O_WRONLY, 0644);
Os.chmod(target.getAbsolutePath(), 0644);
// 如果指定了APK大小,先在磁盘中分配空间
if (stageDir != null && lengthBytes > 0) {
mContext.getSystemService(StorageManager.class).allocateBytes(targetFd, lengthBytes,
PackageHelper.translateAllocateFlags(params.installFlags));
}
if (offsetBytes > 0) {
Os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
}
if (incomingFd != null) {
//如果调用了session.write方法,incomingFd不为null,将会进入此分支
switch (Binder.getCallingUid()) {
case android.os.Process.SHELL_UID:
case android.os.Process.ROOT_UID:
case android.os.Process.SYSTEM_UID:
break;
default:
throw new SecurityException(
"Reverse mode only supported from shell or system");
}
// In "reverse" mode, we're streaming data ourselves from the
// incoming FD, which means we never have to hand out our
// sensitive internal FD. We still rely on a "bridge" being
// inserted above to hold the session active.
try {
final Int64Ref last = new Int64Ref(0);
//将数据写入暂存文件
FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, lengthBytes, null,
Runnable::run, (long progress) -> {
if (params.sizeBytes > 0) {
final long delta = progress - last.value;
last.value = progress;
addClientProgress((float) delta / (float) params.sizeBytes);
}
});
} finally {
IoUtils.closeQuietly(targetFd);
IoUtils.closeQuietly(incomingFd);
//完成传输,关闭file bridge
synchronized (mLock) {
if (PackageInstaller.ENABLE_REVOCABLE_FD) {
mFds.remove(fd);
} else {
bridge.forceClose();
mBridges.remove(bridge);
}
}
}
return null;
}
bridge.setTargetFile(targetFd);
bridge.start();
return new ParcelFileDescriptor(bridge.getClientSocket());
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
try {
//先使用安装程序提供的名称;我们之后会重命名
if (!FileUtils.isValidExtFilename(name)) {
//Check if given filename is valid for an ext4 filesystem.
throw new IllegalArgumentException("Invalid name: " + name);
}
final File target;
final long identity = Binder.clearCallingIdentity();
try {
target = new File(stageDir, name);
} finally {
Binder.restoreCallingIdentity(identity);
}
// TODO: this should delegate to DCS so the system process avoids
// holding open FDs into containers.
//打开文件并设置权限为644
final FileDescriptor targetFd = Os.open(target.getAbsolutePath(),
O_CREAT | O_WRONLY, 0644);
Os.chmod(target.getAbsolutePath(), 0644);
// 如果指定了APK大小,先在磁盘中分配空间
if (stageDir != null && lengthBytes > 0) {
mContext.getSystemService(StorageManager.class).allocateBytes(targetFd, lengthBytes,
PackageHelper.translateAllocateFlags(params.installFlags));
}
if (offsetBytes > 0) {
Os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
}
if (incomingFd != null) {
//如果调用了session.write方法,incomingFd不为null,将会进入此分支
switch (Binder.getCallingUid()) {
case android.os.Process.SHELL_UID:
case android.os.Process.ROOT_UID:
case android.os.Process.SYSTEM_UID:
break;
default:
throw new SecurityException(
"Reverse mode only supported from shell or system");
}
// In "reverse" mode, we're streaming data ourselves from the
// incoming FD, which means we never have to hand out our
// sensitive internal FD. We still rely on a "bridge" being
// inserted above to hold the session active.
try {
final Int64Ref last = new Int64Ref(0);
//将数据写入暂存文件
FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, lengthBytes, null,
Runnable::run, (long progress) -> {
if (params.sizeBytes > 0) {
final long delta = progress - last.value;
last.value = progress;
addClientProgress((float) delta / (float) params.sizeBytes);
}
});
} finally {
IoUtils.closeQuietly(targetFd);
IoUtils.closeQuietly(incomingFd);
//完成传输,关闭file bridge
synchronized (mLock) {
if (PackageInstaller.ENABLE_REVOCABLE_FD) {
mFds.remove(fd);
} else {
bridge.forceClose();
mBridges.remove(bridge);
}
}
}
return null;
}
bridge.setTargetFile(targetFd);
bridge.start();
return new ParcelFileDescriptor(bridge.getClientSocket());
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
}
复制代码
doWriteInternal
这个方法主要是初始化要安装的APK文件的磁盘存储空间(如果指定了大小的话),并创建了一个可以跨进程写文件的FileBridge
对象,通过ParcelFileDescriptor
类包装称文件输出流提供给应用层写入安装的APK数据。
需要注意的是,后续的session.write
方法调用的也是doWriteInternal
,不同的是传入的参数incomingFd
不为null,这意味着doWriteInternal
会将数据写入到incomingFd
这个文件描述符所对应的文件,它也是我们在调用openWrite
方法时创建的文件。
到这一步为止,我们完成了要安装的Apk的复制工作。
session.commit
现在到了我们最后一步了,将Installer Session提交,去真正执行安装Apk的工作。我们来看一下commit
方法:
public void commit(@NonNull IntentSender statusReceiver) {
try {
mSession.commit(statusReceiver, false);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
复制代码
同样的,它会把此方法委托给PackageInstallerSession
类去执行。需要注意的是,commit
方法里有一个IntentSender
参数,这个参数的作用是用来通知安装时Session
状态的改变,比如 安装需要用户进一步确认、安装成功、安装失败等事件。
PackageInstallerSession.commit
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
if (hasParentSessionId()) {
throw new IllegalStateException(
"Session " + sessionId + " is a child of multi-package session "
+ mParentSessionId + " and may not be committed directly.");
}
if (!markAsCommitted(statusReceiver, forTransfer)) {
return;
}
...
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
复制代码
commit
方法最终向mHandler中发送了一条MSG_COMMIT
的消息,而在这个消息里,实际执行的是handleCommit
方法。
handleCommit
private void handleCommit() {
...
// 返回子Session列表,如果Session不是multipackage,则返回null
List<PackageInstallerSession> childSessions = getChildSessions();
try {
synchronized (mLock) {
commitNonStagedLocked(childSessions);
}
} catch (PackageManagerException e) {
final String completeMsg = ExceptionUtils.getCompleteMessage(e);
Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
destroyInternal();
dispatchSessionFinished(e.error, completeMsg, null);
}
// 返回子Session列表,如果Session不是multipackage,则返回null
List<PackageInstallerSession> childSessions = getChildSessions();
try {
synchronized (mLock) {
commitNonStagedLocked(childSessions);
}
} catch (PackageManagerException e) {
final String completeMsg = ExceptionUtils.getCompleteMessage(e);
Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
destroyInternal();
dispatchSessionFinished(e.error, completeMsg, null);
}
}
复制代码
commitNonStagedLocked
private void commitNonStagedLocked(List<PackageInstallerSession> childSessions)
throws PackageManagerException {
//返回一个ActiveInstallSession对象
final PackageManagerService.ActiveInstallSession committingSession =
makeSessionActiveLocked();
if (committingSession == null) {
return;
}
if (isMultiPackage()) {
List<PackageManagerService.ActiveInstallSession> activeChildSessions =
new ArrayList<>(childSessions.size());
boolean success = true;
PackageManagerException failure = null;
for (int i = 0; i < childSessions.size(); ++i) {
final PackageInstallerSession session = childSessions.get(i);
try {
final PackageManagerService.ActiveInstallSession activeSession =
session.makeSessionActiveLocked();
if (activeSession != null) {
activeChildSessions.add(activeSession);
}
} catch (PackageManagerException e) {
failure = e;
success = false;
}
}
if (!success) {
try {
mRemoteObserver.onPackageInstalled(
null, failure.error, failure.getLocalizedMessage(), null);
} catch (RemoteException ignored) {
}
return;
}
mPm.installStage(activeChildSessions);
} else {
//我们通常执行的安装走这个分支
mPm.installStage(committingSession);
}
}
复制代码
在commitNonStagedLocked
方法的最后,调用PMS的installStage方法,这样代码逻辑就进入了PMS中,让PMS去对要安装的APK文件做内容的解析,完成真正的安装工作。
实际上,PackageInstaller
只是负责安装前期的准备工作,它维持了一个安装会话,管理安装的参数,并提供将安装包临时复制到特定路径的功能,但真正实现安装还是得依靠PMS。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库