【Android 進階】一鍵清理
一鍵清理流程圖
系統快取分析
Android 已安裝 app /data/data/packagename/cache 資料夾和 /sdcard/Android/data/packagename/cache 資料夾組成
原生設定(Settings) - 已安裝應用 - 詳情頁
Settings APP 使用了 PackageManager.getPackageSizeInfo 方法來做此事,難道 so easy?屁顛屁顛去查了一下 Android API,發現 PacakgeManager 的文件中壓根就沒有出現 getPackageSizeInfo 的身影,好吧這是一個不對外公開的 API.
Settings 計算快取大小方法:
@Override
public void handleMessage(Message msg) {
......
switch (msg.what) {
......
case MSG_LOAD_SIZES: {
synchronized (mEntriesMap) {
......
mPm.getPackageSizeInfo(mCurComputingSizePkg, mStatsObserver);
... ...
}
} break;
}
}
......
使用 getPackageSizeInfo 需要解決的問題:
1. getPackageSizeInfo 方法是一個 @hide 方法,需要通過反射來呼叫;
2. 使用getPackageSizeInfo 需要在AndroidManiFest.xml 檔案中申明許可權GET_PACKAGE_SIZE
3. 傳給 getPackageSizeInfo 方法的第二個引數型別 IPackageStatsObserver 是在 android.content.pm 包下,需要自已通過 aidl 方式定義
計算快取大小的實現:
實現流程:
1. 工程的 src/main 目錄下建立包目錄結構 aidl/android/content/pm
2. Android 原始碼 frameworks/base/core/java/android/content/pm 目錄下的 IPackageStatsObserver.aidl 與其依賴的 PackageStats.aidl 拷貝到上面一步建立的目錄裡;
3. AndroidManifest.xml 裡宣告需要 GET_PACKAGE_SIZE 許可權
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"></uses-permission>
4. 獲取快取大小實現
PackageManager pm = mContext.getPackageManager();
List<ApplicationInfo> installedPackages = pm.getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES);
mScanTotalCount = installedPackages.size();
for (int i = 0; i < mScanTotalCount; i++) {
ApplicationInfo appInfo = installedPackages.get(i);
getAppCacheSize(appInfo.packageName,observer );
}
public void getAppCacheSize(String packageName, IPackageStatsObserver.Stub observer) {
try {
PackageManager pm = ContextUtil.applicationContext.getPackageManager();
Method getPackageSizeInfo = pm.getClass()
.getMethod("getPackageSizeInfo", String.class, IPackageStatsObserver.class);
getPackageSizeInfo.invoke(pm, packageName, observer);
} catch (NoSuchMethodException e ) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private class PackageSizeObserver extends IPackageStatsObserver.Stub {
@Override
public void onGetStatsCompleted(PackageStats packageStats, boolean succeeded)
throws RemoteException {
if (packageStats == null || !succeeded) {
} else {
AppEntry entry = new AppEntry();
entry.packageName = packageStats.packagename;
entry.cacheSize = packageStats.cacheSize + packageStats.externalCacheSize;
// do something else,比如把 entry 通過訊息傳送給需要的地方,或者新增到你的列表裡
}
}
}
系統快取清理
借鑑系統Settings清理快取方案
public void onClick(View v) {
......
} else if (v == mClearCacheButton) {
// Lazy initialization of observer
if (mClearCacheObserver == null) {
mClearCacheObserver = new ClearCacheObserver();
}
mPm.deleteApplicationCacheFiles(packageName, mClearCacheObserver);
}
......
}
系統快取清理實現
- AndroidManiFest.xml 中新增許可權:
<uses-permission android:name="android.permission.DELETE_CACHE_FILES"/>
- 將 Android 原始碼 frameworks/base/core/java/android/content/pm 目錄下的 IPackageDataObserver.aidl copy到工程pm目錄下面;
- 刪除快取實現
private void deleteCacheFile(String packageName) {
Log.d(TAG, "deleteCacheFile: ");
try {
Method deleteApplicationCacheFiles = PackageManager.class.getDeclaredMethod("deleteApplicationCacheFiles", String.class, IPackageDataObserver.class);
deleteApplicationCacheFiles.invoke(pm, packageName, new PackageDataObserver());
}
catch (NoSuchMethodException e) {
e.printStackTrace();
}
catch (InvocationTargetException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public class PackageDataObserver extends IPackageDataObserver.Stub {
@Override
public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
Log.d(TAG, "onRemoveCompleted: "+packageName);
}
@Override
public IBinder asBinder() {
return null;
}
}
4.app 使用DELETE_CACHE_FILES許可權 需要系統簽名;
掃描其他檔案
//獲取SDCard 檔案目錄
File externalDir = Environment.getExternalStorageDirectory();
if (externalDir != null) {
traverPath(externalDir, 0);
}
private void traverPath(File root, int level) {
if (root == null || !root.exists() || level > SCAN_LEVEL) {
return;
}
File[] listFiles = root.listFiles();
for (File file : listFiles) {
mInfProgress.setPath(file.getAbsolutePath());
if (file.isFile()) {
String fileName = file.getName();
if (fileName.endsWith(".apk") || fileName.endsWith(".log") || fileName.endsWith(".tmp") || fileName.endsWith(".temp")) {
mDataSize += file.length();
CleanDataInfo cleanInfo = new CleanDataInfo();
// List<String> filePath = new ArrayList<>();
cleanInfo.setPath(file.getAbsolutePath());
cleanInfo.setSize(mDataSize);
listsInfo.add(cleanInfo);
Log.d(MyApplication.TAG, "traverPath: " + cleanInfo.getPath());
}
if (mInfProgress != null) {
mScanListener.onProgress(mInfProgress);
}
}
else {
if (level < SCAN_LEVEL) {
traverPath(file, level + 1);
}
}
}
}