Android 動態載入資源例項解析
前不久跑去折騰高德 SDK 中的 HUD 功能,相信用過該功能的使用者都知道 HUD 介面上的導航轉向圖示是動態變化的。從高德官方導航 API 文件中 AMapNaviGuide 類的描述可知,導航轉向圖示有23種型別。
誒,等等,23 種?那圖示應該是放在 assets 資料夾吧?總不可能是在伺服器上下載吧?
看下導航 API 的 jar 包結構。
AMap_ Navi_v1.3.0_20150828.jar |- assets |- autonavi_Resource1_1_0.png |- custtexture*.png (7 張) |- com |- amap.api.navi |- autonavi |- META-INF
納尼?assets 上的圖片總共也只有 8 張,而且圖片的內容跟 HUD 毫無關係,莫非真的是從伺服器下載資源?
用 Android Studio 開啟 jar 包中的 AMapHudView.class 來看下 AMapHudView 的邏輯(AS 1.2 就引入了反編譯功能)。
... import com.autonavi.tbt.g; ... public class AMapHudView extends FrameLayout implements OnClickListener, OnTouchListener, e { static final int[] hud_imgActions = new int[]{2130837532, 2130837532, 2130837532, 2130837533, 2130837534, 2130837535, 2130837536, 2130837537, 2130837538, 2130837539, 2130837522, 2130837523, 2130837524, 2130837525, 2130837526, 2130837527, 2130837528, 2130837529, 2130837530, 2130837531}; ... private ImageView roadsignimg;// 方向圖示對應的 View ... private int resId;// 方向圖示的 id,對應 hud_imgActions 的 index,根據高德的文件,該變數值為 0-23 ... private void updateHudWidgetContent() { ... if(this.roadsignimg != null && this.resId != 0 && this.resId != 1) { Drawable var1 = g.a().getDrawable(hud_imgActions[this.resId]);// g.a() 返回的是 Resource 物件 this.roadsignimg.setBackgroundDrawable(var1); ... } } }
先看hud_imgActions,裡面的值是不是很熟悉?轉成16進位制均為 0x7F02 開頭(0x7F 是應用資源,而 0×02 則是 drawable 資源)。再看updateHudWidgetContent()方法,邏輯比較簡單,通過resId獲取hud_imgActions對應的 drawable id,再通過該 id 獲取到對應的 Drawable 物件並將其設定到 ImageView 中。
看到這,可以肯定高德 SDK 最終是通過本地資源的索引獲取到 Drawable。
然而我們的 apk 中並沒有相應的資源,為什麼能夠正常獲取到對應的 Drawable?我們看回上面的第12行程式碼:
Drawable var1 = g.a().getDrawable(hud_imgActions[this.resId]);// g.a() 返回的是 Resource 物件
我們將注意力集中到g.a()中,找到com.autonavi.tbt.g#a()
public static Resources a() { if (b == null) { b = e.getResources(); } return b; }
其中變數e為上層傳遞進來的 Activity,而我們前面說過,我們的 apk 中並沒有相應的資源,所以將注意力放到變數b在其他地方的賦值上。
public static boolean a(Context context) { ... a = b(context.getFilesDir() + "/autonavi_Resource1_1_0.jar"); b = a(context, a);// 變數 a 為 AssetManager return true; } private static AssetManager b(String str) { try { Class cls = Class.forName("android.content.res.AssetManager"); AssetManager assetManager = (AssetManager) cls.getConstructor().newInstance(); try { cls.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, str); } catch (Throwable th) { } return assetManager; } catch (Throwable th2) { return null; } } private static Resources a(Context context, AssetManager assetManager) { DisplayMetrics displayMetrics = new DisplayMetrics(); displayMetrics.setToDefaults(); return new Resources(assetManager, displayMetrics, context.getResources().getConfiguration()); }
可以看到,高德 SDK 中先通過反射例項化 AssetManager,並且呼叫 `addAssetPath(context.getFilesDir() + “/autonavi_Resource1_1_0.jar”),接著例項化 Resources 物件。所以事實上是通過這個新的 Resource 來獲取到對應資源的 Drawable 物件。
但是我們的 apk 對應的 files 目錄中並不存在 autonavi_Resource1_1_0.jar,這個檔案又是怎麼來的?
private static String k = "autonavi_Resource1_1_0.png"; ... private static boolean b(Context var0) { String filePath = var0.getFilesDir().getAbsolutePath() + "/autonavi_Resource1_1_0.jar"; ... InputStream var1 = var0.getResources().getAssets().open(k); File var3 = new File(filePath); long var21 = var3.length(); int var6 = var1.available(); if(!var3.exists() || var21 != (long)var6) { ... File var22 = new File(filePath); FileOutputStream var2 = new FileOutputStream(var22); byte[] var8 = new byte[1024]; int var9; while((var9 = var1.read(var8)) > 0) { var2.write(var8, 0, var9); } } ... }
還是 com.autonavi.tbt.g 這個類,可以看到,高德是將 jar 包內 assets 目錄中的 autonavi_Resource1_1_0.png 複製到當前 apk 對應的 files 目錄中,並將新的檔案命名為 autonavi_Resource1_1_0.jar。
再回到載入資源的問題上,為什麼載入 autonavi_Resource1_1_0.jar 能索引資源?
因為該檔案其實是 apk(高德將字尾名改成了 jar)。AssetManager 載入該 apk 後,Resource 就能通過該 AssetManager 獲取到裡面的相應資源。
AssetManager 的相關知識請參考老羅的《Android應用程式資源管理器(Asset Manager)的建立過程分析》
至此,我們就可以清楚知道高德 SDK 是如何實現動態載入資源的:
- 將資源 apk 放置在 jar 包的 assets 目錄中;
- 在 View 元件初始化的過程中將 assets 中的資源 apk 複製到 files 目錄中;
- 接著例項化 AssetManager,呼叫 addAssetPath 方法載入 files 目錄中的資源 apk;
- 然後將 AssetManager 作為引數例項化 Resouce,最後通過 Resource 物件獲取資源apk 中相應的資源。
總結
將上述內容再簡略,動態載入資源所必需的幾個核心步驟:
- 例項化 AssetManager 物件,並通過反射呼叫 addAssetPath(String) 方法載入目標 apk(或與 apk 檔案架構一致的目錄)
- 通過第一步得到的 AssetManager 例項化 Resource 物件
- 利用第二步得到的 Resource 物件來動態載入資源
這裡需要注意的是,目標 apk(目錄)需要放在context.getFilesDir()中,不然會載入失敗(addAssetPath 返回 0)。另外,目標 apk 可以不簽名,因為 addAssetPath 過程並沒有進行簽名校驗。
獲取資源 id
實際情況中,如果我們需要獲取相應的資源,就必須先獲得資源對應的 id,而外部 apk 的 R.java 並不屬於主 apk,這就導致了獲取資源的困難。
目前存在的解決方案有:
- 通過反射對應的 R 類獲取對應的 id(極力不推薦,需要知道 field 的 name,若資源 apk 需要混淆,field name 就更不知道是什麼了,再者反射的效率並不理想)
- 通過介面獲取對應的 id(優點在於靈活性高,主 apk 不需要關心資源。缺點在於若需要的資源較多,處理也較多。更多出現在獲取固定資源的場景中,譬如應用換膚)
- 直接將資源 apk 的 R.java 放在主 apk 中,通過 R 獲取 id(簡單粗暴,但若資源 apk 中存在對應的 R.java,會發生衝突。混淆過則不存在這個問題。該方案缺乏靈活性,需要開發人員知道需要的資源名,對應的屬性等。)
最後兩種方案各有各的優缺點,至於怎麼選擇,還得結合自身的場景。
應用場景
動態載入資源技術目前的一些應用場景主要有:
- 替換應用皮膚(如:QQ 空間)
- 減小主 apk 的大小,非重要資源放在服務端
- 類似於文中高德 SDK 的做法,使得 jar 包可以載入資源(這種應用可能現在比較少,以前這種做法也只是因為還沒 aar)
後續
動態載入資源技術相關文章有很多,但就我目前所看到的文章只涉及如何獲取 drawable、string 等資源,並沒有發現關於動態載入資源 apk 中的佈局檔案(我姿勢不對?_(:зゝ∠)_)。後續會分享如何動態載入資源 apk 中的佈局檔案。
相關文章
- Android資源動態載入以及相關原理分析Android
- 動態載入javascript指令碼程式碼例項JavaScript指令碼
- javascript動態載入css檔案程式碼例項JavaScriptCSS
- 動態載入js或者css檔案程式碼例項JSCSS
- 實現js檔案動態載入程式碼例項JS
- 拖動滾動條載入資料程式碼例項
- jQuery動態載入更新外部樣式表程式碼例項jQuery
- APK動態載入框架(DL)解析APK框架
- javascript實現的動態載入css檔案程式碼例項JavaScriptCSS
- 單例項物件動態裝載問題?單例物件
- Android動態載入jar/dexAndroidJAR
- Silverlight動態載入呼叫XAML資源
- Android-動態載入外掛資源,皮膚包的實現原理Android
- java狀態模式例項解析Java模式
- spring框架中多資料來源建立載入並且實現動態切換的配置例項程式碼Spring框架
- PyQt5主視窗動態載入Widget例項程式碼QT
- jQuery實現的動態載入指令碼檔案程式碼例項jQuery指令碼
- Android外掛化快速入門與例項解析(VirtualApk)AndroidAPK
- jQuery實現的動態載入css和js檔案程式碼例項 [jQueryCSSJS
- ListView動態載入資料View
- PHP+InfiniteScroll網頁無限滾動載入資料例項PHP網頁
- js下拉滾動條瀑布流載入資料程式碼例項JS
- Android 資源載入機制剖析Android
- 原生js實現的動態載入css和js檔案程式碼例項JSCSS
- 動態載入css方法實現和深入解析CSS
- 《Android攻略》資源豐富的例項手冊Android
- jQuery 動態載入下拉框選項(Django)jQueryDjango
- longing載入中例項
- 懶載入例項--hibernate
- 基於echarts非同步載入資料之多個series載入例項Echarts非同步
- Android native層動態載入so庫Android
- android: 動態載入碎片佈局的技巧Android
- DLL動態庫動態載入
- Thread 控制資源例項thread
- canvas載入效果程式碼例項Canvas
- Flutter 例項 - 載入更多的ListViewFlutterView
- 例項動態註冊跟蹤
- 動態VLAN詳細配置例項