OkHttp从使用到源代码分析(2)-请求的使用方法

以前说到OKHttp网络请求支持两种方式:同步请求和异步请求,同时又存在get和post请求,那么就是2*2,一共四种状况,接下来就分别介绍下这四种请求的使用和区别html

在gradle中指定Java版本java

compileOptions {
	sourceCompatibility JavaVersion.VERSION_1_8
	targetCompatibility JavaVersion.VERSION_1_8
}

说到这里,须要补一点,关于Android 9.0的http限制:默认使用加密链接,Android P禁止 App 使用全部未加密的链接,所以 Android P 系统不管是接收或者发送流量,都不能明码传输,须要使用下一代(Transport Layer Security)传输层安全协议,若是在Android P中使用http地址,将会出现以下错误(这里使用的url为http://www.baidu.com
W/System.err: java.net.UnknownServiceException: CLEARTEXT communication to www.baidu.com not permitted by network security policy
为了解决这个问题,常见的处理方式有三种:android

  • APP改用https请求
  • targetSdkVersion 降到27(Android 8.1)如下
  • 在 res 下新增一个 xml 目录,而后建立配置文件。再在application中添加进来
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
android:networkSecurityConfig="@xml/network_security_config"

同步请求

  • get同步请求
private void synGetRequest() {
   new Thread(new Runnable() {
       @Override
       public void run() {
           OkHttpClient mClient = new OkHttpClient.Builder()
                   .callTimeout(5, TimeUnit.SECONDS) //设置超时时间
                   .build(); //构建OkHttpClient对象
           Request request = new Request.Builder()
                   .url("http://www.baidu.com") //请求url
                   .get() //默认请求方式,写不写都是get
                   .build(); //建立Request对象
           Call call = mClient.newCall(request); //构建Call对象
           try {
               Response response = call.execute(); //获得Response对象
               if(response.isSuccessful()) {
                   Log.e(TAG, "response code ==> " + response.code());
                   Log.e(TAG, "response message ==> " + response.message());
                   Log.e(TAG, "response res ==> " + response.body().string());
               }else {
                   Log.e(TAG, "response fail");
               }
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
   }).start();
}

此时获得的log以下(省略了html的一长串内容):web

D/NetworkSecurityConfig: Using Network Security Config from resource network_security_config debugBuild: true
E/MainActivity: response code ==> 200
E/MainActivity: response message ==> OK
E/MainActivity: response res ==> <!DOCTYPE html><!--STATUS OK--><html> ··· </html>

使用注意:
因为同步请求是阻塞线程的(call.execute()),因此在使用的时候须要在子线程中运行,同时response.body().string()其本质上是对流的操做,也是须要在子线程中进行。另外,返回码中的code是在http中定义的,遵行http协议。因为response.body().string()的本质是对流进行操做,因此,在调用的时候,只会获得一次的返回值,再次调用会返回null,其根本缘由是客户端发出请求,服务端作出响应,将数据写入到流中,客户端获得返回值,再次调用时服务端并无在流中写入数据,此时客户端就没有数据,获得的就为null。json

  • post同步请求
private void synPostRequest() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            OkHttpClient mClient = new OkHttpClient.Builder()
                    .callTimeout(5, TimeUnit.SECONDS) //设置超时时间
                    .build(); //构建OkHttpClient对象

            MediaType JSON = MediaType.parse("application/json; charset=utf-8"); //数据类型为json格式,
            String json = "{\"username\":\"chen\"}"; //json数据
            RequestBody body = RequestBody.create(JSON, json); //建立RequestBody对象
            Request request = new Request.Builder()
                    .url("http://www.baidu.com") //请求url
                    .post(body) //post请求方式,须要传入RequestBody对象
                    .build(); //建立Request对象
            Call call = mClient.newCall(request); //构建Call对象
            try {
                Response response = call.execute(); //获得Response对象
                if (response.isSuccessful()) {
                    Log.e(TAG, "response code ==> " + response.code());
                    Log.e(TAG, "response message ==> " + response.message());
                    Log.e(TAG, "response res ==> " + response.body().string());
                } else {
                    Log.e(TAG, "response fail");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

post与get同步请求的区别在与post是须要带入请求体的,这也和get与post请求的本质区别有关。
post须要构建出请求体,从而完成post请求。安全

同步请求方法总结:网络

  1. 建立OkHttpClientRequest对象
  2. Request封装成Call对象
  3. 调用Callexecute()方法发送同步请求

异步请求

  • get异步请求
private void asyGetRequest() {
    OkHttpClient mClient = new OkHttpClient.Builder()
            .callTimeout(5, TimeUnit.SECONDS) //设置超时时间
            .build(); //构建OkHttpClient对象
    Request request = new Request.Builder()
            .url("https://www.baidu.com")
            .get()
            .build();
    Call call = mClient.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) { //请求失败回调
            Log.e(TAG, "onFailure ===> " + e.getMessage());
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException { //请求成功回调
            Log.e(TAG, "onResponse ===> " + response.body().string());
        }
    });
}

使用注意:
回调接口位于子线程,即call.enqueue()会将网络请求放在子线程中执行,说到这,就该知道UI更新什么的该怎么使用了吧。app

  • post异步请求
    嗯~~~,这个和上面的get异步请求差很少,区别在于post请求须要构建RequestBody对象传入post(),从而完成Request对象的构建,其余也就没啥区别了。

异步请求方法总结:异步

  1. 建立OkHttpClientRequest对象
  2. Request封装成Call对象
  3. 调用Callenqueue()方法进行异步请求
  • post参数说明
    查看post的方法
public Builder post(RequestBody body) {
  return method("POST", body);
}

能够看到传入的参数为:RequestBody,那么这是个什么玩意儿呢?
其实就是请求体,再看看怎么构建,查看源代码会发现有五种静态构建方法ide

RequestBody.create(MediaType, String)
RequestBody.create(MediaType, ByteString)
RequestBody.create(MediaType, byte[])
RequestBody.create(MediaType, byte[], int, int)
RequestBody.create(MediaType, File)

其源代码以下

public abstract class RequestBody {
  ···
  /** * Returns a new request body that transmits {@code content}. If {@code contentType} is non-null * and lacks a charset, this will use UTF-8. */
  public static RequestBody create(@Nullable MediaType contentType, String content) {
    Charset charset = UTF_8;
    if (contentType != null) {
      charset = contentType.charset();
      if (charset == null) {
        charset = UTF_8;
        contentType = MediaType.parse(contentType + "; charset=utf-8");
      }
    }
    byte[] bytes = content.getBytes(charset);
    return create(contentType, bytes);
  }

  /** Returns a new request body that transmits {@code content}. */
  public static RequestBody create(
      final @Nullable MediaType contentType, final ByteString content) {
    return new RequestBody() {
      @Override public @Nullable MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() throws IOException {
        return content.size();
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.write(content);
      }
    };
  }

  /** Returns a new request body that transmits {@code content}. */
  public static RequestBody create(final @Nullable MediaType contentType, final byte[] content) {
    return create(contentType, content, 0, content.length);
  }

  /** Returns a new request body that transmits {@code content}. */
  public static RequestBody create(final @Nullable MediaType contentType, final byte[] content,
      final int offset, final int byteCount) {
    if (content == null) throw new NullPointerException("content == null");
    Util.checkOffsetAndCount(content.length, offset, byteCount);
    return new RequestBody() {
      @Override public @Nullable MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() {
        return byteCount;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.write(content, offset, byteCount);
      }
    };
  }

  /** Returns a new request body that transmits the content of {@code file}. */
  public static RequestBody create(final @Nullable MediaType contentType, final File file) {
    if (file == null) throw new NullPointerException("file == null");

    return new RequestBody() {
      @Override public @Nullable MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() {
        return file.length();
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        try (Source source = Okio.source(file)) {
          sink.writeAll(source);
        }
      }
    };
  }
}

经常使用的RequestBody有json与file,那么具体怎么构建呢,如下举出两个例子
JSON格式上传

MediaType JSON = MediaType.parse("application/json; charset=utf-8"); //数据类型为json格式
String json = "{\"username\":\"chen\"}";
RequestBody body = RequestBody.create(JSON, json);

FILE格式上传

MediaType fileType = MediaType.parse("File/*"); //数据类型为file格式
File file = new File("/sdcard/test.txt");
RequestBody body = RequestBody.create(fileType, file);

同时查看源代码还发现,RequestBody有两个实现类,FormBodyMultipartBody,这两个是OkHttp对RequestBody的实现,前者用于传递键值对参数,后者用于传递复杂参数。
例如使用FormBody传递键值对

RequestBody body = new FormBody.Builder() //建立表单请求
	.add("username","chen")
	.add("from","PC")
	.build();

使用MultipartBody的例子

MultipartBody multipartBody = new MultipartBody.Builder()
    .setType(MultipartBody.FORM)
    .addFormDataPart("title", "title") //键值对
    .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("file/*"), file)) //文件
    .build();

这里须要说明一点,OkHttp并无实现文件的下载功能,但咱们能够拿到流,那么也就是说能够将流转化为文件保存,也就实现了文件下载功能。

热门开源项目源代码分析导航