foreword
In Android
development, control binding is a long-standing topic.
At the beginning, it was used findViewById
, and the screen was full of various finds;
Later, it appeared Butterknife
, using annotations to bind controls, which made the code of the UI layer a lot cleaner. Even so, there are still many bloated global variable controls;
Later kotlin
, the kotlin-android-extensions plugin was launched, which can directly use the id to get xml
the control object and use it. This method is really delicious. Its principle is to use the bytecode instrumentation technology to help us automatically generate similar findViewById
Here you can refer to Guoshen's blog:
❝
The kotlin-android-extensions plugin is also deprecated? lift me up
blog.csdn.net/guolin_blog…
❞
I guess the reason for the specific abandonment:
1. Not compatible Java
. Although google
various new technologies are taking priority now java
, such as coroutines Compose
, but these can be independent of the platform, and the function of control binding is platform-based, and the java
user group must be considered.
2. Although we kotlin-android-extensions
are very cool to use, but some problems are also exposed from its implementation principle, which invisibly reduces the running efficiency of the program.
use
ViewBinding
The simplicity of use can be said to be very simple.
First configure it under moudle
our build.gradle
:
buildFeatures {
viewBinding true
}
复制代码
viewbinding
The configuration is independent moudle
of .
After configuration, the corresponding Binding class will be generated, and we can call it directly to bind it.
lateinit var binding: MainActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = MainActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.message.text = "Android开发那点事儿"
}
复制代码
In use, we can get the controls defined in binding
our layout directly through it, which is very convenient.XML
principle
生成的binding
文件 是在 build/generated/data_binding_base_class_source_out/debug/out
目录下,我们先看下生成的类内容
这是我定义的XML布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MainFragment"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
复制代码
这是生成的binding
类:
public final class MainActivityBinding implements ViewBinding {
private final ConstraintLayout rootView;
public final ConstraintLayout main;
public final TextView message;
private MainActivityBinding( ConstraintLayout rootView, ConstraintLayout main, TextView message) {
this.rootView = rootView;
this.main = main;
this.message = message;
}
@Override
@NonNull
public ConstraintLayout getRoot() {
return rootView;
}
@NonNull
public static MainActivityBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static MainActivityBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.main_activity, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static MainActivityBinding bind(@NonNull View rootView) {
int id;
missingId: {
ConstraintLayout main = (ConstraintLayout) rootView;
id = R.id.message;
TextView message = ViewBindings.findChildViewById(rootView, id);
if (message == null) {
break missingId;
}
return new MainActivityBinding((ConstraintLayout) rootView, main, message);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}
复制代码
在这里viewbinding
帮我解析的xml
布局文件,并对设置Id
的控件自动进行控件绑定,最后我们最后通过viewbinding
获取根布局,调用setContent
方法来添加布局。
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
复制代码
原来是由actiivty
进行布局解析,由我们自己进行控件绑定并使用,现在就相当于ViewBinding
将这两件事情都做了。
那这些binding
类是如何生成的呢?这就是接下来我们要探索的话题。
当我改变布局文件的时候,发现binding
类文件并不会实时发生改变,需要编译之后binding
类文件才会进行对应改变,由此推断,binding
类文件应该是APG在编译项目的时候生成的。
我们运行一下,看下Task
:
image-20220405142934287
整个编译流程中,只捕捉到了三个关于dataBinding
的task
,我们现在并不能确定是哪个task
生成的binding
类,那怎么办?
那我们就一个一个执行,一个一个去试。
在AS
右侧的gradle
任务栏中,找到了关于databinding
的task
image-20220405143309503
我们再分别执行 dataBindingMergeDependencyArtifactsDebug
,dataBindingMergeGenClassesDebug
和 dataBindingGenBaseClassesDebug
。
先clean
操作,然后执行 前两个task
之后,发现只是生成了两个空文件夹,并未有内容生成:
image-20220405143551157
在执行了dataBindingMergeGenClassesDebug
之后,生成了我们所需要的binding类,
image-20220405143733504
那dataBindingMergeGenClassesDebug
这个task
就是我们要探索的重点。
接下来我们引入AGP
源码和Databinding
源码,进行分析:
implementation 'com.android.tools.build:gradle:7.0.2'
implementation 'androidx.databinding:databinding-compiler-common:7.0.2'
implementation 'androidx.databinding:databinding-common:7.0.2'
implementation 'com.android.databinding:baseLibrary:7.0.2'
复制代码
在app
的build.gradle
中引入就行,我们在External Libraries
中进行查阅。
我们查找的入口主要有两个,一个AGP
的 TaskManager
类,这个是管理Task
创建的类,还有另外一个入口,就是从 /com/android/build/gradle/internal/tasks
路径去找,这个是属于熟能生巧的一个捷径。
首先看TaskManager
,通过方法查阅,我们会发现关于创建DataBinding Task
的只要一个方法 createDataBindingTasksIfNecessary
,
protected fun createDataBindingTasksIfNecessary(creationConfig: ComponentCreationConfig) {
val dataBindingEnabled = creationConfig.buildFeatures.dataBinding
val viewBindingEnabled = creationConfig.buildFeatures.viewBinding
if (!dataBindingEnabled && !viewBindingEnabled) {
return
}
taskFactory.register(DataBindingMergeBaseClassLogTask.CreationAction(creationConfig))
taskFactory.register(
DataBindingMergeDependencyArtifactsTask.CreationAction(creationConfig))
DataBindingBuilder.setDebugLogEnabled(logger.isDebugEnabled)
taskFactory.register(DataBindingGenBaseClassesTask.CreationAction(creationConfig))
// DATA_BINDING_TRIGGER artifact is created for data binding only (not view binding)
if (dataBindingEnabled) {
if (projectOptions[BooleanOption.NON_TRANSITIVE_R_CLASS]
&& isKotlinKaptPluginApplied(project)) {
// TODO(183423660): Undo this workaround for KAPT resolving files at compile time
taskFactory.register(MergeRFilesForDataBindingTask.CreationAction(creationConfig))
}
taskFactory.register(DataBindingTriggerTask.CreationAction(creationConfig))
setDataBindingAnnotationProcessorParams(creationConfig)
}
}
复制代码
这里首先获取配置信息,看看ViewBinding
和 DataBinding
的开关状态,如果两个都是关闭直接返回。否则 进行Task
注册,在这里进行注册的task
,有两个是我们 在编译过程中看到的task
,再继续,就是当databinding
开启的时候,会再额外 注册task
,通过这里我们可以了解到 viewBinding
只是DataBinding
中的一部分功能。viewbinding
只是进行控件绑定,DataBinding
除了基础的控件绑定之外,还拥有双向数据绑定等功能。
接下来我们看看DataBindingGenBaseClassesTask
。
这个Task
类的路径是 com.android.build.gradle.internal.tasks.databinding
,跟我说提到的第二个入口吻合,所以以后分析AGP
源码,可以从这个路径来找对应的Task
,这是一种取巧的方式。
写过自定义插件的朋友都知道,自定义Task
中 需要用注解 @TaskAction
来标识一下task
的运行入口。
@TaskAction
fun writeBaseClasses(inputs: IncrementalTaskInputs) {
recordTaskAction(analyticsService.get()) {
val args = buildInputArgs(inputs)
CodeGenerator(
args,
sourceOutFolder.get().asFile,
Logger.getLogger(DataBindingGenBaseClassesTask::class.java),
encodeErrors,
collectResources()).run()
}
}
复制代码
可以看到 writeBaseClasses
方法被 @TaskAction
注解标识,那么这就是我们分析的入口。
这里主要是创建了 CodeGenerator
类,然后执行了 run()
方法。
override fun run() {
try {
initLogger()
BaseDataBinder(
LayoutInfoInput(args),
if (symbolTables != null) this::getRPackage else null)
.generateAll(DataBindingBuilder.GradleFileWriter(sourceOutFolder.absolutePath))
} finally {
clearLogger()
}
}
复制代码
在CodeGenerator:: run()
中,我们看到这里又创建了BaseDataBinder
类,并运行了 generateAll
方法。
fun generateAll(writer : JavaFileWriter) {
..............
layoutBindings.forEach { layoutName, variations ->
...........
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()
}
.................
}
复制代码
这里对代码进行了精简,这里首先做了一个判断,判断是否为DataBinding
,很明显我们需要分析的内容 在else
里面。
在else
里面,先判断了viewBinding
是否开启,然后将 BaseLayoutModel
对象转化为了 ViewBinder
对象,接下来执行了 ViewBinder
的
拓展方法 toJavaFile
,这个方法名的意思就很明显了,是去转化为Java
文件的。
fun ViewBinder.toJavaFile(useLegacyAnnotations: Boolean = false) =
JavaFileGenerator(this, useLegacyAnnotations).create()
复制代码
这里是创建了JavaFileGenerator
类 ,执行create()
方法。
fun create() = javaFile(binder.generatedTypeName.packageName(), typeSpec()) {
addFileComment("Generated by view binder compiler. Do not edit!")
}
复制代码
这里这就是创建binding
类的方法了 ,我们主要看下 typeSpec()
方法:
private fun typeSpec() = classSpec(binder.generatedTypeName) {
增加 public final 修饰
addModifiers(PUBLIC, FINAL)
实现 ViewBinding 接口
addSuperinterface(ClassName.get(viewBindingPackage, "ViewBinding"))
添加 rootView 变量
addField(rootViewField())
添加 控件 变量
addFields(bindingFields())
创建 无参构造方法
addMethod(constructor())
创建 根布局的 get方法
addMethod(rootViewGetter())
if (binder.rootNode is RootNode.Merge) {
addMethod(mergeInflate())
} else {
创建一个参数的 inflate 方法
addMethod(oneParamInflate())
创建三个参数的 inflate 方法
addMethod(threeParamInflate())
}
添加 bind 方法
addMethod(bind())
}
复制代码
使用过javapoet
的同学可以看出 这就是使用javapoet
来创建java
文件。
经过 这些创建流程 与我们生成的viewbinding
类文件对比,可以发现完全吻合。
So our ViewBinding
class file is javapoet
generated here.
Summarize
Let's conclude by summarizing:
By observing the compilation process, we find that it dataBindingGenBaseClassesDebug
is a generated binding
class task
, and then TaskManager
find the corresponding
DataBindingGenBaseClassesTask
, find the entry of execution through @TaskAction
annotations task
, and finally call to the DataBinding
inside BaseDataBinder
class. In this process, through the ViewBinder
call to the JavaFileGenerator
create() method of , here javapoet
the class we use is generated through Viewbinding
.
The overall calling process:
TaskManager
->writeBaseClasses
->CodeGenerator :: run()
->BaseDataBinder::generateAll()
->ViewBinder::toJavaFile()
->JavaFileGenerator:: create()
->typeSpec()
->javapoet
复制代码
write at the end
The overall process is not complicated. After reading it, it is best for you to follow the source code yourself. This is done in person, and it is only when you understand it thoroughly.
Theory and practice can be truly learned.