Android之MVP(二)之深入封裝
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我就可以把原來的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的介面還是要自己去實現。 以上的方法只是一些比較簡單的封裝。 原始碼下載:http://download.csdn.net/detail/linder_qzy/9580531