去年因为项目重构写了个 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 类中:extends BasePresenter
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 中 onCreate 时绑定了
// MoviePresenter 已经在 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 {
void onSuccess(T data);
void onError(String errorMsg);
}
// Model
@Override
public void downloadData(IModelCallBack iModelCallBack) {
iModelCallBack.onSuccess("Success");
iModelCallBack.onError("Error");
}
// Presenter
@Override
public void getData() {
mModel.downloadData(new IModelCallBack() {
@Override
public void onSuccess(String data) {
}
@Override
public void onError(String errorMsg) {
}
});
}
Demo 的 Github地址:
https://github.com/ZhangZeQiao/GeneralFramework.git