超有用的Android開發技巧:攔截介面View建立

語言: CN / TW / HK

highlight: vs theme: devui-blue


攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第20天,點選檢視活動詳情

本篇文章主要是分析如何攔截ActivityView的建立流程,實現無感知的使用自定義View替換指定的系統View,這對於換膚、埋點設計等等將是非常有幫助的一種方式。

LayoutInflater.Factory2是個啥?

Activity內介面的建立是由LayoutInflater負責,LayoutInflater最終會交給內部的一個型別為LayoutInflater.Factory2factory2成員變數進行建立。

這個屬性值可以外部自定義傳入,預設的實現類為AppCompatDelegateImpl

image.png

然後在AppCompatActivity的初始化構造方法中向LayoutInflater注入AppCompatDelegateImpl:

image.png

image.png

image.png

常見的ImageViewTextView被替換成AppcompatImageViewAppCompatTextView等就是藉助AppCompatDelegateImpl進行實現的。

這裡有個實現的小細節,在initDelegate()方法中,呼叫了addOnContextAvailableListener()方法傳入一個監聽事件實現的factory2注入,這個addOnContextAvailableListener()方法有什麼魅力呢?

addOnContextAvailableListener()是幹啥用的?

咱們先看下這個方法是幹啥用的:

image.png

image.png

最終是將這個監聽物件加入到了ContextAwareHelper類的內部mListeners集合中,咱們接下里看下這個監聽物件集合最終是在哪裡被呼叫的。

image.png

image.png

可以看到,這個集合最終在ComponetActivityonCreate()方法中呼叫,請注意,這個呼叫時機還是在父類的super.onCreate()方法前進行呼叫的。

所以我們可以得出結論,addOnContextAvailableListener()新增的監聽器將在父類onCreate()方法前進行呼叫。

這個用處的場景還是比較多的,比如我們設定Activity的主題就必須在父類的onCreate()方法前呼叫,藉助這個監聽,可以輕鬆實現。

程式碼實戰

請注意,這個factory2的設定必須在ActivityonCreate()方法前呼叫,所以我們可以直接藉助addOnContextAvailableListener()進行實現,也可以重寫onCreate()方法在指定位置實現。當然了,前者更加的靈活,這裡我們還是以後者進行舉例。

```kotlin override fun onCreate(savedInstanceState: Bundle?) { LayoutInflaterCompat.setFactory2(layoutInflater, object : LayoutInflater.Factory2 { override fun onCreateView( parent: View?, name: String, context: Context, attrs: AttributeSet ): View? {

        return if (name == "要替換的系統View名稱") {
            CustumeView()
        } else delegate.createView(parent, name, context, attrs)
    }
})

} ```

請注意,這裡也有一個實現的小細節,如果當某個系統View不屬於我們要替換的View,請繼續委託給AppCompatDelegateImpl進行處理,這樣就保證了實現系統元件特有功能的前提下,又能完成我們的View替換工作。

統一所有介面View的替換工作

如果要替換View的介面非常多,一個Activity一個Activity替換過去太麻煩 ,這個時候就可以使用我們經常使用到的ApplicationregisterActivityLifecycleCallbacks()監聽所有Activity的建立流程,其中我們用到的方法就是onActivityPreCreated():

``` registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks { override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) { LayoutInflaterCompat.setFactory2(activity.layoutInflater, object : LayoutInflater.Factory2 { override fun onCreateView( parent: View?, name: String, context: Context, attrs: AttributeSet ): View? {

            return if (name == "要替換的系統View名稱") {
                CustumeView()
            } else (activity as? AppCompatActivity)?.delegate?.createView(parent, name, context, attrs) ?: null
        }

        override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
            TODO("Not yet implemented")
        }
    })

}

} ```

不過這個Application.ActivityLifecycleCallbacks介面要重寫好多無用的方法,太麻煩了,之前寫過一篇關於介面優化相關的文章嗎,詳情可以參考:介面使用額外重寫的無關方法太多?優化它

總結

之前看過很多換膚、埋點統計上報等相關文章,多多少少都介紹了向AppCompatActivity中注入factory2攔截系統View建立的思想,我們設定還可以藉助此實現介面黑白化的效果,非常的好用,每個開發者都應該去了解掌握的知識點。