滴滴 App 質量優化黑科技,爲了穩定都做了什麼?

一. 序

當 App 達到一定體量的時候,肯定是要考慮質量優化。有些小問題,看似只有 0.01% 觸發率,但是如果發生在 DAU 過千萬的產品中,就不是小問題了。

滴滴這個獨角獸的 DAU 早已過千萬,自然有一些獨到的優化方案。最近滴滴在 Github 上開源了一個 Android App 的質量優化工具Booster,通過動態發現和加載機制,提供了可擴展的能力。等於是一款移動應用的質量優化框架。

說到優化好像也不知道具體能幹什麼。從特性上籠統來說,Booster 可以做到性能檢測和優化、包體積瘦身、代碼注入等。

稍微看了一下,暫時所支持的優化還比較有限,但是好在 Booster 提供了非常便捷的擴展能力,我們可以根據業務場景,進行鍼對性的優化。

雖然 Booster 現在的優化點還很少,但是在開源的同時,也給出了後續發展的 Roadmap,之後的功能應該是會越來越完善的。下面就來了解一下滴滴新開源的 Booster。

二. Booster

2.1 什麼是 Booster

Booster 是專門爲移動應用而設計的簡單易用、輕量級、功能強大且可擴展的質量優化工具包,其通過動態發現和加載機制,提供了可擴展的能力。是一款移動應用的質量優化框架。

Booster 主要由 Transformer 和 Task 組成。

Transformer 用於對字節碼進行掃描或修改(取決於 Transformer 的功能),而 Task 則用於處理構建中的資源。

爲了滿足不同業務場景下的優化需求,Booster 提供了 Transformer SPI 和 VariantProcessor SPI 接口,來允許開發者進行定製。

Booster 的整體框架如下:

Booster 對 Gradle 還有一些小的版本要求:

  • Gradle 4.1 以上版本

  • Android Gradle 插件 3.0 以上版本

2.2 Booster 到底能幹什麼?

讀了一遍官方的介紹,好像還是不知道 Booster 的用處,這裏就以 Booster 已經支持的一個 Transformer 爲例子,來講解它到底能幹什麼。

Toast 是我們在日常開發中經常會用到的一個提示信息的組件,而在 Android 7.0 的系統中,它有可能會觸發 BadTokenException 的異常。這個問題,用 Booster 就可以解決。

Toast 拋 BadTokenException 看起來是在顯示的時候,窗口的 Token 已經失效了,也有可能在顯示 Toast 的時候,窗口已經被銷燬了。

android.view.WindowManager$BadTokenException: 
    at android.view.ViewRootImpl.setView(ViewRootImpl.java)
    at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java)
    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java4)
    at android.widget.Toast$TN.handleShow(Toast.java)

這個問題只出現在 Android 7.0 中,在之後的版本中,Google 的工程師爲了解決這個問題,是直接將其 Catch 住這個異常來解決問題的。下面是 Android 8.0 的相關代碼。

try {
    mWM.addView(mView, mParams);
    trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
    /* ignore */
}

這種問題,在我們自己的代碼中,可以通過定義一個統一的 Toast 幫助類,將其 方法 catch 住。老項目我們就要全文遍歷一遍 Toast 的調用並替換,並且這樣的方案也有缺陷,無法避免來自第三方庫中使用的 Toast 出現此問題。

雖然這個問題已經有解決方案,但是 Booster 給了我們另外一個選擇。

在 Booster 中,已經內置了一些 Transformers,其中就有一個 booster-transform-bugfix-toast,是用於修復 Android 7.0 中,Toast 導致的系統錯誤。

ToastBugfixTransformer 的主要源碼在此:

@AutoService(ClassTransformer::class)
class ToastBugfixTransformer : ClassTransformer {

    override fun transform(context: TransformContext, klass: ClassNode): ClassNode {
        klass.methods.forEach { method ->
            method.instructions?.iterator()?.asIterable()?.filterIsInstance(MethodInsnNode::class.java)?.filter {
                it.owner == TOAST && it.name == "show" && it.desc == "()V"
            }?.forEach {
                it.owner = `TOAST'`
                it.desc = "(L$TOAST;)V"
                it.opcode = Opcodes.INVOKESTATIC
            }
        }
        return klass
    }
}
private const val TOAST = "android/widget/Toast"
private const val `TOAST'` = "com/didiglobal/booster/$TOAST"

它是將系統的 Toast 傳遞到另外定製的一個 com.didiglobal.booster.android.widget 包下的 Toast 來解決。

public static void show(final android.widget.Toast toast) {
    if (Build.VERSION.SDK_INT == 25) {
        workaround(toast).show();
    } else {
        toast.show();
    }
}

private static android.widget.Toast workaround(final android.widget.Toast toast) {
    final Object tn = getFieldValue(toast, "mTN");
    if (null == tn) {
        Log.w(TAG, "Field mTN of " + toast + " is null");
        return toast;
    }

    final Object handler = getFieldValue(tn, "mHandler");
    if (handler instanceof Handler) {
        if (setFieldValue(handler, "mCallback", new CaughtCallback((Handler) handler))) {
            return toast;
        }
    }

    final Object show = getFieldValue(tn, "mShow");
    if (show instanceof Runnable) {
        if (setFieldValue(tn, "mShow", new CaughtRunnable((Runnable) show))) {
            return toast;
        }
    }

    Log.w(TAG, "Neither field mHandler nor mShow of " + tn + " is accessible");
    return toast;
}

當 API Level 不爲 25 時,直接調用 Toast.show() 方法,爲 25 時,通過反射來判斷當前 Toast 的情況,進而返回一個有效的 Toast 對象,再調用 show() 。

到這裏就將 Toast 在 7.0 上的問題修復了,我們正常開發過程中,完全不需要擔心 Toast 的使用,質量保證和業務開發就可以安全區分開。

2.3 Booster 已經內置的功能

在 Booster 開源的時候,內部已經內置了一些 Transformer 和 Task,前面介紹的 booster-transform-bugfix-toast 就是其中之一。

內置 Transformers

  • booster-transform-bugfix-toast:修復 7.0 中 Toast 導致的系統錯誤。

  • booster-transform-lint:檢查潛在的性能問題。

  • booster-transform-shrink:用於清除 class 文件中的常量。

  • booster-transform-usage:用於掃描特定 API 的使用情況。

內置 Tasks

  • Booster-task-artifact:提供顯示 artifact 的 Task。

  • Booster-task-dependency:提供顯示所有依賴項的標識符以及文件路徑的 Task。

  • Booster-task-permission:提供顯示所有依賴項使用的 Android 權限。

這些 Booster 提供的 Transformer 和 Task,功能還有限,它們更多的是提供一些指導意義,可以讓我們通過源碼瞭解到 Booster 是如何使用和工作的。

有更多想法和需要,可以自己去定製實現 Transformer 和 Task。

三. 小結時刻

雖然現在 Booster 的優化功能還有限,但是看其發佈的 Roadmap 中,已經提出接下來幾個版本的迭代計劃,例如會專注:性能優化、Lint、資源壓縮、用戶體驗等等。在性能優化上,會對多線程的使用、SP 的使用、WebView 的預加載進行鍼對性的優化。

整體來看,Booster 是一個非常好的性能優化框架,它使用的都是成熟的技術,將其包裝而成,降低了我們使用的難度。並沒有什麼太大的深坑,有需要可以進行嘗試。

更多內容可以去 Github 上閱讀 Wiki 和源碼,有興趣別忘點個 star。

Github:https://github.com/didi/booster

免費獲取更多安卓開發架構的資料(包括Fultter、高級UI、性能優化、架構師課程、 NDK、Kotlin、混合式開發(ReactNative+Weex)和一線互聯網公司關於android面試的題目彙總可以加入【騰訊@安卓中高級進階】