前言
通过前面两篇文章的学习, 我们知晓了 PKMS 的启动过程
- 解析备份文件
- 扫描已安装的应用程序
不过 PKMS 除了负责管理已安装的应用程序之外, 还负责应用程序的安装操作, 我们 Android 端, 整个应用程序的安装主要有两种方式:
- 通过 Session 发起(adb, 手动点击安装)
- 提前将安装包拷贝到 “data/app/vmdl${sessionId}.tmp/” 目录下
- 再通过 Session commit, 通知 PKMS 的 PKMS.installStage 进行安装
- 通过系统的软件商店自动发起
- 直接通过 PKMS 的 PKMS.installStage 进行安装
也就是说通过非系统软件商店的安装要多一步通过 Session 提前拷贝的过程, 这里我们为了更全面了解应用安装, 选择有 Session 通信的进行分析
一. Session 发起
当我们下载了一个 apk, 点击进行安装时, 会跳转到 PackageInstallerActivity 这个 Activity, 厂商可以为这个 Activity 进行定制和修改, 不过万变不离其宗, 我们看看它是如何安装一个 apk 的
public class PackageInstallerActivity extends OverlayTouchActivity implements OnClickListener {
private Uri mPackageURI;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(null);
final Uri packageUri;
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
......
} else {
mSessionId = -1;
// 1. 获取发起者传来的要待安装文件的 URI
packageUri = intent.getData();
......
}
......
boolean wasSetUp = processPackageUri(packageUri);
// 初始化视图
bindUi(R.layout.install_confirm, false);
}
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
......
return true;
}
private Button mOk;
private void bindUi(int layout, boolean enableOk) {
setContentView(layout);
......
mOk = (Button) findViewById(R.id.ok_button);
......
mOk.setEnabled(enableOk);
......
}
public void onClick(View v) {
if (v == mOk) {
if (mOk.isEnabled()) {
if (mOkCanInstall || mScrollView == null) {
if (mSessionId != -1) {
......
} else {
// 2. 点击确认启动安装
startInstall();
}
} else {
......
}
}
} else if (v == mCancel) {
......
}
}
private void startInstall() {
// 3. 跳转到应用安装页面
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
// 注入安装包 uri
newIntent.setData(mPackageURI);
// 跳转到 InstallInstalling 页面执行应用安装操作
newIntent.setClass(this, InstallInstalling.class);
......
startActivity(newIntent);
finish();
}
}
可以看到 PackageInstallerActivity 在 onCreate 的方法中获取了应用安装包的 URI, 当我们点击确定的时候, 调用了 startInstall 这个方法, 它将安装包 URI 作为参数注入 intent 跳转到了 InstallInstalling 页面
接下来我们看看 InstallInstalling 又是如何执行安装操作的
public class InstallInstalling extends Activity {
/** URI of package to install */
private Uri mPackageURI;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.install_installing);
......
mPackageURI = getIntent().getData();
// 若 URI 为 package 开头, 意为更新应用
if ("package".equals(mPackageURI.getScheme())) {
......
}
// 安装应用
else {
final File sourceFile = new File(mPackageURI.getPath());
......
if (savedInstanceState != null) {
......
} else {
// 1. 获取 SessionId
// 1.1 构建请求参数
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
......
try {
// 1.2 将 params 传入, 创建一个与 PKMS 安装服务交互的 SessionId
mSessionId = getPackageManager().getPackageInstaller().createSession(params);
} catch (IOException e) {
......
}
}
......
mSessionCallback = new InstallSessionCallback();
}
}
......
private InstallingAsyncTask mInstallingTask;
@Override
protected void onResume() {
super.onResume();
//
if (mInstallingTask == null) {
// 获取 PackageInstaller
PackageInstaller installer = getPackageManager().getPackageInstaller();
// 获取会话信息
PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
// 2. 通过 InstallingAsyncTask 将文件发送给 PackageInstallerSession
if (sessionInfo != null && !sessionInfo.isActive()) {
mInstallingTask = new InstallingAsyncTask();
mInstallingTask.execute();
} else {
......
}
}
}
}
好的, 可以看到 InstallInstalling 的生命周期回调中所做的事情如下
- 通过 getPackageManager().getPackageInstaller().createSession(…) 获取一个 SessionId
- 通过 InstallingAsyncTask 将安装包发送给 PackageInstallerSession
首先我们看看获取 SessionId 的动作, PackageInstaller 是 PackageInstallerService 在客户端的 Binder 代理对象, PackageInstallerService 用于维护整个系统的应用安装的任务, 我们直接看看它的实现类 PackageInstallerService 的实现逻辑
一) 获取 SessionId
public class PackageInstallerService extends IPackageInstaller.Stub {
@Override
public int createSession(SessionParams params, String installerPackageName, int userId) {
try {
return createSessionInternal(params, installerPackageName, userId);
} catch (IOException e) {
......
}
}
// 描述一个应用安装任务集合
private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
/** Upper bound on number of active sessions for a UID */
private static final long MAX_ACTIVE_SESSIONS = 1024;
/** Upper bound on number of historical sessions for a UID */
private static final long MAX_HISTORICAL_SESSIONS = 1048576;
private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
throws IOException {
// 描述应用安装发起进程
final int callingUid = Binder.getCallingUid();
......// 执行一堆验参
// 创建 SessionId
final int sessionId;
final PackageInstallerSession session;
synchronized (mSessions) {
// 一个客户端正在安装的应用不能超过 1024
final int activeCount = getSessionCount(mSessions, callingUid);
if (activeCount >= MAX_ACTIVE_SESSIONS) {
throw new IllegalStateException(
"Too many active sessions for UID " + callingUid);
}
// 一个客户端应用安装历史不能炒作 1048576
final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);
if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
......
}
// 1. 为客户端发起的安装任务, 分配一个 SessionId
sessionId = allocateSessionIdLocked();
}
......
// 2. 创建一个 stageDir, 用于接收客户端要安装的 apk
File stageDir = null;
......
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
// 安装到内部存储区, 创建一个路径, 用于存储客户端的 apk
final boolean isInstant = (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
// 构建一个路径
stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant);
} else {
......
}
// 3. 创建一个 PackageInstallerSession, 描述安装任务
session = new PackageInstallerSession(mInternalCallback, mContext, mPm,
mInstallThread.getLooper(), sessionId, userId, installerPackageName, callingUid,
params, createdMillis, stageDir, stageCid, false, false);
// 投入缓存
synchronized (mSessions) {
mSessions.put(sessionId, session);
}
......
return sessionId;
}
private File buildStagingDir(String volumeUuid, boolean isEphemeral) {
return Environment.getDataAppDirectory(volumeUuid);
}
private File buildStageDir(String volumeUuid, int sessionId, boolean isEphemeral) {
final File stagingDir = buildStagingDir(volumeUuid, isEphemeral);
return new File(stagingDir, "vmdl" + sessionId + ".tmp");
}
}
从 PackageInstallerService 的 openSession 实现中还是能够看到很多有意思的信息, 比如一个客户端能够发起安装的最大数量为 1024, 它的历史安装任务不能超过 1048576 等, 不过其中我们需要重点关注的事情如下
- 分配 SessionId
- 创建 stageDir 用于后续接收客户端要安装的 apk
- “data/app/vmdl${sessionId}.tmp/”
- 创建一个 PackageInstallerSession 对象, 描述一个安装任务
- 它也是一个 Binder 代理对象
客户端有了 SessionId, 就可以找到服务端对应的 PackageInstallerSession 与之进行交互了
上面我们看到, 在创建 PackageInstallerSession 的过程中, 创建一个了一个 stageDir, 这个文件路径就是用来接收要安装的 apk 文件的, 接下来我们看看 InstallingAsyncTask 发送安装文件的过程
二) 发送安装文件
public class InstallInstalling extends Activity {
private final class InstallingAsyncTask extends AsyncTask<Void, Void,
PackageInstaller.Session> {
volatile boolean isDone;
@Override
protected PackageInstaller.Session doInBackground(Void... params) {
// 1. 获取 PackageInstallerSession 在客户端的代理对象
PackageInstaller.Session session;
try {
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
} catch (IOException e) {
return null;
}
session.setStagingProgress(0);
// 2. 将安装包发送给 Pacakge Installer
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);
......
out.write(buffer, 0, numRead);
......
}
}
}
return session;
}
.....
}
@Override
protected void onPostExecute(PackageInstaller.Session session) {
if (session != null) {
......
// 2. 提交安装请求
session.commit(pendingIntent.getIntentSender());
......
} else {
......// 回调安装失败
}
}
}
}
InstallingAsyncTask 中做了如下的事务
- 首选根据上面打开的 mSessionId 获取一个 PackageInstallerSession 在客户端的 Binder 代理对象
- 然后通过这个 Binder 代理对象获取 暂存位置的 输出流, 并将安装包拷贝到其中
- 暂存位置为 “data/app/vmdl${sessionId}.tmp/PackageInstaller”
- 文件发送成功之后, 通过 Session 提交一个应用安装的请求
InstallingAsyncTask 执行完毕之后, 我们的文件就拷贝到 stageDir 中了, 接下来我们去系统服务进程中看看 PackageInstallerSession 如何提交一个应用安装请求
三) 提交应用安装请求
public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
......
final boolean wasSealed;
synchronized (mLock) {
wasSealed = mSealed;
if (!mSealed) {
try {
// 1. 将 PackageInstaller 重命名为 "base.apk"
sealAndValidateLocked();
} catch (IOException e) {
......
}
}
......
mCommitted = true;
// 发送一个 MSG_COMMIT 到 Handler 的消息队列中执行
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
......
}
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
......
case MSG_COMMIT:
synchronized (mLock) {
try {
commitLocked();
} catch (PackageManagerException e) {
......
}
}
break;
}
}
}
private final PackageManagerService mPm;
@GuardedBy("mLock")
private void commitLocked()
throws PackageManagerException {
// 2. 解压 Native 库到 "data/app/vmdl${sessionId}.tmp/lib/" 目录下暂存
extractNativeLibraries(mResolvedStageDir, params.abiOverride, mayInheritNativeLibs());
......
// 3. 请求 PKMS.installStage 执行安装操作
mPm.installStage(mPackageName, stageDir, localObserver, params,
mInstallerPackageName, mInstallerUid, user, mSigningDetails);
}
}
可以看到 PackageInstallerSession 的 commit 操作如下
- 调用 sealAndValidateLocked 对拷贝过来的安装包进行验证
- 将 “data/app/vmdl${sessionId}.tmp/PackageInstaller” 重命名为 “data/app/vmdl${sessionId}.tmp/base.apk”
- 解压 Native 库
- 调用 PKMS.installStage 发起这次应用安装的请求
到这里应用安装前的准备就执行完毕了, 下面做个简单的回顾
四) 回顾
应用安装前的准备工作如下
- 获取 SessionId 描述一个安装任务
- 分配 SessionId
- 创建临时目录 stageDir
- “data/app/vmdl{sessionId}.tmp”
- 创建服务进程的 PackageInstallerSession 对象
- 将安装包拷贝到 “data/app/vmdl${sessionId}.tmp/PackageInstaller” 位置下暂存
- 提交安装任务
- 调用 sealAndValidateLocked 对拷贝过来的安装包进行验证
- 将 “data/app/vmdl${sessionId}.tmp/PackageInstaller” 重命名为 “data/app/vmdl${sessionId}.tmp/base.apk”
- 解压 Native 库
- 调用 PKMS.installStage 发起这次应用安装的请求
- 调用 sealAndValidateLocked 对拷贝过来的安装包进行验证
二. PKMS 安装应用
public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
void installStage(String packageName, File stagedDir,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
String installerPackageName, int installerUid, UserHandle user,
PackageParser.SigningDetails signingDetails) {
......
// 构建一个 INIT_COPY 的 Message
final Message msg = mHandler.obtainMessage(INIT_COPY);
......
// 1. 创建安装参数
final InstallParams params = new InstallParams(origin, null, observer,
sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
verificationInfo, user, sessionParams.abiOverride,
sessionParams.grantedRuntimePermissions, signingDetails, installReason);
......
msg.obj = params;
......
mHandler.sendMessage(msg);
}
class PackageHandler extends Handler {
void doHandleMessage(Message msg) {
switch (msg.what) {
......
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
.....
// 若为开启应用安装服务, 则先执行绑定操作
if (!mBound) {
// 2. 尝试取绑定应用安装服务
if (!connectToService()) {
......
return;
} else {
// 3. 绑定成功, 则添加到 mPendingInstalls, 等待执行
mPendingInstalls.add(idx, params);
}
} else {
// 已经绑定了安装服务, 则直接添加到缓存中, 会自动取数据执行
mPendingInstalls.add(idx, params);
// 若 idx 为 0, 则需要手动触发一次, 来执行 mPendingInstalls 中的任务
if (idx == 0) {
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
break;
}
......
}
}
}
}
当 PackageInstallerSession 提交了安装任务之后, PKMS 会构建一个 InstallParams 描述待安装任务的参数, 然后发送一条应用拷贝的消息
应用拷贝的操作是在应用安装服务中进行的, 因此 PKMS 还需要绑定应用安装服务, 绑定成功之后将这个任务添加到 mPendingInstalls 中
接下来我们看看如何绑定应用安装服务
一) 绑定应用安装服务
public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
// 应用安装服务 DefaultContainerService
public static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
// 描述与应用安装服务的连接
final private DefaultContainerConnection mDefContainerConn =
new DefaultContainerConnection();
// 服务连接实现类
class DefaultContainerConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
// 2. 获取与服务端交互的 Binder 代理对象
final IMediaContainerService imcs = IMediaContainerService.Stub
.asInterface(Binder.allowBlocking(service));
// 3. 发送消息表示绑定完毕
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
}
......
}
class PackageHandler extends Handler {
private boolean connectToService() {
......
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
......
// 1. 绑定 DefaultContainerService
if (mContext.bindServiceAsUser(service, mDefContainerConn,
Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) {
......
mBound = true;
return true;
}
......
return false;
}
}
}
PKMS 绑定的应用安装服务为 DefaultContainerService, 绑定完成之后会发送 MCS_BOUND 消息, 继续执行 mPendingInstalls 中的任务
接下来看看 MCS_BOUND 消息如何执行安装任务
二) 执行安装任务
public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
class PackageHandler extends Handler {
void doHandleMessage(Message msg) {
switch (msg.what) {
......
case MCS_BOUND: {
if (msg.obj != null) {
// 获取与 DefaultContainerService 交互的 Binder 代理对象
mContainerService = (IMediaContainerService) msg.obj;
}
if (mContainerService == null) {
......
} else if (mPendingInstalls.size() > 0) {
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
......
// 调用 HandlerParams.startCopy 开启安装包拷贝操作
if (params.startCopy()) {
// 拷贝成功, 移除这个待安装项
if (mPendingInstalls.size() > 0) {
mPendingInstalls.remove(0);
}
// 没有待安装任务了, 解绑 DefaultContainerService
if (mPendingInstalls.size() == 0) {
if (mBound) {
removeMessages(MCS_UNBIND);
Message ubmsg = obtainMessage(MCS_UNBIND);
sendMessageDelayed(ubmsg, 10000);
}
}
// 继续发送 MCS_BOUND, 安装 mPendingInstalls 中的任务
else {
......
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
}
}
......
break;
}
......
}
}
}
}
可以看到 PKMS 对 MCS_BOUND 的处理, 最主要的还是调用了 HandlerParams.startCopy 开启了安装包的拷贝操作
public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
private abstract class HandlerParams {
final boolean startCopy() {
boolean res;
try {
......
if (++mRetries > MAX_RETRIES) {
.......
} else {
// 1. 拷贝安装包
handleStartCopy();
res = true;
}
} catch (RemoteException e) {
......
}
......
// 2. 安装应用程序
handleReturnCode();
return res;
}
}
}
HandlerParams 中的 startCopy
- 调用了 handleStartCopy 执行安装包的拷贝操作
- 执行了 handleReturnCode 进行应用程序的安装
这便是应用安装的终点操作了, HandlerParams 的实现类为 InstallParams, 我们分别看看它的实现
1. 安装包的拷贝
public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
class InstallParams extends HandlerParams {
public void handleStartCopy() throws RemoteException {
int ret = PackageManager.INSTALL_SUCCEEDED;
......
final InstallArgs args = createInstallArgs(this);
mArgs = args;
if (ret == PackageManager.INSTALL_SUCCEEDED) {
.......
if (!origin.existing && requiredUid != -1
&& isVerificationEnabled(
verifierUser.getIdentifier(), installFlags, installerUid)) {
........
} else {
// 调用 FileInstallArgs.copyApk 执行安装包拷贝
ret = args.copyApk(mContainerService, true);
}
}
mRet = ret;
}
}
}
InstallParams 中的 handleStartCopy 它会调用 InstallArgs 的 copyApk 执行 apk 的拷贝操作, 我们看看它的实现
public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
class FileInstallArgs extends InstallArgs {
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
try {
// 拷贝应用程序
return doCopyApk(imcs, temp);
} finally {
......
}
}
private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
......
// 1. 若是通过 Session 拷贝过了, 则跳过拷贝的操作
if (origin.staged) {
if (DEBUG_INSTALL) Slog.d(TAG, origin.file + " already staged; skipping copy");
codeFile = origin.file;
resourceFile = origin.file;
return PackageManager.INSTALL_SUCCEEDED;
}
// 2. 执行拷贝操作
// 2.1 创建临时目录 "data/app/vmdl${sessionId}.tmp/"
try {
final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
final File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
codeFile = tempDir;
} catch (IOException e) {
.....
}
// 2.2 实现一个获取文件描述符的工厂方法, 也是一个 Binder 对象
final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() {
@Override
public ParcelFileDescriptor open(String name, int mode) throws RemoteException {
if (!FileUtils.isValidExtFilename(name)) {
throw new IllegalArgumentException("Invalid filename: " + name);
}
try {
// 用于打开文件描述符
final File file = new File(codeFile, name);
final FileDescriptor fd = Os.open(file.getAbsolutePath(),
O_RDWR | O_CREAT, 0644);
Os.chmod(file.getAbsolutePath(), 0644);
return new ParcelFileDescriptor(fd);
} catch (ErrnoException e) {
throw new RemoteException("Failed to open: " + e.getMessage());
}
}
};
......
// 2.3 调用了 IMediaContainerService 代理对象的 copyPackage, 将 apk 拷贝到安装目录
ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
......
// 2.4 解压 Native 库文件
final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
NativeLibraryHelper.Handle handle = null;
try {
handle = NativeLibraryHelper.Handle.create(codeFile);
ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
abiOverride);
} catch (IOException e) {
Slog.e(TAG, "Copying native libraries failed", e);
ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
} finally {
IoUtils.closeQuietly(handle);
}
......
return ret;
}
}
}
InstallParams 中的 handleStartCopy 流程如下
- 若是已经通过 Session 拷贝过了, 跳过拷贝的过程
- 若未拷贝, 它会调用 InstallArgs 的 copyApk 执行 apk 的拷贝操作, 其主要步骤如下
- 创建拷贝的目录 “data/app/vmdl${sessionId}.tmp/”
- 实现一个获取文件描述符的工厂方法, 也是一个 Binder 对象
- 调用了 IMediaContainerService 的 copyPackage, 在远程服务中执行 apk 拷贝操作
- 拷贝到 “data/app/vmdl${sessionId}.tmp/base.apk” 中
- 解压 Native 库文件
关于安装包的拷贝我们就看到这里, 接下来回到 PKMS 中看看执行应用的安装操作
二) 应用的安装
public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
class InstallParams extends HandlerParams {
@Override
void handleReturnCode() {
if (mArgs != null) {
// 执行安装操作
processPendingInstall(mArgs, mRet);
}
}
}
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
// Queue up an async operation since the package installation may take a little while.
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
// Result object to be returned
PackageInstalledInfo res = new PackageInstalledInfo();
res.setReturnCode(currentStatus);
res.uid = -1;
res.pkg = null;
res.removedInfo = null;
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
// 安装前准备
args.doPreInstall(res.returnCode);
synchronized (mInstallLock) {
// 执行安装操作
installPackageTracedLI(args, res);
}
// 安装结束
args.doPostInstall(res.returnCode, res.uid);
}
......
// 记录正在安装的应用
int token;
if (mNextInstallToken < 0) mNextInstallToken = 1;
token = mNextInstallToken++;
PostInstallData data = new PostInstallData(args, res);
mRunningInstalls.put(token, data);
}
});
}
}
这里我们主要看看 installPackageTracedLI 是如何安装应用程序的
public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
private void installPackageTracedLI(InstallArgs args, PackageInstalledInfo res) {
try {
......
// 安装应用程序
installPackageLI(args, res);
} finally {
......
}
}
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
// tmpPackageFile 即 base.apk
final File tmpPackageFile = new File(args.getCodePath());
final PackageParser.Package pkg;
try {
// 1. 解析安装包中的信息发布到 PKMS 中
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
// 2. 解析 dex 文件
DexMetadataHelper.validatePackageDexMetadata(pkg);
} catch (PackageParserException e) {
.......
}
// 3. 调用 InstallArgs 将 "data/app/vmdl${sessionId}.tmp/" 更换成正式名称
if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
......
}
// 4. 优化 dex 文件
BackgroundDexOptService.notifyPackageChanged(pkg.packageName);
......
}
}
installPackageLI 非常复杂, 这里进行了大量的删减, 其主要流程如下
- 调用了 PackageParser.Package 解析 base.apk 安装包文件
- 解析 apk 的 dex 文件
- 调用 InstallArgs.doRename 更换目录名称
- 更名前: “data/app/vmdl${sessionId}.tmp/”
- 更名后: “data/app/${PackageName}${Base64 随机码}/”
- 优化 dex 文件
重点的流程在于 PackageParser.Package, 这个方法我们在上一篇文章中已经分析过了, 不同的是上一篇文章扫描的是安装好的文件夹, 这里扫描的是 base.apk 中的信息, 最终都会将 apk 内部的 AndroidManifest.xml 中的信息发布到 PKMS 中, 这里就不再赘述了
我们之前一直使用的是 vmdl${sessionId}.tmp 这个目录, 发布成功之后, 将它更名为 ${PackageName}${Base64 随机码}
这里我们旨在分析应用安装的流程, 关于 dex 文件解析和优化后面有机会单独找一篇文章来分析, 这里就不展开讨论了
总结
安装好的应用程序目录如下
我们 Android 端, 整个应用程序的安装主要有两种方式:
- 通过 Session 发起(adb, 手动点击安装)
- 提前将安装包拷贝到 “data/app/vmdl${sessionId}.tmp/” 目录下
- 再通过 Session commit, 通知 PKMS 的 PKMS.installStage 进行安装
- 通过系统的软件商店自动发起
- 直接通过 PKMS 的 PKMS.installStage 进行安装
通过 Session 拷贝安装包
- 获取 SessionId 描述一个安装任务
- 分配 SessionId
- 创建临时目录 stageDir
- “data/app/vmdl{sessionId}.tmp”
- 创建服务进程的 PackageInstallerSession 对象
- 将安装包拷贝到 “data/app/vmdl${sessionId}.tmp/PackageInstaller” 位置下暂存
- 提交安装任务
- 调用 sealAndValidateLocked 对拷贝过来的安装包进行验证
- 将 “data/app/vmdl${sessionId}.tmp/PackageInstaller” 重命名为 “data/app/vmdl${sessionId}.tmp/base.apk”
- 解压 Native 库
- 调用 PKMS.installStage 发起这次应用安装的请求
- 调用 sealAndValidateLocked 对拷贝过来的安装包进行验证
PKMS 安装应用程序
- 连接远程应用安装服务 DefaultContainerService
- 获取 IMediaContainerService 的 Binder 代理对象
- 安装应用
- 拷贝安装包
- 若是已经通过 Session 拷贝过了, 跳过拷贝的过程
- 若未拷贝, 它会调用 InstallArgs 的 copyApk 执行 apk 的拷贝操作, 其主要步骤如下
- 创建拷贝的目录 “data/app/vmdl${sessionId}.tmp/”
- 实现一个获取文件描述符的工厂方法, 也是一个 Binder 对象
- 调用了 IMediaContainerService 的 copyPackage, 在远程服务中执行 apk 拷贝操作
- 拷贝到 “data/app/vmdl${sessionId}.tmp/base.apk” 中
- 解压 Native 库文件
- 安装应用程序
- 调用了 PackageParser.Package 解析 base.apk 安装包文件
- 即将 apk 中的信息发布到 PKMS
- 解析 apk 的 dex 文件
- 调用 InstallArgs.doRename 更换目录名称
- 更名前: “data/app/vmdl${sessionId}.tmp/”
- 更名后: “data/app/${PackageName}${Base64 随机码}/”
- 优化 dex 文件
- 调用了 PackageParser.Package 解析 base.apk 安装包文件
- 拷贝安装包
参考文献
- https://blog.csdn.net/c_z_w/article/details/79785108