apk installLocationがpreferExternalに設定されてインストールに失敗しました

5178 ワード

お客様のフィードバックで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の2つのインストールパーティションの使用可能な容量を判断します.まず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は1 mbが何ブロックあるかを示し、sdStats.getavailableBlocks()はsdcardの利用可能なブロックの数を返すので、正しいavailSdMb計算式はsdStatsであるべきである.getAvailableBlocks()/blocksToMb
ここのコードを修正すると
final int blocksToMb = (1 << 20) / sdStats.getBlockSize();
            availSdMb = sdStats.getAvailableBlocks() / blocksToMb;
でOKです.