Android的网络请求框架
前言
现在很多的app都是依赖网络服务的应用。跟服务器通讯我们就要用到网络请求。网络请求的框架有很多:原生自己封装的,Volley,OKHttp,Spring for android,Retrofit...等等。当然今天不讨论这些框架的使用或者好处,而是讨论网络服务的app的框架要如何搭建。
搭建说明
整体的流程大致是这样:
用户操作->业务处理->网络请求->数据解析->业务处理->界面改变。
如果分层的话,大致是:
视图层 -> 业务处理层 -> 网络请求层
那咱们还得弄清楚一些原则:
- 既然分层了,咱们首先要搞清楚每一层该做什么事情。
视图层: 跟用户交互以及界面展示方面的,例如请求弹出个进度框,请求成功或者失败要通知用户,界面刷新展示,用户体验就靠这层了。
业务处理层:请求数据装载 (例如:登录 Basic Auth,视图层提供输入框里面的用户名和密码, 这里转化成服务器识别的参数要求),业务处理,得到网络请求返回的数据处理 (例如:缓存, 校验等)
**网络请求层: **就是调用各个框架请求网络,负责和服务器交互
那检查手机网络应该放在那一层呢?我这里就不说了。
- 表层可以依赖底层,底层不能依赖表层。
- 不要跨层依赖。
- 层与层的依赖关系要尽量减少耦合
这个得自己用其他框架实现,依赖注入,代理,事件总线,Base基类等等,今天就不讨论这个。
讲到这,其实咱们的框架是这样的:
1~6的过程中,可能有些步骤是耗时,且在不同的线程中执行。但是他们的步奏是一定是1-6顺序执行完。
当然上面是顺利的流程,当然也有可能出现其他的情况,1~6的过程中取消任务或者出现异常。
ok,那我们下面来讨论下不正常的情况
- 执行过程取消任务,我们的框架应该是支持任一过程中取消任务执行。
取消任务触发条件可能是用户操作或者页面退出等,那任务就没有必要执行下去,就要取消。达到的效果就是任一点取消任务,后面的过程将不会执行,这里的3,4可能是线程执行的过程,如果有可能可以中断线程。
- 异常处理
每一个过程都要可能产生异常,异常处理原则就交给有能力处理的层去处理,例如网络异常一般都是抛到UI层,然后提示用户。
代码实现
讨论那么多,那代码如何实现呢?我这边主要讲一下我的思路,如果有其他更好的方法,请告知。
以前的思想是回调的方式,即底层执行的时候,需要调用者提供handler,就是实现回调接口。
现在的思路是,就像滚雪球一样,底层给出核心的Excutor对象,往上抛,每经过一层,包上这一层的业务逻辑,由最外层执行操作。好处我觉得可扩展,代码优雅,低耦合。
网络请求部分:
我用的是 Retrofit + Rxjava,用OkHttp的Interceptor实现网络请求的切面编程,例如前面提到的Basic Auth等。
public interface XXXDao {
@GET("http://120.155.93.85:8080/rs/{name}")
Observable<List<Canteen>> getCanteen(@Path("name") String name, @Query("data") String date);
@GET("http://120.55.93.85:8080/rs/canteens")
Observable<retrofit2.Response<List<Canteen>>> getCanteen1();
}
比较简单吧,过程被框架封装了,这个咱们不管它,主要盯着它的返回,第一个小雪球: Observable<??>,它后面需要2个处理数据,Acton1<??>,Action<Throwable> 看名字就知道一个是处理异常的,一个是处理数据的。不懂的朋友看Rxjava.
正常和异常都处理了,那如何在这一层取消任务呢?
这里用的是Interceptor来拦截掉你要取消的任务。这里使用前后2个拦截动作,如下图:
思路有了,如何代码实现呢?先看看拦截需要我们实现的代码
public class XXXInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = judgeRequest(request);
if(response == null){
response = chain.proceed(request);
}
if(Response temp = judgeRequest(request) != null){
response = temp;
}
return response
}
private Response judgeRequest(Request request) {
if(request 是否被取消){
return 异常的Response;
}
return null;
}
}
拦截器是可以实现我们的效果,但是因为retrofit是支持取消任务的。上面的代码要做一丢丢的改变
call.cancel();
public interface XXXDao {
@GET("http://120.155.93.85:8080/rs/{name}")
Call<List<Canteen>> getCanteen(@Path("name") String name, @Query("data") String date);
@GET("http://120.55.93.85:8080/rs/canteens")
Call<retrofit2.Response<List<Canteen>>> getCanteen1();
}
然后像上层抛出Call对象,当取消任务的时候,就调用cancel的方法,不过我这里没有对3,4做取消处理,因为我比较懒,闲麻烦。所以网络请求返回的还是Observable<??>对象。
业务处理部分:
小雪球拿到,我们就要在包装一下,像三明治一样,用2片面包包起来,其实就是前面做什么&后面做什么。
public interface AppDao {
RestExcuter<List<Canteen>> getCanteen(String path, String date);
RestExcuter<Result<List<Canteen>>> getCanteen1();
}
我用RestExcuter来包装了Observable<??>,代码如下:
private RestExcuter(@NonNull Observable<Bo> observable) {
super();
if (observable == null) {
throw new NullPointerException("Observable null !");
}
this.observable = observable;
}
然后提供了一些给上层调用的接口。
errorDoing是交给视图层错误时候该做什么。
postDoing是交给视图层成功了做什么。
handler是交给视图层做动画处理的,例如某一个App请求网络的时候,是一个Gif图播放。
public interface RestHandler<RestBo> {
/**
* 前面做什么
*/
void pre();
/**
* 完成做什么
* @param bo
*/
void post(RestBo bo);
/**
* 异常做什么
* @param throwable
*/
void error(Throwable throwable);
}
下面是如何在这一层取消请求呢?
//任务是否取消
AtomicBoolean isCancel;
boolean isCancel(){
boolean result = isCancel.get();
if (result) {
ZLog.i("任务已经被取消!");
if (errorDoing != null) {
errorDoing.call(new RestCancelException());
}
}
return result;
}
private void removeExcuter(){
if(container != null){
container.removeRestExcuter(this);
container = null;
cancel();
}
}
/** * 取消任务 */
public void cancel(){
isCancel.compareAndSet(false,true);
}
/**
* 请求的容器,例如activity在它销毁时候 取消这个容器下的所有请求
*/
public interface RestContainer {
void addRestExcuter(RestExcuter<?> excuter);
void cancelAllRestExcuter();
void removeRestExcuter(RestExcuter<?> excuter);
}
isCancel是控制这个任务是否被取消。
视图层
视图层相对简单点,就是调用业务层,干自己改做的事情。代码如下:
public void simpleClick(View view) {
appDao.getCanteen("canteens", "2016-04-01")
.setContainer(this)
.handler(new DialogHandler<List<Canteen>>(this,"网络请求","正在请求网络,请稍等..."))
.error(throwable -> binding.setInfo("请求出错啦"+throwable.getMessage()))
.post(body -> setCanteenInfo(body))
.excute();
}
好吧,就介绍到这。