Package manager has died異常PackageInfo 引發 Crash


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;
    }  
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM