計算Android App佔用的各種空間大小

一個小需求:計算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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章