http://blog.csdn.net/dantestones/article/details/51445208
Android之mvp(一) 之入門使用中我簡單的介紹了mvp,以及怎麼寫mvp。我自己也將mvp運用到了項目中,其實mvp並沒有固定的寫法,正確的去理解架構的思想,都可以有自己獨特的mvp寫法。git上也有很多例子,比如google的android-architecture,simple哥的Android 源碼設計模式解析與實戰中也有mvp的討論。這裏參考了simple哥做了一個通用版的mvp,並對google的MVP做了一點自己的解析。
關於presenter一直持有Activity對象導致的內存泄漏問題
只要用過mvp這個問題可能很多人都知道。寫mvp的時候,presenter會持有view,如果presenter有後臺異步的長時間的動作,比如網絡請求,這時如果返回退出了Activity,後臺異步的動作不會立即停止,這裏就會有內存泄漏的隱患,所以會在presenter中加入一個銷燬view的方法。現在就在之前的項目中做一下修改
//presenter中添加mvpView 置爲null的方法
public void onDestroy(){
mvpView = null;
}
//退出時銷燬持有Activity
@Override
protected void onDestroy() {
mvpPresenter.onDestroy();
super.onDestroy();
}
presenter中增加了類似的生命週期的方法,用來在退出Activity的時候取消持有Activity。
但是在銷燬後需要思考一點,後臺的延時操作返回時,這個時候view被銷燬了,如果接着去調用view的方法就 會拋出空指針異常。所以在後臺的延時操作中需要考慮到這種可能產生空指針的情況,尤其是網絡請求。
BasePresenter
如果每一個Activity都需要做綁定和解綁操作就太麻煩了,現在我希望可以有一個通用的presenter來爲我們添加view的綁定與銷燬。
public abstract class BasePresenter<T> {
public T mView;
public void attach(T mView) {
this.mView = mView;
}
public void dettach() {
mView = null;
}
}
因爲不能限定死傳入的View,所以使用泛型來代替傳入的對象。通過這個通用的presenter我就可以把原來的MvpPresenter簡化成下面的樣子public class LoginPersenter extends BasePresenter<IUserLoginView> {
IUserLoginView loginView;
LoginModel loginModel;
private Handler mHandler = new Handler();
public LoginPersenter(IUserLoginView loginView) {
this.loginView = loginView;
loginModel = new LoginModel();
}
public void clear() {
loginView.clearPassword();
loginView.clearUserName();
}
public void login() {
loginView.showLoading();
loginModel.login(loginView.getUsername(), loginView.getPassword(), new OnLoginListener() {
@Override
public void loginSuccess(final User user) {
mHandler.post(
new Runnable() {
@Override
public void run() {
loginView.hideLoading();
loginView.UpdateView(user);
}
}
);
}
@Override
public void loginFailed() {
mHandler.post(
new Runnable() {
@Override
public void run() {
loginView.hideLoading();
loginView.showFailedError();
}
}
);
}
});
}
}
BaseView
界面需要提供的UI方法中會有很多類似的UI方法,可以把它們提取到一個公共的父類接口中。比如提取顯示loading界面和隱藏loading界面的方法,其他的view層接口就可以直接繼承BaseView接口,不必重複的寫顯示和隱藏loading界面方法。
public interface BaseView {
void showLoading();
void hideLoading();
}
BaseMvpActivity
presenter綁定到activity和View的綁定和解綁操作是每個Activity都會去做的,同樣這裏我也希望能有一個父類來完成這個統一的操作。
public abstract class BaseMvpActivity <V,T extends BasePresenter<V>> extends AppCompatActivity {
public T presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = initPresenter();
}
@Override
protected void onResume() {
super.onResume();
presenter.attach((V)this);
}
@Override
protected void onDestroy() {
presenter.dettach();
super.onDestroy();
}
// 實例化presenter
public abstract T initPresenter();
}
同樣使用泛型來提取通用的邏輯,presenter的初始化,以及view的綁定和解綁操作都提取到父類Activity中。向外部提供了一個 initPresenter();
方法用來初始化presenter,如果想創建不同參數的構造函數都可以隨意去創建。
更加通用的例子
通過上面的base父類,對之前的例子進行優化,寫一個更加好用的例子。
- IUserLoginView繼承BaseView接口,添加自己的初始化方法
public interface IUserLoginView extends BaseView{
void clearUserName();
void clearPassword();
String getUsername();
String getPassword();
void UpdateView(User user);
void showFailedError();
}
- LoginPresenter 繼承BasePresenter類,增加網絡請求和處理點擊事件的方法
public class LoginPersenter extends BasePresenter<IUserLoginView> {
IUserLoginView loginView;
LoginModel loginModel;
private Handler mHandler = new Handler();
public LoginPersenter(IUserLoginView loginView) {
this.loginView = loginView;
loginModel = new LoginModel();
}
public void clear() {
loginView.clearPassword();
loginView.clearUserName();
}
public void login() {
loginView.showLoading();
loginModel.login(loginView.getUsername(), loginView.getPassword(), new OnLoginListener() {
@Override
public void loginSuccess(final User user) {
mHandler.post(
new Runnable() {
@Override
public void run() {
loginView.hideLoading();
loginView.UpdateView(user);
}
}
);
}
@Override
public void loginFailed() {
mHandler.post(
new Runnable() {
@Override
public void run() {
loginView.hideLoading();
loginView.showFailedError();
}
}
);
}
});
}
}
- MainActivity
public class MainActivity extends BaseMvpActivity<IUserLoginView, LoginPersenter> implements IUserLoginView {
private EditText mEtUsername, mEtPassword;
private Button mBtnLogin, mBtnClear;
private ProgressBar mPbLoading;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_login);
initViews();
}
private void initViews() {
mEtUsername = (EditText) findViewById(R.id.id_et_username);
mEtPassword = (EditText) findViewById(R.id.id_et_password);
mBtnClear = (Button) findViewById(R.id.id_btn_clear);
mBtnLogin = (Button) findViewById(R.id.id_btn_login);
mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading);
mBtnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.login();
}
});
mBtnClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.clear();
}
});
}
@Override
public void showLoading() {
mPbLoading.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
mPbLoading.setVisibility(View.GONE);
}
@Override
public String getUsername() {
return mEtUsername.getText().toString();
}
@Override
public String getPassword() {
return mEtPassword.getText().toString();
}
@Override
public void UpdateView(User user) {
Toast.makeText(this, "登錄成功!!!", Toast.LENGTH_SHORT).show();
}
@Override
public void clearUserName() {
mEtUsername.setText("");
}
@Override
public void clearPassword() {
mEtPassword.setText("");
}
@Override
public void showFailedError() {
Toast.makeText(this, "登錄失敗!!!", Toast.LENGTH_SHORT).show();
}
@Override
public LoginPersenter initPresenter() {
return new LoginPersenter(this);
}
}
最終的成果,我們只需要在Acitivity中傳入泛型對象,並寫好initPresenter()
Presenter的初始化的方法就可以直接去使用presenter,當然View的接口還是要自己去實現。 以上的方法只是一些比較簡單的封裝。