Android移动架构汇总
文章目录
- 一、控件的声明
- 二、ViewBinding的基本使用
- 三、ViewBinding特点
- 四、ViewBinding的封装
- 五、源码
一、控件的声明
在Activity中绑定布局中的控件一般有三种实现方式:
- 第一种用最原生态的findViewById方法来绑定
- 第二种方式可以使用ButterKnife开源框架实现,ButterKnife对组件化的支持却很不友好
- 第三种方式是使用Kotlin的扩展插件来获取视图控件,使用局限性:无法跨模块操作,类型不安全:不同的资源文件可以存在相同的控件id,因此在View层存在引用id来源出错的问题。
Kotlin 1.4版本中废弃了扩展插件,Google推荐使用ViewBinding来替代废弃的扩展插件
二、ViewBinding的基本使用
build.gradle中添加如下配置:
android {...viewBinding {enabled = true}...
}
配置完成后,系统会为该模块中的每个XML布局文件生成一个绑定类,这个绑定类的命名就是XML文件的名称转换为驼峰式,并在末尾添加“Binding”一词,直接可以通过view binding这一中间类获取到xml中定义的view组件了
@Override
public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// setContentView(R.layout.activity_new_stock_bjs_records);viewBinding = ActivityNewStockBjsRecordsBinding.inflate(getLayoutInflater());viewBinding.navigationBar.setText("北交所新股申购")}
需要注意的一点是,开发者需要在onDestroyView()方法中将绑定类实例赋值为null。Fragment的存在时间比其视图时间长,所以开发者需要在onDestroyView()方法中清除对绑定类实例的所有引用,否则可能存在内存泄漏的风险
注意
如果某个布局文件不需要的话,可以通过tools:viewBinding-Ignore=“true”属性来设置
三、ViewBinding特点
- 集成简单
- 代码简洁,维护容易
- 对象空值安全:由于视图绑定会对视图直接引用,因此不存在因视图id无效而引发空指针异常的风险。
- 类型安全:每个绑定类中的字段均具有与它们在xml文件中引用的视图相匹配的类型,因此不存在强制转换可能导致的异常问题
四、ViewBinding的封装
ViewBinding组件的使用流程基本是固定的,主要分为三步:
- 调用生成的绑定类中的inflate()方法来获取绑定类的实例。
- 通过调用绑定类的getRoot()方法获取对根视图。
- 将根视图传递到setContentView()中,并与当前Activity绑定。
由于ViewBinding使用的流程是固定的,因此在基础业务的开发中,经常会定义一个BaseActivity处理所有Activity的相同业务逻辑,这时,就可以将这部分逻辑封装在BaseActivity中
/*** 使用ViewBinding的基础类** @param <T>*/
public abstract class ViewBindingBaseActivity<T extends ViewBinding> extends ActivityBase {protected T mViewBinding;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);mViewBinding = getViewBinding();setContentView(mViewBinding.getRoot());}/*** @return 返回Activity对于xml生成的ViewBinding对象*/public abstract T getViewBinding();}
五、源码
在项目模块的build.gradle中开启ViewBinding功能之后,若进行项目编译,就会扫描layout下所有的布局文件,并生成对应的绑定类(Project视图:
app/build/generated/data_binding_base_class_source_out/debug/out/包名/databinding路径下)。这一点是由gradle插件实现的
public final class ActivityTestBinding implements ViewBinding {@NonNullprivate final LinearLayout rootView;@NonNullpublic final ImageView imShezhi;@NonNullpublic final HomeRefresh reRefresh;@NonNullpublic final RecyclerView rv;@NonNullpublic final TextView tvName;private ActivityTestBinding(@NonNull LinearLayout rootView, @NonNull ImageView imShezhi,@NonNull HomeRefresh reRefresh, @NonNull RecyclerView rv, @NonNull TextView tvName) {this.rootView = rootView;this.imShezhi = imShezhi;this.reRefresh = reRefresh;this.rv = rv;this.tvName = tvName;}@Override@NonNullpublic LinearLayout getRoot() {return rootView;}@NonNullpublic static ActivityTestBinding inflate(@NonNull LayoutInflater inflater) {return inflate(inflater, null, false);}@NonNullpublic static ActivityTestBinding inflate(@NonNull LayoutInflater inflater,@Nullable ViewGroup parent, boolean attachToParent) {View root = inflater.inflate(R.layout.activity_test, parent, false);if (attachToParent) {parent.addView(root);}return bind(root);}@NonNullpublic static ActivityTestBinding bind(@NonNull View rootView) {// The body of this method is generated in a way you would not otherwise write.// This is done to optimize the compiled bytecode for size and performance.String missingId;missingId: {ImageView imShezhi = rootView.findViewById(R.id.im_shezhi);if (imShezhi == null) {missingId = "imShezhi";break missingId;}HomeRefresh reRefresh = rootView.findViewById(R.id.re_refresh);if (reRefresh == null) {missingId = "reRefresh";break missingId;}RecyclerView rv = rootView.findViewById(R.id.rv);if (rv == null) {missingId = "rv";break missingId;}TextView tvName = rootView.findViewById(R.id.tv_name);if (tvName == null) {missingId = "tvName";break missingId;}return new ActivityTestBinding((LinearLayout) rootView, imShezhi, reRefresh, rv, tvName);}throw new NullPointerException("Missing required view with ID: ".concat(missingId));}
}
从生成的ActivityTestBinding文件中可以轻松地看出,在调用了inflate之后会调用bind方法,而bind方法依然是通过findViewById绑定