前两篇讲了 ViewBinding和DataBinding的使用和绑定原理。但是有一个很重要的问题没有将,就是生成的代码是如何生成的,什么时候生成的?这篇讲的是 XxxXxxBinding.java
是如何生成的。
其中用到示例在 BindingSample 可以查到,包括 ProcessDataBinding
等代码
DataBinding 包含三篇博客:
DataBinding(含ViewBinding)(一)使用
DataBinding(含ViewBinding)(二)绑定原理
一、Task
// ViewBinding
Task :viewbinding:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
Task :viewbinding:dataBindingMergeGenClassesDebug UP-TO-DATE
Task :viewbinding:dataBindingGenBaseClassesDebug UP-TO-DATE
// DataBinding
Task :app:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
Task :app:dataBindingMergeGenClassesDebug UP-TO-DATE
Task :app:dataBindingTriggerDebug UP-TO-DATE
Task :app:dataBindingGenBaseClassesDebug
这个task分别有对应如下:
这是位于 com.android.tools.build:gradle:4.2.0 包下的。在 module 中的 build.gradle中加入如下依赖就可以查看图片中的 task,不过加入这个会导致重复某些类重复依赖。
dependencies {
//……
implementation 'com.android.tools.build:gradle:4.2.0'
}
简单看一下task的创建流程
graph TD
AppPlugin --> BasePlugin.apply --> BasePlugin.basePluginApply --> configuratorService.recordBlock --> BasePlugin.createTasks --> BasePlugin.createAndroidTasks --> TaskManager.createTask --> TaskManager.createTasksForVariant --> ApplicationTaskManager.doCreateTasksForVariant --> AbstractAppTaskManager.createCommonTasks --> TaskManager.createDataBindingTasksIfNecessary
上线整个流程图是创建DataBinding相关的Task,具体源码就不展示,可以自己看源码,或者参考Android Gradle Plugin源码分析,不过起源码是旧版,已有不少改动。
debug 任务的执行流程
关于 gradle plugin 执行流程可以打断点来查看执行流程。
1. 引入文件
dependencies {
//……
implementation 'com.android.tools.build:gradle:4.2.0'
}
2. 添加断点
如图中在会执行的 ApplicationTaskManager
中设置断点。
3. debug
可以使用图中的两种方式进行调试,左边的是全量调试,右边的是单个task调试,如果左边的调试出现错误就可以用右边的调试,右边task位于 Gradle > Project > moduleName > Tasks > other > xxx
。运行debug后就可以进入断点调试。
资源合并流程
资源合并流程图:
graph TD
MergeResources.doIncrementalTaskAction --> ResourceMerger.mergeData
MergeResources.doFullTaskAction --> ResourceMerger.mergeData --> MergedResourceWriter --> MergeResources.maybeCreateLayoutProcessor --> LayoutXmlProcessor.processSingleFile --> LayoutXmlProcessor.End
执行 dataBindingGenBaseClassesDebug
task 合并 dataBinding资源的流程大体上如上图。其中还有一些是不一样的。
在 MergedResourceWriter.end()
方法中会做如下判断,看是否需要解析dataBinding数据
if (dataBindingExpressionRemover != null
&& request.getInputDirectoryName().startsWith("layout")
&& request.getInputFile().getName().endsWith(".xml")) {
- 首先判断
dataBindingExpressionRemover != null
即SingleFileProcessor != null
转换成LayoutXmlProcessor != null
request.getInputDirectoryName().startsWith("layout")
判断存放文件的文件夹名是不是layout
开始的- 判断文件名是不是已xml结束
如果以上三个条件都满足就去进入
LayoutXmlProcessor
进行解析文件,LayoutXmlProcessor.End
将 xxx_xxx-layout.xml 写入 Project\module\build\intermediates\data_binding_layout_info_type_merge\debug\out\中
在 MergeResources.doIncrementalTaskAction
增量任务或 MergeResources.doFullTaskAction
全量任务中是以for循环的方式合并所有的资源文件。
上面的过程可以通过选择 dataBindingGenBaseClassesDebug
右键执行 Debug ModuleProject
来查看运行步骤。如果不能debug,则可以将module下面的build文件清空,然后再运行查看 编译步骤或者是生成的 XxxBinding.java 文件
对于资源实时编译的理解
当使用了DataBinding或者是ViewBinding后,只要我们新建或者改变布局xml文件,在kotlin或者java文件中就能实时更新,那么理解是android studio有些文件处理进程一直在运行监测资源文件等的变化,当有布局文件有变化时,其就会将增量或全量变化添加进入AndroidStudio运行的内存中,这样我们就可以实时调用。AndroidStudio文件处理器如下图中的 Filesystem events processor:
二、生成文件过程
收集元素
在上一节中分析了在 MergeResources
中是用for循环来遍历所有的资源文件,最后通过 LayoutXmlProcessor
来收集。
LayoutXmlProcessor.java
public boolean processSingleFile(@NonNull RelativizableFile input, @NonNull File output,
boolean isViewBindingEnabled, boolean isDataBindingEnabled)
throws ParserConfigurationException, SAXException, XPathExpressionException,
IOException {
// 解析xml成 LayoutFileBundle
final ResourceBundle.LayoutFileBundle bindingLayout = LayoutFileParser
.parseXml(input, output, mResourceBundle.getAppPackage(), mOriginalFileLookup,
isViewBindingEnabled, isDataBindingEnabled);
if (bindingLayout == null
|| (bindingLayout.isBindingData() && bindingLayout.isEmpty())) {
return false;
}
// 缓存文件
mResourceBundle.addLayoutBundle(bindingLayout, true);
return true;
}
1 拿到layout资源文件的路径和输出路径进行解析,如下:
// RelativizableFile input
absoluteFile = {File@12883} "D:\xxx\module\src\main\res\layout\view_stub.xml"
relativeFile = {File@12884} "module\src\main\res\layout\view_stub.xml"
baseDir = {File@12885} "D:\WorkSpace\BindingSample"
// File output
D:\xxx\module\build\intermediates\incremental\mergeDebugResources\stripped.dir\layout\view_stub.xml
2 将解析的 bindingLayout 存储
LayoutFileParser.java
@Nullable
public static ResourceBundle.LayoutFileBundle parseXml(@NonNull final RelativizableFile input,
@NonNull final File outputFile, xxx) {
……
try {
……
// 使用input和output生成 xxx.xml文件,去掉根节点 layout和添加 tag
stripFile(inputFile, outputFile, encoding, originalFileLookup);
// 解析xml文件
return parseOriginalXml(
RelativizableFile.fromAbsoluteFile(originalFile, input.getBaseDir()),
pkg, encoding, isViewBindingEnabled, isDataBindingEnabled);
} finally {
Scope.exit();
}
}
1 生成 xxx.xml文件,去掉根节点 layout和添加 tag,并且写入到 output文件中。
2 解析 xml文件
生成 xxx.xml 文件
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);
}
}
stringBindingTags
去掉 xml文件中的layout等标签,并且添加 android:tag="binding_1"
,然后将其解析的字符串写入到 output文件中。
解析 xml文件
private static ResourceBundle.LayoutFileBundle parseOriginalXml(
@NonNull final RelativizableFile originalFile, @NonNull final String pkg,
@NonNull final String encoding, boolean isViewBindingEnabled,
boolean isDataBindingEnabled)
throws IOException {
……
if (isBindingData) {
// 1 开启了dataBinding
if (!isDataBindingEnabled) {
L.e(ErrorMessages.FOUND_LAYOUT_BUT_NOT_ENABLED);
return null;
}
data = getDataNode(root);
rootView = getViewNode(original, root);
} else if (isViewBindingEnabled) {
// 2 开启了 ViewBinding
if ("true".equalsIgnoreCase(attributeMap(root).get("tools:viewBindingIgnore"))) {
L.d("Ignoring %s for view binding", originalFile);
return null;
}
data = null;
rootView = root;
} else {
return null;
}
// 3 生成LayoutFileBundle文件
ResourceBundle.LayoutFileBundle bundle =
new ResourceBundle.LayoutFileBundle(
originalFile, xmlNoExtension, original.getParentFile().getName(), pkg,
isMerge, isBindingData, rootViewType, rootViewId);
// 4 解析数据
parseData(original, data, bundle);
// 5 解析表达式
parseExpressions(newTag, rootView, isMerge, bundle);
return bundle;
} finally {
Scope.exit();
}
}
1 开启了DataBinding,获取数据,获取根Element
2 开启了ViewBinding,首先判断是否 xml文件是否忽略了ViewBinding,如果 tools:viewBindingIgnore="true"
则不解析。根 ElementContext
就是 rootView。data = null,data 数据只有 databinding 才会有的元素,viewBinding 是不会去解析的
3 创建 LayoutFileBundle
4 解析 xml文件中的 data标签中的数据,解析 import
variable
标签和class
属性
5 解析表达式,这里面会循环遍历元素,解析 View
的 id
、tag
、include
、fragment
、binding_id
等元素,并且还有 databinding 相关的 @{
、@={
属性表达式
从中可以注意到 DataBinding和 ViewBinging是可以同时设置,属性表达式有两种表现形式。
生成 xxx_xxx-layout.xml文件
MergeResources
@Override
public void end() throws JAXBException {
getProcessor() // LayoutXmlProcessor
.writeLayoutInfoFiles(
getDataBindingLayoutInfoOutFolder().get().getAsFile());
}
LayoutXmlProcessor.java
public void writeLayoutInfoFiles(File xmlOutDir) throws JAXBException {
writeLayoutInfoFiles(xmlOutDir, mFileWriter);
}
public void writeLayoutInfoFiles(File xmlOutDir, JavaFileWriter writer) throws JAXBException {
// For each layout file, generate a corresponding layout info file
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());
}
从代码中可以看到遍历 mResourceBundle
文件,即上一节中存起来的 LayoutFileBundle
文件,然后写入文件 module\build\intermediates\data_binding_layout_info_type_merge\debug\out\xxx_xxx-layout.xml
生成 XxxXxxBinding.java 文件
上面的两步完成后,会执行 DataBindingGenBaseClassesTask.writeBaseClasses
生成Java文件
@TaskAction
fun writeBaseClasses(inputs: IncrementalTaskInputs) {
// TODO extend NewIncrementalTask when moved to new API so that we can remove the manual call to recordTaskAction
recordTaskAction(analyticsService.get()) {
val args = buildInputArgs(inputs)
CodeGenerator(
args,
sourceOutFolder.get().asFile,
Logger.getLogger(DataBindingGenBaseClassesTask::class.java),
encodeErrors).run()
}
}
class CodeGenerator @Inject constructor(
val args: LayoutInfoInput.Args,
private val sourceOutFolder: File,
private val logger: Logger,
private val encodeErrors: Boolean
) : Runnable, Serializable {
override fun run() {
try {
initLogger()
BaseDataBinder(LayoutInfoInput(args))
.generateAll(DataBindingBuilder.GradleFileWriter(sourceOutFolder.absolutePath))
} finally {
clearLogger()
}
}
}
BaseDataBinder
class BaseDataBinder(val input : LayoutInfoInput) {
private val resourceBundle : ResourceBundle = ResourceBundle(
input.packageName, input.args.useAndroidX)
init {
// 1 将之前生成 xxx_xxx-layout.xml 重新生成 LayoutFileBundle
input.filesToConsider
.forEach {
it.inputStream().use {
val bundle = LayoutFileBundle.fromXML(it)
resourceBundle.addLayoutBundle(bundle, true)
}
}
resourceBundle.addDependencyLayouts(input.existingBindingClasses)
resourceBundle.validateAndRegisterErrors()
}
@Suppress("unused")// used by android gradle plugin
fun generateAll(writer : JavaFileWriter) {
// 2 拿到所有的 LayoutFileBundle,并根据文件名进行分组排序
val layoutBindings = resourceBundle.allLayoutFileBundlesInSource
.groupBy(LayoutFileBundle::getFileName)
// 3 遍历 layoutBindings
layoutBindings.forEach { layoutName, variations ->
// 将 LayoutFileBundle 信息包装成 BaseLayoutModel
val layoutModel = BaseLayoutModel(variations)
val javaFile: JavaFile
val classInfo: GenClassInfoLog.GenClass
if (variations.first().isBindingData) {
check(input.args.enableDataBinding) {
"Data binding is not enabled but found data binding layouts: $variations"
}
// 4.a 生成JavaFile文件
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"
}
// 4.b 生成JavaFile
val viewBinder = layoutModel.toViewBinder()
javaFile = viewBinder.toJavaFile(useLegacyAnnotations = !useAndroidX)
classInfo = viewBinder.generatedClassInfo()
}
// 写文件 GradleFileWriter.writeToFile
writer.writeToFile(javaFile)
……
}
}
1 将之前生成 xxx_xxx-layout.xml 重新生成 LayoutFileBundle
2 拿到所有的 LayoutFileBundle,并根据文件名进行分组排序
3 遍历 layoutBindings 文件生成javaFile
4 根据文件中的数据生成 javaFile 两者生成方式区别不大
// 4.a 和 4.b 都是调用此方法生成 javaFile,只是调用的类不同而已
fun create() = javaFile(binder.generatedTypeName.packageName(), typeSpec()) {
addFileComment("Generated by view binder compiler. Do not edit!")
}
BaseLayoutBinderWriter.kt
private fun createType() = classSpec(binderTypeName) {
superclass(viewDataBinding)
addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
addFields(createBindingTargetFields())
addFields(createVariableFields())
addMethod(createConstructor())
addMethods(createGettersAndSetters())
addMethods(createStaticInflaters())
}
ViewBinderGenerateJava.kt
fun ViewBinder.toJavaFile(useLegacyAnnotations: Boolean = false) =
JavaFileGenerator(this, useLegacyAnnotations).create()
private fun typeSpec() = classSpec(binder.generatedTypeName) {
addModifiers(PUBLIC, FINAL)
val viewBindingPackage = if (useLegacyAnnotations) "android" else "androidx"
addSuperinterface(ClassName.get("$viewBindingPackage.viewbinding", "ViewBinding"))
// TODO elide the separate root field if the root tag has an ID (and isn't a binder)
addField(rootViewField())
addFields(bindingFields())
addMethod(constructor())
addMethod(rootViewGetter())
if (binder.rootNode is RootNode.Merge) {
addMethod(mergeInflate())
} else {
addMethod(oneParamInflate())
addMethod(threeParamInflate())
}
addMethod(bind())
}
这是使用 javapoet 中的 TypeSpec 生成java文件。
ProcessDataBinding 分析
在 DataBinding中还有一个非常重要的类需要分析一下,那就是解析DataBinding处理注解的类ProcessDataBinding
,其继承自 AbstractProcessor
。其地址为DataBinding,我的示例中的也有 compiler里面就是 ProcessDataBinding
private boolean doProcess(RoundEnvironment roundEnv) {
if (mProcessingSteps == null) {
readArguments();
initProcessingSteps(processingEnv);
}
……
boolean done = true;
Context.init(processingEnv, mCompilerArgs);
for (ProcessingStep step : mProcessingSteps) {
try {
done = step.runStep(roundEnv, processingEnv, mCompilerArgs) && done;
} catch (JAXBException e) {
L.e(e, "Exception while handling step %s", step);
}
}
if (roundEnv.processingOver()) {
for (ProcessingStep step : mProcessingSteps) {
step.onProcessingOver(roundEnv, processingEnv, mCompilerArgs);
}
}
if (roundEnv.processingOver()) {
Scope.assertNoError();
}
return done;
}
private void initProcessingSteps(ProcessingEnvironment processingEnv) {
final ProcessBindable processBindable = new ProcessBindable();
mProcessingSteps = Arrays.asList(
new ProcessMethodAdapters(),
new ProcessExpressions(),
processBindable
);
……
}
创建了三个 ProcessingStep
: ProcessMethodAdapters
、ProcessExpressions
、ProcessBindable
,分别生成对应数据数据。下面来一一查看一下。
ProcessMethodAdapters
@Override
public boolean onHandleStep(RoundEnvironment roundEnv,
ProcessingEnvironment processingEnvironment,
CompilerArguments args) {
final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
SetterStore store = SetterStore.get();
clearIncrementalClasses(roundEnv, store);
// 将用@BindingAdapter注释的方法解析,并存储到SetterStore中
addBindingAdapters(roundEnv, processingEnvironment, store);
// 将用@BindingMethod注释属性替换的方法解析,并存储到SetterStore中
addRenamed(roundEnv, store);
// 将用@BindingConversion注释的方法解析,并存储到SetterStore中
addConversions(roundEnv, store);
// 将用@Untaggable注释的类接口等的解析,并存储到SetterStore中
addUntaggable(roundEnv, store);
// 将用@InverseBindingAdapter注释的方法等解析,并存储到SetterStore中
addInverseAdapters(roundEnv, processingEnvironment, store);
addInverseBindingMethods(roundEnv, store);
addInverseMethods(roundEnv, processingEnvironment, store);
……
store.write(args.getModulePackage());
……
return true;
}
该类主要是处理 @BindingAdapter
、@BindingMethods
、@BindingConversion
等注解注解的方法、类,并将解析到的信息存储在 SetterStor
中,最终存储在 BindingAdapterStore
相应的 TreeMap
中。
ProcessExpressions
graph TD
ProcessExpressions.onHandleStep --> ProcessExpressions.createIntermediateFromLayouts -->
ProcessExpressions.writeResourceBundle --> CompilerChef.writeViewBinderInterfaces --> DataBinder.writerBaseClasses --> LayoutBinder.writeViewBinderBaseClass --> LayoutBinderWriter.writeBaseClass
ProcessExpressions.writeResourceBundle --> CompilerChef.writeViewBinders --> DataBinder.writeBinders --> LayoutBinder.writeViewBinder --> LayoutBinderWriter.write
……
loadDependencyIntermediates()
……
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);
}
这一段代码将 xxx-layoutinfo.bin中的数据转换成 LayoutFileBundle
解析里面的layout、import、variables等标签。最后使用 LayoutBinderWriter.writeBaseClass
和 LayoutBinderWriter.write
生成 XxxXxxBinding.java 和 XxxXxxBingImpl.java(使用kotlin语言才会生成)。
ProcessBindable
private void generateBRClasses(
ProcessingEnvironment processingEnv,
CompilerArguments compilerArgs,
String pkg) {
try {
CompilerArguments.Type artifactType = compilerArgs.getArtifactType();
……
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) {
// This will be logged later
}
}
生成 BR
和 DataBinderMapper
文件。
三、总结
在引入DataBinding后,在布局的xml文件使用了 layout标签,或者引入ViewBinding后,都会在Android Studio中生成虚拟的 XxxXxxBinding.java,如:com.zbt.databinding.databinding.ActivityTest1Binding
,在构建项目或者运行相应的task后才会生成相应 XxxXxxBinding.java文件,然后打包到apk中,供app运行时使用。
在执行 dataBindingGenBaseClasses
会生成一次XxxXxxBinding.java,同时注解处理类 ProcessDataBinding
也会生成一次 XxxXxxBinding.java。
DataBinding涉及的代码太多,流程也相当复杂,所以文中只贴了部分核心代码,某些核心逻辑也是一笔带过,是因为笔者的水平有限。笔者花了相当长时间去看源码也还是有很多逻辑没有理清,待后面有时间再重新理一遍。读者可以根据上面 debug模式去进行 DataBinding的逻辑验证。唯一的遗憾是没能通过 debug模式调试 ProcessDataBinding
。
四、遗留问题
- BaseDataBinder 将之前生成 xxx_xxx-layout.xml 重新生成 LayoutFileBundle,而不是用之前已经生成LayoutFileBundle
- 为什么引入了 DataBinding或者 ViewBinding就能够使用 XxxXxxBinding.java 这个时候这些文件还未生成
- 为什么task 和 注解要多次生成 XxxXxxBinding.java
- task和 注解生成XxxXxxBinding.java的方式为什么不同
五、参考文章
ViewBinding 的本质
AbstractProcessor注解处理器
Android-DataBinding-原理-编译期
Android Gradle Plugin源码分析