转载请标明出处:
http://blog.csdn.net/iamzgx/article/details/51607387
本文出自:【iGoach的博客】

概括

在上一篇博客android网络框架OkHttp之get请求(源码初识) 讲解了OkHttp的简单使用和OkHttp源码的分析,主要讲解的还是理论上的知识,还是没有去实践下,那么这篇博客里面就来实践下。在上一篇博客里面说到了OkHttp类似HttpUrlConnection。按这样说的话,我们在项目中肯定还是要封装一层。如果嫌封装麻烦的话,也可以拿来主义,比如使用鸿洋大神的OkHttpUtils,网络上对它也好评如潮。又或者曾经很火的Volley框架。为什么说曾经呢?也不是说它用的少了,只能说有更火的框架出来了。是什么呢?没错,就是这篇文章说到的Retrofit框架。既然是新框架,那为什么前面又说是OkHttp的实践呢?这里我们就要理解Retrofit这个框架了。Retrofit这个框架网络请求层事用的是OkHttp,它同样是Square开源组合推出的一个框架。在Retrofit2.0以前,还可以选择HttpUrlConnection或者HttpClient去请求。Retrofit最近推出的2.0版本以后,直接强制用户使用OkHttp去做网络请求了。所以可以说Retrofit和OkHttp已经是一对同胞兄弟了。

其实Retrofit还没有广泛使用的时候,使用的最多的还是Volley框架的。Retrofit和Volley一样对HttpURLConnection或者OkHttp进行封装。然后有一天,你和你的同事说,咱们把Volley改成Retrofit框架吧,你同事就问你,Volley用的好好的,干嘛要换。那我们要怎么劝服他去使用呢?你就会要说,Volley的原理我们通过一系列封装成为一个Request对象,然后我们把它添加到RequestQueue里面,然后通过NetworkDispatcher进行网络请求,而Retrofit只需要定义一个API。就可以直接返回我们要请求的数据了。当然,它最好是一个RestfulAPI。

RestfulAPI的理解

网上对RestfulAPI这个概念有很多种理解,说的已经让我们摸不着头脑了。怎么来理解RestfulAPI呢?符合Restful风格的就是RestfulAPI。Restful风格有是什么鬼?RESTful即Representational State Transfer,可以把它翻译成(资源)表现层状态转换。理解这个名词就懂了。

  • 资源,服务器给客户端的文字,图片,视频都可以理解为资源。我们一般都是URL这个资源实体去指向资源所在的路径,当然这个路径必须是名词组成的,不能是动词。比如https://www.google.com.hk/这个网址就可以说是一种资源。
  • 表现层(Representational ),http请求的时候,会有http协议的head部分,post请求的时候还有http body。它描述了请求资源的Content-Type和Content-length等等。这就是一种表示层。又或者我们常使用的json格式也是一种表示层。
  • 状态转换(State Transfer)在http请求中,GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。都是状态转换,而这些状态转换又是建立在表现层之上的,http头部表现层就会描述请求是通过get或者post方式等来请求的。

    如果还是不太理解,可以看这篇文章理解RESTful架构,推酷上看了很多文章。还是看了这篇之后才明白这个概念的。为什么难理解呢,主要是Restful只是一种风格,没有一套完整的标准,所以网络上各有各的理解。

准备RESTful API

既然这样,那么这里我们就要先准备下几个基本的RESTful API。我这里准备了
一个user表

一个新闻列表(news)表

3个API(我的本地ip为192.168.1.103:8080)
注册接口 http://192.168.1.103:8080/GoachWeb/RegisterDataServlet
参数:username、password(POST/Get)
返回:

{"resultCode": 200, "responseTime": "2016-06-14 22:38:49", "data": {"errorCode": 1, "userId": 1000000, "userName": "Goach"}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

登录接口 http://192.168.1.103:8080/GoachWeb/LoginDataServlet

参数:username、password(POST/Get)
返回:

{"resultCode": 200, "responseTime": "2016-06-14 22:38:49", "data": {"errorCode": 1, "userId": 1000000, "userName": "Goach"}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

新闻列表接口
参数:userId(POST/Get)
返回

{"resultCode": 200, "responseTime": "2016-06-18 22:17:30", "data": {"newsItem": [{"id": 1, "title": "高盛:中国房地产可能在6-9个月内迎来“拐点", "content": "6月14日,王逸等高盛分析师在报告中写道,预计2017年房价将疲弱,因为该行业因杠杆率上升、需求减弱,不久将见到拐点。"}, {"id": 2, "title": "国产大飞机C919首飞时间曝光 已接517架次订单", "content": "《经济参考报》记者日前从多个权威渠道获悉,我国自主研制的C919大型客机将于今年下半年首飞,最快2017年完成后续各项技术验证,并开始正式交付。"}, {"id": 3, "title": "解放军大批巨炮同时开火 现场升硕大火球", "content": " 6月10日,陆军第42集团军某防空旅全员全装在粤东某陌生地域展开战场机动、侦察预警、陆空对抗、实弹射击等课目训练,锤炼部队实战本领。"}, {"id": 4, "title": "拳王邹市明,一场比赛460万奖金,只开90万的车", "content": "中国拳王邹市明,一年的收入有多少?和帕奎奥,梅威瑟这种级别的相比,邹市明的收入只能算是小收入,从最初打职业比赛时的30万美金的奖金,到最高70万美金奖金,这其中受了多少伤只有他自己最清楚。如果能7场比赛速成世界拳王,奖金不过也就100万美金,或许他”永远“也不能成为梅威瑟这样的拳王。"}, {"id": 5, "title": "40万人看杨毅直播讲道理???", "content": " 由总决赛第四场比赛中,杨毅对于詹姆斯和格林的一次冲突而进行的评述,引发的一系列事件,还在持续发酵中。"}, {"id": 11, "title": "女王杯穆雷双抢7险胜 瓦林卡爆冷止步首轮", "content": "腾讯体育6月15日讯 ATP500赛伦敦女王杯草地公开赛今日继续男单首轮比赛的争夺,赛会头号种子、英国名将穆雷通过两盘抢7以7-6(8)和7-6(1)险胜法国选手马胡特,惊险晋级次轮;而2号种子瑞士名将瓦林卡则连丢两盘以2-6和6-7(3)不敌西班牙选手沃达斯科,爆冷止步首轮。"}]}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

接口比较简单。主要是自己后台开发比较low。这里后台使用的是通过servlet和jdbc通过gson转换为json进行开发的。

使用Retrofit框架

接口准备好了。下面就来集成Retrofit框架。

添加几个权限

 <uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  • 1
  • 2
  • 1
  • 2

build.gradle添加依赖,下面会用到的也在这里了:

compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
compile 'com.squareup.okhttp3:okhttp:3.3.0'
compile 'com.squareup.okio:okio:1.7.0'compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
compile 'com.android.support:recyclerview-v7:23.4.0'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

两个retrofit依赖包,两个okhttp依赖包,okhttp3:logging-interceptor依赖包主要是拦截请求日志使用,引入下面要使用的rxandroid的两个依赖包reactivex:rxandroid和reactivex:rxjava,recyclerview主要是新闻列表页要使用的。

基本UI页面

下面就是写登录注册页面。
登录页面效果如下

注册页面效果如下

新闻页面布局效果

布局代码后面源码提供下载,而且比较简单。

创建Retrofit对象

页面写好了。下面我们通过单例形式创建一个Retrofit对象。

public class HRetrofitNetHelper{public static HRetrofitNetHelper mInstance;public Retrofit mRetrofit;//本地ip为192.168.1.103public static final String BASE_URL = "http://192.168.1.103:8080/GoachWeb/";private HRetrofitNetHelper(){mRetrofit = new Retrofit.Builder().baseUrl(BASE_URL).build();}public static HRetrofitNetHelper getInstance(){if(mInstance==null){synchronized (HRetrofitNetHelper.class){if(mInstance==null)mInstance = new HRetrofitNetHelper ();}}return mInstance ;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

简单的创建好了一个Retrofit。这里只是配置了一个接口的baseUrl,也就是根路径。

配置ConverterFactory

如果要Retrofit直接将json转换为为Dao对象。那么我们就要通过addConverterFactory来配置,如下:

 mRetrofit = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create()).build();
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

上面是使用依赖:

compile'com.squareup.retrofit2:converter-gson:2.0.2'
  • 1
  • 1

包。然后addConverterFactory来配置。通过源码方法

addConverterFactory(Converter.Factory factory)
  • 1
  • 1

我们可以看到要传入一个继承Converter.Factory的对象。Retrofit里面就有这样的对象,这里我们用的是Gson来进行解析,那就有对应的GsonConverterFactory。那好下面就来创建这个对象

创建这个对象有两种方式

  • 一种是像上面写的一样
GsonConverterFactory.create()
  • 1
  • 1

这种方式就是简单的创建默认的Gson对象,然后像我们平常一样转换为Dao对象。

  • 还有一种方式就是通过GsonBuilder创建Gson对象,比如这里统一把后台提供的带有yyyy-MM-dd HH:mm:ss格式的Date对象,客户端如果用上面这种方式创建的话,会报下面这个错
java.text.ParseException: Failed to parse date ["2016-06-11 20:57:28']: Invalid time zone indicator ' ' (at offset 0)
  • 1
  • 1

这种情况下,我们就可以这样:

Gson mGson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
  • 1
  • 2
  • 1
  • 2

然后再创建GsonConverterFactory对象的时候传入Gson

.addConverterFactory(GsonConverterFactory.create(mGson))
  • 1
  • 1

就可以很好的解决这个问题了。

这里只是说了使用Gson进行解析,其实Retrofit还提供了其他的一些解析工具,如下:

Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

用法类似这样
导入包(xx可以指Jackson或者Moshi等等):

compile 'com.squareup.retrofit2:converter-xx:2.0.2'
  • 1
  • 1

然后:

.addConverterFactory(xxConverterFactory.create(mGson))
  • 1
  • 1

当然,我们还是可以设置多个converter
比如支持 proto 格式和json格式。那么如下添加:

Retrofit retrofit = new Retrofit.Builder()//....addConverterFactory(ProtoConverterFactory.create()).addConverterFactory(GsonConverterFactory.create()).build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

ProtoConverterFactory和GsonConverterFactory添加 converter 的顺序很重要。Retrofit会依次询问每一个 converter 能否处理一个类型。当Retrofit试图反序列化一个 proto 格式,它其实会被当做 JSON 来对待。所以Retrofit会先要检查 proto buffer 格式,然后才是 JSON。所以要先添加ProtoConverterFactory,然后是GsonConverterFactory。

又比如我们需要Retrofit支持RxJava。添加:

.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
  • 1
  • 1

就好了。

配置HttpLoggingInterceptor

Retrofit还可以添加OkHttpClient对象。比如我们可以添加一个拦截器来监听每次请求体:
依赖的包

compile'com.squareup.okhttp3:logging-interceptor:3.2.0'
  • 1
  • 1
 HttpLoggingInterceptor  interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {@Overridepublic void log(String message) {Log.d("zgx", "OkHttp====message " + message);}});
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);         
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

创建好后,然后通过retrofit对象添加client,如下:

mRetrofit = new Retrofit.Builder()//....client(mOkHttpClient).build();
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

这样我们就通过HttpLoggingInterceptor 拦截器可以获取道http请求体,可以获取我们请求方式,请求的参数,然后的json数据。这里以登录接口为例,如下:

06-11 22:16:11.064 31186-8789/com.goach.client D/zgx: OkHttp====message --> POST http://192.168.1.102:8080/GoachWeb/LoginDataServlet http/1.1
06-11 22:16:11.064 31186-8789/com.goach.client D/zgx: OkHttp====message Content-Type: application/x-www-form-urlencoded
06-11 22:16:11.068 31186-8789/com.goach.client D/zgx: OkHttp====message Content-Length: 30
06-11 22:16:11.068 31186-8789/com.goach.client D/zgx: OkHttp====message
06-11 22:16:11.068 31186-8789/com.goach.client D/zgx: OkHttp====message username=Goach&password=123456
06-11 22:16:11.068 31186-8789/com.goach.client D/zgx: OkHttp====message --> END POST (30-byte body)
06-11 22:16:12.376 31186-8789/com.goach.client D/zgx: OkHttp====message <-- 200 OK http://192.168.1.102:8080/GoachWeb/LoginDataServlet (1308ms)
06-11 22:16:12.376 31186-8789/com.goach.client D/zgx: OkHttp====message Server: Apache-Coyote/1.1
06-11 22:16:12.376 31186-8789/com.goach.client D/zgx: OkHttp====message Content-Type: text/plain;charset=UTF-8
06-11 22:16:12.376 31186-8789/com.goach.client D/zgx: OkHttp====message Transfer-Encoding: chunked
06-11 22:16:12.376 31186-8789/com.goach.client D/zgx: OkHttp====message Date: Sat, 11 Jun 2016 14:15:19 GMT
06-11 22:16:12.384 31186-8789/com.goach.client D/zgx: OkHttp====message
06-11 22:16:12.384 31186-8789/com.goach.client D/zgx: OkHttp====message {"errorCode":1,"userId":1000000,"responseTime":"2016-06-11 22:15:19","resultCode":200}
06-11 22:16:12.384 31186-8789/com.goach.client D/zgx: OkHttp====message <-- END HTTP (86-byte body)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

既然能用OkHttp的拦截机制,那么我们就可以在RequestBody 里面添加基本参数

配置基本提交参数

我们可以再新建一个拦截器,这里我举例加些简单的系统参数,如下:

        class HttpBaseParamsLoggingInterceptor implements Interceptor{@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();Request.Builder requestBuilder = request.newBuilder();RequestBody formBody = new FormBody.Builder().add("userId", "10000").add("sessionToken", "E34343RDFDRGRT43RFERGFRE").add("q_version", "1.1").add("device_id", "android-344365").add("device_os", "android").add("device_osversion","6.0").add("req_timestamp", System.currentTimeMillis() + "").add("app_name","forums").add("sign", "md5").build();String postBodyString = Utils.bodyToString(request.body());postBodyString += ((postBodyString.length() > 0) ? "&" : "") +  Utils.bodyToString(formBody);request = requestBuilder.post(RequestBody.create(MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"),postBodyString)).build();return chain.proceed(request);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

上面Utils类是使用的okio.Buffer里面的工具类。通过RequestBody构建要上传的一些基本公共的参数,然后通过”&”符号在http 的body里面其他要提交参数拼接。然后再通过requestBuilder重新创建request对象,然后再通过chain.proceed(request)返回Response 。

接下来在创建OkHttpClient对象的时候修改为如下代码:

    mOkHttpClient = new OkHttpClient.Builder().addInterceptor(interceptor).addInterceptor(new HttpBaseParamsLoggingInterceptor()).build();
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

这样就添加好了一些基本的公共参数。

当然。我们也可以直接借助github 上的BasicParamsInterceptor。代码如下:

public class BasicParamsInterceptor implements Interceptor {Map<String, String> queryParamsMap = new HashMap<>();Map<String, String> paramsMap = new HashMap<>();Map<String, String> headerParamsMap = new HashMap<>();List<String> headerLinesList = new ArrayList<>();private BasicParamsInterceptor() {}@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request();Request.Builder requestBuilder = request.newBuilder();// process header params injectHeaders.Builder headerBuilder = request.headers().newBuilder();if (headerParamsMap.size() > 0) {Iterator iterator = headerParamsMap.entrySet().iterator();while (iterator.hasNext()) {Map.Entry entry = (Map.Entry) iterator.next();headerBuilder.add((String) entry.getKey(), (String) entry.getValue());}}if (headerLinesList.size() > 0) {for (String line: headerLinesList) {headerBuilder.add(line);}}requestBuilder.headers(headerBuilder.build());// process header params end// process queryParams inject whatever it's GET or POSTif (queryParamsMap.size() > 0) {injectParamsIntoUrl(request, requestBuilder, queryParamsMap);}// process header params end// process post body injectif (request.method().equals("POST") && request.body().contentType().subtype().equals("x-www-form-urlencoded")) {FormBody.Builder formBodyBuilder = new FormBody.Builder();if (paramsMap.size() > 0) {Iterator iterator = paramsMap.entrySet().iterator();while (iterator.hasNext()) {Map.Entry entry = (Map.Entry) iterator.next();formBodyBuilder.add((String) entry.getKey(), (String) entry.getValue());}}RequestBody formBody = formBodyBuilder.build();String postBodyString = bodyToString(request.body());postBodyString += ((postBodyString.length() > 0) ? "&" : "") +  bodyToString(formBody);requestBuilder.post(RequestBody.create(MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"), postBodyString));} else {    // can't inject into body, then inject into urlinjectParamsIntoUrl(request, requestBuilder, paramsMap);}request = requestBuilder.build();return chain.proceed(request);}// func to inject params into urlprivate void injectParamsIntoUrl(Request request, Request.Builder requestBuilder, Map<String, String> paramsMap) {HttpUrl.Builder httpUrlBuilder = request.url().newBuilder();if (paramsMap.size() > 0) {Iterator iterator = paramsMap.entrySet().iterator();while (iterator.hasNext()) {Map.Entry entry = (Map.Entry) iterator.next();httpUrlBuilder.addQueryParameter((String) entry.getKey(), (String) entry.getValue());}}requestBuilder.url(httpUrlBuilder.build());}private static String bodyToString(final RequestBody request){try {final RequestBody copy = request;final Buffer buffer = new Buffer();if(copy != null)copy.writeTo(buffer);elsereturn "";return buffer.readUtf8();}catch (final IOException e) {return "did not work";}}public static class Builder {BasicParamsInterceptor interceptor;public Builder() {interceptor = new BasicParamsInterceptor();}public Builder addParam(String key, String value) {interceptor.paramsMap.put(key, value);return this;}public Builder addParamsMap(Map<String, String> paramsMap) {interceptor.paramsMap.putAll(paramsMap);return this;}public Builder addHeaderParam(String key, String value) {interceptor.headerParamsMap.put(key, value);return this;}public Builder addHeaderParamsMap(Map<String, String> headerParamsMap) {interceptor.headerParamsMap.putAll(headerParamsMap);return this;}public Builder addHeaderLine(String headerLine) {int index = headerLine.indexOf(":");if (index == -1) {throw new IllegalArgumentException("Unexpected header: " + headerLine);}interceptor.headerLinesList.add(headerLine);return this;}public Builder addHeaderLinesList(List<String> headerLinesList) {for (String headerLine: headerLinesList) {int index = headerLine.indexOf(":");if (index == -1) {throw new IllegalArgumentException("Unexpected header: " + headerLine);}interceptor.headerLinesList.add(headerLine);}return this;}public Builder addQueryParam(String key, String value) {interceptor.queryParamsMap.put(key, value);return this;}public Builder addQueryParamsMap(Map<String, String> queryParamsMap) {interceptor.queryParamsMap.putAll(queryParamsMap);return this;}public BasicParamsInterceptor build() {return interceptor;}}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163

我们只要向上面一样配置就行了。

其实拦截器还能做很多事。比如在开发中,我们会遇到,我们去请求某些接口的时候,服务端会直接返回一个信息给客户端,让客户端去Toast提示。下面,我就以只要是请求登录接口就给个提示框为例

Rxandroid的使用和特殊Url请求拦截处理

还是会用到拦截器,要知道,拦截器接口实现的intercept这个方法可不是在ui线程里面执行的,所以这里弹Toast,我们用RxAndroid实现再好不过了。
既然要用到RxAndroid,那就需要再依赖两个包:

compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
  • 1
  • 2
  • 1
  • 2

依赖好了,下面就可以在OkHttpClient创建的时候再添加一个拦截器mUrlInterceptor,代码如下,

 @Overridepublic okhttp3.Response intercept(Chain chain) throws IOException {Request request = chain.request();okhttp3.Response response = chain.proceed(request);String requestUrl = response.request().url().uri().getPath();if(!TextUtils.isEmpty(requestUrl)){if(requestUrl.contains("LoginDataServlet")) {if (Looper.myLooper() == null) {Looper.prepare();}createObservable("现在请求的是登录接口");}}return response;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

然后再上面OkHttp创建的时候修改下:

mOkHttpClient = new OkHttpClient.Builder()//..前面两个拦截器省略.addInterceptor(mUrlInterceptor).build();
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

说下上面intercept里面的,注意在createObservable方法调用前,要先Looper.prepare()下,否则会报错提示你要先调用Looper.prepare()方法下。其他的代码应该理解没什么问题了。我们知道RxAndroid两个核心就是Observable事件被观察者,然后就是subscribe事件订阅者,可以理解为观察者模式,但是它和观察者模式又有不同的地方,就是当事件被观察者没有关注者的时候,事件不会发送出去。详细就不讲解了。我这里只是弹个Toast,不用那么复杂。代码如下:

private void createObservable(String msg){Observable.just(msg).map(new Func1<String, String>() {@Overridepublic String call(String s) {return s;}}).observeOn(AndroidSchedulers.mainThread()).subscribe(onNextAction);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

通过Func1,直接发送一条消息给订阅者,发送完后这个事件就结束了。

.observeOn(AndroidSchedulers.mainThread())
  • 1
  • 1

的作用就是把订阅者处理事件发送给ui线程去处理。

接下来订阅者,就简单的用onNextAction实现了。

 private void createSubscriberByAction() {onNextAction = new Action1<String>() {@Overridepublic void call(String s) {Log.d("zgx","s=========="+s);Toast.makeText(mContext,s, Toast.LENGTH_SHORT).show();}};}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

createSubscriberByAction方法在HRetrofitNetHelper对象构造器里面调用就好了。

  private HRetrofitNetHelper(Context context){//...createSubscriberByAction();//...}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

这样就实现了,上面提的需求。

缓存

配置了这么多,接下来肯定又会想到缓存问题还没有处理呢。那么,接下来就来说下缓存处理了。

写之前,先看下源码里面注释的一段话

 if (!requestMethod.equals("GET")) {// Don't cache non-GET responses. We're technically allowed to cache// HEAD requests and some POST requests, but the complexity of doing// so is high and the benefit is low.return null;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

看懂了吧,OkHttp建议在不是Get请求的响应体不要缓存,因为如果缓存的话会提高它的复杂性而且好处不大。
没看到这段话之前。郁闷了很久为什么Post请求缓存生成不了,而且会报一个错

504 Unsatisfiable Request (only-if-cached)
  • 1
  • 1

这个错的意思就是只去读缓存,但是缓存不存在,所以就会报错了。但是我觉得有时候Post请求缓存的需求还是会有的,比如有时候在应用中经常想在没网的情况下缓存这个页面,而这个页面的请求接口也是post请求。所以还是要有缓存更好,比如volley框架就可以缓存整个页面,但是也是要改下volley的代码。目前还不知道怎么去缓存post请求。目前github上有RxCache,或者是通过Sqlite自己实现缓存都有,没有仔细研究,后面有时间在看。
下面就来看下实现代码

  • 创建局部变量Cache,以及两个Get方法,一个获取Cache对象,一个清除Cache缓存。
private final Cache cache;public Cache getCache(){return cache;}public void clearCache() throws IOException {cache.delete();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 配置OkHttp缓存
File cacheFile = new File(context.getCacheDir(), "HttpCache");
cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb
mOkHttpClient = new OkHttpClient.Builder()//....cache(cache).build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

官方建议缓存路径写在context.getCacheDir()里面,也就是在/data/data/com.goach.client/cache/HttpCache里面。这样配置好了,如果云端通过http的header里面Cache-Control做了缓存。那么这样就缓存完了。但是如果云端没有做了,那么我们客户端也可以自己通过Interceptor实现。这里我就把缓存逻辑写在上面的mUrlInterceptor拦截器里面了。修改如下

    @Overridepublic okhttp3.Response intercept(Chain chain) throws IOException {Request request = chain.request();//缓存if(NetUtil.checkNetwork(mContext)==NetUtil.NO_NETWORK){request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();Log.d("zgx","no network");}okhttp3.Response response = chain.proceed(request);String requestUrl = response.request().url().uri().getPath();if(!TextUtils.isEmpty(requestUrl)){if(requestUrl.contains("LoginDataServlet")) {if (Looper.myLooper() == null) {Looper.prepare();}createObservable("现在请求的是登录接口");}}//缓存响应if(NetUtil.checkNetwork(mContext)!=NetUtil.NO_NETWORK){//有网的时候读接口上的@Headers里的配置,你可以在这里进行统一的设置String cacheControl = request.cacheControl().toString();Log.d("zgx","cacheControl====="+cacheControl);return response.newBuilder().header("Cache-Control", cacheControl)//http1.0的旧东西,优先级比Cache-Control低.removeHeader("Pragma").build();}else{return response.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=30*24*60*60").removeHeader("Pragma").build();}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

没网的情况下Request 直接从缓存里面读取,响应体增加header的Cache-Control,缓存30天,有网的情况下,Request 就会去请求服务器,然后响应体就会去都Retrofit框架里面的@Header配置,如果没有配置,就没不缓存,如果配置了就可以进行缓存。到这里,当我们去Get请求的时候,就会生成缓存

我这里是通过模拟器看到,真机里面是看不到的。打开可以看到我们请求信息。

超时

okhttp如果没有配置默认是10s,错误信息如下

onFailure======java.net.SocketTimeoutException: failed to connect to /192.168.1.101 (port 8080) after 10000ms
  • 1
  • 1

配置

 mOkHttpClient = new OkHttpClient.Builder().connectTimeout(12, TimeUnit.SECONDS)//....build();
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

后,错误信息如下

 onFailure======java.net.SocketTimeoutException: failed to connect to /192.168.1.101 (port 8080) after 12000ms
  • 1
  • 1

还可以配置

 .writeTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).retryOnConnectionFailure(true)
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

没毛病,应该看的懂。
这样Retrofit创建基本的配置就完成了,最后结合上面总结后整个配置类的代码:

public class HRetrofitNetHelper implements HttpLoggingInterceptor.Logger,Interceptor {//HRetrofitNetHelper 实现单例public static HRetrofitNetHelper mInstance;//缓存对象private final Cache cache;public Retrofit mRetrofit;public OkHttpClient mOkHttpClient;//请求日志拦截器public HttpLoggingInterceptor mHttpLogInterceptor;//基本参数拦截器private BasicParamsInterceptor mBaseParamsInterceptor;//缓存和特殊Url拦截处理拦截器private Interceptor  mUrlInterceptor;private Context mContext;//Date对象传递public Gson mGson;//接口baseurlpublic static final String BASE_URL = "http://192.168.1.101:8080/GoachWeb/";private Action1<String> onNextAction;private HRetrofitNetHelper(Context context){this.mContext = context ;//提供Action,供特殊Url拦截然后ToastcreateSubscriberByAction();//yyyy-MM-dd HH:mm:ss的时间格式,可以转换为Date对象mGson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();mHttpLogInterceptor = new HttpLoggingInterceptor(this);//打印http的body体mHttpLogInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);//基本参数Map<String,String> tempParams = getBaseParams(context);mBaseParamsInterceptor = new BasicParamsInterceptor.Builder().addParamsMap(tempParams).build();mUrlInterceptor = this;//创建缓存路径File cacheFile = new File(context.getCacheDir(), "HttpCache");Log.d("zgx","cacheFile====="+cacheFile.getAbsolutePath());cache = new Cache(cacheFile, 1024 * 1024 * 100); //100MbmOkHttpClient = new OkHttpClient.Builder().connectTimeout(12, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).retryOnConnectionFailure(true).addInterceptor(mHttpLogInterceptor).addInterceptor(mBaseParamsInterceptor).addInterceptor(mUrlInterceptor).cache(cache).build();mRetrofit = new Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create(mGson)).client(mOkHttpClient).build();}public static HRetrofitNetHelper getInstance(Context context){if(mInstance==null){synchronized (HRetrofitNetHelper.class){if(mInstance==null){mInstance =  new HRetrofitNetHelper(context);}}}return mInstance;}//获取相应的APIService对象public <T> T getAPIService(Class<T> service) {return mRetrofit.create(service);}//异步callback,对一些特殊response逻辑处理public <D> void enqueueCall(Call<BaseResp<D>> call,final RetrofitCallBack<D> retrofitCallBack){call.enqueue(new Callback<BaseResp<D>>() {@Overridepublic void onResponse(Call<BaseResp<D>> call, Response<BaseResp<D>> response) {BaseResp<D> resp = response.body() ;if (resp == null) {Toast.makeText(mContext, "暂时没有最新数据!", Toast.LENGTH_SHORT).show();return;}if (resp.getResultCode() == 2000 || resp.getResultCode() == 2001 || resp.getResultCode() == 2002) {Toast.makeText(mContext,"code====="+resp.getResultCode(),Toast.LENGTH_SHORT).show();}if (resp.getResultCode() == 200) {if(retrofitCallBack!=null)retrofitCallBack.onSuccess(resp);} else {// ToastMaker.makeToast(mContext, resp.errMsg, Toast.LENGTH_SHORT);if(retrofitCallBack!=null)retrofitCallBack.onFailure(resp.getErrMsg());}}@Overridepublic void onFailure(Call<BaseResp<D>> call, Throwable t) {//   ToastMaker.makeToast(mContext, "网络错误,请重试!", Toast.LENGTH_SHORT);if(retrofitCallBack!=null){retrofitCallBack.onFailure(t.toString());}}});}@Overridepublic void log(String message) {Log.d("zgx","OkHttp: " + message);}//提供一些常用的基本参数public Map<String,String> getBaseParams(Context context){Map<String,String> params = new HashMap<>();params.put("userId", "324353");params.put("sessionToken", "434334");params.put("q_version", "1.1");params.put("device_id", "android7.0");params.put("device_os", "android");params.put("device_type", "android");params.put("device_osversion", "android");params.put("req_timestamp", System.currentTimeMillis() + "");params.put("app_name","forums");String sign = makeSign(params);params.put("sign", sign);return params ;}public String makeSign(Map<String, String> params) {final String signSalt = "fe#%d8ec93a1159a2a3";TreeMap<String, Object> sorted = new TreeMap<String, Object>();for (Map.Entry<String, String> kv : params.entrySet()) {sorted.put(kv.getKey(), kv.getValue());}StringBuilder sb = new StringBuilder(signSalt);for (String key : sorted.keySet()) {if (!"sign".equals(key) && !key.startsWith("file_")) {sb.append(key).append(sorted.get(key));}}sb.append(signSalt);return MD5.md5(sb.toString()).toUpperCase();}@Overridepublic okhttp3.Response intercept(Chain chain) throws IOException {Request request = chain.request();//缓存if(NetUtil.checkNetwork(mContext)==NetUtil.NO_NETWORK){request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();Log.d("zgx","no network");}okhttp3.Response response = chain.proceed(request);String requestUrl = response.request().url().uri().getPath();if(!TextUtils.isEmpty(requestUrl)){if(requestUrl.contains("LoginDataServlet")) {if (Looper.myLooper() == null) {Looper.prepare();}createObservable("现在请求的是登录接口");}}//缓存响应if(NetUtil.checkNetwork(mContext)!=NetUtil.NO_NETWORK){//有网的时候读接口上的@Headers里的配置,你可以在这里进行统一的设置String cacheControl = request.cacheControl().toString();Log.d("zgx","cacheControl====="+cacheControl);return response.newBuilder().header("Cache-Control", cacheControl).removeHeader("Pragma").build();}else{return response.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=120").removeHeader("Pragma").build();}}//异步特殊处理后回调public interface RetrofitCallBack<D>{void onSuccess(BaseResp<D> baseResp);void onFailure(String error);}private void createSubscriberByAction() {onNextAction = new Action1<String>() {@Overridepublic void call(String s) {Log.d("zgx","s=========="+s);Toast.makeText(mContext,s, Toast.LENGTH_SHORT).show();}};}//创建事件源private void createObservable(String msg){Observable.just(msg).map(new Func1<String, String>() {@Overridepublic String call(String s) {return s;}}).observeOn(AndroidSchedulers.mainThread()).subscribe(onNextAction);}public Cache getCache(){return cache;}public void clearCache() throws IOException {cache.delete();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207

Service之请求API

定义上面的3个请求接口API,为了验证缓存,都是Get请求。

public interface ILoginService {@GET("LoginDataServlet")@Headers("Cache-Control: public, max-age=30")Call<BaseResp<RegisterBean>> userLogin(@Query("username") String username, @Query("password") String password);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
public interface INewsService {@GET("NewsDataServlet")@Headers("Cache-Control: public, max-age=30")Call<BaseResp<News>> userNews(@Query("userId") String userId);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
public interface IRegisterService {@FormUrlEncoded@POST("RegisterDataServlet")Call<RegisterBean> createUser(@FieldMap Map<String ,String> params);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

其中Get请求,使用@GET和@Query或者@QueryMap的结合,Post请求@FormUrlEncoded、@POST和@Field或者@FieldMap的结合。又或者url中通过@Path动态添加参数。比如

public interface INewsService
{  @GET("NewsDataServlet/currentPage={currentPage}")  Call<BaseResp<News>> getUser(@Path("currentPage") String currentPage);
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

还有通过@Multipart 实现文件上传等等,详细可以看鸿洋大神的 Retrofit2 完全解析 探索与okhttp之间的关系

HRetrofitNetHelper的使用以及Activity相关代码

BaseActivity

public abstract class BaseActivity extends AppCompatActivity{public HRetrofitNetHelper retrofitNetHelper;public LayoutInflater mInflater;public ProgressDialog mDialog;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);}@Overridepublic void setContentView(@LayoutRes int layoutResID) {super.setContentView(layoutResID);mInflater = LayoutInflater.from(this);setContentView(mInflater.inflate(layoutResID,null));}@Overridepublic void setContentView(View view) {super.setContentView(view);retrofitNetHelper = HRetrofitNetHelper.getInstance(BaseActivity.this);mDialog = new ProgressDialog(BaseActivity.this);}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

LonigActicity

public class LoginActivity extends BaseActivity implements View.OnClickListener,HRetrofitNetHelper.RetrofitCallBack<RegisterBean> {private AutoCompleteTextView mEmailView;private EditText mPasswordView;private View mLoginFormView;private Button mSignInButton;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);mEmailView = (AutoCompleteTextView) findViewById(R.id.email);mPasswordView = (EditText) findViewById(R.id.password);mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {@Overridepublic boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {if (id == R.id.login || id == EditorInfo.IME_NULL) {return true;}return false;}});mSignInButton = (Button) findViewById(R.id.sign_in_button);mSignInButton.setOnClickListener(this);mLoginFormView = findViewById(R.id.login_form);}public void startRegister(View view){Intent intent = new Intent(LoginActivity.this,RegisterActivity.class);startActivity(intent);}@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.sign_in_button:mDialog.setMessage("正在登录中,请稍后...");mDialog.show();ILoginService loginService = retrofitNetHelper.getAPIService(ILoginService.class);String username = mEmailView.getText().toString();String password = mPasswordView.getText().toString();if(!TextUtils.isEmpty(username)&&!TextUtils.isEmpty(password)){final Call<BaseResp<RegisterBean>> repos = loginService.userLogin(username,password);retrofitNetHelper.enqueueCall(repos,this);}break;}}@Overridepublic void onSuccess(BaseResp<RegisterBean> baseResp) {Log.d("zgx","onResponse======"+baseResp.getData().getErrorCode());Date date = baseResp.getResponseTime();Log.d("zgx","RegisterBean======"+date);if(baseResp.getData().getErrorCode()==1){Intent intent = new Intent(LoginActivity.this, NewsActivity.class);intent.putExtra("intent_user_id",String.valueOf(baseResp.getData().getUserId()));startActivity(intent);Toast.makeText(getBaseContext(),"登录成功",Toast.LENGTH_SHORT).show();}else {Toast.makeText(getBaseContext(),"用户不存在",Toast.LENGTH_SHORT).show();}mDialog.dismiss();}@Overridepublic void onFailure(String error) {Log.d("zgx","onFailure======"+error);mDialog.dismiss();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

RegisterActivity

public class RegisterActivity extends BaseActivity implements Callback<RegisterBean> {private AutoCompleteTextView mUserName;private EditText mPasswordEditText;private EditText mConfirmationEditText;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_register);mUserName = (AutoCompleteTextView) findViewById(R.id.id_username);mPasswordEditText = (EditText)findViewById(R.id.password);mConfirmationEditText = (EditText)findViewById(R.id.confirmation_password);}public void startRegister(View view){String userName = mUserName.getText().toString();String password = mPasswordEditText.getText().toString();String mConfirmation = mConfirmationEditText.getText().toString();if(!TextUtils.isEmpty(userName)&&!TextUtils.isEmpty(password)&&!TextUtils.isEmpty(mConfirmation)){if(password.equals(mConfirmation)){IRegisterService loginService = retrofitNetHelper.getAPIService(IRegisterService.class);Map<String,String> mParamsMap = new HashMap<>();mParamsMap.put("username",userName);mParamsMap.put("password",password);Call<RegisterBean> call =  loginService.createUser(mParamsMap);call.enqueue(this);}else {Toast.makeText(getBaseContext(),"密码不一致",Toast.LENGTH_SHORT).show();}}else {Toast.makeText(getBaseContext(),"请填写完整",Toast.LENGTH_SHORT).show();}}@Overridepublic void onResponse(Call<RegisterBean> call, Response<RegisterBean> response) {if(response.body().getErrorCode()==1){Intent intent = new Intent(RegisterActivity.this, LoginActivity.class);startActivity(intent);}else{Toast.makeText(getBaseContext(),"注册失败",Toast.LENGTH_SHORT).show();}}@Overridepublic void onFailure(Call<RegisterBean> call, Throwable t) {}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

NewsActivity

public class NewsActivity extends BaseActivity implements HRetrofitNetHelper.RetrofitCallBack<News>{private String mUserId;private RecyclerView mRecyclerView;private NewsAdapter mNewsAdapter;private List<NewItem> mDataList;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_news);mUserId = getIntent().getStringExtra("intent_user_id");mDataList = new ArrayList<>();mRecyclerView = (RecyclerView)findViewById(R.id.id_news_recycler_view);LinearLayoutManager manager = new LinearLayoutManager(NewsActivity.this);mRecyclerView.setLayoutManager(manager);mNewsAdapter = new NewsAdapter(NewsActivity.this,mDataList);mRecyclerView.setAdapter(mNewsAdapter);loadData();}private void loadData(){mDialog.setMessage("正在加载中,请稍后...");mDialog.show();INewsService newService = retrofitNetHelper.getAPIService(INewsService.class);Log.d("zgx","mUserId====="+mUserId);final Call<BaseResp<News>> repos = newService.userNews(mUserId);retrofitNetHelper.enqueueCall(repos,this);}@Overridepublic void onSuccess(BaseResp<News> baseResp) {mDialog.dismiss();mDataList.clear();mDataList.addAll(baseResp.getData().getNewsItem());mNewsAdapter.notifyDataSetChanged();}@Overridepublic void onFailure(String error) {mDialog.dismiss();Toast.makeText(NewsActivity.this,"请求出现异常"+error,Toast.LENGTH_SHORT).show();}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

其他一些帮助类,后面提供源码下载。

最后来看下实现的效果

缓存效果没有录制,博客上传文件有限。

参考博客

  1. okhttp-logging-interceptor
  2. BasicParamsInterceptor - 为 OkHttp 请求添加公共参数
  3. 使用Retrofit和Okhttp实现网络缓存。无网读缓存,有网根据过期时间重新请求
  4. Okhttp缓存浅析
  5. 用 Retrofit 2 简化 HTTP 请求
  6. Retrofit2 完全解析 探索与okhttp之间的关系

说到这里,其实还有很多地方还要去学习,比如RxCache框架,通过Retrofit和Rxandroid真正的结合实现缓存处理,比如文件的上传下载等等。这些后面有时间再学习了。

源码下载

只是提供服务端和客户端的源码,数据库表和环境搭建配置就不提供了。
使用的环境为:
Android studio 2.1.2
MyEclipse 2014GA
Tomcat8.0
JDK8.0
MySQL Server 5.7
Navicat for MySQL

Retrofit+OkHttp+RxAndroid相关推荐

  1. 学会Retrofit+OkHttp+RxAndroid三剑客的使用,让自己紧跟Android潮流的步伐

    概括 在上一篇博客android网络框架OkHttp之get请求(源码初识) 讲解了OkHttp的简单使用和OkHttp源码的分析,主要讲解的还是理论上的知识,还是没有去实践下,那么这篇博客里面就来实 ...

  2. MVP+Dragger2+Rxjava2+Retrofit+OKhttp进行开发。

    MVP+Dragger2+Rxjava2+Retrofit+OKhttp框架已经流行很长时间,而且也必将成为未来android开发的趋势,在使用这个框架的过程中踩过很多坑, 所以想把我的经验告诉大家, ...

  3. Retrofit+OKHttp+RxJava的使用

    什么是响应式编程   响应式编程是一种基于异步数据 流概念的编程模式.数据流就像一条河:它可以被观测,被过滤,被操作,或者为新的消费者与另外一 条流合并为一条新的流. 什么是RxJava RxJava ...

  4. Android MVVM + Retrofit + OkHttp + Coroutine 协程 + Room + 组件化架构的Android应用开发规范化架构

    BaseDemo 介绍 BaseDemo 是Android MVVM + Retrofit + OkHttp + Coroutine 协程 + Room + 组件化架构的Android应用开发规范化架 ...

  5. Dagger2之应用篇(Dagger2+RxJava+Retrofit+OkHttp+MVP)-第7章

    介绍 前面介绍了Dagger2,今天尝试自己的想法去应用了下,说实话很别扭,晚上睡觉思前想后这个的好处.总是有一种似懂非懂,感觉就是,让对象与对象之间产生了一中关联,多个module的provides ...

  6. java中使用okhttpsoap,Android okHttp网络请求之Retrofit+Okhttp+RxJava组合

    Retrofit介绍: Retrofit和okHttp师出同门,也是Square的开源库,它是一个类型安全的网络请求库,Retrofit简化了网络请求流程,基于OkHtttp做了封装,解耦的更彻底:比 ...

  7. android http常用配置,Android中Retrofit+OkHttp进行HTTP网络编程的使用指南

    Retrofit介绍:Retrofit(GitHub主页https://github.com/square/okhttp)和OkHttp师出同门,也是Square的开源库,它是一个类型安全的网络请求库 ...

  8. Rxjava+retrofit+okHttp+mvp网络请求数据

    //Api类 public class Api {//http://api.svipmovie.com/front/columns/getVideoList.do?catalogId=40283481 ...

  9. 使用Android API最佳实践 Retrofit OKHttp GSON

    点击此处查看原文 写在前面 现在,Android应用程序中集成第三方API已十分流行.应用程序都有自己的网络操作和缓存处理机制,但是大部分比较脆弱,没有针对网络糟糕情况进行优化.感谢Square ln ...

最新文章

  1. 10个省时间的 PyCharm 技巧
  2. 深度学习的搜索应用Searching with Deep Learning
  3. 程序员如何做瑜伽? | 每日趣闻
  4. 中科大 计算机网络2 什么是互联网
  5. LeetCode 第 25 场双周赛(718/1832,前39.2%)
  6. css三栏布局技巧,CSS-三栏布局的常用6种方法
  7. java备份还原mysql数据库_Java备份还原Mysql数据库
  8. 【再探backbone 02】集合-Collection
  9. mysql5.7 的 user表的密码字段从 password 变成了 authentication_string
  10. USACO Section2.1 Hamming Codes 解题报告 【icedream61】
  11. 修改10g RAC public or private or virtual IP [Oracle]
  12. Ceylon 1.0.0
  13. iOS开发UI篇—UIScrollView控件介绍
  14. 关于mysql使用命令行时出现Data too long for column的解决方案:
  15. Java--继承(三)
  16. 我这么玩Web Api(二):数据验证,全局数据验证与单元测试
  17. 怎么把python压缩_python中如何实现图片压缩
  18. Python | 使用turtle库画笑脸滑稽表情
  19. 【使用Mac制作手写签名的方法】
  20. 域中文件服务器的设置权限,域中文件服务器共享权限设置.pdf

热门文章

  1. 学习Java8 Stream流,让我们更加便捷的操纵集合
  2. 92. 反转链表 II【穿针引线、头插法(tmp.next、pre.next 太妙了,绝绝子~)】
  3. 成都旅游必去点605
  4. 怎样查看python的安装路径win7_win7下查找指定程序的安装目录
  5. 施乃俺:三大搜索引擎(百度 谷歌 雅虎)导航站登陆渠道
  6. h264编码算法由浅入深(一)
  7. databus mysql搭建_Databus架构分析与初步实践(for mysql)
  8. 上证B指重上150点
  9. 远程支持软件:轻松解决电脑问题!
  10. 【Python】简约而不简单的Numpy小抄表(含主要语法、代码)