日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

android viewbinding_程序员必懂小技巧之ViewBinding

發布時間:2024/9/15 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android viewbinding_程序员必懂小技巧之ViewBinding 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

終于有一篇帶大家從本質來了解 ViewBinding 了。

如果你完全沒了解過 View Binding,可以先看下面這篇介紹:

AS 3.6 Canary 中推出新技術 視圖綁定 View Binding

今天我們來深入的了解 ViewBinding 的本質,看看他是怎么生成 ActivityMainBinding 這種文件的。

1、使用

ViewBinding 目前只支持 as3.6,使用方法很簡單,僅僅只需要添加如下代碼:

android {viewBinding {enabled = true} }

make project 之后,會在對應的 module 路徑:

app/build/generated/data_binding_base_class_source_out/${buildTypes}/out/${包名}/databinding

生成 ViewBinding 文件,為什么我會說 對應的 module ?因為 viewBinding 只對當前設置了 enabled = true 的 module 才會進行處理。
然后來看下處理后的文件:

public final class ActivityMainBinding implements ViewBinding {@NonNullprivate final ConstraintLayout rootView;@NonNullpublic final Button tv;private ActivityMainBinding(@NonNull ConstraintLayout rootView, @NonNull Button tv) {this.rootView = rootView;this.tv = tv;}@Override@NonNullpublic ConstraintLayout getRoot() {return rootView;}@NonNullpublic static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {return inflate(inflater, null, false);}@NonNullpublic static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,@Nullable ViewGroup parent, boolean attachToParent) {View root = inflater.inflate(R.layout.activity_main, parent, false);if (attachToParent) {parent.addView(root);}return bind(root);}@NonNullpublic static ActivityMainBinding bind(@NonNull View rootView) {// The body of this method is generated in a way you would not otherwise write.// This is done to optimize the compiled bytecode for size and performance.String missingId;missingId: {Button tv = rootView.findViewById(R.id.tv);if (tv == null) {missingId = "tv";break missingId;}return new ActivityMainBinding((ConstraintLayout) rootView, tv);}...} }

我們來看看這個文件有哪些信息:

  • R.layout.activity_main 布局文件
  • 布局文件中的 view 控件和 view id
  • 布局文件的 rootView 和類型

接下來,我們會通過源碼的方式來跟蹤到,這些信息是怎么產生的。

2 、準備

由于我們并沒有依賴其他 plugin 就可以使用,所以能被直接識別只能是 classpath 依賴的 gradle 了:

classpath 'com.android.tools.build:gradle:3.6.1'

既然 make project 之后就可以看到 ViewBinding 的生成類,那么,我們可以根據 make project 的 build 信息查看做了哪些 task:

> Task :app:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE > Task :app:dataBindingMergeGenClassesDebug UP-TO-DATE > ... > Task :app:dataBindingGenBaseClassesDebug

沒有找到 ViewBinding,但找到了 dataBinding,但可以肯定的是,這個 dataBinding 就是生成 ViewBinding 的 task(因為沒有其他的 task 帶有 binding)。
然后我們可以去 maven 倉庫找一下 gradle:3.6.1 ,驚喜的是,gradle:3.6.1 的依賴項有 18 個,第一個就是 Data Binding Compiler Common:

https://mvnrepository.com/artifact/com.android.tools.build/gradle/3.6.1?mvnrepository.com

然后我們進去找到對應的 compiler 3.6.1 版本,通過 gradle 依賴,我們就能看到源碼了:

https://mvnrepository.com/artifact/androidx.databinding/databinding-compiler-common/3.6.1

compile group: 'androidx.databinding', name: 'databinding-compiler-common', version: '3.6.1'

可以看到,ViewBinding 是屬于 dataBinding 庫里面的一個小功能。

3 階段一:收集元素

由于我們僅僅只是查看 dataBinding compiler,所以,對于 gradle 調用 compiler 的哪個部分進行聯結,我們是查看不到的,但這也不影響我們跟蹤源碼。

public boolean processResources(final ResourceInput input, boolean isViewBindingEnabled) throws ParserConfigurationException, SAXException, XPathExpressionException,IOException {...// 文件處理 callbackProcessFileCallback callback = new ProcessFileCallback(){...// 是否是增量編譯if (input.isIncremental()) {// 增量編譯文件處理processIncrementalInputFiles(input, callback);} else {// 全量編譯文件處理processAllInputFiles(input, callback);}...}

我們直接來看 全量編譯文件處理 :

private static void processAllInputFiles(ResourceInput input, ProcessFileCallback callback)throws IOException, XPathExpressionException, SAXException, ParserConfigurationException {...for (File firstLevel : input.getRootInputFolder().listFiles()) {if (firstLevel.isDirectory()) {// ①、判斷 firstLevel.getName() 的 startWith 是否為 layoutif (LAYOUT_FOLDER_FILTER.accept(firstLevel, firstLevel.getName())) {// ②、創建 subPath callback.processLayoutFolder(firstLevel);// ③、遍歷 firstLevel 目錄下面的所有文件,滿足 toLowerCase().endsWith(".xml");for (File xmlFile : firstLevel.listFiles(XML_FILE_FILTER)) {// ④、處理布局文件callback.processLayoutFile(xmlFile);}} else {...}

①、判斷當前的文件夾的文件名 startWith 是否是 layout

②、會創建一個文件輸出目錄,輸出目錄為 new File(input.getRootOutputFolder(),file path); 這個 file path 做了與輸入目錄的 relativize 化,其實,可以理解為,這個輸出目錄為 輸出目錄 + file 文件名 。

③、判斷 layout 下面的文件名 endWith 是否是 .xml

④、處理 xml 文件,這個地方也會創建一個輸出目錄,跟 ② 的方式一樣,最終,這個方法會

調用到 processSingleFile 方法
然后我們來看下 processSingleFile 方法:

public boolean processSingleFile(@NonNull RelativizableFile input, @NonNull File output,boolean isViewBindingEnabled) throws ParserConfigurationException, SAXException, XPathExpressionException,IOException {// ①、解析 xmlfinal ResourceBundle.LayoutFileBundle bindingLayout = LayoutFileParser.parseXml(input, output, mResourceBundle.getAppPackage(), mOriginalFileLookup,isViewBindingEnabled);...// ②、緩存起來mResourceBundle.addLayoutBundle(bindingLayout, true);return true; }

①、這個地方會拿著 xml 文件的路徑和輸出路徑進行解析
②、將解析結果緩存起來


然后來看下 xml 的解析 parseXml

@Nullable public static ResourceBundle.LayoutFileBundle parseXml(@NonNull final RelativizableFile input,@NonNull final File outputFile, @NonNull final String pkg,@NonNull final LayoutXmlProcessor.OriginalFileLookup originalFileLookup,boolean isViewBindingEnabled){...return parseOriginalXml(RelativizableFile.fromAbsoluteFile(originalFile, input.getBaseDir()),pkg, encoding, isViewBindingEnabled); }

parseOriginalXml:

private static ResourceBundle.LayoutFileBundle parseOriginalXml(@NonNull final RelativizableFile originalFile, @NonNull final String pkg,@NonNull final String encoding, boolean isViewBindingEnabled)throws IOException {...// ①、是否是 databindingif (isBindingData) {data = getDataNode(root);rootView = getViewNode(original, root); } else if (isViewBindingEnabled) { // ②、viewBinding 是否開啟data = null;rootView = root;// xml 的根元素} else {return null;}...// 生成 bundleResourceBundle.LayoutFileBundle bundle =new ResourceBundle.LayoutFileBundle(originalFile, xmlNoExtension, original.getParentFile().getName(), pkg,isMerge, isBindingData, getViewName(rootView));final String newTag = original.getParentFile().getName() + '/' + xmlNoExtension;// viewBinding 不會 解析 dataparseData(original, data, bundle);// ③、解析表達式parseExpressions(newTag, rootView, isMerge, bundle);return bundle;

①、是否是 databinding,這個的判斷依據是,根元素是否是 layout , 獲取 data 和 rootView

②、isViewBindingEnable 就是 gradle 設置的 enable = true,根元素就是就是他的 rootView,這個地方要注意的是 data = null,data 數據只有 databinding 才會有的元素,viewBinding 是不會去解析的

③、解析表達式,這里面會循環遍歷元素,解析 view 的 id、tag、include、fragment 等等 xml 相關的元素,并且還有 databinding 相關的 @={ 的表達式,最后將結果緩存起來,源碼我就補貼了,太多,影響文章

4 、階段二:寫 Layout 文件

// xml 的輸出目錄 public void writeLayoutInfoFiles(File xmlOutDir) throws JAXBException {writeLayoutInfoFiles(xmlOutDir, mFileWriter); } public void writeLayoutInfoFiles(File xmlOutDir, JavaFileWriter writer) throws JAXBException {// ①、遍歷收集的 layout filefor (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());}

①、遍歷之前收集到的所有 LayoutFileBundle,寫入 xmlOutDir 路徑

②、生成 LayoutFileBundle 的文件名,這個文件名最終生成為:

layout.getFileName() + '-' + layout.getDirectory() + ".xml

例如 activity_main.xml,生成的 fileName 為 activity_main-layout.xml

③、將 LayoutFileBundle 轉換 xml ,寫入文件
由于我們是直接跟蹤的 databinding compiler 庫,所以無法跟蹤到 gradle 是什么聯結 compiler 庫的,所以,xmlOutDir 我是未知的,也不知道他存到了哪,但沒有關系,我們既然知道了生成的文件名規則,我們可以全局搜索該文件,最終,我們在該目錄中搜索到:

app/build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_main-layout.xml

文件內容如下:

<?xml version="1.0" encoding="utf-8" standalone="yes"?> <Layout directory="layout" filePath="/Users/codelang/project/app/src/main/res/layout/activity_main.xml"isBindingData="false"isMerge="false" layout="activity_main" modulePackage="com.codelang.viewBinding"rootNodeType="androidx.constraintlayout.widget.ConstraintLayout"><Targets><Target tag="layout/activity_main_0"view="androidx.constraintlayout.widget.ConstraintLayout"><Expressions /><location endLine="31" endOffset="51" startLine="1" startOffset="0" /></Target><Target id="@+id/tv" view="Button"><Expressions /><location endLine="16" endOffset="51" startLine="8" startOffset="4" /></Target></Targets> </Layout>

這份 xml 描述了原始 layout 的相關信息,對于 include 和 merge 是怎么關聯 tag 的,讀者可以自行運行查看

5 、階段三:寫 ViewBinding 類

@Suppress("unused")// used by tools class BaseDataBinder(val input : LayoutInfoInput) {init {input.filesToConsider.forEach {it.inputStream().use {// 又將上面收集的 layout,將 xml 轉成 LayoutFileBundleval bundle = LayoutFileBundle.fromXML(it)// 緩存進 ResourceBundleresourceBundle.addLayoutBundle(bundle, true)}}...}

可以看到,最后又去讀之前生成的 layout xml,這個地方為什么會又寫又讀,而不是直接利用之前 layout 的緩存?我想可能是因為解耦,他們都是獨立的 task。

然后來看是如何生成 Binding 類的:

@Suppress("unused")// used by android gradle plugin fun generateAll(writer : JavaFileWriter) {// 拿到所有的 LayoutFileBundle,并根據文件名進行分組排序val layoutBindings = resourceBundle.allLayoutFileBundlesInSource.groupBy(LayoutFileBundle::getFileName)// 遍歷 layoutBindingslayoutBindings.forEach { layoutName, variations ->// 將 LayoutFileBundle 信息包裝成 BaseLayoutModelval layoutModel = BaseLayoutModel(variations)val javaFile: JavaFileval classInfo: GenClassInfoLog.GenClass// 當前是否是 databindingif (variations.first().isBindingData) {...} else {// ①、不是的話,按照 ViewBinding 處理val viewBinder = layoutModel.toViewBinder()// ②、生成 java file 文件javaFile = viewBinder.toJavaFile(useLegacyAnnotations = !useAndroidX)...}writer.writeToFile(javaFile)...}

①、toViewBinder 是 BaseLayoutModel 的拓展函數,他會將 LayoutFileBundle 包裝成 ViewBinder 類返回

②、toJavaFile 是 ViewBinder 的拓展函數,該拓展函數在 ViewBinderGenerateSource 類中

// ①、最終會調用到 JavaFileGenerator 的 create 方法 fun ViewBinder.toJavaFile(useLegacyAnnotations: Boolean = false) =JavaFileGenerator(this, useLegacyAnnotations).create()private class JavaFileGenerator(private val binder: ViewBinder,private val useLegacyAnnotations: Boolean) {// 最終會調用生成 javaFile 方法,生成的類信息主要看 typeSpec 方法fun create() = javaFile(binder.generatedTypeName.packageName(), typeSpec()) {addFileComment("Generated by view binder compiler. Do not edit!")}private fun typeSpec() = classSpec(binder.generatedTypeName) {addModifiers(PUBLIC, FINAL)val viewBindingPackage = if (useLegacyAnnotations) "android" else "androidx"addSuperinterface(ClassName.get("$viewBindingPackage.viewbinding", "ViewBinding"))// TODO determine if we can elide the separate root field if the root tag has an ID.addField(rootViewField())addFields(bindingFields())addMethod(constructor())addMethod(rootViewGetter())if (binder.rootNode is RootNode.Merge) {addMethod(mergeInflate())} else {addMethod(oneParamInflate())addMethod(threeParamInflate())}addMethod(bind()) }

這個地方就貼 typeSpec 方法了,具體的,大家可以自己去看源碼,從 typeSpec 中,我們就可以看到點生成的 ViewBinding 類包含了哪些東西,rootView 字段,inflater 、bind 方法。

總結

文章已經盡量保持源碼簡短,只貼核心部分。本來還想絮絮叨叨一下,算了,就到這。

文章有關參考

https://github.com/AndroidCot/Android?github.com

總結

以上是生活随笔為你收集整理的android viewbinding_程序员必懂小技巧之ViewBinding的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。