在本篇博客中,我们分析一下Android中的APK是如何安装的,以及PKMS在这个过程中进行了哪些工作。
APK的安装方式有很多,我们先来看看如何用adb命令进行安装。
我们从adb install开始分析,该命令有多个参数,这里仅考虑最基本的adb install xxxx.apk。
一、adb命令
看看system/core/adb/commandline.cpp中的adb_commandline函数:
int adb_commandline(int argc, const char **argv) {
...........
else if (!strcmp(argv[0], "install")) {
if (argc < 2) return usage();
FeatureSet features;
std::string error;
if (!adb_get_feature_set(&features, &error)) {
fprintf(stderr, "error: %s\n", error.c_str());
return 1;
}
if (CanUseFeature(features, kFeatureCmd)) {
//支持FeatureCmd时调用install_app
return install_app(transport_type, serial, argc, argv);
}
//否则,利用install_app_legacy
return install_app_legacy(transport_type, serial, argc, argv);
}
...........
}
1、install_app_legacy
我看先看看传统的install_app_legacy:
static int install_app_legacy(TransportType transport, const char* serial, int argc, const char** argv) {
//待安装的APK目前还在源机器上,现在需要把APK的文件复制到手机里
//如果安装在手机内部存储,那么目的地址为DATA_DEST
//如果安装在SD卡上,则目的地址为SD_DEST
static const char *const DATA_DEST = "/data/local/tmp/%s";
static const char *const SD_DEST = "/sdcard/tmp/%s";
.........
//默认安装到手机内部
const char* where = DATA_DEST;
for (i = 1; i < argc; i++) {
//携带参数-s时,才安装到SD卡
if (!strcmp(argv[i], "-s")) {
where = SD_DEST;
}
}
//解析参数,判断adb命令中是否携带了有效的apk文件名
...........
//取出apk名
std::vector<const char*> apk_file = {argv[last_apk]};
//构造apk目的地址
std::string apk_dest = android::base::StringPrintf(
where, adb_basename(argv[last_apk]).c_str());
//do_sync_push将此APK文件传输到手机的目标路径,失败的话将跳转到clenaup_apk
if (!do_sync_push(apk_file, apk_dest.c_str())) goto cleanup_apk;
//执行pm_command
result = pm_command(transport, serial, argc, argv);
cleanup_apk:
//删除刚才传输的文件
//PKMS在安装过程中会将该APK复制一份到/data/app目录下,所有data/local/tmp目录下对应的文件可以删除
delete_file(transport, serial, apk_dest);
return result;
}
从代码来看,传统的安装方式就是将源机器中的APK文件拷贝到目的手机的tmp目录下,然后调用pm_command进行处理。
2、install_app
我们再看看支持FeatureCmd的机器,如何安装APK:
static int install_app(TransportType transport, const char* serial, int argc, const char** argv) {
//利用参数创建出本地文件的名称
const char* file = argv[argc - 1];
//解析参数,判断adb命令中是否携带了有效的apk文件名
.........
//adb_open中将创建出这个file对应的文件
int localFd = adb_open(file, O_RDONLY);
............
std::string cmd = "exec:cmd package";
//添加cmd参数
............
//连接源端,获取源APK文件的描述符
int remoteFd = adb_connect(cmd, &error);
............
//将remoteFd中的数据写入到localFd
copy_to_file(localFd, remoteFd);
//得到结果
read_status_line(remoteFd, buf, sizeof(buf));
adb_close(localFd);
adb_close(remoteFd);
..........
return 0;
}
从代码来看install_app就是将源机器的文件复制到了目的机器中,并没有进行额外的操作。猜想可能是支持特殊FeatureCmd的机器,PKMS能够监听到这个拷贝,然后触发后续的扫描工作。这个过程没有研究过对应代码,暂时不做深入分析。
对于传统的安装方式,我们需要继续往下看看pm_command。
二、pm_command
我们先看看pm_command函数:
static int pm_command(TransportType transport, const char* serial, int argc, const char** argv) {
std::string cmd = "pm";
//构造pm cmd
while (argc-- > 0) {
cmd += " " + escape_arg(*argv++);
}
//发送shell命令给adbd
return send_shell_command(transport, serial, cmd, false);
}
我们跟进下send_shell_command:
// Connects to the device "shell" service with |command| and prints the
// resulting output.
static int send_shell_command(TransportType transport_type, const char* serial,
const std::string& command,
bool disable_shell_protocol,
std::string* output=nullptr,
std::string* err=nullptr) {
...........
while (true) {
bool attempt_connection = true;
// Use shell protocol if it's supported and the caller doesn't explicitly disable it.
if (!disable_shell_protocol) {
.......
if (adb_get_feature_set(&features, &error)) {
//如果定义了feature,则替换shell protocol
use_shell_protocol = CanUseFeature(features, kFeatureShell2);
} else {
// Device was unreachable.
attempt_connection = false;
}
}
if (attempt_connection) {
std::string error;
//此时command中携带的就是以pm开头的命令
std::string service_string = ShellServiceString(use_shell_protocol, "", command);
//向shell服务发送命令
fd = adb_connect(service_string, &error);
if (fd >= 0) {
break;
}
}
............
}
//读取返回结果
int exit_code = read_and_dump(fd, use_shell_protocol, output, err);
if (adb_close(fd) < 0) {
..........
}
return int exit_code;
}
从上面的代码来看,pm_command就是向shell服务发送pm命令。
pm是一个可执行脚本,我们在终端上调用adb shell,然后执行pm,可以得到以下结果:
root:/ # pm
usage: pm list packages [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]
pm list permission-groups
pm list permissions [-g] [-f] [-d] [-u] [GROUP]
pm list instrumentation [-f] [TARGET-PACKAGE]
..........
pm脚本定义在frameworks/base/cmds/pm中:
base=/system
export CLASSPATH=$base/framework/pm.jar
exec app_process $base/bin com.android.commands.pm.Pm "$@"
在编译system.img时,会根据Android.mk将该脚本复制到system/bin目录下。
从脚本的内容来看,当调用pm时,将向app_process目录的main函数传入Pm对应的参数:
我们看看对应的定义于app_main.cpp的main函数(前面的博客分析过,这个其实也是zygote启动的函数):
//app_process的main函数
int main(int argc, char* const argv[]) {
........
//解析参数
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
zygote = true;
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
//此时我们有参数,进入该分支设置className
className.setTo(arg);
break;
} else {
--i;
break;
}
}
...........
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
//此时不再是启动zygote,而是启动className对应的类
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
.........
}
........
}
我们跟进AndroidRuntime.cpp的start函数:
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
..........
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
} else {
//反射调用main函数,从native层进入java世界
env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
.........
}
于是流程会进入到RuntimeInit的main函数:
public static final void main(String[] argv) {
........
//进行一些常规的初始化工作
commonInit();
/*
* Now that we're running in interpreted code, call back into native code
* to run the system.
*/
nativeFinishInit();
.........
}
native函数定义在framework/base/core/jni/AndroidRuntime.cpp中,对应的函数为:
static void com_android_internal_os_RuntimeInit_nativeFinishInit(JNIEnv* env, jobject clazz)
{
//gCurRuntime保存AndroidRuntime,实际上是AndroidRuntime的子类
gCurRuntime->onStarted();
}
App_main.cpp中定义的AppRuntime继承AndroidRuntime,实现了onStarted函数:
virtual void onStarted()
{
//binder通信相关的
sp<ProcessState> proc = ProcessState::self();
ALOGV("App process: starting thread pool.\n");
proc->startThreadPool();
AndroidRuntime* ar = AndroidRuntime::getRuntime();
//调用AndroidRuntime.cpp的callMain函数,参数与Pm.java相关
ar->callMain(mClassName, mClass, mArgs);
IPCThreadState::self()->stopProcess();
}
status_t AndroidRuntime::callMain(const String8& className, jclass clazz,
const Vector<String8>& args) {
..........
env = getJNIEnv();
..........
methodId = env->GetStaticMethodID(clazz, "main", "([Ljava/lang/String;)V");
..........
const size_t numArgs = args.size();
stringClass = env->FindClass("java/lang/String");
strArray = env->NewObjectArray(numArgs, stringClass, NULL);
for (size_t i = 0; i < numArgs; i++) {
jstring argStr = env->NewStringUTF(args[i].string());
env->SetObjectArrayElement(strArray, i, argStr);
}
...........
//最终调用了Pm.java的main函数
env->CallStaticVoidMethod(clazz, methodId, strArray);
return NO_ERROR;
}
这里自己初次看时,认为这里没有fork新的进程,那么APK安装运行在zygote进程中。
实际上这是一个错误的理解,说明自己的理解还不到位。
init创建zygote进程时,是fork出一个子进程,然后才调用app_main中的函数,此时整个zygote严格来讲只是一个native进程;当app_main函数最终通过AndroidRuntime等反射调用zygoteInit.java的main函数后,才演变成了Java层的zygote进程。
这里的情况是类似的,adb进程发送消息给Shell服务,Shell服务执行Pm脚本,由于exec函数并未创建出新的进程,因此调用app_main后整个代码仍然是运行在Shell服务对应的native进程中,同样通过反射后演变为Java层中的进程。
这里自己花了很多的笔墨来分析如何从执行脚本文件,到启动Java进程。
主要是弄懂这个机制后,我们实际上完全可以学习pm的写法,依葫芦画瓢写一个脚本文件,然后定义对应的Java文件。
通过脚本命令,来让Java层的进程提供服务。
最后,我们通过一个图来总结一下这个过程:
三、Pm中的流程
现在我们进入了Pm.java的main函数:
public static void main(String[] args) {
int exitCode = 1;
try {
//别被写法欺骗了,Pm并没有继承Runnable
exitCode = new Pm().run(args);
} catch (Exception e) {
.......
}
System.exit(exitCode);
}
//根据参数进行对应的操作,现在我们仅关注APK安装
public int run(String[] args) throws RemoteException {
...........
//利用Binder通信,得到PKMS服务端代理
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
//保存参数
mArgs = args;
String op = args[0];
mNextArg = 1;
............
//返回PKMS中保存的PackageInstallerService
mInstaller = mPm.getPackageInstaller();
........
if ("install".equals(op)) {
//安装APK将调用runInstall
return runInstall();
}
.......
}
我们跟进runInstall函数:
private int runInstall() throws RemoteException {
//根据参数创建InstallParams,其中包含了SessionParams,标志为MODE_FULL_INSTALL
final InstallParams params = makeInstallParams();
//1 创建Session
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
try {
//inPath对应于安装的APK文件
final String inPath = nextArg();
.......
//2 wirite session
if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
//3 commit session
if (doCommitSession(sessionId, false /*logSuccess*/)
!= PackageInstaller.STATUS_SUCCESS) {
return 1;
}
System.out.println("Success");
return 0;
} finally {
........
}
}
从上面的代码来看,runInstall主要进行了三件事,即创建session、对session进行写操作,最后提交session。
接下来,我们来看看每一步究竟在干些什么:
1、 create session
private int doCreateSession(SessionParams params, String installerPackageName, int userId)
throws RemoteException {
//通过ActivityManagerService得到"runInstallCreate"(作为Context对应的字符串)对应的uid
userId = translateUserId(userId, "runInstallCreate");
if (userId == UserHandle.USER_ALL) {
userId = UserHandle.USER_SYSTEM;
params.installFlags |= PackageManager.INSTALL_ALL_USERS;
}
//通过PackageInstallerService创建session
final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
return sessionId;
}
跟进一下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);
}
}
private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
throws IOException {
//安装权限检查
.......
//修改SessionParams的installFlags
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
} else {
.........
}
..........
// Defensively resize giant app icons
//调整app图标大小,这里应该是不同安装方式共用的代码
//通过adb安装apk时,应该还没有解析到app图标
if (params.appIcon != null) {
........
}
//根据SessionParams的installFlags进行一些操作
..........
} else {
// For now, installs to adopted media are treated as internal from
// an install flag point-of-view.
//adb安装应该进入这个分支(不添加参数指定安装在sd card时),为SessionParams设置InstallInternal Flag,后文会用到
params.setInstallFlagsInternal();
...........
}
final int sessionId;
final PackageInstallerSession session;
synchronized (mSessions) {
// Sanity check that installer isn't going crazy
//确保同一个uid没有提交过多的Session,MAX_ACTIVE_SESSIONS为1024
final int activeCount = getSessionCount(mSessions, callingUid);
if (activeCount >= MAX_ACTIVE_SESSIONS) {
throw new IllegalStateException(
"Too many active sessions for UID " + callingUid);
}
//同样确保同一个uid没有提交过多的Session,MAX_HISTORICAL_SESSIONS为1048576
final int historicalCount = getSessionCount(mHistoricalSessions, callingUid);
if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
throw new IllegalStateException(
"Too many historical sessions for UID " + callingUid);
}
........
//sessionId是个随机值
sessionId = allocateSessionIdLocked();
// We're staging to exactly one location
File stageDir = null;
String stageCid = null;
//根据installFlags,决定安装目录,前文已经提到,过默认将安装到internal目录下
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
final boolean isEphemeral =
(params.installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
//此处将会在临时性的data目录下创建出file,应该是作为copy的目的地址
stageDir = buildStageDir(params.volumeUuid, sessionId, isEphemeral);
} else {
stageCid = buildExternalStageCid(sessionId);
}
session = new PackageInstallerSession(mInternalCallback, mContext, mPm,
mInstallThread.getLooper(), sessionId, userId, installerPackageName, callingUid,
params, createdMillis, stageDir, stageCid, false, false);
mSessions.put(sessionId, session);
mSessions.put(sessionId, session);
}
//进行回调
mCallbacks.notifySessionCreated(session.sessionId, session.userId);
//在mSessionsFile中进行记录
writeSessionsAsync();
return sessionId;
}
从代码来看,上述代码的目的就是为APK安装做好准备工作,例如权限检查、目的临时文件的创建等, 最终创建出PackageInstallerSession对象。PackageInstallerSession可以看做是”安装APK”这个请求的封装,其中包含了处理这个请求需要的一些信息。
这种设计方式,大致可以按照命令模式来理解。
实际上PackageInstallerSession不仅是分装请求的对象,其自身还是个服务端:
public class PackageInstallerSession extends IPackageInstallerSession.Stub
2、write session
创建出PackageInstallerSession后,我们看看Pm.java中的doWriteSession函数:
private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName,
boolean logSuccess) throws RemoteException {
if ("-".equals(inPath)) {
inPath = null;
} else if (inPath != null) {
//此时file指向了待安装的APK文件(adb执行拷贝后的目的地址)
final File file = new File(inPath);
if (file.isFile()) {
sizeBytes = file.length();
}
}
......
//取出PackageInstallerSession中的SessionInfo
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
PackageInstaller.Session session = null;
InputStream in = null;
OutputStream out = null;
try {
//1 获取PackageInstallerSession的调用接口
session = new PackageInstaller.Session(
mInstaller.openSession(sessionId));
if (inPath != null) {
//定义输入端,待安装APK对应文件的源地址
in = new FileInputStream(inPath);
} else {
in = new SizedInputStream(System.in, sizeBytes);
}
//2 定义输出端,对应拷贝后的目的地址
out = session.openWrite(splitName, 0, sizeBytes);
int total = 0;
byte[] buffer = new byte[65536];
int c;
//进行文件的拷贝
while ((c = in.read(buffer)) != -1) {
total += c;
out.write(buffer, 0, c);
if (info.sizeBytes > 0) {
final float fraction = ((float) c / (float) info.sizeBytes);
//只是更新进度而已
session.addProgress(fraction);
}
}
session.fsync(out);
......
return PackageInstaller.STATUS_SUCCESS;
} catch (IOException e) {
........
} finally {
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(session);
}
}
从doWriteSession的代码来看,此处进行的主要工作就是通过Session将源端的数据拷贝到目的端。
其实从整个对象的命名和执行过程来看,这里整个是基于C/S架构的通信过程,Pm作为PackageInstallerService 的客户端,利用PackageInstallerSession来封装每一次完整的通信过程。
2.1 得到PackageInstallerSession的代理对象
我们看看上面代码调用的PackageInstaller.Session的构造函数:
//参数传入的是PackageInstallerService.openSession函数的返回结果,即实际PackageInstallerSession的代理端
public Session(IPackageInstallerSession session) {
mSession = session;
}
我们看看PackageInstallerService.openSession函数:
@Override
public IPackageInstallerSession openSession(int sessionId) {
try {
return openSessionInternal(sessionId);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException {
synchronized (mSessions) {
//根据sessionId得到之前创建的PackageInstallerSession
final PackageInstallerSession session = mSessions.get(sessionId);
if (session == null || !isCallingUidOwner(session)) {
throw new SecurityException("Caller has no access to session " + sessionId);
}
//调用其open函数
session.open();
//PacakgeInstallerSession转化为IPackageInstallerSession返回
return session;
}
}
//open函数就是准备好待拷贝的目录
public void open() throws IOException {
.......
//PackageInstallerService创建出PackageInstallerSession时,传入的prepared参数为false
if (!mPrepared) {
if (stageDir != null) {
prepareStageDir(stageDir);
} else if (stageCid != null) {
prepareExternalStageCid(stageCid, params.sizeBytes);
.....
} else {
//throw exception
......
}
........
}
}
}
2.2 得到客户端
PacakgeInstaller.Session的openWrite函数:
public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes,
long lengthBytes) throws IOException {
try {
//mSession是PacakgeInstallerSession,这里发生了Binder通信
final ParcelFileDescriptor clientSocket = mSession.openWrite(name,
offsetBytes, lengthBytes);
//引入了FileBridge对象,后文分析
return new FileBridge.FileBridgeOutputStream(clientSocket);
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
我们看看PacakgeInstallerSession的openWrite函数:
@Override
public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
try {
return openWriteInternal(name, offsetBytes, lengthBytes);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes)
throws IOException {
// Quick sanity check of state, and allocate a pipe for ourselves. We
// then do heavy disk allocation outside the lock, but this open pipe
// will block any attempted install transitions.
//FileBrige建立了客户端和服务端的管道
final FileBridge bridge;
synchronized (mLock) {
......
bridge = new FileBridge();
mBridges.add(bridge);
}
try {
// Use installer provided name for now; we always rename later
if (!FileUtils.isValidExtFilename(name)) {
throw new IllegalArgumentException("Invalid name: " + name);
}
//打开文件,定义权限
final File target = new File(resolveStageDir(), name);
// holding open FDs into containers.
final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
O_CREAT | O_WRONLY, 0644);
Os.chmod(target.getAbsolutePath(), 0644);
//定义文件内存格式及分配内存
// If caller specified a total length, allocate it for them. Free up
// cache space to grow, if needed.
if (lengthBytes > 0) {
final StructStat stat = Libcore.os.fstat(targetFd);
final long deltaBytes = lengthBytes - stat.st_size;
// Only need to free up space when writing to internal stage
if (stageDir != null && deltaBytes > 0) {
mPm.freeStorage(params.volumeUuid, deltaBytes);
}
Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
}
//定义起始偏移量
if (offsetBytes > 0) {
Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
}
bridge.setTargetFile(targetFd);
bridge.start();
//返回了bridge的client socket
return new ParcelFileDescriptor(bridge.getClientSocket());
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
}
2.2.1 FileBridge
为了更好的理解上述过程,我们需要看看FileBridge:
public class FileBridge extends Thread {
.......
private final FileDescriptor mServer = new FileDescriptor();
private final FileDescriptor mClient = new FileDescriptor();
.......
public FileBridge() {
try {
//构造函数建立的mServer和mClient之间的管道
Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mServer, mClient);
} catch (ErrnoException e) {
throw new RuntimeException("Failed to create bridge");
}
}
.......
public void setTargetFile(FileDescriptor target) {
mTarget = target;
}
public FileDescriptor getClientSocket() {
return mClient;
}
@Override
public void run() {
final byte[] temp = new byte[8192];
try {
//取出mServer中的数据,并进行处理
//注意mSever和mClient通道绑定,于是读出的数据是mClient写入的,向mServer写数据也会递交给mClient
while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) {
final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN);
if (cmd == CMD_WRITE) {
// Shuttle data into local file
int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN);
while (len > 0) {
int n = IoBridge.read(mServer, temp, 0, Math.min(temp.length, len));
.......
IoBridge.write(mTarget, temp, 0, n);
len -= n;
}
} else if (cmd == CMD_FSYNC) {
// Sync and echo back to confirm
Os.fsync(mTarget);
IoBridge.write(mServer, temp, 0, MSG_LENGTH);
} else if (cmd == CMD_CLOSE) {
// Close and echo back to confirm
Os.fsync(mTarget);
Os.close(mTarget);
mClosed = true;
IoBridge.write(mServer, temp, 0, MSG_LENGTH);
break;
}
}
} catch (ErrnoException | IOException e) {
........
} finally {
forceClose();
}
}
通过调用PackageInstallerSession的openWrite函数,Pm将得到与PackageInstallerSession通信的client端,同时PackageInstallerSession启动FileBridge准备接收数据。
在上文中进行文件拷贝时,最终就是利用FileBridge的管道来完成实际的工作。
3、 commit session
根据上面的代码,我们知道doWriteSession结束后,如果没有出现任何错误,那么APK源文件已经copy到目的地址了。
接下来我们看看doCommitSession进行的工作。
private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException {
PackageInstaller.Session session = null;
try {
session = new PackageInstaller.Session(
mInstaller.openSession(sessionId));
//receiver用于接收结果
final LocalIntentReceiver receiver = new LocalIntentReceiver();
//提交session
session.commit(receiver.getIntentSender());
.....
} finally {
IoUtils.closeQuietly(session);
}
}
PackageInstaller.Session中的commit函数,将通过Binder通信调用PackageInstallerSession的commit函数:
public void commit(@NonNull IntentSender statusReceiver) {
try {
mSession.commit(statusReceiver);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
我们跟进PackageInstallerSession的commit函数:
@Override
public void commit(IntentSender statusReceiver) {
.......
final boolean wasSealed;
synchronized (mLock) {
//初始时mSealed为false
wasSealed = mSealed;
if (!mSealed) {
// Verify that all writers are hands-off
//前面的doWriteSession传输数据的结尾,会关闭bridge
for (FileBridge bridge : mBridges) {
if (!bridge.isClosed()) {
throw new SecurityException("Files still open");
}
}
mSealed = true;
}
}
............
final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}
PackageInstallerSession被创建时,指定了mHandler对应callback:
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
synchronized (mLock) {
if (msg.obj != null) {
//其实就是存储Pm.java中的结果接收器
mRemoteObserver = (IPackageInstallObserver2) msg.obj;
}
try {
//因此,commit发送消息后,最终将触发commitLocked
commitLocked();
} catch (PackageManagerException e) {
.......
}
return true;
}
}
};
private void commitLocked() throws PackageManagerException {
.......
try {
//解析安装地址,即前文APK文件copy后的目的地址
resolveStageDir();
} catch (IOException e) {
........
}
// Verify that stage looks sane with respect to existing application.
// This currently only ensures packageName, versionCode, and certificate
// consistency.
//将利用PKMS检查APK文件是否满足要求,主要是保证各个文件是否具有一致性
validateInstallLocked();
//检查权限等
........
if (stageCid != null) {
// Figure out the final installed size and resize the container once
// and for all. Internally the parser handles straddling between two
// locations when inheriting.
final long finalSize = calculateInstalledSize();
resizeContainer(stageCid, finalSize);
}
// Inherit any packages and native libraries from existing install that
// haven't been overridden.
if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
//如果新的APK文件继承某些已安装的Pacakge,此处将copy需要的native库文件等
.........
}
......
// Unpack native libraries
//解压缩native库文件
extractNativeLibraries(mResolvedStageDir, params.abiOverride);
// Container is ready to go, let's seal it up!
if (stageCid != null) {
//针对安装在sdcard的操作,根据uid、gid调用fixSdPermissions
finalizeAndFixContainer(stageCid);
}
.........
//调用PKMS的installStage,进入安装的下一步操作
mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
installerPackageName, installerUid, user, mCertificates);
}
代码看到这里,我们终于明白了APK安装过程中,Pm.java进行的操作其实就是将adb拷贝的文件,拷贝到系统内或sdcard的目录中,然后进行初步的权限检查等工作,最后通知PKMS进入Install Stage。
整个代码引入了PackageInstallerSession,个人认为这里涉及了Binder通信、类似于Java网络通信的架构及命令模式,写的非常巧妙,有值得学习和模仿的地方。
我们同样用一张图来为这一部分做个总结:
大图链接
四、installStage
几经波折,APK的安装流程终于进入到了PKMS,我们看看installStage函数:
void installStage(String packageName, File stagedDir, String stagedCid,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
String installerPackageName, int installerUid, UserHandle user,
Certificate[][] certificates) {
............
//verificationInfo主要用于存储权限验证需要的信息
final VerificationInfo verificationInfo = new VerificationInfo(
sessionParams.originatingUri, sessionParams.referrerUri,
sessionParams.originatingUid, installerUid);
//origin中主要存储的APK文件的路径信息
final OriginInfo origin;
if (stagedDir != null) {
origin = OriginInfo.fromStagedFile(stagedDir);
} else {
origin = OriginInfo.fromStagedContainer(stagedCid);
}
final Message msg = mHandler.obtainMessage(INIT_COPY);
//准备安装所需的参数
final InstallParams params = new InstallParams(origin, null, observer,
sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
verificationInfo, user, sessionParams.abiOverride,
sessionParams.grantedRuntimePermissions, certificates);
.........
msg.obj = params;
.........
//发送INIT_COPY消息,驱动处理流程
mHandler.sendMessage(msg);
}
PKMS中实际的消息处理函数为doHandleMessage:
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
//这里取出的其实就是InstallParams,其继承HandlerParams
HandlerParams params = (HandlerParams) msg.obj;
//idx为当前等待处理处理的安装请求的个数
int idx = mPendingInstalls.size();
............
// If a bind was already initiated we dont really
// need to do anything. The pending install
// will be processed later on.
//初始时,mBound的值为false
if (!mBound) {
............
// If this is the only one pending we might
// have to bind to the service again.
//连接实际的安装服务,后文介绍
if (!connectToService()) {
..................
} else {
// Once we bind to the service, the first
// pending request will be processed.
//绑定服务成功后,将新的请求加入到mPendingIntalls中,等待处理
mPendingInstalls.add(idx, params);
}
} else {
//如果之前已经绑定过服务,同样将新的请求加入到mPendingIntalls中,等待处理
mPendingInstalls.add(idx, params);
// Already bound to the service. Just make
// sure we trigger off processing the first request.
if (idx == 0) {
//如果是第一个请求,则直接发送事件MCS_BOUND,触发处理流程
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
break;
}
}
}
上面代码的处理逻辑实际上是比较简单的,我们就看看connectToService的操作,来寻找一下实际进行安装工作的服务:
private boolean connectToService() {
........
//Component的包名为"com.android.defcontainer";类名为"com.android.defcontainer.DefaultContainerService"
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
if (mContext.bindServiceAsUser(service, mDefContainerConn,
Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mBound = true;
return true;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return false;
}
从代码可以看出实际进行安装工作的服务是DefaultContainerService,当绑定服务成功后:
class DefaultContainerConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
........
//获得与服务端通信的代理对象
IMediaContainerService imcs =
IMediaContainerService.Stub.asInterface(service);
//发送MCS_BOUND消息触发流程
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
}
.......
}
现在我们知道了当服务绑定成功后,也会发送MCS_BOUND消息触发接下来的流程。
MCS_BOUND对应的处理流程同样定义于doHandleMessage中:
void doHandleMessage(Message msg) {
.......
case MCS_BOUND: {
........
if (msg.obj != null) {
mContainerService = (IMediaContainerService) msg.obj;
.......
}
if (mContainerService == null) {
if (!mBound) {
// Something seriously wrong since we are not bound and we are not
// waiting for connection. Bail out.
............
} else {
Slog.w(TAG, "Waiting to connect to media container service");
}
} else if (mPendingInstalls.size() > 0) {
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
........
//调用参数的startCopy函数
if (params.startCopy()) {
........
// Delete pending install
if (mPendingInstalls.size() > 0) {
mPendingInstalls.remove(0);
}
if (mPendingInstalls.size() == 0) {
if (mBound) {
..........
removeMessages(MCS_UNBIND);
Message ubmsg = obtainMessage(MCS_UNBIND);
// Unbind after a little delay, to avoid
// continual thrashing.
sendMessageDelayed(ubmsg, 10000);
}
} else {
// There are more pending requests in queue.
// Just post MCS_BOUND message to trigger processing
// of next pending install.
......
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
.........
}
} else {
// Should never happen ideally.
Slog.w(TAG, "Empty queue");
}
break;
}
.......
}
这一段代码写的非常清晰,就是处理完一个安装请求后,接着处理下一个;如果队列为空,则等待一段时间后,发送MCS_UNBIND消息断开与安装服务的绑定。
顺着流程,我们现在看看HandlerParams的startCopy函数:
final boolean startCopy() {
boolean res;
try {
........
//处理安装失败,MAX_RETRIES = 4
if (++mRetries > MAX_RETRIES) {
.........
mHandler.sendEmptyMessage(MCS_GIVE_UP);
handleServiceError();
return false;
} else {
//先调用handleStartCopy进行实际的copy工作
handleStartCopy();
res = true;
}
} catch (RemoteException e) {
if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
mHandler.sendEmptyMessage(MCS_RECONNECT);
res = false;
}
//然后根据结果做相应处理
handleReturnCode();
return res;
}
如上图所示,从这段代码来看,PKMS将先后调用handleStartCopy和handleReturnCode来完成主要的工作。接下来,我们分别介绍一下这两个函数的工作流程。
五、handleStartCopy
HandlerParams为PKMS的内部抽象类,上面代码中的实际处理函数由其子类InstallParams来实现,我们看看与实际安装相关的handleStartCopy函数:
public void handleStartCopy() throws RemoteException {
int ret = PackageManager.INSTALL_SUCCEEDED;
// If we're already staged, we've firmly committed to an install location
//根据参数决定是安装在手机内还是sdcard中,设置对应标志位
if (origin.staged) {
if (origin.file != null) {
installFlags |= PackageManager.INSTALL_INTERNAL;
installFlags &= ~PackageManager.INSTALL_EXTERNAL;
} else if (origin.cid != null) {
installFlags |= PackageManager.INSTALL_EXTERNAL;
installFlags &= ~PackageManager.INSTALL_INTERNAL;
} else {
throw new IllegalStateException("Invalid stage location");
}
}
final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
final boolean ephemeral = (installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
PackageInfoLite pkgLite = null;
//检查APK的安装位置是否正确
if (onInt && onSd) {
// Check if both bits are set.
...........
//APK不能同时安装在内部存储空间和SD card上
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else if (onSd && ephemeral) {
.......
//APK不能短暂地安装在SD card上
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else {
//1、利用ContainerService获取PackageInfoLite,应该判断了能否进行安装
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
packageAbiOverride);
.........
/*
* If we have too little free space, try to free cache
* before giving up.
*/
//对于安装在SD card上的APK,当存储空间过小导致安装失败时
if (!origin.staged && pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
final StorageManager storage = StorageManager.from(mContext);
//利用StroageManager得到设备内部存储空间允许的最小余量
final long lowThreshold = storage.getStorageLowBytes(
Environment.getDataDirectory());
//利用ContainerService得到安装APK的大小
final long sizeBytes = mContainerService.calculateInstalledSize(
origin.resolvedPath, isForwardLocked(), packageAbiOverride);
try {
//利用Installer释放缓存,试图将缓存释放到大于等于(最小余量与APK大小之和)
mInstaller.freeCache(null, sizeBytes + lowThreshold);
//再次试图得到PackageInfoLite,判断是否满足安装条件
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath,
installFlags, packageAbiOverride);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to free cache", e);
}
/*
* The cache free must have deleted the file we
* downloaded to install.
*
* TODO: fix the "freeCache" call to not delete
* the file we care about.
*/
if (pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
//试图释放cache还是无法安装,只能设置标志位为失败
pkgLite.recommendedInstallLocation
= PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
}
}
if (ret == PackageManager.INSTALL_SUCCEEDED) {
//recommendedInstallLocation中记录了安装的路径信息,即APK保存在终端内部还是Sd card中,此外也可以记录安装失败的信息
int loc = pkgLite.recommendedInstallLocation;
if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
ret = PackageManager.INSTALL_FAILED_INVALID_APK;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
ret = PackageManager.INSTALL_FAILED_INVALID_URI;
} else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
} else {
// Override with defaults if needed.
//2、installLocationPolicy主要判断终端上是否有已经安装过该APK,同一个APK一般只能用新版的替换旧版
loc = installLocationPolicy(pkgLite);
//根据loc调整installFlag
......
}
}
//3、createInstallArgs用于创建一个安装参数对象
final InstallArgs args = createInstallArgs(this);
mArgs = args;
if (ret == PackageManager.INSTALL_SUCCEEDED) {
// Apps installed for "all" users use the device owner to verify the app
UserHandle verifierUser = getUser();
if (verifierUser == UserHandle.ALL) {
verifierUser = UserHandle.SYSTEM;
}
/*
* Determine if we have any installed package verifiers. If we
* do, then we'll defer to them to verify the packages.
*/
final int requiredUid = mRequiredVerifierPackage == null ? -1
: getPackageUid(mRequiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
verifierUser.getIdentifier());
if (!origin.existing && requiredUid != -1
&& isVerificationEnabled(verifierUser.getIdentifier(), installFlags)) {
//如果存在Package检查者,同时满足启动检查的条件,那么将利用Pacakge检查者来检查安装包
//其实就是构造一个intent,action为"android.intent.action.PACKAGE_NEEDS_VERIFICATION"
//在intent中添加需要多信息后,发送给接收者处理
.........
} else {
//4、调用安装参数对象的copyApk函数
ret = args.copyApk(mContainerService, true);
}
}
mRet = ret;
}
handleStartCopy函数整体来看还是比较复杂的,内容比较多,我们需要分4步介绍其中主要的内容。
1、getMinimalPackageInfo
getMinimalPackageInfo实际定义于DefaultContainerService中,其代码如下:
@Override
public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
String abiOverride) {
final Context context = DefaultContainerService.this;
final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
PackageInfoLite ret = new PackageInfoLite();
........
final File packageFile = new File(packagePath);
final PackageParser.PackageLite pkg;
final long sizeBytes;
try {
//如同PKMS的构造函数,利用PackageParser来解析APK文件,得到PackageInfoLite
pkg = PackageParser.parsePackageLite(packageFile, 0);
sizeBytes = PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
} catch (PackageParserException | IOException e) {
.................
}
ret.packageName = pkg.packageName;
ret.splitNames = pkg.splitNames;
ret.versionCode = pkg.versionCode;
ret.baseRevisionCode = pkg.baseRevisionCode;
ret.splitRevisionCodes = pkg.splitRevisionCodes;
ret.installLocation = pkg.installLocation;
ret.verifiers = pkg.verifiers;
//利用resolveInstallLocation来得到一个合理的安装位置
ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
pkg.packageName, pkg.installLocation, sizeBytes, flags);
ret.multiArch = pkg.multiArch;
return ret;
}
从代码可以看出,getMinimalPackageInfo的代码比较简单,其实就是利用PackageParser解析出APK对应Pacakge的基本信息,然后利用resolveInstallLocation得到适合APK安装的路径。
1.1 resolveInstallLocation
我们看看resolveInstallLocation函数:
public static int resolveInstallLocation(Context context, String packageName,
int installLocation, long sizeBytes, int installFlags) {
ApplicationInfo existingInfo = null;
try {
//如果之前该APK之前安装过,那么将获取到之前记录的ApplicationInfo信息
existingInfo = context.getPackageManager().getApplicationInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException ignored) {
.........
}
final int prefer;
final boolean checkBoth;
boolean ephemeral = false;
//以下其实就是根据installFlags决定安装倾向的路径prefer
if ((installFlags & PackageManager.INSTALL_EPHEMERAL) != 0) {
prefer = RECOMMEND_INSTALL_INTERNAL;
ephemeral = true;
checkBoth = false;
} else if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
prefer = RECOMMEND_INSTALL_INTERNAL;
checkBoth = false;
} else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
prefer = RECOMMEND_INSTALL_EXTERNAL;
checkBoth = false;
} else if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
prefer = RECOMMEND_INSTALL_INTERNAL;
checkBoth = false;
} else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
prefer = RECOMMEND_INSTALL_EXTERNAL;
checkBoth = true;
} else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
//一般APK安装的路径就是auto
// When app is already installed, prefer same medium
if (existingInfo != null) {
// TODO: distinguish if this is external ASEC
if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
prefer = RECOMMEND_INSTALL_EXTERNAL;
} else {
prefer = RECOMMEND_INSTALL_INTERNAL;
}
} else {
//可以看到一般默认条件下是安装在手机内部的
prefer = RECOMMEND_INSTALL_INTERNAL;
}
//auto时,checkBoth为true
checkBoth = true;
} else {
prefer = RECOMMEND_INSTALL_INTERNAL;
checkBoth = false;
}
boolean fitsOnInternal = false;
if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
//fitsOnInternal和下面的fitsOnExternal应该就是用于检查对应路径是否有足够的空间来安装APK的
fitsOnInternal = fitsOnInternal(context, sizeBytes);
}
boolean fitsOnExternal = false;
if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
fitsOnExternal = fitsOnExternal(context, sizeBytes);
}
if (prefer == RECOMMEND_INSTALL_INTERNAL) {
// The ephemeral case will either fit and return EPHEMERAL, or will not fit
// and will fall through to return INSUFFICIENT_STORAGE
if (fitsOnInternal) {
return (ephemeral)
? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
: PackageHelper.RECOMMEND_INSTALL_INTERNAL;
}
} else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
if (fitsOnExternal) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
}
//个人感觉这里就是容错用的吧,在前面的代码中正常情况下,prefer已经被取值为RECOMMEND_INSTALL_INTERNAL或RECOMMEND_INSTALL_EXTERNAL了
if (checkBoth) {
if (fitsOnInternal) {
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
} else if (fitsOnExternal) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
}
//没有足够空间,返回对应消息
return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
上述代码相对比较简单,我们主要以fitsOnInternal为例,看看如何判断存储空间是否足够:
public static boolean fitsOnInternal(Context context, long sizeBytes) {
final StorageManager storage = context.getSystemService(StorageManager.class);
final File target = Environment.getDataDirectory();
//APK安装所需的空间,小于等于可用空间时,就返回true
return (sizeBytes <= storage.getStorageBytesUntilLow(target));
}
//StorgeManager中的函数,其实就是用总的可用空间,减去已经使用的空间,得到剩余空间
public long getStorageBytesUntilLow(File path) {
return path.getUsableSpace() - getStorageFullBytes(path);
}
2、installLocationPolicy
当成功得到APK对应的PacakgeInfoLite,并判断安装路径有足够的剩余空间时,将调用installLocationPolicy函数:
private int installLocationPolicy(PackageInfoLite pkgLite) {
String packageName = pkgLite.packageName;
int installLocation = pkgLite.installLocation;
boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
synchronized (mPackages) {
// Currently installed package which the new package is attempting to replace or
// null if no such package is installed.
//判断终端上之前是否安装过同样的APK
PackageParser.Package installedPkg = mPackages.get(packageName);
// Package which currently owns the data which the new package will own if installed.
// If an app is unstalled while keeping data (e.g., adb uninstall -k), installedPkg
// will be null whereas dataOwnerPkg will contain information about the package
// which was uninstalled while keeping its data.
//当一个APK卸载时,那么installedPkg为null
PackageParser.Package dataOwnerPkg = installedPkg;
if (dataOwnerPkg == null) {
//但是如果APK卸载时,保留了数据,那么PKMS将取出对应的PacakgeSettings
PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null) {
//从PacakgeSettings中取出Pacakge
dataOwnerPkg = ps.pkg;
}
}
//存在旧APK对应的信息时
if (dataOwnerPkg != null) {
final boolean downgradeRequested =
(installFlags & PackageManager.INSTALL_ALLOW_DOWNGRADE) != 0;
final boolean packageDebuggable =
(dataOwnerPkg.applicationInfo.flags
& ApplicationInfo.FLAG_DEBUGGABLE) != 0;
//当安装一个重复的APK时,新装的APK版本一般要比旧APK的版本高
//除非满足以下要求,例如显示要求装入旧版本、在debug模式下等
final boolean downgradePermitted =
(downgradeRequested) && ((Build.IS_DEBUGGABLE) || (packageDebuggable));
//默认模式下,即仅能安装高版本时
if (!downgradePermitted) {
try {
//比较两个Package信息中的VersionCode
checkDowngrade(dataOwnerPkg, pkgLite);
} catch (PackageManagerException e) {
Slog.w(TAG, "Downgrade detected: " + e.getMessage());
return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE;
}
}
}
//旧有的APK还存在终端上时
if (installedPkg != null) {
//installFlags中必须携带REPLACE_EXISTING,否则将报错
if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
// Check for updated system application.
if ((installedPkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
//系统APK不应该存在SD card上
if (onSd) {
Slog.w(TAG, "Cannot install update to system app on sdcard");
return PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION;
}
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
} else {
// If current upgrade specifies particular preference
if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
// Application explicitly specified internal.
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
} else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
// App explictly prefers external. Let policy decide
} else {
// Prefer previous location
//未指定安装路径时,与之前的安装路径保持一致
if (isExternal(installedPkg)) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
}
}
} else {
// Invalid install. Return error code
return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
}
}
}
// All the special cases have been taken care of.
// Return result based on recommended install location.
if (onSd) {
return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
}
return pkgLite.recommendedInstallLocation;
}
3、createInstallArgs
处理完潜在的重复安装APK的风险后,PKMS调用createInstallArgs生成安装参数对象:
private InstallArgs createInstallArgs(InstallParams params) {
if (params.move != null) {
return new MoveInstallArgs(params);
} else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
return new AsecInstallArgs(params);
} else {
return new FileInstallArgs(params);
}
}
这部分的代码较为简单,就是利用参数决定创建哪个InstallArgs的子类,我们主要关注在终端安装APK时,将要使用的FileInstallArgs,后文介绍其功能。
4、copyApk
如果不需要进行安装包检查,对于安装在终端内部的APK而言,将调用FileInstallArgs的copyAPK函数:
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyApk");
try {
//doCopyApk负责进行实际的工作
return doCopyApk(imcs, temp);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
//与之前版本不同的时,Android7.0中,已经通过Session进行了文件拷贝
//当进入到前文所述的PKMS的installStage时,OriginInfo.fromStagedFile或OriginInfo.fromStagedContainer均会将staged变量置为true
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;
}
}
//当使用其它方式安装APK时,将进入到以下流程
try {
//当需要临时安装时,创建一个临时安装目录
final boolean isEphemeral = (installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
final File tempDir =
mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
codeFile = tempDir;
resourceFile = tempDir;
} catch (IOException e) {
Slog.w(TAG, "Failed to create copy file: " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
//定义回调接口
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());
}
}
};
//调用DefaultContainerService进行copyPackage的操作,传入了回调的接口
int ret = PackageManager.INSTALL_SUCCEEDED;
ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
if (ret != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Failed to copy package");
return ret;
}
//拷贝APK对应的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;
}
对于非adb安装的APK,我们看看DefaultContainerService对应的copyPackage是如何进行处理的:
@Override
public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
if (packagePath == null || target == null) {
return PackageManager.INSTALL_FAILED_INVALID_URI;
}
PackageLite pkg = null;
try {
final File packageFile = new File(packagePath);
//解析出PackageFile
pkg = PackageParser.parsePackageLite(packageFile, 0);
//利用copyPackageInner进行实际的处理
return copyPackageInner(pkg, target);
} catch (PackageParserException | IOException | RemoteException e) {
Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
}
private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
throws IOException, RemoteException {
//copyFile负责实际的拷贝
copyFile(pkg.baseCodePath, target, "base.apk");
if (!ArrayUtils.isEmpty(pkg.splitNames)) {
for (int i = 0; i < pkg.splitNames.length; i++) {
//对于多APK文件的情况,需依次拷贝所有的子文件
copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk");
}
}
return PackageManager.INSTALL_SUCCEEDED;
}
private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName)
throws IOException, RemoteException {
Slog.d(TAG, "Copying " + sourcePath + " to " + targetName);
InputStream in = null;
OutputStream out = null;
try {
//源文件作为输入
in = new FileInputStream(sourcePath);
//目的文件作为输出,这里进行了多层封装
//上文提到过,回调接口target调用open函数后,将创建并打开目的端文件,然后赋予相应的写权限
//ParcelFileDescriptor.AutoCloseOutputStream利用文件描述符构造出一个可自动关闭的输出流
out = new ParcelFileDescriptor.AutoCloseOutputStream(
target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE));
//进行实际的数据拷贝
Streams.copy(in, out);
} finally {
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(in);
}
}
至此整个handleStartCopy流程介绍完毕,可以看出当利用adb安装时,handleStartCopy实际上并没有完成什么实际的操作;对于其它方式安装APK时,handleStartCopy才会进行真正的数据拷贝工作。
整个过程的大致流程如下:
六、handleReturnCode
copy过程结束后,将调用InstallParams的handleReturnCode:
void handleReturnCode() {
if (mArgs != null) {
processPendingInstall(mArgs, mRet);
}
}
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
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) {
//1、安装在终端上的APK,将调用FileInstallArgs的doPreInstall进行处理
args.doPreInstall(res.returnCode);
synchronized (mInstallLock) {
//2、调用installPackageTracedLI进行安装
installPackageTracedLI(args, res);
}
//3、调用FileInstallArgs的doPostInstall
args.doPostInstall(res.returnCode, res.uid);
}
// A restore should be performed at this point if (a) the install
// succeeded, (b) the operation is not an update, and (c) the new
// package has not opted out of backup participation.
//判断是否需要备份恢复
final boolean update = res.removedInfo != null
&& res.removedInfo.removedPackage != null;
final int flags = (res.pkg == null) ? 0 : res.pkg.applicationInfo.flags;
boolean doRestore = !update
&& ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0);
// Set up the post-install work request bookkeeping. This will be used
// and cleaned up by the post-install event handling regardless of whether
// there's a restore pass performed. Token values are >= 1.
int token;
if (mNextInstallToken < 0) mNextInstallToken = 1;
token = mNextInstallToken++;
PostInstallData data = new PostInstallData(args, res);
mRunningInstalls.put(token, data);
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
//调用BackupManager的接口进行恢复工作
.......
}
if (!doRestore) {
.......
//4、生成一个POST_INSTALL消息,触发后续操作
Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
mHandler.sendMessage(msg);
}
}
});
}
从上面的代码可以看出,handleReturnCode主要做了4件事:
*调用InstallArgs的doPreInstall函数,对于安装在终端内部的APK而言,将调用FileInstallArgs的doPreInstall函数;
*调用PKMS的installPackageTracedLI函数进行APK安装;
*调用InstallArgs的doPostInstall函数;
*利用结果构造PostInstallData,然后发送POST_INSTALL消息触发后续处理流程
现在我们分别介绍这几部分工作:
1、doPreInstall
int doPreInstall(int status) {
if (status != PackageManager.INSTALL_SUCCEEDED) {
cleanUp();
}
return status;
}
private boolean cleanUp() {
if (codeFile == null || !codeFile.exists()) {
return false;
}
removeCodePathLI(codeFile);
if (resourceFile != null && !FileUtils.contains(codeFile, resourceFile)) {
resourceFile.delete();
}
return true;
}
从代码来看,正常流程下doPreInstall并不会进行实际的工作,只是当handleStartCopy出现问题时,doPreInstall将清理拷贝的文件。
2、installPackageTracedLI
private void installPackageTracedLI(InstallArgs args, PackageInstalledInfo res) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackage");
installPackageLI(args, res);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
//定义一些变量
.........
// Result object to be returned
res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
//定义parseFlags
.......
PackageParser pp = new PackageParser();
.......
final PackageParser.Package pkg;
try {
//解析APK文件,形成Package对象
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
} catch (PackageParserException e) {
res.setError("Failed parse during installPackageLI", e);
return;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
/ If we are installing a clustered package add results for the children
if (pkg.childPackages != null) {
//在需要的情况下,解析Child Package信息
........
}
try {
// either use what we've been given or parse directly from the APK
if (args.certificates != null) {
try {
//如果参数中定义了权限信息,就用参数中的权限信息配置Package对象
PackageParser.populateCertificates(pkg, args.certificates);
} catch (PackageParserException e) {
// there was something wrong with the certificates we were given;
// try to pull them from the APK
PackageParser.collectCertificates(pkg, parseFlags);
}
} else {
//否则,就从AndroidManifest.xml文件中解析出权限信息
PackageParser.collectCertificates(pkg, parseFlags);
}
} catch (PackageParserException e) {
res.setError("Failed collect during installPackageLI", e);
return;
}
//当安装重复的APK时,根据权限、签名信息、版本等条件,判断能否进一步操作
.............
........
//根据Package中的信息,修改拷贝文件时,临时赋予的名称
//此处将利用FileInstallArgs的doRename
if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
return;
}
.........
try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
"installPackageLI")) {
if (replace) {
//用新的package信息替换旧的
replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
installerPackageName, res);
} else {
//将新的pacakge信息加入到PKMS中
installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
args.user, installerPackageName, volumeUuid, res);
}
}
..........
}
从代码来看,installPackageTracedLI的主要工作就是解析APK文件,形成对应的Package对象;生成对应的权限信息后,根据Package中的信息,更改存储路径对应目录的名称。
3、doPostInstall
int doPostInstall(int status, int uid) {
if (status != PackageManager.INSTALL_SUCCEEDED) {
cleanUp();
}
return status;
}
可以看出,FileInstallArgs中定义的doPostInstall函数和doPreInstall函数完全一样,正常流程下不需要进行任何操作;当之前的处理流程出现问题时,利用cleanUp清楚创建的文件和资源。
4、处理POST_INSTALL消息
在PackageHandler的doHandleMessage中处理POST_INSTALL消息:
.....
case POST_INSTALL: {
............
PostInstallData data = mRunningInstalls.get(msg.arg1);
final boolean didRestore = (msg.arg2 != 0);
mRunningInstalls.delete(msg.arg1);
if (data != null) {
............
// Handle the parent package
handlePackagePostInstall(parentRes, grantPermissions, killApp,
grantedPermissions, didRestore, args.installerPackageName,
args.observer);
// Handle the child packages
final int childCount = (parentRes.addedChildPackages != null)
? parentRes.addedChildPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
//同样利用handlePackagePostInstall处理child Package
........
}
........
} else {
.........
}
}
break;
......
我们跟进一下handlePackagePostInstall函数:
private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,
boolean killApp, String[] grantedPermissions,
boolean launchedForRestore, String installerPackage,
IPackageInstallObserver2 installObserver) {
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
// Send the removed broadcasts
if (res.removedInfo != null) {
res.removedInfo.sendPackageRemovedBroadcasts(killApp);
}
//调用grantRequestedRuntimePermissions等,赋予Package权限
...........
//发送ACTION_PACKAGE_ADDED等广播消息
...........
...........
}
// If someone is watching installs - notify them
//将安装的结果通知给Pm.java中观察者
if (installObserver != null) {
try {
Bundle extras = extrasForInstallResult(res);
installObserver.onPackageInstalled(res.name, res.returnCode,
res.returnMsg, extras);
} catch (RemoteException e) {
Slog.i(TAG, "Observer no longer exists.");
}
}
}
从上面的代码来看,处理POST_INSTALL的主要工作其实还是通过广播、回调接口通知系统中的其它组件,有新的Pacakge安装或发生了改变。
最后整理一下handleReturnCode的流程,如下所示:
七、总结
从上面的代码来看,整个APK的安装过程极其琐碎复杂,但核心思想还是比较简单的:就是将待安装的APK文件拷贝到手机的指定位置,然后利用PackageParser来解析出对应的Package对象,最终将Package对象加入到PKMS中。
整体流程的主干大体如下图所示:
大图链接