Android?DataBinding布局的加載深入探究
上一章說明了DataBinding生存的類之間關(guān)系,現(xiàn)在這里來看看布局是如何加載的以及view是如何映射的。
一、布局加載
這里把之前的代碼重新貼下方便說明,代碼如下:
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),所以進入到DataBindingUtil中,代碼如下:
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}就是簡單的調(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在這里就幫我們加載了布局。
接下來,看DataBinding是如何實現(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);
}
}在我們的場景里面,endChildren 應(yīng)該為1,childrenAdded 也為1,所以走了第一個分支,繼續(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,還記得上一章說過有兩個DataBinderMapperImpl嗎?為了便于說明,這里再把之前的類圖貼下:

額,這就尷尬了,所以這里的Mapper到底是哪個呢?之前說過左邊的是android提供的,右邊的是我們自己包下面的;其實這里的sMapper屬于左邊這個行列,也就是androidx這個包下面的。那他們有什么區(qū)別呢?你可以認為左邊的提供了一個簡單的代理功能,其實它就是簡單對右邊的Mapper類進行包裝而已。
這里需要說明下sMapper對象的初始化過程,我們知道類加載會觸發(fā)類變量(靜態(tài)變量)的初始化,這個時候sMapper就會被初始化,這個時候會調(diào)用DataBinderMapperImpl(左邊那個mapper)的構(gòu)建函數(shù),代碼如下:
package androidx.databinding;//位于androidx包下面
public class DataBinderMapperImpl extends MergedDataBinderMapper {
DataBinderMapperImpl() {
//這個DataBinderMapperImpl就是我們自己包下面的了
addMapper(new com.zfang.databindingstudy.DataBinderMapperImpl());
}
}正如前面所說,androidx下面的mapper類包裝了項目中的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);
}
}
}這里會把項目中的mapper(即DataBinderMapperImpl)加入到mMappers這個CopyOnWriteArrayList中,后面會用到。
此時可以繼續(xù)看看getDataBinder的實現(xiàn)了(其實現(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拿出來,再根據(jù)傳遞進來的參數(shù)view、layoutId找到相應(yīng)的ViewDataBinding對象;這里的mMappers就是剛剛提到的那個CopyOnWriteArrayList,所以會調(diào)用到我們的DataBinderMapperImpl,其中的getDataBinder實現(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;
}這里有個SparseIntArray ,它定義了我們的布局與一個整數(shù)的映射關(guān)系,上面的代碼首先拿到view的tag,這里返回的tag為layout/activity_main_0(回憶下:上一章說過DataBinding會生存兩個xml,其中一個加了tag,那里說的tag正是和這里對應(yīng)上了,其作用就體現(xiàn)在這里),所以會返回ActivityMainBindingImpl,這正是需要的ViewDataBinding類。
繼續(xù)進入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òu)造函數(shù),然后進入第二個。第二個構(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;
}沒錯,上面的bindings數(shù)組中的bindings[1]、bindings[2]正是對應(yīng)到了我們這個場景中的first和second兩個view。現(xiàn)在的問題是bindings數(shù)組中的值是怎么來的呢?
我們繼續(xù)看看ActivityMainBindingImpl類中第一個構(gòu)建數(shù)據(jù)中調(diào)用的函數(shù)mapBindings,看來在mapBindings中會填充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新建了一個數(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;
//第一次進來isRoot為true,tag為根據(jù)布局所以是以layout開頭,因此這進入第一個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標簽的情況
indexInIncludes = includes == null ? -1 : index;
isBound = true;
} else {
indexInIncludes = -1;
}
} else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
//如何不是根布局,對應(yīng)到我們的場景則會走到這里,我們的兩個TextView的
//tag剛是以binding開頭的,其實只要寫了綁定表達式就會到這里。
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標簽
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);
}
}
}
}這里就是實現(xiàn)view數(shù)組映射的關(guān)鍵,主要功能就是填充了bindings數(shù)組,思路就是找到包含綁定表達式的控件,然后把它們記錄下來放到一個數(shù)組中,方便在相應(yīng)控件的數(shù)據(jù)變化的時候能夠通知到控件, 這里其實就是找到如下布局中的兩個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") 以及兩個TextView(tag分別為binding_1和binding_2),總共三個控件。ConstraintLayout就是根布局,兩個TextView就是我們需要操作的View。
好了,布局view映射完成,簡單總結(jié)下:首先就是DataBinding會幫我們調(diào)用setContentView,所以我們不用調(diào)用這個方法;其次DataBinding會幫我們找到包含有數(shù)據(jù)綁定表達式的View其后幫我們存起來,方便在數(shù)據(jù)變化的時候操作我們的View。
下一章繼續(xù)分析數(shù)據(jù)是如何與控件進行綁定的。
如果你對DataBinding生存的類關(guān)系有疑問,可以返回上一章DataBinding原理----類關(guān)系進行參考。
到此這篇關(guān)于Android DataBinding布局的加載深入探究的文章就介紹到這了,更多相關(guān)Android DataBinding 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android使用phonegap從相冊里面獲取照片(代碼分享)
本文主要介紹了使用phonegap從相冊里面獲取照片的實現(xiàn)方法代碼。具有很好的參考價值,下面跟著小編一起來看下吧2017-03-03
android編程獲取和設(shè)置系統(tǒng)鈴聲和音量大小的方法
這篇文章主要介紹了android編程獲取和設(shè)置系統(tǒng)鈴聲和音量大小的方法,實例分析了Android針對音頻的相關(guān)操作技巧,需要的朋友可以參考下2017-06-06
Android編程使用加速度傳感器實現(xiàn)搖一搖功能及優(yōu)化的方法詳解
這篇文章主要介紹了Android編程使用加速度傳感器實現(xiàn)搖一搖功能及優(yōu)化的方法,結(jié)合實例形式分析了Android傳感器的調(diào)用方法、參數(shù)含義及具體使用技巧,需要的朋友可以參考下2017-08-08

