您好,欢迎来到源码搜藏!分享精神,快乐你我!提示:担心找不到本站?在百度搜索“源码搜藏”,网址永远不丢失!
  • 首 页
  • 在线工具
  • 当前位置:首页 > 安卓源码 > 技术博客 >

    Data Binding 在 library module 中遇到的大坑解决

    时间:2016-12-01 23:55 来源:互联网 作者:源码搜藏 浏览:收藏 挑错 推荐 打印

    使用 Data Binding 也有半年多了,从最初的 setVariable,替换 findViewById,到比较高级的双向绑定,自定义 Adapter、Component,查看源码了解编译、运行流程,也算是小有成果,且没有碰到 Data Binding 本身实现上的问题。 然而,最近在一次重构组件化(见

    使用 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 一样,加上:

    1
    2
    3
    4
    5
    
    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 中:

    1
    2
    3
    4
    
    public void writeViewBinderInterfaces(boolean isLibrary) {
        ensureDataBinder();
        mDataBinder.writerBaseClasses(isLibrary);
    }
    

    对应 DataBinder:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    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):

    1
    2
    3
    4
    
    public String writeViewBinderBaseClass(boolean forLibrary) {
        ensureWriter();
        return mWriter.writeBaseClass(forLibrary);
    }
    

    可以看到如果是 library module,我们会做特殊的编译,而不会生成真正的实现:

    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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    
    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 接口而已

    Data Binding 在 library module 中遇到的大坑解决转载http://www.codesocang.com/anzhuoyuanma/boke/33918.html
    标签:网站源码