Android 从观察者模式到 DataBinding

前言

做过 iOS 的同学应该都了解过 KVO,是观察者模式在 Objective-C 中的应用。使用 KVO,能很方便的实现对对象属性的监听。虽然 iOS 提供了对对象属性的观察者模式机制,但想想很多 Android 同学们应该不会在意。这不是很容易么,我分分钟也能写一个:

public class User {
    String mName;
    Observable mObservable;

    public User(String name) {
        mName = name;
    }

    public String getName() {
        return mName;
    }

    public void setName(String name) {
        boolean isSame = TextUtils.equals(this.mName, name);
        this.mName = name;
        if (!isSame && mObservable != null) {
            mObservable.onNameChanged(name);
        }
    }

    public void setObservable(Observable observer) {
        this.mObservable = observer;
    }

    public interface Observable {
        void onNameChanged(String newName);
    }
}
User user = new User("我叫王尼玛");
user.setObservable(new User.Observable() {
    @Override
    public void onNameChanged(String newName) {
        Log.i("user newName = ", newName);
    }
});
user.setName("呵呵,这你都信");

但是冷静下来想想,如果一个大的工程中有很多这种需求呢?是不是 User1User2,...... 都要写这些机械的代码了?那回过头想想,如果不想自己写这些代码的话,那么我们大 Android 真的就没有这种机制么?想想不服气,于是翻了翻资料,果然我们还是有的: ObservableField

ObservableField

1. 使用方式

使用还是很简单的,我们直接看代码吧

ObservableField<String> name = new ObservableField<>();
name.addOnPropertyChangedCallback(
    new android.databinding.Observable.OnPropertyChangedCallback() {
        @Override
        public void onPropertyChanged(android.databinding.Observable observable, int i) {
            Log.d("name = ", "name = " + observable.toString() + "; i= " + i);
        }
    });
name.set("我叫张三");

这下舒服多了,不用自己实现 Observable 接口和 setObservable 方法,同时对于其他类型的变量,如 int,float 或者 自定义的类型,也不用重新实现了,直接定义 ObservableField<Integer>ObservableField<Float> 就行了,好开心_

2. 原理实现

还是直接看源码吧,反正代码量也不多 O(∩_∩)O~

public class ObservableField<T> extends BaseObservable implements Serializable {
    static final long serialVersionUID = 1L;
    private T mValue;
    
    ......

    public T get() {
        return mValue;
    }

    public void set(T value) {
        if (value != mValue) {
            mValue = value;
            notifyChange();
        }
    }
}
public class BaseObservable implements Observable {
    private transient PropertyChangeRegistry mCallbacks;

    ......
    
    @Override
    public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
        ......
        
        mCallbacks.add(callback);
    }

    @Override
    public synchronized void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
        if (mCallbacks != null) {
            mCallbacks.remove(callback);
        }
    }

    public synchronized void notifyChange() {
        if (mCallbacks != null) {
            mCallbacks.notifyCallbacks(this, 0, null);
        }
    }

    ......
}

注:mCallbacks.notifyCallbacks(this, 0, null); 方法中,0 表示的是 fieldID,在 dataBinding 中表示数据资源 id。因此这里并没有关联视图资源,所以这里设置为 0

可以看到,ObservableField 是一个泛型,所以支持多种类型的观察者模式。BaseObservable 是其父类,实现了观察者模式的核心代码,可以看到 addOnPropertyChangedCallbackaddOnPropertyChangedCallback 2个添加和移除监听的方法,真正的监听者都被保存到 PropertyChangeRegistry.mCallbacks (类型是 List) 里面。

当调用 ObservableFieldset 方法时,会执行 ObservableField.notifiyCallbacks 方法,如下:

public class BaseObservable implements Observable {
    public synchronized void notifyChange() {
        if (mCallbacks != null) {
            mCallbacks.notifyCallbacks(this, 0, null);
        }
    }
    
    ......
}

最终会执行到 callback.onPropertyChanged(sender, arg);,如下代码所示:

public class CallbackRegistry<C, T, A> implements Cloneable {
    private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
            final int endIndex, final long bits) {
        ......
        for (int i = startIndex; i < endIndex; i++) {
            ......
            mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
            .....
        }
    }

    ......
}
private static final CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback,
        Observable, Void> NOTIFIER_CALLBACK = 
        new CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void>() {
        @Override
        public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,
                int arg, Void notUsed) {
            callback.onPropertyChanged(sender, arg);
        }
    };

这里,我们可以看到,通过遍历的方式,去执行 callback 方法,将前面通过 BaseObservable.addOnPropertyChangedCallback 添加的全部观察者都响应了一边

3. 小结

到这里为止,虽然把 ObservableField 的观察者模式给讲清楚了,但还是感觉有些失望,内容很少很简单。不过还没完呢,Google 大神们就是基于此,玩出了 DataBinding

DataBinding

使用步骤

  1. IDE 配置

    • Android SDK API 版本 7 以上

    • 使用 Gradle 1.5.0-alpha1 及以上

    • 使用 Android Studio 1.3 及以上

  2. 配置开启 dataBinding

    在主工程的 build.gradle 中,添加代码:

    android {
        ......
        
        dataBinding {
            enabled = true
        }
    }
    
  3. 定义数据层 Model

    定义的 ObservableUser 类,继承自 BaseObservable (同前面的 ObservableField)。在 set 接口里添加 notifyPropertyChanged 调用,通知视图更新

    public class ObservableUser extends BaseObservable {
        private String firstName;
        private String lastName;
    
        public ObservableUser(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
    
        @Bindable
        public String getFirstName() {
            return firstName;
        }
    
        @Bindable
        public String getLastName() {
            return lastName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
            notifyPropertyChanged(com.netease.mvvmsample.BR.firstName);
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
            notifyPropertyChanged(com.netease.mvvmsample.BR.lastName);
        }
    }
    
  4. 定义事件响应 Handler

    public class Handler {
        private ObservableUser mObservableUser;
    
        public Handler(ObservableUser user) {
            mObservableUser = user;
        }
    
        public void onClickButton(View view) {
            mObservableUser.setLastName("呵呵呵,我变了 " + mCount++ + " 次");
        }
    }
    
  5. 定义布局代码

    在 data 标签下面,定义 model 数据和事件响应 handler。使用 @{} 分别将 Button 的文本信息和 user.lastNameButton 的点击响应和 handler.onClickButton 绑定在一起

    <?xml version="1.0" encoding="utf-8"?>
    <layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
        <data>
            <variable
                name="user"
                type="com.netease.mvvmsample.ObservableUser">
            </variable>
            <variable
                name="handler"
                type="com.netease.mvvmsample.Handler">
            </variable>
        </data>
        <LinearLayout
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.lastName}"
                android:onClick="@{handler.onClickButton}"/>
        </LinearLayout  
    </layout>
    
  6. 设置布局和绑定数据和事件

    在定义了上面的布局 xml 文件之后,Android Studio 会自动生成 ViewModel 类。假设文件名是 activity_main.xml,那么程序编译后,会生成 ActivityMainBinding 类。代替原来的 setContentView(R.layout.activity_main); 方法,使用 ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); 来设置布局资源,并返回 binding 对象。新建数据和事件处理对象,并设置给 binding 对象

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
            ObservableUser user = new ObservableUser("Zhang", "San");
            Handler handler = new Handler(user);
            binding.setUser(user);
            binding.setHandler(handler);
        }
    }
    
  7. 效果展示

    image

注:这里仅仅讲了 DataBinding 的基本使用,并没有打算深入讲述 DataBinding 的进一步使用,如果有同学想要了解高级使用的话,如类方法,类型别名等,可以查看官方文档 Data Binding Library

数据和事件关联原理

知道了 DataBinding 如何使用之后,就很好奇,这个DataBinding 机制是如何实现的了。但是在 Android Studio 里面怎么也没有找到 ActivityMainBinding 的代码,在 ObservableUsergetLastName 方法中设置断点,查看调用栈。我们可以发现,其实是 ActivityMainBinding.executeBindings 方法调用了 model 的 get 方法。

image

然而,悲剧的是,我想点击查看 executeBindings 调用情况,Android Studio 是跳到了 activity_main.xml 里去了。

image

好吧,还是想看源码,那就用 dex2jarjd-gui 工具查看了 class.dex 文件,果然看到了源码

image

当然,其他 Android Studio 上没能看到的代码,如 DataBinderMapper 等的代码也都能找到了。

直接查看 DataBindingUtil.setContentView 里面的源码吧:

  1. MainActivity.onCreate

    ActivityMainBinding binding = 
            DataBindingUtil.setContentView(this, R.layout.activity_main);
    
  2. 省略部分代码,直接来到 DataBindingUtil.bind

    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent,
            View root, int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    }
    

    其中,bindingComponentnullrootlayoutId 都对应 activity_main.xml 中定义的 LinearLayout

  3. DataBinderMapper.getDataBinder

    public ViewDataBinding getDataBinder(DataBindingComponent paramDataBindingComponent,
        View paramView, int paramInt) {
        switch (paramInt) {
            default:
              return null;
            case 2130968601:
        }
        return ActivityMainBinding.bind(paramView, paramDataBindingComponent);
    }
    
  4. ActivityMainBinding.bind

    public static ActivityMainBinding bind(View paramView, DataBindingComponent paramDataBindingComponent) {
        if (!"layout/activity_main_0".equals(paramView.getTag()))
            throw new RuntimeException("view tag isn't correct on view:" + paramView.getTag());
        return new ActivityMainBinding(paramDataBindingComponent, paramView);
    }
    

    这里可以看到,返回的 ActivityMainBinding 对象是在这里被创建的了

  5. ActivityMainBinding 构造函数

    public ActivityMainBinding(DataBindingComponent paramDataBindingComponent, View paramView) {
        super(paramDataBindingComponent, paramView, 1);
        paramDataBindingComponent = mapBindings(paramDataBindingComponent, paramView, 2, sIncludes, sViewsWithIds);
        this.mboundView0 = ((LinearLayout)paramDataBindingComponent[0]);
        this.mboundView0.setTag(null);
        this.mboundView1 = ((Button)paramDataBindingComponent[1]);
        this.mboundView1.setTag(null);
        setRootTag(paramView);
        invalidateAll();
    }
    

    这里还有好多疑问,mboundView0 和 mboundView1 分别对应什么控件呢?setRootTag 和 invalidateAll 都是干啥的呢?

  6. ViewDataBinding.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;
    }
    
    private static void mapBindings(DataBindingComponent bindingComponent, View view,
            Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
            boolean isRoot) {
        ......
        if (view instanceof ViewGroup) {
            ......
            for (int i = 0; i < count; i++) {
                ......
                if (bindings[index] == null) {
                    bindings[index] = view;
                }
                ......
                {   
                    bindings[index] = DataBindingUtil.bind(bindingComponent, included,
                                        layoutId);
                }
                ......
                
                if (!isInclude) {
                    mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
                }
            }
        }
    }
    

    注:这里会递归的执行 mapBindings 将传入的 bindings 数据给填充好。
    binding 数组里面的数据,可能是 view 也可能是 ViewDataBinding
    在当期的示例程序中,bindings[0]LinearLayoutbindings[1]Button;所以,ActivityMainBinding.mboundView0 就是 layout 中定义的 LinearLayout;ActivityMainBinding.mboundView1 就是 layout 中定义的 Button。

  7. ViewDataBinding.setRootTag

    protected void setRootTag(View view) {
        ......
        view.setTag(R.id.dataBinding, this);
        ......
    }
    

    ActivityMainBinding 和布局文件中的 LinearLayout 关联起来了。

  8. ActivityMainBinding 构造函数

    public ActivityMainBinding(DataBindingComponent paramDataBindingComponent, View paramView) {
        ......
        invalidateAll();
    }
    
  9. 跳过部分代码,调用到 ActivityMainBinding.requestRebind

    protected void requestRebind() {
        ......
        if (SDK_INT >= 16) {
            mChoreographer.postFrameCallback(mFrameCallback);
        } else {
            mUIThreadHandler.post(mRebindRunnable);
        }
    }
    

    假设当前 SDK_INT == 23,直接查看 mFrameCallback 的定义。则在下一帧的时候,调用 mRebindRunnable.run();

    mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            mRebindRunnable.run();
        }
    };
    
  10. 最终执行 ActivityMainBinding.executeBindings 方法

    protected void executeBindings()
    {
        ......  
    
        Handler localHandler = this.mHandler;
        ObservableUser localObservableUser = this.mUser;
            
        ......
        while (true)
        {
            ......
            
            localObject1 = new OnClickListenerImpl();
            this.mAndroidViewViewOnCl = ((OnClickListenerImpl)localObject1);
            localObject1 = ((OnClickListenerImpl)localObject1).setValue(localHandler);
    
            localObject3 = localObject4;
            
            ......
            
            localObject3 = localObservableUser.getLastName();
            TextViewBindingAdapter.setText(this.mboundView1, (CharSequence)localObject3);
            
            this.mboundView1.setOnClickListener((View.OnClickListener)localObject1);
            return;
            ......
        }
    }
    

    注:这里 mUsermHandler 是 MainActivity.onCreate 中的设置的:

    binding.setUser(user);
    binding.setHandler(handler);
    

    这里清楚的看到调用 localObservableUser.getLastName 获取 model 中的数据,然后设置给 mboundView1 (Button)

    新建 OnClickListenerImpl 对象,处理 mboundView1 (Button)的点击事件,而最终也还是会调用到 Handler.onClickButton 方法上

    public static class OnClickListenerImpl implements View.OnClickListener {
        private Handler value;
    
        public void onClick(View paramView) {
            this.value.onClickButton(paramView);
        }
        
        ......
    }
    

数据变化驱动视图改变

查看下代码,set 函数中,需要添加一句 notifyPropertyChanged 方法。其实这里对 lastName 的监听者,就是 ViewDataBinding$WeakPropertyListener,而内部调用的还是 AcitivityMainBinding.handleFieldChange 方法,最终还是调用了 AcitivityMainBinding.requestRebind。这里就已经和前面分析的过程一样,也就是说最终视图发生改变生效,走的还是消息队列。

public class ObservableUser extends BaseObservable {
    
    ......
        
    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(com.netease.mvvmsample.BR.lastName);
    }
}
private static class WeakPropertyListener
        extends Observable.OnPropertyChangedCallback
        implements ObservableReference<Observable> {
    final WeakListener<Observable> mListener;

    @Override
    public void onPropertyChanged(Observable sender, int propertyId) {
        ViewDataBinding binder = mListener.getBinder();
        ......
        binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
    }
}

小结

由上面的源码解析,已经知道几点

  1. 数据如何和 view 关联起来的

  2. 事件处理如何和 view 关联起来的

  3. 数据和事件处理的关联发生是扔给消息队列处理的

    • SDK_INT >= 16: mChoreographer.postFrameCallback(mFrameCallback);
    • SDK_INT < 16: mUIThreadHandler.post(mRebindRunnable);
  4. 当数据改变,通知视图改变时,走的是消息队列。因此一次数据改动,并界面可能不会立马生效

  5. 数据和视图的绑定,其实是单向的,即数据发生改变通知了视图,而视图发生并不能自动通知数据

  6. 虽然没看到 Android Studio 是如何实现代码生成,但相关的工具大家可以看下 javapoet

总结

有了 DataBinding,后面就有人玩出了 MVVM 模式了。当然啦,这里主要是抱着学习的态度在阐述 Android 里面的 DataBinding,并不是在推崇 DataBindingMVVM。这些概念有人推崇有人贬低,引用别人的一句话,希望大家对新知识都能做到:

我们需要保持的是一个拥抱变化的心,以及理性分析的态度。
在新技术的面前,不盲从,也不守旧,一切的决策都应该建立在认真分析的基础上,这样才能应对技术的变化
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容