從crash看kotlin-android-extensions工作原理

zjw-swun發表於2018-03-26

最近專案開始轉kotlin其中用了官方推薦的kotlin-android-extensions,這次通過一次crash看kotlin-android-extensions免去findviewbyId的原理

1.一次crash的產生

引入kotlin-android-extensions這裡不多做解釋了,下面是一次fragment中不當使用kotlin-android-extensions導致crash的程式碼

 override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        rootView = inflater.inflate(R.layout.fragment_blank, container, false)
        //import kotlinx.android.synthetic.main.fragment_blank.*
       textView.text = "123"  //其中textView 是fragment_blank.xml中的一個TextView控制元件的id
        return rootView
    }
複製程式碼

此時會存在以下錯誤

     Caused by: android.view.InflateException: Binary XML file line #0: Binary XML file line #0: Error inflating class fragment
                                                                        Caused by: android.view.InflateException: Binary XML file line #0: Error inflating class fragment
                                                                        Caused by: java.lang.IllegalStateException: textView must not be null
                                                                           at com.zjw.mykotlinproject.BlankFragment.onCreateView(BlankFragment.kt:45)
                                                                           at android.support.v4.app.Fragment.performCreateView(Fragment.java:2261)
複製程式碼

好這個時候就有人說了fragment應該使用如下程式碼,可以解決這個異常

 override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        rootView = inflater.inflate(R.layout.fragment_blank, container, false)
        import kotlinx.android.synthetic.main.fragment_blank.view.*
      //  rootView?.textView?.text = "123"
        return rootView
    }
複製程式碼

可以解決,但是這一種方式是一次迴避檢視原始碼的機會,因為以上2種方式的原理是不一樣的,那麼怎麼分析上述的異常呢,因為程式碼被kotlin-android-extensions動了手腳,因此直接看是看不懂的,什麼你要看kotlin-android-extensions原始碼?你自己找找吧,反正我在本地找到的source.jar是空的,網上github上也沒找到,就算找到也要花時間分析,我只想知道我現在的這個fragment到底被kotlin-android-extensions改成了什麼樣子而已,emmmm~嚶嚶嚶~,怎麼辦呢?以下將給出辦法

2. 使用kotlin程式碼還原術!讓外掛給我生成的程式碼在我面前一覽無餘吧!

第一步

圖片描述
第二步
圖片描述

按了 Decompile 按鈕之後

圖片描述

咦,onCreateView方法裡面多了一個_$_findCachedViewById方法,在當前檔案搜尋這個方法可以看見實現如下圖

圖片描述

到這裡就清楚了,kotlin-android-extensions讓你直接用id就能得到xml中的控制元件物件並且使用,其實是他生成了findviewbyId的程式碼我們開發者就方便了,這裡注意到這句this.getView()這裡才是產生crash的真正原因,因為`this.getView()呼叫時候onCreateView還沒有返回,因此最後findViewById的時候就產生了問題。要避免crash的話同時不想用 rootView?.textView``這一長串程式碼的話就這樣幹

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        textView.text = "123"
        // Log.e(TAG,TAG)
    }
複製程式碼

好接著說 為什麼下面程式碼使用正常

   override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        rootView = inflater.inflate(R.layout.fragment_blank, container, false)
        //import kotlinx.android.synthetic.main.fragment_blank.*
        //textView.text = "123"
        // import kotlinx.android.synthetic.main.fragment_blank.view.*
        rootView?.textView?.text = "123"
        return rootView
    }
複製程式碼

來繼續反編譯操作!看看真相

圖片描述
簡單吧

     // import kotlinx.android.synthetic.main.fragment_blank.view.*
        rootView?.textView?.text = "123"
複製程式碼

上述程式碼做的事情就是使用了rootView然後用rootView.findViewById 所以我上文才說這一種方式是一次迴避檢視原始碼的機會

3.思維擴充套件

通過反編譯手段我們除了可以看見第三方外掛對原始碼的改動以外,還能定位到java和kotlin之間如何相互翻譯語義的,有的時候語義翻譯不對會導致crash(具體例子有興趣就加我和我討論吧),當出現神奇的Bug的時候建議你往這方面想想

相關文章