okhttp的使用与封装

一、介绍

1、特性

OkHttp是一个精巧的网络请求库,有如下特性: 


  1. 支持http2,对一台机器的所有请求共享同一个socket 

  2. 内置连接池,支持连接复用,减少延迟 

  3. 支持透明的gzip压缩响应体
  4.  
通过缓存避免重复的请求
  5. 
请求失败时自动重试主机的其他ip,自动重定向
  6. 
好用的API

其本身就是一个很强大的库,再加上Retrofit2、Picasso的这一套组合拳,使其愈发的受到开发者的关注。本篇博客,我将对Okhttp3进行分析(源码基于Okhttp3.4)

二、使用

1、配置

配置Okhttp3非常简单,只需要在Android Studio 的gradle进行如下的配置:
 compile 'com.squareup.okhttp3:okhttp:3.4.1'
添加网络权限:
<uses-permission android:name="android.permission.INTERNET"/>

2、基本使用

2.1、okHttp的get请求


okHttp的一般使用如下,okHttp默认使用的就是get请求

String url = "http://write.blog.csdn.net/postlist/0/0/enabled/1";
    mHttpClient = new OkHttpClient();

    Request request = new Request.Builder().url(url).build();
    okhttp3.Response response = null;
    try {
            response = mHttpClient.newCall(request).execute();
            String json = response.body().string();
            Log.d("okHttp",json);

    } catch (IOException e) {
        e.printStackTrace();
    }
}

我们试着将数据在logcat进行打印,发现会报错,原因就是不能在主线程中进行耗时的操作 
 
说明mHttpClient.newCall(request).execute()是同步的,那有没有异步的方法呢,答案是肯定的,就是mHttpClient.newCall(request).enqueue()方法,里面需要new一个callback我们对代码进行修改,如下

public void requestBlog() {
     String url = "http://write.blog.csdn.net/postlist/0/0/enabled/1";

     mHttpClient = new OkHttpClient();

     Request request = new Request.Builder().url(url).build();
/* okhttp3.Response response = null;*/

         /*response = mHttpClient.newCall(request).execute();*/
     mHttpClient.newCall(request).enqueue(new Callback() {
         @Override
         public void onFailure(Call call, IOException e) {

         }

         @Override
         public void onResponse(Call call, Response response) throws IOException {
             String json = response.body().string();
             Log.d("okHttp", json);
         }
     });
 }

2.2、Okhttp的POST请求

POST提交Json数据

private void postJson() throws IOException {
    String url = "http://write.blog.csdn.net/postlist/0/0/enabled/1";
    String json = "haha";

    OkHttpClient client = new OkHttpClient();

    RequestBody body = RequestBody.create(JSON, json);
    Request request = new Request.Builder()
            .url(url)
            .post(body)
            .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 {

            Log.d(TAG, response.body().string());
        }
    });

}

POST提交键值对 
很多时候我们会需要通过POST方式把键值对数据传送到服务器。 OkHttp提供了很方便的方式来做这件事情。

private void post(String url, String json) throws IOException {
     OkHttpClient client = new OkHttpClient();
     RequestBody formBody = new FormBody.Builder()
             .add("name", "liming")
             .add("school", "beida")
             .build();

     Request request = new Request.Builder()
             .url(url)
             .post(formBody)
             .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 {
             String str = response.body().string();
             Log.i(TAG, str);
         }
     });
 }

异步上传文件 
上传文件本身也是一个POST请求 
定义上传文件类型

public static final MediaType MEDIA_TYPE_MARKDOWN
        = MediaType.parse("text/x-markdown; charset=utf-8");

将文件上传到服务器上:

private void postFile() {
    OkHttpClient mOkHttpClient = new OkHttpClient();
    File file = new File("/sdcard/demo.txt");
    Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
            .build();

    mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {

        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            Log.i(TAG, response.body().string());
        }
    });
}

添加如下权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

提取响应头 
典型的HTTP头 像是一个 Map

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    Request request = new Request.Builder()
            .url("https://api.github.com/repos/square/okhttp/issues")
            .header("User-Agent", "OkHttp Headers.java")
            .addHeader("Accept", "application/json; q=0.5")
            .addHeader("Accept", "application/vnd.github.v3+json")
            .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println("Server: " + response.header("Server"));
    System.out.println("Date: " + response.header("Date"));
    System.out.println("Vary: " + response.headers("Vary"));
}

Post方式提交String 
使用HTTP POST提交请求到服务。这个例子提交了一个markdown文档到web服务,以HTML方式渲染markdown。因为整个请求体都在内存中,因此避免使用此api提交大文档(大于1MB)。

private void postString() throws IOException {

    OkHttpClient client = new OkHttpClient();

    String postBody = ""
            + "Releases\n"
            + "--------\n"
            + "\n"
            + " * zhangfei\n"
            + " * guanyu\n"
            + " * liubei\n";

    Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
            .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 {
            System.out.println(response.body().string());

        }

    });
}

Post方式提交流
以流的方式POST提交请求体。请求体的内容由流写入产生。这个例子是流直接写入Okio的BufferedSink。你的程序可能会使用OutputStream,你可以使用BufferedSink.outputStream()来获取。

public static final MediaType MEDIA_TYPE_MARKDOWN
        = MediaType.parse("text/x-markdown; charset=utf-8");

private void postStream() throws IOException {
    RequestBody requestBody = new RequestBody() {
        @Override
        public MediaType contentType() {
            return MEDIA_TYPE_MARKDOWN;
        }

        @Override
        public void writeTo(BufferedSink sink) throws IOException {
            sink.writeUtf8("Numbers\n");
            sink.writeUtf8("-------\n");
            for (int i = 2; i <= 997; i++) {
                sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
            }
        }

        private String factor(int n) {
            for (int i = 2; i < n; i++) {
                int x = n / i;
                if (x * i == n) return factor(x) + " × " + i;
            }
            return Integer.toString(n);
        }
    };

    Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(requestBody)
            .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 {
            System.out.println(response.body().string());

        }

    });
}

Post方式提交表单

private void postForm() {
    OkHttpClient client = new OkHttpClient();

    RequestBody formBody = new FormBody.Builder()
            .add("search", "Jurassic Park")
            .build();

    Request request = new Request.Builder()
            .url("https://en.wikipedia.org/w/index.php")
            .post(formBody)
            .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 {
            System.out.println(response.body().string());

        }
    });
}

Post方式提交分块请求 
MultipartBody 可以构建复杂的请求体,与HTML文件上传形式兼容。多块请求体中每块请求都是一个请求体,可以定义自己的请求头。这些请求头可以用来描述这块请求,例如他的Content-Disposition。如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中。

private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

private void postMultipartBody() {
    OkHttpClient client = new OkHttpClient();

    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    MultipartBody body = new MultipartBody.Builder("AaB03x")
            .setType(MultipartBody.FORM)
            .addPart(
                    Headers.of("Content-Disposition", "form-data; name=\"title\""),
                    RequestBody.create(null, "Square Logo"))
            .addPart(
                    Headers.of("Content-Disposition", "form-data; name=\"image\""),
                    RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
            .build();

    Request request = new Request.Builder()
            .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
            .url("https://api.imgur.com/3/image")
            .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 {
            System.out.println(response.body().string());

        }
    });
}

响应缓存 ;
为了缓存响应,你需要一个你可以读写的缓存目录,和缓存大小的限制。这个缓存目录应该是私有的,不信任的程序应不能读取缓存内容。 
一个缓存目录同时拥有多个缓存访问是错误的。大多数程序只需要调用一次new OkHttpClient(),在第一次调用时配置好缓存,然后其他地方只需要调用这个实例就可以了。否则两个缓存示例互相干扰,破坏响应缓存,而且有可能会导致程序崩溃。 
响应缓存使用HTTP头作为配置。你可以在请求头中添加Cache-Control: max-stale=3600 ,OkHttp缓存会支持。你的服务通过响应头确定响应缓存多长时间,例如使用Cache-Control: max-age=9600。

int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(cacheDirectory, cacheSize);

OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.cache(cache);
OkHttpClient client = builder.build();

Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .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 {
        String response1Body = response.body().string();
        System.out.println("Response 1 response:          " + response);
        System.out.println("Response 1 cache response:    " + response.cacheResponse());
        System.out.println("Response 1 network response:  " + response.networkResponse());
    }

});

超时 
没有响应时使用超时结束call。没有响应的原因可能是客户点链接问题、服务器可用性问题或者这之间的其他东西。OkHttp支持连接,读取和写入超时。

private void ConfigureTimeouts() {

    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    OkHttpClient client = builder.build();

    client.newBuilder().connectTimeout(10, TimeUnit.SECONDS);
    client.newBuilder().readTimeout(10,TimeUnit.SECONDS);
    client.newBuilder().writeTimeout(10,TimeUnit.SECONDS);

    Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .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 {
            System.out.println("Response completed: " + response);
        }

    });

}

三、简单封装okHttp框架

新建一个工具类OkHttpUtils 
OkHttpClient必须是单例的,所以这里我们需要使用到单例设计模式,私有化构造函数,提供一个方法给外界获取OkHttpUtils实例对象

public class OkHttpUtils {

    private  static  OkHttpUtils mInstance;
    private OkHttpClient mHttpClient;

    private OkHttpUtils() {

    };

    public static  OkHttpUtils getInstance(){
        return  mInstance;
    }

}

一般网络请求分为get和post请求两种,但无论哪种请求都是需要用到request的,所以我们首先封装一个request,创建一个doRequest方法,在其内先编写mHttpClient.newCall(request).enqueue(new Callback())相关逻辑
 

public  void doRequest(final Request request){

    mHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {

        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {

        }
    });
}

我们需要自定义一个callback,BaseCallback,并将其传入request方法中

public class BaseCallback  {

}

在OkHttpUtils中编写get和post方法

public void get(String url){


}

public void post(String url,Map<String,Object> param){


}

post方法中构建request对象,这里我们需要创建一个buildRequest方法,用于生成request对象

private  Request buildRequest(String url,HttpMethodType methodType,Map<String,Object> params){
    return null;
}

这里需要定一个枚举对象HttpMethodType,用于区分是get还是post

enum  HttpMethodType{
    GET,
    POST
}

buildRequest方法根据HttpMethodType不同有相应的逻辑处理

private  Request buildRequest(String url,HttpMethodType methodType,Map<String,Object> params){
    Request.Builder builder = new Request.Builder().url(url);
    if (methodType == HttpMethodType.POST){
        builder.post(body);
    }
    else if(methodType == HttpMethodType.GET){
      builder.get();
    }
    return builder.build();
}

builder.post()方法中需要一个body,所以我们需要创建一个方法builderFormData()方法用于返回RequestBody,这里内部逻辑后面再进行完善

private RequestBody builderFormData(Map<String,Object> params){
    return null;
}

于是buildRequest方法变成了这样

private  Request buildRequest(String url,HttpMethodType methodType,Map<String,Object> params){
    Request.Builder builder = new Request.Builder().url(url);
    if (methodType == HttpMethodType.POST){
        RequestBody body = builderFormData(params);
        builder.post(body);
    }
    else if(methodType == HttpMethodType.GET){
        builder.get();
    }
    return builder.build();
}

get方法进行修改:

public void get(String url,BaseCallback callback){
    Request request = buildRequest(url,HttpMethodType.GET,null);
    doRequest(request,callback);
}

post方法进行修改:

public void post(String url,Map<String,Object> params,BaseCallback callback){
    Request request = buildRequest(url,HttpMethodType.POST,params);
    doRequest(request,callback);
}

完善builderFormData()方法

private RequestBody builderFormData(Map<String,String> params){
    FormBody.Builder builder =  new FormBody.Builder();

    if(params!=null){
        for(Map.Entry<String,String> entry:params.entrySet()){
            builder.add(entry.getKey(),entry.getValue());
        }
    }
    return builder.build();
}

BaseCallback中定义一个抽象方法onBeforeRequest,这样做的理由是我们在加载网络数据成功前,一般都有进度条等显示,这个方法就是用来做这些处理的

public abstract class BaseCallback  {
    public  abstract void onBeforeRequest(Request request);
}

OkHttpUtils的doRequest方法增加如下语句:
baseCallback.onBeforeRequest(request);
BaseCallback中多定义2个抽象方法

public abstract  void onFailure(Request request, Exception e) ;

/**
 *请求成功时调用此方法
 * @param response
 */
public abstract  void onResponse(Response response);
/**
 *
 * 状态码大于200,小于300 时调用此方法
 * @param response
 * @param t
 * @throws
 */
public abstract void onSuccess(Response response,T t) ;

/**
 * 状态码400,404,403,500等时调用此方法
 * @param response
 * @param code
 * @param e
 */
public abstract void onError(Response response, int code,Exception e) ;

/**
 * Token 验证失败。状态码401,402,403 等时调用此方法
 * @param response
 * @param code

 */
public abstract void onTokenError(Response response, int code);

由于Response的状态有多种,比如成功和失败,所以需要onResponse分解为3个抽象方法

/**
 *
 * 状态码大于200,小于300 时调用此方法
 * @param response
 * @param t
 * @throws
 */
public abstract void onSuccess(Response response,T t) ;

/**
 * 状态码400,404,403,500等时调用此方法
 * @param response
 * @param code
 * @param e
 */
public abstract void onError(Response response, int code,Exception e) ;

/**
 * Token 验证失败。状态码401,402,403 等时调用此方法
 * @param response
 * @param code

 */
public abstract void onTokenError(Response response, int code);

response.body.string()方法返回的都是String类型,而我们需要显示的数据其实是对象,所以我们就想抽取出方法,直接返回对象,由于我们不知道对象的类型是什么,所以我们在BaseCallback中使用范型

public abstract class BaseCallback<T>  

BaseCallback中需要将泛型转换为Type,所以要声明Type类型

public   Type mType;

BaseCallback中需要如下一段代码,将泛型T转换为Type类型

static Type getSuperclassTypeParameter(Class<?> subclass)
{
    Type superclass = subclass.getGenericSuperclass();
    if (superclass instanceof Class)
    {
        throw new RuntimeException("Missing type parameter.");
    }
    ParameterizedType parameterized = (ParameterizedType) superclass;
    return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}

在BaseCallback的构造函数中进行mType进行赋值

public BaseCallback()
{
    mType = getSuperclassTypeParameter(getClass());
}

OkHttpUtils中doRequest方法的onFailure与onResponse方法会相应的去调用baseCallback的方法

mHttpClient.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        baseCallback.onFailure(request,e);
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        if(response.isSuccessful()) {
            baseCallback.onSuccess(response,null);
        }else {
            baseCallback.onError(response,response.code(),null);
        }
        /*mGson.fromJson(response.body().string(),baseCallback.mType);*/
    }
});

onResponse方法中成功的情况又有区分,根据mType的类型不同有相应的处理逻辑,同时还要考虑Gson解析错误的情况

@Override
public void onResponse(Call call, Response response) throws IOException {
    if(response.isSuccessful()) {
        String resultStr = response.body().string();
        if (baseCallback.mType == String.class){
            baseCallback.onSuccess(response,resultStr);
        }
        else {
            try {
                Object obj = mGson.fromJson(resultStr, baseCallback.mType);
                baseCallback.onSuccess(response,obj);
            }
            catch (com.google.gson.JsonParseException e){ // Json解析的错误
                baseCallback.onError(response,response.code(),e);
            }
        }
    }else {
        baseCallback.onError(response,response.code(),null);
    }
}

构造函数中进行一些全局变量的初始化的操作,还有一些超时的设计

private OkHttpUtils() {

    mHttpClient = new OkHttpClient();
    OkHttpClient.Builder builder = mHttpClient.newBuilder();
    builder.connectTimeout(10, TimeUnit.SECONDS);
    builder.readTimeout(10,TimeUnit.SECONDS);
    builder.writeTimeout(30,TimeUnit.SECONDS);

    mGson = new Gson();
};

静态代码块初始化OkHttpUtils对象

static {
    mInstance = new OkHttpUtils();
}

在okHttpUtils内,需要创建handler进行UI界面的更新操作,创建callbackSuccess方法

private void callbackSuccess(final  BaseCallback callback , final Response response, final Object obj ){

    mHandler.post(new Runnable() {
        @Override
        public void run() {
            callback.onSuccess(response, obj);
        }
    });
}


doRequest方法的onResponse方法也进行相应的改写

if (baseCallback.mType == String.class){

    /*baseCallback.onSuccess(response,resultStr);*/
    callbackSuccess(baseCallback,response,resultStr);
}

创建callbackError方法

private void callbackError(final BaseCallback callback, final Response response, final Exception e) {

    mHandler.post(new Runnable() {
        @Override
        public void run() {
            callback.onError(response, response.code(), e);
        }
    });
}

将doRequest方法的onResponse方法中的baseCallback.onError(response,response.code(),e);替换为callbackError(baseCallback,response,e);方法

@Override
public void onResponse(Call call, Response response) throws IOException {
    if(response.isSuccessful()) {
        String resultStr = response.body().string();
        if (baseCallback.mType == String.class){
            /*baseCallback.onSuccess(response,resultStr);*/
            callbackSuccess(baseCallback,response,resultStr);
        }
        else {
            try {
                Object obj = mGson.fromJson(resultStr, baseCallback.mType);
                /*baseCallback.onSuccess(response,obj);*/
                callbackSuccess(baseCallback,response,obj);
            }
            catch (com.google.gson.JsonParseException e){ // Json解析的错误
                /*baseCallback.onError(response,response.code(),e);*/
                callbackError(baseCallback,response,e);
            }
        }
    }else {
        callbackError(baseCallback,response,null);
        /*baseCallback.onError(response,response.code(),null);*/
    }
}

至此,我们的封装基本完成。
OkHttp3源码分析

发布了205 篇原创文章 · 获赞 217 · 访问量 235万+

猜你喜欢

转载自blog.csdn.net/heng615975867/article/details/105268822