1. 程式人生 > >Android MVP架構實現

Android MVP架構實現

最近在學習Android的MVP架構,在網上找了許多資料都沒有一個清晰的認識,偶然看到簡書上駱駝騎士前輩的文章,對MVP的實現過程有了一個較為清晰的認識。後又研究了一下Google官方Demo,分別對兩種實現方式做了一個Demo進行對比。

對於Android MVP實現方式,借用一下駱駝騎士前輩的分析

目前常見的MVP在Android裡的實踐有兩種解決方案:
1.直接將Activity看作View,讓它只承擔View的責任。
2.將Activity看作一個MVP三者以外的一個Controller,只控制生命週期。

下面我們來講述一下這兩種方式的實現過程。


第一種:Activity承擔MVP中V的角色

我們以一個不需要網路請求的登入作為示例進行實現。

在這個MVP架構中,我們需要以下幾個檔案:

型別 檔名
Model 介面 ILoginModel
View 介面 ILoginView
Presenter 介面 ILoginPresenter
Model 介面實現 ILoginModelImpl
View 介面實現 LoginActivity
Presenter 介面實現 ILoginPresenterImpl

首先,我們先定義一個實體類UserEntity,模擬伺服器返回的資料。

UserEntity.java
public class UserEntity {
    /**
     * 響應碼
     */
    private int code;
    /**
     * 提示資訊
     */
    private String msg;
    /**
     * 使用者名稱
     */
    private
String user_name; public int getCode() { return code; } public String getMsg() { return msg; } public String getUser_name() { return user_name; } public void setCode(int code) { this.code = code; } public void setMsg(String msg) { this.msg = msg; } public void setUser_name(String user_name) { this.user_name = user_name; } }

接下來,我們需要編寫ILoginModelILoginViewILoginPresenter三個介面

ILoginModel.java
import com.example.mvp.login.CallBack;

public interface ILoginModel {
    /**
     * 登入相關的業務邏輯在此處理
     *
     * @param username 賬號
     * @param password 密碼
     * @param callBack 回撥介面
     */
    void login(String username, String password, CallBack callBack);
}
ILoginView.java
import com.example.mvp.login.entity.UserEntity;

public interface ILoginView {
    /**
     * 登入成功的回撥顯示
     *
     * @param userEntity 實體類
     */
    void showLoginSuccessMsg(UserEntity userEntity);

    /**
     * 登入失敗的回撥顯示
     *
     * @param errorMsg 失敗資訊
     */
    void showLoginFailMsg(String errorMsg);
}
ILoginPresenter.java
public interface ILoginPresenter {
    /**
     * 連線 Model 和 View 的登入方法
     *
     * @param username 賬號
     * @param password 密碼
     */
    void login(String username, String password);
}

因為在Model中用到了一個回撥介面CallBack,所以我們接下來定義一下這個介面:

CallBack.java
import com.example.mvp.login.entity.UserEntity;

public interface CallBack {
    /**
     * 登入成功的回撥
     *
     * @param userEntity 實體類
     */
    void onSuccess(UserEntity userEntity);

    /**
     * 登入失敗的回撥
     *
     * @param errorMsg 錯誤資訊
     */
    void onFail(String errorMsg);
}

至此,MVP架構的準備工作都做好了,接下來我們開始實現這些介面,將各個介面連線起來,實現一個登入功能。

大家知道,Model是承擔了資料處理和業務邏輯的功能,所以我們來實現一下ILoginModel這個介面。在這個類中,我們假設當賬號為 user,密碼為 123456 時即登入成功。

LoginModelImpl.java
import com.example.mvp.login.CallBack;
import com.example.mvp.login.entity.UserEntity;

public class LoginModelImpl implements ILoginModel {
    @Override
    public void login(String username, String password, CallBack callBack) {
        if (username.equals("user") && password.equals("123456")) {
            // 模擬伺服器返回的資料
            UserEntity userEntity = new UserEntity();
            userEntity.setCode(1);
            userEntity.setMsg("登入成功");
            userEntity.setUser_name("Vip User");

            callBack.onSuccess(userEntity);
        } else {
            callBack.onFail("使用者名稱或密碼錯誤");
        }
    }
}

接下來我們實現一下作為中間聯絡人的ILoginPresenter介面:

LoginPresenterImpl.java
import com.example.mvp.login.CallBack;
import com.example.mvp.login.entity.UserEntity;
import com.example.mvp.login.model.ILoginModel;
import com.example.mvp.login.view.ILoginView;

public class LoginPresenterImpl implements ILoginPresenter {
    /**
     * View
     */
    private ILoginView view;
    /**
     * Model
     */
    private ILoginModel model;

    /**
     * 構造方法
     *
     * @param view  實現介面的類
     * @param model 實現介面的類
     */
    public LoginPresenterImpl(ILoginView view, ILoginModel model) {
        this.view = view;
        this.model = model;
    }

    @Override
    public void login(String username, String password) {
        //主動呼叫model中的login方法實現登入
        model.login(username, password, new CallBack() {
            @Override
            public void onSuccess(UserEntity userEntity) {
                //如果登入成功,將登入成功的資料傳遞給View進行顯示
                view.showLoginSuccessMsg(userEntity);
            }

            @Override
            public void onFail(String errorMsg) {
                //如果登入失敗,將登陸失敗的資料傳遞給View顯示
                view.showLoginFailMsg(errorMsg);
            }
        });
    }
}

最後一步,新建一個LoginActivity,實現ILoginView,該activity的內容非常簡單,只有兩個EditText用於輸入賬號和密碼,一個Button用於點選登入,在這裡,只貼出Activity的程式碼:

LoginActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.example.mvp.R;
import com.example.mvp.login.Presenter.ILoginPresenter;
import com.example.mvp.login.Presenter.LoginPresenterImpl;
import com.example.mvp.login.entity.UserEntity;
import com.example.mvp.login.model.LoginModelImpl;

public class LoginActivity extends AppCompatActivity implements ILoginView {
    /**
     * Presenter
     */
    private ILoginPresenter presenter;
    /**
     * 登入按鈕
     */
    private Button btn;
    /**
     * 使用者名稱輸入框
     */
    private EditText etName;
    /**
     * 密碼輸入框
     */
    private EditText etPwd;

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

        //例項化Presenter
        presenter = new LoginPresenterImpl(this, new LoginModelImpl());

        //繫結控制元件
        btn = findViewById(R.id.btn);
        etName = findViewById(R.id.et_name);
        etPwd = findViewById(R.id.et_pwd);

        //實現登入的點選事件
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String username = etName.getText().toString();
                String password = etPwd.getText().toString();

                //檢測賬號密碼均不為空時呼叫presenter的登入方法
                if ("".equals(username) || "".equals(password)) {
                    Toast.makeText(LoginActivity.this, "使用者名稱或密碼不能為空", Toast.LENGTH_SHORT).show();
                } else {
                    presenter.login(username, password);
                }
            }
        });
    }


    @Override
    public void showLoginSuccessMsg(UserEntity userEntity) {
        Toast.makeText(this, userEntity.getUser_name() + "登入成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showLoginFailMsg(String errorMsg) {
        Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
    }
}

以上就是本例的全部程式碼,現在我們來整理一下整個建立流程:

  1. 建立一個實體類UserEntity,用於模擬伺服器返回的資料;
  2. 建立ILoginModelILoginViewILoginPresenter介面;
  3. 建立一個CallBack介面,用於登入回撥;
  4. 實現M、P、V介面,建立LoginModelImplLoginPresenterImplLoginActivity,實現登入功能。

實現的效果如下:
登陸成功登入失敗
我們再來整理一下整個登入過程是如何實現的:

  1. 在 LoginActivity 中宣告一個 ILoginPresenter,初始化為 LoginPresenterImpl,向建構函式中傳入兩個引數—— LoginActivity、LoginModelImpl。
  presenter = new LoginPresenterImpl(this, new LoginModelImpl());
  1. 點選 button,呼叫 presenter.login() —— 實際上呼叫的是 LoginPresenterImpl.login()
 presenter.login(username, password);
  1. 在 LoginPresenterImpl.login() 中呼叫 model.login(),實現 CallBack 介面,將 CallBack 回撥的資料推送給 View 進行顯示 —— 實際上呼叫的是 LoginModelImpl.login()
model.login(username, password, new CallBack() {
            @Override
            public void onSuccess(UserEntity userEntity) {
                //如果登入成功,將登入成功的資料傳遞給View進行顯示
                view.showLoginSuccessMsg(userEntity);
            }

            @Override
            public void onFail(String errorMsg) {
                //如果登入失敗,將登陸失敗的資料傳遞給View顯示
                view.showLoginFailMsg(errorMsg);
            }
        });
  1. 在 model.login() 中實現登入判斷,將登入結果通過 CallBack 介面提交給 Presenter;
if (username.equals("user") && password.equals("123456")) {
	// 模擬伺服器返回的資料
      UserEntity userEntity = new UserEntity();
      userEntity.setCode(1);
      userEntity.setMsg("登入成功");
      userEntity.setUser_name("Vip User");

      callBack.onSuccess(userEntity);
} else {
     callBack.onFail("使用者名稱或密碼錯誤");
}
  1. 在 LoginActivity 實現的 onLoginSuccessMsg() 、onLoginFailMsg() 中處理登陸成功和失敗的提示。
@Override
public void showLoginSuccessMsg(UserEntity userEntity) {
	Toast.makeText(this, userEntity.getUser_name() + "登入成功", Toast.LENGTH_SHORT).show();
}

@Override
public void showLoginFailMsg(String errorMsg) {
   Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show();
}

第二種:將Activity看作一個MVP三者以外的一個Controller,只控制生命週期

我們仍舊以一個不需要網路請求的登入作為示例進行實現。

在這個個MVP架構中,我們需要以下幾個檔案:

型別 檔名
Model 介面 ILoginModel
View 介面 ILoginView
Presenter 介面 ILoginPresenter
Model 介面實現 ILoginModelImpl
View 介面實現 LoginFragment
Presenter 介面實現 ILoginPresenterImpl
Controller LoginActivity

大家可以看到,和上一個例子相比,我們多了一個 LoginFragment,並且對 View 介面的實現並不是 LoginActivity 來實現了,而是改為LoginFragment。( 其實在Google官方Demo裡,View 和 Presenter 的介面是放在一個名為 Contract 的介面檔案裡的,此處為了更好的理解,將 View 和 Presenter 分別放置在一個檔案裡。

因為這個Demo和上一個Demo並沒有太大的區別,所以此處只貼出不同部分的程式碼片段。

LoginModelImpl.java (增加如下程式碼)
/**
 * 生成例項
 */
private static LoginModelImpl instance = null;

public static LoginModelImpl getInstance() {
	if (instance == null) {
		instance = new LoginModelImpl();
    }
    return instance;
}

private LoginModelImpl() {  }
LoginPresenterImpl.java(修改變數和構造方法)
/**
 * View
 */
private ILoginView view;
/**
 * Model
 */
private LoginModelImpl model;
/**
 * 構造方法
 *
 * @param view  實現介面的類
 * @param model 實現介面的類
 */
public LoginPresenterImpl(ILoginView view, LoginModelImpl model) {
	this.view = view;
    view.setPresenter(this);
    this.model = model;
}
ILoginView.java(增加如下方法)
/**
 * 設定Presenter
 *
 * @param presenter
 */
void setPresenter(ILoginPresenter presenter);

下面貼出 LoginActivityLoginFragment 的程式碼供大家進行比較:

LoginActivity.java
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.mvp.R;
import com.example.mvp.login.Presenter.LoginPresenterImpl;
import com.example.mvp.login.model.LoginModelImpl;

public class LoginActivity extends AppCompatActivity {
    /**
     * Presenter
     */
    LoginPresenterImpl presenter;

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

		 // 繫結Fragment
        FragmentManager fragmentManager = getSupportFragmentManager();
        LoginFragment loginFragment = (LoginFragment) fragmentManager.findFragmentById(R.id.frame_layout);
        if (loginFragment == null) {
            loginFragment = LoginFragment.newInstance();
            if (fragmentManager != null && loginFragment != null) {
                FragmentTransaction transaction = fragmentManager.beginTransaction();
                transaction.add(R.id.frame_layout, loginFragment);
                transaction.commit();
            }
        }
        
        //例項化Presenter
        presenter = new LoginPresenterImpl(loginFragment, LoginModelImpl.getInstance());
    }
}
LoginFragment.java
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.example.mvp.R;
import com.example.mvp.login.Presenter.ILoginPresenter;
import com.example.mvp.login.entity.UserEntity;

public class LoginFragment extends Fragment implements ILoginView {
    /**
     * presenter
     */
    private ILoginPresenter presenter;
    /**
     * 登入按鈕
     */
    private Button btn;
    /**
     * 使用者名稱輸入框
     */
    private EditText etName;
    /**
     * 密碼輸入框
     */
    private EditText etPwd;

    /**
     * 生成例項
     *
     * @return loginFragment
     */
    public static LoginFragment newInstance() {
        return new LoginFragment();
    }

    public LoginFragment() {

    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_login, container, false);
        //繫結控制元件
        btn = view.findViewById(R.id.btn);
        etName = view.findViewById(R.id.et_name);
        etPwd = view.findViewById(R.id.et_pwd);

        //實現登入的點選事件
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String username = etName.getText().toString();
                String password = etPwd.getText().toString();

                //檢測賬號密碼均不為空時呼叫presenter的登入方法
                if ("".equals(username) || "".equals(password)) {
                    Toast.makeText(getContext(), "使用者名稱或密碼不能為空", Toast.LENGTH_SHORT).show();
                } else {
                    presenter.login(username, password);
                }
            }
        });
        return view;
    }

    @Override
    public void setPresenter(<