最近一直在搞appStore,之前一直都是从网络获取apk下载进行安装,现在车机要求需要支持从U
盘安装apk,本以为简单很容易的事,却一不小心掉了一个坑里,
爬了好久,主要是没很明显的报错,没遇到过的话真的很难发现错误。
- 1
- 2
- 3
- 4
一、首先来看下错误,以及造成的错误原因
由上图可知,当U盘拔掉之后,网易云音乐apk依旧被占用,
从而系统发出了sighup,kill信号,导致当前进程被杀掉,
也就是说我们读取apk信息时,资源未能及时释放。
- 1
- 2
- 3
- 4
二、解决思路,解决方案
既然是因为U盘拔掉后,资源未释放引起的,那解决的方案无非就是找到占用的地方,
释放掉资源。然而你会发现你将所有可能引起的资源占用都处理置空,
拔掉U盘时,将数据清空,还是会奔溃。
具体原因以及解决思路可以看看这两篇博客
- 1
- 2
- 3
- 4
- 5
关于Usb直接拔出导致的进程崩溃问题
Android 开发:加载未安装apk图标-拔出U盘导致进程被杀的解决方案
上述两篇博客已经讲的很详细了,引起原因以及解决方案,
都提供了不同的思路,但是他们只是针对,获取apk的图标,
可能他们的需求只是需要个图标而已,针对图标进行了操作,
但是在我的应用中,不仅仅需要图标,还需要应用的版本,名称,
包名等等信息,从而我进行了各种尝试,确实如果我只是获取图标,
通过他们的方式确实不会再出现进程被杀的情况,但是当我将所有需要的其他信息,依旧会报错。
- 1
- 2
- 3
- 4
- 5
- 6
三、我的做法,以及解决思路
首先先上代码:
public static UninstallApkInfo cloneUninstallAPKInfo(Context ctx, String archiveFilePath) {
UninstallApkInfo uninstallApkInfo = new UninstallApkInfo();
Log.i("Apkinfo", "parse " + archiveFilePath);
PackageInfo pakinfo = null;
PackageManager pm = ctx.getPackageManager();
pakinfo = pm.getPackageArchiveInfo(archiveFilePath, PackageManager.GET_ACTIVITIES);
if (pakinfo != null) {
ApplicationInfo appinfo = pakinfo.applicationInfo;
appinfo.sourceDir = archiveFilePath;
appinfo.publicSourceDir = archiveFilePath;
uninstallApkInfo.setAppVersion(pakinfo.versionName);
// uninstallApkInfo.setAppName(pm.getApplicationLabel(appinfo).toString());// 得到应用名 );
// uninstallApkInfo.setAppIcon(appinfo.loadIcon(pm));//得到应用图标
uninstallApkInfo.setAppPkgName(appinfo.packageName);
uninstallApkInfo.setFileName(archiveFilePath);
uninstallApkInfo.setInstallState(ApkOperateManager.INSTALL_NONE);
AssetManager assetManager = new AssetManager();
assetManager.addAssetPath(archiveFilePath);
Resources resources = new Resources(assetManager, ctx.getResources().getDisplayMetrics(), ctx.getResources().getConfiguration());
uninstallApkInfo.setAppIcon(resources.getDrawable(appinfo.icon));
uninstallApkInfo.setAppName(resources.getString(appinfo.labelRes));
assetManager.close();
File file = new File(archiveFilePath);
uninstallApkInfo.setAppSize(file.length() / (1024 * 1024));
Log.i("UN", uninstallApkInfo.appName + uninstallApkInfo.appPkgName);
} else {
Log.w("Apkinfo", "get no pkginfo from " + archiveFilePath);
}
if (pakinfo == null)
return null;
else
return uninstallApkInfo;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
以上代码是不会出现U盘被占用的情况,首先对比一下我获取appName跟appIcon,
注释的是我之前会出错的写法,然后再看看我现在获取的方式,
首先先看出错的写法,我们用到了PackageManager(pm)跟PackageInfo(pakinfo),
而现在的获取方式只是通过PackageInfo,
我们进源码可以看到PackageInfo只是一个javaBean实现了Parcelable接口,
也就是说PackageInfo只是用来放置apk里面的信息数据的,并没有进行实际的文件操作,
我的理解是,读取apk文件信息的操作应该可能是放在PackageManager里面进行的,
当我们用PackageManager来获取图标或者应用名时,PackageManager因为被我们引用了从而无法回收释放资源
,而我们只用到PackageInfo,PackageManager也就能成功释放掉了,也就不会再占用U盘文件了。
(仅代表个人见解,如有不对的地方或者更深的理解请指教)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
好了U盘占用问题得到了解决,现在又遇到了一个问题就是只通过PackageInfo并不能获取到图标跟应用名信息,
可是我们就是需要啊,那该怎么办?从上面提到的第二遍博客得到了解决的方案,
有兴趣的可以看那篇博客得到更详细的说明,PackageInfo里面的ApplicationInfo里面含有图标资源和应用名的资源id,
然后再通过AssetManager 来获取apk的resources,从而通过资源id得到apk的图标和应用名,
并且AssetManager提供了close方法,所以获取到之后及时关闭资源也就不会出现U盘占用的问题了.
- 1
- 2
- 3
- 4
- 5
- 6
AssetManager assetManager = new AssetManager();
assetManager.addAssetPath(archiveFilePath);
Resources resources = new Resources(assetManager, ctx.getResources().getDisplayMetrics(), ctx.getResources().getConfiguration());
uninstallApkInfo.setAppIcon(resources.getDrawable(appinfo.icon));
uninstallApkInfo.setAppName(resources.getString(appinfo.labelRes));
assetManager.close();
- 1
- 2
- 3
- 4
- 5
- 6
说明:AssetManager的所需要的几个方法都是隐藏的(@hide),
我这边AssetManager能直接调用,是因为我们系统是自己定制的,
已经让系统层那边将这些接口暴露了,如果你们系统是可以自己定制的,
那么你可以采用跟我一样的方式让系统层暴露接口,否则你只能通过反射来调用这些接口,
不会的话可以查看上面推荐的第二遍博客里面的第三个方案。
- 1
- 2
- 3
- 4
- 5