Android-architecture之MVC、MVP、MVVM、Data-Binding
来源:互联网 发布:mac itunes铃声 编辑:程序博客网 时间:2024/04/30 03:21
- 传送门
- MVC
- 结构简介
- 实例分析
- 总结
- MVP
- 结构简介
- 为什么使用MVP模式
- 实例分析
- MVP与MVC的异同
- MVVM
- Data-Binding
- 前言
- 参考链接
传送门
Android Architecture(Is Activity God?)
MVC
结构简介
实例分析
Controller控制器式
public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener { private WeatherModel weatherModel; private Dialog loadingDialog; private EditText cityNOInput; private TextView city; private TextView cityNO; private TextView temp; private TextView wd; private TextView ws; private TextView sd; private TextView wse; private TextView time; private TextView njd; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); weatherModel = new WeatherModelImpl(); initView(); } /** * 初始化View */ private void initView() { cityNOInput = findView(R.id.et_city_no); city = findView(R.id.tv_city); cityNO = findView(R.id.tv_city_no); temp = findView(R.id.tv_temp); wd = findView(R.id.tv_WD); ws = findView(R.id.tv_WS); sd = findView(R.id.tv_SD); wse = findView(R.id.tv_WSE); time = findView(R.id.tv_time); njd = findView(R.id.tv_njd); findView(R.id.btn_go).setOnClickListener(this); loadingDialog = new ProgressDialog(this); loadingDialog.setTitle(加载天气中...); } /** * 显示结果 * * @param weather */ public void displayResult(Weather weather) { WeatherInfo weatherInfo = weather.getWeatherinfo(); city.setText(weatherInfo.getCity()); cityNO.setText(weatherInfo.getCityid()); temp.setText(weatherInfo.getTemp()); wd.setText(weatherInfo.getWD()); ws.setText(weatherInfo.getWS()); sd.setText(weatherInfo.getSD()); wse.setText(weatherInfo.getWSE()); time.setText(weatherInfo.getTime()); njd.setText(weatherInfo.getNjd()); } /** * 隐藏进度对话框 */ public void hideLoadingDialog() { loadingDialog.dismiss(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_go: loadingDialog.show(); weatherModel.getWeather(cityNOInput.getText().toString().trim(), this); break; } } @Override public void onSuccess(Weather weather) { hideLoadingDialog(); displayResult(weather); } @Override public void onError() { hideLoadingDialog(); Toast.makeText(this, 获取天气信息失败, Toast.LENGTH_SHORT).show(); } private T findView(int id) { return (T) findViewById(id); }}
Model模型
public interface WeatherModel { void getWeather(String cityNumber, OnWeatherListener listener);}
public class WeatherModelImpl implements WeatherModel { @Override public void getWeather(String cityNumber, final OnWeatherListener listener) { /*数据层操作*/ VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html, Weather.class, new Response.Listener() { @Override public void onResponse(Weather weather) { if (weather != null) { listener.onSuccess(weather); } else { listener.onError(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { listener.onError(); } }); }
总结
- 扩展性好、维护性、模块职责明确
- 耦合性低(解耦)、V和M非真正意义上的分离
什么时候适合使用MVC设计模式?
当一个小的项目且无需频繁修改需求就不用MVC框架来设计了,那样反而觉得代码过度设计,代码臃肿。一般在大的项目中,且业务逻辑处理复杂,页面显示比较多,需要模块化设计的项目使用MVC就有足够的优势了。
MVP
结构简介
为什么使用MVP模式
在Android开发中,Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户界面,并接受并处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿。当我们将其中复杂的逻辑处理移至另外的一个类(Presneter)中时,Activity其实就是MVP模式中View,它负责UI元素的初始化,建立UI元素与Presenter的关联(Listener之类),同时自己也会处理一些简单的逻辑(复杂的逻辑交由Presenter处理).
另外,回想一下你在开发Android应用时是如何对代码逻辑进行单元测试的?是否每次都要将应用部署到Android模拟器或真机上,然后通过模拟用户操作进行测试?然而由于Android平台的特性,每次部署都耗费了大量的时间,这直接导致开发效率的降低。而在MVP模式中,处理复杂逻辑的Presenter是通过interface与View(Activity)进行交互的,这说明了什么?说明我们可以通过自定义类实现这个interface来模拟Activity的行为对Presenter进行单元测试,省去了大量的部署及测试的时间。
实例分析
MVP模式
View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。其中Presenter中同时持有Viwe层以及Model层的Interface的引用,而View层持有Presenter层Interface的引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的某个接口,然后Presenter层会调用Model层请求数据,当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载完毕,最后Presenter层再调用View层的接口将加载后的数据展示给用户。这就是MVP模式的整个核心过程。
官方模式图
android-architecture官方传送门
案例
这里以暴风体育中的话题列表为例来进行介绍:
TopicModel
public interface TopicModel { /** * 加载话题列表首页数据 * * @param context * @param listener */ void loadTopicList(Context context, TopicModelImpl.OnLoadTopicListListener listener); /** * 从本地数据库中获取我关注的话题数据 * * @param context * @param listener * @return */ ArrayList<TopicItem> loadFollowTopic(Context context, TopicModelImpl.OnLoadTopicListListener listener); /** * 全部话题加载更多数据 * * @param context * @param paramMap * @param listener */ void loadMoreAllTopic(Context context, Map<String, String> paramMap, TopicModelImpl.OnLoadTopicListListener listener); /** * 更新我关注的话题的最新帖子数和帖子最近的更新时间 * * @param context * @param threadItem * @param listener */ void updateThreadItem(final Context context, ThreadItem threadItem, TopicModelImpl.OnLoadTopicListListener listener);}
TopicPresenter
public interface TopicPresenter { /** * 加载话题列表首页数据 * * @param context */ void loadTopicList(Context context); /** * 全部话题加载更多数据 * * @param context * @param paramMap */ void loadMoreAllTopic(Context context, Map<String, String> paramMap); /** * * @param context * @return */ ArrayList<TopicItem> loadFollowTopic(Context context);}
TopicView
public interface TopicView { void showProgress(); void addTopics(List<TopicItem> topicList); void addSwipeUpItem(SwipeUpItem item); void addLoadMoreTopics(List<TopicItem> topicList); void hideProgress(); void showLoadFailMsg(); //二次请求需要重新刷新界面 void notifyAdapter();}
TopicModelImpl
/** * DES: * Created by sushuai on 2016/4/13. */public class TopicModelImpl implements TopicModel { private static final String TAG = "TopicModelImpl"; /** * 加载话题列表首页数据 * * @param context * @param listener */ @Override public void loadTopicList(final Context context, final OnLoadTopicListListener listener) { AsyncHttpRequest.doASynGetRequest(context, UrlContainer.HOME_TOPIC, null, true, new AsyncHttpRequest.CallBack() { @Override public void fail(String ret) { listener.onFailure(Net.ErrorNo.NO_DATA); } @Override public void call(String data) { try { ArrayList<TopicItem> items = (ArrayList<TopicItem>) TopicListDataParseUtils.readJsonTopicLists(data, listener); //items.addAll(0, loadFollowTopic(context, listener)); if (items != null) { listener.onSuccess(items); } } catch (JSONException e) { e.printStackTrace(); listener.onFailure(Net.ErrorNo.ERROR_JSON); } } }); } /** * 从本地数据库中获取我关注的话题数据 * * @param context * @param listener * @return */ @Override public ArrayList<TopicItem> loadFollowTopic(Context context, final OnLoadTopicListListener listener) { ArrayList<TopicItem> items = new ArrayList<>(); ArrayList<ThreadItem> ThreadItems = (ArrayList<ThreadItem>) FollowTopicDao.getInstance(context).getLatest3Topics(); if (ThreadItems.size() <= 0) { return items; } for (int i = 0; i < ThreadItems.size(); i++) { ThreadItem threadItem = ThreadItems.get(i); updateThreadItem(context, threadItem, listener); } TopicItem meItem = new TopicItem(); meItem.setType(TopicAdapter.TYPE_TOPIC_TITLE_ME); items.add(meItem); for (int i = 0; i < ThreadItems.size(); i++) { TopicItem topicItem = new TopicItem(); topicItem.setType(TopicAdapter.TYPE_TOPIC_THREAD); topicItem.setOther(ThreadItems.get(i)); items.add(topicItem); } return items; } /** * 更新我关注的话题的最新帖子数和帖子最近的更新时间 * * @param context * @param threadItem * @param listener */ @Override public void updateThreadItem(final Context context, final ThreadItem threadItem, final OnLoadTopicListListener listener) { Map<String, String> map = new HashMap<>(); map.put(Net.Field.id, String.valueOf(threadItem.getId())); final int prePosts = threadItem.getCount(); AsyncHttpRequest.doASynGetRequest(context, UrlContainer.GET_TOPIC_POSTS, (HashMap<String, String>) map, true, new AsyncHttpRequest.CallBack() { @Override public void fail(String ret) { } @Override public void call(String data) { try { JSONObject jo = new JSONObject(data); int errno = DataParseUtils.getJsonInt(jo, Net.Field.errno); if (errno == Net.ErrorNo.SUCCESS) { JSONObject jsonObj = DataParseUtils.getJsonObj(jo, Net.Field.data); int count = DataParseUtils.getJsonInt(jsonObj, Net.Field.count); long latest_update_tm = DataParseUtils.getJsonLong(jsonObj, Net.Field.latest_update_tm); threadItem.setUpdateCount(count - prePosts); threadItem.setCount(count); threadItem.setUpdateTime(latest_update_tm); FollowTopicDao.getInstance(context).updatePostsById(threadItem.getId(), count); listener.onUpdateThreadItem(); } } catch (JSONException e) { e.printStackTrace(); } } }); } /** * 全部话题加载更多数据 * * @param context * @param paramMap * @param listener */ @Override public void loadMoreAllTopic(Context context, Map<String, String> paramMap, final OnLoadTopicListListener listener) { AsyncHttpRequest.doASynGetRequest(context, UrlContainer.TOPIC_LIST, (HashMap<String, String>) paramMap, true, new AsyncHttpRequest.CallBack() { @Override public void fail(String ret) { listener.onFailure(Net.ErrorNo.NO_DATA); } @Override public void call(String data) { try { ArrayList<TopicItem> items = (ArrayList<TopicItem>) TopicListDataParseUtils.readMoreAllTopic(data, listener); if (items != null) { listener.onLoadMoreAllTopics(items); } } catch (JSONException e) { e.printStackTrace(); listener.onFailure(Net.ErrorNo.ERROR_JSON); } } }); } public interface OnLoadTopicListListener { //加载话题列表首页数据成功 void onSuccess(List<TopicItem> list); //加载话题列表首页数据失败 void onFailure(int erroNo); //全部话题加载更多相关配置 void onLoadMoreSwipeUp(SwipeUpItem item); //回去加载更多数据 void onLoadMoreAllTopics(List<TopicItem> list); //更新我关注的话题的相关数据 void onUpdateThreadItem(); }}
TopicPresenterImpl
/** * DES: * Created by sushuai on 2016/4/13. */public class TopicPresenterImpl implements TopicPresenter, TopicModelImpl.OnLoadTopicListListener { private static final String TAG = "TopicPresenterImpl"; private TopicModel mTopicModel; private TopicView mTopicView; public TopicPresenterImpl(TopicView topicView) { this.mTopicModel = new TopicModelImpl(); this.mTopicView = topicView; } @Override public void loadTopicList(Context context) { mTopicModel.loadTopicList(context, this); } @Override public void loadMoreAllTopic(Context context, Map<String, String> paramMap) { mTopicModel.loadMoreAllTopic(context, paramMap, this); } @Override public ArrayList<TopicItem> loadFollowTopic(Context context) { return mTopicModel.loadFollowTopic(context,this); } @Override public void onSuccess(List<TopicItem> list) { mTopicView.hideProgress(); mTopicView.addTopics(list); } @Override public void onFailure(int erroNo) { mTopicView.hideProgress(); mTopicView.showLoadFailMsg(); } @Override public void onLoadMoreSwipeUp(SwipeUpItem item) { mTopicView.addSwipeUpItem(item); } @Override public void onLoadMoreAllTopics(List<TopicItem> list) { mTopicView.addLoadMoreTopics(list); } @Override public void onUpdateThreadItem() { mTopicView.notifyAdapter(); }}
TabTopicFragment
/** * 话题 * SuShuai * 2016/4/12 14:39 */public class TabTopicFragment extends BaseFragment implements TopicAdapter.AdapterCallback, TopicView, IHandlerMessage, XListView.IXListViewListener { // TODO: Rename parameter arguments, choose names that match // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private static final String TAG = "TabTopicFragment"; private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM2 = "param2"; // TODO: Rename and change types of parameters private String mParam1; private String mParam2; private XListView listView; private TopicAdapter topicAdapter; private ArrayList<TopicItem> topicList = new ArrayList<>(); private ArrayList<TopicItem> homeList = new ArrayList<>(); private ArrayList<TopicItem> followTopicList = new ArrayList<>(); private TopicPresenter mTopicPresenter; private CommonHandler<TabTopicFragment> handler; private SwipeUpItem swipeUpItem; private View rootView; private String after = ""; public TabTopicFragment() { // Required empty public constructor } /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment TabTopicFragment. */ // TODO: Rename and change types and number of parameters public static TabTopicFragment newInstance(String param1, String param2) { TabTopicFragment fragment = new TabTopicFragment(); Bundle args = new Bundle(); args.putString(ARG_PARAM1, param1); args.putString(ARG_PARAM2, param2); fragment.setArguments(args); return fragment; } @Override public void onAttach(Context context) { super.onAttach(context); LogHelper.e(TAG, "SuS--> onAttach: "); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mParam1 = getArguments().getString(ARG_PARAM1); mParam2 = getArguments().getString(ARG_PARAM2); } mTopicPresenter = new TopicPresenterImpl(this); LogHelper.e(TAG, "SuS--> onCreate: "); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { LogHelper.e(TAG, "whb--> onCreateView: "); if (rootView == null) { rootView = inflater.inflate(R.layout.fragment_tab_topic, container, false); initViews(rootView); } return rootView; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { LogHelper.e(TAG, "SuS--> onActivityCreated: "); super.onActivityCreated(savedInstanceState); initData(); } private void initData() { handler = new CommonHandler<TabTopicFragment>(this); topicAdapter = new TopicAdapter(getActivity(), this); listView.setAdapter(topicAdapter); if (NetUtils.isNetworkAvaliable(getActivity())) { showLoadingView(); mTopicPresenter.loadTopicList(getActivity()); followTopicList = mTopicPresenter.loadFollowTopic(getActivity()); } else { if (topicList.size() > 0) { topicAdapter.update(topicList); ToastUtils.toast(getActivity(), "没有网络"); listView.restListView(); return; } showNetErroView(R.string.tips_net_error); } } private void initViews(View v) { setImmerseLayout(v.findViewById(R.id.common_back)); setTitleBar(v, R.string.tab_topic); setLeftGone(v); listView = (XListView) v.findViewById(R.id.lv_topic); listView.setPullRefreshEnable(true); listView.setPullLoadEnable(true); listView.setAutoLoadEnable(true); listView.setXListViewListener(this); } @Override public void onAdapterCallback(int eventId, Object obj) { if (isAdded()) { BaofengStatistics.onUmengEvent(getActivity(), BfCountConst.TopicConst.BBS_MOREFOLLOW_CLICK); LogHelper.v("umeng", "bbs_morefollow_click 计数一次"); } ActivityUtil.startActivity(getActivity(), MoreFollowTopicActivity.class, null, false); } @Override public void showProgress() { } @Override public void addTopics(List<TopicItem> topicList) { handler.obtainMessage(HandlerMsg.MSG_LOAD_TOPIC_LIST_SUC, topicList).sendToTarget(); } @Override public void addSwipeUpItem(SwipeUpItem item) { if (item == null) { return; } handler.obtainMessage(HandlerMsg.MSG_LOAD_SWIPE_UP_ITEM, item).sendToTarget(); } @Override public void addLoadMoreTopics(List<TopicItem> topicList) { handler.obtainMessage(HandlerMsg.MSG_LOAD_MORE_TOPICS, topicList).sendToTarget(); } @Override public void hideProgress() { // handler.obtainMessage(HandlerMsg.MSG_DISMISS_LOADING).sendToTarget(); } @Override public void showLoadFailMsg() { if (topicList == null || topicList.size() == 0) { handler.obtainMessage(HandlerMsg.MSG_SHOW_EMPTY_CONTENT).sendToTarget(); }else { handler.obtainMessage(HandlerMsg.MSG_SHOW_FAIL).sendToTarget(); } } @Override public void notifyAdapter() { handler.obtainMessage(HandlerMsg.MSG_NOTIFY_ADAPTER_CONTENT).sendToTarget(); } @Override public void handlerCallback(Message msg) { switch (msg.what) { case HandlerMsg.MSG_LOAD_TOPIC_LIST_SUC: dealTopicListSuc(msg); break; case HandlerMsg.MSG_LOAD_SWIPE_UP_ITEM: SwipeUpItem item = (SwipeUpItem) msg.obj; this.swipeUpItem = item; break; case HandlerMsg.MSG_LOAD_MORE_TOPICS: dealLoadMoreTopics(msg); break; case HandlerMsg.MSG_DISMISS_LOADING: dismissLoadingView(); break; case HandlerMsg.MSG_SHOW_EMPTY_CONTENT: showContentEmptyView(); break; case HandlerMsg.MSG_NOTIFY_ADAPTER_CONTENT: topicAdapter.notifyDataSetChanged(); break; case HandlerMsg.MSG_SHOW_FAIL: ToastUtils.toast(getActivity(),R.string.error_no); break; default: break; } } private void dealLoadMoreTopics(Message msg) { List<TopicItem> moreList = (List<TopicItem>) msg.obj; int count1 = listView.getLastVisiblePosition(); int count2 = topicAdapter.getCount()-1+2; if (moreList.size() < swipeUpItem.getLimit() && count1 == count2) { ToastUtils.toast(getActivity(), "已到达底部"); } if (moreList.size() > 0) { after = TabTopicUtil.getLastKey(moreList); } TabTopicUtil.filterDuplicatedTopic(moreList,homeList); this.topicList.addAll(moreList); topicAdapter.update(this.topicList); listView.restListView(); } private void dealTopicListSuc(Message msg) { List<TopicItem> topicList = (List<TopicItem>) msg.obj; if (topicList.size() <= 0) { showContentEmptyView(); return; } after = TabTopicUtil.getLastKey(topicList); TabTopicUtil.removeDuplicateWithOrder(topicList); topicList.addAll(0,followTopicList); this.topicList = (ArrayList<TopicItem>) topicList; this.homeList = (ArrayList<TopicItem>) topicList; topicAdapter.update(topicList); dismissLoadingView(); listView.restListView(); } @Override public void onRefresh() { //handler.postDelayed(new Runnable() { // @Override //public void run() { if (NetUtils.isNetworkAvaliable(getActivity())) { mTopicPresenter.loadTopicList(getActivity()); } else { if (topicList.size() > 0) { ToastUtils.toast(getActivity(), "没有网络"); listView.restListView(); return; } showNetErroView(R.string.tips_net_error); } // } //}, 2000); } @Override public void onLoadMore() { handler.postDelayed(new Runnable() { @Override public void run() { Map<String, String> m = new HashMap<>(); int size = topicList.size(); if (size <= 0) return; m.put(Net.Param.ID, String.valueOf(swipeUpItem.getId())); m.put(Net.Param.AFTER, after); m.put(Net.Param.LIMIT, String.valueOf(swipeUpItem.getLimit())); if (NetUtils.isNetworkAvaliable(getActivity())) { mTopicPresenter.loadMoreAllTopic(getActivity(), m); } else { ToastUtils.toast(getActivity(), "没有网络"); listView.restListView(); } } }, 500); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.fragment_net_error_subTree: reQuestData(); break; default: break; } } /** * 重新请求数据 */ private void reQuestData() { dismissNetErroView(); dismissContentEmptyView(); if (NetUtils.isNetworkAvaliable(getActivity())) { showLoadingView(); mTopicPresenter.loadTopicList(getActivity()); } else { showNetErroView(R.string.tips_net_error); } } public interface HandlerMsg { //获取话题列表成功 int MSG_LOAD_TOPIC_LIST_SUC = 2002; //获取加载更多配置选项 int MSG_LOAD_SWIPE_UP_ITEM = 2003; //加载更多话题 int MSG_LOAD_MORE_TOPICS = 2004; //隐藏loading int MSG_DISMISS_LOADING = 2005; //显示空 int MSG_SHOW_EMPTY_CONTENT = 2006; //二次请求刷新界面 int MSG_NOTIFY_ADAPTER_CONTENT = 2007; //显示失败 int MSG_SHOW_FAIL = 2008; } @Override public void onDestroyView() {// unbindDrawables(getView()); LogHelper.e(TAG, "whb--> onDestroyView: "); super.onDestroyView(); } @Override public void onResume() { super.onResume(); LogHelper.d(TAG, "SuS--> onResume: "); BaofengStatistics.onUmengEvent(getActivity(), BfCountConst.TopicConst.BBS_CHANNELLIST_SHOW); LogHelper.v("umeng", "bbs_channelList_show 计数一次"); topicList.removeAll(followTopicList); followTopicList = mTopicPresenter.loadFollowTopic(getActivity()); topicList.addAll(0,followTopicList); topicAdapter.notifyDataSetChanged(); //initData(); }}
MVP与MVC的异同
MVC模式与MVP模式都作为用来分离UI层与业务层的一种开发模式被应用了很多年。在我们选择一种开发模式时,首先需要了解一下这种模式的利弊:
无论MVC或是MVP模式都不可避免地存如下弊端,这就导致了这两种开发模式也许并不是很小型应用。
- 额外的代码复杂度和学习成本
但比起他们的优点,这点弊端基本可以忽略了:
- 降低耦合度
- 模块职责划分明显
- 利于测试驱动开发
- 代码复用
- 隐藏数据
- 代码灵活性
对于MVP与MVC这两种模式,它们之间也有很大的差异。以下是这两种模式之间最关键的差异:
MVP模式:
- View不直接与Model交互,而是通过与Presenter交互来与Model间接交互
- Presenter与View的交互是通过接口来进行的,更有利于添加单元测试
- 通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑
MVC模式:
- View可以与Model直接交互
- Controller是基于行为的,并且可以被多个View共享
- 可以负责决定显示哪个View
MVVM
Data-Binding
前言
- 第三方的数据绑定框架随时有停止更新的风险,官方的则相对更稳定一些
- 大量的findViewById,增加代码的耦合性
- 虽然可以通过注解框架抛弃大量的findViewById,但是注解注定要拖慢我们代码的速度,Data Binding则不会,官网文档说还会提高解析XML的速度
这里不赘述了,下面几篇文章都讲的很详细!
精通 Android Data Binding
Android官方数据绑定框架DataBinding(一)
Android官方数据绑定框架DataBinding(二)
官方Data Binding Library
参考链接:
1、https://www.zhihu.com/question/21406685
2、http://liuling123.com/2015/12/mvp-pattern-android.html
3、http://www.2cto.com/kf/201506/405766.html
4、http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0313/2599.html
5、http://blog.csdn.net/qibin0506/article/details/47393725
6、http://zjutkz.net/2016/04/13/%E9%80%89%E6%8B%A9%E6%81%90%E6%83%A7%E7%97%87%E7%9A%84%E7%A6%8F%E9%9F%B3%EF%BC%81%E6%95%99%E4%BD%A0%E8%AE%A4%E6%B8%85MVC%EF%BC%8CMVP%E5%92%8CMVVM/#plg_nld=1&plg_auth=1&plg_nld=1&plg_dev=1&plg_uin=1&plg_usr=1&plg_vkey=1&plg_nld=1&more?hmsr=toutiao.io&utm_source=toutiao.io&plg_uin=1&plg_auth=1&utm_medium=toutiao.io&plg_dev=1&plg_nld=1&plg_usr=1&plg_vkey=1
7、http://blog.csdn.net/wusuopubupt/article/details/8817826
8、[https://github.com/LyndonChin/MasteringAndroidDataBinding](https://github.com/LyndonChin/MasteringAndroidDataBinding)
9、https://github.com/googlesamples/android-architecture
10、http://www.jianshu.com/p/569ab68da482
11、http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0425/4178.html
12、http://blog.csdn.net/vector_yi/article/details/24719873
- Android-architecture之MVC、MVP、MVVM、Data-Binding
- Android之MVC、MVP、MVVM(Data Binding)
- MVC、MVP、MVVM、Data Binding
- Android架构之MVC,MVP与MVVM
- android MVC,MVP,MVVM
- Android MVC MVP MVVM
- Android MVC MVP MVVM
- MVVM 和 Android Data Binding
- MVC,MVP,MVVM之异曲同工
- android MVC,MVP,MVVM概论
- Android MVC MVP MVVM 模式
- Android 中的MVC、MVP、MVVM
- Android架构--MVC、MVP、MVVM
- Android mvvm mvc mvp到底是什么?简述mvvm mvc mvp
- android 的MVVM模型--Data Binding
- Android-MVVM架构-Data Binding的使用
- Android Data Binding代码实战,mvvm
- 【MVVM】Android Data Binding实战(一)
- map的操作
- Android【Fresco】真正实现三级缓存的第三方图片加载框架
- SQL SERVER 36进制转换10进制
- jQuery获取radio选中后的文字
- vs2010反汇编
- Android-architecture之MVC、MVP、MVVM、Data-Binding
- Android 获取手机本地图片所在的位置
- failed to obtain a cell from its dataSource 解决方案
- “58信息定时发布器” 编写纪要
- [从头学声学] 第213节 声调的频谱
- 游戏服务器中的ID生成策略
- iOS自定义NavigationBar
- 试试
- ABAP源代码保护