去年因爲項目重構寫了個 MVP 模式的框架 Demo,一直想整理一下,但上半年確實浪得一(*),現在難得有點空閒,馬上整理一下,作爲回顧,查漏補缺。
相關文章:《 RxJava + Retrofit 應用整理 》
接口:
/**
* @author 小僑
* @time 2017/7/21 10:17
* @desc View 層接口/約束類
*/
public interface IView {
void showLoading();
void hideLoading();
void showToast(String message);
}
在 MVP 模式中:View 負責顯示,Presenter 負責邏輯處理,Model 提供數據;且 View 並不直接使用 Model,它們之間的通信是通過 Presenter 來進行的,所有的交互都發生在 Presenter 內部,所以要在 Presenter 中對 View 進行綁定,然後退出時解綁避免內存泄漏。
/**
* @author 小僑
* @time 2017/7/21 10:17
* @desc Presenter 層接口/約束類
*/
public interface IPresenter {
void attachView(IView view);
void detachView();
}
因爲獲取數據的內容、方式不同,所以沒有特定的通用方法可以抽取,但是爲求樣式的統一,所以新建 IModel 接口,而且 IModel 可用於標明子類的類型。
/**
* @author 小僑
* @time 2017/7/21 11:05
* @desc Model 層接口/約束類
*/
public interface IModel {
}
基類:
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
/**
* @author 小僑
* @time 2017/7/21 10:10
* @desc activity 基類
*/
public abstract class BaseActivity<P extends BasePresenter>
extends AppCompatActivity implements IView {
// 此處的泛型可以理解成用於提醒使用者創建 Presenter 以及規範創建的 Presenter 類型
// 標記 View 綁定的 Presenter
protected Context mContext;
protected P mPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(setContentView());
mContext = this;
initView();
// 先創建才能綁定/解綁
mPresenter = initPresenter();
mPresenter.attachView(this);
initData();
}
protected abstract int setContentView();
protected abstract void initView();
protected abstract P initPresenter();
protected abstract void initData();
@Override
public void showLoading() {
showToast("--- 下載中 ---");
}
@Override
public void hideLoading() {
showToast("+++ 下載完 +++");
}
@Override
public void showToast(String message) {
// TODO: 如果子類有自己的 showLoading()、hideLoading()、showToast(),可以重寫
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
}
}
}
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* @author 小僑
* @time 2017/7/26 17:56
* @desc fragment 基類(來源:http://www.jianshu.com/p/651146bd0688)
*/
public abstract class BaseFragment<P extends BasePresenter> extends Fragment implements IView {
// 此處的泛型可以理解成用於提醒使用者創建 Presenter 以及規範創建的 Presenter 類型
// 標記 View 綁定的 Presenter
protected Context mContext; // activity 的上下文
protected P mPresenter;
private int mLayoutId;
@Override
public Context getContext() {
return mContext;
}
public BaseFragment getFragment() {
return this;
}
/**
* 綁定 activity
*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: Activity 重新創建時,會重新構建它所管理的 Fragment,原先的 Fragment 的字段值將會全部丟失,
// 但是通過 Fragment.setArguments(Bundle bundle)方法設置的 bundle 會保留下來。
// 所以儘量使用 Fragment.setArguments(Bundle bundle)方式來傳遞參數
Bundle bundle = getArguments();
if (bundle != null) {
mLayoutId = bundle.getInt("layoutId");
}
mPresenter = initPresenter();
}
/**
* 運行在 onCreate 之後
* 生成 view 視圖
*/
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(getContentView(), container, false);
return view;
}
/**
* 運行在 onCreateView 之後
* 加載數據
*/
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// 由於 fragment 生命週期比較複雜,所以 Presenter 在 onCreateView 創建視圖之後再進行綁定,不然會報空指針異常
mPresenter.attachView(this);
initView();
}
protected abstract P initPresenter();
private int getContentView() {
mLayoutId = setContentView();
Bundle bundle = new Bundle();
bundle.putInt("layoutId", mLayoutId);
setArguments(bundle);
return mLayoutId;
}
protected abstract int setContentView();
protected abstract void initView();
@Override
public void showLoading() {
showToast("--- 下載中 ---");
}
@Override
public void hideLoading() {
showToast("+++ 下載完 +++");
}
/**
* 跳轉 fragment
*/
public void startFragment(Fragment toFragment, String tag) {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.hide(this).add(android.R.id.content, toFragment, tag);
fragmentTransaction.addToBackStack(tag);
fragmentTransaction.commitAllowingStateLoss();
}
/**
* fragment 回退
*/
public void onBack() {
getFragmentManager().popBackStack();
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
@Override
public void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
}
}
}
/**
* @author 小僑
* @time 2017/7/21 10:11
* @desc Presenter 基類
*/
public abstract class BasePresenter<V extends IView, M extends IModel> implements IPresenter {
// 此處的泛型可以理解成用於提醒使用者 Presenter 是用來操作 Model,然後更新 View 的
// 標記 Presenter 綁定的 View 和 Model
protected V mView;
protected M mModel;
public BasePresenter() {
mModel = initModel();
}
protected abstract M initModel();
@Override
public void attachView(IView view) {
mView = (V) view;
}
@Override
public void detachView() {
mView = null;
}
}
就像上面說的,因爲獲取數據的內容、方式不同,所以沒有特定的通用方法可以抽取,但是爲求樣式的統一,所以新建 IModel 接口,用 IModel 標明子類的類型即可,就不用專門創建 BaseModel。
實現:
/**
* @author 小僑
* @time 2017/7/21 10:23
* @desc movie 頁面合約類
*/
public class MovieContract {
/**
* V 視圖層
*/
interface IView {
void showData(String s);
}
/**
* P 視圖與邏輯處理的連接層
*/
interface IPresenter {
void getData();
}
/**
* M 邏輯(業務)處理層
*/
interface IModel {
String downloadData();
}
}
通過上面的合約類來約束下面的 M、V、P 各自需要、可以做的事情,其實這樣是更有利於維護的:M、V、P 是環環相扣的,在 View 呈現某一效果,都得跟 P、M 聯動;那麼如果要添加、刪除、修改某個特定功能,就可以在合約類中統一修改,實現類受到約束,就必須去響應這些實現。
import android.view.View;
import android.widget.TextView;
/**
* @author 小僑
* @time 2017/7/21 10:25
* @desc movie 頁面 View 層
*/
public class MovieActivity extends BaseActivity<MoviePresenter>
implements MovieContract.IView {
// 互相關聯
// MovieActivity 類中:extends BaseActivity<MoviePresenter>
// MoviePresenter 類中:extends BasePresenter<MovieActivity>
private TextView mTv;
@Override
protected int setContentView() {
return R.layout.activity_movie;
}
@Override
protected void initView() {
mTv = (TextView) findViewById(R.id.tv);
}
@Override
protected MoviePresenter initPresenter() {
return new MoviePresenter();
}
@Override
protected void initData() {
mPresenter.getData();
}
@Override
public void showData(String s) {
mTv.setText(s);
}
}
/**
* @author 小僑
* @time 2017/7/21 10:25
* @desc movie 頁面 Presenter 層
*/
public class MoviePresenter extends BasePresenter<MovieActivity, MovieModel>
implements MovieContract.IPresenter {
// MoviePresenter<MovieActivity> 已經在 MovieActivity 中 onCreate 時綁定了
// MoviePresenter<MovieModel> 已經在 MoviePresenter 新建時調用 initModel()方法綁定了
@Override
protected MovieModel initModel() {
return new MovieModel();
}
@Override
public void getData() {
mView.showData(mModel.downloadData());
}
}
/**
* @author 小僑
* @time 2017/7/21 10:30
* @desc movie 頁面 Model 層
*/
public class MovieModel implements IModel, MovieContract.IModel {
@Override
public String downloadData() {
return "abc";
}
}
補充:
因爲之前項目用的網絡請求框架是 RxJava + Retrofit 【RxJava + Retrofit 應用整理】,所以在 Presenter 中使用 Medel 能直接回調 onSuccess 和 onError 方法,但是有一些網絡請求框架沒有這麼方便,所以要自己寫回調接口
/**
* Model 層獲取數據回調接口
*/
public interface IModelCallBack<T> {
void onSuccess(T data);
void onError(String errorMsg);
}
// Model
@Override
public void downloadData(IModelCallBack<String> iModelCallBack) {
iModelCallBack.onSuccess("Success");
iModelCallBack.onError("Error");
}
// Presenter
@Override
public void getData() {
mModel.downloadData(new IModelCallBack<String>() {
@Override
public void onSuccess(String data) {
}
@Override
public void onError(String errorMsg) {
}
});
}
Demo 的 Github地址:
https://github.com/ZhangZeQiao/GeneralFramework.git