Rx系列<第三十篇>:Android开发必须了解的Okhttp

Okhttp是目前主流网络框架之一,往往结合retrofit一起使用。因为现在项目主流框架是RxJava+Okhttp+Retrofit,所以我将Okhttp的讲解放在了Rx系列专题。

(1)添加依赖库
implementation 'com.squareup.okhttp3:okhttp:3.12.2'
(2)解决引入依赖库之后的异常

引入依赖库之后需要对Java8的编译支持,否则会报错,在build.gradle中添加:

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
图片.png
(3)添加网络权限
<uses-permission android:name="android.permission.INTERNET" />
(4)Get请求
  • 同步调用

使用call.execute()实现同步调用,但是从Android3.0之后出现一个新的策略,凡是网络请求必须在异步线程执行,不能在UI线程执行,否则抛出异常,所以以下代码的网络请求部分必须在子线程执行

            //(1)创建client对象
            OkHttpClient client = new OkHttpClient.Builder().build();
            //(2)创建Request对象
            Request request = new Request.Builder()
                    .get()
                    .url("https:www.baidu.com")
                    .build();
            //(3)将Request封装成Call
            Call call = client.newCall(request);

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //(4)开始Get请求
                        Response response = call.execute();
                        if(response.isSuccessful()){

                        }else{

                        }
                        Log.d("aaa", response.body().toString());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

在平时,我们应当尽量避免在UI线程执行网络请求,因为网络请求必然会阻塞UI线程。
其实call.execute()是可以在UI线程执行的,我们可以通过代码取消Android3.0对网络请求不能在UI线程执行的限制

    StrictMode.ThreadPolicy policy=new StrictMode.ThreadPolicy.Builder().permitAll().build();
    StrictMode.setThreadPolicy(policy);

onCreate方法中添加这两句代码就可以让call.execute()在主线程执行。

            //(1)创建client对象
            OkHttpClient client = new OkHttpClient.Builder().build();
            //(2)创建Request对象
            Request request = new Request.Builder()
                    .get()
                    .url("https:www.baidu.com")
                    .build();
            //(3)将Request封装成Call
            Call call = client.newCall(request);

            //(4)开始Get请求
            Response response = null;
            try {
                response = call.execute();
                if(response.isSuccessful()){
                    
                }else{
                    
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            Log.d("aaa", response.body().toString());
  • 异步调用

              //(1)创建client对象
              OkHttpClient client = new OkHttpClient.Builder().build();
              //(2)创建Request对象
              Request request = new Request.Builder()
                      .get()
                      .url("https:www.baidu.com")
                      .build();
              //(3)将Request封装成Call
              Call call = client.newCall(request);
              
              //(4)异步调用,并设置回调函数
              call.enqueue(new Callback() {
                  @Override
                  public void onFailure(Call call, IOException e) {
                  
                  }
    
                  @Override
                  public void onResponse(Call call, final Response response) throws IOException {
                      final String res = response.body().string();
    
                  }
              });
    

异步调用onFailureonResponse是在非UI线程上执行的。

(5)Post请求
            //创建client对象
            OkHttpClient client = new OkHttpClient();
            //创建FormBody对象,并传参
            FormBody formBody = new FormBody.Builder()
                    .add("username", "admin")
                    .add("password", "admin")
                    .build();
            //创建request对象
            Request request = new Request.Builder()
                    .url("http://www.jianshu.com/")
                    .post(formBody)
                    .build();
            //将Request封装成Call
            Call call = client.newCall(request);
            //开始网络请求
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Log.d("aaa", "11111111111111");
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    String res = response.body().string();
                    Log.d("aaa", "22222:"+res);

                }
            });

同步调用和Get请求一致。

(6)OkHttpClient

OkHttpClient可配置的属性有

        OkHttpClient client=new OkHttpClient.Builder()
                .connectTimeout(60, TimeUnit.SECONDS)       //设置连接超时
                .readTimeout(60, TimeUnit.SECONDS)          //设置读超时
                .writeTimeout(60,TimeUnit.SECONDS)          //设置写超时
                .retryOnConnectionFailure(true)             //是否自动重连
                .build();                                   //构建OkHttpClient对象

内存的释放

            client.dispatcher().executorService().shutdown();   //清除并关闭线程池
            client.connectionPool().evictAll();                 //清除并关闭连接池
            client.cache().close();                             //清除cache

newBuilder的使用

OkHttpClient eagerClient = client.newBuilder() 
       .readTimeout(500, TimeUnit.MILLISECONDS)
       .build();

这样创建的实例与原实例共享线程池、连接池和其他设置项,只需进行少量配置就可以实现特殊需求。

(7)Request

Request是网络请求的对象,其本身的构造函数是private的,只能通过Request.Builder来构建(建造者设计模式)。下面代码展示了常用的设置项。

    Request request = new Request.Builder()
            .url("https://api.github.com/repos/square/okhttp/issues")                          //设置访问url
            .get()                                                                             //类似的有post、delete、patch、head、put等方法,对应不同的网络请求方法
            .header("User-Agent", "OkHttp Headers.java")                                       //设置header
            .addHeader("Accept", "application/json; q=0.5")                                    //添加header
            .removeHeader("User-Agent")                                                        //移除header
            .headers(new Headers.Builder().add("User-Agent", "OkHttp Headers.java").build())   //移除原有所有header,并设置新header
            .addHeader("Accept", "application/vnd.github.v3+json")
            .build();
(8)RequestBodyFormBodyMultipartBody
  • RequestBody
图片.png

从上图可知,创建RequestBody请求体支持文件、byte数组以及字符串。

就以字符串为例

            JSONObject jsonObject = new JSONObject();
            try {
                jsonObject.put("username", "admin");
                jsonObject.put("password", "admin");
            } catch (JSONException e) {
                e.printStackTrace();
            }
            RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonObject.toString());

另外,requestBody可以获取以下属性

图片.png

contentLength: 消息体长度
isDuplex: 是否支持双工
isOneShot: 是否支持单工
contentType: 获取MediaType

这里还需要重点介绍一下MediaType,可分为::

json: application/json
xml: application/xml
png: image/png
jpg: image/jpeg
gif: imge/gif
form: application/x-www-form-urlencoded

使用时最好带上编码:MediaType.parse("application/json; charset=utf-8")

  • FormBody

FromBody用于提交表单键值对,其作用类似于HTML中的<form>标记。

FormBody是RequestBody的子类,从源码可以看出它默认MediaType是application/x-www-form-urlencoded

图片.png

FormBody支持设置键值对

            RequestBody formBody = new FormBody.Builder()
                    .add("username", "admin")
                    .add("password", "admin")
                    .addEncoded(URLEncoder.encode("A"), URLEncoder.encode("B"))
                    .build();

FormBody 的两个方法addaddEncoded可以添加键值对。

FormBody对象可以获取到的值如图所示:

图片.png

encodedName: 指定角标,获取已编码的key;
encodedValue: 指定角标,获取已编码的value;
name: 指定角标获取key;
value: 指定角标获取value;
contentLength: 获取请求体的长度;
contentType: 获取MediaType类型;
size: 获取键值对的数量

  • MultipartBody

使用MultipartBody.Builder可以构建与HTML文件上传格式兼容的复杂请求体。多块请求体中每块请求都是一个独立的请求体,都可以定义自己的请求头。这些请求头应该用于描述对应的请求体,例如Content-Disposition,Content-Length,和Content-Type会自动被添加到请求头中。

它有五种MediaType,分别是:

MultipartBody.MIXED: "multipart/mixed"
MultipartBody.ALTERNATIVE: "multipart/alternative"
MultipartBody.DIGEST: "multipart/digest"
MultipartBody.PARALLEL: "multipart/parallel"
MultipartBody.FORM: "multipart/form-data"

MultipartBody.FORM 比较常用,其他四种查询了很多资料也没有完全理清楚。

multipartBody可调用的方法有:

图片.png

setType: 设置MediaType
addFormDataPart:

图片.png

两个参数:键值对
三个参数:键值对+文件消息体(一般用于文件上传)

addPart: 每个Part(块)都会封装一个请求体

图片.png

代码如下:

            RequestBody multipartBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addPart(
                            Headers.of("Content-Disposition", "form-data; name=\"title\""),
                            RequestBody.create(null, "Logo"))
                    .addPart(
                            Headers.of("Content-Disposition", "form-data; name=\"image\""),
                            RequestBody.create(MediaType.parse("image/png"), new File("website/static/logo.png")))
                    .addFormDataPart("discription","beautiful")
                    .build();
(9)Call

Call对象表示一个已经准备好可以执行的请求,用这个对象可以查询请求的执行状态,或者取消当前请求。它具有以下方法:

    Call call=client.newCall(request);              //获取Call对象
    Response response=call.execute();               //同步执行网络请求,不要在主线程执行
    call.enqueue(new Callback());                   //异步执行网络请求
    call.cancel();                                  //取消请求
    call.isCanceled();                              //查询是否取消
    call.isExecuted();                              //查询是否被执行过

要注意的是,每个Call对象只能执行一次请求。如果想重复执行相同的请求,可以:

Call reCall=client.newCall(call.request());     //获取另一个相同配置的Call对象
(10)Response

Response是网络请求的结果下面是一些常用方法:

    Response response=call.execute();               //获取Response对象
    response.code();                                //请求的状态码
    response.isSuccessful();                        //如果状态码为[200..300),则表明请求成功
    Headers headers=response.headers();              //获取响应头
    List<String> names=response.headers("name");     //获取响应头中的某个字段
    ResponseBody body=response.body();             //获取响应体

其中ResponseBody代表响应体,用于操作网络请求返回的内容。常用方法如下:

    body.contentLength();                           //body的长度
    String content=body.string();                   //以字符串形式解码body
    byte[] byteContent=body.bytes();                //以字节数组形式解码body
    InputStreamReader reader=body.charStream();     //将body以字符流的形式解码
    InputStream inputStream=body.byteStream();      //将body以字节流的形式解码

需要注意的是:

  • ResponseBody必须关闭,不然可能造成资源泄漏,你可以通过以下方法关闭ResponseBody,对同一个ResponseBody只要关闭一次就可以了。

      Response.close();
      Response.body().close();
      Response.body().source().close();
      Response.body().charStream().close();
      Response.body().byteString().close();
      Response.body().bytes();
      Response.body().string();
    
  • ResponseBody只能被消费一次,也就是string(),bytes(),byteStream()或 charStream()方法只能调用其中一个。

  • 如果ResponseBody中的数据很大,则不应该使用bytes() 或 string()方法,它们会将结果一次性读入内存,而应该使用byteStream()或 charStream(),以流的方式读取数据。

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

推荐阅读更多精彩内容