Android Xapk安装(二)

1 引言

在文章Android Xapk安装(一)中,讨论了xapk包的组成以及安装方式,并分析了使用adb install-multiple方式安装xapk的过程,在该文章中,我们知道,adb install-multiple命令安装xapk,实际上是在Android系统中运行execc:cmd package install-create,exec:cmd package install-write,exec:cmd package install-commit三个命令,而这三个命令最终会进入到PackageManagerServiceonShellCommand方法去执行。本文接着前文,分析在PackageManagerService中的执行过程,本文的源码基于Android 10.

2 PackageManagerService中执行过程

2.1 onShellCommand

根据前文分析,exec:cmd package指令最后会进入到PackageManagerService(源码位置:/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java)的onShellCommand方法中,其源码如下:

Java 复制代码
  @Override
    public void onShellCommand(FileDescriptor in, FileDescriptor out,
            FileDescriptor err, String[] args, ShellCallback callback,
            ResultReceiver resultReceiver) {
        (new PackageManagerShellCommand(this)).exec(
                this, in, out, err, args, callback, resultReceiver);
    }

onShellCommand方法中,会创建一个PackageManagerShellCommand对象,然后调用其exec方法.PackageManagerShellCommand源码位于于/frameworks/base/services/core/java/com/android/server/pm/目录,PackageManagerShellCommand继承自ShellCommand(源码位于/frameworks/base/core/java/android/os/ShellCommand.java),exec方法实现在ShellCommand类中,在该方法中,实际调用的是onCommand方法,而该方法是由各个子类去实现,所以实际上最后代码会进入大PackageManagerShellCommandonCommand方法中,该方法用来解析和PackageManagerService有关的相关命令,如pm dump,pm list这些命令最终也会进入到该方法。和本次安装xapk有关的处理代码如下

Java 复制代码
  public int onCommand(String cmd) {
        if (cmd == null) {
            return handleDefaultCommands(cmd);
        }

        final PrintWriter pw = getOutPrintWriter();
        try {
            switch(cmd) {
                ...
                case "install-abandon":
                case "install-destroy":
                    return runInstallAbandon();
                case "install-commit":
                    return runInstallCommit();
                case "install-create":
                    return runInstallCreate();
                case "install-write":
                    return runInstallWrite();
                ...
            }
        } catch (RemoteException e) {
            pw.println("Remote exception: " + e);
        }
        return -1;
    }

从代码中可以看到,install-create,install-write,install-commit这三个命令分别由runInstallCreate,runInstallWrite,runInstallCommit三个方法执行,下面分别来看这三个方法

2.2 runInstallCreate

runInstallCreate方法源码如下

Java 复制代码
   private int runInstallCreate() throws RemoteException {
        final PrintWriter pw = getOutPrintWriter();
        final InstallParams installParams = makeInstallParams();
        final int sessionId = doCreateSession(installParams.sessionParams,
                installParams.installerPackageName, installParams.userId);

        // NOTE: adb depends on parsing this string
        pw.println("Success: created install session [" + sessionId + "]");
        return 0;
    }

在该方法中,先是构造了一个InstallParams对象,然后使用该对象做为参数,调用doCreateSession方法生成session并返回sessionId. InstallParamsPackageManagerShellCommand的一个内部类

Java 复制代码
    private static class InstallParams {
        SessionParams sessionParams;
        String installerPackageName;
        int userId = UserHandle.USER_ALL;
    }

从该类定义可以看到,InstallParams有三个成员变量:sessionParams:与session有关的参数,installerPackageName:调用安装程序或命令的应用,userId:本次安装的应用,对哪些用户可见,默认是所有用户. 了解了InstallParam的结构后,下面看构造InstallParam对象的makeInstallParams方法。在该方法中,首先创建了一个SessionParams对象,并将安装模式设为SessionParams.MODE_FULL_INSTALL,创建该对象后,创建一个InstallParams对象,并将SessionParams对象赋予其成员变量sessionParams.在完成这两步工作后,InstallParams对象就创建完成了。

Java 复制代码
 private InstallParams makeInstallParams() {
        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
        final InstallParams params = new InstallParams();

        params.sessionParams = sessionParams;
        // Whitelist all permissions by default
        //PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS 是一个用于 Android 的 //PackageManager 类的常量。该常量指示安装包管理器在安装应用程序时,是否将该应用程序添加到所有白名单受限权限的白名单中,默认是添加
        sessionParams.installFlags |= PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;

方法剩下的部分就是为SessionParams对象赋值,首先为本次安装的应用赋予白名单权限,然后解析传递过来的参数,执行相应的策略,回顾前文, install-create指令:

lua 复制代码
exec:cmd package install-create -S size au.com.metrotrains.dwtd4.apk config.arm64_v8a.apk

在该指令中,有一个-S选项参数,在本方法中,和-S参数有关的代码如下

Java 复制代码
...
 switch (opt) {
                ....
                case "-S":
                    final long sizeBytes = Long.parseLong(getNextArg());
                    if (sizeBytes <= 0) {
                        throw new IllegalArgumentException("Size must be positive");
                    }
                    sessionParams.setSize(sizeBytes);
                    break;
                    ...

从代码中可以看到,对于选项-S,就是把本次安装的包总大小写入到SessionParamsize属性中。至此,InstallParams对象就构造完成了,注意到,在该对象的三个属性中,installerPackageName属性为空。在完成InstallParams对象构造的分析后,下面分析doCreateSession方法,该方法是用来实际生成Session并返回sessionId:

Java 复制代码
    private int doCreateSession(SessionParams params, String installerPackageName, int userId)
            throws RemoteException {
        userId = translateUserId(userId, true /*allowAll*/, "runInstallCreate");
        if (userId == UserHandle.USER_ALL) {
            userId = UserHandle.USER_SYSTEM;
            params.installFlags |= PackageManager.INSTALL_ALL_USERS;
        }

        final int sessionId = mInterface.getPackageInstaller()
                .createSession(params, installerPackageName, userId);
        return sessionId;
    }

在该方法中,首先对userId做了一些检查,主要也还是权限方面,之后调用PackageInstallerServicecreateSession方法创建session,并返回sessionId

2.2.1 PackageInstallerService#createSession

PackageInstallerService 源码位于frameworks/services/core/java/com/android/server/pm/PackageInstallerService,createSession源码如下:

Java 复制代码
 @Override
    public int createSession(SessionParams params, String installerPackageName, int userId) {
        try {
            return createSessionInternal(params, installerPackageName, userId);
        } catch (IOException e) {
            throw ExceptionUtils.wrap(e);
        }
    }

createSession中,实际调用的是createSessionInternal方法完成具体的生成session工作,createSessionInsternal方法中和生成session有关的代码如下

Java 复制代码
        ...
        final int sessionId;
        final PackageInstallerSession session;
        synchronized (mSessions) {
            // Sanity check that installer isn't going crazy
            final int activeCount = getSessionCount(mSessions, callingUid);
            if (activeCount >= MAX_ACTIVE_SESSIONS) {
                throw new IllegalStateException(
                        "Too many active sessions for UID " + callingUid);
            }
            final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);
            if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
                throw new IllegalStateException(
                        "Too many historical sessions for UID " + callingUid);
            }

            sessionId = allocateSessionIdLocked();
        }

        final long createdMillis = System.currentTimeMillis();
        // We're staging to exactly one location
        File stageDir = null;
        String stageCid = null;
        if (!params.isMultiPackage) {
            if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
                stageDir = buildSessionDir(sessionId, params);
            } else {
                stageCid = buildExternalStageCid(sessionId);
            }
        }
        session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                mInstallThread.getLooper(), mStagingManager, sessionId, userId,
                installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
                false, false, null, SessionInfo.INVALID_ID, false, false, false,
                SessionInfo.STAGED_SESSION_NO_ERROR, "");

        synchronized (mSessions) {
            mSessions.put(sessionId, session);
        }
        if (params.isStaged) {
            mStagingManager.createSession(session);
        }

        if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) {
            mCallbacks.notifySessionCreated(session.sessionId, session.userId);
        }
        writeSessionsAsync();
        return sessionId;
    }

在生成Session时,首先会检查当前系统中活跃的sessionId个数,有多少个sessionId就意味着现在系统有多个在安装的应用,所以需要控制sessionId个数,避免过多的应用同时安装拖垮系统;然后调用allocateSessionIdLocked方法生成一个随机的sessionId,在生成sessionId后,调用buildSessionDirbuindExternalStageCid方法分别生成stageDirstagCid,其中stageDir是用来临时存放apk文件的目录,其目录类似/data/app/vmdl{sessionId}.tmp;在生成stageDirstageCid后,创建一个PackageInstallerSession对象作为本次安装的Session并将其和SessionId放在mapmSessions中,这样就将sessionIdSession关联起来了,PackageInstallerSession的源码位于frameworks/services/core/java/com/android/server/pm/PackageInstallerSession,在构造方法中将这些参数保存到其成员变量中;在createSessionInternal方法的最后,调用writeSessionsAsync方法将session数据持久化保存到目录/data/system/install_session.xml中,该文件保存着系统所有尚未完成安装的session.若系统重启,PackageManagerService启动时会从该文件中读取session数据,继续完成安装 。 至此,install-create就完成了session的创建,并返回其对应的sessionId,下面看install-write的执行方法runInstallWrite

2.3 runInstallWrite

在执行完install-create指令创建好session后,接下来就是执行install-write方法写入apk文件, 回顾前文写入指令

perl 复制代码
exec:cmd package install-write -S apkSize sessionId au.com.metrotrains.dwtd4.apk -

install-write指令对应的执行方法就是runInstallWrite方法:

Java 复制代码
  private int runInstallWrite() throws RemoteException {
        long sizeBytes = -1;

        String opt;
        while ((opt = getNextOption()) != null) {
            if (opt.equals("-S")) {
                sizeBytes = Long.parseLong(getNextArg());
            } else {
                throw new IllegalArgumentException("Unknown option: " + opt);
            }
        }

        final int sessionId = Integer.parseInt(getNextArg());
        final String splitName = getNextArg();
        final String path = getNextArg();
        return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
    }

在该方法中,首先获取install-write命令携带的-S选项,该选项携带了本次写入apk文件的大小,而且该参数是必须的,获取到该参数后,再依次获取本次操作的sessionIdapk文件的路径,获取到这些参数后,调用doWriteSplit方法完成写入:

Java 复制代码
    private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
            boolean logSuccess) throws RemoteException {
        PackageInstaller.Session session = null;
        try {
            final PrintWriter pw = getOutPrintWriter();
            //1. 根据输入路径,打开对应的文件,其中STDIN_PATH为'-''
            final ParcelFileDescriptor fd;
            if (STDIN_PATH.equals(inPath)) {
                fd = ParcelFileDescriptor.dup(getInFileDescriptor());
            } else if (inPath != null) {
                fd = openFileForSystem(inPath, "r");
                if (fd == null) {
                    return -1;
                }
                sizeBytes = fd.getStatSize();
                if (sizeBytes < 0) {
                    getErrPrintWriter().println("Unable to get size of: " + inPath);
                    return -1;
                }
            } else {
                fd = ParcelFileDescriptor.dup(getInFileDescriptor());
            }
            //2. 检查本次写入的文件的大小
            if (sizeBytes <= 0) {
                getErrPrintWriter().println("Error: must specify a APK size");
                return 1;
            }
            //3. 根据seesionId打开对应的PackageInstallerSession ,并调用其write方法
            session = new PackageInstaller.Session(
                    mInterface.getPackageInstaller().openSession(sessionId));
            session.write(splitName, 0, sizeBytes, fd);

            if (logSuccess) {
                pw.println("Success: streamed " + sizeBytes + " bytes");
            }
            return 0;
        } catch (IOException e) {
            getErrPrintWriter().println("Error: failed to write; " + e.getMessage());
            return 1;
        } finally {
            IoUtils.closeQuietly(session);
        }
    }

可以看到,该方法主要分为3步:

  1. 根据指令传入的apk文件写入路径,打开对应的文件,在上文中的指令中,传入的写入文件路径为-,其对应的就是STDIN_PATH,即写入标准写入中,
  2. 检查本次写入的apk文件大小,写入的大小应该大于0
  3. 根据传入的seesionId,打开对应的Session,并调用其write方法完成写入Session在系统中对应的是PackageInstallerSession对象,所以实际上就是调用PackageInstallerSessionwrite方法。下面进入到PackageInstallerSession中,分析其write方法

2.3.1 PackageInstallerSession#write

write方法源码如下

Java 复制代码
 @Override
    public void write(String name, long offsetBytes, long lengthBytes,
            ParcelFileDescriptor fd) {
        try {
            doWriteInternal(name, offsetBytes, lengthBytes, fd);
        } catch (IOException e) {
            throw ExceptionUtils.wrap(e);
        }
    }

write方法中,实际上是调用doWriteInternal方法完成实际的写入操作:

Java 复制代码
 private ParcelFileDescriptor doWriteInternal(String name, long offsetBytes, long lengthBytes,
            ParcelFileDescriptor incomingFd) 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.
        final RevocableFileDescriptor fd;
        final FileBridge bridge;
        final File stageDir;
        synchronized (mLock) {
            assertCallerIsOwnerOrRootLocked();
            assertPreparedAndNotSealedLocked("openWrite");
            //1.获取写入安装包的stageDir,该值在创建session时创建,其路径类似/data/app/vmdl{sessionId}.tmp
            if (PackageInstaller.ENABLE_REVOCABLE_FD) {
                fd = new RevocableFileDescriptor();
                bridge = null;
                mFds.add(fd);
            } else {
                fd = null;
                bridge = new FileBridge();
                mBridges.add(bridge);
            }

            stageDir = resolveStageDirLocked();
        }

        try {
            // Use installer provided name for now; we always rename later
            if (!FileUtils.isValidExtFilename(name)) {
                throw new IllegalArgumentException("Invalid name: " + name);
            }
            //2. 创建要写入的目标文件,一般就是stageDir/apkName.apk
            final File target;
            final long identity = Binder.clearCallingIdentity();
            try {
                target = new File(stageDir, name);
                 Slog.i("InstallTrace","PackageInstallerSeiion#dowriteInternal write apk in session: targetPath="+target.getAbsolutePath());
            } finally {
                Binder.restoreCallingIdentity(identity);
            }

            // TODO: this should delegate to DCS so the system process avoids
            // holding open FDs into containers.
            final FileDescriptor targetFd = 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 (stageDir != null && lengthBytes > 0) {
                mContext.getSystemService(StorageManager.class).allocateBytes(targetFd, lengthBytes,
                        PackageHelper.translateAllocateFlags(params.installFlags));
            }

            if (offsetBytes > 0) {
                Os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
            }

            if (incomingFd != null) {
                switch (Binder.getCallingUid()) {
                    case android.os.Process.SHELL_UID:
                    case android.os.Process.ROOT_UID:
                    case android.os.Process.SYSTEM_UID:
                        break;
                    default:
                        throw new SecurityException(
                                "Reverse mode only supported from shell or system");
                }

                // In "reverse" mode, we're streaming data ourselves from the
                // incoming FD, which means we never have to hand out our
                // sensitive internal FD. We still rely on a "bridge" being
                // inserted above to hold the session active.
                try {
                    final Int64Ref last = new Int64Ref(0);
                    //3.将apk文件从标准输入(STDIN_FIENO)copy到目标文件中
                    FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, lengthBytes, null,
                            Runnable::run, (long progress) -> {
                                if (params.sizeBytes > 0) {
                                    final long delta = progress - last.value;
                                    last.value = progress;
                                    addClientProgress((float) delta / (float) params.sizeBytes);
                                }
                            });
                } finally {
                    IoUtils.closeQuietly(targetFd);
                    IoUtils.closeQuietly(incomingFd);

                    // We're done here, so remove the "bridge" that was holding
                    // the session active.
                    synchronized (mLock) {
                        if (PackageInstaller.ENABLE_REVOCABLE_FD) {
                            mFds.remove(fd);
                        } else {
                            bridge.forceClose();
                            mBridges.remove(bridge);
                        }
                    }
                }
                return null;
            } else if (PackageInstaller.ENABLE_REVOCABLE_FD) {
                fd.init(mContext, targetFd);
                return fd.getRevocableFileDescriptor();
            } else {
                bridge.setTargetFile(targetFd);
                bridge.start();
                return new ParcelFileDescriptor(bridge.getClientSocket());
            }

        } catch (ErrnoException e) {
            throw e.rethrowAsIOException();
        }
    }

可以看到,在该方法中,主要分为三步

  1. 获取写入安装包的临时目录stageDir该目录在install-create阶段生成,一般就是/data/app/vmdl{sessionId}.tmp目录
  2. 创建要写入的目录文件,一般就是stageDir/fileName.apk
  3. 调用FileUtils.copy方法将apk文件从标准输入中copy到目标文件中,完成本次的intall-write

在对所有的apk执行上述操作后,本次安装的xapk包就完成了install-write操作,下面分析install-commit的执行方法runInstallCommit

2.4 runInstallCommit

在写入apk文件成功后,执行install-commit指令提交本次安装

Java 复制代码
 private int runInstallCommit() throws RemoteException {
        final int sessionId = Integer.parseInt(getNextArg());
        return doCommitSession(sessionId, true /*logSuccess*/);
    }

runInstallCommit方法中,实际是调用doCommitSession完成commit操作而在doCommitSession中,实际上是调用PacakgeInstallerSessioncommit方法并返回结果:

Java 复制代码
    private int doCommitSession(int sessionId, boolean logSuccess)
            throws RemoteException {

        final PrintWriter pw = getOutPrintWriter();
        PackageInstaller.Session session = null;
        try {
            session = new PackageInstaller.Session(
                    mInterface.getPackageInstaller().openSession(sessionId));
            if (!session.isMultiPackage() && !session.isStaged()) {
                // Sanity check that all .dm files match an apk.
                // (The installer does not support standalone .dm files and will not process them.)
                try {
                    DexMetadataHelper.validateDexPaths(session.getNames());
                } catch (IllegalStateException | IOException e) {
                    pw.println(
                            "Warning [Could not validate the dex paths: " + e.getMessage() + "]");
                }
            }
            final LocalIntentReceiver receiver = new LocalIntentReceiver();
            session.commit(receiver.getIntentSender());
            final Intent result = receiver.getResult();
            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                    PackageInstaller.STATUS_FAILURE);
            if (status == PackageInstaller.STATUS_SUCCESS) {
                if (logSuccess) {
                    pw.println("Success");
                }
            } else {
                pw.println("Failure ["
                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
            }
            return status;
        } finally {
            IoUtils.closeQuietly(session);
        }
    }

下面进入到PacakgeInstallerSession中继续分析commit方法

2.4.1 PackageInstallerSession#commit

commit源码如下

Java 复制代码
    @Override
    public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
        if (mIsPerfLockAcquired && mPerfBoostInstall != null) {
            mPerfBoostInstall.perfLockRelease();
            mIsPerfLockAcquired = false;
        }
        if (hasParentSessionId()) {
            throw new IllegalStateException(
                    "Session " + sessionId + " is a child of multi-package session "
                            + mParentSessionId +  " and may not be committed directly.");
        }
        //做一些准备工作,校验一下安装包,然后将应用标记为已提交
        if (!markAsCommitted(statusReceiver, forTransfer)) {
            return;
        }
        //判断是否是安装多个ap,adb install-multiple_packages命令,该值为true
        //adb install-mulitple命令安装的还是一个apk,只是这个apk由多个子包构成,所以该值为false
        if (isMultiPackage()) {
            final SparseIntArray remainingSessions = mChildSessionIds.clone();
            final IntentSender childIntentSender =
                    new ChildStatusIntentReceiver(remainingSessions, statusReceiver)
                            .getIntentSender();
            RuntimeException commitException = null;
            boolean commitFailed = false;
            for (int i = mChildSessionIds.size() - 1; i >= 0; --i) {
                final int childSessionId = mChildSessionIds.keyAt(i);
                try {
                    // commit all children, regardless if any of them fail; we'll throw/return
                    // as appropriate once all children have been processed
                    if (!mSessionProvider.getSession(childSessionId)
                            .markAsCommitted(childIntentSender, forTransfer)) {
                        commitFailed = true;
                    }
                } catch (RuntimeException e) {
                    commitException = e;
                }
            }
            if (commitException != null) {
                throw commitException;
            }
            if (commitFailed) {
                return;
            }
        }
        mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
    }

在该方法中,首先会调用markAsCommitted方法对需要安装的应用做一些校验,主要是一些参数检查工作,并且在该阶段,会解析出安装的应用的包名并赋值给当前PackageInstallerSessionmPackageName属性,markAsComitted方法经过一系列调用后,最终会调用validateApkInstallLocked,该方法用来检测安装应用的各个参数,并完成一些安装的准备工作.该方法较长,下面只列出本次安装需要关注的地方:

Java 复制代码
private void validateApkInstallLocked(@Nullable PackageInfo pkgInfo)
            throws PackageManagerException {
        ApkLite baseApk = null;
        mPackageName = null;
        mVersionCode = -1;
        mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
        ...
        final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter);
        if (ArrayUtils.isEmpty(addedFiles) && removeSplitList.size() == 0) {
            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
        }

        // Verify that all staged packages are internally consistent
        final ArraySet<String> stagedSplits = new ArraySet<>();
        for (File addedFile : addedFiles) {
            final ApkLite apk;
            try {
                 //1.解析出apk的ApkLite
                apk = PackageParser.parseApkLite(
                        addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
            } catch (PackageParserException e) {
                throw PackageManagerException.from(e);
            }

            if (!stagedSplits.add(apk.splitName)) {
                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                        "Split " + apk.splitName + " was defined multiple times");
            }

            // Use first package to define unknown values
            //2. 获取apk的packageName和versionCode,对于xapk,每一个子apk的包的packName,versionCode都是一样的
            if (mPackageName == null) {
                mPackageName = apk.packageName;
                mVersionCode = apk.getLongVersionCode();
            }
            if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
                mSigningDetails = apk.signingDetails;
            }

            assertApkConsistentLocked(String.valueOf(addedFile), apk);

            // Take this opportunity to enforce uniform naming
            final String targetName;
            //3.主包改名为base.apk,非主包前面加上split_前缀
            if (apk.splitName == null) {
                targetName = "base" + APK_FILE_EXTENSION;
            } else {
                targetName = "split_" + apk.splitName + APK_FILE_EXTENSION;
            }
            if (!FileUtils.isValidExtFilename(targetName)) {
                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                        "Invalid filename: " + targetName);
            }

            final File targetFile = new File(mResolvedStageDir, targetName);
            resolveAndStageFile(addedFile, targetFile);

            // Base is coming from session
            //4.将主包(base包保存下来)
            if (apk.splitName == null) {
                mResolvedBaseFile = targetFile;
                baseApk = apk;
            }
            ....
         }
       
        }

在该方法中,会遍历stagedDir,即写入apk文件的目录,对每一个apk做如下操作:

  1. 解析出apk的ApkLite
  2. 获取apk的packageName和versionCode,对于xapk,每一个子apk的包的packName,versionCode都是一样的
  3. 主包改名为base.apk,非主包前面加上split_前缀
  4. 如果是主包,则将主包路径保存下来 从代码中可以看到,关键的步骤是第1步,即调用packageParser.parseApkLite方法生成ApkLite对象,ApkLite位于源码/frameworks/base/core/java/android/content/pm/PackageParser.java主要是存放AndroidMainfest.xml文件中跟安装应用有关的属性,如packageName,versionCode
java 复制代码
   
    public static class ApkLite {
        public final String codePath;
        public final String packageName;
        public final String splitName;
        public boolean isFeatureSplit;
        public final String configForSplit;
        public final String usesSplitName;
        public final int versionCode;
        public final int versionCodeMajor;
        public final int revisionCode;
        public final int installLocation;
        public final int minSdkVersion;
        public final int targetSdkVersion;
        public final VerifierInfo[] verifiers;
        public final SigningDetails signingDetails;
        public final boolean coreApp;
        public final boolean debuggable;
        public final boolean multiArch;
        public final boolean use32bitAbi;
        public final boolean extractNativeLibs;
        public final boolean isolatedSplits;
        public final boolean isSplitRequired;
        public final boolean useEmbeddedDex;
        
   }

在完成应用的参数检查后,下一步判断是否是MutilPackage,即批量安装多个应用的场景,一般是false,所以在该方法中,实际上是向Handler中发送一个MSG_COMMITMessgae,在Handler中处理该消息的代码如下

Java 复制代码
 private final Handler.Callback mHandlerCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_COMMIT:
                    handleCommit();
                    break;
                //省略其他case
            }

            return true;
        }
    };

在处理MSG_COMMIT时会调用handleCommit方法,该方法源码如下

Java 复制代码
  private void handleCommit() {
        //设备管理和设备安全相关,确认当前用户是否有权限安装应用
        if (isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked()) {
            DevicePolicyEventLogger
                    .createEvent(DevicePolicyEnums.INSTALL_PACKAGE)
                    .setAdmin(mInstallerPackageName)
                    .write();
        }
        //该apk是否分段传输,如果分段传输,需要指定--staged选项,一般不用
        if (params.isStaged) {
            mStagingManager.commitSession(this);
            destroyInternal();
            dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "Session staged", null);
            return;
        }
        //是否安装apex
        if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
            destroyInternal();
            dispatchSessionFinished(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
                    "APEX packages can only be installed using staged sessions.", null);
            return;
        }

        // For a multiPackage session, read the child sessions
        // outside of the lock, because reading the child
        // sessions with the lock held could lead to deadlock
        // (b/123391593).
        //多应用安装场景,获取所有的chidlSessions,单应用安装不用关注
        List<PackageInstallerSession> childSessions = getChildSessions();

        try {
            synchronized (mLock) {
                //提交所有的session
                commitNonStagedLocked(childSessions);
            }
        } catch (PackageManagerException e) {
            final String completeMsg = ExceptionUtils.getCompleteMessage(e);
            Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
            destroyInternal();
            dispatchSessionFinished(e.error, completeMsg, null);
        }
    }

在该方法中,做一些必要的校验后,如果是安装非stag应该和非apex,最后会进入到commitNonStagedLocked方法中方法中,对于单应用来说,childSessions参数为null,下面看commitNonStagedLocked方法

Java 复制代码
    @GuardedBy("mLock")
    private void commitNonStagedLocked(List<PackageInstallerSession> childSessions)
            throws PackageManagerException {
        //1.创建一个committingSession
        final PackageManagerService.ActiveInstallSession committingSession =
                makeSessionActiveLocked();
        if (committingSession == null) {
            return;
        }
        //处理多应用情况
        if (isMultiPackage()) {
             Slog.i("InstallTrace", " PackageInstallerSeiion#commitNonStagedLocked commit session: isMultiPackage");
            List<PackageManagerService.ActiveInstallSession> activeChildSessions =
                    new ArrayList<>(childSessions.size());
            boolean success = true;
            PackageManagerException failure = null;
            for (int i = 0; i < childSessions.size(); ++i) {
                final PackageInstallerSession session = childSessions.get(i);
                try {
                    final PackageManagerService.ActiveInstallSession activeSession =
                            session.makeSessionActiveLocked();
                    if (activeSession != null) {
                        activeChildSessions.add(activeSession);
                    }
                } catch (PackageManagerException e) {
                    failure = e;
                    success = false;
                }
            }
            if (!success) {
                try {
                    mRemoteObserver.onPackageInstalled(
                            null, failure.error, failure.getLocalizedMessage(), null);
                } catch (RemoteException ignored) {
                }
                return;
            }
            mPm.installStage(activeChildSessions);
        } else {
            //2.调用instllStage方法
            mPm.installStage(committingSession);
        }
    }

在该方法中,实际上就做了两件事:

  1. 调用makeSessionActiveLocked方法生成ActiveInstallSession对象
  2. 调用PackageManagerServiceinstallStage方法完成安装

下面先看makeSessionActiveLocked方法

Java 复制代码
    @GuardedBy("mLock")
    private PackageManagerService.ActiveInstallSession makeSessionActiveLocked()
            throws PackageManagerException {
        if (mRelinquished) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                    "Session relinquished");
        }
        if (mDestroyed) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
        }
        if (!mSealed) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
        }

        final IPackageInstallObserver2 localObserver;
        if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
            localObserver = null;
        } else {
            Slog.i("InstallTrace", " PackageInstallerSeiion#makeSessionActiveLocaked commit session: isMultipackage="+params.isMultiPackage);
            if (!params.isMultiPackage) {
                Preconditions.checkNotNull(mPackageName);
                Preconditions.checkNotNull(mSigningDetails);
                Preconditions.checkNotNull(mResolvedBaseFile);
                //1.是否还需要询问授权安装,对于adb安装一般不用
                if (needToAskForPermissionsLocked()) {
                    // User needs to confirm installation;
                    // give installer an intent they can use to involve
                    // user.
                    final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
                    intent.setPackage(mPm.getPackageInstallerPackageName());
                    intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
                    try {
                        mRemoteObserver.onUserActionRequired(intent);
                    } catch (RemoteException ignored) {
                    }

                    // Commit was keeping session marked as active until now; release
                    // that extra refcount so session appears idle.
                    closeInternal(false);
                    return null;
                }

                // Inherit any packages and native libraries from existing install that
                // haven't been overridden.
                //2.安装mode是否继承已有的package,adb install 一般是SessionParams.MODE_FULL_INSTALL
                if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
                    try {
                        final List<File> fromFiles = mResolvedInheritedFiles;
                        final File toDir = resolveStageDirLocked();

                        if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
                        if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
                            throw new IllegalStateException("mInheritedFilesBase == null");
                        }

                        if (isLinkPossible(fromFiles, toDir)) {
                            if (!mResolvedInstructionSets.isEmpty()) {
                                final File oatDir = new File(toDir, "oat");
                                createOatDirs(mResolvedInstructionSets, oatDir);
                            }
                            // pre-create lib dirs for linking if necessary
                            if (!mResolvedNativeLibPaths.isEmpty()) {
                                for (String libPath : mResolvedNativeLibPaths) {
                                    // "/lib/arm64" -> ["lib", "arm64"]
                                    final int splitIndex = libPath.lastIndexOf('/');
                                    if (splitIndex < 0 || splitIndex >= libPath.length() - 1) {
                                        Slog.e(TAG,
                                                "Skipping native library creation for linking due"
                                                        + " to invalid path: " + libPath);
                                        continue;
                                    }
                                    final String libDirPath = libPath.substring(1, splitIndex);
                                    final File libDir = new File(toDir, libDirPath);
                                    if (!libDir.exists()) {
                                        NativeLibraryHelper.createNativeLibrarySubdir(libDir);
                                    }
                                    final String archDirPath = libPath.substring(splitIndex + 1);
                                    NativeLibraryHelper.createNativeLibrarySubdir(
                                            new File(libDir, archDirPath));
                                }
                            }
                            linkFiles(fromFiles, toDir, mInheritedFilesBase);
                        } else {
                            // TODO: this should delegate to DCS so the system process
                            // avoids holding open FDs into containers.
                            copyFiles(fromFiles, toDir);
                        }
                    } catch (IOException e) {
                        throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
                                "Failed to inherit existing install", e);
                    }
                }

                // TODO: surface more granular state from dexopt
                mInternalProgress = 0.5f;
                computeProgressLocked(true);

                // Unpack native libraries
                //3. 提取出lib库
                 Slog.i("InstallTrace", " PackageInstallerSeiion#makeSessionActiveLocaked commit session: mResolvedStageDir="+mResolvedStageDir+"params.abi="+params.abiOverride);
                extractNativeLibraries(mResolvedStageDir, params.abiOverride,
                        mayInheritNativeLibs());
            }

            // We've reached point of no return; call into PMS to install the stage.
            // Regardless of success or failure we always destroy session.
            localObserver = new IPackageInstallObserver2.Stub() {
                @Override
                public void onUserActionRequired(Intent intent) {
                    throw new IllegalStateException();
                }

                @Override
                public void onPackageInstalled(String basePackageName, int returnCode, String msg,
                        Bundle extras) {
                    destroyInternal();
                    dispatchSessionFinished(returnCode, msg, extras);
                }
            };
        }
        //4 确定use,一般是UserHandle.ALL
        final UserHandle user;
        if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
            user = UserHandle.ALL;
        } else {
            user = new UserHandle(userId);
        }
        //5.返回AcitveInstallSession对象
        mRelinquished = true;
           Slog.i("InstallTrace", " PackageInstallerSeiion#makeSessionActiveLocaked commit session: mPackageName="+mPackageName+" stageDir="+stageDir+" mInstallerPackageName=" + mInstallerPackageName+" uid="+ mInstallerUid);
        return new PackageManagerService.ActiveInstallSession(mPackageName, stageDir,
                localObserver, params, mInstallerPackageName, mInstallerUid, user,
                mSigningDetails);
    }

makeSessionActiveLocked方法中,主要有下面5个步骤

  1. 是否还需要询问授权安装,对于adb安装一般不用
  2. 安装mode是否继承已有的package,adb install 一般是SessionParams.MODE_FULL_INSTALL
  3. 提取出lib
  4. 确定use,一般是UserHandle.ALL
  5. 返回AcitveInstallSession对象

这5个步骤中,1,2是进行安装前的一些检查,4是确认应用的用户权限,5是返回生成的ActiveInstallSession对象,这四个步骤都比较简单,比较重要的是第3步,提取应用的lib库,因为xapk的lib库和apk应用的lib库提取有一些差异,这部分内容放在下文单独分析,我们先继续看commitNonStagedLocked方法。

在调用makeSessionActiveLocked生成ActiveInstallSession后,commitNonStagedLocked方法下一步调用PackageManagerServiceinstallStage完成应用安装。在Android 10中,无论是在应用商店下载应用自动安装,还是下载apk手动安装,亦或是使用pm installadb install这些命令的方式安装,最终都会调用PackageManagerService#installStage方法完成应用安装,所以该方法不只是和xapk安装有关系。对于该方法的分析,我们放在下一篇中单独分析。

在进入下一篇分析installStage方法时,先看看xapk中比较重要的提取lib库的方法:PackageInstallSession中的extractNativeLibraries方法

3 Android应用提取lib库

3.1 Android 应用提取lib库整体流程

在android应用,尤其是游戏中,一般会包含许多的动态so库,若这些游戏是apk形式的安装包,其so库一般会打包到apk的lib目录下,在安装时,会将这些so库从apk中提取到游戏的lib目录下。而对于包含多个apk的xapk游戏安装包,其so库可能会和普通apk游戏安装包一样,将so库打包到xapk的主apk中,这样使用adb multiple-install安装时,会将so库解析到应用的lib目录;而更常见的情况是,应用会将so库打包成一个单独的apk,在运行游戏时,会将这个apk加载到内存中,以此来完成so库动态加载,对于这种情况,一般是不能将so库解压到应用的lib目录。这样,对于xapk的应用在安装时,就需要考虑是否将so库抽取到lib目录下,前文提到,在安装应用时,会调用PackageInstallSession中的extractNativeLibraries方法提取so库,下面看看该方法代码:

Java 复制代码
  private static void extractNativeLibraries(File packageDir, String abiOverride, boolean inherit)
            throws PackageManagerException {
         //libDir:即lib目录
        final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
         packageDir.getAbsolutePath()+";abioverride="+abiOverride+";inherit="+inherit);
        //inherit一般为false
        if (!inherit) {
            // Start from a clean slate
            NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
        }

        NativeLibraryHelper.Handle handle = null;
        try {
            //根据packageDir目录生成对应的Handle
            handle = NativeLibraryHelper.Handle.create(packageDir);
            //解析so库到lib目录
            final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
                    abiOverride);
            if (res != PackageManager.INSTALL_SUCCEEDED) {
                throw new PackageManagerException(res,
                        "Failed to extract native libraries, res=" + res);
            }
        } catch (IOException e) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                    "Failed to extract native libraries", e);
        } finally {
            IoUtils.closeQuietly(handle);
        }
    }

从代码中可以看到,该方法主要在于NativeLibraryHelper.Handle.create NativeLibraryHelper.copyNativeBinariesWithOverride这两个方法的逻辑上,NativeLibraryHelper源码位于frameworks/base/core/java/com/android/internal/content/NativeLibraryHelper.java,Handle是其一个内部类,该类的create方法为:

java 复制代码
  public static class Handle implements Closeable {
        private final CloseGuard mGuard = CloseGuard.get();
        private volatile boolean mClosed;

        final long[] apkHandles;
        final boolean multiArch;
        final boolean extractNativeLibs;
        final boolean debuggable;

        public static Handle create(File packageFile) throws IOException {
            try {
                final PackageLite lite = PackageParser.parsePackageLite(packageFile, 0);
                return create(lite);
            } catch (PackageParserException e) {
                throw new IOException("Failed to parse package: " + packageFile, e);
            }
        }
       
       ....

在该方法中,会先调用PackageParser.parsePackageLite方法,该方法和前文的packageParser.parseApkLite类似,会生成一个PackageLite对象,和ApkLite用来记录一个apk的关键信息不同,PackageLite用来记录整个安装包的关键信息,包括该安装包有多少个apk等信息:

Java 复制代码
    /**
     * Lightweight parsed details about a single package.
     */
    public static class PackageLite {
        @UnsupportedAppUsage
        public final String packageName;
        public final int versionCode;
        public final int versionCodeMajor;
        @UnsupportedAppUsage
        public final int installLocation;
        public final VerifierInfo[] verifiers;

        /** Names of any split APKs, ordered by parsed splitName */
        public final String[] splitNames;

        /** Names of any split APKs that are features. Ordered by splitName */
        public final boolean[] isFeatureSplits;

        /** Dependencies of any split APKs, ordered by parsed splitName */
        public final String[] usesSplitNames;
        public final String[] configForSplit;

        /**
         * Path where this package was found on disk. For monolithic packages
         * this is path to single base APK file; for cluster packages this is
         * path to the cluster directory.
         */
        public final String codePath;

        /** Path of base APK */
        public final String baseCodePath;
        /** Paths of any split APKs, ordered by parsed splitName */
        public final String[] splitCodePaths;

        /** Revision code of base APK */
        public final int baseRevisionCode;
        /** Revision codes of any split APKs, ordered by parsed splitName */
        public final int[] splitRevisionCodes;

        public final boolean coreApp;
        public final boolean debuggable;
        public final boolean multiArch;
        public final boolean use32bitAbi;
        public final boolean extractNativeLibs;
        public final boolean isolatedSplits;

在生成PackageLite对象后,再调用其他create方法完成Handle创建,最终会将Handle的系统属性由PackageLite对象中获取到,比如其extractNativeLibs属性就是PackageLite对象的extractNativeLibs属性。其apkHandles就是PackageLite对象中保存的所有apk文件路径的文件描述符. 在生成了Handle对象后,下一步就是调用NativeLibraryHelper.copyNativeBinariesWithOverride方法完成so库的拷贝,在该方法中,最终会调用copyNativeBinaries方法完成so库拷贝

Java 复制代码
    /**
     * Copies native binaries to a shared library directory.
     *
     * @param handle APK file to scan for native libraries
     * @param sharedLibraryDir directory for libraries to be copied to
     * @return {@link PackageManager#INSTALL_SUCCEEDED} if successful or another
     *         error code from that class if not
     */
    public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) {
        for (long apkHandle : handle.apkHandles) {
            int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi,
                    handle.extractNativeLibs, handle.debuggable);
            if (res != INSTALL_SUCCEEDED) {
                return res;
            }
        }
        return INSTALL_SUCCEEDED;
    }

在该方法中,会调用nativeCopyNativeBinaries方完成拷贝操作,该方法中的参数 handle.extractNativeLibs是用来标识是否需要提取so库,其值即为前文说的PackageLite对象的extractNativeLibs属性值。nativeCopyNativeBinaries是一个JNI方法,其实现位于/frameworks/base/core/jni/com_android_internal_content_NativeLibraryHelper.cpp#com_android_internal_content_NativeLibraryHelper_copyNativeBinaries,在该方法中,最终会调用其该类的copyFileIfChanged方法,下面列出来该方法部分代码

cpp 复制代码
copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName)
{
    void** args = reinterpret_cast<void**>(arg);
    jstring* javaNativeLibPath = (jstring*) args[0];
    jboolean extractNativeLibs = *(jboolean*) args[1];

    ScopedUtfChars nativeLibPath(env, *javaNativeLibPath);

    uint32_t uncompLen;
    uint32_t when;
    uint32_t crc;

    uint16_t method;
    off64_t offset;

    if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, NULL, &offset, &when, &crc)) {
        ALOGE("Couldn't read zip entry info\n");
        return INSTALL_FAILED_INVALID_APK;
    }
    //如果extractNativeLibs属性为false,则只是做一些校验然后返回,不会copy so库
    if (!extractNativeLibs) {
        // check if library is uncompressed and page-aligned
        if (method != ZipFileRO::kCompressStored) {
            ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
                fileName);
            return INSTALL_FAILED_INVALID_APK;
        }

        if (offset % PAGE_SIZE != 0) {
            ALOGE("Library '%s' is not page-aligned - will not be able to open it directly from"
                " apk.\n", fileName);
            return INSTALL_FAILED_INVALID_APK;
        }

        return INSTALL_SUCCEEDED;
    }
    //extractNativeLibs属性为true,解析所有apk的so库
    // Build local file path
    const size_t fileNameLen = strlen(fileName);
    char localFileName[nativeLibPath.size() + fileNameLen + 2];
    

在该方法中,会判断extractNativeLibs参数的值,该参数就是PackageLite对象中的extractNativeLibs属性值,若该值为true,则解析所有apk文件的so库,若该值为false,则不解析so库

至此,Android 应用提取so库的逻辑就分析完成了,总结起来就是:根据生成的PackageLite对象的extractNativeLibs属性来判断是否需要将应用的so库解析到lib目录,若该值为true,则解析到lib目录,若该值为false,则不解析。下面我们继续分析PackageLite对象的创建过程。

3.2 PackageLite对象创建过程

前文提到,create方法中的PackageLite对象是通过PackageParser#parsePackageLite方法得到的,该方法源码如下

Java 复制代码
    public static PackageLite parsePackageLite(File packageFile, int flags)
            throws PackageParserException {
        if (packageFile.isDirectory()) {
            return parseClusterPackageLite(packageFile, flags);
        } else {
            return parseMonolithicPackageLite(packageFile, flags);
        }
    }

packageFile参数不是一个目录(即是一个apk文件),则执行parseMonolithicPackageLite方法:

Java 复制代码
    private static PackageLite parseMonolithicPackageLite(File packageFile, int flags)
            throws PackageParserException {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
        final ApkLite baseApk = parseApkLite(packageFile, flags);
        final String packagePath = packageFile.getAbsolutePath();
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        return new PackageLite(packagePath, baseApk, null, null, null, null, null, null);
    }

可以看到,在该方法中,就是调用parseApkLite解析apk文件,然后利用该apkLite生成一个PackageLite对象。 若packageFile参数是一个目录,则执行方法parseClusterPackageLite方法,对于xapk来说,该参数一般就是一个目录,下面看该方法代码

Java 复制代码
    static PackageLite parseClusterPackageLite(File packageDir, int flags)
            throws PackageParserException {
        //1.获取该目录下所有apk文件
        final File[] files = packageDir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                    "No packages found in split");
        }

        String packageName = null;
        int versionCode = 0;

        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
        final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
        for (File file : files) {
            //2.遍历apk文件
            if (isApkFile(file)) {
                //3.调用parseApkLite生成ApkLite
                final ApkLite lite = parseApkLite(file, flags);

                // Assert that all package names and version codes are
                // consistent with the first one we encounter.
                if (packageName == null) {
                    packageName = lite.packageName;
                    versionCode = lite.versionCode;
                } else {
                    //4.检查packageName和version,对于xapk包,每一个apk的packageName和versionCode应该一致
                    if (!packageName.equals(lite.packageName)) {
                        throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                                "Inconsistent package " + lite.packageName + " in " + file
                                + "; expected " + packageName);
                    }
                    if (versionCode != lite.versionCode) {
                        throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                                "Inconsistent version " + lite.versionCode + " in " + file
                                + "; expected " + versionCode);
                    }
                }
                //5.将splitName和对应的ApkLite放入map表,注意,主包的splitName为null
                // Assert that each split is defined only once
                if (apks.put(lite.splitName, lite) != null) {
                    throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                            "Split name " + lite.splitName
                            + " defined more than once; most recent was " + file);
                }
            }
        }
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        //6. 移除掉主包,并报错主包的ApkLite
        final ApkLite baseApk = apks.remove(null);
        if (baseApk == null) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                    "Missing base APK in " + packageDir);
        }

        // Always apply deterministic ordering based on splitName
        final int size = apks.size();

        //7.获取splitNames等参数
        String[] splitNames = null;
        boolean[] isFeatureSplits = null;
        String[] usesSplitNames = null;
        String[] configForSplits = null;
        String[] splitCodePaths = null;
        int[] splitRevisionCodes = null;
        String[] splitClassLoaderNames = null;
        if (size > 0) {
            splitNames = new String[size];
            isFeatureSplits = new boolean[size];
            usesSplitNames = new String[size];
            configForSplits = new String[size];
            splitCodePaths = new String[size];
            splitRevisionCodes = new int[size];

            splitNames = apks.keySet().toArray(splitNames);
            Arrays.sort(splitNames, sSplitNameComparator);

            for (int i = 0; i < size; i++) {
                final ApkLite apk = apks.get(splitNames[i]);
                usesSplitNames[i] = apk.usesSplitName;
                isFeatureSplits[i] = apk.isFeatureSplit;
                configForSplits[i] = apk.configForSplit;
                splitCodePaths[i] = apk.codePath;
                splitRevisionCodes[i] = apk.revisionCode;
            }
        }
        //8.获取codePath,即packageDir的绝对路径(stagedDir的路径)
        final String codePath = packageDir.getAbsolutePath();
        //9.调用对应的构造方法生成PackageLite对象
        return new PackageLite(codePath, baseApk, splitNames, isFeatureSplits, usesSplitNames,
                configForSplits, splitCodePaths, splitRevisionCodes);
    }

如代码注释所示,该方法主要有9个步骤,在这9个步骤中,也包含调用parseApkLite方法生成ApkLite对象的步骤,下面先看看该方法:

Java 复制代码
    public static ApkLite parseApkLite(File apkFile, int flags)
            throws PackageParserException {
        return parseApkLiteInner(apkFile, null, null, flags);
    }
      private static ApkLite parseApkLiteInner(File apkFile, FileDescriptor fd, String debugPathName,
            int flags) throws PackageParserException {
        final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();

        XmlResourceParser parser = null;
        ApkAssets apkAssets = null;
        try {
            try {
                apkAssets = fd != null
                        ? ApkAssets.loadFromFd(fd, debugPathName, false, false)
                        : ApkAssets.loadFromPath(apkPath);
            } catch (IOException e) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                        "Failed to parse " + apkPath);
            }
            //读取apk的AndroidManifest.xml文件
            parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME);

            final SigningDetails signingDetails;
            if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
                // TODO: factor signature related items out of Package object
                final Package tempPkg = new Package((String) null);
                final boolean skipVerify = (flags & PARSE_IS_SYSTEM_DIR) != 0;
                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
                try {
                    collectCertificates(tempPkg, apkFile, skipVerify);
                } finally {
                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                }
                signingDetails = tempPkg.mSigningDetails;
            } else {
                signingDetails = SigningDetails.UNKNOWN;
            }

            final AttributeSet attrs = parser;
            //解析AndroidManifest.xml文件中属性
            return parseApkLite(apkPath, parser, attrs, signingDetails);

        } catch (XmlPullParserException | IOException | RuntimeException e) {
            Slog.w(TAG, "Failed to parse " + apkPath, e);
            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                    "Failed to parse " + apkPath, e);
        } finally {
            IoUtils.closeQuietly(parser);
            if (apkAssets != null) {
                try {
                    apkAssets.close();
                } catch (Throwable ignored) {
                }
            }
            // TODO(b/72056911): Implement AutoCloseable on ApkAssets.
        }
    }

parseApkLite方法中,实际调用的是parseApkLiteInner,在该方法中,会读取该apk文件的AndroidManifest.xml文件,然后调用重载的parseApkLite方法生成ApkLite对象:

Java 复制代码
    private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
            SigningDetails signingDetails)
            throws IOException, XmlPullParserException, PackageParserException {
        final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);

        int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
        int versionCode = 0;
        int versionCodeMajor = 0;
        int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
        int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
        int revisionCode = 0;
        boolean coreApp = false;
        boolean debuggable = false;
        boolean multiArch = false;
        boolean use32bitAbi = false;
        boolean extractNativeLibs = true;
        boolean isolatedSplits = false;
        boolean isFeatureSplit = false;
        boolean isSplitRequired = false;
        boolean useEmbeddedDex = false;
        String configForSplit = null;
        String usesSplitName = null;

        for (int i = 0; i < attrs.getAttributeCount(); i++) {
            final String attr = attrs.getAttributeName(i);
            if (attr.equals("installLocation")) {
                installLocation = attrs.getAttributeIntValue(i,
                        PARSE_DEFAULT_INSTALL_LOCATION);
            } else if (attr.equals("versionCode")) {
                versionCode = attrs.getAttributeIntValue(i, 0);
            } else if (attr.equals("versionCodeMajor")) {
                versionCodeMajor = attrs.getAttributeIntValue(i, 0);
            } else if (attr.equals("revisionCode")) {
                revisionCode = attrs.getAttributeIntValue(i, 0);
            } else if (attr.equals("coreApp")) {
                coreApp = attrs.getAttributeBooleanValue(i, false);
            } else if (attr.equals("isolatedSplits")) {
                isolatedSplits = attrs.getAttributeBooleanValue(i, false);
            } else if (attr.equals("configForSplit")) {
                configForSplit = attrs.getAttributeValue(i);
            } else if (attr.equals("isFeatureSplit")) {
                isFeatureSplit = attrs.getAttributeBooleanValue(i, false);
            } else if (attr.equals("isSplitRequired")) {
                isSplitRequired = attrs.getAttributeBooleanValue(i, false);
            }
        }

        // Only search the tree when the tag is the direct child of <manifest> tag
        int type;
        final int searchDepth = parser.getDepth() + 1;

        final List<VerifierInfo> verifiers = new ArrayList<VerifierInfo>();
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() >= searchDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            if (parser.getDepth() != searchDepth) {
                continue;
            }

            if (TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
                final VerifierInfo verifier = parseVerifier(attrs);
                if (verifier != null) {
                    verifiers.add(verifier);
                }
            } else if (TAG_APPLICATION.equals(parser.getName())) {
                for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                    final String attr = attrs.getAttributeName(i);
                    if ("debuggable".equals(attr)) {
                        debuggable = attrs.getAttributeBooleanValue(i, false);
                    }
                    if ("multiArch".equals(attr)) {
                        multiArch = attrs.getAttributeBooleanValue(i, false);
                    }
                    if ("use32bitAbi".equals(attr)) {
                        use32bitAbi = attrs.getAttributeBooleanValue(i, false);
                    }
                    if ("extractNativeLibs".equals(attr)) {
                        extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
                    }
                    if ("useEmbeddedDex".equals(attr)) {
                        useEmbeddedDex = attrs.getAttributeBooleanValue(i, false);
                    }
                }
            } else if (TAG_USES_SPLIT.equals(parser.getName())) {
                if (usesSplitName != null) {
                    Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
                    continue;
                }

                usesSplitName = attrs.getAttributeValue(ANDROID_RESOURCES, "name");
                if (usesSplitName == null) {
                    throw new PackageParserException(
                            PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                            "<uses-split> tag requires 'android:name' attribute");
                }
            } else if (TAG_USES_SDK.equals(parser.getName())) {
                for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                    final String attr = attrs.getAttributeName(i);
                    if ("targetSdkVersion".equals(attr)) {
                        targetSdkVersion = attrs.getAttributeIntValue(i,
                                DEFAULT_TARGET_SDK_VERSION);
                    }
                    if ("minSdkVersion".equals(attr)) {
                        minSdkVersion = attrs.getAttributeIntValue(i, DEFAULT_MIN_SDK_VERSION);
                    }
                }
            }
        }

        return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
                configForSplit, usesSplitName, isSplitRequired, versionCode, versionCodeMajor,
                revisionCode, installLocation, verifiers, signingDetails, coreApp, debuggable,
                multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, isolatedSplits,
                minSdkVersion, targetSdkVersion);
    }

可以看到,在该方法中就是解析AndroidManifest.xml的主要属性,然后使用这个属性值生成ApkLite属性,其中需要注意这些属性的默认值,其中extractNativeLibs属性的默认值为true,表示需要将动态so库解析到lib目录,若该值为false,则表示不能把so库解析到lib目录。

在分析了parseApkLite方法后,继续看parseClusterPackageLite,在该方法的最后,会调用PackageLite的构造方法生成一个PackageLite的对象

Java 复制代码
        public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
                boolean[] isFeatureSplits, String[] usesSplitNames, String[] configForSplit,
                String[] splitCodePaths, int[] splitRevisionCodes) {
            this.packageName = baseApk.packageName;
            this.versionCode = baseApk.versionCode;
            this.versionCodeMajor = baseApk.versionCodeMajor;
            this.installLocation = baseApk.installLocation;
            this.verifiers = baseApk.verifiers;
            this.splitNames = splitNames;
            this.isFeatureSplits = isFeatureSplits;
            this.usesSplitNames = usesSplitNames;
            this.configForSplit = configForSplit;
            this.codePath = codePath;
            this.baseCodePath = baseApk.codePath;
            this.splitCodePaths = splitCodePaths;
            this.baseRevisionCode = baseApk.revisionCode;
            this.splitRevisionCodes = splitRevisionCodes;
            this.coreApp = baseApk.coreApp;
            this.debuggable = baseApk.debuggable;
            this.multiArch = baseApk.multiArch;
            this.use32bitAbi = baseApk.use32bitAbi;
            this.extractNativeLibs = baseApk.extractNativeLibs;
            this.isolatedSplits = baseApk.isolatedSplits;
        }

在该构造方法中,主要关注属性extractNativeLibs,该属性是主包(baseApk)的extractNativeLibs属性的值。该值默认是ture,若安装包的主包的AndroidManifest.xml文件中包含android:extractNativeLibs属性值,则使用其值,若不包含该属性,则使用默认值true。 至此,我们就明白了PackageLite对象的extractNativeLibs属性是怎么来的了,其实就是解析主包的AndroidManifest.xml文件的android:extractNativeLibs属性值,若不包含该属性,则默认为true.在只包含一个apk的应用中(普通apk包或apk+obb形式的xapk包),其主包的AndroidManifest.xml文件中一般不会包含android:extractNativeLibs属性,而对于多个apk组成的xapk安装包,其主包的AndroidManifest.xml中一般都会明确指定android:extractNativeLibs属性的值。

3.3 总结

通过对extractNativeLibraries方法的分析,我们得出了下面的结论:

  1. 若安装包主包的AndroidManifest.xml文件中不包含android:extractNativeLibs属性或该属性值为true,系统会解析出so库到lib目录
  2. 若安装包主包的AndroidManifest.xml文件中包含android:extractNativeLibs属性且该属性为false,系统不会解析so库到lib目录

分析完了解析so库的逻辑,adb install-multiply的三个核心方法runInstallCreate, runInstallWrite, runInstallCommit三个方法整体流程就分析完成了,在下一篇,我们将继续分析PackageManagerService#installStage方法,该方法完成最终的安装操作

相关推荐
小彭努力中2 小时前
8.Three.js中的 StereoCamera 立体相机详解+示例代码
开发语言·前端·javascript·vue.js·深度学习·数码相机·ecmascript
牧天白衣.5 小时前
html中margin的用法
前端·html
NoneCoder5 小时前
HTML与安全性:XSS、防御与最佳实践
前端·html·xss
沃野_juededa5 小时前
关于uniapp 中uview input组件设置为readonly 或者disabled input区域不可点击问题
java·前端·uni-app
哎哟喂_!5 小时前
UniApp 实现分享功能
前端·javascript·vue.js·uni-app
k1955142395 小时前
uniapp常用
前端·javascript·uni-app
wuhen_n8 小时前
CSS元素动画篇:基于页面位置的变换动画
前端·css·html·css3·html5
sql123456789118 小时前
前端——CSS1
前端
Nueuis8 小时前
微信小程序分页和下拉刷新
服务器·前端·微信小程序
小白64028 小时前
前端性能优化(实践篇)
前端·性能优化