在本篇博客中,我们分析一下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_appreturn install_app(transport_type, serial, argc, argv);}//否则,利用install_app_legacyreturn 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_DESTstatic 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_apkif (!do_sync_push(apk_file, apk_dest.c_str())) goto cleanup_apk;//执行pm_commandresult = 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中的数据写入到localFdcopy_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 cmdwhile (argc-- > 0) {cmd += " " + escape_arg(*argv++);}//发送shell命令给adbdreturn 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 protocoluse_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-groupspm 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) {//此时我们有参数,进入该分支设置classNameclassName.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并没有继承RunnableexitCode = 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中保存的PackageInstallerServicemInstaller = mPm.getPackageInstaller();........if ("install".equals(op)) {//安装APK将调用runInstallreturn runInstall();}.......
}

我们跟进runInstall函数:

private int runInstall() throws RemoteException {//根据参数创建InstallParams,其中包含了SessionParams,标志为MODE_FULL_INSTALLfinal InstallParams params = makeInstallParams();//1 创建Sessionfinal int sessionId = doCreateSession(params.sessionParams,params.installerPackageName, params.userId);try {//inPath对应于安装的APK文件final String inPath = nextArg();.......//2 wirite sessionif (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {return 1;}//3 commit sessionif (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对应的字符串)对应的uiduserId = translateUserId(userId, "runInstallCreate");if (userId == UserHandle.USER_ALL) {userId = UserHandle.USER_SYSTEM;params.installFlags |= PackageManager.INSTALL_ALL_USERS;}//通过PackageInstallerService创建sessionfinal 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的installFlagsif ((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为1024final 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为1048576final 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 locationFile 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中的SessionInfofinal 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得到之前创建的PackageInstallerSessionfinal 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参数为falseif (!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 laterif (!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 stageif (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 socketreturn 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;}@Overridepublic void run() {final byte[] temp = new byte[8192];try {//取出mServer中的数据,并进行处理//注意mSever和mClient通道绑定,于是读出的数据是mClient写入的,向mServer写数据也会递交给mClientwhile (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 fileint 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 confirmOs.fsync(mTarget);IoBridge.write(mServer, temp, 0, MSG_LENGTH);} else if (cmd == CMD_CLOSE) {// Close and echo back to confirmOs.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();//提交sessionsession.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为falsewasSealed = mSealed;if (!mSealed) {// Verify that all writers are hands-off//前面的doWriteSession传输数据的结尾,会关闭bridgefor (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() {@Overridepublic boolean handleMessage(Message msg) {synchronized (mLock) {if (msg.obj != null) {//其实就是存储Pm.java中的结果接收器mRemoteObserver = (IPackageInstallObserver2) msg.obj;}try {//因此,commit发送消息后,最终将触发commitLockedcommitLocked();} 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调用fixSdPermissionsfinalizeAndFixContainer(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,其继承HandlerParamsHandlerParams 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的值为falseif (!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 installif (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 = 4if (++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 appUserHandle 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文件,得到PackageInfoLitepkg = 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决定安装倾向的路径preferif ((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 mediumif (existingInfo != null) {// TODO: distinguish if this is external ASECif ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {prefer = RECOMMEND_INSTALL_EXTERNAL;} else {prefer = RECOMMEND_INSTALL_INTERNAL;}} else {//可以看到一般默认条件下是安装在手机内部的prefer = RECOMMEND_INSTALL_INTERNAL;}//auto时,checkBoth为truecheckBoth = 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_STORAGEif (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.//判断终端上之前是否安装过同样的APKPackageParser.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为nullPackageParser.Package dataOwnerPkg = installedPkg;if (dataOwnerPkg  == null) {//但是如果APK卸载时,保留了数据,那么PKMS将取出对应的PacakgeSettingsPackageSetting ps = mSettings.mPackages.get(packageName);if (ps != null) {//从PacakgeSettings中取出PacakgedataOwnerPkg = 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信息中的VersionCodecheckDowngrade(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 preferenceif (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 codereturn 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变量置为trueif (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() {@Overridepublic 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);//解析出PackageFilepkg = 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 returnedPackageInstalledInfo 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的doPostInstallargs.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 returnedres.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 childrenif (pkg.childPackages != null) {//在需要的情况下,解析Child Package信息........}try {// either use what we've been given or parse directly from the APKif (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 APKPackageParser.collectCertificates(pkg, parseFlags);}} else {//否则,就从AndroidManifest.xml文件中解析出权限信息PackageParser.collectCertificates(pkg, parseFlags);}} catch (PackageParserException e) {res.setError("Failed collect during installPackageLI", e);return;}//当安装重复的APK时,根据权限、签名信息、版本等条件,判断能否进一步操作.....................//根据Package中的信息,修改拷贝文件时,临时赋予的名称//此处将利用FileInstallArgs的doRenameif (!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 packagehandlePackagePostInstall(parentRes, grantPermissions, killApp,grantedPermissions, didRestore, args.installerPackageName,args.observer);// Handle the child packagesfinal 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 broadcastsif (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中。
整体流程的主干大体如下图所示:

大图链接

Android7.0 PackageManagerService (3) APK安装相关推荐

  1. Android7.0 PackageManagerService (2) PKMS构造函数的主要工作

    从本篇博客开始,我们开始分析PKMS的构造函数,看看PKMS到底是如何解析和管理手机中APK的信息的. 由于PKMS的构造函数较长,我们会分段进行研究. public PackageManagerSe ...

  2. android 7.0不能自动安装失败,解决Android7.0更新后无法安装的问题

    最近在我们的应用中加入更新功能,按照往常一样加入代码 if (!apkfile.exists()) { Toast.makeText(mContext, "下载的安装包不存在", ...

  3. [RK3288][Android6.0] 调试笔记 --- apk安装添加黑名单

    Platform: ROCKCHIP OS: Android 6.0 Kernel: 3.10.92 出于定制考虑,需要把系统一些不必要的apk移除,但是一个个找又太麻烦, 在不考虑存储空间的情况下可 ...

  4. Android开发之下载Apk安装的方法兼容Android7.0和8.0及以上

    具体查看代码: 首先在清单文件配置三个权限读写权限和请求安装权限(兼容Android8.0手机)如下: <!--安装apk权限--><uses-permission android: ...

  5. Android 在线下载更新App 下载完成安装APK(兼容Android7.0)

    先上图: 首先对android7.0的打开文件方式进行适配 使用FileProvider 第一步: 在AndroidManifest.xml清单文件中注册provider,因为provider也是An ...

  6. Android7.0下载Apk自动安装

    Android7.0下载Apk自动安装 1. 整体需求 下载APK文件 使用DownloadManager来下载 在应用界面中展示下载进度 安装下载后的APK文件 root模式: 可以自动安装,不需要 ...

  7. 华为android7.0 root,android7.0更新安装apk的方法

    StrictMode模式 从android7.0开始强制启用StrictMode"严苛模式".StrictMode是在android2.3引进的类.当时它的作用是作为一个开发工具用 ...

  8. 下载安装APK(兼容Android7.0)

    我们使用手机的时候经常会看到应用程序提示升级,大部分应用内部都需要实现升级提醒和应用程序文件(APK文件)下载. 一般写法都差不多,比如在启动app的时候,通过api接口获得服务器最新的版本号,然后和 ...

  9. Android7.0+安装apk文件之后不弹出安装完成的界面解决办法

    在Android7.0+手上,版本升级完成,发现手机安装完成,不启动安装完成页面,而是直接关闭了,小编也是一头雾水.琢磨了很久,下面小编把解决办法show出来. 第一步:在资源文件下面新建 xml文件 ...

最新文章

  1. python可以自学吗-python可以自学吗
  2. 文献记录(part94)--Clustering and outlier detection using isoperimetric number of trees
  3. python做一个小游戏_利用python做个小游戏
  4. c 内存加载易语言dll,[求助]MemoryLoadLibrary 加载MFC 易语言 DLL 失败
  5. SQL数据库语言基础之SqlServer条件查询、排序数据表、like模糊查询【大总结】
  6. 2021-2025年中国一次性透析装置行业市场供需与战略研究报告
  7. java string对象,java中String对象
  8. Python urllib HTTP头注入漏洞
  9. R语言解决安装rgl包问题:ERROR: compilation failed for package ‘rgl‘
  10. 上海数据分析师面试经历
  11. Win 10 深度隐藏文件夹命令
  12. 测试内存的频率稳定性软件,超频心得:3000MHz通过1000%稳定性测试_内存硬盘评测-中关村在线...
  13. usb接口芯片ft245bm的功能及其应用
  14. 精读《对 Markdown 的思考》
  15. 当iPod Touch来临,你是否也开始心动?
  16. kmplayer安卓版外部编码器_求KMplayer编码器的设置教程
  17. halcon第一讲:基本操作
  18. 华为云智慧物流解决方案
  19. 5G一周热闻:华为夺联通5G大单,首张5G电话卡发放
  20. 化工行业多计量单位管理

热门文章

  1. 易语言和python混合编程_关于易语言与Python的一点想法
  2. 一个APP想法,如何做到从想法到上线的全过程操作
  3. 破解某Wifi APP,无需Root也可查看密码
  4. IOS开发之——更换Storyboard
  5. 微信小程序--基础内容(详解)(一)
  6. 优质github项目集合-珍藏版
  7. 设置夜神模拟器自动连接功能
  8. DateAdd 函数
  9. STM32 输入捕获测量脉冲周期/频率
  10. 古罗马数字的基本知识