Booster 系列之——佈局:XML vs 純代碼

項目地址:github.com/didi/booste…java

背景

作過開發的同窗都深有體會,用 XML 來擼 UI 的效率簡直是吊打手寫代碼,在 Anko (Kotlin 庫) 尚未流行以前,你們對 XML 仍是親睞有加,雖然性能上偶爾會有卡頓,可是整體來講,仍是勉強能接受,可是 Anko 的流行,也讓咱們開始思考,有沒有辦法既能享受 XML 擼 UI 的快感,又能享受像 Anko 同樣的性能呢?android

XML vs 純代碼

XML 佈局是在運行時解析的,由 XmlPullParser 一邊解析二進制的 XML 文件一邊反射構造 View 節點,像 APP 首頁通常都是由不少 XML 組成,這樣會致使屢次「加載-解析」。都說手寫代碼性能更好,到底有多好呢?如下是 Anko 與 XML 的性能對比數據:git

機型 規格 Anko XML 差別
阿爾卡特
One Touch
Mediatek MT6572
Dual-core 1.3GHz Cortex-A7
512MB RAM
169 ms 608 ms 359%
華爲 Y300 Qualcomm MSM 8225
Dual-core 1.0GHz Cortex-A5
512MB RAM
593 ms 3435 ms 578%
華爲 Y330 Mediatek MT6572
Dual-core 1.3GHz Cortex-A7
512MB RAM
162 ms 984 ms 606%
三星 Galaxy S2 Exynos 4210
Dual-core 1.2GHz Cortex-A9
1GB RAM
207 ms 753 ms 363%

以上數據來源於 android.jlelse.eu/400-faster-…github

魚和熊掌兼得

既然 Booster 是作專門用來作性能優化的,看到這裏,你們可能想到了解決方案——將 XML 轉譯成字節碼。沒錯,就是這樣,你們可能會問:轉譯成字節碼會有兼容性問題麼?——這得看實現方式。目前來看,有如下兩種實現:canvas

  1. 手寫代碼的思路api

    在解析 XML 後,根據 XML 元素的屬性去調用對應的 API性能優化

    RelativeLayout root = new RelativeLayout(context);
    TextView txt = new TextView(context);
    txt.setText(...);
    txt.setTextColor(...);
    txt.setTextSize(...);
    root.addView(txt);
    複製代碼
  2. 運行時解析 XML 的思路markdown

    在解析 XML 後,根據 XML 元素的屬性去構造 AttributeSet工具

    AttributeSet attr = new AttributeSet(...);
    RelativeLayout root = new RelativeLayout(context, attr);
    TextView txt = new TextView(context, attr);
    複製代碼

從以上兩種方案,咱們能夠發現:oop

  • 方案 1 須要適配全部的 Layout ,對於自定義的 Layout 或者 View,生成的代碼可能與 XML 的呈現不一致
  • 方案 2 只要保證 AttributeSet 正確性,就能保證最終呈現的 UI 跟 XML 渲染出來的是一致的

所以,Booster 選擇了第 2 種方案,至於其中的細節和原理,請參考此示例工程

LayoutInflator

Android SDK 提供了 LayoutInflator 用於將 XML 轉換成 View,讀過 AOSP 源碼的同窗可能會發現 LayoutInflator 是一個系統服務,爲何要將 LayoutInflator 做爲一個系統服務而不是一個工具類呢?我認爲,最主要的緣由在於提高性能。

「What? 調用系統服務會涉及到跨進程調用,怎麼可能會是爲了提高性能呢?」

這得從 XML 解析提及,由於 XML 佈局中會引用三類資源:

  1. 系統資源
  2. 本 APP 的資源
  3. 其它 APP 的資源

因此,對於系統資源來講

  • 若是每次 inflate 時都要去加載一次系統資源,不 ANR 纔怪
  • 若是引用了其它 package 的資源,對於當前 APP 來講,正常狀況是沒法訪問的

所以,基於以上緣由,須要有一個更高層次的資源管理,所以做爲一個系統服務合情合理。

Layoutlib

爲了分析 LayoutInflator 構造 View 的過程,咱們想到了 Android Studio 的佈局可視化設計器,就是這個示例工程的由來,這樣就能夠在 IDE 中去單步調試 XML 的渲染過程。

Layout Library 由兩部分組成:

  • layoutlib-api:layoutlib 的接口層,隨着 Android Studio 一塊兒發佈
  • layoutlib:layoutlib-api 的實現層,隨着 Android SDK 一塊兒發佈

之因此這麼設計,主要仍是爲了考慮向後兼容。

Layout Library 的另外一大亮點在於它參用了相似 Robolectric Shadow 的方式,對 Android SDK 中的 API 進行了替換,這樣,本來調用 native 的 API 都在 Layout Library 中用 Java 實現了:

@LayoutlibDelegate
static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap, float left, float top, long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity) {
    Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
    if (bitmapDelegate != null) {
        BufferedImage image = bitmapDelegate.getImage();
        float right = left + (float)image.getWidth();
        float bottom = top + (float)image.getHeight();
        drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(), image.getHeight(), (int)left, (int)top, (int)right, (int)bottom);
    }
}
複製代碼

有興趣的同窗能夠用單步調式示例工程

總結

瞭解了 LayoutInflatorLayout Library 的實現原理後,對於 Booster 如何去轉換 XML 成爲字節碼,相信你們已經有了思路了,實現原理以下:

  1. 根據 compileSdk 加載 Android SDK 中對應 platform 中的系統資源(第2步會用到)
  2. mergeRes 任務以後,加載 APP 資源
  3. 解析項目中的佈局 xml,並根據解析結果生成對應的代碼
  4. compile 的過程當中,將生成的代碼一塊兒參與編譯
  5. transform 的過程當中,掃描 class 中的如下方法調用替換成調用第3步生成的代碼
    • LayoutInflater.inflate(...)
    • Activity.setContentView(int)
    • Dialog.setContentView(int)
    • ......
相關文章
相關標籤/搜索