概述
關於Android MVP設計模式已經出來很久了,在項目中也普遍使用這個設計模式,但是MVP對於初學者來說的話還是有點難以捉摸的!每次看上去都感覺這樣寫很不錯,很好理解,就是增加了很多類,看完之後自己去寫又很難寫出來。再講MVP設計模式之前,我們先來講一下MVC設計模式。
MVC模式
MVC(模型-視圖-控制器)用一種業務邏輯、數據、界面顯示分離的方法組織代碼,在改進和個性化定製界面及用戶交互時,無須重新編寫業務邏輯,MVC模式結構分爲三個部分:
- Model:實體模型,我們針對業務模型,建立的數據結構和相關類,對應着Bean實體類。Model與View無關,但是與業務有關。
- View:一般採用XML文件或者Java代碼進行界面的描述,也可以使用JS+HTML的方式作爲View。
- Controller:Android的控制器通常在Activity、Fragment或者由它們控制的其他業務類中。
MVC用一句話來概括就是通過Controller來操作Model層的數據,並且返回給View層來展示。如圖所示:
看起來像是那麼回事,但是MVC也有它的缺點,不然就沒我們MVP模式什麼事了。
- Activity並不是一個標準的MVC模式中的Controller,它的首要職責是加載應用佈局和初始化界面,接受並出來來自用戶的請求,並作出相應,隨着界面及其邏輯複雜度的不斷提升,Activity類的職責不斷增加,導致其變得龐大而臃腫。
- Model層和View層相互耦合,不易於開發和維護。
MVP模式
MVP模式是MVC模式的一個演化版本,它的結構同樣分爲三個部分:
Mode:實體模型,同樣對應着Bean實體類主要提供數據的存取的功能,Present需要通過Model層來存儲和或者獲取數據
從網絡,數據庫,文件,傳感器,第三方等數據源讀寫數據。
對外部的數據類型進行解析轉換爲APP內部數據交由上層處理。
對數據的臨時存儲,管理,協調上層數據請求。View:負責處理用戶事件交互和視圖部分的展示,在Android中它可以使Activity和Fragment或者是某個View控件
提供UI交互
在presenter的控制下修改UI。
將業務事件交由presenter處理。
注意: View層不存儲數據,不與Model層交互。View和Presenter通過接口進行交互 面向接口編程Presenter:作爲View與Model交互的中間紐帶,處理與用戶交互的負責邏輯。它還從Model中去獲取數據然後返回給View,使得Model和View沒有耦合。
MVP的核心思想
MVP 把 Activity 中的 UI 邏輯抽象成 View 接口,把業務邏輯抽象成 Presenter 接口,Model 類還是原來的 Model。這就是 MVP 模式,現在這樣的話,Activity 的工作的簡單了,只用來響應生命週期,其他工作都丟到 Presenter 中去完成。從上圖可以看出,Presenter 是 Model 和 View 之間的橋樑,爲了讓結構變得更加簡單,View 並不能直接對 Model 進行操作,這也是 MVP 與 MVC 最大的不同之處。
MVP的優點
- 分離了視圖邏輯和業務邏輯,降低了耦合
- Activity 只處理生命週期的任務,代碼變得更加簡潔
- 視圖邏輯和業務邏輯分別抽象到了 View 和 Presenter 的接口中去,提高代碼的可閱讀性
- Presenter 被抽象成接口,可以有多種具體的實現,所以方便進行單元測試
- 把業務邏輯抽到 Presenter 中去,避免後臺線程引用着 Activity 導致 Activity 的資源無法被系統回收從而引起內存泄露和 OOM
使用 MVP,至少需要經歷以下步驟:
創建 IPresenter 接口,把所有業務邏輯的接口都放在這裏,並創建它的實現PresenterCompl(在這裏可以方便地查看業務功能,由於接口可以有多種實現所以也方便寫單元測試)創建 IView 接口,把所有視圖邏輯的接口都放在這裏,其實現類是當前的 Activity/Fragment。Activity 裏包含了一個 IPresenter,而 PresenterCompl 裏又包含了一個 IView 並且依賴了 Model。Activity 只保留對 IPresenter 的調用,其它工作全部留到 PresenterCompl 中實現。Model 並不是必須有的,但是一定會有 View 和 Presenter。
MVP簡單的登陸實例
首先看看我們的項目的結構:
就跟我們上面說的一樣將業務邏輯和試圖邏輯抽象成接口,然後創建他的實現類,其中IView的實現類是Activity。
(一) Model
首先就和我們上面說的一樣,實體類User是要有的。然後至少有一個業務方法Login()用來從網絡中讀取數據。
public class User {
private String name;
private String pwd;
public User(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
public interface IUserBiz {
void login(String name, String pwd, OnLoginListener onLoginListener);
}
public interface OnLoginListener
{
void loginSuccess(User user);
void loginFailed();
}
public class UserBiz implements IUserBiz {
@Override
public void login(final String name, final String pwd, final OnLoginListener onLoginListener) {
//子線程耗時操作
new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//登錄成功
if ("admin".equals(name) && "admin".equals(pwd)) {
User user = new User(name,pwd);
onLoginListener.loginSuccess(user);
} else {
onLoginListener.loginFailed();
}
}
}.start();
}
}
(二) View
我們這裏是模擬登陸,有Login和Clear兩個按鈕,用來登陸和清除賬號密碼。所以我們需要清楚賬號密碼的方法,還有ProgressBar的顯示與隱藏和對登陸這個操作的結果做出響應的方法。綜上所述接口爲:
public interface IUserLoginView {
void clearUserName();
void clearPassword();
void showLoading();
void hideLoading();
void loginSuccess(User user);
void loginError();
}
實現類代碼:
public class LoginOptimized1Activity extends AppCompatActivity implements IUserLoginView, View.OnClickListener {
private EditText editUser;
private EditText editPass;
private Button btnLogin;
private Button btnClear;
private ProgressBar progressBar;
private ILoginPresenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initViews();
mPresenter = new LoginPresentCompl(this);
}
private void initViews() {
editUser = (EditText) this.findViewById(R.id.et_login_username);
editPass = (EditText) this.findViewById(R.id.et_login_password);
btnLogin = (Button) this.findViewById(R.id.btn_login_login);
btnClear = (Button) this.findViewById(R.id.btn_login_clear);
progressBar = (ProgressBar) this.findViewById(R.id.progress_login);
btnLogin.setOnClickListener(this);
btnClear.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login_clear:
mPresenter.clear();
break;
case R.id.btn_login_login:
mPresenter.login(editUser.getText().toString(), editPass.getText().toString());
break;
}
}
@Override
public void clearUserName() {
editUser.setText("");
}
@Override
public void clearPassword() {
editPass.setText("");
}
@Override
public void showLoading() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void loginSuccess(User user) {
Toast.makeText(this, "Login Success", Toast.LENGTH_SHORT).show();
}
@Override
public void loginError() {
Toast.makeText(this, "Login Error", Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
(三) Presenter
Presenter是用作Model和View之間交互的橋樑,那麼應該有什麼方法呢?其實也是主要看該功能有什麼操作,比如本例,兩個操作:login和clear。
public interface ILoginPresenter {
void login(String name, String pwd);
void clear();
}
實現類:
public class LoginPresentCompl implements ILoginPresenter {
private IUserBiz userBiz;
private IUserLoginView userLoginView;
private Handler mHandler = new Handler(Looper.getMainLooper());
public LoginPresentCompl(IUserLoginView userLoginView) {
this.userLoginView = userLoginView;
this.userBiz = new UserBiz();
}
@Override
public void login(String name, String pwd) {
userLoginView.showLoading();
userBiz.login(name, pwd, new OnLoginListener() {
@Override
public void loginSuccess(final User user) {
//需要在UI線程執行
mHandler.post(new Runnable() {
@Override
public void run() {
userLoginView.hideLoading();
userLoginView.loginSuccess(user);
}
});
}
@Override
public void loginFailed() {
mHandler.post(new Runnable() {
@Override
public void run() {
userLoginView.hideLoading();
userLoginView.loginError();
}
});
}
});
}
@Override
public void clear() {
userLoginView.clearUserName();
userLoginView.clearPassword();
}
}
解決OOM和內存泄漏
好了以上基本上就是一個簡單的MVP模擬登陸的例子了。但是以上的代碼還有一個問題,就是可能會造成內存泄漏,上面我們說MVP的優點的時候也說了。MVP能避免OOM和內存泄漏。接下來我們就利用弱引用結合Activity的生命週期來解決這個問題。
首先我們定義一個Present的基類,讓其他presenter繼承此類。
public class BasePresenter<V> {
protected Reference<V> mViewRef;
public void attachView(V view) {
mViewRef = new WeakReference<V>(view);
}
public V getView() {
if (mViewRef == null) {
return null;
}
return mViewRef.get();
}
public boolean isViewAttached() {
return mViewRef != null && mViewRef.get() != null;
}
public void detachView() {
if (mViewRef != null) {
mViewRef.clear();
mViewRef = null;
}
}
}
這是一個泛型類,參數爲V爲要引用的View.
attachView()方法的作用是創建View的虛引用,此方法放在View初始化過程中,例如onCreate()方法中
detachView()方法的作用是銷燬View的虛引用,此方法放在View銷燬化過程中,例如onDestroy()方法中
getView()放用來獲取View
然後我們定義一個View的基類,讓其他的View繼承此類。
public abstract class MVPBaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {
protected T mPresenter;
@Override
protected void onCreate(Bundle arg) {
super.onCreate(arg);
mPresenter = createPresenter();
mPresenter.attachView((V) this);
}
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter.detachView();
}
protected abstract T createPresenter();
}
這也是一個泛型基類,第一個泛型參數V:代表當前處理的View,第二個泛型參數T:代表當前使用的Presenter,要求此presenter必須繼承至 BasePresenter.要求每個Activity必須繼承至此類。在onCreate()和onDestroy()中的處理邏輯已經在講解 Prestener 時候已經說過。createPresenter() 用來創建presenter實例,便於在View中通過其調用presenter的方法,此方法是abstract的,這就要求MVPBaseActivity子類必須實現此方法。
最後講到這裏基本就結束了。其他的有什麼問題的地方可以去查看本文的源碼去解決。
寫在最後(參考文章)
https://blog.csdn.net/lmj623565791/article/details/46596109
http://kaedea.com/2015/10/11/android-mvp-pattern/
https://blog.csdn.net/ShuSheng0007/article/details/77938378
https://www.jianshu.com/p/9a6845b26856