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 官方说明--------

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