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


    http://blog.csdn.net/iamzgx/article/details/51607387

    概括

    在上一篇博客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.103
        public 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() {
                 @Override
                 public 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{
    
            @Override
            public 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() {
    
        }
    
        @Override
        public Response intercept(Chain chain) throws IOException {
    
            Request request = chain.request();
            Request.Builder requestBuilder = request.newBuilder();
    
            // process header params inject
            Headers.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 POST
            if (queryParamsMap.size() > 0) {
                injectParamsIntoUrl(request, requestBuilder, queryParamsMap);
            }
            // process header params end
    
    
    
    
            // process post body inject
            if (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 url
                injectParamsIntoUrl(request, requestBuilder, paramsMap);
            }
    
            request = requestBuilder.build();
            return chain.proceed(request);
        }
    
        // func to inject params into url
        private 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);
                else
                    return "";
                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,代码如下,

     @Override
        public 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>() {
                @Override
                public 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>() {
                @Override
                public 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拦截器里面了。修改如下

        @Override
        public 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;
        //接口baseurl
        public 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拦截然后Toast
            createSubscriberByAction();
            //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); //100Mb
            mOkHttpClient = 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>>() {
                @Override
                public 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());
                    }
                }
    
                @Override
                public void onFailure(Call<BaseResp<D>> call, Throwable t) {
                 //   ToastMaker.makeToast(mContext, "网络错误,请重试!", Toast.LENGTH_SHORT);
                    if(retrofitCallBack!=null){
                        retrofitCallBack.onFailure(t.toString());
                    }
                }
            });
        }
        @Override
        public 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();
        }
        @Override
        public 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>() {
                @Override
                public 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>() {
                @Override
                public 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;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }
    
        @Override
        public void setContentView(@LayoutRes int layoutResID) {
            super.setContentView(layoutResID);
            mInflater = LayoutInflater.from(this);
            setContentView(mInflater.inflate(layoutResID,null));
        }
    
        @Override
        public 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;
    
        @Override
        protected 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() {
                @Override
                public 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);
        }
    
        @Override
        public 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;
            }
        }
        @Override
        public 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();
        }
    
        @Override
        public 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;
        @Override
        protected 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();
            }
        }
    
        @Override
        public 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();
            }
        }
    
        @Override
        public 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;
        @Override
        protected 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);
        }
    
        @Override
        public void onSuccess(BaseResp<News> baseResp) {
            mDialog.dismiss();
            mDataList.clear();
            mDataList.addAll(baseResp.getData().getNewsItem());
            mNewsAdapter.notifyDataSetChanged();
        }
    
        @Override
        public 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

  • 相关阅读:
    asp.net——Ajax与ashx异步请求的简单案例
    【转载】ADO.NET与ORM的比较(2):NHibernate实现CRUD
    【转载】ADO.NET与ROM的比较(1):ADO.NET实现CRUD
    STM32 IO口重映射
    keil集成开发环境下,编译stm32f103的工程,bug总结
    [转载]轻松玩转LCD12864-基于AVR单片机的LCD12864串行显示
    头文件 INTRINS.H 的用法
    JAVA
    Sublime 使用技巧
    STM32 空操作
  • 原文地址:https://www.cnblogs.com/jukan/p/6340872.html
Copyright © 2020-2023  润新知