Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Data Binding

Android Data Binding

編輯:關於Android編程

1 引入

如何高效地實現以下界面?

登錄/未登錄vc23z/FJbWFnZVZpZXehorHqzOJUZXh0Vmlld6Giu/231lRleHRWaWV3oaK1x8K8QnV0dG9uo6zIu7rzuPhCdXR0b27J6NbDvODM/cb3o6zU2bj5vt21x8K917TMrNW5yr621NOmyv2+3aO7PC9wPg0KPHA+yrXP1sjnz8KjujxiciAvPg0KKiBVc2VyLmphdmE8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> public class User { private String name; private int score; private int level; private int avatar; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getScore() { return score; } public void setScore(int score) { this.score = score; } public int getLevel() { return level; } public int getAvatar() { return avatar; } public void setAvatar(int avatar) { this.avatar = avatar; } public void setLevel(int level) { this.level = level; } public static User newInstance() { User user = new User(); user.setName("王大錘:" + (int)(Math.random() * 10)); user.setScore((int) (Math.random() * 999)); user.setLevel((int) (Math.random() * 77)); user.setAvatar(R.drawable.avatar); return user; } } activity_detail.xml



    
    
    
    
    
    
DetailActivity
public class DetailActivity extends AppCompatActivity {

    ImageView avatarIV;
    TextView nameTV;
    TextView descTV;
    Button actionBtn;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);

        initView();
        login();
    }

    private void login(){ fill(User.newInstance()); }

    private void logout(){ fill(null); }

    private void initView() {
        avatarIV = (ImageView) findViewById(R.id.detail_avatar);
        nameTV = (TextView) findViewById(R.id.detail_name);
        descTV = (TextView) findViewById(R.id.detail_desc);
        actionBtn = (Button) findViewById(R.id.detail_action_button);
    }

    private void fill(final User user){
        final int visibility = user != null ? View.VISIBLE : View.GONE;
        if (avatarIV != null){
            avatarIV.setVisibility(visibility);
            if (user != null)
                avatarIV.setImageDrawable(ContextCompat.getDrawable(this,user.getAvatar()));
        }

        if (nameTV != null){
            nameTV.setVisibility(visibility);
            if (user != null)
                nameTV.setText(user.getName());
        }

        if (descTV != null){
            descTV.setVisibility(visibility);
            if (user != null)
                descTV.setText(String.format("積分:%d 等級:%d",user.getScore(),user.getLevel()));
        }

        if (actionBtn != null){
            actionBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (user == null) login();
                    else logout();
                }
            });
            actionBtn.setText(user == null ? "登錄":"退出登錄");
        }
    }
}

2 去掉煩人的findViewById(View注入)

可以看到,在Activity中View的定義、find、判空占據了大量篇幅,我們需要更優雅的實現。

2.1 ButterKnife

你可能聽說過Jake Wharton的ButterKnife,這個庫只需要在定義View變量的時候通過注解傳入對應id,隨後在onCreate時調用ButterKnife.bind(this)即可完成view的注入,示例如下:

class ExampleActivity extends Activity {
  @BindView(R.id.user) EditText username;
  @BindView(R.id.pass) EditText password;

  @Override public void onCreate(Bundle savedInstanceState{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
  }
}

2.2 Android Data Binding

如果使用了Android Data Binding,那麼View的定義、find、判空這些都不用寫了,如何做呢?

2.2.1 准備工作

首先,你需要滿足一個條件:你的Android Plugin for Gradle版本必須等於或高於1.5.0-alpha1版本,這個版本位於根目錄build.gradle中,示例如下:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0-rc1'
    }
}

接著,你必須告訴編譯器開啟Data Binding,一般位於app:build.gradle的android標簽中,示例如下:

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    dataBinding {
        enabled true
    }
    ...
}

2.2.2 修改layout.xml

activity_detail.xml為例,原來的根節點為LinearLayout,如下所示:




    
    
    ....

我們拷一份activity_detail.xml,改為activity_detail2.xml,並且需要在外面wrap一層layout標簽,修改後的activity_detail2.xml為:



    
    
        
        
        ...
    

2.2.3 開始享受樂趣吧!

在上述操作完成後,編譯器會自動為我們生成
com.asha.demo.databinding.ActivityDetail2Binding.java類,這個類的命令方式為:包名 + databinding + activity_detail2駝峰命名方式 + Binding.java。隨後,使用這個activity_detail2DetailActivity2.java的代碼可以簡化為:

public class DetailActivity2 extends AppCompatActivity {

    ActivityDetail2Binding binding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this,R.layout.activity_detail2);

        login();
    }

    private void login(){ fill(User.newInstance()); }

    private void logout(){ fill(null); }

    private void fill(final User user){
        final int visibility = user != null ? View.VISIBLE : View.GONE;
        if (user != null){
            binding.detailAvatar.setImageDrawable(ContextCompat.getDrawable(this,user.getAvatar()));
            binding.detailName.setText(user.getName());
            binding.detailDesc.setText(String.format("積分:%d 等級:%d",user.getScore(),user.getLevel()));
        }

        binding.detailAvatar.setVisibility(visibility);
        binding.detailName.setVisibility(visibility);
        binding.detailDesc.setVisibility(visibility);
        binding.detailActionButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                if (user == null) login();
                else logout();
            }
        });
        binding.detailActionButton.setText(user == null ? "登錄":"退出登錄");
    }
}

是的,所有View的定義、find、判空都不見了,所有的這些操作都在編譯器為我們生成的ActivityDetail2Binding.java中完成,只需要在onCreate時調用如下代碼進行setContentView即可實現,

binding = DataBindingUtil.setContentView(this,R.layout.activity_detail2);

我的天哪

2.2.4 ActivityDetail2Binding中注入View相關的代碼分析

可以在as中方便的查看編譯器自動生成的類,這個類位於/app/build/intermediates/classes/debug/com/asha/demo/databinding/ActivityDetail2Binding.class中,縮減掉Binding邏輯後的代碼為:

public class ActivityDetail2Binding extends ViewDataBinding {
    private static final IncludedLayouts sIncludes = null;
    private static final SparseIntArray sViewsWithIds = new SparseIntArray();
    public final Button detailActionButton;
    public final ImageView detailAvatar;
    public final TextView detailDesc;
    public final TextView detailName;
    private final LinearLayout mboundView0;
    private long mDirtyFlags = -1L;

    public ActivityDetail2Binding(DataBindingComponent bindingComponent, View root) {
        super(bindingComponent, root, 0);
        Object[] bindings = mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds);
        this.detailActionButton = (Button)bindings[4];
        this.detailAvatar = (ImageView)bindings[1];
        this.detailDesc = (TextView)bindings[3];
        this.detailName = (TextView)bindings[2];
        this.mboundView0 = (LinearLayout)bindings[0];
        this.mboundView0.setTag((Object)null);
        this.setRootTag(root);
        this.invalidateAll();
    }
    ...
    static {
        sViewsWithIds.put(2131492948, 1);
        sViewsWithIds.put(2131492949, 2);
        sViewsWithIds.put(2131492950, 3);
        sViewsWithIds.put(2131492951, 4);
    }
}

其中全局靜態SparseIntArray數組中存放了4個數字,這個四個數字為R.java中生成的對應View的id,

public final class R {
    ...
    public static final class id {
        ...
        public static final int detail_action_button = 2131492951;
        public static final int detail_avatar = 2131492948;
        public static final int detail_desc = 2131492950;
        public static final int detail_name = 2131492949;
        ...
    }
    ...
}

ActvityDetail2Binding實例構造的時候調用了mapBindings,一次解決了所有View的查找,mapBindings函數在ActvityDetail2Binding父類ViewDataBinding中實現。

3 使用表達式在layout.xml中填充model數據

ActivityDetail2.java中還存在大量的View控制、數據填充代碼,如何把這些代碼在交給layout.xml完成呢?

3.1 ModelAdapter類

第2節中已經定義了User.java類作為Model類,但是我們經常會遇到Model類和真正View展示不一致的情況,本例子中定義一個來ModelAdapter類來完整Model數據到展示數據的適配。示例代碼為ActivityDetail3.java的內部類,可以調用ActivityDetail3.java中的函數,代碼定義如下:

public class DetailActivity3 extends AppCompatActivity {
    public class ModelAdapter {
        private User user;

        public ModelAdapter(User user) { this.user = user;}

        public String getName(){ return user != null ? user.getName() : null;}

        public Drawable getAvatar(){
            return user != null ? ContextCompat.getDrawable(DetailActivity3.this,user.getAvatar()) : null;
        }

        public String getDesc(){
            return user != null ? String.format("積分:%d 等級:%d",user.getScore(),user.getLevel()) : null;
        }

        public String actionText(){ return user != null ? "退出登錄" : "登陸"; }

        public void clickHandler(View view){
            if (user != null) logout();
            else login();
        }
    }
}

3.2 activity_detail3.xml中使用model

同樣復制一份activity_detail2.xmlactivity_detail3.xml,在節點加入節點,並且在裡面定義需要用的model類(比如ModelAdapter adapter),當然也可以是基礎類型變量(比如int visibility);

隨後,就可以在下面的view中使用表達式了,全部布局文件如下:



    
        
        
    
    
        
        
        
        
        
        

3.3 DetailActivity3.java中調用填充

如下代碼所示,只需要在登錄狀態改變的時候,給viewDataBinding設置所需要的adatper、visibility值,即可完成數據的填充

public class DetailActivity3 extends AppCompatActivity {

    ActivityDetail3Binding binding;
    public class ModelAdapter {
        ...
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this,R.layout.activity_detail3);
        login();
    }

    private void login(){
        fill(User.newInstance());
    }

    private void logout(){
        fill(null);
    }

    private void fill(final User user){
        binding.setAdapter(new ModelAdapter(user));
        binding.setVisibility( user != null ? View.VISIBLE : View.GONE);
    }
}

3.4 ActivityDetail3Binding中填充相關的代碼分析

同樣,ActivityDetail3Binding中,編譯器根據activity_detail3.xml中的標簽,自動生成了諸如setAdapter、setVisibility的代碼,setAdapter相關代碼如下:

public class ActivityDetail3Binding extends ViewDataBinding{

    private ModelAdapter mAdapter;
    ...
    public void setAdapter(ModelAdapter adapter) {
        this.mAdapter = adapter;
        synchronized(this) {
            this.mDirtyFlags |= 1L;
        }

        this.notifyPropertyChanged(1);
        super.requestRebind();
    }

    public ModelAdapter getAdapter() {
        return this.mAdapter;
    }
    ...
}

非常簡單,自動生成了getter和setter,在完成set操作後,調用執行notifyPropertyChangedsuper.requestRebind()
* notifyPropertyChanged
ViewDataBinding本身就是一個BaseObservable, 在往ViewDataBinding注冊觀察某個屬性的變化,如果注冊了mAdapter的變化,對應的觀察器就會接收到回調。相關邏輯與反向Binding相關,谷歌官方還沒給出相關使用文檔,不再深入分析;

super.requestRebind()
1.此函數為ViewDataBinding中的函數,具體實現為判斷現在是否有Rebind請求,如果有則return;如果沒有則根據運行時sdk版本交給handler或者choreographer插入到下一幀中執行mRebindRunnable。
2.在mRebindRunnable中會根據當前sdk版本,如果大於等於KITKAT,則需要在onAttachToWindow後執行executePendingBindings;否則直接執行executePendingBindings。
public abstract class ViewDataBinding extends BaseObservable {
    protected void requestRebind() {
        synchronized (this) {
            if (mPendingRebind) {
                return;
            }
            mPendingRebind = true;
        }
        if (USE_CHOREOGRAPHER) {
            mChoreographer.postFrameCallback(mFrameCallback);
        } else {
            mUIThreadHandler.post(mRebindRunnable);
        }

    }

    /**
     * Runnable executed on animation heartbeat to rebind the dirty Views.
     */
    private final Runnable mRebindRunnable = new Runnable() {
        @Override
        public void run() {
            synchronized (this) {
                mPendingRebind = false;
            }
            if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
                // Nested so that we don't get a lint warning in IntelliJ
                if (!mRoot.isAttachedToWindow()) {
                    // Don't execute the pending bindings until the View
                    // is attached again.
                    mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                    mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                    return;
                }
            }
            executePendingBindings();
        }
    };
}

3.在父類ViewDataBinding中經過一些的判斷,調用到ActivityDetail3Binding中的executeBindings,在executeBindings中根據dirtyFlags執行不同的View屬性賦值,以下所有ActivityDetail3Binding相關代碼都是編譯器自動生成的

public class ActivityDetail3Binding extends ViewDataBinding{
    ...

    protected void executeBindings() {
        long dirtyFlags = 0L;
        synchronized(this) {
            dirtyFlags = this.mDirtyFlags;
            this.mDirtyFlags = 0L;
        }

        Drawable avatarAdapter = null;
        ModelAdapter adapter = this.mAdapter;
        String descAdapter = null;
        String nameAdapter = null;
        ActivityDetail3Binding.OnClickListenerImpl androidViewViewOnCli = null;
        String actionTextAdapter = null;
        int visibility = this.mVisibility;
        if((dirtyFlags & 5L) != 0L && adapter != null) {
            avatarAdapter = adapter.getAvatar();
            descAdapter = adapter.getDesc();
            nameAdapter = adapter.getName();
            androidViewViewOnCli = (this.mAndroidViewViewOnCl == null?(this.mAndroidViewViewOnCl = new ActivityDetail3Binding.OnClickListenerImpl()):this.mAndroidViewViewOnCl).setValue(adapter);
            actionTextAdapter = adapter.actionText();
        }

        if((dirtyFlags & 6L) != 0L) {
            ;
        }

        if((dirtyFlags & 5L) != 0L) {
            TextViewBindingAdapter.setText(this.detailActionButton, actionTextAdapter);
            this.detailActionButton.setOnClickListener(androidViewViewOnCli);
            ImageViewBindingAdapter.setImageDrawable(this.detailAvatar, avatarAdapter);
            TextViewBindingAdapter.setText(this.detailDesc, descAdapter);
            TextViewBindingAdapter.setText(this.detailName, nameAdapter);
        }

        if((dirtyFlags & 6L) != 0L) {
            this.detailAvatar.setVisibility(visibility);
            this.detailDesc.setVisibility(visibility);
            this.detailName.setVisibility(visibility);
        }

    }
    ...
}

至此,完成了View數據的填充分析。

4 Binding

自動生成的ViewDataBinding類(例如ActivityDetail3Binding)內包含了Model + View,是MVVM中的MV的概念。

第2章的View注入,第3章的View賦值都是鋪墊,他們最後都是為Binding操作進行服務。目前谷歌已經支持雙向Binding,但上文已經提到,目前資料比較少。本文只關注單向的Binding,即:Model的變化,自動同步到View上。

4.1 使用ObservableField

目前所提供的ObservableField有:

Observable類型 對應原類型 ObservableArrayList ArrayList ObservableArrayMap ArrayMap ObservableBoolean boolean ObservableByte byte ObservableChar char ObservableFloat float ObservableDouble double ObservableLong long ObservableInt int ObservableParcelable   ObservableField  

本文使用簡單的ObservableInt作為示例,解決visibility的單項綁定問題。
* 改造activity_detail4.xml:定義類型為ObservableInt的variable,name為visibility,隨後賦值給ImageView的android:visibility,示例如下:


    
        
    
    
       ...
        
        ...
    
改造DetailActivity4.java,只需要在onCreate時把visibility賦值給binding(ActivityDetail4Binding)即可,後面對visibility的操作,就會更新到view上,示例代碼如下:
public class DetailActivity4 extends AppCompatActivity {
    ActivityDetail4Binding binding;
    ObservableInt visibility = new ObservableInt();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this,R.layout.activity_detail4,new MyComponent());
        binding.setVisibility(visibility);
        login();
    }

    private void login(){  fill(User.newInstance());  }

    private void logout(){ fill(null); }

    private void fill(final User user){
        visibility.set(user != null ? View.VISIBLE : View.GONE);
        ....
    }
    ....
}

4.2 ActivityDetail4Binding中單向綁定相關的代碼分析

與給ActivityDetail4Binding直接set純Model不同,所有的ObservableField都實現了Observable接口,只要實現了Observable接口,都是單向Binding類型,所以ActivityDetail4Binding中的setVisibility多加了一行代碼:this.updateRegistration(1, visibility),其中1為propertyId,目前一共自動生成了2個,0為adatper,1為visibility,代碼如下:

public class ActivityDetail4Binding extends ViewDataBinding {
    ...
    public void setVisibility(ObservableInt visibility) {
        this.updateRegistration(1, visibility);
        this.mVisibility = visibility;
        synchronized(this) {
            this.mDirtyFlags |= 2L;
        }

        this.notifyPropertyChanged(3);
        super.requestRebind();
    }
    ...
}

updateRegistration函數為ViewDataBinding中的函數,會根據 Observable、ObservableList、ObservableMap三種類型,分別創建對應的Listener。ObservableInt為Observable,所以會使用CREATE_PROPERTY_LISTENER,在registerTo函數中創建WeakPropertyListener
代碼如下:

public abstract class ViewDataBinding extends BaseObservable {
    ...
    private boolean updateRegistration(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        if (observable == null) {
            return unregisterFrom(localFieldId);
        }
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener == null) {
            registerTo(localFieldId, observable, listenerCreator);
            return true;
        }
        if (listener.getTarget() == observable) {
            return false;//nothing to do, same object
        }
        unregisterFrom(localFieldId);
        registerTo(localFieldId, observable, listenerCreator);
        return true;
    }
    ...
}

在WeakPropertyListener的mListener有個setTarget函數,這個函數會向mObservable(即外面傳進來的visibility)注冊一個監聽器,如果visibility值發生變化,這個listener就會得到通知,回調到WeakPropertyListeneronPropertyChanged,接著通知到binding(ActivityDetail4Binding)的handleFieldChange,在handleFieldChange中調用了ActivityDetail4BindingonFieldChange函數,如果返回值為true,則在handleFieldChange中調用requestRebind(),通知View進行賦值更新界面,onFieldChange相關代碼如下:

public abstract class ViewDataBinding extends BaseObservable {
    ...
   private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
        if (result) {
            requestRebind();
        }
    }
    ...
}
public class ActivityDetail4Binding extends ViewDataBinding {
    ...
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch(localFieldId) {
        case 0:
            return this.onChangeAdapter((ModelAdapter)object, fieldId);
        case 1:
            return this.onChangeVisibility((ObservableInt)object, fieldId);
        default:
            return false;
        }
    }
    ...
}

4.3 Observable Objects

與4.1 ObservableField類似,可以改造一下ModelAdapter:為getter方法增加@Bindable注解,為setter方法增加notifyPropertyChanged(com.asha.demo.BR.name)通知。其中,BR是根據@Bindalbe自動生成的類,給getter方法增加@Bindable注解後,BR文件自動會生成一個整型的name。改造後代碼如下:

public class DetailActivity4 extends AppCompatActivity {

    ActivityDetail4Binding binding;
    ObservableInt visibility = new ObservableInt();

    public class ModelAdapter extends BaseObservable{
        private User user;

        public ModelAdapter(User user) {
            this.user = user;
        }
        ...
        @Bindable
        public String getName(){
            return user != null ? user.getName() : null;
        }

        public void setName(String name){
            if (user != null) user.setName(name);
            notifyPropertyChanged(com.asha.demo.BR.name);
        }
        ...
    }
    ...
}

隨後,在DetailActivity4.java中調用測試代碼,執行完會在1秒後改變adapter上的name值,並且同步到View上,測試代碼如下:

binding.detailActionButton.postDelayed(new Runnable() {
    @Override
    public void run() {
        adapter.setName("haha");
    }
},1000);

具體原理與4.1類似,不再贅述。

5 layout.xml中View屬性的setter

在下述示例中,detail_name這個TextView想把adapter.name賦值給自身的text屬性,就需要調用textView.setText(String)方法,這個方法就是View屬性的setter方法。

5.1 @BindingAdapter

上述的setter方法,Data Binding庫幫我們實現了大部分默認方法,具體方法參見android.databinding.adapters包下的類,下圖為ViewBindingAdatper具體實現,
ViewBindingAdatper
其中setter方法都為static方法,第一個參數都為自身的實例,後面為xml中傳入的參數,只要加入@BindingAdapter注解,編譯器就會全局搜索保存在一個temp文件中,並在生成類似ActivityDetail4Binding過程中去查找所需的setter方法的。如果需要自定義,只需要在任意app代碼中定義@BindingAdapter即可,例如:

public class DetailActivity4 extends AppCompatActivity {
    @BindingAdapter("android:alpha")
    public static void globalSetAlpha(View view, float alpha) {
        view.setAlpha(alpha);
    }
}

5.2 DataBindingComponent

很多情況下只是某個Binding文件(例如ActivityDetail4Binding)需要自定義setter方法,這個時候就需要使用DataBindingComponent,
* 首先,定義一個MyComponent,

public class MyComponent implements android.databinding.DataBindingComponent {
    @BindingAdapter("android:alpha")
    public void setAlpha(View view, float alpha) {
        view.setAlpha(0.5f);
    }

    @Override
    public MyComponent getMyComponent() {
        return new MyComponent();
    }
}
接著,在生成Binding對象時傳入這個DataBindingComponent實例,代碼如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = DataBindingUtil.setContentView(this,R.layout.activity_detail4,new MyComponent());
    ...
}

完成後,這個ActivityDetail4Binding范圍內的所有android:alpha="@{foo}"的方式賦值alpha的setter函數都會使用MyComponent#setAlpha

5.3 @BindingConversion

有時候會遇到類型不匹配的問題,比如R.color.white是int,但是通過Data Binding賦值給android:background屬性後,需要把int轉換為ColorDrawable,實現方式如下:
* 1.定義一個靜態函數,放在項目任意類中,

@BindingConversion
public static Drawable convertColorToDrawable(int drawable) {
    return new ColorDrawable(drawable);
}
2.在layout.xml中使用Data Binding,如:

對應在ActivityDetail4Binding.java中生成的代碼如下所示,其中AvatarAdapterObjectn1為int類型:

ViewBindingAdapter.setBackground(this.mboundView1, DetailActivity4.convertColorToDrawable(AvatarAdapterObjectn1));

5.4 @BindingMethod

例如layout.xml中android:onClick屬性,在Binding中真正使用setter時,就對應到了setOnClickListener方法,

@BindingMethod(type = View.class, attribute = "android:onClick", method = "setOnClickListener"),

6 Data Binding利用編譯器在背後做的那些事兒

Data Binding相關的jar包由四部分組成,
* 1.baseLibrary-2.1.0-rc1.jar
作為運行時類庫被打進APK中;

2.DataBinderPlugin(gradle plugin)
在編譯期使用,利用gradle-api(之前叫transform-api,1.5生,2.0改名)處理xml文件,生成DataBindingInfo.java;

3.compiler-2.1.0-rc1.jar
在編譯器使用,入口類繼承自AbstractProcessor,用於處理注解,並生成Binding類,DataBindingCompoent.java,DataBinderMapper.java類;

4.compilerCommon-2.1.0-rc1.jar
DataBinderPlugincompiler-2.1.0-rc1.jar所依賴

為了提高運行時的效率,Data Binding在背後做了非常多的工作,下圖是我整理的編譯流程,如圖所示:

Data Binding編譯流程

6.1 相關對象介紹

白色部分為輸入,包括
1.res/layout;
2.源代碼中的注解;

黃色部分為編譯器處理類,包括
1.aapt編譯時處理,入口類名為MakeCopy.java;
2.gradle-api處理,入口類名為DataBinderPlugin.java;
3.AbstractProcessor處理,入口類名為ProcessDataBinding.java

藍色部分為中間產物,包括
1.data-binding-info文件夾,包含了layout的基本信息,導入的變量,View標簽中的表達式,標簽的位置索引等等,如下所示為data-binding-info/activity_detail3-layout.xml




  
    
  

  
    
  
  

    
      
      
    

    
      
        
          
          false
          
        

        
          
          false
          
        
      
      
    

    ....
  

2.setter_store.bin,包含所有setter相關信息;
3.layoutinfo.bin,包含所有layout相關信息;
4.br.bin,包含所有BR相關信息;
以上bin文件都以Serializable方式序列化到磁盤上,需要的時候進行反序列化操作;

綠色部分為最終產物,包括
1.data-binding-layout-out(最終輸出到res/layout),即去掉根節點,去掉節點,與不使用Data Binding時的layout相一致,例如data-binding-layout-out/activity_detail2.xml:


    
    
    ...

2.DataBindingInfo.class,一個看似空的類,但在SOURCE階段包含了一個@BindingBuildInfo注解,包含了基本DataBinding的基本信息,代碼如下:

// DataBindingInfo.class
public class DataBindingInfo {
    public DataBindingInfo() {
    }
}
// @BindingBuildInfo
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface BindingBuildInfo {
    String buildId();
    String modulePackage();
    String sdkRoot();
    int minSdk();
    String layoutInfoDir();
    String exportClassListTo();
    boolean isLibrary();
    boolean enableDebugLogs() default false;
    boolean printEncodedError() default false;
}

3.DataBindingComponent.class,會根據自定義的DataBindingComponent自動生成對應實例化方法,例如:

public interface DataBindingComponent {
    MyComponent getMyComponent();
}

4.ViewDataBinding.class的子類(ActivityDetail2Binding.class等)
5.BR.class,Bindable屬性索引表,例如:

public class BR {
    public static final int _all = 0;
    public static final int adapter = 1;
    public static final int name = 2;
    public static final int visibility = 3;

    public BR() {
    }
}

6.DataBindingMapper.class,Mapper,用於尋找某個layout.xml對應的ViewDataBinding類,例如:

class DataBinderMapper {
    static final int TARGET_MIN_SDK = 16;

    public DataBinderMapper() {
    }

    public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view, int layoutId) {
        switch(layoutId) {
        case 2130968602:
            return ActivityDetail2Binding.bind(view, bindingComponent);
        case 2130968603:
            return ActivityDetail3Binding.bind(view, bindingComponent);
       ....
        default:
            return null;
        }
    }

    ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View[] views, int layoutId) {
        return null;
    }

    int getLayoutId(String tag) {
        if(tag == null) {
            return 0;
        } else {
            int code = tag.hashCode();
            switch(code) {
            case -600937657:
                if(tag.equals("layout/activity_detail2_0")) {
                    return 2130968602;
                }
                break;
            case -600936696:
                if(tag.equals("layout/activity_detail3_0")) {
                    return 2130968603;
                }
                break;
            ....

            return 0;
        }
    }

    String convertBrIdToString(int id) {
        return id >= 0 && id < DataBinderMapper.InnerBrLookup.sKeys.length?DataBinderMapper.InnerBrLookup.sKeys[id]:null;
    }

    private static class InnerBrLookup {
        static String[] sKeys = new String[]{"_all", "adapter", "name", "visibility"};

        private InnerBrLookup() {
        }
    }
}

6.2 相關編譯流程

STEP1 資源處理
aapt或者gradle執行時,都會觸發資源處理,在資源處理過程中,DataBinding都會掃描一遍現有的資源,生成不包含data-binding-layout-out以及DataBinding所需要的data-binding-info; STEP2 DataBindingInfo.class生成
在完成資源處理後,aapt或者gradle-api都會去執行DataBindingInfo.class生成操作,把相關的信息寫入DataBindingInfo.class@BindingBuildInfo注解中; STEP3 監聽到注解變化
生成@BindingBuildInfo注解,或者code中發現有新的注解寫入,AbstractProcessor注解處理器就開始執行注解處理。DataBinding中有一個ProcessDataBinding.java類專門來處理DataBinding相關的注解; STEP4 ProcessDataBinding處理注解,生成bin
ProcessDataBinding中處理注解永遠會按順執行3步,ProcessMethodAdapterProcessExpressionsProcessBindable。每次執行都會從磁盤反序列化對應的bin文件,然後忘bin中寫入新的,完成後再序列化到磁盤; STEP5 生成最終產物
執行ProcessMethodAdapter生成DataBindingComponents.class;執行ProcessExpressions生成ViewDataBinding.class子類(ActivityDetail2Binding.class),並觸發DataBindingMapper.class更新;執行ProcessBindable生成BR.class,並觸發DataBindingMapper.class更新;

7 細節補充-View Tag的使用

第二章有講到View是如何注入的,其實需要分兩種情況:
* 1.如果這個View標簽屬性中只有id,沒有其他”@{表達式}”形式,則按照第2章提到的方式直接通過id查找;
* 2.如果這個View標簽屬性中有”@{表達式}”形式的值,則編譯器會自動給這個View加個android:tag=”binding_{N}”, 其中{N}按順序從0開始遞增,如android:tag=”binding_0”。當執行ViewDataBinding#mapBindings去注入View時,會找tag為binding_開頭的View,隨後執行View注入;

另外,如果View標簽原來就有android:tag值,則編譯器會先保存原有值信息,寫入android:tag=”binding_{N}”。當執行完view注入後,再把原來的值賦值給android:tag。注意如果原來的android:tag值為”binding_0”,那麼在View注入時將會發生錯亂。

在完成View注入後,ActivityDetail3Binding會執行this.setRootTag(root),代碼如下:

public class ActivityDetail3Binding extends ViewDataBinding {

    public ActivityDetail3Binding(DataBindingComponent bindingComponent, View root) {
        super(bindingComponent, root, 0);
        Object[] bindings = mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds);
        this.detailActionButton = (Button)bindings[4];
        this.detailActionButton.setTag((Object)null);
        ...
        this.setRootTag(root);
        this.invalidateAll();
    }
}

這與ListView中的ViewHoloder實現方式相似,所以如果把DataBinding運用到ListView的ViewHolder中,就不需要多生成一個ViewHolder,直接使用這個ViewDataBinding類即可,例如ListAdapter實現:

public static class ListAdapter extends BaseAdapter{
    ...

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ItemFooBinding binding;
        if (convertView == null){
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            binding = DataBindingUtil.inflate(inflater,R.layout.item_foo,parent,false);
        } else {
            binding = DataBindingUtil.getBinding(convertView);
        }
        if (binding == null) return null;

        bind(binding,position);
        return binding.getRoot();
    }

    private void bind(ItemFooBinding binding, int position) {
        binding.title.setText("position:" + position);

        Context context = binding.avatar.getContext();
        int colorId = position % 2 == 0  ? R.color.colorAccent : R.color.colorPrimary;
        binding.avatar.setBackgroundColor(ContextCompat.getColor(context,colorId));
    }
}

8 總結

DataBinding 庫非常小
目前Android Data Binding在運行類庫只有632個方法數,算上每個layout.xml自動生成的ViewDataBinding子類(demo中每個類不超過20個方法數),方法數總和也非常有限。
Data Binding方法數 DataBinding 運行時沒有多余性能損耗
DataBinding所有的View注入、View賦值、Binding都是編譯器自動生成的代碼,這些重復的體力勞動本身就需要去做,只是交給了編譯器來完成,所以運行時沒有多余的性能損耗。 DataBinding 可以減少錯誤率
既然View注入、View賦值、Binding都是編譯器自動完成的,只要使用正確,100%無低級錯誤保證,可以提高代碼質量,讓開發者心情愉悅。 DataBinding 對編譯時長的影響
還沒實際運用到生產環境,肯定有所延長,具體量級還未知。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved