Okhttp简单使用

网络框架

Posted by BY Cc1over on July 12, 2018

Okhttp的简单使用

使用Okhttp进行网络请求支持两种方式,一种是异步请求,一种是同步请求。

get请求方法

同步请求:

对于同步请求在请求的时候开启线程,请求成功之后就跳转到主线程进行UI更新

public void okhttpSync(String address){
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象
                Request request = new Request.Builder()
                        .url(address)//请求接口。如果需要传参拼接到接口后面。
                        .build();//创建Request 对象
                Response response = client.newCall(request).execute();//得到Response 对象
                if (response.isSuccessful()) {
                //此时的代码执行在子线程,修改UI的操作请使用handler跳转到UI线程。
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();
}

注意事项

/**
   * Never {@code null}, must be closed after consumption, can be consumed only once.
   */
  public ResponseBody body() {
    return body;
  }

response的body方法返回的是ResponseBody对象,然后从ResponseBody对象中可以获取到流,字节数组,字符串等类型的数据。下面就分析一波ResponseBody的原理

public RealResponseBody(Headers headers, BufferedSource source) {
    this.headers = headers;
    this.source = source;
  }

ResponseBody是一个抽象类,所以一般使用它的子类RealResponseBody去实例化对象,构造函数如上面所示,在这里一个关键点就是BufferedSource,它是网络请求成功后返回的流,所有的数据都要从这个流这里获取。(它其实就是封装InputStream)
接下来就是ResponseBody的bytes方法:

 public final byte[] bytes() throws IOException {
    long contentLength = contentLength();
    BufferedSource source = source();
    byte[] bytes;
    try {
      bytes = source.readByteArray();
    } finally {
      Util.closeQuietly(source);
    }
    return bytes;
  }

其实网络的所得到的数据最原始的样子就是这种字节数组的形式,然后再通过转化的过程,转化成我们需要的类型(如body.string())就把字节数组转换成了字符串。 基于上面的原理

其实response.body().string()的本质是输入流的读操作,所以它还是网络请求的一部分,所以这行代码必须放在子线程。

也是因为response.body().string()的本质是输入流的读操作,必须有服务器的输出流的写操作时客户端的读操作才能得到数据。而服务器的写操作只执行一次,所以客户端的读操作也只能执行一次,第二次将返回null,所以这个方法只能调用一次!!

异步请求:

private void okhttpAsync(String address) {
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
            .url(address)
            .build();
    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if(response.isSuccessful()){//回调的方法执行在子线程。
                Log.d("kwwl","获取数据成功了");
                Log.d("kwwl","response.code()=="+response.code());
                Log.d("kwwl","response.body().string()=="+response.body().string());
            }
        }
    });
}

异步请求的打印结果与注意事项与同步请求时相同。最大的不同点就是异步请求不需要开启子线程,enqueue方法会自动将网络请求部分放入子线程中执行。
**注意事项: ** 1,回调接口的onFailure方法和onResponse执行在子线程。 2,response.body().string()方法也必须放在子线程中。当执行这行代码得到结果后,再跳转到UI线程修改UI。

post请求方式

post请求也分同步和异步两种方式,同步与异步的区别与get方式类似。

post方法接收的参数是RequestBody,可以根据不同的情况传入不一致的RequestBody或者其子类满足需要。

public Builder post(RequestBody body)

示例代码

private void httpAsyncWithPost(String address) {
    OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象。
    FormBody.Builder formBody = new FormBody.Builder();//创建表单请求体
    formBody.add("username","zhangsan");//传递键值对参数
    Request request = new Request.Builder()//创建Request 对象。
            .url(address)
            .post(formBody.build())//传递请求体
            .build();
    client.newCall(request).enqueue(new Callback(){});//回调方法的使用与get异步请求相同。
}

1、使用FormBody传递键值对参数

private void postDataWithParame(String address) {
    OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象。
    FormBody.Builder formBody = new FormBody.Builder();//创建表单请求体
    formBody.add("key","values");//传递键值对参数
    Request request = new Request.Builder()//创建Request 对象。
            .url(address)
            .post(formBody.build())//传递请求体
            .build();
    client.newCall(request).enqueue(new Callback() {});
}

2、 使用RequestBody传递json数据或者文件

OkHttpClient client = new OkHttpClient();//创建OkHttpClient对象。
MediaType JSON = MediaType.parse("application/json; charset=utf-8");//数据类型为json格式,
String jsonStr = "{\"username\":\"lisi\",\"nickname\":\"李四\"}";//json数据.
RequestBody body = RequestBody.create(JSON, josnStr);
Request request = new Request.Builder()
        .url(address)
        .post(body)
        .build();
client.newCall(request).enqueue(new Callback() {});

3、使用MultipartBody同时传递键值对参数和File对象

OkHttpClient client = new OkHttpClient();
MultipartBody multipartBody =new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("groupId",""+groupId)//添加键值对参数
        .addFormDataPart("title","title")
        .addFormDataPart("file",file.getName(),RequestBody.create(MediaType.parse("file/*"), file))//添加文件
        .build();
final Request request = new Request.Builder()
        .url(URLContant.CHAT_ROOM_SUBJECT_IMAGE)
        .post(multipartBody)
        .build();
client.newCall(request).enqueue(new Callback() {});

4、自定义RequestBody实现流的上传
1)首先创建一个RequestBody类的子类对象:

RequestBody body = new RequestBody() {
    @Override
    public MediaType contentType() {
        return null;
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {//重写writeTo方法
        FileInputStream fio= new FileInputStream(new File("fileName"));
        byte[] buffer = new byte[1024*8];
        if(fio.read(buffer) != -1){
             sink.write(buffer);
        }
    }
};

Okhttp下载文件

可以通过CallBack的onResponse方法获取Response对象,然后再获取流对象,然后用这个流对象去实现文件下载。

try{
    InputStream  is = response.body().byteStream();//从服务器得到输入流对象
    long sum = 0;
    File dir = new File(mDestFileDir);
    if (!dir.exists()){
        dir.mkdirs();
    }
    File file = new File(dir, mdestFileName);//根据目录和文件名得到file对象
    FileOutputStream  fos = new FileOutputStream(file);
    byte[] buf = new byte[1024*8];
    int len = 0;
    while ((len = is.read(buf)) != -1){
        fos.write(buf, 0, len);
    }
    fos.flush();
    return file;
}