PackageManagerServie 安装位置确定

        PackageManagerService分析起来存在一些问题,由于我们对一些功能不甚了解,想一口吃一个胖子的方式分析下来还是比较困难的,所以我们采用庖丁解牛的方式,将Pms拆分成几个功能来进行分析。今天我们的入手点就是来分析下PackageManagerService如何确定应用安装的位置。
        经过分析,我们发现这部分代码是非常混乱的,估计google的开发人员对这段代码也感到头疼,为了兼顾历史问题,代码写的有很多前后矛盾的地方。还有很多代码其实已经失去了作用。 我们这里不考虑MultiPackage情况的apk,以Android 10 代码为例进行分析。经过我分析后发现Android 10 安装应用要考虑的因素比较多,总结起来基本上有以下几点:

  • 系统应用只允许安装到/data分区。
  • 分区空间要足够。
  • 覆盖安装要保持和原有安装在同一个分区。
  • 是否允许第三方应用安装到/data分区。
  • 是否强制允许安装应用到外置存储。
  • AndroidManifest.xml里面的installLocation 标签。
    auto, internalOnly,preferExternal

         前边5条比较好理解,后面的installLocation参考又分为几种情况, 在Android10中,默认的installLocation是internalOnly, 也就是只允许应用安装到/data分区,但是需要考虑之前是否已经安装过这个应用,如果已经在其他分区安装过,则可能发生不兼容,auto 和 preferExternal都是参考值,目前Android已经不在允许将应用直接安装到主存储,只允许将应用安装到内置存储和内置扩展存储。这是基于安全考虑,另外其实目前的主存储也是从内置存储或者扩展的内置存储上使用fuse划分出来的一个目录,二者其实是共用存储空间的,所以安装到主存储上其实意义不大,所以preferExternal标签其实已经被架空。

        我们分析安装代码有两个入口,1 是 应用商店程序, 2是pm install命令,由于pm install 更简单直接,可以省去一些策略的分析,所以我们从pm install命令入手, pm install 最终会调用frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java类的runInstall方法,该方法解析如下两个安装位置相关的参数。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java

case "-f":
   sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL;
case "--force-uuid":
   sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
   sessionParams.volumeUuid = getNextArg();
   if ("internal".equals(sessionParams.volumeUuid)) {
       sessionParams.volumeUuid = null;
   }

-f 指定安装到内部存储(/data和/mnt/expand/${uuid})。
--force-uuid 指定强制安装的分区(这也是内置分区的卷)。

这里并没有安装到主存储的参数,所以要想安装到外置存储只能通过AndroidManifest.xml文件中的installLocation的preferExternal标志。我们来大体看下runInstall函数,知道安装应用的基本流程。

private int runInstall() throws RemoteException {
        final PrintWriter pw = getOutPrintWriter();
        // 1 准备安装参数
        final InstallParams params = makeInstallParams();
        final String inPath = getNextArg();

        setParamsSize(params, inPath);
        // 2 创建session
        final int sessionId = doCreateSession(params.sessionParams,
                params.installerPackageName, params.userId);
        boolean abandonSession = true;
        try {
            if (inPath == null && params.sessionParams.sizeBytes == -1) {
                pw.println("Error: must either specify a package size or an APK file");
                return 1;
            }
            final boolean isApex =
                    (params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
            String splitName = "base." + (isApex ? "apex" : "apk");
            // 3 写入安装应用
            if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
                    false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
                return 1;
            }
            // 4 提交session
            if (doCommitSession(sessionId, false /*logSuccess*/)
                    != PackageInstaller.STATUS_SUCCESS) {
                return 1;
            }
            abandonSession = false;
            pw.println("Success");
            return 0;
        } finally {
            if (abandonSession) {
                try {
                    doAbandonSession(sessionId, false /*logSuccess*/);
                } catch (Exception ignore) {
                }
            }
        }
    }

        从上面代码我们可以知道,安装主要和Pms交互的三个步骤

  1. 创建session
  2. 写入apk
  3. 提交session

这里的session是只PackageInstallerSession, 创建PackageInstallerSession的服务为frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java类, 创建session的代码如下,我们截取部分和安装位置相关的代码。

frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java

  private int createSessionInternal(SessionParams params, String installerPackageName, int userId) {
    ......
           // If caller requested explicit location, sanity check it, otherwise
           // resolve the best internal or adopted location.
            if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
                if (!PackageHelper.fitsOnInternal(mContext, params)) {
                    throw new IOException("No suitable internal storage available");
                }
            } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
                // For now, installs to adopted media are treated as internal from
                // an install flag point-of-view.
                params.installFlags |= PackageManager.INSTALL_INTERNAL;
            } else {
                params.installFlags |= PackageManager.INSTALL_INTERNAL;

                // Resolve best location for install, based on combination of
                // requested install flags, delta size, and manifest settings.
                final long ident = Binder.clearCallingIdentity();
                try {
                    params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            }
            ......
		    
    ......
  }

         这里的三种情况,铁了心的将params.installFlags都设置成了PackageManager.INSTALL_INTERNAL,也就是不在允许apk安装到主存储。这里的三个分支解析如下:

  1. 如果本来就指定了PackageManager.INSTALL_INTERNAL标志,则表示将应用安装到/data分区, 如果/data分区空间不满足,只能抛出异常给调用方处理。
  2. PackageManager.INSTALL_FORCE_VOLUME_UUID 标志,现在也会忽略该标志和指定的分区id, 使用/data分区来安装。
  3. 这种情况也就是没有使用PackageManager.INSTALL_INTERNAL和PackageManager.INSTALL_FORCE_VOLUME_UUID标志来安装,允许使用其他扩展的内置存储安装,所以调用PackageHelper.resolveInstallVolume(mContext, params)来进行解析。所以即使安装程序指定了volumeId也会被忽略。

        我们展开来看下第三种情况,没有指定卷的情况下如何处理。

    @VisibleForTesting
    public static String resolveInstallVolume(Context context, SessionParams params,
            TestableInterface testInterface) throws IOException {
        final StorageManager storageManager = testInterface.getStorageManager(context);
        final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context);
        final boolean allow3rdPartyOnInternal =
                testInterface.getAllow3rdPartyOnInternalConfig(context);
        // TODO: handle existing apps installed in ASEC; currently assumes
        // they'll end up back on internal storage
        ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context,
                params.appPackageName);

        // Figure out best candidate volume, and also if we fit on internal
        final ArraySet<String> allCandidates = new ArraySet<>();
        boolean fitsOnInternal = false;
        VolumeInfo bestCandidate = null;
        long bestCandidateAvailBytes = Long.MIN_VALUE;
        for (VolumeInfo vol : storageManager.getVolumes()) {
            if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
                final boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id);
                final UUID target = storageManager.getUuidForPath(new File(vol.path));
                final long availBytes = storageManager.getAllocatableBytes(target,
                        translateAllocateFlags(params.installFlags));
                if (isInternalStorage) {
                    fitsOnInternal = (params.sizeBytes <= availBytes);
                }
                if (!isInternalStorage || allow3rdPartyOnInternal) {
                    if (availBytes >= params.sizeBytes) {
                        allCandidates.add(vol.fsUuid);
                    }
                    if (availBytes >= bestCandidateAvailBytes) {
                        bestCandidate = vol;
                        bestCandidateAvailBytes = availBytes;
                    }
                }
            }
        }
        // 1 上面allCandidates表示空间充足的PRIVATE分区, allCandidates表示空间充足的候选分区, bestCandidate表示空间最充足的候选分区,fitsOnInternal表示/data分区是否空间充足。注意这里如果第三方应用不允许安装到/data分区,allCandidates和bestCandidate不会包含/data分区。

       // 2 系统应用只允许安装在/data分区,/data分区如果空间不足,抛出异常
        // System apps always forced to internal storage
        if (existingInfo != null && existingInfo.isSystemApp()) {
            if (fitsOnInternal) {
                return StorageManager.UUID_PRIVATE_INTERNAL;
            } else {
                throw new IOException("Not enough space on existing volume "
                        + existingInfo.volumeUuid + " for system app " + params.appPackageName
                        + " upgrade");
            }
        }

       // 3 没有强制允许安装到扩展分区的情况,并且有INSTALL_LOCATION_INTERNAL_ONLY标志表示有强烈愿望安装到/data分区
        // If app expresses strong desire for internal storage, honor it
        if (!forceAllowOnExternal
                && params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
            // 3.1 已经安装到了扩展分区,不允许move
            if (existingInfo != null && !Objects.equals(existingInfo.volumeUuid,
                    StorageManager.UUID_PRIVATE_INTERNAL)) {
                throw new IOException("Cannot automatically move " + params.appPackageName
                        + " from " + existingInfo.volumeUuid + " to internal storage");
            }
            //3.2 不允许第三方应用安装到/data分区, 直接抛出异常。
            if (!allow3rdPartyOnInternal) {
                throw new IOException("Not allowed to install non-system apps on internal storage");
            }
            //3.3 /data分区存储空间满足,安装到/data分区。空间不满足抛出异常。
            if (fitsOnInternal) {
                return StorageManager.UUID_PRIVATE_INTERNAL;
            } else {
                throw new IOException("Requested internal only, but not enough space");
            }
        }
       
        // 4 覆盖安装
        // If app already exists somewhere, we must stay on that volume
        if (existingInfo != null) {
            // 4.1 原来也安装在/data分区,并且空间足够则允许覆盖安装到/data分区。非allow3rdPartyOnInternal的情况/data分区不在allCandidates中,所以有这个分支的存在。
            if (Objects.equals(existingInfo.volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)
                    && fitsOnInternal) {
                return StorageManager.UUID_PRIVATE_INTERNAL;
            // 4.2 如果原有安装的扩展分区空间满足,则使用扩展分区。
            } else if (allCandidates.contains(existingInfo.volumeUuid)) {
                return existingInfo.volumeUuid;
            // 4.2 没有合适的分区,抛出异常。
            } else {
                throw new IOException("Not enough space on existing volume "
                        + existingInfo.volumeUuid + " for " + params.appPackageName + " upgrade");
            }
        }
        // 5 没有强烈要求安装到内部分区,也没有被安装过,也没有强制要求安装到扩展分区,则使用最合适的分区(也可能是/data分区)
        // We're left with new installations with either preferring external or auto, so just pick
        // volume with most space
        if (bestCandidate != null) {
            return bestCandidate.fsUuid;
        } else {
            throw new IOException("No special requests, but no room on allowed volumes. "
                + " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal);
        }
    }

确定安装卷这里看起来比较复杂,其实有几个参考指标:
1、磁盘的空间要满足。
2、是否允许三方应用安装在/data分区(allow3rdPartyOnInternal)。
3、是否强制允许安装到扩展分区(forceAllowOnExternal)。
4、应用是否要求安装到/data分区。(PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY)
5、不支持move应用到其他盘。
6、系统应用只允许安装在/data分区。
7、是否强制安装到外置存储。
8、哪个磁盘剩余空间最大。

        在分析代码之前先说明下,这里internal分区代表/data 目录所对应的分区。它对应的volume.id为ID_PRIVATE_INTERNAL,类型是VolumeInfo.TYPE_PRIVATE, 其他的类型为VolumeInfo.TYPE_PRIVATE的分区作为内置分区的扩展,被全盘加密后挂载到/mnt/expand/${volumeId}下面, 所以应用只能安装到VolumeInfo.TYPE_PRIVATE分区下面(早期Android应用可以安装到PUBLIC类型分区)。剩下的请参上面代码我添加的注释理解。到这里只参考了params.installLocation 的PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY标志。

        回到createSessionInternal函数中。params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);这行代码没有抛出异常则选好了合适的卷。没有合适的安装位置则抛出异常,安装过程结束, createSessionInternal后面的流程有如下代码:

private int createSessionInternal(SessionParams params, String installerPackageName, int userId) {
        ......
        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, "");

      ......
}

        buildSessionDir 这行代码会在选定的分区下创建临时目录,用于将apk文件拷贝过来,我们展开来看buildSessionDir函数:

    private File buildSessionDir(int sessionId, SessionParams params) {
        if (params.isStaged) {
            final File sessionStagingDir = Environment.getDataStagingDirectory(params.volumeUuid);
            return new File(sessionStagingDir, "session_" + sessionId);
        }
        return buildTmpSessionDir(sessionId, params.volumeUuid);
    }
    private File buildTmpSessionDir(int sessionId, String volumeUuid) {
        final File sessionStagingDir = getTmpSessionDir(volumeUuid);
        return new File(sessionStagingDir, "vmdl" + sessionId + ".tmp");
    }
    private File getTmpSessionDir(String volumeUuid) {
        return Environment.getDataAppDirectory(volumeUuid);
    }
   public static File getDataAppDirectory(String volumeUuid) {
        return new File(getDataDirectory(volumeUuid), "app");
    }
    public static File getDataDirectory(String volumeUuid) {
        if (TextUtils.isEmpty(volumeUuid)) {
            return DIR_ANDROID_DATA;
        } else {
            return new File("/mnt/expand/" + volumeUuid);
        }
    }

        buildSessionDir 函数有两个分支,当params.isStaged为真的时候表示下次启动的时候才真正安装这个应用,一般用于apex的安装。我们不关系它。所以展开buildTmpSessionDir函数,其实stageDir是在/data/app/app/vmdl${serssionId}.tmp或者/mnt/expand/${volumeId}/app/vmdl${serssionId}.tmp。

public void open() throws IOException {
       ......
            if (!mPrepared) {
                if (stageDir != null) {
                    prepareStageDir(stageDir);
                } else if (params.isMultiPackage) {
                    // it's all ok
                } else {
                    throw new IllegalArgumentException("stageDir must be set");
                }

                mPrepared = true;
            }
        }
        ......
    }

        在打开session的时候会调用prepareStageDir(stageDir)来创建stageDir。后面应用商店程序或者adb程序会调用两个个可能的函数将安装到拷贝到stageDir文件夹下面。这里个函数我们就不分析了,他们分别是:

public void write(String name, long offsetBytes, long lengthBytes,
            ParcelFileDescriptor fd) 
 public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes)  

        前者是外部传进来一个ParcelFileDescriptor,从里面读内容写到${stageDir}/PackageInstaller 文件,后者是打开${stageDir}/PackageInstaller文件,交由客户端去写。 ${stageDir}/PackageInstaller文件实际就是apk文件。后面的安装过程都是对该文件进行处理。安装完成后会将${stageDir}文件夹更名为应用报名, ${stageDir}/PackageInstaller会编程base.apk, 这其实就是我们比较常见的/data/app/com.xxx/ 文件夹,当然也可能是/mnt/expand/${volumeId}/app/com.xxxx。

        makeSessionActiveLocked() 会选择abi进行解压到lib文件夹。
        validateApkInstallLocked(@Nullable PackageInfo pkgInfo) 改名为base.apk。

/******************************被架空的部分开始********************************/
        下面关于安装位置的确定就是在安装应用前的拷贝工作,这里面的带部分逻辑基本已经被前面的创建session时确定的位置架空。这里不详细分析,只是通过注释的方式写出。

 /*
         * Invoke remote method to get package information and install
         * location values. Override install location based on default
         * policy if needed and then create install arguments based
         * on the install location.
         */
        public void handleStartCopy() {
            int ret = PackageManager.INSTALL_SUCCEEDED;

           // 1 表示前边已经确定好了安装目录(\${stageDir}/PackageInstaller),确定文件是否存在,设置INSTALL_INTERNAL标志(double check)。
            // If we're already staged, we've firmly committed to an install location
            if (origin.staged) {
                if (origin.file != null) {
                    installFlags |= PackageManager.INSTALL_INTERNAL;
                } else {
                    throw new IllegalStateException("Invalid stage location");
                }
            }

            ......
            // 2 简单的解析下安装包的AndroidManifest.xml文件,这里面会调用PackageHelper.resolveInstallVolume(mContext, params)函数确定推荐的安装位置。
            pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
                    origin.resolvedPath, installFlags, packageAbiOverride);

            if (DEBUG_INSTANT && ephemeral) {
                Slog.v(TAG, "pkgLite for install: " + pkgLite);
            }

            /*
             * If we have too little free space, try to free cache
             * before giving up.
             */
             // 3 空间不足使用mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);释放一些空间,
             // 再次调用PackageManagerServiceUtils.getMinimalPackageInfo(
             //  mContext,origin.resolvedPath, installFlags, packageAbiOverride)
             // 注意3 这种情况并没有使用PackageInstallerSession将安装文件拷贝到临时目录,如果已经拷贝,则并不需要关心空间问题
            if (!origin.staged && pkgLite.recommendedInstallLocation
                    == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
                // TODO: focus freeing disk space on the target device
                final StorageManager storage = StorageManager.from(mContext);
                final long lowThreshold = storage.getStorageLowBytes(
                        Environment.getDataDirectory());

                final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(
                        origin.resolvedPath, packageAbiOverride);
                if (sizeBytes >= 0) {
                    try {
                        mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
                        pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
                                origin.resolvedPath, installFlags, packageAbiOverride);
                    } catch (InstallerException e) {
                        Slog.w(TAG, "Failed to free cache", e);
                    }
                }

                /*
                 * The cache free must have deleted the file we downloaded to install.
                 *
                 * TODO: fix the "freeCache" call to not delete the file we care about.
                 */
                 // 3.1 freeCache把安装包删了, 设置PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE表示是由于空间问题安装失败的。
                if (pkgLite.recommendedInstallLocation
                        == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
                    pkgLite.recommendedInstallLocation
                            = PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
                }
            }


            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                // 4.1 错误情况处理
                int loc = pkgLite.recommendedInstallLocation;
                if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
                    ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
                    ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_APK;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_URI;
                } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
                    ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
                } else {
                    // 4.2 前面都处理成功了,看下是否还需要更换安装位置
                    // Override with defaults if needed.
                    loc = installLocationPolicy(pkgLite);
                    if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {
                        ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
                    } else if (loc == PackageHelper.RECOMMEND_FAILED_WRONG_INSTALLED_VERSION) {
                        ret = PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
                    } else if (!onInt) {
                        // Override install location with flags
                        if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
                            // Set the flag to install on external media.
                            installFlags &= ~PackageManager.INSTALL_INTERNAL;
                        } else if (loc == PackageHelper.RECOMMEND_INSTALL_EPHEMERAL) {
                            if (DEBUG_INSTANT) {
                                Slog.v(TAG, "...setting INSTALL_EPHEMERAL install flag");
                            }
                            installFlags |= PackageManager.INSTALL_INSTANT_APP;
                            installFlags &= ~PackageManager.INSTALL_INTERNAL;
                        } else {
                            // Make sure the flag for installing on external
                            // media is unset
                            installFlags |= PackageManager.INSTALL_INTERNAL;
                        }
                    }
                }
            }
            ......
     }

/**
     * Parse given package and return minimal details.
     */
    public static PackageInfoLite getMinimalPackageInfo(Context context, String packagePath,
            int flags, String abiOverride) {
        final PackageInfoLite ret = new PackageInfoLite();
        // 1 apk 路径为空返回 PackageHelper.RECOMMEND_FAILED_INVALID_APK
        if (packagePath == null) { 
            Slog.i(TAG, "Invalid package file " + packagePath);
            ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
            return ret;
        }

        final File packageFile = new File(packagePath);
        final PackageParser.PackageLite pkg;
        final long sizeBytes;
        try {
            pkg = PackageParser.parsePackageLite(packageFile, 0);
            sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
        } catch (PackageParserException | IOException e) {
            Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
            if (!packageFile.exists()) {
                // 2 apk文件不存在返回PackageHelper.RECOMMEND_FAILED_INVALID_URI
                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
            } else {
                //3 解析失败返回 PackageHelper.RECOMMEND_FAILED_INVALID_APK
                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
            }

            return ret;
        }
        // 4 获取推荐安装位置
        final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
                pkg.packageName, pkg.installLocation, sizeBytes, flags);

        ......
        ret.recommendedInstallLocation = recommendedInstallLocation;
        ret.multiArch = pkg.multiArch;

        return ret;
    }

看注释

    @Deprecated
    public static int resolveInstallLocation(Context context, String packageName,
            int installLocation, long sizeBytes, int installFlags) {
        final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
        params.appPackageName = packageName;
        params.installLocation = installLocation;
        params.sizeBytes = sizeBytes;
        params.installFlags = installFlags;
        try {
            return resolveInstallLocation(context, params);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

出于空间考量

/**
     * Given a requested {@link PackageInfo#installLocation} and calculated
     * install size, pick the actual location to install the app.
     */
    public static int resolveInstallLocation(Context context, SessionParams params)
            throws IOException {
        ApplicationInfo existingInfo = null;
        try {
            existingInfo = context.getPackageManager().getApplicationInfo(params.appPackageName,
                    PackageManager.MATCH_ANY_USER);
        } catch (NameNotFoundException ignored) {
        }
        // 最合适的安装路径
        final int prefer;
        // 是否需要检查主存储和/data分区
        final boolean checkBoth;
        // 是否是短暂安装
        boolean ephemeral = false;
        if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
            // 1 INSTALL_INSTANT_APP 推荐安装在/data分区,instant app为短暂安装的app, 不需要检查主存储
            prefer = RECOMMEND_INSTALL_INTERNAL;
            ephemeral = true;
            checkBoth = false;
        } else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
            // 2 PackageManager.INSTALL_INTERNAL 标志表示安装在/data分区,不需要检查主存储。
            prefer = RECOMMEND_INSTALL_INTERNAL;
            checkBoth = false;
        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
            // 3 只允许安装在/data分区,不需要检查主存储
            prefer = RECOMMEND_INSTALL_INTERNAL;
            checkBoth = false;
        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
            // 4 推荐安装到主存储上(隐含表示主存储和/data分区都可以安装),需要检查/data分区和主存储
            prefer = RECOMMEND_INSTALL_EXTERNAL;
            checkBoth = true;
        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
            // 5 自动选择安装分区,默认安装到/data分区,只有之前就安装在主存储,现在覆盖安装才推荐安装到外置存储。需要检查内置存储和主存储。隐含表示主存储和/data分区都可以安装)
            // When app is already installed, prefer same medium
            if (existingInfo != null) {
                // TODO: distinguish if this is external ASEC
                if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
                    prefer = RECOMMEND_INSTALL_EXTERNAL;
                } else {
                    prefer = RECOMMEND_INSTALL_INTERNAL;
                }
            } else {
                prefer = RECOMMEND_INSTALL_INTERNAL;
            }
            checkBoth = true;
        } else {
            // 6 未指定installLocation ,默认安装到内置存储。
            prefer = RECOMMEND_INSTALL_INTERNAL;
            checkBoth = false;
        }

        boolean fitsOnInternal = false;
        // 7 检查internal分区空间是否充足
        if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
            fitsOnInternal = fitsOnInternal(context, params);
        }
        // 8 检查主存储空间是否充足
        boolean fitsOnExternal = false;
        if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
            fitsOnExternal = fitsOnExternal(context, params);
        }

        if (prefer == RECOMMEND_INSTALL_INTERNAL) { // 9 推荐安装到/data分区,如果data分区足够则安装到/data分区
            // The ephemeral case will either fit and return EPHEMERAL, or will not fit
            // and will fall through to return INSUFFICIENT_STORAGE
            if (fitsOnInternal) {
                return (ephemeral)
                        ? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL
                        : PackageHelper.RECOMMEND_INSTALL_INTERNAL;
            }
        } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {// 9 推荐安装到主存储,如果主存储足够则安装到主存储
            if (fitsOnExternal) {
                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
            }
        }

        if (checkBoth) { // 10 推荐位置空间没有满足,并且可以安装到主存储和/data分区,则找一个空间满足的分区作为安装分区
            if (fitsOnInternal) {
                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
            } else if (fitsOnExternal) {
                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
            }
        }
        // 11 没有找到合适的安装分区,则返回失败
        return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
    }

这里并没有考虑其他Private 卷。只允许安装在/data分区和主存储。

出于之前安装的包考量

        private int installLocationPolicy(PackageInfoLite pkgLite) {
            String packageName = pkgLite.packageName;
            int installLocation = pkgLite.installLocation;
            // reader
            synchronized (mPackages) {
                // Currently installed package which the new package is attempting to replace or
                // null if no such package is installed.
                PackageParser.Package installedPkg = mPackages.get(packageName);
                // Package which currently owns the data which the new package will own if installed.
                // If an app is unstalled while keeping data (e.g., adb uninstall -k), installedPkg
                // will be null whereas dataOwnerPkg will contain information about the package
                // which was uninstalled while keeping its data.
                PackageParser.Package dataOwnerPkg = installedPkg;
                if (dataOwnerPkg  == null) {
                    PackageSetting ps = mSettings.mPackages.get(packageName);
                    if (ps != null) {
                        dataOwnerPkg = ps.pkg;
                    }
                }
                // 1 安装版本错误
                if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
                    if (dataOwnerPkg == null) {
                        Slog.w(TAG, "Required installed version code was "
                                + requiredInstalledVersionCode
                                + " but package is not installed");
                        return PackageHelper.RECOMMEND_FAILED_WRONG_INSTALLED_VERSION;
                    }

                    if (dataOwnerPkg.getLongVersionCode() != requiredInstalledVersionCode) {
                        Slog.w(TAG, "Required installed version code was "
                                + requiredInstalledVersionCode
                                + " but actual installed version is "
                                + dataOwnerPkg.getLongVersionCode());
                        return PackageHelper.RECOMMEND_FAILED_WRONG_INSTALLED_VERSION;
                    }
                }

                if (dataOwnerPkg != null) {
                    if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
                            dataOwnerPkg.applicationInfo.flags)) {
                        try {
                            checkDowngrade(dataOwnerPkg, pkgLite);
                        } catch (PackageManagerException e) {
                            Slog.w(TAG, "Downgrade detected: " + e.getMessage());
                            return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE;
                        }
                    }
                }

                if (installedPkg != null) {
                    // 2 覆盖安装情况
                    if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
                        // Check for updated system application.
                        //2.1 系统应用只允许安装在 /data分区
                        if ((installedPkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
                        } else {
                             // 2.2  升级版本只允许安装在内置存储,返回推荐安装到内置存储。
                            // If current upgrade specifies particular preference
                            if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
                                // Application explicitly specified internal.
                                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
                           // 2.3 推荐安装在外置存储,使用之前计算到的分区。
                            } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
                                // App explictly prefers external. Let policy decide
                            } else {
                                //2.4 之前安装到了外置存储,继续使用爱之存储,否则使用内置存储。
                                // Prefer previous location
                                if (isExternal(installedPkg)) {
                                    return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
                                }
                                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
                            }
                        }
                    } else {
                        // Invalid install. Return error code
                        return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
                    }
                }
            }
            // 2.5 使用前面计算的推荐位置
            return pkgLite.recommendedInstallLocation;
        }


/******************************被架空的部分结束********************************/

        PackageManagerService中真正执行拷贝的地方为doCopyApk()函数。
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

        private int doCopyApk() {
            if (origin.staged) { // 前边计算了一大通,最终还是以origin.staged为准
                if (DEBUG_INSTALL) Slog.d(TAG, origin.file + " already staged; skipping copy");
                codeFile = origin.file;
                resourceFile = origin.file;
                return PackageManager.INSTALL_SUCCEEDED;
            }

            try {// 2 instant app 和external 计算安装位置并拷贝
                final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
                final File tempDir =
                        mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
                codeFile = tempDir;
                resourceFile = tempDir;
            } catch (IOException e) {
                Slog.w(TAG, "Failed to create copy file: " + e);
                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
            }

            int ret = PackageManagerServiceUtils.copyPackage(
                    origin.file.getAbsolutePath(), codeFile);
            if (ret != PackageManager.INSTALL_SUCCEEDED) {
                Slog.e(TAG, "Failed to copy package");
                return ret;
            }
            //3 拷贝lib目录
            final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
            NativeLibraryHelper.Handle handle = null;
            try {
                handle = NativeLibraryHelper.Handle.create(codeFile);
                ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
                        abiOverride);
            } catch (IOException e) {
                Slog.e(TAG, "Copying native libraries failed", e);
                ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
            } finally {
                IoUtils.closeQuietly(handle);
            }

            return ret;
        }


        这里已经使用PackageInstallerSession将apk拷贝到了正确的位置,origin.staged就是对应的目录,所以这里直接返回拷贝成功。

 @GuardedBy("mInstallLock")
    private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
       ......
       if (!args.doRename(res.returnCode, pkg)) {
            throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
       }
       ......
   }

```java
class FileInstallArgs extends InstallArgs 

boolean doRename(int status, PackageParser.Package pkg) {
            if (status != PackageManager.INSTALL_SUCCEEDED) {
                cleanUp();
                return false;
            }

            final File targetDir = codeFile.getParentFile();
            final File beforeCodeFile = codeFile;
            final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName);

            if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
            try {
                Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
            } catch (ErrnoException e) {
                Slog.w(TAG, "Failed to rename", e);
                return false;
            }

            if (!SELinux.restoreconRecursive(afterCodeFile)) {
                Slog.w(TAG, "Failed to restorecon");
                return false;
            }

            // Reflect the rename internally
            codeFile = afterCodeFile;
            resourceFile = afterCodeFile;

            // Reflect the rename in scanned details
            try {
                pkg.setCodePath(afterCodeFile.getCanonicalPath());
            } catch (IOException e) {
                Slog.e(TAG, "Failed to get path: " + afterCodeFile, e);
                return false;
            }
            pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile,
                    afterCodeFile, pkg.baseCodePath));
            pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
                    afterCodeFile, pkg.splitCodePaths));

            // Reflect the rename in app info
            pkg.setApplicationVolumeUuid(pkg.volumeUuid);
            pkg.setApplicationInfoCodePath(pkg.codePath);
            pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
            pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
            pkg.setApplicationInfoResourcePath(pkg.codePath);
            pkg.setApplicationInfoBaseResourcePath(pkg.baseCodePath);
            pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);

            return true;
        }


        后面在安装之前preparePackageLI函数中将staged目录改名为应用包名,至此应用的安装位置就确定了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值