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
}
}
如上所示
- 使用
android.util.SparseArray
进行缓存ID - 使用
HashMap
进行缓存ID - 不缓存 每次都是通过
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 所以我们从这得到如下结论
- View binding本质上是通过包装
findViewById()
和HashMap结合实现数据绑定的,findViewById()
的一切特性在这里都适用 - View binding获取的ID可能为null
- Fragment在销毁之后不能再使用View binding,其返回的ID必定为null,所以在Fragment中慎用 view.post{} 函数 因为可能在post执行时 该Fragment已经销毁,