最近工作上碰到了一个Android项目需要重构,因为之前的项目是MV模式,UI和业务混杂在一起,加上最初开发这个项目的程序员离职,这个项目已经处于维护十分困难的情况,最终选择重构。考虑到本项目属于业务复杂度中等的项目,而且希望新的项目具有鲜明的UI-业务-数据层次以方便维护,而MVP架构十分适合中大型项目,所以趁这个机会再次熟悉了一下MVP架构的搭建,下面说说我的做法。
我希望M、P层是不与Activity UI体系有关系的纯java代码,与AndroidUI有关的逻辑交给V层处理,出于这些考虑,我提供了了MVP三个层次的BASE抽象类(接口):
public interface BaseView {
void showloading();
void hideloading();
}
V层的具体实现需要实现网络取数据时的loading图的业务逻辑,当然你们有其它的业务需要所有v都实现,也可以写在BaseView里
public abstract class BasePresenter<V extends BaseView> {
public V view;
public void attachView(V view) {
this.view = view;
}
public V getView() {
return view;
}
protected boolean isAttach() {
return null != view;
}
public void onDettach() {
if (null != view) {
view = null;
}
}
abstract protected void start();
}
我希望P层和V层是一一绑定的,这样的好处在于业务逻辑会很清晰,比如我想知道某个页面有哪些业务,我不需要从ui看起,直接找这个V层绑定的P就可以找到全部的业务,如果代码比较规范或者注释比较到位,我们甚至可以直接知道各个业务的前后关系,这样维护起来即使没有接触过这个项目的人也很快可以上手。
那么在来说说我们的BasePresenter,这个抽象类主要使用泛型实现了绑定/解绑V层,注意如果Activity/Fragment已经destory而P层未解绑的话是会内存泄漏的。另外,提供了start抽象方法供V层在创建时就请求数据,典型的业务场景:首页创建时就请求首页数据来展示。
public interface BaseModel {
void getRemoteData(String url, String content);
void getRemoteData(String url, String content, int type);
// void getLocalData();
void setHttplistner(Httplistener httplistener);
}
BaseModel就十分简单啦,实现数据请求即可,setHttplistner方法设置数据请求的回调供P层使用。
下面是MVP这个三个基类的实现:
V层的主要载体是Acitivity和Fragment,因此需要实现BaseView接口,它同时也需要绑定P层,所以也需要实现BasePresenter:
public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity implements BaseView {
public P presenter;
protected Context mContext;
private CustomDialog dialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
mContext = this;
presenter = getT(this, 0);
if (presenter != null) {
presenter.attachView(this);
presenter.start();
}
initView();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (Httphelper.handler != null) {
Httphelper.handler.removeCallbacksAndMessages(null);
}
if (presenter != null) {
presenter.onDettach();
}
}
/**
* 绑定activity的layout
*/
public abstract int getLayoutId();
/**
* 初始化页面的实现
*/
public abstract void initView();
private <T> T getT(Object o, int i) {
try {
return ((Class<T>) ((ParameterizedType) (o.getClass().getGenericSuperclass())).getActualTypeArguments()[i]).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void showloading() {
if (dialog == null) {
dialog = CustomDialog.instance(this);
dialog.setCancelable(false);
dialog.show();
} else {
dialog.show();
}
}
@Override
public void hideloading() {
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
}
}
public abstract class BaseFragment<P extends BasePresenter> extends Fragment implements BaseView {
protected View mRootView;
public P presenter;
private CustomDialog dialog;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
mRootView = inflater.inflate(getContentViewId(), container, false);
presenter = getT(this, 0);
if (presenter != null) {
presenter.attachView(this);
presenter.start();
}
initAllMembersView(savedInstanceState);
return mRootView;
}
@Override
public void onDetach() {
super.onDetach();
}
@Override
public void onDestroy() {
super.onDestroy();
if (presenter != null) {
presenter.onDettach();
}
}
/**
* 绑定Fragment Layout Id
*/
public abstract int getContentViewId();
/**
* 初始化Fragment UI
*
* @param savedInstanceState
*/
protected abstract void initAllMembersView(Bundle savedInstanceState);
private <T> T getT(Object o, int i) {
try {
return ((Class<T>) ((ParameterizedType) (o.getClass().getGenericSuperclass())).getActualTypeArguments()[i]).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void showloading() {
if (dialog == null) {
dialog = CustomDialog.instance(getActivity());
dialog.setCancelable(false);
dialog.show();
} else {
dialog.show();
}
}
@Override
public void hideloading() {
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
}
}
BaseActivity和BaseFragment要做的事情相似,利用泛型获取P层具体的实现对象,然后绑定这个对象,showloading、hideloading方法实现loading页面的展示/隐藏,再次说明,生命周期destory中要解绑presenter,否则会导致内存泄漏。
现在我们的MVP基类都准备好了,那么具体怎么使用呢,以一个项目的首页为例:
首先首页肯定需要加载首页内容,再加一个,首页需要检查更新。
那么我们的P层就需要提供这些业务:
public class HomePresenter extends BasePresenter<HomeView> {
@Override
protected void start() {
if (!isAttach()) {
return;
}
getIndexData();
getVersionInfo();
}
public void getIndexData() {
if (!isAttach()) {
return;
}
BaseModel model = new HomePresenterModel();
model.setHttplistner(new Httplistener<HomeBean>() {
@Override
public void onFail(int status, String message) {
if (!isAttach()) {
return;
}
view.onHttpfail(status, message);
}
@Override
public void onSuccess(HomeBean bean) {
if (!isAttach()) {
return;
}
view.onHttpsuccess(bean);
}
@Override
public void onError() {
if (!isAttach()) {
return;
}
view.onHttpError();
}
});
String url = "XXXXXXXX";//首页数据的地址,这里是网络加载
model.getRemoteData(url, "", HomePresenterModel.GETDATE);
}
public void getVersionInfo() {
if (!isAttach()) {
return;
}
String app_version = "x.x.x";//这里获取版本号
JsonObject json = new JsonObject();
json.addProperty("app_version", app_version);
BaseModel model = new HomePresenterModel();
model.setHttplistner(new Httplistener<GetVersionInfoBean>() {
@Override
public void onFail(int status, String message) {
if (!isAttach()) {
return;
}
view.getVersionInfoFail(status, message);
}
@Override
public void onSuccess(GetVersionInfoBean bean) {
if (!isAttach()) {
return;
}
view.getVersionInfoSuccess(bean);
}
@Override
public void onError() {
if (!isAttach()) {
return;
}
view.getVersionInfoError();
}
});
String url = "XXXXXXXX";//检查更新的地址,这里是网络加载
model.getRemoteData(url, json.toString());
}
}
说明一下,每个业务方法都需要调用一下isAttach方法来判断一下View是否绑定了Presenter,因为View层只要没有绑定,业务层就不需要对View层进行数据回调,还有另一种情况,在View层发起数据请求后随即destory,而Presenter层因为网络延迟还没有返回数据,所以等到数据回调的时候会发生因为View层已经解绑导致view对象为空的错误,所以数据回调后同样也需要用isAttach判断一下。另外,我是采用Gson进行json的解析,当然也可以用其它的工具,Gson组装了请求数据提供给Model层使用。设置了回调把数据返回给View层,所以View层应该提供这些回调方法:
public interface HomeView extends BaseView {
void onHttpsuccess(HomeBean bean);
void onHttpfail(int status, String message);
void onHttpError();
void getVersionInfoFail(int status, String message);
void getVersionInfoSuccess(GetVersionInfoBean bean);
void getVersionInfoError();
}
Model层的具体实现:
class HomePresenterModel implements BaseModel {
public static final int GETDATE = 1;
private Httplistener httplistener;
@Override
public void getRemoteData(String url, String content) {
new Httphelper(httplistener, GetVersionInfoBean.class).getAsynHttp(url, content);
}
@Override
public void getRemoteData(String url, String content, int type) {
switch (type) {
case GETDATE:
new Httphelper(httplistener, HomeBean.class).getAsynHttp(url, content);
break;
}
}
@Override
public Object getLocalData() {
return null;
}
@Override
public void setHttplistner(Httplistener httplistener) {
this.httplistener = httplistener;
}
}
model层要做的事情就是获取数据,可以是本地的也可以是网络上的,我封装了一个httphelper来处理,封装了Javabean来保存Gson解析出的数据,方便View层使用。
MVP的具体实现有了,现在要在android的ui上使用:
public class HomeActivity extends BaseActivity<HomePresenter> implements HomeView {
@Override
public int getLayoutId() {
return R.layout.xxxx;//绑定xml
}
@Override
public void initView() {
//view初始化
}
@Override
public void getVersionInfoFail(int status, String message) {
}
@Override
public void getVersionInfoSuccess(GetVersionInfoBean bean) {
//对返回的数据进行处理
}
@Override
public void getVersionInfoError() {
}
@Override
public void onHttpsuccess(HomeBean bean) {
//请求首页数据成功后的处理
}
@Override
public void onHttpfail(int status, String message) {
hideloading();
new MsgAlert().show(message);
}
@Override
public void onHttpError() {
hideloading();
}
}
首页Activity具体实现了View接口,这样数据和UI就实现了分离,做UI的程序员可以专注于UI的实现,尤其是在开发一些非常复杂的页面的时候,可以多个程序员开发不同的层次,使得同一个复杂功能可以并行开发,大大减少开发周期。
需要说明的是,这个例子中的presenter层的业务都是在presenter的start中进行的,在实际的开发中,View层当然也可以直接调用presenter的业务方法来请求数据,这也是MVP架构所希望的:View层、Model层只和Presenter层交互,VM不直接进行交互。