您的位置:首页 > 理论基础 > 计算机网络

Android通用网络请求解析框架.7(同步请求,公共部分)

2017-01-06 18:21 615 查看
笔者将通过11篇博客对个人开源框架进行讲解,本篇为第7篇,讲解同步请求,公共部分。

开源库github地址 https://github.com/qq296216078/Android-Universial-NetFrame

如果有兴趣一起讨论本框架的内容,请加QQ群:271335749

在第一篇中,需求讲解的时候,我们希望得到数据时,已经回到主线程
NetHelper.get("url", new NetSingleBeanListener<NetUserBean>() {
@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {
// 这里是ui线程
}

@Override
protected void onSuccess(NetUserBean userBean) {
// 这里是ui线程
}
});


这种情况,也是最常见的情况,就是我们通常所说的异步请求。
但是在实际项目中,有时候不需要回到主线程,比如下面这种情况
private void startRequest() {
new Thread() {
@Override
public void run() {
request1();
request2();
try {
Thread.sleep(60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}


开发者需要一些在后台定时跑的任务,固定一分钟进行几个接口的请求,这些请求在返回时不需要在主线程中有操作,或者开发者想自己用Handler来返回到主线程。

这个时候,也就是通常所说的同步请求,如果继续使用异步请求的代码,是可能出现问题的。
因为异步请求的代码中有些部分是不需要的

1.不需要创建AsyncTask
2.不需要创建Handler

第1点,开发者发起同步请求的时候,必须是自己已经处在子线程中,大家都知道ui线程中进行网络请求是会出错的。

第2点,在第1点的前提下创建的Handler,原先实现的代码是会报错的,通常会是这个错
Can't create handler inside thread that has not called Looper.prepare()


因为在非ui线程中创建的Handler,并不能将消息传送到ui线程,需要加入一些Looper相关的代码才可以。而同步请求不需要将结果返回到ui线程,所以这里的代码也是不需要的。

因为第2点的问题,所以在子线程中进行同步请求时,直接使用异步请求代码,会出现异常。那该怎么办呢?

这里有两个解决方案:
1.在之前的代码的许多地方加上一些参数,在调用的时候逐层传递进去,框架内层根据参数判断要进行哪些操作
2.重新模仿之前的代码,实现一套同步请求的代码

笔者采用了第2种办法。原因也有两点:
1.如果采用第1种办法,框架中的代码将会变得更复杂,提供给开发者使用的入口也会变得复杂。这导致了在编程和使用上都变麻烦了。
2.如果采用第2种办法,并不是所有代码都要重写,只要关键部分进行修改就可以了,与线程操作无关的代码,可以不要修改,所以实现起来也不麻烦。

在这里,我们先不急着实现同步请求的内步代码,我们先假设已经实现了相应代码,直接开始使用。
那么之前的需求,实现起来会是这样的
private void startRequest() {
new Thread() {
@Override
public void run() {
NetHelper.get("url1", new NetStringListener() {
@Override
protected void onSuccess(String string) {

}

@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {

}
}
NetHelper.get("url2", new NetStringListener() {
@Override
protected void onSuccess(String string) {

}

@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {

}
}
}.start();
}


url1和url2的请求是按顺序下来的,也就是说url1必须是请求结束了,要么回调了onSuccess,要么回调了onError,才会开始url2请求。这看起来非常的奇怪,同步执行的代码,看不出来他们的顺序性。再看看这样的需求
private void startRequest() {
new Thread() {
@Override
public void run() {
if (request1()) {
request2();
}
}
}.start();
}


url2的请求需要依赖url1请求的结果。如果用NetHelper实现,代码看起来就非常的奇怪了
private void startRequest() {
new Thread() {
@Override
public void run() {
NetHelper.get("url1", new NetStringListener() {
@Override
protected void onSuccess(String string) {
NetHelper.get("url2", new NetStringListener() {
@Override
protected void onSuccess(String string) {

}

@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {

}
});
}

@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {

}
});
}
}.start();
}


需要嵌套多层,同步执行的代码,因为回调的原因,让开发者看不明白执行顺序。而且回调方法里面的参数,并不能直接给下面的代码使用,必须要用到全局变量才行,回调方法中如果要使用外部的变量,也需要全局变量,或者定义为final的局部变量。明明是按顺序执行的代码,但这代码结构却让人感到非常蛋疼。对于按顺序同步执行的代码,笔者不能忍受用这种回调的方式来实现。

有没有好的办法解决呢?显然是有的,就像上面的代码一样,request1()的请求是有返回值的,而不是通过回调来返回。
那么,如果NetHelper.get方法具有返回值,就好办了,我们就可以这样完成需求
private void startRequest() {
new Thread() {
@Override
public void run() {
boolean ret = NetHelper.get("url1", new NetStringListener() {
@Override
protected void onSuccess(String string) {

}

@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {

}
});

if (ret) {
NetHelper.get("url2", new NetStringListener() {
@Override
protected void onSuccess(String string) {

}

@Override
protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {

}
});
}
}
}.start();
}


看起来好像是可以,我们甚至可以用NetTempListener,不需要实现回调,让代码更简洁
private void startRequest() {
new Thread() {
@Override
public void run() {
boolean ret = NetHelper.get("url1", new new NetTempListener());

if (ret) {
NetHelper.get("url2", new NetTempListener());
}
}
}.start();
}

可是问题又来了,这里的get方法只能返回boolean类型,如果需求是根据url1请求的结果解析后的Bean的某一个属性来判断是否要进行url2请求,那么又要写一个不同返回值的get方法,而且内部逻辑又麻烦了许多。需求都是不断变化的,返回值要根据需求做不同的变化。之前我们做异步请求,将不同的返回结果进行归类,成功和失败时的返回值有不同的回调。而现在一个方法只有一个返回值,我们要怎样在不改代码的情况下,让这个返回值适合各种情况呢?

可能有些人想到了用Map,对,Map是可以,但是Map里面的key值是哪些,取出来后每个value都要强转,确实够麻烦。

有现成的东西为什么不用呢?对了,就是NetRetBean,我们让同步请求时,就返回NetRetBean。
返回NetRetBean的好处我想大家能够明白的,因为这个类中封装了请求成功和失败时所有情况的数据,包括解析后的,还有自定义解析的数据。

拿到NetRetBean返回值后,根据CallbackCode判断请求是否成功,
如果成功,NetRetBean中的外层数据也都不为null
如果成功,NetRetBean的内层数据也不为null
如果失败,可以根据CallbackCode进行判断是哪种原因导致的

在使用上,应该是这样的
private void startRequest() {
new Thread() {
@Override
public void run() {
NetRetBean netRetBean = NetHelper.getSync("url", new NetTempListener());
CallbackCode callbackCode = netRetBean.getCallbackCode();
switch (callbackCode) {
case CODE_SUCCESS_REQUEST:
String string = (String) netRetBean.getServerObject();
System.out.println(string);
break;
case CODE_ERROR_HTTP_NOT_200:
case CODE_ERROR_REQUEST_EXP:
case CODE_ERROR_SERVER_DATA_ERROR:
case CODE_ERROR_JSON_EXP:
case CODE_ERROR_UNKNOWN:
default:
System.out.println(netRetBean.toString());
break;
}
}
}.start();
}


这里,我们为NetHelper添加了getSync方法,表明使用同步请求。
做的是同步请求,所以强转操作必须开发者自己来进行,因为serverObject是Object类型的。

之前笔者也想过将NetRetBean中serverObject的类型写成泛型,根据传入不同的监听器和泛型,自动转成相应的类型。但是后来因为自定义解析器时返回的数据可能不只有一个,可能是非常多个的,最后还是得用到NetRetBean中的serverObjectMap,所以放弃了将serverObject类型写成泛型的写法。

那么现在,之前的需求,根据url1请求的结果来判断是否进行url2请求,也就变成这样了
private void startRequest() {
new Thread() {
@Override
public void run() {
NetRetBean netRetBean = NetHelper.getSync("url1", new NetTempListener());
if (netRetBean.isSuccess()) {
NetHelper.getSync("url2", new NetTempListener());
}
}
}.start();
}


使用上代码好像没有比返回boolean简洁多少,但是NetRetBean好在可以通用,基本上所有返回结果的需求都包含了。

内部代码要如何实现呢?既然要返回值,那就应该逐层进行返回。看看代码,或许就更清楚了。

先从NetHelper入手,异步请求的代码利用NetExcutor进行get或者post操作,然后在返回时回调NetListener。
在这里,我们希望有数据返回,那么,直接在get或者post方法处进行返回,显然是非常合理的
public class NetHelper {
public static NetRetBean getSync(String url, boolean isWaitForToken, NetListener netListener) {
NetExcutor netExcutor = new NetExcutor();
netExcutor.setUrl(url);
netExcutor.setWaitForToken(isWaitForToken);
netExcutor.setNetListener(netListener);
return netExcutor.get();
}
}


对比之前的代码,仅仅是让get方法具有返回值而已。

接下来我们实现内部代码。根据之前的分析,在涉及线程相关的代码的地方,不再使用之前的类,而是重新写一套代码。

而NetExcutor类,里面就有AsyncTask,那就把原来的AsyncTask给去掉,换上另一个名字SyncNetExcutor
package com.chenjian.net.core.sync;

import com.chenjian.net.bean.NetRetBean;
import com.chenjian.net.core.common.RequestType;
import com.chenjian.net.request.RequestUtil;
import com.chenjian.net.token.TokenUtil;

/**
* 网络请求处理核心类
* <p>
* 作者: ChenJian
* 时间: 2016.12.13 17:42
*/

public class SyncNetExcutor {

/**
* 请求url
*/
private String mUrl;

/**
* 请求类型
*/
private RequestType mRequestType;

/**
* post请求时的参数
*/
private String mParams;

/**
* 是否先等待token请求成功
*/
private boolean isWaitForToken;

/**
* 请求后的回调
*/
private SyncNetListener mSyncNetListener;

public void setUrl(String url) {
this.mUrl = url;
}

private void setRequestType(RequestType requestType) {
this.mRequestType = requestType;
}

public void setParams(String params) {
this.mParams = params;
}

public void setWaitForToken(boolean waitForToken) {
isWaitForToken = waitForToken;
}

public void setSyncNetListener(SyncNetListener syncNetListener) {
this.mSyncNetListener = syncNetListener;
}

public NetRetBean get() {
setRequestType(RequestType.REQUEST_TYPE_GET);
return startRequest();
}

public NetRetBean post() {
setRequestType(RequestType.REQUEST_TYPE_POST);
return startRequest();
}

/**
* 同步请求,直接返回
*
* @return 返回 NetRetBean
*/
private NetRetBean startRequest() {
if (isWaitForToken) {
TokenUtil.waitToken();
}
try {
String result = request();
return mSyncNetListener.sendSuccess(result);
} catch (Exception e) {
e.printStackTrace();
return mSyncNetListener.sendError(e);
}
}

public String getString() {
setRequestType(RequestType.REQUEST_TYPE_GET);
return startRequestString();
}

public String postString() {
setRequestType(RequestType.REQUEST_TYPE_POST);
return startRequestString();
}

/**
* 同步请求,直接返回
*
* @return 返回 String
*/
private String startRequestString() {
if (isWaitForToken) {
TokenUtil.waitToken();
}
try {
return request();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

private String request() throws Exception {
String result = null;
switch (mRequestType) {
case REQUEST_TYPE_GET:
result = RequestUtil.getRequest(mUrl);
break;

case REQUEST_TYPE_POST:
result = RequestUtil.postRequest(mUrl, mParams);
break;

default:
break;
}
return result;
}

}


与NetExcutor的代码相比,有以下几处修改:
1.最大的变化就是去掉了AsyncTask,而在get和post两个入口不创建线程直接进行request请求。
2.NetListener换成了SyncNetListener。

3.get和post方法变成有返回值的了,返回值是NetRetBean,其返回值是从SyncNetListener处得来的。
4.增加了getString和postString方法,他返回的是String类型,是网络请求的直接返回结果。

先来看看SyncNetListener
package com.chenjian.net.core.sync;

import com.chenjian.net.bean.NetRetBean;

/**
* 网络请求回调核心类
* <p>
* 作者: ChenJian
* 时间: 2016.12.13 17:42
*/

public interface SyncNetListener {

/**
* http请求,数据解密部分,成功
*
* @param result result
* @return NetRetBean
*/
NetRetBean sendSuccess(String result);

/**
* http请求,数据解密部分,失败
*
* @param e e
* @return NetRetBean
*/
NetRetBean sendError(Exception e);
}


只是把之前的返回类型void改成NetRetBean,因为需要返回值嘛

因为内部代码的修改,外部调用也要做修改才行,我们接着来看看NetHelper入口吧
package com.chenjian.net.helper;

import com.chenjian.net.bean.NetRetBean;
import com.chenjian.net.core.async.NetExcutor;
import com.chenjian.net.core.async.NetListener;
import com.chenjian.net.core.sync.SyncNetExcutor;
import com.chenjian.net.core.sync.SyncNetListener;
import com.chenjian.net.data.NetConstants;

/**
* 网络请求工具类
* <p>
* 作者: ChenJian
* 时间: 2016.12.14 11:24
*/

public class NetHelper {

/**
* get同步请求
*
* @param url             url
* @param syncNetListener 监听器
* @return NetRetBean
*/
public static NetRetBean getSync(String url, SyncNetListener syncNetListener) {
return getSync(url, NetConstants.defaultWaitForToken, syncNetListener);
}

/**
* get同步请求
*
* @param url             url
* @param isWaitForToken  是否等待token请求成功
* @param syncNetListener 监听器
* @return NetRetBean
*/
public static NetRetBean getSync(String url, boolean isWaitForToken, SyncNetListener syncNetListener) {
SyncNetExcutor syncNetExcutor = new SyncNetExcutor();
syncNetExcutor.setUrl(url);
syncNetExcutor.setWaitForToken(isWaitForToken);
syncNetExcutor.setSyncNetListener(syncNetListener);
return syncNetExcutor.get();
}

/**
* post同步请求
*
* @param url             url
* @param params          参数
* @param syncNetListener 监听器
* @return NetRetBean
*/
public static NetRetBean postSync(String url, String params, SyncNetListener syncNetListener) {
return postSync(url, params, NetConstants.defaultWaitForToken, syncNetListener);
}

/**
* post同步请求
*
* @param url             url
* @param params          参数
* @param isWaitForToken  是否等待token请求成功
* @param syncNetListener 监听器
* @return NetRetBean
*/
public static NetRetBean postSync(String url, String params, boolean isWaitForToken, SyncNetListener syncNetListener) {
SyncNetExcutor syncNetExcutor = new SyncNetExcutor();
syncNetExcutor.setUrl(url);
syncNetExcutor.setParams(params);
syncNetExcutor.setWaitForToken(isWaitForToken);
syncNetExcutor.setSyncNetListener(syncNetListener);
return syncNetExcutor.post();
}

/**
* get同步请求,不设置监听器,直接返回数据给调用者
*
* @param url url
* @return String
*/
public static String getStringSync(String url) {
return getStringSync(url, NetConstants.defaultWaitForToken);
}

/**
* get同步请求,不设置监听器,直接返回数据给调用者
*
* @param url            url
* @param isWaitForToken 是否等待token请求成功
* @return String
*/
public static String getStringSync(String url, boolean isWaitForToken) {
SyncNetExcutor syncNetExcutor = new SyncNetExcutor();
syncNetExcutor.setUrl(url);
syncNetExcutor.setWaitForToken(isWaitForToken);
return syncNetExcutor.getString();
}

/**
* post同步请求,不设置监听器,直接返回数据给调用者
*
* @param url    url
* @param params 参数
* @return String
*/
public static String postStringSync(String url, String params) {
return postStringSync(url, params, NetConstants.defaultWaitForToken);
}

/**
* post同步请求,不设置监听器,直接返回数据给调用者
*
* @param url            url
* @param params         参数
* @param isWaitForToken 是否等待token请求成功
* @return String
*/
public static String postStringSync(String url, String params, boolean isWaitForToken) {
SyncNetExcutor syncNetExcutor = new SyncNetExcutor();
syncNetExcutor.setUrl(url);
syncNetExcutor.setParams(params);
syncNetExcutor.setWaitForToken(isWaitForToken);
return syncNetExcutor.postString();
}
}


使用起来也就是调用SyncNetExcutor,然后再把返回值给开发者。
getSync和postSync返回类型是NetRetBean,是网络请求解析后的结果。
getStringSync和postStringSync返回的是String类型,是网络请求的直接返回结果。

如果你直接使用getStringSync或者postStringSync,是非常简单的
private void syncGetString() {
new Thread() {
@Override
public void run() {
String getString = NetHelper.getStringSync("url");
System.out.println(getString);
String postString = NetHelper.getStringSync("url");
System.out.println(postString);
}
}.start();
}


这两个方法只是简单的返回网络请求的数据,并没有对数据进行任何解析,而且返回的string是可能为null的。

如果我们直接调用NetHelper.getSync,会是这样的
private void startRequest() {
new Thread() {
@Override
public void run() {
NetRetBean netRetBean = NetHelper.getSync("url", new SyncNetListener() {
@Override
public NetRetBean sendSuccess(String result) {
return null;
}

@Override
public NetRetBean sendError(Exception e) {
return null;
}
});
}
}.start();
}


这里的回调并不是最后执行的地方。我们看看SyncNetExcutor中的部分代码
public NetRetBean get() {
setRequestType(RequestType.REQUEST_TYPE_GET);
return startRequest();
}

public NetRetBean post() {
setRequestType(RequestType.REQUEST_TYPE_POST);
return startRequest();
}

/**
* 同步请求,直接返回
*
* @return 返回 NetRetBean
*/
private NetRetBean startRequest() {
if (isWaitForToken) {
TokenUtil.waitToken();
}
try {
String result = request();
return mSyncNetListener.sendSuccess(result);
} catch (Exception e) {
e.printStackTrace();
return mSyncNetListener.sendError(e);
}
}


调用监听器来实现成功和失败时候的处理,而且都要返回一个NetRetBean,然后再把这个NetRetBean返回给上层。
所以最后执行的地方是getSync方法的返回,然后再往下执行,只是请求的过程当中调用了这两个回调中的一个。

从代码结构上,还是不能容忍的,而且,还他还没真正的去解析,还要开发者自己去完成解析和错误处理。
如果在回调的解析方法中直接返回null,那么返回的NetRetBean中的众多属性还是为null。
所以仅仅做到这里,同步请求的代码实现还没完成。解析和错误处理的代码,要在框架内部进行处理才合理。

回忆一下,异步请求时外层解析和错误处理是在NetHandleListener类里面,
而且,同步请求除了AsyncTask外,还有另一个与线程相关的代码,就是Handler,也在NetHandleListener中,
那么NetHandleListener也需要改写。

NetHandleListener继承自NetListener,所以我们实现一个SyncNetHandleListener,继承自SyncNetListener
package com.chenjian.net.listener.sync;

import com.chenjian.net.bean.NetRetBean;
import com.chenjian.net.core.sync.SyncNetListener;
import com.chenjian.net.exp.RequestErrorException;
import com.chenjian.net.exp.RespondErrorException;
import com.chenjian.net.listener.common.CallbackCode;

import org.json.JSONException;
import org.json.JSONObject;

/**
* 公用网络逻辑,核心监听器。自定义监听器一般继承这个类
* <p>
* 作者: ChenJian
* 时间: 2016.12.15 11:12
*/

abstract public class SyncNetHandleListener implements SyncNetListener {

/**
* 处理NetRetBean并返回。是一个中转站。本类和其子类都可以调用这个方法
*
* @param netRetBean netRetBean
* @return netRetBean
*/
protected NetRetBean handleResult(NetRetBean netRetBean) {
return netRetBean;
}

@Override
public NetRetBean sendSuccess(String result) {
NetRetBean netRetBean = new NetRetBean();
try {
JSONObject jsonObject = new JSONObject(result);
String code = jsonObject.getString("code");
String message = jsonObject.getString("message");
String time = jsonObject.getString("time");
String data = jsonObject.getString("data");
netRetBean.setServerCode(code);
netRetBean.setServerMsg(message);
netRetBean.setServerTime(time);
netRetBean.setServerData(data);
if (code.equals("00001")) {
netRetBean.setCallbackCode(CallbackCode.CODE_SUCCESS_REQUEST);
netRetBean = onReceivedRet(netRetBean);
} else {
netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_SERVER_DATA_ERROR);
}
} catch (JSONException e) {
e.printStackTrace();
netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_JSON_EXP);
}
return handleResult(netRetBean);
}

@Override
public NetRetBean sendError(Exception exp) {
exp.printStackTrace();

NetRetBean netRetBean = new NetRetBean();
netRetBean.setException(exp);

try {
throw exp;
} catch (RespondErrorException e) {
netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_HTTP_NOT_200);
} catch (RequestErrorException e) {
netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_REQUEST_EXP);
} catch (JSONException e) {
netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_JSON_EXP);
} catch (Exception e) {
netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_UNKNOWN);
}

return handleResult(netRetBean);
}

/**
* 子类根据业务区分,将netRetBean解析成list或者单个实体,或者解析成其它结果
*
* @param netRetBean server返回的数据实体,data字段将在子类中解析
* @return 解析后的netRetBean
* @throws JSONException 解析json异常
*/
abstract protected NetRetBean onReceivedRet(NetRetBean netRetBean) throws JSONException;
}

与NetHandleListener相比,SyncNetHandleListener显得更加简洁,这里的代码有以下几处改变:
1.最大的变化就是去掉了Handler,因为无需将解析后的结果返回到ui线程
2.由于1的变化,handleResult方法将直接返回NetRetBean,而不是通过Handler把数据传递给ui线程
3.onReceivedRet是具有返回值的,因为sendSuccess里面需要返回结果
4.去掉了onSuccess和onError方法,因为这里不需要再回调,而是直接返回结果

第2点中的handleResult方法,虽然只有一行,但是笔者为了让代码与异步请求的保持相似的风格,还是保留了这个方法。

公共部分的代码,还包括RequestUtil,HttpUtil,还有一些相关的类,这些类的代码都没有做修改。

这个时候,如果我们再调用NetHelper.getSync,使用上SyncNetHandleListener,会是这样的
private void syncGetString() {
new Thread() {
@Override
public void run() {
NetRetBean netRetBean = NetHelper.getSync("url", new SyncNetHandleListener() {
@Override
protected NetRetBean onReceivedRet(NetRetBean netRetBean) throws JSONException {
return null;
}
});
}
}.start();
}


和使用SyncNetListener一样,还是存在代码执行顺序模糊的问题。还有内层数据的解析,需要开发者自己来完成。
如果在onReceivedRet回调的解析方法中直接返回null,那么在返回的NetRetBean的内层数据还是为null。

不过我们把外层的数据在SyncNetHandleListener类里面进行解析了,公共部分的代码实现完成了。
分支部分,即内层解析部分的代码还需要一定的篇幅,所以将其放到下一篇中去讲解。

至此,同步请求公共部分讲解完成。

下一篇,将讲解同步请求分支部分
Android通用网络请求解析框架.8(同部请求,分支部分)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: