Android 開發高手課 溫故知新篇
首先推薦大家先閱讀《Android 開發高手課》和我之前的三篇練習:
最近二刷了《Android 開發高手課》,對於老師提到的一些案例,自己實踐了一下。分享給學習此專欄的大家:
1.Android 7.0 Toast的BadTokenException
這個是在第二課崩潰優化(下)中提到的一個問題。
當然,Google在Android 8.0修復了這個問題,其實就是捕獲了一下異常:
try { mWM.addView(mView, mParams); trySendAccessibilityEvent(); } catch (WindowManager.BadTokenException e) { /* ignore */ } 複製程式碼
所以如果要避免這個問題,我們就要找到HOOK點。7.0 Toast原始碼裡面有一個變數叫 mTN,它的型別為 handler,我們只需要代理它就可以實現捕獲。
簡單實現程式碼如下:
private void hook(Toast toast) { try { Field tn = Toast.class.getField("mTN"); tn.setAccessible(true); Field handler = tn.getType().getField("mHandler"); handler.setAccessible(true); Field callback = handler.getClass().getField("mCallback"); callback.setAccessible(true); // 替換 callback.set(handler, new NewCallback((Handler) handler.get(tn.get(toast)))); } catch (Exception e) { e.printStackTrace(); } } public class NewCallback implements Handler.Callback { private final Handler mHandler; public NewCallback(final Handler handler) { this.mHandler = handler; } @Override public boolean handleMessage(final Message msg) { try { this.mHandler.handleMessage(msg); } catch (final RuntimeException e) {} return true; } } 複製程式碼
在前一陣didi開源的booster
中也有對此問題的修復
,看了後我覺得考慮的更加完善。有興趣的可以看看。
當然了,Toast
的小問題還有不少,相關的開源修復方案也很多,具體可以參考這篇文章。
2.Dex檔案的類重排
這個是在第八課啟動優化(下)中提到的一個優化啟動速度的方法。主要使用redex工具來實現,對redex用法不熟悉的,可以看我之前的文章。
帶來的好處:
- 更少的IO:避免在多個dex檔案中的讀取。
- 減少記憶體使用量:避擴音前載入不必要的類。
- 更少page cache汙染。
使用方法:
- 首先連線手機,獲取應用程序的pid
adb shell ps | grep YOUR_APP_PACKAGE | awk '{print $2}' 複製程式碼
- 獲取Heap Dump檔案
// 生成 adb shell am dumpheap YOUR_PID /data/local/tmp/SOMEDUMP.hprof // 獲取 adb pull /data/local/tmp/SOMEDUMP.hprof YOUR_DIR_HERE 複製程式碼
- 獲取類載入順序檔案
python redex/tools/hprof/dump_classes_from_hprof.py --hprof SOMEDUMP.hprof > list_of_classes.txt 複製程式碼
我在這裡遇到了些問題,首先這個python指令碼只支援python2,同時需要安裝 pip、enum、enum34。否則會提示類似pip command not found
這樣的錯誤。
pip是python的包管理工具,在Python2.7的安裝包中,easy_install.py是預設安裝的,而pip需要我們手動安裝
安裝方法如下:
sudo easy_install pip sudo pip install enum sudo pip install enum34 複製程式碼
還有這個指令碼不支援8.0以上
的裝置生成的DUMP.hprof
檔案。
如果想獲取8.0裝置的類載入順序,可以參考老師文中複寫ClassLoader
的方案。
以上完成後我們就獲取到了一個list_of_classes.txt
檔案,大致格式如下:
com/bumptech/glide/load/resource/bitmap/BitmapResource.class java/lang/Integer.class android/graphics/Bitmap.class libcore/util/NativeAllocationRegistry.class java/lang/ref/FinalizerReference$Sentinel.class java/lang/ref/FinalizerReference.class libcore/util/NativeAllocationRegistry$CleanerThunk.class sun/misc/Cleaner.class libcore/util/NativeAllocationRegistry$CleanerRunner.class android/graphics/Canvas.class android/graphics/Paint.class android/graphics/BitmapShader.class android/graphics/RectF.class java/util/TreeMap$TreeMapEntry.class okhttp3/Request$Builder.class okhttp3/Headers$Builder.class java/util/ArrayList.class okhttp3/HttpUrl$Builder.class java/lang/String.class java/lang/StringBuffer.class android/icu/impl/ReplaceableUCharacterIterator.class android/icu/text/ReplaceableString.class okhttp3/HttpUrl.class java/util/Collections$UnmodifiableRandomAccessList.class okio/Buffer.class java/lang/StringBuilder.class java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$1.class java/util/HashMap$EntryIterator.class java/util/Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry.class okhttp3/Request.class okhttp3/Headers.class com/bumptech/glide/load/resource/bitmap/LazyBitmapDrawableResource.class ... 複製程式碼
- 最後執行優化命令
redex --sign -s test.jks -a key0 -p 111111 -c interdex.config -P proguard-rules.pro -o app_1.apk app.apk 複製程式碼
其中interdex.config
檔案配置如下:
{ "redex" : { "passes" : [ "InterDexPass" ] }, "coldstart_classes" : "list_of_classes.txt" } 複製程式碼
我們可以使用010 Editor
來檢視我們的前後對比。
觀察第二張圖可以看到,就是我們list_of_classes.txt
檔案中的順序。(Interdex Pass將忽略它在apk中找不到相應的類)
其實,Google在Android 8.0的ART優化中也有引用一個叫dexlayout的來實現類和方法的重排,大家可以瞭解一下。
3.使用FileVisitor替代 ListFiles
在第十三課儲存優化(中)中,老師提到檔案遍歷在 API 26 之後建議使用FileVisitor
替代ListFiles
,因為檔案遍歷的耗時跟資料夾下的檔案數量有關。老師文中說道:“曾經我們出現過一次 bug 導致某個資料夾下面有幾萬個檔案,在遍歷這個資料夾時,使用者手機直接重啟了。”
一般的資料夾刪除方法:
public static void deleteDir(String path) { File dir = new File(path); if (dir == null || !dir.exists() || !dir.isDirectory()){ return; } for (File file : dir.listFiles()) { if (file.isFile()){ file.delete(); }else if (file.isDirectory()){ deleteDir(path); } } dir.delete(); } 複製程式碼
就是利用dir.listFiles()
方法遞迴刪除子檔案。FileVisitor是Java7的新特性之一,在Android 8.0開始支援。所以完善後的刪除檔案方法如下:
public static void deleteDir(String path) throws IOException { File dir = new File(path); if (dir == null || !dir.exists() || !dir.isDirectory()){ return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>(){ @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); //表示繼續遍歷 return FileVisitResult.CONTINUE; } /** * 訪問某個path失敗時呼叫 */ @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { //如果目錄的迭代完成而沒有錯誤,有時也會返回null if (exc == null) { Files.delete(file); return FileVisitResult.CONTINUE; } else { throw exc; } } }); }else { for (File file : dir.listFiles()) { if (file.isFile()){ file.delete(); }else if (file.isDirectory()){ deleteDir(path); } } } dir.delete(); } 複製程式碼
4.PrecomputedText
第一次聽到PrecomputedText
就是在二十一課UI優化(下)中,它可以非同步進行 measure 和 layout。我也寫了一篇部落格,有興趣可以點選檢視。
5.Litho
Litho 如我前面提到的 PrecomputedText 一樣,把 measure 和 layout 都放到了後臺執行緒,只留下了必須要在主執行緒完成的 draw,這大大降低了 U執行緒的負載。
具體的原理我就不介紹了,大家可以參看美團技術團隊前一陣分享的文章:基本功 | Litho的使用及原理剖析 。我主要說說我的使用過程。
我用Litho
的RecyclerCollectionComponent
實現了和我在PrecomputedText
部落格中相同的例子。程式碼很簡單,我就不貼出來了,有興趣的可以檢視Github
。我們直接看看效果:
幀數效果來說略遜色PrecomputedText
,但是Litho
支援的元件更多,不單單是TextView
,還有Image
、EditView
等。並且它還能實現介面扁平化、佔用更少的記憶體的優點。據說給美團App帶來了不錯的效能提升,官方文件也很詳細,所以還是值得大家嘗試的。
好了,暫時就分享這麼多了。如果對你有啟發幫助,希望可以點贊支援 !