Better

业精于勤荒于嬉

Kotlin Android Extensions:正确的绑定View

Better's Avatar 2019-05-25 Kotlin

在使用 kotlin 编写Android 应用的时候,会使用到一个插件kotlin-android-extensions既:

1
apply plugin: 'kotlin-android-extensions'

该插件的主要作用就是避免了手动调用findViewById()的来获取视图,绑定 View。类似ButterKnife 中BindVIew背后逻辑也是调用findViewById()来绑定 VIew。这里理一下背后的实现逻辑。
bummock

kotlin-android-extensions 提供了两个功能来获取 xml 中 View 的实例

  • 一 在Activity、Fragment、View中可以直接通过ID名称访问试图
  • 二 可以直接访问一个view的内部属性 通过view.xxx_id访问 view 内部的xxx_id资源控件

通过Tools -> Kotlin -> Show Kotlin ByteCode 点击Decompile 按钮查看生成的代码可以发现

对于第一中方式,内部的实现方式是生成了_$_findCachedViewById()来 findView,以及_$_clearFindViewByIdCache()来清除 View 实例

调用方式:
Activity:

1
2
3
4
5
6
7
8
9
10
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
main_title.visibility = View.VISIBLE
}
}

Fragment:

1
2
3
4
5
6
7
8
9
10
11
12
13
import kotlinx.android.synthetic.main.fragment_main.*

class MainFragment : Fragment() {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
content_list.apply {
layoutManager = LinearLayoutManager(context)
adapter = ContentListAdapterInnel()
}
}

}

View:

1
2
3
4
5
6
7
8
9
10
11
12
import kotlinx.android.synthetic.main.view_title.view.*

class TitleView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
init {
LayoutInflater.from(context).inflate(R.layout.view_title, this, true)
view_title.text = "Background KTA Extension"
}
}

背后的实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private HashMap _$_findViewCache;

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

View var2 = (View)this._$_findViewCache.get(var1);
if (var2 == null) {
View var10000 = this.getView();
if (var10000 == null) {
return null;
}

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

return var2;
}

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

对于第二种方式,却是直接调用的findViewById()来绑定 View 实列。

比如在RecyclerView 的 bindViewHolder 中

1
2
3
4
5
6
7
8
9
10
import kotlinx.android.synthetic.main.item_list.view.*

class ContentListAdapter : RecyclerView.Adapter<ContentListAdapter.Holder>() {

override fun onBindViewHolder(holder: Holder, position: Int) {
holder.itemView.item_list_position.text = list[position]
}

class Holder(v: View) : RecyclerView.ViewHolder(v)
}

背后的实现方式:

1
2
3
4
5
6
7
8
public void onBindViewHolder(@NotNull ContentListAdapter.Holder holder, int position) {
Intrinsics.checkParameterIsNotNull(holder, "holder");
View var10000 = holder.itemView;
Intrinsics.checkExpressionValueIsNotNull(var10000, "holder.itemView");
TextView var3 = (TextView)var10000.findViewById(id.item_list_position);
Intrinsics.checkExpressionValueIsNotNull(var3, "holder.itemView.item_list_position");
var3.setText((CharSequence)this.list.get(position));
}

第一种是会缓存View,第二种是每次都findViewById(),所以第一种性能上要高于第二种。

由于默认的 extension 只支持第一类,如果想在任意类中都提供这种缓存机制可以使用LayoutContainer接口。

LayoutContainer接口还是一个实验功能,需要增加配置

1
2
3
4
5
android {
androidExtensions{
experimental = true
}
}

LayoutContainer定于如下:

1
2
3
4
public interface LayoutContainer {
/** Returns the root holder view. */
public val containerView: View?
}

只需要是实现containerView的赋值即可。
修改后的调用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import kotlinx.android.synthetic.main.item_list.*

class ContentListAdapterInnel : RecyclerView.Adapter<ContentListAdapterInnel.Holder>() {

override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bind(list[position])
holder.item_list_position.text = list[position]
}

class Holder(override val containerView: View) : RecyclerView.ViewHolder(containerView),
LayoutContainer {

fun bind(s: String) {
item_list_position.text = s
}
}
}

调用的是holder.xxx_id,而不是holder.itemView.xxx_id ,从导入语法上来看也由import kotlinx.android.synthetic.main.item_list.view.*变成了import kotlinx.android.synthetic.main.item_list.*

下面是几种方式的区分点

种类 Auto Import 调用方式 背后实现方式
Activity/Fragment import kotlinx.android.synthetic.main.item_list.* xxx_id $findCachedViewById
View import kotlinx.android.synthetic.main.view_title.view.* xxx_id $findCachedViewById
view.xxx_id import kotlinx.android.synthetic.main.item_list.view.* view.xxx_id findViewById
LayoutContiner import kotlinx.android.synthetic.main.item_list.* xxx_id $findCachedViewById

参考:
kotlin-android-extensions
Kotlin Android Extensions: Using View Binding the right way
tutorial-android-plugin

This article was last updated on days ago, and the information described in the article may have changed.