android:MVP架構(gòu)模式的優(yōu)雅封裝

簡(jiǎn)介

關(guān)于Android程序的構(gòu)架, 主流的不外乎以下幾種:MVC、MVP和MVVM。
MVC:相對(duì)于較為落后,耦合度太高、職責(zé)不明確,不易于維護(hù)。
MVVM:使用DataBinding,普及性不如MVP。

此外,Google官方提供了Sample代碼來展示MVP模式的用法,因此主流還是選擇MVP架構(gòu)。

因本文主要講的是MVP模式的優(yōu)雅封裝,MVVM模式在此就不作贅述,后續(xù)文章會(huì)講到。

MVC:

提到MVP就不得不提到MVC,關(guān)于MVC架構(gòu),可以看下面這張圖

image.png
MVC工作原理:

MVC即Model View Controller,簡(jiǎn)單來說就是通過controller的控制去操作model層的數(shù)據(jù),并且返回給view層展示,具體見上圖。當(dāng)用戶出發(fā)事件的時(shí)候,view層會(huì)發(fā)送指令到controller層,接著controller去通知model層更新數(shù)據(jù),model層更新完數(shù)據(jù)以后直接顯示在view層上,這就是MVC的工作原理。

這種原理就會(huì)造成一個(gè)一個(gè)致命的缺陷:當(dāng)我們把很多業(yè)務(wù)邏輯寫在activity中時(shí),activity既充當(dāng)了View層,又充當(dāng)了Controller層。因此,耦合性極高,各種業(yè)務(wù)邏輯代碼和View代碼混合在一起你中有我我中有你,如果要修改一個(gè)需求,改動(dòng)的地方可能相當(dāng)多,維護(hù)起來十分不便。

作為一個(gè)追求優(yōu)雅的程序猿,這種架構(gòu)必然要被拋棄。

MVP:

image.png
概念

MVP即Model、View、Presenter
View:負(fù)責(zé)視圖部分展示、視圖事件處理。Activity、Fragment、Dialog、ViewGroup等呈現(xiàn)視圖的組件都可以承擔(dān)該角色。
Model:負(fù)責(zé)數(shù)據(jù)的請(qǐng)求、解析、過濾等數(shù)據(jù)層操作。
Presenter:View和Model交互的橋梁。

優(yōu)勢(shì)

單一職責(zé)
Model、View、Presenter只處理某一類邏輯

解耦

Model層修改和View層修改互不影響
面向接口編程,依賴抽象
Presenter和View互相持有抽象引用,對(duì)外隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié)

可能存在的問題

1、Model進(jìn)行異步操作,獲取結(jié)果通過Presenter回傳到View時(shí),出現(xiàn)View引用的空指針異常
2、Presenter和View互相持有引用,解除不及時(shí)造成的內(nèi)存泄漏。

因此,在進(jìn)行MVP架構(gòu)設(shè)計(jì)時(shí)需要考慮Presenter對(duì)View進(jìn)行回傳時(shí),View是否為空?
Presenter與View何時(shí)解除引用即Presenter能否和View層進(jìn)行生命周期同步?

好了,說了這么多廢話,總之一句話,MVP好。下面我們來看看具體如何優(yōu)雅的實(shí)現(xiàn)MVP的封裝。

MVP架構(gòu)優(yōu)雅的封裝

1、首先,我們定義一個(gè)BaseView

/**
 * 視圖基類
 */
public interface BaseView {
    
}

上面說過,

如果Presenter與View不及時(shí)解除引用關(guān)系,那么內(nèi)存泄漏乃至內(nèi)存溢出就是必然。

具體來說,

當(dāng)Presenter對(duì)象持有一個(gè)或多個(gè)大型Activity的引用,如果該對(duì)象(P)不能被系統(tǒng)回收,那么當(dāng)這些Activity不再使用時(shí),這個(gè)Activity也不會(huì)被系統(tǒng)回收,這樣一來便出現(xiàn)了內(nèi)存泄漏的情況。在應(yīng)用中內(nèi)出現(xiàn)一次兩次的內(nèi)存泄漏或許不會(huì)出現(xiàn)什么影響,但是在應(yīng)用長(zhǎng)時(shí)間使用以后,若是這些占據(jù)大量?jī)?nèi)存的Activity無法被GC回收的話,最終會(huì)導(dǎo)致OOM的出現(xiàn),就會(huì)直接Crash應(yīng)用。

我們當(dāng)然不會(huì)坐視這種情況的發(fā)生,解決的思路就是,

我們將Presenter的生命周期和View層的生命周期綁定在一起,給Presenter定義兩個(gè)方法,一個(gè)綁定View層,一個(gè)解綁View層,在需要的時(shí)候進(jìn)行綁定,不需要的時(shí)候進(jìn)行解綁就可以了。

于是就有了下面這個(gè)定義。

2、將Presenter的生命周期和View層的生命周期綁定

/**
 * 控制器接口:
 * 定義P層生命周期與 V層同步
 */
public interface IPresenter<V extends BaseView> {

    void onMvpAttachView(V view, Bundle savedInstanceState);

    void onMvpStart();

    void onMvpResume();

    void onMvpPause();

    void onMvpStop();

    void onMvpSaveInstanceState(Bundle savedInstanceState);

    void onMvpDetachView(boolean retainInstance);

    void onMvpDestroy();
}

為了代碼的優(yōu)雅性,我們對(duì)它進(jìn)行一次封裝

/**
 * 控制器基類:
 * Presenter生命周期包裝、View的綁定和解除,P層實(shí)現(xiàn)的基類
 */
public class BasePresenter<V extends BaseView> implements IPresenter<V> {

    private WeakReference<V> viewRef;

    protected V getView() {
        return viewRef.get();
    }

    protected boolean isViewAttached() {
        return viewRef != null && viewRef.get() != null;
    }

    private void _attach(V view, Bundle savedInstanceState) {
        viewRef = new WeakReference<V>(view);
    }

    @Override
    public void onMvpAttachView(V view, Bundle savedInstanceState) {
        _attach(view, savedInstanceState);
    }

    @Override
    public void onMvpStart() {

    }

    @Override
    public void onMvpResume() {

    }

    @Override
    public void onMvpPause() {

    }

    @Override
    public void onMvpStop() {

    }

    @Override
    public void onMvpSaveInstanceState(Bundle savedInstanceState) {

    }

    private void _detach(boolean retainInstance) {
        if (viewRef != null) {
            viewRef.clear();
            viewRef = null;
        }
    }

    @Override
    public void onMvpDetachView(boolean retainInstance) {
        _detach(retainInstance);
    }

    @Override
    public void onMvpDestroy() {

    }
}

3、對(duì)于View層,我們一般都會(huì)寫一個(gè)BaseActivity

/**
 * @description 在此類中添加自己的基類功能
 */

 public class BaseActivity extends FragmentActivity {

   protected void openActivity(String action) {
          openActivity(action, null);
   }

   public void showEnsureDialog(String message) {
       
        
   }
}

4、我們?cè)賹懸粋€(gè)綁定生命周期的BaseMvpActivity包裝類

/**
 * MVP的Activity基類:
 * 純粹的 MVP 包裝,不要增加任何View層基礎(chǔ)功能
 * 如果要添加基類功能,請(qǐng)?jiān)趝@link BaseActivity} 中添加
 */
public abstract class BaseMvpActivity<P extends IPresenter> extends BaseActivity implements BaseView {

    protected P mPresenter;

    protected abstract P createPresenter();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = createPresenter();
        if (mPresenter == null) {
            throw new NullPointerException("Presenter is null! Do you return null in createPresenter()?");
        }
        mPresenter.onMvpAttachView(this, savedInstanceState);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (mPresenter != null) {
            mPresenter.onMvpStart();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mPresenter != null) {
            mPresenter.onMvpResume();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mPresenter != null) {
            mPresenter.onMvpPause();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mPresenter != null) {
            mPresenter.onMvpStop();
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mPresenter != null) {
            mPresenter.onMvpSaveInstanceState(outState);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.onMvpDetachView(false);
            mPresenter.onMvpDestroy();
        }
    }

}

5、我們以登錄為例,定義一個(gè)契約類

/**
 * 契約接口類:
 * P層與 V層接口定義
 */
public class LoginContract {

    public interface ILoginView extends BaseView {
        /**
         * 登錄成功
         */

        void LoginSuccess();

        /**
         * 登錄失敗
         *
         * @param msg
         */
        void LoginFailed(String msg);
    }

    public interface ILoginPresenter extends IPresenter<ILoginView> {

        /**
         * 登錄
         */
        void login(String username, String password);
    }
}

6、我們?cè)俣x一個(gè)登錄的Presenter的實(shí)現(xiàn)類,在這個(gè)類中,完成互相訪問。

/**
 * 控制器實(shí)現(xiàn)類
 */
public class LoginPresenterImpl extends BasePresenter<LoginContract.ILoginView> implements LoginContract.ILoginPresenter {

    @Override
    public void login(String username, String password) {
        //先進(jìn)行非空判斷
        if (isViewAttached()) {
            handleLogin(getView(), username, password);
        }
    }

    private void handleLogin(LoginContract.ILoginView view, String username, String password) {
        if (username.isEmpty() || password.isEmpty()) {
            view.LoginFailed("賬號(hào)和密碼不能為空");
        } else if (password.length() < 6 || password.length() > 20) {
            view.LoginFailed("密碼須在6-20位之間");
        } else {
            if (username.equals("mvp")) {
                if (password.equals("123456")) {
                    view.LoginSuccess();
                } else {
                    view.LoginFailed("密碼錯(cuò)誤");
                }
            } else {
                view.LoginFailed("用戶名錯(cuò)誤");
            }
        }
    }

    @Override
    public void onMvpAttachView(LoginContract.ILoginView view, Bundle savedInstanceState) {
        super.onMvpAttachView(view, savedInstanceState);
    }

    /**
     * 重寫P層需要的生命周期,進(jìn)行相關(guān)邏輯操作
     */
    @Override
    public void onMvpResume() {
        super.onMvpResume();
    }

}

7、到這里,我們的封裝基本完成,我們現(xiàn)在來看看我們的LoginActivity是怎么樣的。

public class LoginActivity extends BaseMvpActivity<LoginContract.ILoginPresenter> implements LoginContract.ILoginView {
    @BindView(R.id.et_username)
    EditText etUsername;
    @BindView(R.id.et_password)
    EditText etPassword;
    @BindView(R.id.btn_login)
    Button btnLogin;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvp);
        ButterKnife.bind(this);
    }

    @Override
    protected LoginContract.ILoginPresenter createPresenter() {
        return new LoginPresenterImpl();
    }

    @OnClick({R.id.btn_login})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn_login:
                String username = etUsername.getText().toString().trim();
                String password = etPassword.getText().toString().trim();
                mPresenter.login(username, password);
                break;
        }
    }

    @Override
    public void LoginSuccess() {
        Toast.makeText(this, "LoginSuccess", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void LoginFailed(String msg) {
        Toast.makeText(this, "LoginFailed", Toast.LENGTH_SHORT).show();
    }
}

這樣,是不是看起來,清爽多了。

8、最后,總結(jié):

Mvp模式很好的將View層和Presenter解耦,View層和Presenter層的修改互不影響,并且符合軟件設(shè)計(jì)原則之單一職責(zé),提高了代碼的靈活性和可擴(kuò)展性。最后二者之間通過抽象進(jìn)行關(guān)聯(lián),使之可以互相訪問。
從MVC到最簡(jiǎn)單的MVP架構(gòu),我們解決了MVC的數(shù)據(jù)層和視圖層耦合的問題;隨之而來的是內(nèi)存泄露的問題,通過設(shè)置對(duì)應(yīng)的綁定解綁方法來解決這個(gè)問題;之后又是代碼冗余的問題,于是利用Java的多態(tài)性,我們將重復(fù)性工作交由基類去完成,子類繼承基類重寫對(duì)應(yīng)方法即可。而實(shí)際上我們只需要修改上面Presenter中的構(gòu)造代碼,不需要在構(gòu)造中傳遞V層了,然后再寫一個(gè)綁定和解綁的方法,最后修改Activity創(chuàng)建Presenter時(shí)進(jìn)行綁定,在onDestroy中進(jìn)行解綁。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容