Android OkHttp常用详解

图片发自简书App

OkHttp不需要多介绍了,已经是网络框架界的大佬了,很多网络框架都基于OkHttp封装,也有很多涉及到网络的第三方框架都可以支持使用OkHttp替换网络。

OkHttp的4.0.x版本已经全部由java替换到了Kotlin,API的一些使用也会有些不同,具体的参考Upgrading to OkHttp 4

由于不熟悉Kotlin代码,本文使用的OkHttp的版本为3.14.2,是3.14.x的最后一个版本

接入

OkHttp在3.13.x以上的版本需要在Android 5.0+ (API level 21+)和Java 1.8的环境开发。

同时还需要再添加Okio的依赖库,而Okio在1.x版本是基于Java实现的,2.x则是Kotlin实现的。

dependencies {
    //...
    //OkHttp
    implementation 'com.squareup.okhttp3:okhttp:3.14.2'
    implementation 'com.squareup.okio:okio:1.17.4'
}

3.12.x以及以下的版本支持Android 2.3+ (API level 9+)和Java 1.7的开发环境

Get请求

请求分为同步请求和异步请求,先看看同步请求

public void getSyn(final String url) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                //创建OkHttpClient对象
                OkHttpClient client = new OkHttpClient();
                //创建Request
                Request request = new Request.Builder()
                        .url(url)//访问连接
                        .get()
                        .build();
                //创建Call对象        
                Call call = client.newCall(request);
                //通过execute()方法获得请求响应的Response对象        
                Response response = call.execute();
                if (response.isSuccessful()) {
                    //处理网络请求的响应,处理UI需要在UI线程中处理
                    //...
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

这就是一段同步Get请求的代码,同步网络请求需要在子线程中执行,而处理UI需要回到UI线程中处理。

在看看Get的异步请求,这时就不需要自己创建子线程了,但是处理UI同样需要在UI线程中处理,不能再请求响应的回调方法中处理

public void getAsyn(String url) {
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).build();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            //...
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if(response.isSuccessful()){
                String result = response.body().string();
                //处理UI需要切换到UI线程处理
            }
        }
    });
}

Request.Builder中默认的使用Get请求,所以可以不调用get()方法

看了两种不同的Get请求,基本流程都是先创建一个OkHttpClient对象,然后通过Request.Builder()创建一个Request对象,OkHttpClient对象调用newCall()并传入Request对象就能获得一个Call对象。而同步和异步不同的地方在于execute()enqueue()方法的调用,调用execute()为同步请求并返回Response对象;调用enqueue()方法测试通过callback的形式返回Response对象。

注意:无论是同步还是异步请求,接收到Response对象时均在子线程中,其中通过Response对象获取请求结果需要在子线程中完成,在得到结果后再切换到UI线程改变UI

Post请求

Post请求与Get请求不同的地方在于Request.Builderpost()方法,post()方法需要一个RequestBody的对象作为参数

public void post(String url,String key,String value){
    OkHttpClient client = new OkHttpClient();
    FormBody body = new FormBody.Builder()
            .add(key,value)
            .build();
    Request request = new Request.Builder()
            .url(url)
            .post(body)
            .build();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            //...
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if(response.isSuccessful()){
                String result = response.body().string();
                //处理UI需要切换到UI线程处理
            }
        }
    });
}

RequestBody是一个抽象类,分别有FormBodyMultipartBody两个子类,上面这个例子使用的是FormBody,用于传输表单类型的参数。MultipartBody则支持多类型的参数传递,例如:在传输表单类型的参数的同时,还是可以传输文件。创建一个MultipartBody对象再调用post()方法就OK了。

MultipartBody body = new MultipartBody.Builder()
//      添加表单参数
//      .addFormDataPart(key,value)
        .addFormDataPart(name, fileName, RequestBody.create(MediaType.get("application/octet-stream"), file))
        .build();

RequestBody

前面的代码可以看到使用了RequestBody.create()方法,改方法的返回值也是一个RequestBody对象。

其实Post请求就是包含了RequestBody对象,MultipartBody则是可以支持多中以及多个RequestBody对象。

RequestBody提供了5个重载的create()静态方法,如下图

如果只需要上传文件,请求就比较简单了,如下:

public void uploadFile(String url, File file) {
    OkHttpClient client = new OkHttpClient();
    RequestBody body =  RequestBody.create(MediaType.get("application/octet-stream"), file);
    Request request = new Request.Builder()
            .url(url)
            .post(body)
            .build();
    Call call = client.newCall(request);
    //call.enqueue();
    //...
}

这个MediaType.get()的传值是"application/octet-stream",这是二进制流传输,在不知道文件类型的情况下可以这么操作,具体的传参可以参考Content-Type的类型使用对照表

PS:如果要监听文件的上传进度就没这么简单了

这是生成一个上传JSON的RequestBody对象的代码

MediaType jsonType = MediaType.parse("application/json; charset=utf-8");
String jsonStr = "{\"username\":\"Sia\"}";//json数据.
RequestBody body = RequestBody.create(jsonType, josnStr);

设置超时时间

OkHttp可以设置调用、连接和读写的超时时间,都是通过OkHttpClient.Builder设置的。如果不主动设置,OkHttp将使用默认的超时设置。

OkHttpClient mClient = new OkHttpClient.Builder()
        .callTimeout(6_000, TimeUnit.MILLISECONDS)
        .connectTimeout(6_000, TimeUnit.MILLISECONDS)
        .readTimeout(20_000, TimeUnit.MILLISECONDS)
        .writeTimeout(20_000, TimeUnit.MILLISECONDS)
        .build();

设置请求Header

请求的Header是通过Request.Builder对象的相关方法来维护的,如下:

  • headers(Headers headers)
  • header(String name, String value)
  • addHeader(String name, String value)
  • removeHeader(String name)

addHeaderremoveHeader方法比较好理解,分别是添加和移除header信息。header(String name, String value)这是会重新设置指定name的header信息。

headers(Headers headers)则是会移除掉原有的所有header信息,将参数headers的header信息添加到请求中。这是这几个方法的一些差别。

使用的话都是Builder模式的链式调用,举个栗子

Request request = new Request.Builder()
        .header("Accept","image/webp")
        .addHeader("Charset","UTF-8")
        .url(url)
        .build();

Cookie也是header信息中的一个字段,通过Header相关方法添加就好了

请求部分的基础使用基本上就这些了,具体的一些用法可以参考官方文档https://square.github.io/okhttp/recipes/

Interceptors(拦截器)

拦截器是OkHttp当中一个比较强大的机制,可以监视、重写和重试调用请求。

这是一个比较简单的Interceptor的实现,对请求的发送和响应进行了一些信息输出。

class LoggingInterceptor implements Interceptor {
    public static final String TAG = "Http_log";

    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();

        long t1 = System.nanoTime();
        Log.i(TAG, String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));

        Response response = chain.proceed(request);

        long t2 = System.nanoTime();
        Log.i(TAG, String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (t2 - t1) / 1e6d, response.headers()));

        return response;
    }
}

需要实现其中intercept(Interceptor.Chain chain)方法,同时必须调用chain.proceed(request)代码,也就是网络请求真正发生的地方。

拦截器可以设置多个,并且拦截器的调用是有顺序的。官网举的例子是,同时添加一个压缩拦截器和一个校验拦截器,需要决定数据是先被压缩在校验,还是先校验在压缩。

拦截器还分为应用拦截器(Application Interceptors)和网络拦截器(Network Interceptors)

上图可以看出应用拦截器是处于应用和OkHttp核心之间,而网络拦截器则是在OkHttp核心与网络之间,这里就直接搬运官网的示例,使用LoggingInterceptor来看一下两种拦截器的差异。

OkHttp自身有5个Interceptor的实现,有兴趣可以阅读源码

  • RetryAndFollowUpInterceptor
  • BridgeInterceptor
  • CacheInterceptor
  • ConnectInterceptor
  • CallServerInterceptor
Application Interceptors

先看看应用拦截器,通过OkHttpClient.BuilderaddInterceptor方法添加拦截器

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new LoggingInterceptor())
    .build();

Request request = new Request.Builder()
    .url("http://www.publicobject.com/helloworld.txt")
    .header("User-Agent", "OkHttp Example")
    .build();

Response response = client.newCall(request).execute();
response.body().close();

看请求和响应的两个链接是不同的,URL http://www.publicobject.com/helloworld.txt会重定向到 https://publicobject.com/helloworld.txt,OkHttp会自动跟随重定向,而应用拦截器只被调用一次,并且chain.proceed()返回的Response对象是具有重定向响应对象。

INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example

INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
Network Interceptors

再来看看网络拦截器,通过OkHttpClient.BuilderaddNetworkInterceptor方法添加拦截器

OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new LoggingInterceptor())
    .build();

Request request = new Request.Builder()
    .url("http://www.publicobject.com/helloworld.txt")
    .header("User-Agent", "OkHttp Example")
    .build();

Response response = client.newCall(request).execute();
response.body().close();

结果日志:

INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt

INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

从日志来看,拦截器运行了两次,第一次请求了http://www.publicobject.com/helloworld.txt,第二次则是重定向到https://publicobject.com/helloworld.txt。同时通过网络拦截能获得更多的header信息。更多的关于Interceptor的使用以及它们各自的优缺点,请参考OkHttp官方说明文档

其他

OkHttp已经出来很久了,本文只是为了完成自己的执念吧!还有Events的使用没有写出来,这个东西平常也没用过,后续研究了会补充上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270