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

如图所示:

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

  • 缓存:指的是:/data/data/${packageName}/cache/ +/sdcard/Android/${packageName}/cache/ 的占用大小

  • 用户数据:这个数据不同的手机表现不一样,上述截图是google pixel手机的显示, 此处的数据是计算是:

    /data/data/${packageName}/ + /sdcard/Android/${packageName} - 缓存目录大小

    用户数据部分,很多国产手机的计算方式也是不一样的,例如华为手机:数据部分是没有减去缓存目录大小的,只是单纯的/data/data/${packageName}/ + /sdcard/Android/${packageName}之和。

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

    了解以上内容后, 我们如何在运行时获取应用占用磁盘大小呢?

    8.0以下设备获取方式

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * 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);

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

1)、在src目录下新建android.content.pm包

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

1
2
3
package android.content.pm;

parcelable PackageStats;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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受限的方法,所以必须通过反射实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 获取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:

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

8.0以上设备获取方式

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
* 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;
//此权限只能用户手动去设置里打开,故如有需要可以直接跳到设置界面
// try {
// Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
// intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
// context.startActivity(intent);
// } catch (Throwable e) {
// e.printStackTrace();
// }
//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;
}
}