Android 綜合技術
導語
本章主要講解,CrashHandler來監視App的crash資訊,通過Google的multiDex方案解決Android方法數超過65536的問題,Android動態載入dex,反編譯。
主要內容
- 使用CrashHandler來獲取應用的crash資訊
- 使用multidex來解決方法數越界
- Android動態載入技術
- 反編譯初步
具體內容
使用CrashHandler來獲取應用的crash資訊
如何檢測崩潰並瞭解詳細的crash資訊? 首先需實現一個uncaughtExceptionHandler物件,在它的uncaughtException方法中獲取異常資訊並將其儲存到SD卡或者上傳到伺服器中,然後呼叫Thread的setDefaultUncaughtExceptionHandler為當前程序的所有執行緒設定異常處理器。
CrashHandler原始碼
在Application初始化的時候為執行緒設定CrashHandler,這樣之後,Crash就會通過我們自己的異常處理器來處理異常了。
public class BaseApplication extends Application { @Override public void onCreate() { super.onCreate(); CrashHandler crashHandler = CrashHandler.getInstance(); crashHandler.init(this); } }
使用multidex來解決方法數越界
Android中單個dex檔案所能包含的最大方法數為65536, 這包含了FrameWork, 依賴的jar包以及應用本身的程式碼中的所有方法. 會爆出:
com.android.dex.DexIndexOverflowException: method ID not in[0, 0xffff] :65536
可能在一些低版本的手機, 即使沒有超過方法數的上限卻還是出現錯誤。
E/dalvikvm: Optimization failed E/installd: dexopt failed on '/data/dalvik-cache/.....'
這個現象, 首先dexpot是一個程式, 應用在安裝時, 系統會通過dexopt來優化dex檔案, 在優化過程中dexopt採用一個固定大小的緩衝區來儲存應用中所有方法訊息, 這個緩衝區就是linearAlloc. LinearAlloc緩衝區在新版本的Android系統中大小為8MB或者16MB. 在Android 2.2和2.3中卻只有5MB. 這是如果方法過多, 即使方法數沒有超過65535也有可能會因為儲存空間失敗而無法安裝。
解決方案:
- 外掛化: 是一套重量級的技術方案, 通過將一個dex拆分成兩個或者多個dex,可以在一定程度上解決方法數的越界問題. 但是還有相容性問題需要考慮, 所以需要權衡是否需要使用這個方案。
- multidex: 這是Google在2014年提出的解決方案.在Android5.0之前需要引入Google提供的android-support-multidex.jar;從5.0開始系統預設支援了multidex,它可以從apk檔案中載入多個dex檔案。
使用步驟:
-
修改對應工程目錄下的build.gradle檔案,在defaultConfig中新增multiDexEnabled
true這個配置項。 -
在build.gradle的dependencies中新增multidex的依賴:compile
‘com.android.support:multidex:#1.0.0’ -
程式碼中加入支援multidex功能。
第一種方案,在manifest檔案中指定Application為MultiDexApplication。
第二種方案,讓應用的Application繼承MultiDexApplication。
第三種方案,重寫 attachBaseContext 方法,這個方法比onCreate還要先執行。
public class BaseApplication extends Application { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } }
採用上面的配置項後,如果這個應用方法數沒有越界,那麼Gradle是不會生成多個dex檔案的,當方法數越界後,Gradle就會在apk中打包2個或多個dex檔案。當需要指定主dex檔案中所包含的類,這時候就需要通過—multi-dex-list來選項來實現這個功能。
//在對應工程目錄下的build.gradle檔案,加入 afterEvaluate { println "afterEvaluate" tasks.matching { it.name.startsWith('dex') }.each { dx -> def listFile = project.rootDir.absolutePath + '/app/maindexlist.txt' println "root dir:" + project.rootDir.absolutePath println "dex task found: " + dx.name if (dx.additionalParameters == null) { dx.additionalParameters = [] } dx.additionalParameters += '--multi-dex' dx.additionalParameters += '--main-dex-list=' + listFile dx.additionalParameters += '--minimal-main-dex' } }
maindexlist.txt
com/ryg/multidextest/TestApplication.class com/ryg/multidextest/MainActivity.class // multidex 這9個類必須在主Dex中 android/support/multidex/MultiDex.class android/support/multidex/MultiDexApplication.class android/support/multidex/MultiDexExtractor.class android/support/multidex/MultiDexExtractor$1.class android/support/multidex/MultiDex$V4.class android/support/multidex/MultiDex$V14.class android/support/multidex/MultiDex$V19.class android/support/multidex/ZipUtil.class android/support/multidex/ZipUtil$CentralDirectory.class
需要注意multidex的jar中的9個類必須要打包到主dex中,因為Application的attachBaseContext方法中需要用到MultiDex.install(this)需要用到MultiDex。
Multidex的缺點:
- 啟動速度會降低,由於應用啟動時會載入額外的dex檔案,這將導致應用的啟動速度降低,甚至產生ANR現象。
- 因為Dalvik linearAlloc的bug,可以導致使用multidex的應用無法在Android4.0之前的手機上執行,需要做大量相容性測試。
Android動態載入技術
動態載入也叫外掛化. 當專案越來越大的時候, 可以通過外掛化來減輕應用的記憶體和CPU佔用. 還可以實現熱插拔, 即可以在不釋出新版本的情況下更新某些模組.
學習一下作者的外掛化開源框架:dynamic-load-apk
各種外掛化方案都需要解決3個基礎性問題
宿主和外掛的概念:宿主是指普通的apk, 而外掛一般指經過處理的dex或者apk. 在主流的外掛化框架中多采用經過處理的apk來作為外掛, 處理方式往往和編譯以及打包環節有關, 另外很多外掛化框架都需要用到代理Activity的概念, 外掛Activity的啟動大多數是藉助一個代理Activity來實現。
資源訪問
外掛中凡是以R開頭的資原始檔都不能訪問。
Activity的工作主要是通過ContextImpl完成的,Activity中有一個mBase的成員變數,它的型別就是ContextImpl。Context有兩個獲取資源的抽象方法getAsssets()和getResources();只要實現這兩個方法就可以解決資源問題。
protected void loadResources() { try { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); addAssetPath.invoke(assetManager, mDexPath); mAssetManager = assetManager; } catch (Exception e) { e.printStackTrace(); } Resources superRes = super.getResources(); mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); mTheme = mResources.newTheme(); mTheme.setTo(super.getTheme()); }
從loadResources()的實現看出,載入資源的方法是反射,通過呼叫AssetManager的addAssetPath方法,我們可以將一個apk中的資源載入到Resources物件中。傳遞的路徑可以是zip或資源目錄,因此直接將apk的路徑傳給它,資源就載入到AssetManager了。然後再通過AssetManager建立一個新的Resources物件,通過這個物件就可以訪問外掛apk中的資源了。
接著在代理Activity中實現getAssets()和getResources()。關於代理Activity參考作者的外掛化開源框架。
@Override public AssetManager getAssets() { return mAssetManager == null ? super.getAssets() : mAssetManager; } @Override public Resources getResources() { return mResources == null ? super.getResources() : mResources; }
Activity的生命週期管理
為什麼會有這個問題,其實很好理解,apk被宿主程式調起以後,apk中的activity其實就是一個普通的物件,不具有activity的性質,因為系統啟動activity是要做很多初始化工作的,而我們在應用層通過反射去啟動activity是很難完成系統所做的初始化工作的,所以activity的大部分特性都無法使用包括activity的生命週期管理,這就需要我們自己去管理。
ClassLoader的管理
為了避免多個ClassLoader載入了同一個類所引發的型別轉換錯誤。將不同外掛的ClassLoader儲存在一個HashMap中。
反編譯初步
- 使用dex2jar和jd-gui反編譯apk
-
使用apktool對apk進行二次打包
以上網上資料特別多,不贅述。