Android(安卓)获取 PackageInfo 引发 Crash 填坑
一般 Android 通过 PackageInfo
这个类来获取应用安装包信息,比如应用内包含的所有 Activity
名称、应用版本号之类的。PackageInfo
通过 PackageManager
来获取,代码如下:
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
比如我们要获取应用版本号时:
public static int getVersionCode(Context context) { PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); return info.versionCode;}
Tip: 获取应用自身版本号,推荐使用
BuildConfig.VERSION_CODE
方式,这里只是为了方便举例说明问题。
一般情况下,上面的方法是可以正常拿到数据的,但是在某些情况下这也可能会引发 java.lang.RuntimeException: Package manager has died
异常。
java.lang.RuntimeException: Package manager has died at android.app.ApplicationPackageManager.getPackageInfo(ApplicationPackageManager.java:82)
为了分析引发 Package manager has died 这个问题的具体原因,我们先来看看 getPackageInfo 这个方法:
frameworks/base/core/java/android/app/ApplicationPackageManager.java:
@Override public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException { try { PackageInfo pi = mPM.getPackageInfo(packageName, flags, mContext.getUserId()); if (pi != null) { return pi; } } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } throw new NameNotFoundException(packageName); }
从上面可以看出,getPackageInfo 具体实现是一个 Binder 调用,造成这个的原因是因为发生了 RemoteException 。
Binder 调用为什么会造成 Exception,下面再来看看 Binder 代码
frameworks/base/core/jni/android_util_Binder.cpp:
case FAILED_TRANSACTION: ALOGE("!!! FAILED BINDER TRANSACTION !!!"); // TransactionTooLargeException is a checked exception, only throw from certain methods. // FIXME: Transaction too large is the most common reason for FAILED_TRANSACTION // but it is not the only one. The Binder driver can return BR_FAILED_REPLY // for other reasons also, such as if the transaction is malformed or // refers to an FD that has been closed. We should change the driver // to enable us to distinguish these cases in the future. jniThrowException(env, canThrowRemoteException ? "android/os/TransactionTooLargeException" : "java/lang/RuntimeException", NULL); break;
可以看出造成 Binder crash 抛出 RuntimeException 是因为获取应用 PackageInfo
中数据量太大了,超出了 Binder
可传递的最大容量,进而导致 PackageManager
崩溃。
对于上面这种情况,考虑如果只获取versionName
和versionCode
两个信息,不需要Activity
等信息,设法让PackageInfo
的信息量小点,避免超出了 Binder
可传递的最大容量。
我们可以利用 getPackageInfo(String packageName, @PackageInfoFlags int flags)
它的第二个参数 flag
,使得该方法返回的对象容量减小,比如使用 PackageManager.GET_CONFIGURATIONS
此外,如果对与Binder
的同时调用超出了限制就会抛出
TransactionTooLargeException
这个异常,虽然这种场景比较少见,但是我们还是有比较避免多个线程同时来调用Binder
就可以了。
优化后代码如下:
public static int getVersionCode(Context context) { synchronized(Hold.class){ PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_CONFIGURATIONS); return info.versionCode; } }
更多相关文章
- 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
- 之View state changes(视图状态改变)
- Android(安卓)代码用来返回上一个activity 调用onKeyDown()时发
- Android(安卓)PopupWindow Dialog 关于 is your activity runnin
- Android(安卓)MediaScanner源代码解析
- gradle自动修改android版本号的方法,取java静态变量重命名apk文件
- Android(安卓)BaseAdapter与ListView的使用
- Android滑动手势侦测方法
- Android4.0 LCD和键盘 背光亮度设置