【Android PackageManager】使用PackageManager讀取APK ICON時候的大坑

panpf發表於2019-04-15

先來看一段常用的讀取APK Icon的程式碼

public Drawable parseApkIcon(Context context, String apkFilePath){
     PackageManager packageManager = context.getPackageManager();
     PackageInfo packageInfo = packageManager.getPackageArchiveInfo(apkFilePath, 0);
     if(packageInfo == null){
          return null;
     }

     packageInfo.applicationInfo.sourceDir = apkFilePath;
     packageInfo.applicationInfo.publicSourceDir = apkFilePath;
     Drawable iconDrawable = packageInfo.applicationInfo.loadIcon(packageManager);
     return iconDrawable;
}
複製程式碼

上述程式碼表面看沒啥問題能夠順利的讀取到APK檔案的ICON,但是PackageManager卻隱藏了一個細節,就是當無法讀取到APK的Icon的時候PackageManager就會返回了一個公用的預設綠色機器人圖示(相信凡是做Android應用開發的都見過這個圖示)。

如果我們不考慮Bitmap回收的話這個是沒有一點問題的(我們可能不考慮嗎),當一旦考慮到Bitmap回收就會出問題,例如如下場景:

我們的應用要掃描所有本地APK檔案,然後用列表顯示給使用者,列表中顯示APP的名稱和圖示。

列表滾動的時候我們會依次啟動非同步任務去讀取APK的圖示,如果在這個列表中有多個APK都無法順利讀取到圖示,那麼這多個任務最終拿到的就會是同一張圖片(不同的BitmapDrawable卻指向同一個Bitmap)。

接下來在繼續滾動的過程中如果其中某個請求由於某些原因取消了並且回收了它拿到的Bitmap,那麼其它請求的Bitmap就也被回收了,但是其它請求並不知道自己拿到的Bitmap已經被回收了,解析來其它請求在顯示圖片的時候就會崩潰。

解決這個問題的關鍵點就是怎麼判斷拿到的icon是不是公用的預設圖片,是的話就不要了。

通過檢視loadIcon方法的原始碼我們發現,當讀不到APK的icon的時候會通過packageManager.getDefaultActivityIcon()獲取公用的默圖示並返回,這下就好辦了,我們只需比較一下就可以了,修改後的程式碼如下:

public Drawable parseApkIcon(Context context, String apkFilePath){
     PackageManager packageManager = context.getPackageManager();
     PackageInfo packageInfo = packageManager.getPackageArchiveInfo(apkFilePath, 0);
     if(packageInfo == null){
          return null;
     }

     packageInfo.applicationInfo.sourceDir = apkFilePath;
     packageInfo.applicationInfo.publicSourceDir = apkFilePath;
     Drawable iconDrawable = packageInfo.applicationInfo.loadIcon(packageManager);
     if(iconDrawable == null){
          return null;
     }

     if(iconDrawable instanceof BitmapDrawable && ((BitmapDrawable) iconDrawable).getBitmap() == ((BitmapDrawable) packageManager.getDefaultActivityIcon()).getBitmap()){
          return null;
     }

     return iconDrawable;
}
複製程式碼

過濾了預設的圖示問題就結束了嗎?當然不!

首先PackageManager會快取apk icon ,然後ActivityThread持有一個ResourcesManager,ResourcesManager會快取Resources,而Resources又會快取Drawable,於是乎在重重快取之下即使我們拿到的是正常的ape icon 我們也不能回收它,因為你下次拿到的很可能是被你已回收的Drawable。

那麼最終的解決辦法就是拿到drawable後直接在本地建立快取檔案,將icon快取到本地,下次就從本地讀取,然後drawable用完就不管了,不做任何處理。

相關文章