1. 程式人生 > >Android之MVP(二)之深入封裝

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