优雅FindViewById

Author Avatar
CodePoem 4月 18, 2020
  • 在其它设备中阅读本文章

findBiewById

findBiewById 是 Android 开发中在布局中查找 View 元素的 Api。

findBiewById 基本使用

1
2
3
4
5
6
7
8
9
10
class DemoActivity extends AppCompatActivity {
TextView mTextDemo;

@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
mTextDemo = (TextView) findViewById(R.id.tv_demo);
mTextDemo.setText("Demo");
}
}

因为写起来很繁琐(而且还需要手动强转类型),所以逐渐出现了各种简化或者代替它的方式。

省略强转

Android Support Library 26.0.0 Beta 1 开始 findViewById 将不再需要强转了。

findViewById() 方法的所有实例现在会返回 <T extends View> T,而不是 View。

1
2
3
4
5
6
7
8
9
10
class DemoActivity extends AppCompatActivity {
TextView mTextDemo;

@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
mTextDemo = findViewById(R.id.tv_demo);
mTextDemo.setText("Demo");
}
}

findBiewById 原理

findBiewById 原理实质上是递归遍历查找匹配 Id 的 View。

Activity 中 findViewId 方法会调用获取 Window -> DecoreView -> View 的 findBiewById 方法。

最终会调用到 View 中的 findViewTraversal 方法。方法名看上去是遍历操作,在 View 类中找不到遍历逻辑;
实际上 ViewGroup 覆写了 View 的 findViewTraversal 方法,实现了递归遍历查找匹配 View 的方法。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class View {
@Nullable
public final <T extends View> T findViewById(@IdRes int id) {
if (id == NO_ID) {
return null;
}
return findViewTraversal(id);
}

protected <T extends View> T findViewTraversal(@IdRes int id) {
if (id == mID) {
return (T) this;
}
return null;
}
}
class ViewGroup {

/**
* {@hide}
*/
@Override
protected <T extends View> T findViewTraversal(@IdRes int id) {
if (id == mID) {
return (T) this;
}

final View[] where = mChildren;
final int len = mChildrenCount;

for (int i = 0; i < len; i++) {
View v = where[i];

if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);

if (v != null) {
return (T) v;
}
}
}

return null;
}
}

ButterKnife

ButterKnife 是 jakewharton 大神开源作品,用于替代 findViewById ,避免繁琐的写法。

ButterKnife 基本使用

1
2
3
4
5
6
7
8
9
10
11
class DemoActivity extends AppCompatActivity {
@BindView(R.id.tv_demo)
TextView mTextDemo;

@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
ButterKnife.bind(this);
mTextDemo.setText("Demo");
}
}

ButterKnife 原理

注解编译时生成绑定类,代替我们完成 FindViewById 的操作。

PS:ButterKnife Github ReadMe 中说明已不推荐使用,推荐下文提到的 Android 官方提供的 ViewBinding

Data Binding Library

Data Binding 基本使用

布局需要使用 <layout> 标签包裹。

1
2
3
4
5
6
7
8
9
class DemoActivity extends AppCompatActivity {

@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.demo_activity);
ActivityDemoBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_demo);
binding.tvDemo.setText("Demo");
}
}

Data Binding 原理

自动查找所有 View 并缓存到 binding 实例中以供访问。性能超过手写的 findViewById,因为它只遍历了一遍 XML 布局,而 findViewById 每次都会去遍历 XML 布局;include 布局中的 view 也能同样能访问,并且保留结构。

Kotlin Android Extensions

直接生成对应的 View 作为属性,不需要 findViewById,不需要定义变量,直接使用。使用时需要注意访问的 View 属于哪个 Layout,因为智能提示的候选项会提供所有布局中的 View 供你选择,然后帮你 import 对应包以便你访问这个 View;假如 import 的多个同一层级的 layout 中具有相同的 id,则这个 id 对应的 View 将无法访问。

Kotlin Android Extensions synthetic 基本使用

1
2
3
4
5
6
7
8
9
10
11

import kotlinx.android.synthetic.main.activity_demo.*

class DemoActivity : AppCompatActivity() { {

@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.demo_activity);
tvDemo.setText("Demo");
}
}

Kotlin Android Extensions synthetic 原理

Kotlin 会自动生成类似 findViewById() 的方法:findCachedViewById(),在这个方法里面创建一个 HashMap 缓存每次查找到的 View,避免每次调用 View 的属性或方法时都会重新调用 findCachedViewById() 进行查找。

PS:在 Kotlin 1.4.20-M2 中,JetBrain s废弃了 Kotlin Android Extensions 编译插件。推荐使用 ViewBinding。

(推荐使用)ViewBinding

ViewBinding 基本使用

Android Studio 3.6 Canary 11 及更高版本中可用。

1
2
3
4
5
6
android {
...
viewBinding {
enabled = true
}
}
1
2
3
4
5
6
7
8
9
10
11
class DemoActivity : AppCompatActivity() { {

private lateinit var binding: ActivityDemoBinding

@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityDemoBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvDemo.setText("Demo");
}
}

ViewBinding 原理

ViewBinding 优缺点

优点

  • Null 安全:由于视图绑定会创建对视图的直接引用,因此不存在因视图 ID 无效而引发 Null 指针异常的风险。此外,如果视图仅出现在布局的某些配置中,则绑定类中包含其引用的字段会使用 @Nullable 标记。

  • 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。

  • 更快的编译速度:视图绑定不需要处理注释,因此编译时间更短。

  • 易于使用:视图绑定不需要特别标记的 XML 布局文件,因此在应用中采用速度更快。在模块中启用视图绑定后,它会自动应用于该模块的所有布局。

缺点与限制

  • 布局和代码之间的不兼容性可能会导致编译版本在编译时(而非运行时)失败。
  • 视图绑定不支持布局变量或布局表达式,因此不能用于直接在 XML 布局文件中声明动态界面内容。
  • 视图绑定不支持双向数据绑定。

相关文章

使用视图绑定替代 findViewById
你好, View Binding! 再次再见, findViewById!
Kotlin 干掉了 findViewById,但用不好也会有性能问题
Migrating the deprecated Kotlin Android Extensions compiler plugin
【译】迁移被废弃的Kotlin Android Extensions插件