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的使用及原理剖析。我主要說說我的使用過程。

我用LithoRecyclerCollectionComponent 實現了和我在PrecomputedText博客中相同的例子。代碼很簡單,我就不貼出來了,有興趣的可以查看Github。我們直接看看效果:

幀數效果來說略遜色PrecomputedText,但是Litho支持的組件更多,不單單是TextView,還有ImageEditView等。並且它還能實現界面扁平化、佔用更少的內存的優點。據說給美團App帶來了不錯的性能提升,官方文檔也很詳細,所以還是值得大家嘗試的。

好了,暫時就分享這麼多了。如果對你有啓發幫助,希望可以點贊支持

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章