Kotlin 擴展程序View binding在Fragment中使用view.post{}導致的空指針解決方法&&引起的思考總結

Kotlin 擴展程序View binding在Fragment中使用view.post{}導致的空指針解決方法&&引起的思考總結

問題描述:

使用kotlin的擴展函數View binding
在Fragment中使用 view.post造成空指針
代碼:

   tvUserName.post {
        var margin = 0
        tvUserName.layoutParams?.also {
            val param = it
            if (param is ViewGroup.MarginLayoutParams) {
                margin = param.leftMargin + param.rightMargin
            }
        }
        //base36dp 是vip圖標的寬度 因爲vip默認是gone的,所以無法測量 此處要手動設置
        tvUserName.maxWidth = llUserName.measuredWidth  - margin - context.resources.getDimensionPixelOffset(R.dimen.base50dp)

    }

解決方法:

tvUserName.post {
	if(isDetached||tvUserName==null){
		return
	}
	.....
}

思考&總結:

當導入 import kotlinx.android.synthetic.main.<佈局>.* 文件後 我們可以直接使用控件的ID,而避免了多次調用findViewById(),但是在使用過程中我們還是需要注意幾點的

1,設置緩存方式

View binding 給我們提供了三種緩存控件ID的方式

public enum class CacheImplementation {
/** Use [android.util.SparseArray] as a backing store for the resolved views. */
SPARSE_ARRAY,
/** Use [HashMap] as a backing store for the resolved views (default). */
HASH_MAP,
/** Do not cache views for this layout. */
NO_CACHE;

companion object {
    /** The default cache implementation is [HASH_MAP]. */
    val DEFAULT = HASH_MAP
  }
} 

如上所示

  1. 使用android.util.SparseArray進行緩存ID
  2. 使用HashMap進行緩存ID
  3. 不緩存 每次都是通過findViewById()獲取控件的ID

更改緩存方式有兩種,

1 全局更改 在app的build.gradle 中添加

 androidExtensions {
  experimental = true //這個也是必須的 否則不生效
  defaultCacheImplementation = "HASH_MAP" // 也可以是 SPARSE_ARRAY、NONE
} 


2,如果你僅僅是在在某個Activity/fragment中更改 緩存方法,則可以使用註解的方式
(1)
androidExtensions {
  experimental = true //這個也是必須的 否則不生效(開啓實驗性標誌)
} 
(2)
import kotlinx.android.extensions.ContainerOptions
@ContainerOptions(cache = CacheImplementation.NO_CACHE)// 也可以是 SPARSE_ARRAY、NONE
class MyActivity : Activity()

fun MyActivity.a() { 
    // findViewById() 會被調用兩次
    textView.text = "Hidden view"
    textView.visibility = View.INVISIBLE
}

2 正確認識緩存

將kotlin的代碼轉義成java代碼 發現View binding獲取控件的方式如下:

Fragment

 public android.view.View _$_findCachedViewById(int var1) {
  if (this._$_findViewCache == null) {
     this._$_findViewCache = new HashMap();
  }

  android.view.View var2 = (android.view.View)this._$_findViewCache.get(var1);
  if (var2 == null) {
     android.view.View var10000 = this.getView();//獲取Fragment的根佈局
     if (var10000 == null) {
        return null;
     }

     var2 = var10000.findViewById(var1);
     this._$_findViewCache.put(var1, var2);
  }

  return var2;

}

...

 public void onDestroyView() {
  super.onDestroyView();
  this._$_clearFindViewByIdCache();
   }

...
	 public void _$_clearFindViewByIdCache() {
		  if (this._$_findViewCache != null) {
   	  this._$_findViewCache.clear();
     }
 }

Activity

   public View _$_findCachedViewById(int var1) {
  if (this._$_findViewCache == null) {
     this._$_findViewCache = new HashMap();
  }

  View var2 = (View)this._$_findViewCache.get(var1);
  if (var2 == null) {
     var2 = this.findViewById(var1);
     this._$_findViewCache.put(var1, var2);
  }

  return var2;
}

 public void _$_clearFindViewByIdCache() {
  if (this._$_findViewCache != null) {
     this._$_findViewCache.clear();
  }

}

上面在獲取控件的ID的時候 大致相同 都是通過HashMap緩存控件ID 避免了二次調用findViewById(),但是我們需要注意到Fragment比Activity多一個在 onDestroyView() 方法時調用 _$_clearFindViewByIdCache() 方法:即是清空緩存的所有ID 所以我們從這得到如下結論

  1. View binding本質上是通過包裝findViewById() 和HashMap結合實現數據綁定的,findViewById()的一切特性在這裏都適用
  2. View binding獲取的ID可能爲null
  3. Fragment在銷燬之後不能再使用View binding,其返回的ID必定爲null,所以在Fragment中慎用 view.post{} 函數 因爲可能在post執行時 該Fragment已經銷燬,

-----kotlin擴展函數View Binding 官方說明--------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章