手记

Android架构篇--通过泛型和抽象,简化MVP框架

摘要: 在

摘要: 在上一篇文章中,我们学习了关于MVP模式的简单使用方法。相信很多(实际上只有几个--)读者看完文章后都会产生一个疑问:按照这个方法使用MVP模式进行开发的话,代码量是不是会大大增加?答案是对的,如果这样简单地使用MVP模式的话代码的确会大大增加。每个网络请求(数据库)都会由原来的MV模式的一个Model类文件变成4个类文件,分别是:IModel、Model、IPresenter、Presenter。所以简单的使用MVP模式进行开发带来的优点是逻辑清晰、代码间的耦合度大大下降,但是会带来一个很严重的问题就是代码量增加。作为一个懒人,要写这么多功能类似的代码实在是一种煎熬。所以在这一篇我会来探讨下如何优化MVP模式的结构来解决一个问题。

使用MVP模式开发时遇到的难题

如果你使用过MVP模式进行过实际开发的话(很多程序员在使用MVP模式进行开发的时候,都是使用上一篇文章中所介绍的方式)应该会发现,事实上每个功能块的代码都是类似的,只是细节上会有所不同。作为一个优秀的程序员,在这个时候,一般会把一些相同的功能块抽象成一个基类。例如显示通知View层显示( 隐藏 )进度条、网络错误处理、服务器拒绝请求返回的错误处理等,笔者在刚使用MVP模式时也是这样做的。但是随着项目的进行,很快就会发现,类文件量、代码量仍然会增加得很快,随之带来的问题是项目的管理会变得越来越复杂(这也和笔者的项目结构有关)。所以我认为,在使用MVP模式的时候,能解决这个问题的话,会大大提高我们的工作效率。

分析简单的网络请求模块

在项目的时候,使用MVP模式最多的地方应该就是网络请求了。我们可以分析下世纪开发中简单的网络请求M、V、P三层各有什么特点。

Model层:

这一层主要就是负责向数据源( 一般为服务器/数据库,下同)发起获取数据请求,并且把获取的数据或者错误信息回调给持有的Presenter。除了发起请求功能外,一般我们还需要一个取消请求的方法。
所以Model层主要的功能是:

  1. 向数据源发起请求;

  2. 取消该请求;

  3. 通知Presenter处理结果。

Presenter层:

这层主要负责通知Model层向服务器发起请求并接收Model层回调的数据或者错误信息,并且这一层还要负责把数据或者错误信息处理后回调到View层,由View层负责显示。
一般在网络请求中的错误信息分为两种,一种是网络设备的网络状态错误,无法发送请求;另外一种是服务器拒绝了这次请求。所以Presenter的主要功能是:

  1. 通知Model层向服务器发起请求;

  2. 接收Model层返回的数据(服务器可能返回数据或者拒绝服务信息);

  3. 接收Model层返回的网络错误信息;

  4. 通知Model层取消这次请求;

  5. 通知View接收处理后的数据。

View层:

上一篇文章介绍过,在MVP模式中,View层是一个接口。它的首要任务是把Presenter处理后的数据传到具体的原生控件中显示,并且控制是否显示加载进度条。
所以View层的主要功能是:

  1. 显示/隐藏进度条。

  2. 接收Presenter处理后的正确数据。

  3. 接收Presenter返回的网络错误信息。

  4. 接收Presenter返回的服务器拒绝服务信息。

一个普通的网络请求的M、V、P各层主要负责的功能大致就是这么多。那么我们如何通过上述的特点简化MVP的结构呢?下面我们来分析一下。

如何优化MVP模式的结构

优化Model层

根据上面的分析我们知道了Model层主要承担的功能。在这里为了简便,以Volley网络框架处理网络请求。而关于Volley框架的使用方法读者可以上网查询,在这里不作过多的描述。

我们知道,发起网络请求一般需要传入请求地址、请求方式、请求参数这三个参数。其中请求参数是可为空的,下面我们来看看用Volley框架实现的一个登陆的代码。

123456789101112131415161718192021222324252627282930
@Override  public void login(String userName, String password){LoginRequest loginRequest = new LoginRequest();loginRequest.userName = userName;loginRequest.password = password;myJsonRequest = new MyJsonRequest(Request.Method.POST, ServerConst.getServerUrl(ApiInterface.LOGIN),loginRequest.toMap(), new Response.Listener<JSONObject>() {@Override  public void onResponse(JSONObject response) {try {LoginResponse loginResponse = new LoginResponse(context);LogHelper.d(getClass(), "onResponse: "+response);loginResponse.fromJson(response);loginPresenter.loginSucceed(loginResponse);} catch (JSONException e) {e.printStackTrace();}}}, new Response.ErrorListener() {@Override  public void onErrorResponse(VolleyError error) {loginPresenter.volleyError(ErrorCode.VOLLEYERROR, ErrorDesc.VOLLEYERRORDESC,ApiInterface.LOGIN);}});SingletonRequestQueue.getInstance(MedicalAPP.getInstance()).addRequestQueue(myJsonRequest);}

上述代码中,笔者自定义了一个Volley的Request。这个Request的作用是,向服务器提交Map类型的参数,然后服务器返回的类型为Json类型的,读者可以根据自己的需要进行定义。关于LoginParams.toMap()方法读者可以参考:Android(Java)Bean自动转为map的方法这篇文章,这个方法的作用时自动把参数实体类转化为Map类型的数据。
接下来要放大招了。如果按照上面的方法来实现Model层的话的话,那么我们每个请求都需要有一个Model类文件,但是Model实际上负责的功能是非常简单的。所以有没有可能把所有关于网络请求的Model全部干掉,封装成一个BaseModel呢?答案是可以的,下面笔者来分析下具体的方案。

我们先来看看IBaseModel接口的代码

123456789101112
public interface IBaseModel {void sendRequestToServer();void setApiInterface(String apiInterface);void setMethod(int method);void cancelRequest();}

对请求方式与接口地址的处理

123456789101112
private int method = Request.Method.GET;       //请求方式,默认get  private String apiInterface;@Override  public void setMethod(int method ){  this.method = method;}@Override  public void setApiInterface(String apiInterface){  this.apiInterface = apiInterface;}

这个处理方式十分简单,在Presenter的构造方法中调用这两个方法设置下这两个变量就可以了。如果为Get方式的话,可以不作处理。

对发起请求方法的处理

12345678910111213141516171819
@Override  public void sendRequestToServer() {request = new MyVolleyRequest(method, ServerManager.getServerUrl(apiInterface), basePresenter.getParams(), new Response.Listener<JSONObject>() {@Override  public void onResponse(JSONObject response) {basePresenter.accessSucceed(response);}}, new Response.ErrorListener() {@Override  public void onErrorResponse(VolleyError error) {basePresenter.volleyError(VOLLEYERROR, VOLLEYERRORDESC,apiInterface);}});SingletonRequestQueue.getInstance(MyApp.getInstance()).addRequestQueue(request);}

我们可以看到,这个BaseModel中的发起请求的方法和上面的LoginModel的很类似,其中的主要区别有五点:

  1. 接口的地址和请求的方式为动态设置的;

  2. sendRequestToServer方法不带任何参数;

  3. 把参数请求交给BasePresenter层处理;

  4. 把服务器返回的数据交给BasePresenter层处理;

  5. 把网络连接错误交给BasePresenter层处理。

通过上述方法,我们已经把所有网络请求相关的Model封装成一个BaseModel了。其中需要重点处理的问题只有一个,那就是请求参数。因为在Model中请求参数是不确定的,请求参数的具体处理方法会在Presenter中介绍。

优化View层

在这里笔者把一些基本的处理都封装了一个IBaseView接口,下面看看IBaseView接口的方法。

12345678
public interface IBaseView {void showProcess(final boolean show);void showVolleyError(int errorCode, String errorDesc, String ApiInterface);void showServerError(int errorCode, String errorDesc);}

很简单,这里就不作更多解释了。

优化Presenter层

在MVP模式中,Presenter层是逻辑控制层。我们在优化这一层的时候,不能像Model层这样,把其它全部的Presenter都封装好。这里的原因有几点:

  1. 如果把Presenter封装成BasePresenter层的话就会失去MVP模式的优势,这样会导致部分业务逻辑必须在View层中处理,和MVP模式的思想冲突。

  2. Presenter层作为逻辑层,必须要处理View层的交互逻辑和Model层返回的数据。

  3. 如果把Presenter封装成一个Base类的话,那么我们实际上时没办法复用同一个功能模块的,每次都需要在View层中处理。

那么为什么Model层可以做这样的处理呢?在这里我的理解是,Model层的作用只是通知服务器获取数据,并且把产生的错误或回传的数据发送给Presenter层。在这里Model层只是充当于一个请求的发起者和数据的转发者,所有的Model功能都是类似的,所以在这里我把Model层封装成了一个BaseModel类。

IBasePresenter接口

具体代码:

1234567891011121314
public interface IBasePresenter<P> {void accessServer(P params);void accessSucceed(JSONObject response);Map getParams();IBaseModel getModel();void cancelRequest();void volleyError(int errorCode, String errorDesc, String ApiInterface);}

方法说明:

  1. void accessServer(P params):这个方法主要是在View层中调用,通过该方法通知Model层向服务器发起请求,params可为空。

  2. void accessSucceed(JSONObject response):这个方法在Model层中调用,通过该方法把服务器返回的数据传递给Presenter层处理。

  3. Map getParams():该方法在Model层中调用,Model层通过该方法获取Presenter处理好的参数。

  4. IBaseModel getModel():该方法主要在子类中调用,用于获取Model对象。

  5. void cancelRequest(): 该方法在View层中调用,作用时通知Model层取消请求。

  6. void volleyError(int errorCode, String errorDesc, String ApiInterface):当产生网络错误时,Model层就会调用该方法。

BasePresenter实现

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
/**  * @ClassName: BasePresenter* @author create by Tang* @date date 16/9/29 下午2:14* @Description: 普通presenter基类* @Params: 提交参数类* @Data: 服务器返回数据*/public abstract class BasePresenter<Params extends BaseParams,Data>  implements IBasePresenter<Params> {/**  * @Method: serverResponse(Data data)* @author create by Tang* @date 16/10/15 上午1:19* @Description:* 如果返回的data为null,则代表该接口没有数据返回* 有数据返回必须调用在子类中调用BasePresenter(IBaseView baseView,Class<Data> clazz)构造方法*/public abstract void serverResponse(Data data);private IBaseModel baseModel;private IBaseView baseView;private Params params;private Class<Data> clazz;/**  * @Method: BasePresenter* @author create by Tang* @date date 16/10/14 下午5:32* @Description: BaseResponse中Data为空使用该构造方法*/public BasePresenter(IBaseView baseView){this.baseView = baseView;this.baseModel = new BaseVolleyModel(this) ;}/**  * @Method: BasePresenter* @author create by Tang* @date date 16/10/14 下午5:32* @Description: BaseResponse中Data不为空使用该构造方法* @param clazz 数据的类型*/public BasePresenter(IBaseView baseView,Class<Data> clazz){this.baseView = baseView;this.baseModel = new BaseVolleyModel(this);this.clazz = clazz;}@Override  public Map getParams(){if (params != null){return params.toMap();}else {return null;}}@Override  public IBaseModel getModel(){return baseModel;}@Override  public void accessServer(Params params) {this.params = params;baseView.showProcess(true);baseModel.sendRequestToServer();}@Override  public void accessSucceed(JSONObject response) {baseView.showProcess(false);Gson gson = new Gson();BaseResponse mResponse = gson.fromJson(String.valueOf(response),new TypeToken<BaseResponse>() {}.getType());/**  * 在实际设计系统的时候,通过状态码来判断服务器是否正确响应* 如果响应错误,可以在这里直接通知view层错误情况* 以下为根据百度api的数据格式设计的回调处理* errorNum = 0 时,响应成功*/if (mResponse.errNum == 0){if (mResponse.data == null || clazz == null){serverResponse(null);}else {serverResponse(gson.fromJson(mResponse.data,clazz));}}else {baseView.showServerError(mResponse.errNum,mResponse.errMsg);}}@Override  public void volleyError(int errorCode, String errorDesc, String apiInterface) {baseView.showVolleyError(errorCode,errorDesc,apiInterface);}@Override  public void cancelRequest() {baseModel.cancelRequest();}}

通过上面代码可以看出BasePresenter<Params extends BaseParams,Data>是一个使用了泛型抽象类,泛型P: 发送的参数类。D: 处理后的数据,D在这里一般对应为BaseResponse中的data。通过BasePresenter的代码可以看出,BasePresenter处理View层和Model层中的大部分逻辑。我们只需要在子类中实现 void serverResponse(D data)方法就可以了。
这里我们要注意下accessSucceed(JSONObject response) 方法。在这个方法中,笔者希望实现的功能是能够如果服务器返回错误信息,BasePresenter能够直接通知View层,如果返回正确信息则返回解析后的数据然后在子类中处理,实现这个功能需要设计一个BaseResponse类。

通过使用BaseResponse实现数据自动解析

下面是笔者一开始设计的BaseResponse\类的代码:\>

12345678910111213141516171819
public class BaseResponse<Data>{@SerializedName(value = "errNum",alternate = {"status","errno"})  public int errNum;@SerializedName(value = "errMsg",alternate = {"msg"})  public String errMsg;/@author create by Tang@date date 16/10/12 下午4:18  @Description: 兼容不同的情况@restData: 百度api,天气data  /@SerializedName(value = "data",alternate = {"retData","categories","shop"})  public Data data;}

可以看到,这里的Data是一个泛型,但是在实际使用的过程中存一个很严重的问题,就是泛型擦除的问题。如果采用这种设计,我们使用Gson解析数据的方法是:

12
BaseResponse mResponse = gson.fromJson(String.valueOf(response)  ,new TypeToken<BaseResponse<Data>>() {}.getType());

如果BaseResponse采用上面的设计的话,在编译的过程中Data泛型会被擦除。这样就导致一个后果就是Gson无法解析Data中的数据。Data中的数据最终会被解析成LinkedTreeMap类型,无法解析成我们需要的Data类型。所以我们需要采用其它的方法实现自动解析返回数据的功能。

新增一个构造方法,这个构造方法增加了一个Class类型参数。

12345
public BasePresenter(IBaseView baseView,Class<Data> clazz){this.baseView = baseView;this.baseModel = new BaseVolleyModel(this);this.clazz = clazz;}

在方法中accessSucceed(JSONObject response)进行解析
accessSucceed(JSONObject response)方法的实现如下:

1234567891011121314151617181920212223242526
public void accessSucceed(JSONObject response) {baseView.showProcess(false);Gson gson = new Gson();BaseResponse<Data> mResponse;if(clazz != null){ParameterizedType parameterized = ClassTypeUtil.type(BaseResponse.class, clazz);Type type = $Gson$Types.canonicalize(parameterized);mResponse = gson.fromJson(String.valueOf(response), type);}else {mResponse = gson.fromJson(String.valueOf(response),BaseResponse.class);}/*** 在实际设计系统的时候,通过状态码来判断服务器是否正确响应* 如果响应错误,可以在这里直接通知view层错误情况* 以下为根据百度api的数据格式设计的回调处理* errorNum = 0 时,响应成功*/if (mResponse.errNum == 0) {serverResponse(mResponse.data);} else {baseView.showServerError(mResponse.errNum, mResponse.errMsg);}}

serverResponse的定义为:public abstract void serverResponse(Data data);

可以看到,在这里我使用了ClassTypeUtil.type()解决BaseResponse 的泛型擦除问题。这个方法的具体实现是:

12345678910111213141516171819
public static ParameterizedType type(final Class raw, final Type... args){return new ParameterizedType() {@Overridepublic Type[] getActualTypeArguments() {return args;}@Overridepublic Type getRawType() {return raw;}@Overridepublic Type getOwnerType() {return null;}};}

BaseResponse中Data的类型是clazz,我们通过ParameterizedType type(final Class raw, final Type... args)方法可以获取到BaseResponse 的ParameterizedType对象,然后再通过$Gson$Types.canonicalize(parameterized)方法获取实际BaseResponse的type对象,通过这个方法获取BaseResponse类的实际type。

其实这和我们使用gson.fromJson(String.valueOf(response) ,new TypeToken<BaseResponse<Data>>() {}.getType());中的new TypeToken<BaseResponse<Data>>() {}.getType()方法获取类的type是类似的,由于这里的Data是不确定的,所以我们需要自己实现一个获取class type的方法。如果大家有更好的方法,欢迎一起讨论哦。

通过上面的方法,我们实现了在serverResponse(Data data)自动处理服务器的错误或自动解析服务器返回的数据

小结

限于篇幅,今天就先介绍到这里。这里先对以后会增加的内容进行预告,在后面几篇文章中,笔者会提供上面框架的使用实例、如何通过拓展BasePresenter模块增加功能等。最后再下一篇中,笔者会放出这个系列的源码,这份源码会随着这个系列的文章的进展而完善,读者可以在我的github上关注下这个项目的动态。

原文链接:http://www.apkbus.com/blog-866962-68063.html

0人推荐
随时随地看视频
慕课网APP