Android7.0 PackageManagerService (3) APK安装
来源:互联网 发布:淘宝卖内衣赚钱吗 编辑:程序博客网 时间:2024/05/16 09:27
在本篇博客中,我们分析一下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:/ # pmusage: 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=/systemexport CLASSPATH=$base/framework/pm.jarexec 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函数:
@Overridepublic 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函数:
@Overridepublic 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函数:
@Overridepublic 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函数:
@Overridepublic 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中,其代码如下:
@Overridepublic 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是如何进行处理的:
@Overridepublic 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中。
整体流程的主干大体如下图所示:
大图链接
- Android7.0 PackageManagerService (3) APK安装
- Android7.0 PackageManagerService APK安装
- Android7.0 apk安装
- PackageManagerService安装APK流程
- Android7.0 PackageManagerService (5) installd
- 下载安装APK(兼容Android7.0)
- Android7.0下载Apk自动安装
- Android7.0下载Apk自动安装
- 下载安装APK(兼容Android7.0)
- 下载安装APK(兼容Android7.0)
- 下载安装APK(兼容Android7.0)
- Android7.0使用FileProvider安装apk
- Android7.0下载Apk自动安装
- Android7.0以上自动更新安装apk
- Android7.0升级安装APK,FileUriExposedException问题
- 下载安装APK(兼容Android7.0)
- Android7.0之安装apk文件
- Android7.0下载Apk自动安装
- Swift中defer在oc中的实现
- Git 中级用户的25个小贴士
- SSH Secure Shell 无法登录:server responded "algorithm negotiation failed”
- 阴阳师:卡牌依旧
- Jquery DIV滚动至浏览器顶部位置固定
- Android7.0 PackageManagerService (3) APK安装
- Java 调用svnkit实现svn功能
- web.xml配置加载的顺序
- 一张图解释队列算法
- Ubuntu Kylin 14.04 安装配置 jdk、eclipse、tomcat 通用
- CentOS6.5:升级了python,把输入法给玩坏了
- js中for遍历数组和对象,及对象和数组之间的区别
- gem5跑HM的测试结果
- 速算