Android获取应用磁盘空间占用

一、应用详情数据含义

Android下如何获取应用所占用的磁盘空间呢? 带着这个疑问我们先看一下应用详细信息里的各项数据都代表什么含义。
在这里插入图片描述
如图所示:

  • 应用大小: 值得是apk解压后,所占用的磁盘空间

  • 缓存:指的是:/data/data/ p a c k a g e N a m e / c a c h e / + / s d c a r d / A n d r o i d / {packageName}/cache/ +/sdcard/Android/ packageName/cache/+/sdcard/Android/{packageName}/cache/ 的占用大小

  • 用户数据:这个数据不同的手机表现不一样,上述截图是google pixel手机的显示, 此处的数据是计算是:/data/data/ p a c k a g e N a m e / + / s d c a r d / A n d r o i d / {packageName}/ + /sdcard/Android/ packageName/+/sdcard/Android/{packageName} - 缓存目录大小

  • 用户数据部分,很多国产手机的计算方式也是不一样的,例如华为手机:数据部分是没有减去缓存目录大小的,只是单纯的/data/data/ p a c k a g e N a m e / + / s d c a r d / A n d r o i d / {packageName}/ + /sdcard/Android/ packageName/+/sdcard/Android/{packageName}之和。

  • 总计: 总计部分,不同手机和厂商有会有一定的差异性,google原生系统显示的是:应用大小+用户数据+缓存;然而有些国产手机显示的是:应用大小+数据-缓存 比如华为手机

二、获取应用占用磁盘大小代码

1、8.0以下设备获取方式

查看Android中PackageManager源码,找到getPackageSizeInfo方法:

/**
 * Retrieve the size information for a package.
 * Since this may take a little while, the result will
 * be posted back to the given observer.  The calling context
 * should have the {@link android.Manifest.permission#GET_PACKAGE_SIZE} permission.
 *
 * @param packageName The name of the package whose size information is to be retrieved
 * @param observer An observer callback to get notified when the operation
 * is complete.
 * {@link android.content.pm.IPackageStatsObserver#onGetStatsCompleted(PackageStats, boolean)}
 * The observer's callback is invoked with a PackageStats object(containing the
 * code, data and cache sizes of the package) and a boolean value representing
 * the status of the operation. observer may be null to indicate that
 * no callback is desired.
 *
 * @hide
 */
public abstract void getPackageSizeInfo(String packageName,IPackageStatsObserver observer);

getPackageSizeInfo方法有两个参数,第一个是需要计算的App包名,第二个是一个回调。不过IPackageStatesObserver这个class在API里貌似找不到,找了点儿资料,需要通过Android AIDL的方式来搞。

(1)、在该包下新建PackageStats.aidl文件,内容如下:

package android.content.pm;
parcelable PackageStats;

(2)、在该包下新建IPackageStatsObserver.aidl接口文件,内容如下:

package android.content.pm;

import android.content.pm.PackageStats;
/**
 * API for package data change related callbacks from the Package Manager.
 * Some usage scenarios include deletion of cache directory, generate
 * statistics related to code, data, cache usage(TODO)
 * {@hide}
 */
oneway interface IPackageStatsObserver {
    void onGetStatsCompleted(in PackageStats pStats, boolean succeeded);
}

(3)、getPackageSizeInfo方法不能通过context.getPackageManager.getPackageSizeInfo的方式来调用,因为它其实是一个invoke受限的方法,所以必须通过反射实现:

/**
 * 获取Android Native App的缓存大小、数据大小、应用程序大小
 *
 * @param context
 *            Context对象
 * @param pkgName
 *            需要检测的Native App包名
 * @throws NoSuchMethodException
 * @throws InvocationTargetException
 * @throws IllegalAccessException
 */
public static void getPkgSize(final Context context, String pkgName) throws NoSuchMethodException,
        InvocationTargetException,
        IllegalAccessException {
    // getPackageSizeInfo是PackageManager中的一个private方法,所以需要通过反射的机制来调用
    Method method = PackageManager.class.getMethod("getPackageSizeInfo",
            new Class[] { String.class, IPackageStatsObserver.class });
    // 调用 getPackageSizeInfo 方法,需要两个参数:1、需要检测的应用包名;2、回调
    method.invoke(context.getPackageManager(), 
            pkgName, new IPackageStatsObserver.Stub() {
                @Override
                public void onGetStatsCompleted(PackageStats pStats, boolean succeeded) throws RemoteException {
                    // 从pStats中提取各个所需数据
                    Log.i(TAG, "缓存大小=" + Formatter.formatFileSize(context, pStats.cacheSize));
                    Log.i(TAG, "数据大小=" + Formatter.formatFileSize(context, pStats.dataSize));
                    Log.i(TAG, "程序大小=" + Formatter.formatFileSize(context, pStats.codeSize));
                }
            });
    }

(4)、根据PackageManager中getPackageSizeInfo注释中的提示,还需要在AndroidManifest.xml中加入permission:

<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />

2、8.0以上设备获取方式

8.0以上设备提供了,更加简单的API, 但是有一点是需要注意的,apk运行时获取当前自己进程的磁盘占用是不需要权限的,如果想要获取其他进程的占用是需要让用户进入到指定设置界面进行授权的。

/**
 * API 26以上获取App磁盘空间占用
 */
@RequiresApi(api = Build.VERSION_CODES.O)
private static void getAppsizeTop26(Context context, String packageName, CommCallback<AppSpaceInfo> callback) {
    //26以上的获取方法
    //调用前需要检查权限, 查询自己apk的磁盘占用不需要申请权限,查询非自己需要申请权限
    //非自己的包名
    if (!packageName.equals(context.getPackageName()) && !checkPackageUsageStats(context)) {
        //检查权限(查询自己apk的磁盘占用不需要申请权限,查询非自己需要申请权限)
        callback.onFinished(false, null);
        return;
    }
    AppSpaceInfo result = null;
    boolean exception = false;
    try {
        StorageStatsManager storageStatsManager = (StorageStatsManager) context.getSystemService(Context.STORAGE_STATS_SERVICE);
        StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        File path = new File(context.getDataDir().getParent(), packageName);
        if (path.exists()) {
            try {
                UUID uuid = storageManager.getUuidForPath(path);
                int uid = context.getPackageManager().getApplicationInfo(packageName, PackageManager.GET_META_DATA).uid;
                StorageStats storageStats = storageStatsManager.queryStatsForUid(uuid, uid);
                if (storageStats != null) {
                    result = new AppSpaceInfo(storageStats.getAppBytes(), storageStats.getDataBytes(), storageStats. getCacheBytes());
                    result.pacakgeName = packageName;
                }
            } catch (IOException | PackageManager.NameNotFoundException  e) {
                e.printStackTrace();
                exception = true;
            }
        }
    } catch (Throwable e) {
        e.printStackTrace();
        exception = true;
    }
    callback.onFinished(!exception, result);
}

public static class AppSpaceInfo {
/**
    * 设置-应用信息里的"应用"占用
    */
   public long apkBytes;

   /**
    * 设置-应用信息里的"数据"占用
    */
   public long dataBytes;

   /**
    * 设置-应用信息里的"缓存"占用
    */
   public long cacheByte;

   public String pacakgeName;


   AppSpaceInfo(long apkBytes, long dataBytes, long cacheByte){
       this.apkBytes = apkBytes;
       this.dataBytes = dataBytes;
       this.cacheByte = cacheByte;
   }
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网小熊猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值