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 崩溃。

对于上面这种情况,考虑如果只获取versionNameversionCode两个信息,不需要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;
    }  
}

复制代码

转载于:https://juejin.im/post/5a3522f551882506146efc0d

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值