前言

Android开发中,控件绑定是一个久远的话题。

最开始就是使用findViewById,满屏都是各种find;

后来出现了Butterknife,使用注解来进行控件绑定,这样一来使UI层的代码清爽了很多,即使这样,还存在众多臃肿的全局变量的控件;

后来kotlin 推出了kotlin-android-e源码中的图片xtensiohttp代理ns 插件,可以直接用id就能得到xml中的控件对象并且使用,这种方式真的很香,它原理是利用了字节码插桩技java环境变量配置术,帮我们自动生成了类似findViewById的东西,这里可以参考一下郭神的博客:

kotlin-android-extensions插件也被废弃了?扶我起来
blog.csdn.net/guolin_blog…

具体废弃的原因我猜测:

1.不兼容Javajava培训。虽然现在google各种新技术都在以java为主,像协程,Compose之类,但是这些都是可以独立于平台的,而控件绑定这个功能是基于平台的,必然需要考虑jav源码编辑器下载a用户群体。

2.虽然我们kotlin-android-extensions 我们使用起来非常爽,但是从它的实现原理也暴露出来一些问题,在无形之中降低了程序httpclient的运行效率。

使用

VieKotlinwBinding的简单使用可以说是非常简单。

首先在我kotlin语言们的moudlebuild.gradle下进行配置:

buildFeatures{
viewBindingtrue
}

viewbinding的配置是独立于moudle的。

配置之后,会生成对应的Binding类,我们直接调用,进行绑定即可。

lateinitvarbinding:MainActivityBinding
overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
binding=MainActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.message.text="Android开发那点事儿"
}

在使用上,我们就可以直接通过binding来获取到我们XML布局中定义的控件,非常方便。

原理

生成的binding文件 是在 build/generajava培训ted/data_binding_base_class_source_out/debug/out 目录下,我们先看下生成的类内容

这是我定义的XML布局:

<?xmlversion="1.0"encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayoutxmlns: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类:

publicfinalclassMainActivityBindingimplementsViewBinding{
privatefinalConstraintLayoutrootView;
publicfinalConstraintLayoutmain;
publicfinalTextViewmessage;
privateMainActivityBinding(ConstraintLayoutrootView,ConstraintLayoutmain,TextViewmessage){
this.rootView=rootView;
this.main=main;
this.message=message;
}
@Override
@NonNull
publicConstraintLayoutgetRoot(){
returnrootView;
}
@NonNull
publicstaticMainActivityBindinginflate(@NonNullLayoutInflaterinflater){
returninflate(inflater,null,false);
}
@NonNull
publicstaticMainActivityBindinginflate(@NonNullLayoutInflaterinflater,
@NullableViewGroupparent,booleanattachToParent){
Viewroot=inflater.inflate(R.layout.main_activity,parent,false);
if(attachToParent){
parent.addView(root);
}
returnbind(root);
}
@NonNull
publicstaticMainActivityBindingbind(@NonNullViewrootView){
intid;
missingId:{
ConstraintLayoutmain=(ConstraintLayout)rootView;
id=R.id.message;
TextViewmessage=ViewBindings.findChildViewById(rootView,id);
if(message==null){
breakmissingId;
}
returnnewMainActivityBinding((ConstraintLayout)rootView,main,message);
}
StringmissingId=rootView.getResources().getResourceName(id);
thrownewNullPointerException("MissingrequiredviewwithID:".concat(missingId));
}
}

在这里viewbinding 帮我解析的xkotlin语言ml布局文件,并对设置Id的控件自动进行控件绑定,最后我们最后通过viewbinding获取根布局,调用sehttp 404tContent方法来添加布局。

@Override
publicvoidsetContentView(Viewv){
ensureSubDecor();
ViewGroupcontentParent=mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}

原来是由actiivty 进行布局http 500解析,由我们自己进行控件绑定并使用,现在就相当于Viewgradle是什么意思Binding将这两件事情都做了。

那这些binding类是如何生成的呢?这就是接下来我们要探索的话题。

当我改变布局文件的时候,发现binding类文件并不会实时发生改变,需要编译之后binding类文件才会进行对应改变,由此推断,binding类文件应该是APG在编译项目的时候生成的。

我们运行一下源码资本,看下Task

ViewBinding,你真的理解了吗?

imagradle怎么读ge-202204051429342java环境变量配置87

整个编译流程中,只捕捉到了三个关于dataBindingtask,我们现在并不能确定是哪个task生成的bgradleinding类,那怎么办?

那我们就一个一个执行,一个一个去试。

AS右侧的gradle任务栏中,找到了关于databindingtask

ViewBinding,你真的理解了吗?

ihttps和http的区别mage-20220405143309503

我们再分别执行 dajava编译器taBinkotlin面试题dingMergeDependencyArtifactsDebugdataBindingMergeGenClassesDebugdataBindingGenBaseClassesDebug

clean操作,然后执行 前两个t源码时代ask 之后,发现只是生成了两个空文件夹,并未有内容生成:

ViewBinding,你真的理解了吗?

image-20220405143551157

在执行了dataBHTTPindingMergeGenClassesDebug 之后,生成了我们所需要的binding类,

ViewBinding,你真的理解了吗?

image-20220405143733504

dataBindingMergeGenClassesDebug 这个tagradle教程sk 就是我们要探索的重点。

接下来我们引入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'

appbuild.gradle中引入就行,我们在External Libraries 中进行查阅。

我们查找的入口主要有两个,一个AGPTaskManager 类,这个是管理Task创建的类,还有另外一个入口,就是从 /com/android/build/gradle/internal/tasks 路径去找,这个是属于熟能生巧的一个捷径。

首先看TaskManager ,通过方法查阅,我们会发现关于创建DataBinding Task 的只要一个方法 createDataBinjava模拟器dingTjava面试题asksIfNecessary

protectedfuncreateDataBindingTasksIfNecessary(creationConfig:ComponentCreationConfig){
valdataBindingEnabled=creationConfig.buildFeatures.dataBinding
valviewBindingEnabled=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_TRIGGERartifactiscreatedfordatabindingonly(notviewbinding)
if(dataBindingEnabled){
if(projectOptions[BooleanOption.NON_TRANSITIVE_R_CLASS]
&&isKotlinKaptPluginApplied(project)){
//TODO(183423660):UndothisworkaroundforKAPTresolvingfilesatcompiletime
taskFactory.register(MergeRFilesForDataBindingTask.CreationAction(creationConfig))
}
taskFactory.register(DataBindingTriggerTask.CreationAction(creationConfig))
setDataBindingAnnotationProcessorParams(creationConfig)
}
}

这里首先获Kotlin取配置信息,看看ViewBindingDataBindingkotlin语言 的开关状态,如果两个都是关闭直接返回。否则 进行Task注册,在这里进行注册的task,有两个是我们 在编译过程中看到的task,再继续,就是当databkotlin和javainding 开启的时候,gradle是什么会再额外 注册task ,通过这里我们可以了解源码编辑器下载viewBindin源码编辑器g 只是DataBinding 中的一部分功能。viewbinding只是进行控件绑定,DataBinding除了基础的控件绑定之外,还拥有双向数据绑定等功能。

接下来我们看看DataBindingGenBaseClassesTask

这个Task类的路径是 co源码时代m.android.build.gradle.internal.tasks.databindiJavang ,跟我说提到的第二个入口吻合,所以以HTTP后分析AGP源码,可以gradle是什么从这个路径来找对应的Task,这是一种取巧的方式。

写过自定义插件的朋友都知道,自定义Task中 需要用注解 @TaskAction 来标识一下task 的运行入口。

@TaskAction
funwriteBaseClasses(inputs:IncrementalTaskInputs){
recordTaskAction(analyticsService.get()){
valargs=buildInputArgs(inputs)
CodeGenerator(
args,
sourceOutFolder.get().asFile,
Logger.getLogger(DataBindingGenBaseClassesTask::class.java),
encodeErrors,
collectResources()).run()
}
}

可以看到 writeBaseClasses 方法被 @TaskAction 注解标识,那么这就是我们分析的入口。

这里主要是创建了 CodeGenerator 类,然后执行了 run()方法。

overridefunrun(){
try{
initLogger()
BaseDataBinder(
LayoutInfoInput(args),
if(symbolTables!=null)this::getRPackageelsenull)
.generateAll(DataBindingBuilder.GradleFileWriter(sourceOutFolder.absolutePath))
}finally{
clearLogger()
}
}

Codegradle是什么Generator:: run() 中,我们看到这里又创建了BaseDataBinder类,并源码资本运行了 generateAll源码编辑器 方法。

fungenerateAll(writer:JavaFileWriter){
..............
layoutBindings.forEach{layoutName,variations->
...........
if(variations.first().isBindingData){
check(input.args.enableDataBinding){
"Databindingisnotenabledbutfounddatabindinglayouts:$variations"
}
valbinderWriter=BaseLayoutBinderWriter(layoutModel,libTypes)
javaFile=binderWriter.write()
classInfo=binderWriter.generateClassInfo()
}else{
check(input.args.enableViewBinding){
"Viewbindingisnotenabledbutfoundnon-databindinglayouts:$variations"
}
valviewBinder=layoutModel.toViewBinder()
javaFile=viewBinder.toJavaFile(useLegacyAnnotations=!useAndroidX)
classInfo=viewBinder.generatedClassInfo()
}
.................
}

这里对代码进行了精简,这里首先源码中的图片做了一个判断,判断是否为DataBinding ,很明显我们需http://192.168.1.1登录要分析的内容 在else 里面。

else 里面,先判断了viewBingradle安装与配置ding是否开启,然后将java怎么读 BaseLayoutModel 对象转化为了 ViewBinder 对象,Java接下来执行了 ViewBinder

Java展方法 toJavaFile ,这个方法名的意思就很java面试题明显了,是去转化为Java文件的。

funViewBinder.toJavaFile(useLegacyAnnotations:Boolean=false)=
JavaFileGenerator(this,useLegacyAnnotations).create()

这里是创建了JavaFileGjava培训enerator 类 ,执行create() 方法。

funcreate()=javaFile(binder.generatedTypeName.packageName(),typeSpec()){
addFileComment("Generatedbyviewbindercompiler.Donotedit!")
}

这里这就是创建bindinggradle教程类的方法了 ,我们主要看下 typeSpec() 方法:

privatefuntypeSpec()=classSpec(binder.generatedTypeName){
增加publicfinal修饰
addModifiers(PUBLIC,FINAL)
实现ViewBinding接口
addSuperinterface(ClassName.get(viewBindingPackage,"ViewBinding"))
添加rootView变量
addField(rootViewField())
添加控件变量
addFields(bindingFields())
创建无参构造方法
addMethod(constructor())
创建根布局的get方法
addMethod(rootViewGetter())

if(binder.rootNodeisRootNode.Merge){
addMethod(mergeInflate())
}else{
创建一个参数的inflate方法
addMethod(oneParamInflate())
创建三个参数的inflate方法
addMethod(threeParamInflate())
}
添加bind方法
addMethod(bind())
}

使用过javapoet的同学可以看出 这就是使用javapoet 来创建java文件。

经过 这些创建流程 与我们生成的viewbinding类文件对比,可以发现完全吻合。

所以我们的ViewBinding类文件 就是在这里通过javapoet生成的。

总结

我们最后总结一下:

我们通过观察编httpwatch译流程,得出dataBindingGenBaseClassesDebug 是生成binding类的task,然后通过TaskManager 找到对应的

DataBindingGenBaseClassesTask ,通过@TaskAction 注解找到task执行的入口,最后调用到 DataBindkotlin和javaing 里面 BaseDataBindhttpwatcher 类,在这个过程中,通过 ViewBinder 调用到了 Jhttp://www.baidu.comavaFileGenerator 的 create() 方法,在这里通过jjava面试题avapoet 生成了我们所使用Viewbgradle是什么indihttp 404ng类。

整体调用流程:

TaskManager
->writeBaseClasses
->CodeGenerator::run()
->BaseDataBinder::generateAll()
->ViewBinder::toJavaFile()
->JavaFileGenerator::create()
->typeSpec()
->javapoet

写在最后

整体流程并不是算复杂,大家在阅读后最好还java编译器是自己去跟一遍源码,这个亲自跟一遍,自己理解的才算透彻。

理论配合实践,才能真正学会。