android数据绑定框架介绍

简介: 背景 数据绑定框架有很多,其实我就看过谷歌官方的数据绑定框架,官方的框架用起来的时候,觉得不是很顺手,侵入性还比较强。而且也一直纠结彷徨,从心底里质疑数据绑定框架的价值,到底给我们开发带来了什么,实用吗,可维护吗? 某一天的早晨突然灵光一现,决定自己去试试开发一个自己喜欢的数据绑定框架,经过

github地址

地址

背景

数据绑定框架有很多,其实我就看过谷歌官方的数据绑定框架,官方的框架用起来的时候,觉得不是很顺手,侵入性还比较强。而且也一直纠结彷徨,从心底里质疑数据绑定框架的价值,到底给我们开发带来了什么,实用吗,可维护吗?

某一天的早晨突然灵光一现,决定自己去试试开发一个自己喜欢的数据绑定框架,经过没日没夜的艰苦风斗(当然是开玩笑的),中间反复修改设计,最后尘埃落定。虽然这个框架已经出世,也在生产环境试运行了一些页面,但是我对于数据绑定框架的价值,始终抱有质疑的态度,所以今天我将其取名sword,希望他像一把双刃剑,能够有一个面给开发者带来一些小小的便利。

价值

数据绑定的价值,我的理解就是一句话:就是通过操作一个数据对象,达到修改视图的目的,或者反过来,通过操作视图对象,达到修改数据对象的目的。

Screen_Shot_2016_01_11_at_9_37_37_PM

提示:在大多数的情况下,通常都是数据单向操作视图的,极少的情况下需要数据和视图双向绑定,互相操作。

Screen_Shot_2016_01_11_at_9_38_54_PM

模型分析

textView.setText(CharSequence data);
data = textView.getText();

第一行代码代表值到视图的映射,第二行代码代表视图到值的映射。

值到视图的绑定

Screen_Shot_2016_01_12_at_9_20_46_AM

视图到值的绑定

Screen_Shot_2016_01_12_at_10_46_50_AM

sword的使用方法

sword的用法分为单向绑定(常用的),双向绑定2种。

单向绑定

新建data

class Data implements BindImpl {
    @Bind(viewId = R.id.end_title,setMethod = B.setText)
    public String end_title = "1234";
    public String end_desc = "aaa";

    @Bind(viewName = "end_title", setMethod = B.setTextColor)
    public int endTitleColor=getResources().getColor(R.color.colorAccent);
    @Bind(viewName = "end_title", setMethod = B.setOnClickListener, getMethod = B.getMethodNull)
    public View.OnClickListener end_titleL = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                end_title = "88888";
                end_desc = "+++++";
                endTitleColor = getResources().getColor(R.color.colorPrimary);
                url = "~~~~~";
                bindH.dataChange();
            }
        };

}

值的写法有3种

@Bind(viewId = R.id.end_title,setMethod = B.setText)
public String end_title = "1234";
@Bind(viewName = "end_title",setMethod = B.setText)
public String end_title = "1234";
@Bind(setMethod = B.setText)
public String end_title = "1234";

如果要映射的视图是一个textview,setMethod的默认值就是“setText”那么还有第四种写法.

public String end_title = "1234";

我来解释一下,上面3种用法,直接上图。

Screen_Shot_2016_01_12_at_11_10_19_AM

天然的2个缺陷

值对应的缺陷

textView.setText(CharSequence data);

所谓绑定,就是一一对应,view的方法只能接受一个参数。抽离出关键字就是:view,一个方法参数。但是有时候没办法处理特殊情况,比如我们使用volly框架的NetworkImageView,用法是这样的

 NetworkImageView.setImageUrl(String url, ImageLoader imageLoader)

解决缺陷办法

class Data implements BindImpl {
...
        @Bind(viewName = B.viewNameSelf, setMethod = "setImageUrl")
        private String url;
        
        void setImageUrl(String url) {
            networkImageView.setImageUrl(url, imageLoader);
        }
        ....
        }

指定到data自身viewName = B.viewNameSelf,指定到自身的方法setImageUrl(名字可以任意取),在方法内部构造特殊情况。

伪值绑定的缺陷

textView.setText(int textId);

上面代码是找不到视图到值的对应,即不存在:

textId = textView.getText()

这样的对应,我将其称之为伪值对应,即只存在数据到视图的对应。
所以这样的类型我们要告知没有返回值(单向绑定的情况下不需要告知,只有双向绑定的时候才需要这么做,即添加getMethod = B.getMethodNull),代码如下:

@(Bind viewName="text",getMethod = B.getMethodNull)
int textId

完整的用法:

使用的时候只需要2句代码即可:

 data = new Data();
 swordBind.bindOneWay(this, data);

data变化时通知view变化:

    end_title = "88888";
    end_desc = "+++++";
    endTitleColor = getResources().getColor(R.color.colorPrimary);
    url = "~~~~~";
    swordBind.dataChange();

完整代码:

package com.taobao.pandora.hello;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import com.taobao.sword.BindImpl;
import com.taobao.sword.SwordBind;
import com.taobao.sword.meta.B;
import com.taobao.sword.meta.Bind;

/**
 * Created by shanksYao on 1/5/16.
 */
public class BindActivity extends Activity {
    Data data;
    SwordBind swordBind = new SwordBind();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.live_include_deal_status);
        data = new Data();
        swordBind.bindOneWay(this, data);

    }

    /**
     * data就是用来操作View   任何跟View无关的属性 请不要放进来,以免造成设计混乱
     */
    class Data implements BindImpl {

        @Bind(viewName = "end_title")
        public String end_title = "1234";


        public String end_desc = "aaa";

        @Bind(viewName = "end_title", setMethod = B.setTextColor)
        public int endTitleColor = getResources().getColor(R.color.colorAccent);

        @Bind(viewName = "end_title", setMethod = B.setOnClickListener, getMethod = B.getMethodNull)
        public View.OnClickListener end_titleL = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                end_title = "88888";
                end_desc = "+++++";
                endTitleColor = getResources().getColor(R.color.colorPrimary);
                url = "~~~~~";
                swordBind.dataChange();
            }
        };

        @Bind(viewName = "end_desc", setMethod = B.setOnClickListener)
        public View.OnClickListener end_descL = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("===", end_desc);
                end_title = "+++++";
                end_desc = "88888";
                endTitleColor = getResources().getColor(R.color.colorAccent);
                url = "++++++++";
                swordBind.dataChange();
                Log.e("===", end_desc);
            }
        };

        @Bind(viewName = B.viewNameSelf, setMethod = "setImageUrl")
        private String url;

        void setImageUrl(String url) {
            Log.e("self", "--------" + url);
        }


    }

}

双向绑定

双向绑定和单向绑定的使用类似,只不过双向绑定,需要建立一个view的java类,如下:

   class ViewHolder implements BindImpl {
        private View price_ll;
        private TextView luochui;
        private View price_num;
        private View other_win_desc;
        private View end_rl;
        private ImageView end_img;
        private TextView end_title;
        private TextView end_desc;
        private View to_my_order;
    }

使用时viewHolder的属性名就是xml里面id的名字,也就是说 private TextView luochui;对应

   <TextView
            android:id="@+id/luochui"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="落槌价:¥" />

双向绑定的用法:

        SwordBind.renderById(holder,this);
        data = new Data();
        swordBind.bind(holder,data);

view变化时,通知data变化:

        holder.luochui.setText("123");
        holder.end_desc.setText("hahahah");
        ...
        swordBind.viewChange();

优化特性:

1.getMethod不指明的情况下,是根据setMethod来推演的,推演出getMethod或者isMethod.
2.内部有一个对应常量关系,罗列了几乎所有用到的view的setMethod方法和对应的getMethod方法。
3.数据更新的时候,是变化的数据才会应用更新,内部采用差量更新的策略。

法则

  1. 尽量使用真值绑定(真值指的是具有双向绑定的值,上面提到的伪值绑定有缺陷)
  2. 单向绑定受外力可能会有影响(外力指的是除了data作用外的其他调用方式)
  3. 单向绑定很容易转化为双向绑定
  4. 尽量不要使用外力影响view的展现

请务必记住这张图

Screen_Shot_2016_01_12_at_4_48_13_PM

最新功能

  1. 2016.14 ,对 minifyenable =true 支持。## 标题 ##

Sword 框架新增实验特性

标签(空格分隔): Android


背景

这段时间一直在想能不能去掉swordBind.dataChange();或者swordBind.viewChange();代码的调用.即用户只需要关心操作数据,或者操作视图,不需要关心操作完数据后,通知sword框架数据有变化。这段时间有一些想法,虽然不是很完美,但是确实做到了去掉swordBind.dataChange();swordBind.viewChange();代码的目的。

直接代码对比

原来的代码是这样写的

        end_title = "+++++";
        end_desc = "88888";
        url = "++++++++";
        swordBind.dataChange();

现在成了这样:

        end_title = "+++++";
        end_desc = "88888";
        url = "++++++++";

少了一行swordBind.dataChange();,只不过我把这行代码隐藏起来放到了另外的地方。

如何做到

Android的UI是运行在一个loop线程上的,每一次loop运行完,android.os.Looper.loop()都会调用代码

 if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

利用logging的回调,我们可以注入一个android.util.Printer对象,每次UI的线程执行完,我们都会得到一次回调。在这个回调里面做我们想做的事情,即swordBind.dataChange();或者swordBind.viewChange();.

限定模型

我不敢保证我上述的做法在任何情况下都是OK的,但是每一次引起数据变化,肯定是存在交互,交互最后都会回溯到UI线程上面,在UI线程的底部完成交互。即使是网络调用,AsycTask依赖会回调到UI线程上onPostExecute.
根据这一普适的特性,我可以构造一个属于我的模型,在这个模型里面我可以保证这一个实验特性的正确性。
Screen_Shot_2016_01_18_at_3_11_36_PM

收窄模型,即只要遵循data的值操作都是在UI线程上进行,(视图操作就不用说了,肯定在UI线程操作)

Screen_Shot_2016_01_19_at_9_27_03_AM

总结: 只要data的值操作都是在UI线程上进行,那么这个模型一定有效。这个实验特性也是一样有效

核心代码

  /////check value change
    private boolean useExperiment=false;
    private static List<SwordBind> swordBindList = new ArrayList<>();
    private static boolean isInitSmart=false;

    public void setUseExperiment(boolean useExperiment) {
        this.useExperiment = useExperiment;
    }

    private void initSmartCheck(final Context context) {
        if (!isInitSmart) {
            context.getMainLooper().setMessageLogging(new Printer() {
                @Override
                public void println(String x) {
                    if(x.startsWith("<<<<< Finished to"))
                    for (SwordBind swordBind : swordBindList) {
                        swordBind.dataChange();
                        swordBind.viewChange();
                    }
                }
            });
            isInitSmart = true;
        }
    }

    public void onPause(){
        if(useExperiment)
        swordBindList.remove(this);
    }
    public void onResume(){
        if(!useExperiment)
            return;
        if(parent!=null)
            initSmartCheck(parent.getContext());
        else
            initSmartCheck(activity);
        swordBindList.add(this);
    }

效率

为了平衡最少代码调用和代码执行效率2个方面,我苦苦挣扎了好几天,最后还是效率为王,即sword所在的activity或者fragment当前是否在前台,需要主动告诉我。如果在adaptor里一定要用这个实验特性,需要构造2个回调方法,让activity或者fragment回调。

用法

开启实验特性

 swordBind.setUseExperiment(true);

开启完毕之后,调用2个核心代码即可:

    @Override
    protected void onResume() {
        super.onResume();
        swordBind.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        swordBind.onPause();
    }

完整代码:

package com.taobao.pandora.shanks;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.taobao.pandora.sword.BindImpl;
import com.taobao.pandora.sword.SwordBind;
import com.taobao.pandora.sword.meta.B;
import com.taobao.pandora.sword.meta.Bind;
/**
 * Created by shanksYao on 1/5/16.
 */
public class BindActivity extends Activity {
    Data data;
    SwordBind swordBind = new SwordBind();

     ViewHolder holder = new ViewHolder();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.live_include_deal_status);
        swordBind.renderById(holder,this);
        data = new Data();
        swordBind.bind(holder,data);
        swordBind.setUseExperiment(true);
    }

    @Override
    protected void onResume() {
        super.onResume();
        swordBind.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        swordBind.onPause();
    }

    class ViewHolder implements BindImpl {
        private View price_ll;
        private TextView luochui;
        private View price_num;
        private View other_win_desc;
        private View end_rl;
        private ImageView end_img;
        private TextView end_title;
        private TextView end_desc;
        private View to_my_order;
    }


    /**
     * data就是用来操作View   任何跟View无关的属性 请不要放进来,以免造成设计混乱
     */
    class Data implements BindImpl {

        @Bind(viewName = "end_title",setMethod = B.setText,getMethod = "getText")
        public CharSequence end_title = "1234";


        public String end_desc = "aaa";

        @Bind(viewName = "end_title", setMethod = B.setTextColor)
        public int endTitleColor = getResources().getColor(R.color.colorAccent);

        @Bind(viewName = "end_title", setMethod = B.setOnClickListener, getMethod = B.getMethodNull)
        public View.OnClickListener end_titleL = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               /* end_title = "88888";
                end_desc = "+++++";
                endTitleColor = getResources().getColor(R.color.colorPrimary);
                url = "~~~~~";*/
                holder.end_title.setText("123");
                holder.end_desc.setText("hahahah");
            //    swordBind.dataChange();
            }
        };

        @Bind(viewName = "end_desc", setMethod = B.setOnClickListener)
        public View.OnClickListener end_descL = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("===", end_desc);
                end_title = "+++++";
                end_desc = "88888";
                endTitleColor = getResources().getColor(R.color.colorAccent);
                url = "++++++++";
             //   swordBind.dataChange();
                Log.e("===", end_desc);
            }
        };

        //
        void setEndTitle(int id) {
            end_title = getResources().getString(id);
            //swordBind.dataChange();
        }

        void setUrl(ViewHolder holder, String url) {
            /////设置方法处理
            holder.end_img.setImageDrawable(null);
        }

        @Bind(viewName = B.viewNameSelf, setMethod = "setImageUrl")
        private String url;

        void setImageUrl(String url) {
            Log.e("self", "--------" + url);
        }


    }
    ////Ui数据绑定


}
相关文章
|
3月前
|
安全 API Android开发
Android网络和数据交互: 解释Retrofit库的作用。
Android网络和数据交互: 解释Retrofit库的作用。
38 0
|
4月前
|
XML 物联网 API
Android Ble蓝牙App(五)数据操作
Android Ble蓝牙App(五)数据操作
|
4月前
|
数据库 Android开发 开发者
Android Studio入门之内容共享ContentProvider讲解以及实现共享数据实战(附源码 超详细必看)
Android Studio入门之内容共享ContentProvider讲解以及实现共享数据实战(附源码 超详细必看)
36 0
|
3天前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android&#39;s AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
3 0
|
3天前
|
网络协议 安全 API
Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
5 0
|
17天前
|
XML Java Android开发
Android每点击一次按钮就添加一条数据
Android每点击一次按钮就添加一条数据
21 1
|
1月前
|
存储 Android开发 C++
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
30 3
|
2月前
|
JavaScript Java 数据安全/隐私保护
安卓逆向 -- POST数据解密
安卓逆向 -- POST数据解密
25 2
|
3月前
|
编解码 测试技术 开发工具
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
|
3月前
|
数据采集 编解码 图形学
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
100 0