超有用的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創建的思想,我們設置還可以藉助此實現界面黑白化的效果,非常的好用,每個開發者都應該去了解掌握的知識點。