Android-architecture之MVC、MVP、MVVM、Data-Binding
傳送門
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模式的整個核心過程。
官方模式圖
案例
這裡以暴風體育中的話題列表為例來進行介紹:
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的速度
這裡不贅述了,下面幾篇文章都講的很詳細!