apk installLocation设置为preferExternal后安装失败

    客户反馈把apk installLocation设置为preferExternal后,如果sdcard路径已满,不会自动安装到内部data分区,而是安装失败。

    installLocation是apk用来指定安装位置的属性,设置为preferExternal表示程序优先安装到sdcard中,如果sdcard容量不足,则安装到data分区中。而在系统中,真正用来指定安装位置的变量是recommendedInstallLocation。recommendedInstallLocation的值就是recommendAppInstallLocation函数的返回值。

public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
                long threshold) {
            ...

            ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
                    packagePath, flags, threshold);

            return ret;
        }

    在recommendAppInstallLocation函数中对data和sdcard两个安装分区的可用容量进行判断。首先是对data分区的判断

boolean fitsOnInternal = false;
        if (checkBoth || prefer == PREFER_INTERNAL) {
            try {
                fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold);
            } catch (IOException e) {
                return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
            }
        }
    fitsOnInternal就是data分区是否可用的标志,isUnderInternalThreshold函数中进行具体的可用容量判断

private boolean isUnderInternalThreshold(File apkFile, boolean isForwardLocked, long threshold)
            throws IOException {
        long size = apkFile.length();
        if (size == 0 && !apkFile.exists()) {
            throw new FileNotFoundException();
        }

        if (isForwardLocked) {
            size += PackageHelper.extractPublicFiles(apkFile.getAbsolutePath(), null);
        }

        final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
        final long availInternalSize = (long) internalStats.getAvailableBlocks()
                * (long) internalStats.getBlockSize();

        return (availInternalSize - size) > threshold;
    }

    然后对sdcard的可用容量进行判断

boolean fitsOnSd = false;
        if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {
            try {
                fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked);
            } catch (IOException e) {
                return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
            }
        }

    跟data分区的步骤大同小异,fitsOnSd为sdcard分区是否可用的标志。isUnderExternalThreshold函数进行具体的判断

private boolean isUnderExternalThreshold(File apkFile, boolean isForwardLocked)
            throws IOException {
        if (Environment.isExternalStorageEmulated()) {
            return false;
        }

        final int sizeMb = calculateContainerSize(apkFile, isForwardLocked);

        final int availSdMb;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            final StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
            final int blocksToMb = (1 << 20) / sdStats.getBlockSize();
            availSdMb = sdStats.getAvailableBlocks() * blocksToMb;
        } else {
            availSdMb = -1;
        }

        return availSdMb > sizeMb;
    }

    问题就是出在上面的isUnderExternalThreshold函数中,这个函数的步骤为如下

1、sizeMb为要安装的apk文件的本身大小和依赖的lib库大小之和,其值最小为2

private int calculateContainerSize(File apkFile, boolean forwardLocked) throws IOException {
        // Calculate size of container needed to hold base APK.
        long sizeBytes = apkFile.length();
        if (sizeBytes == 0 && !apkFile.exists()) {
            throw new FileNotFoundException();
        }

        // Check all the native files that need to be copied and add that to the
        // container size.
        sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(apkFile);

        if (forwardLocked) {
            sizeBytes += PackageHelper.extractPublicFiles(apkFile.getPath(), null);
        }

        int sizeMb = (int) (sizeBytes >> 20);
        if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) {
            sizeMb++;
        }

        /*
         * Add buffer size because we don't have a good way to determine the
         * real FAT size. Your FAT size varies with how many directory entries
         * you need, how big the whole filesystem is, and other such headaches.
         */
        sizeMb++;

        return sizeMb;
    }

2、availSdMb为sdcard分区所剩余的可用空间大小,这里的计算有点问题

final StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
            final int blocksToMb = (1 << 20) / sdStats.getBlockSize();
            availSdMb = sdStats.getAvailableBlocks() * blocksToMb;
blocksToMb表示1mb有多少块,sdStats.getAvailableBlocks()返回sdcard的可用块的数目,所以正确的availSdMb计算公式应为sdStats.getAvailableBlocks()/blocksToMb

所以修改下这里的代码为

final int blocksToMb = (1 << 20) / sdStats.getBlockSize();
            availSdMb = sdStats.getAvailableBlocks() / blocksToMb;
就ok了。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值