利用IPackageManager接口进行缓存垃圾清理

  • 获取IPackageManager
  • getPackageSizeInfo方法介绍
  • freeStorageAndNotify方法释放存储空间
  • clearApplicationUserData方法清除用户数据
  • 删除cache文件目录来清除缓存
  • PMS源码中LI、LIF、LP(LPw、LPr)的含义
  • 结语

获取IPackageManager

通过Context.getPackageManager方法可得到PackageManager,通过PackageManager可实现一些应用处理。

public PackageManager getPackageManager(){
  if(mPackageManager !=null){
    return mPackageManager;
  }
  
  IPackageManager pm = ActivityThread.getPackageManager();
  if(pm != null){
    return (mPackageManager = new ApplicationPackageManager(this, pm));
  }
  return null;
}

PackageManager的实质为获取IPackageManager,即PackageManagerService的客户端代理接口,PackageManager为抽象类,实际实现为ApplicationPackageManager,为包级类,无法直接访问。
IPackageManager获取的两种方式如下:

IPackageManager pm = ActivityThread.getPackageManager()
IPackageManager pm = AppGlobals.getPackageManager()

实际都是通过ActivityThread.getPackageManager()

getPackageSizeInfo方法介绍

getPackageSizeInfo为获取应用缓存大小,方法参数如下:

public void getPackageSizeInfo(String packageName, int userHandle, IPackageStatsObserver observer)

检索包的大小信息。由于这可能需要一点时间,结果将被发送回给定的观察者。需要android.Manifest.permission.GET_PACKAGE_SIZE权限。 参数packageName 要获取其大小信息的包的名称 。参数 userHandle 应获取其大小信息的用户。 参数observer 操作完成时收到通知的观察者回调。使用 PackageStats 对象(包含包的代码、数据和缓存大小)和表示操作状态的布尔值调用观察者的回调,观察者为空时表示不需要回调。 此方法android弃用, 使用StorageStatsManager 代替,StorageStatsManager 用法详见博客:android利用StorageStatsManager获取应用程序的存储信息。

PackageStatsObserver packageStatsObserver = new PackageStatsObserver();
    try {
            iPackageManager.getPackageSizeInfo(packageName, UserHandle.myUserId(), packageStatsObserver);
            
        } catch (RemoteException e) {
            e.printStackTrace();
        }
      
    private static class PackageStatsObserver extends IPackageStatsObserver.Stub {

        @Override
        public void onGetStatsCompleted(PackageStats packageStats, boolean b) throws RemoteException {
            String packageName = packageStats.packageName
            long cacheSize = packageStats.cacheSize
            String cacheStr = Formatter.formatShortFileSize(mContext, packageStats.cacheSize)
        }
    }

可调用Formatter.formatShortFileSize(mContext, cacheSize)格式化文件大小。

freeStorageAndNotify方法释放存储空间

PackageDataObserver packageDataObserver = new PackageDataObserver();

        try {

            
            iPackageManager.clearApplicationUserData(packageName, packageDataObserver, UserHandle.myUserId());

            
            StatFs stat = new StatFs(Environment.getDataDirectory().getAbsolutePath());
            iPackageManager.freeStorageAndNotify(volumeUuid, (long) stat.getBlockCount() * (long) stat.getBlockSize(), packageDataObserver);
            
            iPackageManager.deleteApplicationCacheFiles(packageName, packageDataObserver);

        } catch (RemoteException e) {
            e.printStackTrace();
        }

    private static class PackageDataObserver extends IPackageDataObserver.Stub {

        @Override
        public void onRemoveCompleted(String packageName, boolean result) throws RemoteException {

        }
    }

freeStorageAndNotify方法通过删除所有应用程序中按 LRU 排序的缓存文件列表来释放存储空间。如果设备上当前可用的空闲存储空间大于或等于请求的空闲存储空间,则不会清除缓存文件。如果设备上当前可用的存储空间小于请求的可用存储空间,则删除所有应用程序中的部分或全部缓存文件(基于上次访问时间),以将设备上的可用存储空间增加到请求的值。无法保证从所有应用程序中清除所有缓存文件将清除足够的存储空间以实现所需的值。freeStorageSize 系统要释放的存储字节数。说如果freeStorageSize是XX,当前空闲存储是YY,如果XX小于YY,直接返回。如果可能,则释放 XX-YY 字节数。 观察者回调用于通知操作何时完成。
方法参数:

public void freeStorageAndNotify(@Nullable String volumeUuid, long freeStorageSize,
            @Nullable IPackageDataObserver observer)

最终通过frameworks/native/cmds/installd/binder/android/os/IInstalld.aidl的AIDL接口调用到frameworks/native/cmds/installd/InstalldNativeService.cpp的Native服务进行存储释放。

Installer方法:

接口缓存 spring_释放存储空间


InstalldNativeService.cpp的方法:

接口缓存 spring_缓存_02

clearApplicationUserData方法清除用户数据

通过ActivityManager清除用户数据方式如下:

ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    boolean res = am.clearApplicationUserData(packageName, packageDataObserver);

其过程AMS与PMS通信调用clearApplicationUserData清除用户数据。
利用IPackageManager清除用户数据的方式如下:

try {
            iPackageManager.clearApplicationUserData(packageName, packageDataObserver, UserHandle.myUserId());

        } catch (RemoteException e) {
            e.printStackTrace();
        }

clearApplicationUserData清除用户数据的过程不仅清除保存的用户数据,同时也清除了缓存数据cacheFiles即deleteApplicationCacheFiles,如果单独清除缓存可调用如下方法:

iPackageManager.deleteApplicationCacheFiles(packageName, packageDataObserver);

deleteApplicationCacheFiles方法清除应用缓存,即清除cache目录。

clearApplicationUserData最终在PMS访问InstallerInstalldNativeService的Native服务进行数据清理。

Installer方法:

接口缓存 spring_android_03


InstalldNativeService.cpp的方法:

接口缓存 spring_android_04

删除cache文件目录来清除缓存

除了通过上述PackageManager.deleteApplicationCacheFiles(packageName, packageDataObserver) 还有另一种方法,通过删除cache文件目录:

public void clearALLCache() {
        List<PackageInfo> packList = mPackageManager.getInstalledPackages(PackageManager.DELETE_CHATTY);
        for (int i=0; i < packList.size(); i++)
        {
            PackageInfo packInfo = packList.get(i);
            //非系统应用
            if ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                try {
                    Context context = getApplicationContext().createPackageContext(packInfo.packageName,Context.CONTEXT_IGNORE_SECURITY);
                    deleteCache(context);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public static void deleteCache(Context context) {
         try {
             File dir = context.getCacheDir();
             deleteDir(dir);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }

     public static boolean deleteDir(File dir) {
         if (dir != null && dir.isDirectory()) {
             String[] children = dir.list();
             for (String child : children) {
                 boolean success = deleteDir(new File(dir, child));
                 if (!success) {
                     return false;
                 }
             }
             return dir.delete();
         } else if(dir!= null && dir.isFile()) {
             return dir.delete();
         } else {
             return false;
         }
     }

通过得到安装应用列表,遍历得到应用package的Context,通过Context得到缓存目录,遍历文件目录进行删除。

PMS源码中LI、LIF、LP(LPw、LPr)的含义

PackageManagerService内部使用的两个锁来进行应用数据操作。用LI、LIF、LP(LPw、LPr)后缀名来标志加锁处理,因为其中开头字母L指的是Lock锁,而后的字母I和P指的是两个锁,I表示mInstallLock同步锁(安装锁),P表示mPackages同步锁(包锁)。LPw、LPr中的w表示writing,r表示reading。LIF中的F表示Freeze
mPackages同步锁,是指操作mPackages时,用对mPackages加锁保护起来。mPackages同步锁用来保护内存中已经解析的包信息和其他相关状态。mPackages同步锁是细粒度的锁,只能短时间持有这个锁,因为争抢mPackages锁的请求很多,短时间持有mPackages锁,可以让其他请求等待的时间短些。
mInstallLock同步锁,是指安装App的时候,对安装的处理要用mInstaller加锁保护起来。同时用来保护所有对installd的访问。installd通常包含对应用数据的繁重操作。
由于installd是单线程的,并且installd的操作通常很慢,所以在已经持有mPackages同步锁的时候,千万不要再请求mInstallLock同步锁。反之,在已经持有mInstallLock同步锁的时候,可以去请求mPackages同步锁。
具体参考PackageManagerService中的方法名中的LI、LIF、LPw、LPr的含义

结语

以上为利用IPackageManager接口进行缓存垃圾清理,此为系统应用调用的接口,在使用过程中需考虑特殊应用是否需要清理以免删除重要数据!!!