不自重者,取辱。不自长者,取祸。不自满者,受益。不自足者,博闻。
计算Android App占用的各种空间大小 进入全屏
line

一个小需求:计算Android App所占用d的手机内存(RAM)大小、App所产生的数据(Data)大小、App本身所占用的磁盘空间(ROM)大小。当然,这个就必须用到PackageManager了。

1、查看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);

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

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

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

package android.content.pm;
  
parcelable PackageStats;

3)、在该包下新建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(), new Object[] {
            pkgName,
            new IPackageStatsObserver.Stub() {
                @Override
                public void onGetStatsCompleted(PackageStats pStats, boolean succeeded) throws RemoteException {
                    // 子线程中默认无法处理消息循环,自然也就不能显示Toast,所以需要手动Looper一下
                    Looper.prepare();
                    // 从pStats中提取各个所需数据
                    Toast.makeText(context,
                            "缓存大小=" + Formatter.formatFileSize(context, pStats.cacheSize) +
                            "\n数据大小=" + Formatter.formatFileSize(context, pStats.dataSize) +
                            "\n程序大小=" + Formatter.formatFileSize(context, pStats.codeSize),
                            Toast.LENGTH_LONG).show();
                    // 遍历一次消息队列,弹出Toast
                    Looper.loop();
                }
            }
    });
}

我是直接在Observer回调中通过Toast的方式直接显示出来的,不过这个回调是在子线程中异步完成的,子线程中默认不处理消息循环,所以Toast.show无法被正确执行,需要手动将Toast显示部分的内容,加到Looper中,分别调用Looper.prepare和Looper.loop方法即可!

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

<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"></uses-permission>
趣店(原趣分期)技术学院
重点关注技术架构、服务化、优秀工具、自动化平台、开发全流程一体化解决方案、新人培养、工程师进阶之道等方面
这里环境优雅、氛围年轻、主要是福利还多,还等什么?我们敞开技术的大门,欢迎各种工程师加入!

评论区域

line