吾日三省吾身,爲人謀而不忠乎?與朋友交而不信乎?傳不習乎?
問題
上一個桌面項目MVC模式,在主activity中的代碼超過了2000行,完成後感覺自己的項目很爛。雖然邏輯還算清晰,但是整個View層和Controller層感覺太臃腫了。
是時候該從MVC轉到MVP模式了。下面就有我來簡單的總結一點心得,介紹一下MVP模式,希望能給想用MVP的人一點幫助。
MVP總結和介紹
在MVP模式裏通常包含4個要素:
(1)View:負責繪製UI元素、與用戶進行交互(在Android中體現爲Activity);
(2)View interface:需要View實現的接口,View通過View interface與Presenter進行交互,降低耦合,方便進行單元測試;
(3)Model:負責存儲、檢索、操縱數據(有時也實現一個Model interface用來降低耦合);
(4)Presenter:作爲View與Model交互的中間紐帶,處理與用戶交互的負責邏輯。
MVC與MVP模式比較
MVC模式
- M : 業務層和模型層,相當與javabean和我們的業務請求代碼
- V : 視圖層,對應Android的layout.xml佈局文件
C : 控制層,對應於Activity中對於UI 的各種操作
MVP模式
M :還是業務層和模型層
- V : 視圖層的責任由Activity來擔當
- P : 新成員Prensenter 用來代理 C(control) 控制層
MVPDemo講解
下面我將用一個比較簡單的Demo來向大家展示一下MVP,感受一下朵密的力量吧!
這裏先來看一下biz,也就是業務層,業務層獨立出去,該在哪兒調用就在哪兒調用。
RequestForDataBiz代碼如下:
/**
* Created by Administrator on 2017/2/25 0025.
* 一個請求數據的biz
* biz就是業務層的意思
*/
public interface RequestForDataBiz {
//請求數據業務
void requestForData(OnRequestListener listener);
}
數據請求的回掉接口,聲明瞭成功和失敗的方法 。OnRequestListener代碼如下:
/**
* Created by Administrator on 2017/2/25 0025.
*/
/*請求成功或者失敗的回調接口,就和網絡請求一樣,一個網絡請求的回調大致
有四個success,finish error cancel.這裏就簡單用兩個,請求返回的數據爲一個String集合*/
public interface OnRequestListener {
void onSuccess(List<String> data);
void onFailed();
}
RequestForDataBizIml代碼如下:
請求的實現類爲了模擬網絡請求,開啓了一個會sleep1秒的線程,然後裝填請求的數據,通過OnRequestListener 接口回調出去,與我們平時開發的方式一致。
/**
* Created by Administrator on 2017/2/25 0025.
* 底下這是一個完整的網絡請求,就是模擬網絡數據太麻煩了,弄個假的簡單說明
*/
public class RequestForDataBizIml implements RequestForDataBiz {
@Override
public void requestForData(final OnRequestListener listener) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
ArrayList<String> data = new ArrayList<String>();
for (int i = 1; i < 12; i++) {
data.add("item" + i);
}
if (null != listener) {
listener.onSuccess(data);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
// public static String doGet(Context context, String url, String params) //
// throws IOException {
// InputStream in = null;
// URL realUrl = new URL(url.replace(" ", "%20"));
// Log.e("infodoGet", "HttpUtil:doGet realUrl=" + realUrl.toString());
// URLConnection conn = realUrl.openConnection();
// conn.setConnectTimeout(5000);
// conn.setReadTimeout(5000);
// conn.setRequestProperty("accept", "*/*");
// conn.setRequestProperty("connection", "Keep-Alive");
// conn.setRequestProperty("user-agent",
// "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)") ;
// // conn.setRequestProperty("X-Bsl-Client" , Configure.PACKAGE_NAME + "^" + Configure.has_secure) ;
//
// try {
// conn.connect();
// in = conn.getInputStream();
// BufferedReader br = new BufferedReader(new InputStreamReader(in));
// String line = "";
// result = new StringBuffer();
// while (null != (line = br.readLine())) {
// result.append(line);
// }
// } catch (SocketException e) {
// return new String("SocketException");
// } catch (SocketTimeoutException e) {
// return new String("SocketException");
// }
//
// try {
// if (in != null) {
// in.close();
// }
// } catch (IOException ex) {
// ex.printStackTrace();
// }
// Log.e("infodoGet", "Httpresult.toString()"+ result.toString());
// return result.toString();
// }
//
}
業務層的業務在此處完成。
MVP的凝視與思考
由於Activity變成了view層不再去控制界面,但是具體的界面的改變api其實還是由Activity來提供的,所以在寫MVP之前需要思考,View層需要哪些方法,要做哪些事情。
- 1,顯示loading
- 2,隱藏loading
- 3,listview的初始化,展現頁面
- 4,彈出Toast消息
MVPView接口代碼如下
/**
* Created by Administrator on 2017/2/25 0025.
* view層需要哪些方法,涉及到的UI展示。
*/
public interface MVPView {
//顯示loading progressBar
void showLoading();
//隱藏loading progressBar
void hideLoading();
//ListView的初始化,展示界面
void initListView(List<String> data);
//Toast message
void showMessage(String message);
}
我們總結出View層需要的接口。我們的Activty就是View層,所以直接用Activity來實現上面的方法。將View視圖層就完成。
public class MainActivity extends AppCompatActivity implements MVPView {
private ProgressBar mMvpLoadingbarProgressBar;
private ListView mMvpListviewListView;
private RelativeLayout mActivityMainRelativeLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMvpLoadingbarProgressBar = (ProgressBar) findViewById(R.id.mvp_loadingbar);
mMvpListviewListView = (ListView) findViewById(R.id.mvp_listview);
mActivityMainRelativeLayout = (RelativeLayout) findViewById(R.id.activity_main);
}
@Override
public void showLoading() {
mMvpLoadingbarProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
mMvpLoadingbarProgressBar.setVisibility(View.GONE);
}
@Override
public void initListView(List<String> data) {
ArrayAdapter adapter = new ArrayAdapter(MainActivity.this,android.R.layout.simple_list_item_1,data);
mMvpListviewListView.setAdapter(adapter);
}
@Override
public void showMessage(String message) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
}
}
View視圖層完成了。接下來開始寫presenter層, 同樣在寫presenter之前想想控制層需要哪些方法?
- (1)網絡請求數據
- (2)點擊事件的響應
MVPresenter代碼如下
/**
* Created by Administrator on 2017/2/25 0025.
* - (1)網絡請求數據
- (2)點擊事件的響應
*/
public class MVPresenter {
private MVPView mvpView;
RequestForDataBiz requestBiz;
private Handler mHandler;
public MVPresenter(MVPView mvpView) {
this.mvpView = mvpView;
requestBiz = new RequestForDataBizIml();
mHandler = new Handler(Looper.getMainLooper());
}
public void onResume(){
mvpView.showLoading();
requestBiz.requestForData(new OnRequestListener() {
@Override
public void onSuccess(final List<String> data) {
//由於請求開啓了新線程,所以用handler去更新界面
mHandler.post(new Runnable() {
@Override
public void run() {
mvpView.hideLoading();
mvpView.initListView(data);
}
});
}
@Override
public void onFailed() {
mvpView.showMessage("請求失敗");
}
});
}
public void onItemClick(int position){
mvpView.showMessage("點擊了item"+position);
}
public void onDestroy(){
mvpView = null;
}
}
Presenter完成(Presenter裏面像不像接管了MVC模式中的C層?理解纔是硬道理啊!),現在就剩下一件事,Activity中使用Presenter。
下面放大招:完整版的MainActivity:
/*
* MVP實現了,是不是很簡潔?
* 就是這麼清爽,沒有亂七八糟的業務。
* 沒有各種點擊處理邏輯,Activity只需要提供View層的方法就可以了。
* 你看現在activity的生命週期都託管給了MVPresenter。
* 你要做的就是加深理解,把你的業務邏輯也帶入到prenter層去處理。
* */
public class MainActivity extends AppCompatActivity implements MVPView ,AdapterView.OnItemClickListener{
private ProgressBar mMvpLoadingbarProgressBar;
private ListView mMvpListviewListView;
private RelativeLayout mActivityMainRelativeLayout;
MVPresenter mMVPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMvpLoadingbarProgressBar = (ProgressBar) findViewById(R.id.mvp_loadingbar);
mMvpListviewListView = (ListView) findViewById(R.id.mvp_listview);
mActivityMainRelativeLayout = (RelativeLayout) findViewById(R.id.activity_main);
mMVPresenter=new MVPresenter(this);
}
@Override
public void showLoading() {
mMvpLoadingbarProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
mMvpLoadingbarProgressBar.setVisibility(View.GONE);
}
@Override
public void initListView(List<String> data) {
ArrayAdapter adapter = new ArrayAdapter(MainActivity.this,android.R.layout.simple_list_item_1,data);
mMvpListviewListView.setAdapter(adapter);
}
@Override
public void showMessage(String message) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mMVPresenter.onItemClick(position);
}
//將生命週期託付給MVPresenter,你的邏輯,業務,在這裏面做的事都可以轉移到MVPresenter
@Override
protected void onResume() {
super.onResume();
mMVPresenter.onResume();
}
@Override
protected void onDestroy() {
mMVPresenter.onDestroy();
super.onDestroy();
}
}
一點體會和小小的心得
可以看到,View只負責處理與用戶進行交互,並把數據相關的邏輯操作都扔給了Presenter去做。視圖層與控制層完全分離,可以讓我們在界面還是很粗糙的情況下,先進行控制層的開發,甚至可以先讓View層先提供方法出來,這樣可以節省很多時間。