DataBinding 中 Gradle Plugin相关分析,主要实现了以下功能
生成了 xx.xml
以及xx-layout.xml
生成了xxBinding.java
和xxBindingImpl.java
生成了BR.java
和DataBinderMapperImpl.java
以上的文件为后面的Library分析提供了基础保障
相关模块 1 compiler`、 `compilerCommon`、`baseLibrary
资源合并流程 通过Debug GradlePlugin 起点为 MergeResource
相关源码:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/MergeResources.kt
核心类 baselibrary/注解释义 以上标记的注解均来自于baseLibrary
下,接下来对他们进行一些简单的介绍
*Bindable
用于双向绑定 ,主要用于标记model中的getXX/isXX
,并且model必须继承BaseObservable
使用@Bindable
注解的getXX/isXX
,会在BR.java
中生成对应的字段。
1 2 3 4 5 @Target ({ElementType.FIELD, ElementType.METHOD})@Retention (RetentionPolicy.RUNTIME) public @interface Bindable { String[] value() default {}; }
参数
示例代码
Java实现
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 public class JavaBook extends BaseObservable { private String name; private String author; @Bindable public String getName () { return name; } public void setName (String name) { this .name = name; notifyPropertyChanged(BR.name); } @Bindable public String getAuthor () { return author; } public void setAuthor (String author) { this .author = author; notifyPropertyChanged(BR.author); } } val books = JavaBook() books.name = "DataBinding-Java" binding.javaBook = books
Kotlin实现
由于kotlin没有get/set方法,没法使用@Bindable
和notifyPropertyChanged
。需要采用以下方法
1 2 3 4 5 6 7 8 9 class Book { val name:ObservableField<String> by lazy { ObservableField<String>() } val author:ObservableField<String> by lazy { ObservableField<String>() } } val book = Book() book.name.set ("DataBinding" ) binding.book = book
*BindingAdapter
属性设置预处理,主要对View的属性进行赋值
可以对某些属性需要自定义处理逻辑时调用
1 2 3 4 5 6 7 @Target (ElementType.METHOD)public @interface BindingAdapter { String[] value(); boolean requireAll () default true ; }
参数
使用场景
对View已有属性进行自定义逻辑处理
例如设置 @BindingAdapter(“android:text”)
自定义属性进行逻辑处理
如下示例所示
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @BindingAdapter (value = {"imageUrl" , "placeholder" , "error" },requireAll = false )public static oid loadImage (ImageView view, String url, Drawable placeholder, Drawable error) { RequestOptions options = new RequestOptions(); options.placeholder(placeholder); options.error(error); Glide.with(view).load(url).apply(options).into(view); } <ImageView android:layout_width="100dp" android:layout_height="100dp" android:layout_marginTop="10dp" app:imageUrl="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}" app:placeholder="@{@drawable/icon}" />
BindingMethods
当View中某个属性与该属性对应的set方法名称不对应时可以进行映射
@BindingMethods
只是一个容器,需要配合@BindingMethod
进行使用
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 @Target ({ElementType.TYPE})public @interface BindingMethods { BindingMethod[] value(); }@Target (ElementType.ANNOTATION_TYPE)public @interface BindingMethod { Class type () ; String attribute () ; String method () ; }
参数
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @BindingMethods ({ @BindingMethod (type = TextView.class , attribute = "android:autoLink" , method = "setAutoLinkMask" ), @BindingMethod (type = TextView.class , attribute = "android:drawablePadding" , method = "setCompoundDrawablePadding" ), @BindingMethod (type = TextView.class , attribute = "android:editorExtras" , method = "setInputExtras" ), @BindingMethod (type = TextView.class , attribute = "android:inputType" , method = "setRawInputType" ), @BindingMethod (type = TextView.class , attribute = "android:scrollHorizontally" , method = "setHorizontallyScrolling" ), @BindingMethod (type = TextView.class , attribute = "android:textAllCaps" , method = "setAllCaps" ), @BindingMethod (type = TextView.class , attribute = "android:textColorHighlight" , method = "setHighlightColor" ), @BindingMethod (type = TextView.class , attribute = "android:textColorHint" , method = "setHintTextColor" ), @BindingMethod (type = TextView.class , attribute = "android:textColorLink" , method = "setLinkTextColor" ), @BindingMethod (type = TextView.class , attribute = "android:onEditorAction" , method = "setOnEditorActionListener" ), })public class TextViewBindingAdapter { ... }
BindingConversion
可以对数据、类型进行转换
1 2 3 @Target ({ElementType.METHOD})public @interface BindingConversion { }
参数
无
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @BindingConversion fun colorToDrawable (color:String ) :ColorDrawable{ val colorInt = Color.parseColor(color) return ColorDrawable(colorInt) } <ImageView android:layout_width="100dp" android:layout_height="50dp" android:background='@{"#00ff00"}' />
重复定义多次@BindingConversion
方法,默认选择最后一次定义的方法作为全局实现。
InverseBindingAdapter
与BindingAdapter
相反,InverseBindingAdapter
是从View中获取对应属性的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Target ({ElementType.METHOD, ElementType.ANNOTATION_TYPE})public @interface InverseBindingAdapter { String attribute () ; String event () default "" ; }
需要配合InverseBindingListener
进行使用,可以监听到属性的变化
参数
attribute 监听的属性
event 获取值的触发条件
示例代码
InverseBindingMethods
todo
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 @Target (ElementType.TYPE)public @interface InverseBindingMethods { InverseBindingMethod[] value(); }@Target (ElementType.ANNOTATION_TYPE)public @interface InverseBindingMethod { Class type () ; String attribute () ; String event () default "" ; String method () default "" ; }
InverseMethod
todo
1 2 3 4 5 6 7 8 @Target (ElementType.METHOD)@Retention (RetentionPolicy.RUNTIME) public @interface InverseMethod { String value () ; }
TODO
BindingBuildInfo
DataBinding生成相关代码时,主要用来生成相关的dataBinding信息
1 2 3 @Target ({ElementType.TYPE})public @interface BindingBuildInfo { }
Untaggable
DataBinding默认通过给View设置tag
的方式进行标记,后续再根据设置的tag
找到对应的View。
该注解的用处为:针对设置的View不去设置tag
,避免后续操作异常
目前设置了@Untaggable
的有ViewStub
和fragment
1 2 3 4 @Target ({ElementType.TYPE})public @interface Untaggable { String[] value(); }
参数
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 @RestrictTo (RestrictTo.Scope.LIBRARY)@Untaggable ({"android.view.ViewStub" })@BindingMethods ({ @BindingMethod (type = android.view.ViewStub.class , attribute = "android:layout" , method = "setLayoutResource" ) })public class ViewStubBindingAdapter { @BindingAdapter ("android:onInflate" ) public static void setOnInflateListener (ViewStubProxy viewStubProxy, OnInflateListener listener) { viewStubProxy.setOnInflateListener(listener); } }
ProcessDataBinding 根据一般插件开发流程,插件处理类配置在META_INF
下
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 public class ProcessDataBinding extends AbstractProcessor { @Override public boolean process (Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { try { return doProcess(roundEnv); } finally { if (roundEnv.processingOver()) { Context.fullClear(processingEnv); } } } private boolean doProcess (RoundEnvironment roundEnv) { if (mProcessingSteps == null ) { readArguments(); initProcessingSteps(processingEnv); } if (mCompilerArgs == null ) { return false ; } ... } private void initProcessingSteps (ProcessingEnvironment processingEnv) { final ProcessBindable processBindable = new ProcessBindable(); mProcessingSteps = Arrays.asList( new ProcessMethodAdapters(), new ProcessExpressions(), processBindable ); ... } }
将流程拆分成以下三部分分别进行处理
flowchart LR
A[ProcessDataBinding] --> B[ProcessMethodAdapters]
A[ProcessDataBinding] --> C[ProcessExpressions]
A[ProcessDataBinding] --> D[ProcessBindable]
B --> E[收集特定注解的类,解析后数据存放在BindingAdapterStore中\n生成 -setter_store.json文件]
C --> F[解析layout下xml文件,得到LayoutFileBundle对象\n期间生成了xx.xml xx-layout.xml \n生成了xxBinding.java xxBindingImpl.java]
D --> G[生成BR文件和 DataBinderMapperImpl文件]
ProcessMethodAdapters
搜索工程下的所有类,找出包含以下注解的相关类
@BindingAdapter
@BindingMethods
@BindingConversion
@Untaggable
@InverseBindingAdapter
@InverseBindingMethods
@InverseMethod
找到以上注解类后,将解析后的信息放在BindingAdapterStore
中,最后通过Json格式将其保存在-setter_store.json
文件中
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 public class ProcessMethodAdapters extends ProcessDataBinding .ProcessingStep { private final static String INVERSE_BINDING_EVENT_ATTR_SUFFIX = "AttrChanged" ; @Override public boolean onHandleStep (RoundEnvironment roundEnv, ProcessingEnvironment processingEnvironment, CompilerArguments args) { L.d("processing adapters" ); final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance(); Preconditions.checkNotNull(modelAnalyzer, "Model analyzer should be" + " initialized first" ); SetterStore store = SetterStore.get(); clearIncrementalClasses(roundEnv, store); addBindingAdapters(roundEnv, processingEnvironment, store); addRenamed(roundEnv, store); addConversions(roundEnv, store); addUntaggable(roundEnv, store); addInverseAdapters(roundEnv, processingEnvironment, store); addInverseBindingMethods(roundEnv, store); addInverseMethods(roundEnv, processingEnvironment, store); try { try { store.write(args.getModulePackage()); } catch (IOException e) { L.e(e, "Could not write BindingAdapter intermediate file." ); } } catch (LoggedErrorException e) { } return true ; } ... }
拿出相对常用的BindingAdapter
进行分析
addBindingAdapters 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 private void addBindingAdapters (RoundEnvironment roundEnv, ProcessingEnvironment processingEnv, SetterStore store) { LibTypes libTypes = ModelAnalyzer.getInstance().libTypes; Class<? extends Annotation> adapterAnnotation = libTypes.getBindingAdapterClass(); for (Element element : AnnotationUtil .getElementsAnnotatedWith(roundEnv, adapterAnnotation)) { try { if (element.getKind() != ElementKind.METHOD || !element.getModifiers().contains(Modifier.PUBLIC)) { L.e(element, "@BindingAdapter on invalid element: %s" , element); continue ; } BindingAdapterCompat bindingAdapter = BindingAdapterCompat.create(element); ExecutableElement executableElement = (ExecutableElement) element; List<? extends VariableElement> parameters = executableElement.getParameters(); if (bindingAdapter.getAttributes().length == 0 ) { L.e(element, "@BindingAdapter requires at least one attribute. %s" , element); continue ; } final boolean takesComponent = takesComponent(executableElement, processingEnv); final int startIndex = 1 + (takesComponent ? 1 : 0 ); final int numAttributes = bindingAdapter.getAttributes().length; final int numAdditionalArgs = parameters.size() - startIndex; ... try { if (numAttributes == 1 ) { final String attribute = bindingAdapter.getAttributes()[0 ]; store.addBindingAdapter(processingEnv, attribute, executableElement, takesComponent); } else { store.addBindingAdapter(processingEnv, bindingAdapter.getAttributes(), executableElement, takesComponent, bindingAdapter.getRequireAll()); } } catch (IllegalArgumentException e) { L.e(element, "@BindingAdapter for duplicate View and parameter type: %s" , element); } } catch (LoggedErrorException e) { } } }
SetterStore.addBindingAdapter 单入参处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void addBindingAdapter (ProcessingEnvironment processingEnv, String attribute, ExecutableElement bindingMethod, boolean takesComponent) { attribute = stripNamespace(attribute); List<? extends VariableElement> parameters = bindingMethod.getParameters(); final int viewIndex = takesComponent ? 1 : 0 ; TypeMirror viewType = eraseType(processingEnv, parameters.get(viewIndex).asType()); String view = getQualifiedName(viewType); TypeMirror parameterType = eraseType(processingEnv, parameters.get(viewIndex + 1 ).asType()); String value = getQualifiedName(parameterType); AccessorKey key = new AccessorKey(view, value); MethodDescription desc = new MethodDescription(bindingMethod, 1 , takesComponent); mStore.addBindingAdapter(attribute, key, desc); }
相关的两个类为AccessorKey
、MethodDescription
,决定在-setter_store.json
保存的数据格式
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 static class AccessorKey implements Serializable , Comparable <AccessorKey > { public final String viewType; public final String valueType; public AccessorKey (String viewType, String valueType) { this .viewType = viewType; this .valueType = valueType; } ... } static class MethodDescription implements Serializable , Comparable <MethodDescription > { public final String type; public final String method; public final boolean requiresOldValue; public final boolean isStatic; public final String componentClass; public MethodDescription (String type, String method) { this .type = type; this .method = method; this .requiresOldValue = false ; this .isStatic = true ; this .componentClass = null ; } ... }
对应生成的json格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 "adapterMethods": { "setShape": [ [ { "viewType": "android.view.View", "valueType": "com.example.behaviordemo.bindingadapter.ShapeBuilder" }, { "type": "com.example.behaviordemo.bindingadapter.ViewExpressionKt", "method": "setShape", "requiresOldValue": false, "isStatic": true, "componentClass": null } ] ] }
多入参处理 1 2 3 4 5 6 7 8 9 10 public void addBindingAdapter (ProcessingEnvironment processingEnv, String[] attributes, ExecutableElement bindingMethod, boolean takesComponent, boolean requireAll) { L.d("STORE add multi-value BindingAdapter %d %s" , attributes.length, bindingMethod); MultiValueAdapterKey key = new MultiValueAdapterKey(processingEnv, bindingMethod, attributes, takesComponent, requireAll); testRepeatedAttributes(key, bindingMethod); MethodDescription methodDescription = new MethodDescription(bindingMethod, attributes.length, takesComponent); mStore.addMultiValueAdapter(key, methodDescription); }
相关的两个类为MethodDescription
、MultiValueAdapterKey
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static class MultiValueAdapterKey implements Serializable , Comparable <MultiValueAdapterKey > { private static final long serialVersionUID = 1 ; public final String viewType; public final String[] attributes; public final String[] parameterTypes; public final boolean requireAll; public final TreeMap<String, Integer> attributeIndices = new TreeMap<>(); }
对应生成的json格式
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 "multiValueAdapters": [ [ { "viewType": "android.widget.TextView", "attributes": [ "android:beforeTextChanged", "android:onTextChanged", "android:afterTextChanged", "android:textAttrChanged" ], "parameterTypes": [ "androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged", "androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged", "androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged", "androidx.databinding.InverseBindingListener" ], "requireAll": false, "attributeIndices": { "android:afterTextChanged": 2, "android:beforeTextChanged": 0, "android:onTextChanged": 1, "android:textAttrChanged": 3 } }, { "type": "com.example.behaviordemo.bindingadapter.ViewExpressionKt", "method": "setTextChange", "requiresOldValue": false, "isStatic": true, "componentClass": null } ] ]
生产对应的类后,需要将他们写入json文件进行存储
SetterStore.write 1 2 3 4 5 6 7 8 9 10 public void write (String projectPackage) throws IOException { Preconditions.checkNotNull(mStore.getCurrentModuleStore(), "current module store should not be null" ); GenerationalClassUtil.get().write( projectPackage, GenerationalClassUtil.ExtensionFilter.SETTER_STORE_JSON, mStore.getCurrentModuleStore()); }
调用GenerationalClassUtil
的write()写入文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 fun write (pkg:String, ext : ExtensionFilter, item: Any) { L.d("writing output file for %s, %s into %s" , pkg, ext, outputDir) try { Preconditions.checkNotNull(outputDir, "incremental out directory should be" + " set to aar output directory." ) outputDir!!.mkdirs() val outFile = File(outputDir, "$pkg${ext.ext}" ) if (ext.isJson) { outFile.bufferedWriter(Charsets.UTF_8).use { GSON.toJson(item, it) } } else { outFile.outputStream().use { ObjectOutputStream(it).use { it.writeObject(item) } } } L.d("done writing output file %s into %s" , pkg, outFile.canonicalPath) } catch (t : Throwable) { L.e(t, "cannot write file $pkg $ext" ) } }
参数主要有三个
最终生成的-setter_sotre.json
格式如下,存储路径为./build/intermediates/data_binding_artifact/debug/kaptDebugKotlin/XX-setter_store.json
1 2 3 4 5 6 7 8 9 10 11 12 { "version" : 5 , "adapterMethods" : {}, "renamedMethods" : {}, "conversionMethods" : {}, "untaggableTypes" : {}, "multiValueAdapters" : {}, "inverseAdapters" : {}, "inverseMethods" : {}, "twoWayMethods" : {}, "useAndroidX" : true }
flowchart TD
A[ProcessMethodAdapters.onHandlerStep] --> B[addBindingAdapters]
B --> C[bindingAdapter.getAttributes 获取入参个数]
C --> D[入参个数>1]
D --> | 个数为1| E[SetterStore.addBindingAdapter String attribute]
D --> | 个数大于1| F[SetterStore.addBindingAdapter String数组 attribute]
E --> G[得到AccessorKey & MethodDescription]
G --> H[单入参Json格式]
F --> I[得到MultiValueAdapterKey & MethodDescription]
I --> J[多入参Json格式]
H --> K[SetterStore.write]
J --> K
K --> L[GenerationClassUtil.write]
L --> M[写入路径为 -setter_store.json 写入内容为 BingingAdapterStore Json格式]
ProcessExpressions
检索所有layout
目录下的xml文件,把这个xml文件拆分为两个文件 //找到xml中最外层为<layout></layout>
的xml文件?
XX.xml 正常的布局文件 路径./build/intermediates/incremental/debug/mergeDebugResources/stripped.dir/layout/fragment_test_db.xml
XX-layout.xml 包含了绑定信息的xml文件 ./build/intermediates/data_binding_layout_info_type_merge/debug/out/fragment_test_db-layout.xml
生成上述两个文件后,继续生成以下文件
XXBinding.java 抽象类 路径./build/generated/data_binding_base_class_source_out/debug/out/com/example/behaviordemo/databinding/FragmentTestDbBinding.java
XXBindingImpl.java 继承自 XXBinding ./build/generated/source/kapt/debug/com/example/behaviordemo/databinding/FragmentTestDbBindingImpl.java
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 public class ProcessExpressions extends ProcessDataBinding .ProcessingStep { ... @Override public boolean onHandleStep (RoundEnvironment roundEnvironment, ProcessingEnvironment processingEnvironment, CompilerArguments args) throws JAXBException { try { ResourceBundle resourceBundle; resourceBundle = new ResourceBundle( args.getModulePackage(), ModelAnalyzer.getInstance().libTypes.getUseAndroidX()); L.d("creating resource bundle for %s" , args.getModulePackage()); final List<IntermediateV2> intermediateList; GenClassInfoLog infoLog = null ; @Nullable CompilerChef v1CompatChef = null ; ... IntermediateV2 mine = createIntermediateFromLayouts(args.getLayoutInfoDir(), intermediateList); if (mine != null ) { if (!args.isEnableV2()) { mine.updateOverridden(resourceBundle); intermediateList.add(mine); saveIntermediate(args, mine); } mine.appendTo(resourceBundle, true ); } try { writeResourceBundle(resourceBundle, args, infoLog, v1CompatChef); } catch (Throwable t) { L.e(t, "cannot generate view binders" ); } } catch (LoggedErrorException e) { } return true ; } }
收集Xml中expression信息
主要为了得到LayoutFileBundle
对象,用于后续生成相关文件
LayoutXmlProcessor.processResources 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 private static final FilenameFilter LAYOUT_FOLDER_FILTER = (dir, name) -> name.startsWith("layout" ); private static final FilenameFilter XML_FILE_FILTER = (dir, name) -> name.toLowerCase().endsWith(".xml" ); public boolean processResources ( ResourceInput input, boolean isViewBindingEnabled, boolean isDataBindingEnabled) throws ParserConfigurationException, SAXException, XPathExpressionException, IOException { final URI inputRootUri = input.getRootInputFolder().toURI(); ProcessFileCallback callback = new ProcessFileCallback() { private File convertToOutFile (File file) { final String subPath = toSystemDependentPath(inputRootUri .relativize(file.toURI()).getPath()); return new File(input.getRootOutputFolder(), subPath); } @Override public void processLayoutFile (File file) throws ParserConfigurationException, SAXException, XPathExpressionException, IOException { processSingleFile(RelativizableFile.fromAbsoluteFile(file, null ), convertToOutFile(file), isViewBindingEnabled, isDataBindingEnabled); } @Override public void processLayoutFolder (File folder) { convertToOutFile(folder).mkdirs(); } ... if (input.isIncremental()) { processIncrementalInputFiles(input, callback); } else { processAllInputFiles(input, callback); } ... private static void processAllInputFiles (ResourceInput input, ProcessFileCallback callback) throws IOException, XPathExpressionException, SAXException, ParserConfigurationException { FileUtils.deleteDirectory(input.getRootOutputFolder()); for (File firstLevel : input.getRootInputFolder().listFiles()) { if (firstLevel.isDirectory()) { if (LAYOUT_FOLDER_FILTER.accept(firstLevel, firstLevel.getName())) { callback.processLayoutFolder(firstLevel); for (File xmlFile : firstLevel.listFiles(XML_FILE_FILTER)) { callback.processLayoutFile(xmlFile); } } else { ... } } else { callback.processOtherRootFile(firstLevel); } } } }
主要流程如下:
找到layout文件夹
创建文件输出目录
寻找 layout目录下以xml结尾的文件
处理xml文件,继续调用到processSingleFile
LayoutFileParser.parseXml 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 public static ResourceBundle.LayoutFileBundle parseXml (@NonNull final RelativizableFile input, @NonNull final File outputFile, @NonNull final String pkg, @NonNull final LayoutXmlProcessor.OriginalFileLookup originalFileLookup, boolean isViewBindingEnabled, boolean isDataBindingEnabled) throws ParserConfigurationException, IOException, SAXException, XPathExpressionException { ... stripFile(inputFile, outputFile, encoding, originalFileLookup); return parseOriginalXml( RelativizableFile.fromAbsoluteFile(originalFile, input.getBaseDir()), pkg, encoding, isViewBindingEnabled, isDataBindingEnabled); } private static void stripFile (File xml, File out, String encoding, LayoutXmlProcessor.OriginalFileLookup originalFileLookup) throws ParserConfigurationException, IOException, SAXException, XPathExpressionException { ... boolean changed = isBindingLayout(doc, xPath); if (changed) { stripBindingTags(xml, out, binderId, encoding); } else if (!xml.equals(out)){ FileUtils.copyFile(xml, out); } } private static void stripBindingTags (File xml, File output, String newTag, String encoding) throws IOException { String res = XmlEditor.strip(xml, newTag, encoding); Preconditions.checkNotNull(res, "layout file should've changed %s" , xml.getAbsolutePath()); if (res != null ) { L.d("file %s has changed, overwriting %s" , xml.getAbsolutePath(), output.getAbsolutePath()); FileUtils.writeStringToFile(output, res, encoding); } } private static ResourceBundle.LayoutFileBundle parseOriginalXml ( @NonNull final RelativizableFile originalFile, @NonNull final String pkg, @NonNull final String encoding, boolean isViewBindingEnabled, boolean isDataBindingEnabled) throws IOException { ... ResourceBundle.LayoutFileBundle bundle = new ResourceBundle.LayoutFileBundle( originalFile, xmlNoExtension, original.getParentFile().getName(), pkg, isMerge, isBindingData, rootViewType, rootViewId); final String newTag = original.getParentFile().getName() + '/' + xmlNoExtension; parseData(original, data, bundle); parseExpressions(newTag, rootView, isMerge, bundle); }
主要流程如下:
调用到parseXml
执行如下两步
stripFile
stripBindingTags
XmlEditor.strip 去除xml中 标签,并且给View设置tag
writeStringToFile 将执行以上操作后的xml文件 写入build/intermediates/incremental/debug/mergeDebugResources/stripped.dir/layout
parseOriginXml
new LayoutFileBundle对象
parseData 解析标签 ,主要是内部的 属性
parseExpressions 解析表达式 ,主要是循环遍历View,主要处理 id、tag binding_id 以及 @{} 这类表达式
得到LayoutFileBundle
对象,里面主要记录了 xml文件中的 Variables Imports 等核心信息
详细信息可查看 compilerCommon/src/main/java/android/databinding/tool/store/ResourceBundle.java
生成Layout xml文件
根据得到的LayoutFileBundle
对象生成xx-layout.xml
文件
MergeResources.xx.end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Throws(JAXBException::class) override fun end () { processor .writeLayoutInfoFiles( dataBindingLayoutInfoOutFolder.get ().asFile ) } artifacts.setInitialProvider(taskProvider) { obj: MergeResources -> obj.dataBindingLayoutInfoOutFolder } .withName("out" ) .on( if (mergeType === TaskManager.MergeType.MERGE) DATA_BINDING_LAYOUT_INFO_TYPE_MERGE else DATA_BINDING_LAYOUT_INFO_TYPE_PACKAGE )
MergeResources
执行到end
后调用writeLayoutInfoFiles
LayoutXmlProcesser.writeLayoutInfoFiles 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public void writeLayoutInfoFiles (File xmlOutDir) throws JAXBException { writeLayoutInfoFiles(xmlOutDir, mFileWriter); }public void writeLayoutInfoFiles (File xmlOutDir, JavaFileWriter writer) throws JAXBException { for (ResourceBundle.LayoutFileBundle layout : mResourceBundle .getAllLayoutFileBundlesInSource()) { writeXmlFile(writer, xmlOutDir, layout); } ... }private void writeXmlFile (JavaFileWriter writer, File xmlOutDir, ResourceBundle.LayoutFileBundle layout) throws JAXBException { String filename = generateExportFileName(layout); writer.writeToFile(new File(xmlOutDir, filename), layout.toXML()); }public static String generateExportFileName (String fileName, String dirName) { return fileName + '-' + dirName + ".xml" ; }
主要流程如下:
遍历LayoutFileBundle对象,并写入文件
generateExportFileName 设置生成的文件名 xx-layout.xml
得到的LayoutFileBundle对象 toXML,后写入 xx-layout.xml文件中
生成的 xx-layout.xml文件内容
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 //build/intermediates/data_binding_layout_info_type_merge/debug/out/fragment_test_db-layout.xml<?xml version="1.0" encoding="utf-8" standalone="yes"?> <Layout directory ="layout" filePath ="app/src/main/res/layout/fragment_test_db.xml" isBindingData ="true" isMerge ="false" layout ="fragment_test_db" modulePackage ="com.example.behaviordemo" rootNodeType ="androidx.constraintlayout.widget.ConstraintLayout" > //参数 <Variables name ="text" declared ="true" type ="String" > <location endLine ="14" endOffset ="27" startLine ="12" startOffset ="8" /> </Variables > ... //引用 <Imports name ="ShapeBuilder" type ="com.example.behaviordemo.bindingadapter.ShapeBuilder" > <location endLine ="8" endOffset ="77" startLine ="8" startOffset ="8" /> </Imports > ... //作用对象 <Targets > <Target tag ="layout/fragment_test_db_0" view ="androidx.constraintlayout.widget.ConstraintLayout" > <Expressions /> <location endLine ="57" endOffset ="55" startLine ="22" startOffset ="4" /> </Target > <Target id ="@+id/tv_txt" tag ="binding_1" view ="TextView" > <Expressions > //xml里写的表达式信息 <Expression attribute ="android:text" text ="text, default = 23456" > <Location endLine ="30" endOffset ="50" startLine ="30" startOffset ="12" /> <TwoWay > false</TwoWay > <ValueLocation endLine ="30" endOffset ="48" startLine ="30" startOffset ="28" /> </Expression > </Expressions > <location endLine ="32" endOffset ="55" startLine ="26" startOffset ="8" /> </Target > </Targets > </Layout >
生成Binding文件
得到LayoutFileBundle
之后,继续生成xxBinding.java文件
ProcessExpressions.writeResourceBundle 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 private void writeResourceBundle ( ResourceBundle resourceBundle, CompilerArguments compilerArgs, @Nullable GenClassInfoLog classInfoLog, @NonNull CompilerChef v1CompatChef) { if (compilerArgs.isLibrary() || (!compilerArgs.isTestVariant() && !compilerArgs.isFeature())) { compilerChef.writeComponent(); } if (compilerChef.hasAnythingToGenerate()) { if (!compilerArgs.isEnableV2()) { compilerChef.writeViewBinderInterfaces(compilerArgs.isLibrary() && !compilerArgs.isTestVariant()); } if (compilerArgs.isApp() != compilerArgs.isTestVariant() || (compilerArgs.isEnabledForTests() && !compilerArgs.isLibrary()) || compilerArgs.isEnableV2()) { compilerChef.writeViewBinders(compilerArgs.getMinApi()); } } }
CompilerChef -> DataBinder -> LayoutBinder 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 public void writeViewBinderInterfaces (boolean isLibrary) { ensureDataBinder(); mDataBinder.writerBaseClasses(isLibrary); } public void writeViewBinders (int minSdk) { ensureDataBinder(); mDataBinder.writeBinders(minSdk); } public void ensureDataBinder () { if (mDataBinder == null ) { LibTypes libTypes = ModelAnalyzer.getInstance().libTypes; mDataBinder = new DataBinder(mResourceBundle, mEnableV2, libTypes); mDataBinder.setFileWriter(mFileWriter); } } public DataBinder (ResourceBundle resourceBundle, boolean enableV2, LibTypes libTypes) { ... for (ResourceBundle.LayoutFileBundle bundle : resourceBundle.getLayoutFileBundlesInSource()) { try { mLayoutBinders.add(new LayoutBinder(bundle, true )); } catch (ScopedException ex) { Scope.defer(ex); } } }
以上几步串联了CompilerChef
与 LayoutBinder
的关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class LayoutBinder implements FileScopeProvider { public LayoutBinder (ResourceBundle.LayoutFileBundle layoutBundle, boolean enableV2) { ... for (ResourceBundle.VariableDeclaration variable : mBundle.getVariables()) { addVariable(variable.name, variable.type, variable.location, variable.declared); names.add(variable.name); } for (ResourceBundle.NameTypeLocation userImport : mBundle.getImports()) { mExprModel.addImport(userImport.name, userImport.type, userImport.location); names.add(userImport.name); } ... } }
LayoutBinderWriter.writeBaseClass - V1 版本生成xxBinding.java文件 1 2 3 4 public fun writeBaseClass (forLibrary: Boolean , variations: List <LayoutBinder >) : String = ...
BaseLayoutBinderWriter.write - V2 版本生成XXBinding.java文件
//build-system/gradle-core/src/main/java/com/android/build/gradle/internal/tasks/databinding/DataBindingGenBaseClassesTask.kt
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 @CacheableTask abstract class DataBindingGenBaseClassesTask : AndroidVariantTask () { @TaskAction fun writeBaseClasses (inputChanges: InputChanges ) { recordTaskAction(analyticsService.get ()) { val args = buildInputArgs(inputChanges) CodeGenerator( args, sourceOutFolder.get ().asFile, Logger.getLogger(DataBindingGenBaseClassesTask::class .java ), encodeErrors , collectResources ()).run() } } class CodeGenerator @Inject constructor ( val args: LayoutInfoInput.Args, private val sourceOutFolder: File, private val logger: Logger, private val encodeErrors: Boolean , private val symbolTables: List<SymbolTable>? = null ) : Runnable, Serializable { override fun run () { try { initLogger() BaseDataBinder( LayoutInfoInput(args), if (symbolTables != null ) this ::getRPackage else null ) .generateAll(DataBindingBuilder.GradleFileWriter(sourceOutFolder.absolutePath)) } finally { clearLogger() } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 fun generateAll (writer : JavaFileWriter ) { if (variations.first().isBindingData) { check(input.args.enableDataBinding) { "Data binding is not enabled but found data binding layouts: $variations " } val binderWriter = BaseLayoutBinderWriter(layoutModel, libTypes) javaFile = binderWriter.write() classInfo = binderWriter.generateClassInfo() } else { check(input.args.enableViewBinding) { "View binding is not enabled but found non-data binding layouts: $variations " } val viewBinder = layoutModel.toViewBinder() javaFile = viewBinder.toJavaFile(useLegacyAnnotations = !useAndroidX) classInfo = viewBinder.generatedClassInfo() } }
LayoutBinderWriter.write - 生成xxBindingImpl.java文件 1 2 3 4 5 fun write (minSdk: kotlin.Int) : String { .. }
//todo 流程图
ProcessBindable
主要是生成BR
和DataBinderMapperImpl
位置分别在:
生成BR文件
主要包含根据<variable>
和@Bindable
注解的字段生成的id
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 public class ProcessBindable extends ProcessDataBinding .ProcessingStep implements BindableHolder { @Override public boolean onHandleStep (RoundEnvironment roundEnv, ProcessingEnvironment processingEnv, CompilerArguments args) { if (mProperties == null ) { mProperties = new IntermediateV1(args.getModulePackage()); mergeLayoutVariables(); ... for (Element element : AnnotationUtil .getElementsAnnotatedWith(roundEnv, libTypes.getBindableClass())) { Element parentElement = element.getEnclosingElement(); ... } } GenerationalClassUtil.get().write(mProperties.getPackage(), GenerationalClassUtil.ExtensionFilter.BR, mProperties); generateBRClasses(processingEnv, args, mProperties.getPackage()); } private void mergeLayoutVariables () { for (String containingClass : mLayoutVariables.keySet()) { for (String variable : mLayoutVariables.get(containingClass)) { mProperties.addProperty(containingClass, variable); } } } private void generateBRClasses ( ProcessingEnvironment processingEnv, CompilerArguments compilerArgs, String pkg) { try { CompilerArguments.Type artifactType = compilerArgs.getArtifactType(); HashSet<String> properties = new HashSet<>(); mProperties.captureProperties(properties); BindableBag bindableBag = new BindableBag( compilerArgs, getProperties(mProperties), processingEnv); final JavaFileWriter writer = getWriter(); boolean useFinal = compilerArgs.isApp() || compilerArgs.isFeature() || compilerArgs.isTestVariant(); BRWriter brWriter = new BRWriter(useFinal); bindableBag.getToBeGenerated().forEach(brWithValues -> { String out = brWriter.write(brWithValues); writer.writeToFile(brWithValues.getPkg() + ".BR" , out); }); mCallback.onBrWriterReady( bindableBag.getVariableIdLookup(), bindableBag.getWrittenPackages()); } catch (LoggedErrorException e) { } } }
BR.java
文件内容如下
1 2 3 4 5 6 7 public class BR { public static final int _all = 0 ; public static final int map = 1 ; public static final int text = 2 ; }
生成DataBinderMapperImpl文件
主要存储tag
对应的xml文件
,BR文件
中id
到属性名
的映射
1 2 3 4 5 6 7 8 9 10 11 12 private void generateBRClasses ( ProcessingEnvironment processingEnv, CompilerArguments compilerArgs, String pkg) { ... mCallback.onBrWriterReady( bindableBag.getVariableIdLookup(), bindableBag.getWrittenPackages()); }
mCallback
位于ProcessingStep
中,在ProcessDataBinding.initProcessingSteps
中赋值
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 private void initProcessingSteps (ProcessingEnvironment processingEnv) { final ProcessBindable processBindable = new ProcessBindable(); mProcessingSteps = Arrays.asList( new ProcessMethodAdapters(), new ProcessExpressions(), processBindable ); Callback dataBinderWriterCallback = new Callback() { CompilerChef mChef; List<String> mModulePackages; BindableBag.BRMapping mBRVariableLookup; boolean mWrittenMapper = false ; @Override public void onChefReady ( @NonNull CompilerChef chef, @Nullable GenClassInfoLog classInfoLog) { Preconditions.checkNull(mChef, "Cannot set compiler chef twice" ); chef.addBRVariables(processBindable); mChef = chef; considerWritingMapper(); } private void considerWritingMapper () { if (mWrittenMapper || mChef == null || mBRVariableLookup == null ) { return ; } boolean justLibrary = mCompilerArgs.isLibrary() && !mCompilerArgs.isTestVariant(); if (justLibrary && !mCompilerArgs.isEnableV2()) { return ; } mWrittenMapper = true ; mChef.writeDataBinderMapper(processingEnv, mCompilerArgs, mBRVariableLookup, mModulePackages); } @Override public void onBrWriterReady (BindableBag.BRMapping brWithValues, List<String> brPackages) { Preconditions.checkNull(mBRVariableLookup, "Cannot set br writer twice" ); mBRVariableLookup = brWithValues; mModulePackages = brPackages; considerWritingMapper(); } }; AnnotationJavaFileWriter javaFileWriter = new AnnotationJavaFileWriter(processingEnv); for (ProcessingStep step : mProcessingSteps) { step.mJavaFileWriter = javaFileWriter; step.mCallback = dataBinderWriterCallback; } }
CompilerChef.writeDataBinderMapper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public void writeDataBinderMapper ( ProcessingEnvironment processingEnv, CompilerArguments compilerArgs, BindableBag.BRMapping brValueLookup, List<String> modulePackages) { if (compilerArgs.isEnableV2()) { ... if (generateMapper) { writeMapperForModule(compilerArgs, brValueLookup, availableDependencyModules); } ... if (generateMergedMapper) { ... writeMergedMapper(compilerArgs); } } }
为module&app
生成DataBinderMapperImpl.java
,其中包含tag
和layout
的映射关系,可以根据tag找到XXBindingImpl
实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void writeMapperForModule ( CompilerArguments compilerArgs, BindableBag.BRMapping brValueLookup, Set<String> availableDependencyModules) { ... BindingMapperWriterV2 v2 = new BindingMapperWriterV2( infoLogInThisModule, compilerArgs, libTypes, availableDependencyModules); ... try { JavaFile.builder(v2.getPkg(), spec).build().writeTo(sb); mFileWriter.writeToFile(v2.getQualifiedName(), sb.toString()); } catch (IOException e) { Scope.defer(new ScopedException("cannot generate mapper class" , e)); } }
生成的DataBinderMapperImpl
格式如下
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 74 75 76 77 78 79 80 81 82 83 84 85 86 public class DataBinderMapperImpl extends DataBinderMapper { private static final int LAYOUT_FRAGMENTTESTDB = 1 ; private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1 ); static { INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.behaviordemo.R.layout.fragment_test_db, LAYOUT_FRAGMENTTESTDB); } @Override public ViewDataBinding getDataBinder (DataBindingComponent component, View view, int layoutId) { int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId); if (localizedLayoutId > 0 ) { final Object tag = view.getTag(); if (tag == null ) { throw new RuntimeException("view must have a tag" ); } switch (localizedLayoutId) { case LAYOUT_FRAGMENTTESTDB: { if ("layout/fragment_test_db_0" .equals(tag)) { return new FragmentTestDbBindingImpl(component, view); } throw new IllegalArgumentException("The tag for fragment_test_db is invalid. Received: " + tag); } } } return null ; } @Override public ViewDataBinding getDataBinder (DataBindingComponent component, View[] views, int layoutId) { if (views == null || views.length == 0 ) { return null ; } int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId); if (localizedLayoutId > 0 ) { final Object tag = views[0 ].getTag(); if (tag == null ) { throw new RuntimeException("view must have a tag" ); } switch (localizedLayoutId) { } } return null ; } @Override public int getLayoutId (String tag) { if (tag == null ) { return 0 ; } Integer tmpVal = InnerLayoutIdLookup.sKeys.get(tag); return tmpVal == null ? 0 : tmpVal; } @Override public String convertBrIdToString (int localId) { String tmpVal = InnerBrLookup.sKeys.get(localId); return tmpVal; } @Override public List<DataBinderMapper> collectDependencies () { ArrayList<DataBinderMapper> result = new ArrayList<DataBinderMapper>(1 ); result.add(new androidx.databinding.library.baseAdapters.DataBinderMapperImpl()); return result; } private static class InnerBrLookup { static final SparseArray<String> sKeys = new SparseArray<String>(3 ); static { sKeys.put(0 , "_all" ); sKeys.put(1 , "map" ); sKeys.put(2 , "text" ); } } private static class InnerLayoutIdLookup { static final HashMap<String, Integer> sKeys = new HashMap<String, Integer>(1 ); static { sKeys.put("layout/fragment_test_db_0" , com.example.behaviordemo.R.layout.fragment_test_db); } } }
具体的分析 会在DataBinding-API分析时具体描述
writeMergedMapper
为app
生成DataBinderMapperImpl.java
,其中包含对其他module中的DataBinderMapperImpl.java
文件的映射
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 private void writeMergedMapper (CompilerArguments compilerArgs) { Set<String> featurePackageIds = loadFeaturePackageIds(compilerArgs); StringBuilder sb = new StringBuilder(); LibTypes libTypes = ModelAnalyzer.getInstance().libTypes; MergedBindingMapperWriter mergedBindingMapperWriter = new MergedBindingMapperWriter( compilerArgs, featurePackageIds, mV1CompatChef != null , libTypes); TypeSpec mergedMapperSpec = mergedBindingMapperWriter.write(); try { JavaFile.builder(mergedBindingMapperWriter.getPkg(), mergedMapperSpec) .build().writeTo(sb); mFileWriter.writeToFile(mergedBindingMapperWriter.getQualifiedName(), sb.toString()); } catch (IOException e) { Scope.defer(new ScopedException("cannot generate merged mapper class" , e)); } }
生成DataBinderMapperImpl
格式如下
1 2 3 4 5 public class DataBinderMapperImpl extends MergedDataBinderMapper { DataBinderMapperImpl() { addMapper(new com.example.behaviordemo.DataBinderMapperImpl()); } }
//todo流程图
总结
GradlePlugin主要产出了以下文件,以便后续API功能调用
xx.xml :正常的资源编译文件,后续apk中保留为这份文件。主要是将原始的xml文件中的 标签移除,并未每个view设置tag
xx-layout.xml :记录组件的绑定信息,如标签内容,以及xml使用的表达式
XXBinding.java :记录View的id
以及<data>定义的<variable>参数
,后文会有详细介绍
XXBindingImpl.java :基于XXBinding
的实现类,双向绑定参数的赋值逻辑均在内部实现,后文会有详细介绍
BR.java :记录@Bindable
以及<variable>
相关参数
DataBinderMapperImpl.java
包名为 androidx.databinding.library
:记录项目中ViewDataBinding的映射表,内部主要为 其他module里的DataBinderMapperImpl.java
包名为module 或 app
name:记录module或app
中哪些布局文件使用了DataBinding
,即使用<layout>
包裹
参考链接 DataBinding-注解详解
DataBinding构建过程分析