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方法,该方法完成最终的安装操作

相关推荐
恋猫de小郭3 小时前
Flutter 3.35 发布,快来看看有什么更新吧
android·前端·flutter
chinahcp20083 小时前
CSS保持元素宽高比,固定元素宽高比
前端·css·html·css3·html5
gnip4 小时前
浏览器跨标签页通信方案详解
前端·javascript
gnip5 小时前
运行时模块批量导入
前端·javascript
hyy27952276845 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
逆风优雅5 小时前
vue实现模拟 ai 对话功能
前端·javascript·html
若梦plus6 小时前
http基于websocket协议通信分析
前端·网络协议
不羁。。6 小时前
【web站点安全开发】任务3:网页开发的骨架HTML与美容术CSS
前端·css·html
这是个栗子6 小时前
【问题解决】Vue调试工具Vue Devtools插件安装后不显示
前端·javascript·vue.js
姑苏洛言6 小时前
待办事项小程序开发
前端·javascript