Android Data Binding 在 library module 中遇到错误及解决办法
作者:魔都三帅 发布时间:2021-08-31 12:16:31
记一次 Data Binding 在 library module 中遇到的大坑
使用 Data Binding 也有半年多了,从最初的 setVariable,替换 findViewById,到比较高级的双向绑定,自定义 Adapter、Component,查看源码了解编译、运行流程,也算是小有成果,且没有碰到 Data Binding 本身实现上的问题。
然而,最近在一次重构组件化(见 MDCC 上冯森林的《回归初心,从容器化到组件化》)的过程中,碰到了一个比较严重的 BUG。已经提交 issue(#224048)到了 AOSP,虽然改起来是不麻烦,但是因为是 gradle plugin,所以 - -,还是让 Google 自己来吧。希望能早日修复。
Library module 生成 class
在 library module 下启用 Data Binding 很简单,跟 application module 一样,加上:
android {
dataBinding {
enabled = true
}
}
对应生成的 binding 类会在 manifest 里面指定的 package name 下的 databinding 包下。
坑
于是坑的地方就在这里了,编译不过了…
为啥呢?报错说 symbol 找不到…于是在 module 的 build 下查看生成的 Binding 类… * ?!怎么是 abstract 的?怎么都找不到那些 get 方法了?虽然我也不知道为什么我们会从 binding 类里面去拿之前 set 进去的 ViewModel。
WTF?!
What happened
Fuck 归 fuck,究竟怎么回事还是要研究一下的。
是我们姿势错了?Dagger2 生成哪里出问题了?还是 Data Binding 的 bug 呢?
因为之前也研究过 data binding 生成部分的代码,所以找到问题所在没有花太多时间,这里不多啰嗦,直接看对应位置。
在 CompilerChief 的 writeViewBinderInterfaces 中:
public void writeViewBinderInterfaces(boolean isLibrary) {
ensureDataBinder();
mDataBinder.writerBaseClasses(isLibrary);
}
对应 DataBinder:
public void writerBaseClasses(boolean isLibrary) {
for (LayoutBinder layoutBinder : mLayoutBinders) {
try {
Scope.enter(layoutBinder);
if (isLibrary || layoutBinder.hasVariations()) {
String className = layoutBinder.getClassName();
String canonicalName = layoutBinder.getPackage() + "." + className;
if (mWrittenClasses.contains(canonicalName)) {
continue;
}
L.d("writing data binder base %s", canonicalName);
mFileWriter.writeToFile(canonicalName,
layoutBinder.writeViewBinderBaseClass(isLibrary));
mWrittenClasses.add(canonicalName);
}
} catch (ScopedException ex){
Scope.defer(ex);
} finally {
Scope.exit();
}
}
}
这里调用了 LayoutBinder(真正的实现类会调用 writeViewBinder):
public String writeViewBinderBaseClass(boolean forLibrary) {
ensureWriter();
return mWriter.writeBaseClass(forLibrary);
}
可以看到如果是 library module,我们会做特殊的编译,而不会生成真正的实现:
public fun writeBaseClass(forLibrary : Boolean) : String =
kcode("package ${layoutBinder.`package`};") {
Scope.reset()
nl("import android.databinding.Bindable;")
nl("import android.databinding.DataBindingUtil;")
nl("import android.databinding.ViewDataBinding;")
nl("public abstract class $baseClassName extends ViewDataBinding {")
layoutBinder.sortedTargets.filter{it.id != null}.forEach {
tab("public final ${it.interfaceClass} ${it.fieldName};")
}
nl("")
tab("protected $baseClassName(android.databinding.DataBindingComponent bindingComponent, android.view.View root_, int localFieldCount") {
layoutBinder.sortedTargets.filter{it.id != null}.forEach {
tab(", ${it.interfaceClass} ${it.constructorParamName}")
}
}
tab(") {") {
tab("super(bindingComponent, root_, localFieldCount);")
layoutBinder.sortedTargets.filter{it.id != null}.forEach {
tab("this.${it.fieldName} = ${it.constructorParamName};")
}
}
tab("}")
nl("")
variables.forEach {
if (it.userDefinedType != null) {
val type = ModelAnalyzer.getInstance().applyImports(it.userDefinedType, model.imports)
tab("public abstract void ${it.setterName}($type ${it.readableName});")
}
}
tab("public static $baseClassName inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot) {") {
tab("return inflate(inflater, root, attachToRoot, android.databinding.DataBindingUtil.getDefaultComponent());")
}
tab("}")
tab("public static $baseClassName inflate(android.view.LayoutInflater inflater) {") {
tab("return inflate(inflater, android.databinding.DataBindingUtil.getDefaultComponent());")
}
tab("}")
tab("public static $baseClassName bind(android.view.View view) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return bind(view, android.databinding.DataBindingUtil.getDefaultComponent());")
}
}
tab("}")
tab("public static $baseClassName inflate(android.view.LayoutInflater inflater, android.view.ViewGroup root, boolean attachToRoot, android.databinding.DataBindingComponent bindingComponent) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return DataBindingUtil.<$baseClassName>inflate(inflater, ${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname}, root, attachToRoot, bindingComponent);")
}
}
tab("}")
tab("public static $baseClassName inflate(android.view.LayoutInflater inflater, android.databinding.DataBindingComponent bindingComponent) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return DataBindingUtil.<$baseClassName>inflate(inflater, ${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname}, null, false, bindingComponent);")
}
}
tab("}")
tab("public static $baseClassName bind(android.view.View view, android.databinding.DataBindingComponent bindingComponent) {") {
if (forLibrary) {
tab("return null;")
} else {
tab("return ($baseClassName)bind(bindingComponent, view, ${layoutBinder.modulePackage}.R.layout.${layoutBinder.layoutname});")
}
}
tab("}")
nl("}")
}.generate()
}
那么问题来了,这里的这个只是用来使 library module 编译能通过的 abstract class,只生成了所有 variable 的 setter 方法啊,getter 呢?坑爹呢?
看来是 Google 压根没考虑到还需要这个。写 Kotlin 的都少根筋吗?
规避方案
为了让 library module 能编译通过(这样才能在 application module 生成真正的 Binding 实现),只好避免使用 getter 方法,幸而通过之前开发的 DataBindingAdapter 和 lambda presenter 确实能规避使用 getter 去拿 viewmodel。
不管怎么说,希望 Google 能在下个版本修复这个问题。就是 iterator 一下,写个 abstract 接口而已。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
来源:http://blog.csdn.net/marktheone/article/details/54891360


猜你喜欢
- 如何接收Post请求Body里的参数ApiPost测试数据{ "list": [
- 接触过Android开发的同学们都知道在Android中访问程序资源基本都是通过资源ID来访问。这样开发起来很简单,并且可以不去考虑各种分辨
- 这篇文章主要介绍了spring boot如何指定启动端口,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- 本文实例为大家分享了flutter实现底部导航栏切换的具体代码,供大家参考,具体内容如下思路:MaterialApp是提供了bottomna
- 话不多说,请看代码:/// <summary> /// 获取客户端IP /// </summary
- 服务提供者@GetMapping("/{id}") public void queryJobInfoLogD
- 在Android 4.4系统中,外置存储卡(SD卡)被称为二级外部存储设备(secondary storage),应用程序已无法往外置存储卡
- 阿里、华为、腾讯Java技术面试题精选,具体内容如下JVM的类加载机制是什么?有哪些实现方式?类加载机制:类的加载指的是将类的.class文
- JVM默认物理内存JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。
- 目前,比较常用的实现Java导入、导出Excel的技术有两种Jakarta POI和Java Excel直接上代码:一,POIPOI是apa
- 1.引言在开发过程中,我们经常会遇到需要显示或隐藏View视图的情况,如果在隐藏或显示View的过程中加上动画,能让交互更加的友好和动感,本
- 构造函数、析构函数构造函数:1.若没提供任何构造函数,则系统会自动提供一个默认的构造函数,初始化所有成员为默认值(引用类型为空引用null,
- 干java 开发这么多年, 之前一直没留意java 进程还区分守护进程和用户进程。守护进程这个概念最早还是在linux系统中接触的,直到近期
- 这篇文章主要介绍了基于Java检查IPv6地址的合法性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 示例【通过班级查询老师信息】创建t_classes创建t_classessTeacher创建t_teacher创建Classespackag
- 最近在开发的过程中,遇到了一个需要截取屏幕保存为图片的需求,具体为截取webview的视图保存图片。方法1:首先想到的思路是利用SDK提供的
- 写在前面在平时的开发之中,我们需要对于数据加载的情况进行展示:空数据网络异常加载中等等情况现在设置页面状态的方式有多种,由于笔者近期一直在使
- Java 序列化和反序列化实例详解在分布式应用中,对象只有经过序列化才能在各个分布式组件之间传输,这就涉及到两个方面的技术-发送者将对象序列
- C# 利用代理爬虫网页实现代码:// yanggang@mimvp.com// http://proxy.mimvp.com// 2015-
- Android 设置Edittext获取焦点并弹出软键盘/** * EditText获取焦点并显示软键盘 */