美文网首页
应用安装策略之可用存储空间阈值

应用安装策略之可用存储空间阈值

作者: 里蛰肤 | 来源:发表于2018-09-21 14:18 被阅读0次

应用安装策略之可用存储空间阈值

对于从来没有深入看过源码的我来说,峰哥真的给我安排了一个棘手且完全不熟悉的任务。但是我也只好硬着头皮往下做,期间参考了网上的一些文章,启硕哥也指点了我不少,最终还是找到了规定的阈值。

期间代码不需要全看,只看画出来的重点部分即可。贴出源码是为了方便查漏补缺。

  • 参考打包安装程序

    • 发现基本都是调用android.content.pm.PackageManager
  • 网上查询各种安装方式

    • 通过shell命令的pm install <packgename>

    • 通过调用android.content.pm.PackageManager

  • 以上两种方式发现无论怎么样最终都会调用 installer.java

    • installer.java 最终回到PackageManagerService

    • 在满负荷调试中出现 INSTALL_FAILED_INSUFFICIENT_STORAGE

      • 通过AndroidXRef 查询结果定位到PackageManagerService.java
  • 根据上述两步跟踪问题,找到 PackageManagerService.java

frameworks\base\services\java\com\android\server\pm\PackageManagerService.java

  • 找到 InstallParams

    • 这一整段基本就是安装所需的各个流程,但是我们只看其中和存储空间相关的策略

class InstallParams extends HandlerParams {

        //......

        private InstallArgs mArgs;

        private int mRet;

        private File mTempPackage;

        //.......

        private int installLocationPolicy(PackageInfoLite pkgLite, int flags) {

            String packageName = pkgLite.packageName;

            int installLocation = pkgLite.installLocation;

            boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;

            // reader

            synchronized (mPackages) {

                PackageParser.Package pkg = mPackages.get(packageName);

                if (pkg != null) {

                    if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {

                        // Check for downgrading.

                        if ((flags & PackageManager.INSTALL_ALLOW_DOWNGRADE) == 0) {

                            if (pkgLite.versionCode < pkg.mVersionCode) {

                                Slog.w(TAG, "Can't install update of " + packageName

                                        + " update version " + pkgLite.versionCode

                                        + " is older than installed version "

                                        + pkg.mVersionCode);

                                return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE;

                            }

                        }

                        // Check for updated system application.

                        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {

                            if (onSd) {

                                Slog.w(TAG, "Cannot install update to system app on sdcard");

                                return PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION;

                            }

                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;

                        } else {

                            if (onSd) {

                                // Install flag overrides everything.

                                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;

                            }

                            // If current upgrade specifies particular preference

                            if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {

                                // Application explicitly specified internal.

                                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;

                            } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {

                                // App explictly prefers external. Let policy decide

                            } else {

                                // Prefer previous location

                                if (isExternal(pkg)) {

                                    return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;

                                }

                                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;

                            }

                        }

                    } else {

                        // Invalid install. Return error code

                        return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;

                    }

                }

            }

            // All the special cases have been taken care of.

            // Return result based on recommended install location.

            if (onSd) {

                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;

            }

            return pkgLite.recommendedInstallLocation;

        }

        /*

        * 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() throws RemoteException {

            int ret = PackageManager.INSTALL_SUCCEEDED;

            final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;

            final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;

            PackageInfoLite pkgLite = null;

            if (onInt && onSd) {

                // Check if both bits are set.

                Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");

                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;

            } else {

                final long lowThreshold;

                final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager

                        .getService(DeviceStorageMonitorService.SERVICE);

                if (dsm == null) {

                    Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");

                    lowThreshold = 0L;

                } else {

                    lowThreshold = dsm.getMemoryLowThreshold();

                }

                try {

                    //......

                    if (packageFile != null) {

                        // Remote call to find out default install location

                        final String packageFilePath = packageFile.getAbsolutePath();

                        pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,

                                lowThreshold);

                        /*

                        * If we have too little free space, try to free cache

                        * before giving up.

                        */

                        if (pkgLite.recommendedInstallLocation

                                == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {

                            final long size = mContainerService.calculateInstalledSize(

                                    packageFilePath, isForwardLocked());

                            if (mInstaller.freeCache(size + lowThreshold) >= 0) {

                                pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath,

                                        flags, lowThreshold);

                            }

                            /*

                            * 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.

                            */

                            if (pkgLite.recommendedInstallLocation

                                    == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {

                                pkgLite.recommendedInstallLocation

                                    = PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;

                            }

                        }

                    }

                } finally {

                    mContext.revokeUriPermission(mPackageURI,

                            Intent.FLAG_GRANT_READ_URI_PERMISSION);

                }

            }

            if (ret == PackageManager.INSTALL_SUCCEEDED) {

                int loc = pkgLite.recommendedInstallLocation;

                if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {

                    //......

                } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {

                    ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;

                } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {

                    //......

                }

            }

            final InstallArgs args = createInstallArgs(this);

            mArgs = args;

            //......

        }

        //......

    }

  • 找到重点 handleStartCopy 函数

    • 出现了前面提到的 INSTALL_FAILED_INSUFFICIENT_STORAGE

    public void handleStartCopy() throws RemoteException {

        int ret = PackageManager.INSTALL_SUCCEEDED;

        final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;

        final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;

        PackageInfoLite pkgLite = null;

        if (onInt && onSd) {

            // Check if both bits are set.

            Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");

            ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;

        } else {

            final long lowThreshold;

            final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager

                    .getService(DeviceStorageMonitorService.SERVICE);

            if (dsm == null) {

                Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");

                lowThreshold = 0L;

            } else {

                lowThreshold = dsm.getMemoryLowThreshold();

            }

            try {

                //......

                if (packageFile != null) {

                    // Remote call to find out default install location

                    final String packageFilePath = packageFile.getAbsolutePath();

                    pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,

                            lowThreshold);

                    /*

                        * If we have too little free space, try to free cache

                        * before giving up.

                        */

                    if (pkgLite.recommendedInstallLocation

                            == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {

                        final long size = mContainerService.calculateInstalledSize(

                                packageFilePath, isForwardLocked());

                        if (mInstaller.freeCache(size + lowThreshold) >= 0) {

                            pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath,

                                    flags, lowThreshold);

                        }

                        /*

                            * 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.

                            */

                        if (pkgLite.recommendedInstallLocation

                                == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {

                            pkgLite.recommendedInstallLocation

                                = PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;

                        }

                    }

                }

            } finally {

                mContext.revokeUriPermission(mPackageURI,

                        Intent.FLAG_GRANT_READ_URI_PERMISSION);

            }

        }

        if (ret == PackageManager.INSTALL_SUCCEEDED) {

            int loc = pkgLite.recommendedInstallLocation;

            if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {

                //......

            } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {

                ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;

            } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {

                //......

            }

        }

        final InstallArgs args = createInstallArgs(this);

        mArgs = args;

        //......

    }

  • 其中有获取最低存储空间的信息 lowThreshold

    • dsm是 DeviceStorageMonitorService 的实体

    final long lowThreshold;

    final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager.getService(DeviceStorageMonitorService.SERVICE);

    if (dsm == null) {

        Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");

        lowThreshold = 0L;

    } else {

        lowThreshold = dsm.getMemoryLowThreshold();

    }

  • 找到 DeviceStorageMonitorService

frameworks\base\services\java\com\android\server\DeviceStorageMonitorService.java

  • 找到 getMemoryLowThreshold


    public long getMemoryLowThreshold() {

        return mMemLowThreshold;

    }

  • 找到 mMemLowThreshold


    final StorageManager sm = StorageManager.from(context);

    mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);

    mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH);

    mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4;

    mMemCacheTrimToThreshold = mMemLowThreshold

            + ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2);

    mFreeMemAfterLastCacheClear = mTotalMemory;

    checkMemory(true);

  • 根据 checkMemory 确认 mMemLowThreshold 是我们要找的阈值目标


    private final void checkMemory(boolean checkCache) {

        //if the thread that was started to clear cache is still running do nothing till its

        //finished clearing cache. Ideally this flag could be modified by clearCache

        // and should be accessed via a lock but even if it does this test will fail now and

        //hopefully the next time this flag will be set to the correct value.

        if(mClearingCache) {

          //......

        } else {

            restatDataDir();

            if (localLOGV)  Slog.v(TAG, "freeMemory="+mFreeMem);

            //post intent to NotificationManager to display icon if necessary

            if (mFreeMem < mMemLowThreshold) {

                if (checkCache) {

                    // We are allowed to clear cache files at this point to

                    // try to get down below the limit, because this is not

                    // the initial call after a cache clear has been attempted.

                    // In this case we will try a cache clear if our free

                    // space has gone below the cache clear limit.

                    if (mFreeMem < mMemCacheStartTrimThreshold) {

                        // We only clear the cache if the free storage has changed

                        // a significant amount since the last time.

                        if ((mFreeMemAfterLastCacheClear-mFreeMem)

                                >= ((mMemLowThreshold-mMemCacheStartTrimThreshold)/4)) {

                            // See if clearing cache helps

                            // Note that clearing cache is asynchronous and so we do a

                            // memory check again once the cache has been cleared.

                            mThreadStartTime = System.currentTimeMillis();

                            mClearSucceeded = false;

                            clearCache();

                        }

                    }

                } else {

                    // This is a call from after clearing the cache.  Note

                    // the amount of free storage at this point.

                    mFreeMemAfterLastCacheClear = mFreeMem;

                    if (!mLowMemFlag) {

                        // We tried to clear the cache, but that didn't get us

                        // below the low storage limit.  Tell the user.

                        Slog.i(TAG, "Running low on memory. Sending notification");

                        sendNotification();

                        mLowMemFlag = true;

                    } else {

                        if (localLOGV) Slog.v(TAG, "Running low on memory " +

                                "notification already sent. do nothing");

                    }

                }

            } else {

                //......

        }

        if(localLOGV) Slog.i(TAG, "Posting Message again");

        //keep posting messages to itself periodically

        postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);

    }

  • 找到 StorageManager.java

\frameworks\base\core\java\android\os\storage\StorageManager.java

  • 找到 getStorageLowBytes 函数


    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;

    private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;

    private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;

    public long getStorageLowBytes(File path) {

        final long lowPercent = Settings.Global.getInt(mResolver,

                Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);

        final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;

        final long maxLowBytes = Settings.Global.getLong(mResolver,

                Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);

        return Math.min(lowBytes, maxLowBytes);

    }

  • 确认目标

    • 总存储空间的10%500M中较小的那一个
  • 找到系统判定空间可用空间较低的阈值后,根据之前的错误提示信息 INSTALL_FAILED_INSUFFICIENT_STORAGE ,定位问题


    if (ret == PackageManager.INSTALL_SUCCEEDED) {

        int loc = pkgLite.recommendedInstallLocation;

        if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {

            //......

        } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {

            ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;

        } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {

            //......

        }

    }

  • 再找到loc的声明

    • pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags, lowThreshold)

    • int loc = pkgLite.recommendedInstallLocation

    • 可以看出下一步需要找到 getMinimalPackageInfo 的声明

  • 经过一番查找,在 DefaultContainerService.java 中找到其声明

frameworks\base\packages\DefaultContainerService\src\com\android\defcontainer\DefaultContainerService.java


    public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,

            long threshold) {

        PackageInfoLite ret = new PackageInfoLite();

        //...

        ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,

                packagePath, flags, threshold);

        return ret;

    }

  • 找到 recommendedInstallLocation 的声明


    private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,

            long threshold) {

        int prefer;

        boolean checkBoth = false;

        final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;

        //......

        final boolean emulated = Environment.isExternalStorageEmulated();

        final File apkFile = new File(archiveFilePath);

        boolean fitsOnInternal = false;

        if (checkBoth || prefer == PREFER_INTERNAL) {

            try {

                fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold);

            } catch (IOException e) {

                return PackageHelper.RECOMMEND_FAILED_INVALID_URI;

            }

        }

        boolean fitsOnSd = false;

        if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {

            try {

                fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked);

            } catch (IOException e) {

                return PackageHelper.RECOMMEND_FAILED_INVALID_URI;

            }

        }

        if (prefer == PREFER_INTERNAL) {

            if (fitsOnInternal) {

                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;

            }

        } else if (!emulated && prefer == PREFER_EXTERNAL) {

            if (fitsOnSd) {

                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;

            }

        }

        if (checkBoth) {

            if (fitsOnInternal) {

                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;

            } else if (!emulated && fitsOnSd) {

                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;

            }

        }

        /*

            * If they requested to be on the external media by default, return that

            * the media was unavailable. Otherwise, indicate there was insufficient

            * storage space available.

            */

        if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)

                && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {

            return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;

        } else {

            return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;

        }

    }

  • loc的关键返回判断信息语句如下


    if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)

            && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {

        return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;

    } else {

        return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;

    }

  • 联系前文,依据判断条件,找到关键语句


fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold)

  • 寻找其声明 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;

    }

  • 从上面我们可以看出返回值为 可用空间—安装大小>最低阈值

结论

  • 我们终于做完了所有的事了,其结论就是:当满足 可用空间的大小 减去 安装包需要的大小 大于 最低阈值(500M或存储空间总大小的10%中的较小值) 的条件时,安装才能进行。

后记

为了找出这个规定的阈值,我查了不少文章,几乎把安装流程完全走了一遍,可以说工作量非常巨大了。并且一开始还成功地掉入了坑里,差点就把错误的结论拿出来讲了。后来想想看好像不太对劲,重新梳理了一遍逻辑才发现确实做错了。不过走这一遍,虽然不知道有什么用,但是相信也是积淀的一部分,希望在未来的日子里能够让我少踩一点坑。

以下为有帮助的工具和参考文章

AndroidXRef 查源码巨好用的网站

Android7.0 PackageManagerService

APK安装流程详解14——PMS中的新安装流程上(拷贝)补充

Android PackageManagerService分析二:安装APK

APK安装流程源码追踪

androiod 学习--PMS应用安装过程

相关文章

网友评论

      本文标题:应用安装策略之可用存储空间阈值

      本文链接:https://www.haomeiwen.com/subject/ezbanftx.html