Android 开发:加载未安装apk图标-拔出U盘导致进程被杀的解决方案

Android 开发:加载未安装apk图标-拔出U盘导致进程被杀的解决方案

在开发一款文件管理器,出现一个难题:因为要显示apk文件的图标,导致在拔出U盘的时候进程被杀,继而crash。
本文就是针对此问题,提出我的解决办法。


首先来看一下crash的log:

W/Vold    (  139): Failed to unmount /storage/udisk2 (Device or resource busy, retries 9, action 0)
E/ProcessKiller(  139): Process com.xxx.filemanager (2426) has open file /mnt/media_rw/udisk2/apk/Test.apk
W/Vold    (  139): Failed to unmount /storage/udisk2 (Device or resource busy, retries 8, action 0)
E/ProcessKiller(  139): Process com.xxx.filemanager (2426) has open file /mnt/media_rw/udisk2/apk/Test.apk
W/Vold    (  139): Failed to unmount /storage/udisk2 (Device or resource busy, retries 7, action 0)
I/ProcessorService( 1018): sleep, syncing time, now:91842237
E/ProcessKiller(  139): Process com.xxx.filemanager (2426) has open file /mnt/media_rw/udisk2/apk/Test.apk
W/Vold    (  139): Failed to unmount /storage/udisk2 (Device or resource busy, retries 6, action 0)
D/dalvikvm(  612): GC_FOR_ALLOC freed 526K, 29% free 1604K/2260K, paused 12ms, total 12ms
E/ProcessKiller(  139): Process com.xxx.filemanager (2426) has open file /mnt/media_rw/udisk2/apk/Test.apk
W/Vold    (  139): Failed to unmount /storage/udisk2 (Device or resource busy, retries 5, action 0)
E/ProcessKiller(  139): Process com.xxx.filemanager (2426) has open file /mnt/media_rw/udisk2/apk/Test.apk
I/CRASHLOG(  154): monitorDiskSpace : Percent of Using Data partition = 3 %
W/Vold    (  139): Failed to unmount /storage/udisk2 (Device or resource busy, retries 4, action 0)
E/ProcessKiller(  139): Process com.xxx.filemanager (2426) has open file /mnt/media_rw/udisk2/apk/Test.apk
W/Vold    (  139): Failed to unmount /storage/udisk2 (Device or resource busy, retries 3, action 0)
D/dalvikvm(  612): GC_FOR_ALLOC freed 513K, 30% free 1603K/2260K, paused 13ms, total 13ms
E/ProcessKiller(  139): Process com.xxx.filemanager (2426) has open file /mnt/media_rw/udisk2/apk/Test.apk
W/Vold    (  139): Failed to unmount /storage/udisk2 (Device or resource busy, retries 2, action 1)
E/ProcessKiller(  139): Process com.xxx.filemanager (2426) has open file /mnt/media_rw/udisk2/apk/Test.apk
W/ProcessKiller(  139): Sending SIGHUP to process 2426
I/WindowState(  374): WIN DEATH: Window{a60d6c50 u0 com.xxx.filemanager/com.xxx.filemanager.FileActivity}
I/ActivityManager(  374): Process com.xxx.filemanager (pid 2426) has died.
W/ActivityManager(  374): Force removing ActivityRecord{a5d80268 u0 com.xxx.filemanager/.FileActivity t5}: app died, no saved state
V/PhoneStatusBar(  532): onReceive: title=Launcher3 appname=Launcher3
V/Activity( 1045): onResume com.android.launcher3.Launcher@a5d881d8 title=Launcher3 applicationName=Launcher3
E/BufferQueue(  141): [com.android.launcher3/com.android.launcher3.Launcher] dequeueBuffer: can't dequeue multiple buffers without setting the buffer count
I/DecodeHelper(  812): reset
W/InputMethodManagerService(  374): Got RemoteException sending setActive(false) notification to pid 2426 uid 10049
D/Zygote  (  142): Process 2426 terminated by signal (15)
I/Vold    (  139): /storage/udisk2 sucessfully unmounted
I/Vold    (  139): /mnt/media_rw/udisk2 sucessfully unmounted
I/Vold    (  139): /mnt/media_rw/udisk2 unmounted successfully
D/Vold    (  139): Volume udisk2 state changing 5 (Unmounting) -> 1 (Idle-Unmounted)
D/DirectVolume(  139): Crisis averted
D/DirectVolume(  139): Volume udisk2 /mnt/media_rw/udisk2 disk 8:0 removed
D/Vold    (  139): Volume udisk2 state changing 1 (Idle-Unmounted) -> 0 (No-Media)

我们来分析一下log:

ProcessKiller( 139): Process com.xxx.filemanager (2426) has open file /mnt/media_rw/udisk2/apk/Test.apk

log中可以看出来,拔出U盘的时候,因为filemanager(我的文件管理器)占用了U盘中的Test.apk文件,导致process被系统kill了,所以app就挂了。


要想解决这个问题,就要及时解除对apk文件的占用

我们就先来看看,到底是哪里占用了apk文件资源:

首先,加载未安装apk图标的方式我知道的有三种:

-

  1. 纯反射;
  2. 调用PackageParser(隐藏api)
  3. 调用PackageManager(最简单好用)

-

后来研究了一下PackageManager、PackageInfo、PackageParser、ApplicationInfo、Resources 和 AssetManager 的源码,我发现问题就在于AssetManager,也只有AssetManager存在close()方法,然后就解决了问题。

-

我们来一一看一下三种方法(详细加载apk图标和防止crash的办法都写在下面方法的注释里了):


纯反射


   private static final String PACKAGE_PARSER = "android.content.pm.PackageParser";
    private static final String ASSET_MANAGER = "android.content.res.AssetManager";

    public static Drawable getUninstallApkIcon(Context context, String archiveFilePath) {
        Drawable icon = null;
        try {
            Class pkgParserCls = Class.forName(PACKAGE_PARSER);
            Class[] typeArgs = new Class[1];
            typeArgs[0] = String.class;
            Constructor pkgParserCt = pkgParserCls.getConstructor(typeArgs);
            Object[] valueArgs = new Object[1];
            valueArgs[0] = archiveFilePath;
            Object pkgParser = pkgParserCt.newInstance(valueArgs);

            DisplayMetrics metrics = new DisplayMetrics();
            metrics.setToDefaults();
            typeArgs = new Class[4];
            typeArgs[0] = File.class;
            typeArgs[1] = String.class;
            typeArgs[2] = DisplayMetrics.class;
            typeArgs[3] = Integer.TYPE;

            Method pkgParser_parsePackageMtd = pkgParserCls.getDeclaredMethod("parsePackage", typeArgs);
            valueArgs = new Object[4];
            valueArgs[0] = new File(archiveFilePath);
            valueArgs[1] = archiveFilePath;
            valueArgs[2] = metrics;
            valueArgs[3] = 0;
            Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser, valueArgs);

            Field appInfoFld = pkgParserPkg.getClass().getDeclaredField("applicationInfo");

            ApplicationInfo info = (ApplicationInfo) appInfoFld.get(pkgParserPkg);

            Class assetMagCls = Class.forName(ASSET_MANAGER);
            Constructor assetMagCt = assetMagCls.getConstructor((Class[]) null);
            Object assetMag = assetMagCt.newInstance((Object[]) null);
            typeArgs = new Class[1];
            typeArgs[0] = String.class;
            Method assetMag_addAssetPathMtd = assetMagCls.getDeclaredMethod("addAssetPath", typeArgs);
            valueArgs = new Object[1];
            valueArgs[0] = archiveFilePath;

            //通过发射获取AssetManager.close()方法   
            Method assetMag_close = assetMagCls.getDeclaredMethod("close");

            Resources res = context.getResources();
            typeArgs = new Class[3];
            typeArgs[0] = assetMag.getClass();
            typeArgs[1] = res.getDisplayMetrics().getClass();
            typeArgs[2] = res.getConfiguration().getClass();
            Constructor resCt = Resources.class.getConstructor(typeArgs);
            valueArgs = new Object[3];
            valueArgs[0] = assetMag;
            valueArgs[1] = res.getDisplayMetrics();
            valueArgs[2] = res.getConfiguration();
            res = (Resources) resCt.newInstance(valueArgs);

            icon = ((BitmapDrawable) res.getDrawable(info.icon));

            //在此调用AssetManager.close()方可治愈crash的毛病
            assetMag_close.invoke(assetMag);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return icon;
    }

调用PackageParser(隐藏api)

PackageParser属于隐藏api,需要签名,不适合通用app,不过最适合于我的app。

-

要想调用隐藏api,你需要使用源码编译出的一个jar包:
out\target\common\obj\JAVA_LIBRARIES\framework_intermediates\classes.jar

-

eclipse:右键app目录 -> Properties-> Java Build Path -> Libraries -> Add Library -> User Library(起个名字,比如:framework) -> 导入classes.jar -> Order and Export -> 选中framework(起的名字)-> up (让framework位于系统sdk(比如:Android 5.1)之上)-> ok
这样就可以在eclipse里直接调用隐藏api了。

    public static Drawable getUninstallApkIcon(Context context, String apkPath) {
        PackageParser packageParser = new PackageParser(apkPath);
        DisplayMetrics metrics = new DisplayMetrics();
        metrics.setToDefaults();

        PackageParser.Package mPkgInfo = packageParser.parsePackage(new File(apkPath), apkPath, metrics, 0);
        ApplicationInfo apinfo = mPkgInfo.applicationInfo;

        Resources pRes = context.getResources();
        AssetManager assmgr = new AssetManager();
        assmgr.addAssetPath(apkPath);
        Resources res = new Resources(assmgr, pRes.getDisplayMetrics(), pRes.getConfiguration());
        if (apinfo.icon != 0) {
            Drawable icon = res.getDrawable(apinfo.icon);

            //在此调用AssetManager.close()方可治愈crash的毛病
            assmgr.close();
            return icon;

        }
        return null;
    }

调用PackageManager(结合以两种方法的优点,部分反射)

public static Drawable getUninstallApkIcon(Context context, String apkPath) {
        Resources res = getResource(context, apkPath);
        PackageManager pm = context.getPackageManager();
        PackageInfo info = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
        ApplicationInfo appInfo = info.applicationInfo;
        if (appInfo.icon != 0) {
            Drawable icon = res.getDrawable(appInfo.icon);
            //AssetManager must be closed to avoid ProcessKiller after unmounting usb disk.
            res.getAssets().close();
            return icon;
        }
        return null;
    }

    public static Resources getResource(Context context, String apkPath) {
        AssetManager assetManager = createAssetManager(apkPath);
        return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
    }

    //利用反射调用AssetManager的addAssetPath()方法
    private static AssetManager createAssetManager(String apkPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(
                    assetManager, apkPath);
            return assetManager;
        } catch (Throwable th) {
            th.printStackTrace();
        }
        return null;
    }

总结:

请apk图标容易,送apk图标不容易,一切都是因为AssetManager没有关好门,不关门的话,系统就要把你杀掉!

这个问题比较冷门,google也没帮我找到解决办法,只有自己动手,丰衣足食了。

你可能感兴趣的:(Android开发,android,文件管理器,loadIcon,u盘,crash)