從0搭建MVP架構的簡易Demo

一、扯淡

MVP是在MVC架構的基礎上演化來的一種架構,在MVC中Activity屬於C,因此業務邏輯也都是在Activity中實現,造成Activity十分臃腫,而且使用不當容易造成內存泄漏,比如在Activity中有線程在執行耗時操作,然後點back鍵,銷燬Activity,此時因爲有線程存在,線程依然持有Activity的引用,造成Activity無法正常銷燬,從而內存泄漏,MVP的主要優點是將業務層與Activity層解耦。

  • Model 業務邏輯和實體模型
  • View 對應於Activity,負責View的繪製以及與用戶交互
  • Presenter 負責完成View於Model間的交互
  • Contract 這個根據具體個人習慣,起一個契約作用

這裏以網絡請求登錄模塊做一個簡單的Demo,用戶點擊登錄按鈕,這一步是在View層做的,也就是Activity,然後我們去調用Presenter的登錄業務接口,Presenter不需要自己去完成網絡請求,而是繼續調用Model層去具體實現網絡請求,Model從網絡拿到數據後封裝成JavaBean,然後將請求結果(Javabean或者請求異常消息)告訴給Presenter,Presenter拿到結果後再次回調View層去更新UI,這就是一個簡單的MVP流程,這裏也很明顯,Model層和View層是不直接溝通的,而是通過Presenter這個橋樑。

二、基類

BaseModel

model層就是真正業務邏輯實現的地方,當業務邏輯實現完之後需要通知Presenter層,因此BaseModel需要拿到Presenter的引用,所以在BaseModel構造方法中需要將Presenter傳進來,然後還需要契約方法,讓子類去實現具體的業務邏輯。

//接受P層給他的需求(基類)
public abstract class BaseModel<P extends BasePresenter, CONTRACT> {
    protected P p;

    //業務結束通過,通過presenter調用契約、合同 void responseResult(T t);

    public BaseModel(P p) {
        this.p = p;
    }

    protected abstract CONTRACT getContract();
}

BasePresenter

Presenter層首先在其構造方法中創建Model,業務邏輯不同是不同的Model,因此我們生命抽象方法getModel交給子類去創建具體的Model,另外我們需要持有View層(Activity)的引用,但是直接在構造方法中把Activity傳進來這種強引用很容易因爲代碼不當而內存泄漏,因此採用弱引用是比較好的一種方式。

public abstract class BasePresenter<M extends BaseModel, V extends BaseView, CONTRACT> {

    protected M m;

    //綁定View層的弱引用
    private WeakReference<V> vWeakReference;

    public BasePresenter() {
        m = getModel();
    }

    public void bindView(BaseView v) {
        vWeakReference = (WeakReference<V>) new WeakReference<>(v);
    }

    public void unbindView() {
        if (vWeakReference != null) {
            vWeakReference.clear();
            vWeakReference = null;
            System.gc();
        }
    }

    //獲取View  ,P->V
    public V getView() {
        if (vWeakReference != null) {
            return vWeakReference.get();
        }
        return null;
    }

    public abstract M getModel();

    //獲取子類具體契約
    public abstract CONTRACT getContract();
    
}

BaseView

View層就是Activity,View層需要和Presenter層進行通訊,我們在onCreate方法中創建Presenter,因爲但是具體的Presenter要根據業務邏輯不同而不同,因此我們讓子類去實現,我們定義抽象方法getPresenter,並在onCreate中調用,這樣View層就持有了Presenter的一個對象,同時我們調用presenter層的bindView將自身傳給Presenter層,Presenter通過弱引用就持有了View層的引用,最後別忘了在onDestroy中釋放Presenter層,解綁爲了避免內存泄漏。

public abstract class BaseView<P extends BasePresenter, CONTRACT> extends AppCompatActivity {
    protected P p;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        p = getPresenter();

        p.bindView(this);
    }

    //P層做什麼需求
    public abstract CONTRACT getContract();

    //從子類中獲取具體的契約
    public abstract P getPresenter();


    //如果Presenter層出現異常,需要告知View層
    public void error(Exception e) {
    }

    @Override
    protected void onDestroy() {
        p.unbindView();
        super.onDestroy();
    }
}

三、具體實現類

契約類LoginContract

契約類就是將M、V、P層協商的共同業務封裝成接口,讓代碼邏輯更清晰。Model層就是一個請求登錄的最終實現。View層(Activity)則是處理登錄的具體結果,刷新UI。Presenter層既要去調用Model層的具體登錄,又要講回調結果通知給View層。

public interface LoginContract {

    interface Model {
        //Model層子類完成方法的具體實現
        void executeLogin(String name, String psw) throws Exception;
    }

    interface View<T extends BaseEntity> {
        //項目中往往是以Javabean
        void handleResult(T t);
    }

    interface Presenter<T extends BaseEntity> {
        //登錄請求(接受View層的指令,可以自己去做,也可以讓model層去做)
        void requestLogin(String name, String psw);

        //結果響應(接受Model層處理的結果,通知View層去刷新)
        void responseResult(T t);
    }

}

LoginModel

Model類需要實現具體具體登錄的契約。

public class LoginModel extends BaseModel<LoginPresenter, LoginContract.Model> {


    public LoginModel(LoginPresenter loginPresenter) {
        super(loginPresenter);
    }

    @Override
    protected LoginContract.Model getContract() {
        return new LoginContract.Model() {
            @Override
            public void executeLogin(String name, String psw) throws Exception {
                if ("123".equals(name) && "123".equals(psw)) {
                    p.getContract().responseResult(new UserInfo("123", "123"));
                } else {
                    p.getContract().responseResult(null);
                }
            }
        };
    }
}

LoginPresenter

首先實現基類的getModel方法,創建LoginModel,並將自己傳給LoginModel。然後實現自己的契約,在請求登錄的實現中調用Model層的登錄方法,並在拿到Model層的結果後調用View層的處理方法。

public class LoginPresenter extends BasePresenter<LoginModel, LoginActivity, LoginContract.Presenter> {
    @Override
    public LoginModel getModel() {
        return new LoginModel(this);
    }

    @Override
    public LoginContract.Presenter getContract() {
        return new LoginContract.Presenter<UserInfo>() {
            @Override
            public void requestLogin(String name, String psw) {
                try {
                    //3種風格,P層很極端,要不不做事製作轉發,要不拼命一個人幹活,Google的MVPDemo中在P端做了所有的業務邏輯
                    m.getContract().executeLogin(name, psw);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void responseResult(UserInfo userInfo) {
                getView().getContract().handleResult(userInfo);
            }

        };
    }
}

LoginActivity

LoginActivity層首先實現父類的抽象方法getPresenter創建LoginPresenter,然後在按鈕點擊後調用P層的登錄接口,並在請求結果返回的回調中刷新UI。

public class LoginActivity extends BaseView<LoginPresenter, LoginContract.View> {

    private EditText etCount;
    private EditText etPwd;
    private Button btnLongin;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        initView();
        initListener();

    }

    private void initView() {
        etCount = findViewById(R.id.et_count);
        etPwd = findViewById(R.id.et_password);
        btnLongin = findViewById(R.id.btn_login);


    }

    private void initListener() {
        btnLongin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String name = etCount.getText().toString();
                String psw = etPwd.getText().toString();
                p.getContract().requestLogin(name, psw);
            }
        });
    }


    @Override
    public LoginContract.View getContract() {
        return new LoginContract.View<UserInfo>() {
            @Override
            public void handleResult(UserInfo userInfo) {
                if (userInfo != null) {
                    Toast.makeText(LoginActivity.this, userInfo.toString(), Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(LoginActivity.this, "登錄失敗", Toast.LENGTH_SHORT).show();
                }
            }
        };
    }

    @Override
    public LoginPresenter getPresenter() {
        return new LoginPresenter();
    }
}

最後補充上兩個JavaBean的代碼,比較簡單

public class BaseEntity {
    private int code;
    private boolean success;
    private String error;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getError() {
        return error;
    }

    public void setError(String error) {
        this.error = error;
    }
}



public class UserInfo extends BaseEntity {
    private String name;
    private String company;

    public UserInfo() {
    }

    public UserInfo(String name, String company) {
        this.name = name;
        this.company = company;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", company='" + company + '\'' +
                '}';
    }
}

一個簡易版的MVP Demo就完成了。

Demo地址:https://github.com/987570437/MVPDemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章