Android?DataBinding布局的加載深入探究
上一章說(shuō)明了DataBinding生存的類之間關(guān)系,現(xiàn)在這里來(lái)看看布局是如何加載的以及view是如何映射的。
一、布局加載
這里把之前的代碼重新貼下方便說(shuō)明,代碼如下:
class MainActivity : AppCompatActivity() { private val viewModel: SimpleViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.lifecycleOwner = this binding.viewModel = viewModel } }
其中布局加載就這一行:DataBindingUtil.setContentView(this, R.layout.activity_main),所以進(jìn)入到DataBindingUtil中,代碼如下:
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity, int layoutId) { return setContentView(activity, layoutId, sDefaultComponent); }
就是簡(jiǎn)單的調(diào)用轉(zhuǎn)發(fā)而已,繼續(xù)下一步,如下:
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity, int layoutId, @Nullable DataBindingComponent bindingComponent) { activity.setContentView(layoutId); View decorView = activity.getWindow().getDecorView(); ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content); return bindToAddedViews(bindingComponent, contentView, 0, layoutId); }
activity.setContentView(layoutId),這和我們不用DataBinding寫的一樣啊,所以Databinding在這里就幫我們加載了布局。
接下來(lái),看DataBinding是如何實(shí)現(xiàn)view映射的。
二、view映射
然后拿到decorView 并找到contentView ,最后調(diào)用bindToAddedViews,bindToAddedViews的函數(shù)如下:
private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component, ViewGroup parent, int startChildren, int layoutId) { final int endChildren = parent.getChildCount(); final int childrenAdded = endChildren - startChildren; if (childrenAdded == 1) { final View childView = parent.getChildAt(endChildren - 1); return bind(component, childView, layoutId); } else { final View[] children = new View[childrenAdded]; for (int i = 0; i < childrenAdded; i++) { children[i] = parent.getChildAt(i + startChildren); } return bind(component, children, layoutId); } }
在我們的場(chǎng)景里面,endChildren 應(yīng)該為1,childrenAdded 也為1,所以走了第一個(gè)分支,繼續(xù)調(diào)用bind函數(shù),如下:
private static DataBinderMapper sMapper = new DataBinderMapperImpl(); static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root, int layoutId) { return (T) sMapper.getDataBinder(bindingComponent, root, layoutId); }
調(diào)用了sMapper的getDataBinder函數(shù),這里的sMapper類型為DataBinderMapperImpl,還記得上一章說(shuō)過(guò)有兩個(gè)DataBinderMapperImpl嗎?為了便于說(shuō)明,這里再把之前的類圖貼下:
額,這就尷尬了,所以這里的Mapper到底是哪個(gè)呢?之前說(shuō)過(guò)左邊的是android提供的,右邊的是我們自己包下面的;其實(shí)這里的sMapper屬于左邊這個(gè)行列,也就是androidx這個(gè)包下面的。那他們有什么區(qū)別呢?你可以認(rèn)為左邊的提供了一個(gè)簡(jiǎn)單的代理功能,其實(shí)它就是簡(jiǎn)單對(duì)右邊的Mapper類進(jìn)行包裝而已。
這里需要說(shuō)明下sMapper對(duì)象的初始化過(guò)程,我們知道類加載會(huì)觸發(fā)類變量(靜態(tài)變量)的初始化,這個(gè)時(shí)候sMapper就會(huì)被初始化,這個(gè)時(shí)候會(huì)調(diào)用DataBinderMapperImpl(左邊那個(gè)mapper)的構(gòu)建函數(shù),代碼如下:
package androidx.databinding;//位于androidx包下面 public class DataBinderMapperImpl extends MergedDataBinderMapper { DataBinderMapperImpl() { //這個(gè)DataBinderMapperImpl就是我們自己包下面的了 addMapper(new com.zfang.databindingstudy.DataBinderMapperImpl()); } }
正如前面所說(shuō),androidx下面的mapper類包裝了項(xiàng)目中的mapper類,addMapper代碼如下:
public void addMapper(DataBinderMapper mapper) { Class<? extends DataBinderMapper> mapperClass = mapper.getClass(); if (mExistingMappers.add(mapperClass)) { mMappers.add(mapper); final List<DataBinderMapper> dependencies = mapper.collectDependencies(); for(DataBinderMapper dependency : dependencies) { addMapper(dependency); } } }
這里會(huì)把項(xiàng)目中的mapper(即DataBinderMapperImpl)加入到mMappers這個(gè)CopyOnWriteArrayList中,后面會(huì)用到。
此時(shí)可以繼續(xù)看看getDataBinder的實(shí)現(xiàn)了(其實(shí)現(xiàn)位于MergedDataBinderMapper中),代碼如下:
@Override public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view, int layoutId) { for(DataBinderMapper mapper : mMappers) { ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId); if (result != null) { return result; } } if (loadFeatures()) { return getDataBinder(bindingComponent, view, layoutId); } return null; }
這里就是從mMappers中把mapper拿出來(lái),再根據(jù)傳遞進(jìn)來(lái)的參數(shù)view、layoutId找到相應(yīng)的ViewDataBinding對(duì)象;這里的mMappers就是剛剛提到的那個(gè)CopyOnWriteArrayList,所以會(huì)調(diào)用到我們的DataBinderMapperImpl,其中的getDataBinder實(shí)現(xiàn)如下:
private static final int LAYOUT_ACTIVITYMAIN = 1; private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1); static { INTERNAL_LAYOUT_ID_LOOKUP.put(com.zfang.databindingstudy.R.layout.activity_main, LAYOUT_ACTIVITYMAIN); } @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_ACTIVITYMAIN: { if ("layout/activity_main_0".equals(tag)) { return new ActivityMainBindingImpl(component, view); } throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag); } } } return null; }
這里有個(gè)SparseIntArray ,它定義了我們的布局與一個(gè)整數(shù)的映射關(guān)系,上面的代碼首先拿到view的tag,這里返回的tag為layout/activity_main_0(回憶下:上一章說(shuō)過(guò)DataBinding會(huì)生存兩個(gè)xml,其中一個(gè)加了tag,那里說(shuō)的tag正是和這里對(duì)應(yīng)上了,其作用就體現(xiàn)在這里),所以會(huì)返回ActivityMainBindingImpl,這正是需要的ViewDataBinding類。
繼續(xù)進(jìn)入ActivityMainBindingImpl的構(gòu)建函數(shù)中,代碼如下:
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) { this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds)); } private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) { super(bindingComponent, root, 2 , (android.widget.TextView) bindings[1] , (android.widget.TextView) bindings[2] ); this.first.setTag(null); this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0]; this.mboundView0.setTag(null); this.second.setTag(null); setRootTag(root); // listeners invalidateAll(); }
先調(diào)用了第一個(gè)構(gòu)造函數(shù),然后進(jìn)入第二個(gè)。第二個(gè)構(gòu)造函數(shù)又調(diào)用了父類的相應(yīng)構(gòu)造函數(shù),代碼如下:
protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount, TextView first, TextView second) { super(_bindingComponent, _root, _localFieldCount); this.first = first; this.second = second; }
沒(méi)錯(cuò),上面的bindings數(shù)組中的bindings[1]、bindings[2]正是對(duì)應(yīng)到了我們這個(gè)場(chǎng)景中的first和second兩個(gè)view?,F(xiàn)在的問(wèn)題是bindings數(shù)組中的值是怎么來(lái)的呢?
我們繼續(xù)看看ActivityMainBindingImpl類中第一個(gè)構(gòu)建數(shù)據(jù)中調(diào)用的函數(shù)mapBindings,看來(lái)在mapBindings中會(huì)填充bindings數(shù)組,mapBindings代碼如下:
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root, int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) { Object[] bindings = new Object[numBindings]; mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true); return bindings; }
這里 根據(jù)numBindings新建了一個(gè)數(shù)組,繼續(xù):
private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot) { final int indexInIncludes; final ViewDataBinding existingBinding = getBinding(view); if (existingBinding != null) { return; } Object objTag = view.getTag(); final String tag = (objTag instanceof String) ? (String) objTag : null; boolean isBound = false; //第一次進(jìn)來(lái)isRoot為true,tag為根據(jù)布局所以是以layout開(kāi)頭,因此這進(jìn)入第一個(gè)if if (isRoot && tag != null && tag.startsWith("layout")) { final int underscoreIndex = tag.lastIndexOf('_'); if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) { final int index = parseTagInt(tag, underscoreIndex + 1); if (bindings[index] == null) { bindings[index] = view;//放入bindings數(shù)組,這里的view代表根布局 } //處理包含布局中有include標(biāo)簽的情況 indexInIncludes = includes == null ? -1 : index; isBound = true; } else { indexInIncludes = -1; } } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) { //如何不是根布局,對(duì)應(yīng)到我們的場(chǎng)景則會(huì)走到這里,我們的兩個(gè)TextView的 //tag剛是以binding開(kāi)頭的,其實(shí)只要寫了綁定表達(dá)式就會(huì)到這里。 int tagIndex = parseTagInt(tag, BINDING_NUMBER_START); if (bindings[tagIndex] == null) { bindings[tagIndex] = view; } isBound = true; indexInIncludes = includes == null ? -1 : tagIndex; } else { // Not a bound view indexInIncludes = -1; } if (!isBound) { final int id = view.getId(); if (id > 0) { int index; if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 && bindings[index] == null) { bindings[index] = view; } } } //如果是ViewGroup則遞歸處理找到相應(yīng)的view if (view instanceof ViewGroup) { final ViewGroup viewGroup = (ViewGroup) view; final int count = viewGroup.getChildCount(); int minInclude = 0; for (int i = 0; i < count; i++) { final View child = viewGroup.getChildAt(i); boolean isInclude = false; //處理include標(biāo)簽 if (indexInIncludes >= 0 && child.getTag() instanceof String) { String childTag = (String) child.getTag(); if (childTag.endsWith("_0") && childTag.startsWith("layout") && childTag.indexOf('/') > 0) { // This *could* be an include. Test against the expected includes. int includeIndex = findIncludeIndex(childTag, minInclude, includes, indexInIncludes); if (includeIndex >= 0) { isInclude = true; minInclude = includeIndex + 1; final int index = includes.indexes[indexInIncludes][includeIndex]; final int layoutId = includes.layoutIds[indexInIncludes][includeIndex]; int lastMatchingIndex = findLastMatching(viewGroup, i); if (lastMatchingIndex == i) { bindings[index] = DataBindingUtil.bind(bindingComponent, child, layoutId); } else { final int includeCount = lastMatchingIndex - i + 1; final View[] included = new View[includeCount]; for (int j = 0; j < includeCount; j++) { included[j] = viewGroup.getChildAt(i + j); } bindings[index] = DataBindingUtil.bind(bindingComponent, included, layoutId); i += includeCount - 1; } } } } //非include if (!isInclude) { mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false); } } } }
這里就是實(shí)現(xiàn)view數(shù)組映射的關(guān)鍵,主要功能就是填充了bindings數(shù)組,思路就是找到包含綁定表達(dá)式的控件,然后把它們記錄下來(lái)放到一個(gè)數(shù)組中,方便在相應(yīng)控件的數(shù)據(jù)變化的時(shí)候能夠通知到控件, 這里其實(shí)就是找到如下布局中的兩個(gè)TextView然后加入到bindings中。
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:tag="layout/activity_main_0" tools:context=".MainActivity"> <TextView android:id="@+id/first" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginVertical="12dp" android:tag="binding_1" android:textColor="#333333" android:textSize="18sp" app:layout_constraintBottom_toTopOf="@id/second" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" /> <TextView android:id="@+id/second" android:layout_width="wrap_content" android:layout_height="wrap_content" android:tag="binding_2" android:textColor="#999" android:textSize="14sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/first" /> </androidx.constraintlayout.widget.ConstraintLayout>
上面那段代碼的邏輯就是找到ConstraintLayout(android:tag="layout/activity_main_0") 以及兩個(gè)TextView(tag分別為binding_1和binding_2),總共三個(gè)控件。ConstraintLayout就是根布局,兩個(gè)TextView就是我們需要操作的View。
好了,布局view映射完成,簡(jiǎn)單總結(jié)下:首先就是DataBinding會(huì)幫我們調(diào)用setContentView,所以我們不用調(diào)用這個(gè)方法;其次DataBinding會(huì)幫我們找到包含有數(shù)據(jù)綁定表達(dá)式的View其后幫我們存起來(lái),方便在數(shù)據(jù)變化的時(shí)候操作我們的View。
下一章繼續(xù)分析數(shù)據(jù)是如何與控件進(jìn)行綁定的。
如果你對(duì)DataBinding生存的類關(guān)系有疑問(wèn),可以返回上一章DataBinding原理----類關(guān)系進(jìn)行參考。
到此這篇關(guān)于Android DataBinding布局的加載深入探究的文章就介紹到這了,更多相關(guān)Android DataBinding 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android 解決TextView排版參差不齊的問(wèn)題
這篇文章主要介紹了Android 解決TextView排版參差不齊的問(wèn)題的相關(guān)資料,需要的朋友可以參考下2017-01-01Android使用phonegap從相冊(cè)里面獲取照片(代碼分享)
本文主要介紹了使用phonegap從相冊(cè)里面獲取照片的實(shí)現(xiàn)方法代碼。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-03-03Android apk無(wú)法安裝及閃退問(wèn)題解決辦法
這篇文章主要介紹了Android apk無(wú)法安裝及閃退問(wèn)題的相關(guān)資料,這里對(duì)閃退問(wèn)題進(jìn)行詳解及解決步驟的詳細(xì)介紹,需要的朋友可以參考下2017-07-07android編程獲取和設(shè)置系統(tǒng)鈴聲和音量大小的方法
這篇文章主要介紹了android編程獲取和設(shè)置系統(tǒng)鈴聲和音量大小的方法,實(shí)例分析了Android針對(duì)音頻的相關(guān)操作技巧,需要的朋友可以參考下2017-06-06Android camera2 判斷相機(jī)功能是否可控的實(shí)例
下面小編就為大家?guī)?lái)一篇Android camera2 判斷相機(jī)功能是否可控的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03Android項(xiàng)目中引入aar包的正確方法介紹
生成aar之后下一步就是如何引用本地的aar文件,下面這篇文章主要給大家介紹了關(guān)于Android項(xiàng)目中引入aar包的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08Android編程使用加速度傳感器實(shí)現(xiàn)搖一搖功能及優(yōu)化的方法詳解
這篇文章主要介紹了Android編程使用加速度傳感器實(shí)現(xiàn)搖一搖功能及優(yōu)化的方法,結(jié)合實(shí)例形式分析了Android傳感器的調(diào)用方法、參數(shù)含義及具體使用技巧,需要的朋友可以參考下2017-08-08