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;
}
}
接下來,我們需要編寫ILoginModel
、ILoginView
、ILoginPresenter
三個介面
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();
}
}
以上就是本例的全部程式碼,現在我們來整理一下整個建立流程:
- 建立一個實體類UserEntity,用於模擬伺服器返回的資料;
- 建立
ILoginModel
、ILoginView
、ILoginPresenter
介面; - 建立一個
CallBack
介面,用於登入回撥; - 實現M、P、V介面,建立
LoginModelImpl
、LoginPresenterImpl
、LoginActivity
,實現登入功能。
實現的效果如下:
我們再來整理一下整個登入過程是如何實現的:
- 在 LoginActivity 中宣告一個 ILoginPresenter,初始化為 LoginPresenterImpl,向建構函式中傳入兩個引數—— LoginActivity、LoginModelImpl。
presenter = new LoginPresenterImpl(this, new LoginModelImpl());
- 點選 button,呼叫 presenter.login() ——
實際上呼叫的是 LoginPresenterImpl.login()
;
presenter.login(username, password);
- 在 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);
}
});
- 在 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("使用者名稱或密碼錯誤");
}
- 在 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);
下面貼出 LoginActivity
和 LoginFragment
的程式碼供大家進行比較:
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(<