Okhttp3

    1. 简介
    1.支持http和https协议,api相同,易用; 
    2.http使用线程池,https使用多路复用;
    3.okhttp支持同步和异步调用; 
    4.支持普通form和文件上传form; 
    5.操作请求和响应(日志,请求头,body等); 
    6.okhttp可以设置缓存;
    7.支持透明的gzip压缩响应体
    1. 配置
    implementation 'com.squareup.okhttp3:okhttp:3.10.0'//okhttp3
    <uses-permission android name = "android.permission.INTERNET"/>
    1. 请求思路
get请求思路
  1.获取okHttpClient对象
  2.构建Request对象
  3.构建Call对象
  4.通过Call.enqueue(callback)方法来提交异步请求;execute()方法实现同步请求
post请求思路
  1.获取okHttpClient对象
  2.创建RequestBody
  3.构建Request对象
  4.构建Call对象
  5.通过Call.enqueue(callback)方法来提交异步请求;execute()方法实现同步请求
    1. get,post 同步和异步请求

异步请求(get)

        String url = "http://";

        //第一步获取okHttpClient对象
        OkHttpClient client = new OkHttpClient.Builder()
                .build();
        //第二步构建Request对象
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        //第三步构建Call对象
        Call call = client.newCall(request);
        //第四步:异步get请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i("onFailure", e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                    String result = response.body().string();
                    // 数据刷新必须在主线程中
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, result , Toast.LENGTH_SHORT).show();
                        }
                    });

                Log.i("result", result);
            }
        });

同步请求(get)

        String url = "http://";

        //第一步获取okHttpClient对象
        OkHttpClient client = new OkHttpClient.Builder()
                .build();
        //第二步构建Request对象
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        //第三步构建Call对象
        final Call call = client.newCall(request);
        //第四步:同步get请求
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Response response = call.execute();//必须子线程执行
                    String result = response.body().string();
                    Log.i("response", result);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

异步请求(post)

        //接口参数 String username,String password

        String url = "http://";
        //第一步创建OKHttpClient
        OkHttpClient client = new OkHttpClient.Builder()
                .build();
        //第二步创建RequestBody(Form表达)
        RequestBody body = new FormBody.Builder()
                .add("username", "admin")
                .add("password", "123456")
                .build();
        //第三步创建Rquest
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        //第四步创建call回调对象
        final Call call = client.newCall(request);
        //第五步发起请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i("onFailure", e.getMessage());
            }

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

同步请求(post)

        //接口参数 String username,String password

        String url = "http://";
        //第一步创建OKHttpClient
        OkHttpClient client = new OkHttpClient.Builder()
                .build();
        //第二步创建RequestBody
        RequestBody body = new FormBody.Builder()
                .add("username", "admin")
                .add("password", "123456")
                .build();
        //第三步创建Rquest
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        //第四步创建call回调对象
        final Call call = client.newCall(request);
        //第五步发起请求
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Response response = call.execute();
                    String result = response.body().string();
                    Log.i("response", result);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start(); 
  • 5.请求头处理(Header)以及超时和缓冲处理以及响应处理
        //超时设置
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(5,TimeUnit.SECONDS)
                .readTimeout(5,TimeUnit.SECONDS)
                .writeTimeout(5,TimeUnit.SECONDS)
                .cache(new Cache(cacheDirectory,10*1024*1024))
                .build();

        //表单提交
        RequestBody requestBody = new FormBody.Builder()
                .add("pno", "1")
                .add("ps","50")
                .add("dtype","son")
                .add("key","4a7cf244fd7efbd17ecbf0cb8e4d1c85")
                .build();

        //请求头设置
        Request request = new Request.Builder()
                .url(url)
                .addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
                .header("User-Agent", "OkHttp Example")
                .post(body)
                .build();
      
        //响应处理
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            //响应行
            Log.d("ok", response.protocol() + " " +response.code() + " " + response.message());
            //响应头
            Headers headers = response.headers();
            for (int i = 0; i < headers.size(); i++) {
                Log.d("ok", headers.name(i) + ":" + headers.value(i));
            }
            //响应体
            final String string = response.body().string();

            Log.d("ok", "onResponse: " + string);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    tx.setText(string);
                }
            });
        }

  • 6.请求体处理(Form表单,String字符串,流,文件)
        //1.POST方式提交String/JSON    application/json;json串
        MediaType mediaType1 = MediaType.parse("application/x-www-form-urlencoded;charset=utf-8");
        String requestBody = "pno=1&ps=50&dtype=son&key=4a7cf244fd7efbd17ecbf0cb8e4d1c85";
        RequestBody requestBody1 = RequestBody.create(mediaType1, requestBody);

        //POST方式提交JSON:传递JSON同时设置son类型头
        RequestBody requestBodyJson = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), "{}");
        request.addHeader("Content-Type", "application/json")//必须加json类型头
            
        //POST方式提交无参
        RequestBody requestBody1 = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded;charset=utf-8"), "");


        //2.POST方式提交流
        RequestBody requestBody2 = new RequestBody() {
            @Nullable
            @Override
            public MediaType contentType() {
                return MediaType.parse("application/x-www-form-urlencoded;charset=utf-8");
            }

            @Override
            public void writeTo(BufferedSink sink) throws IOException {
                sink.writeUtf8("pno=1&ps=50&dtype=son&key=4a7cf244fd7efbd17ecbf0cb8e4d1c85");
            }
        };

       //3.POST方式提交表单
        RequestBody requestBody4 = new FormBody.Builder()
                .add("pno", "1")
                .add("ps","50")
                .add("dtype","son")
                .add("key","4a7cf244fd7efbd17ecbf0cb8e4d1c85")
                .build();

        //4.POST提交文件
        MediaType mediaType3 = MediaType.parse("text/x-markdown; charset=utf-8");
        File file = new File("test.txt");
        RequestBody requestBody3 = RequestBody.create(mediaType3, file);

        //5.POST方式提交分块请求
        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(MediaType.parse("image/png"), new File("website/static/logo-square.png")))
                .build();
  • 7.拦截器(interator)
RetryAndFollowUp Intercaptors
Bridge interceptors
Cache interceptors
Connect interceptors
Callserver Intercaptors

一个完整的异步请求,主要有以下几点:new okhttpclient new Request(),通过client.newCall,传入request请求对象且返回一个Call对象,执行call.enqueue()来实现一个完整的请求逻辑。主要涉及几点:
1.构建okhttpclient对象的时候,会new Dispatcher()对象,Dispatcher主要用于维护同步和异步请求的状态。并维护一个线程池,有三个集合,一个异步等待集合,一个异步运行集合,一个是同步运行集合。
2.RealCall 对call接口的实现类,封装了请求服务器的数据和配置项目,同时处理执行具体同步和异步的操作。
3.interceptors 拦截器,一个完整的请求会依次执行以下几个拦截器,最终返回结果。
RetryAndFollowUpIntercaptors 重试和重定向拦截器
Bridgeinterceptors 桥接拦截器
Cacheinterceptors 缓存拦截器
Connectinterceptors 链接拦截器
CallserverIntercaptors 请求服务拦截器

4.其它拦截器

Application interceptors 应用拦截器
Network Interceptors 网络拦截器
Logging Interceptor 日志拦截器
一、Application Intercetor和NetworkInterceptor的区别

1,Application interceptors 应用拦截器

builder.addInterceptor(new LoggingInterceptor())

Application Interceptor 是第一个 Interceptor 因此它会被第一个执行,因此这里的 request 还是最原始的。而对于 response 而言呢,因为整个过程是递归的调用过程,因此它会在 CallServerInterceptor 执行完毕之后才会将 Response 进行返回,因此在 Application Interceptor 这里得到的 response 就是最终的响应,虽然中间有重定向,但是这里只关心最终的 response

1,不需要去关心中发生的重定向和重试操作。因为它处于第一个拦截器,会获取到最终的响应 
2,只会被调用一次,即使这个响应是从缓存中获取的。
3,只关注最原始的请求,不去关系请求的资源是否发生了改变,我只关注最后的 response 结果而已。
4,因为是第一个被执行的拦截器,因此它有权决定了是否要调用其他拦截,也就是 Chain.proceed() 方法是否要被执行。
5,因为是第一个被执行的拦截器,因此它有可以多次调用 Chain.proceed() 方法,其实也就是相当与重新请求的作用了。

2,Network Interceptors 网络拦截器

builder.addNetworkInterceptor(new LoggingInterceptor())

NetwrokInterceptor 处于第 6 个拦截器中,它会经过 RetryAndFollowIntercptor 进行重定向并且也会通过 BridgeInterceptor 进行 request 请求头和 响应 resposne 的处理,因此这里可以得到的是更多的信息。在打印结果可以看到它内部是发生了一次重定向操作,在上图可以看出,为什么 NetworkInterceptor 可以比 Application Interceptor 得到更多的信息了

1,因为 NetworkInterceptor 是排在第 6 个拦截器中,因此可以操作经过 RetryAndFollowup 进行失败重试或者重定向之后得到的resposne。
2,对于从缓存获取的 response 则不会去触发 NetworkInterceptor 。因为响应直接从 CacheInterceptor 返回了。
3,观察数据在网络中的传输。
4,可以获得装载请求的连接。
二、拦截器的应用

1.日志拦截器

在 LoggingInterceptor 中主要做了 3 件事:
1,请求前-打印请求信息;
2,网络请求-递归去调用其他拦截器发生网络请求;
3,网络响应后-打印响应信息

OkHttpClient client = builder
            .addInterceptor(new LoggingInterceptor())//应用拦截器
            .addNetworkInterceptor(new LoggingInterceptor())//网络拦截器
            .readTimeout(5, TimeUnit.SECONDS)
            .connectTimeout(5, TimeUnit.SECONDS)
            .writeTimeout(5, TimeUnit.SECONDS)
            .build();

class LoggingInterceptor implements Interceptor {

    private static final String TAG = "LoggingInterceptor";

    @Override
    public Response intercept(Chain chain) throws IOException {

        Request request = chain.request();

        //1.请求前--打印请求信息
        long startTime = System.nanoTime();
        Log.d(TAG, String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));

        //2.网络请求
        Response response =  chain.proceed(request);

        //3.网络响应后--打印响应信息
        long endTime = System.nanoTime();
        Log.d(TAG, String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (endTime - startTime) / 1e6d, response.headers()));

        return response;
    }
}

2.缓存拦截器

在 MyCacheinterceptor 中主要做了(post方式无法缓存)
1,设置缓存位置
2,无网时:设置缓存协议
3,有网:加载网络数据;无网:加载缓存数据

OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addInterceptor(myCacheinterceptor)//应用拦截器
            .addNetworkInterceptor(myCacheinterceptor)//网络拦截器
            .connectTimeout(5, TimeUnit.SECONDS)
            .cache(new Cache(new File(getCacheDir(), "Cache"), 1024 * 1024 * 10))
            .build();

class MyCacheinterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {

        Request request = chain.request();

        if (!isNetworkAvailable(MainActivity.this)) {
            request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();

        }

        Response originalResponse = chain.proceed(request);

        if (isNetworkAvailable(MainActivity.this)) {
            int maxAge = 0;
            return originalResponse.newBuilder()
                    .removeHeader("Pragma")
                    .header("Cache-Control", "public ,max-age=" + maxAge)
                    .build();
        } else {
            int maxStale = 15*60;
            return originalResponse.newBuilder()
                    .removeHeader("Pragma")
                    .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                    .build();
        }

    }
}

/**
    检测是否有网
 */
public static boolean isNetworkAvailable(Context context) {
    if (context != null) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo info = cm.getActiveNetworkInfo();
        if (info != null) {
            return info.isAvailable();
        }
    }
    return false;
}
  • 8 .注意事项
1,推荐让 OkHttpClient 保持单例,用同一个 OkHttpClient 实例来执行你的所有请求,因为每一个 OkHttpClient 实例都拥有自己的连接池和线程池,重用这些资源可以减少延时和节省资源,如果为每个请求创建一个 OkHttpClient 实例,显然就是一种资源的浪费。

2,response.body().string()只调用一次

3,每一个Call(其实现是RealCall)只能执行一次,否则会报异常

4,子线程加载数据后,主线程刷新数据
  • 9 .HttpUrlCollection 及 Okhttp3 的对比分析
1,HttpUrlConnection,google官方提供的用来访问网络,但是实现的比较简单,只支持1.0/1.1
2,并没有多路复用,如果碰到app大量网络请求的时候,性能比较差,
3,HttpUrlConnection底层也是用Socket来实现的
4,OkHttp像HttpUrlConnection一样,实现了一个网络连接的过程。
5,OkHttp和HttpUrlConnection是一级的,用socket实现了网络连接,OkHttp更强大,
6,HttpUrlConnection在IO方面用到的是InputStream和OutputStream,但OkHttp用的是sink和source,这两个是在Okio这个开源库里的,    feredSink(支持缓冲)、GzipSink(支持Gzip压缩)、ForwardingSink和InflaterSink(后面这两者服务于GzipSink)
7,多个相同host和port的stream可以共同使用一个socket,而RealConnection就是处理连接的,那也就是说一个RealConnection上可能有很多个Stream
8,OkHttp代码比HttpURLConnection精简的多
  • 10.OkHttp源码分析(设计模式,线程池的使用)
单利、多线程安全问题
/**
  饿汉式
 */
public class Person {

    //1.构造函数私有化
    private Person(){}

    //2.创建单个私有对象对象
    private static  Person person = new Person();

    //3.提供对外公开访问方法
    public static Person getPerson() {
        return person;
    }
}

/**
 * 懒汉式
 *
 * 多线程安全问题:
 *      1.多个线程同时操作
 *      2.共用同一个资源
 *      3.分步执行操作同一个资源
 *
 * 多线程安全解决方式:
 *      1.同步方法
 *      2.同步代码块
 *      3.锁机制
 */
public class Student {

    //1.构造函数私有化
    private Student(){}

    //2.创建单个私有对象对象
    private static  Student student;

    //3.提供对外公开访问方法
    public static Student getInstance(){

        //解决线程安全问题
        if (student == null){
            synchronized (Student.class){
                if (student == null) {
                    student = new Student();
                }
            }
        }

        return student;
    }
}
  • 10.OkHttpUtils工具类抽取
public class OkHttpUtils {

    private static OkHttpUtils okHttpUtils;
    private static OkHttpClient okHttpClient;
    private static Handler mHandler;

    /**
     * 构造初始化
     */
    private OkHttpUtils(){
        /**
         * 构建OkHttpClient
         */
        okHttpClient = new OkHttpClient.Builder()
        /**
         * 请求的超时时间
         */
        .readTimeout(5000, TimeUnit.MILLISECONDS)
        /**
         * 设置响应的超时时间
         */
        .writeTimeout(5000, TimeUnit.MILLISECONDS)
        /**
         * 设置连接的超时时间
         */
        .connectTimeout(5000, TimeUnit.MILLISECONDS)
        /**
         * 构建
         */
        .build();


        /**
         * 获取主线程的handler
         */
        mHandler = new Handler(Looper.getMainLooper());
    }

    /**
     * 通过单例模式构造对象
     * @return
     */
    public static OkHttpUtils getInstance(){
        if (OkHttpUtils == null){
            synchronized (OkHttpUtils.class){
                if (okHttpUtils == null){
                    okHttpUtils = new OkHttpUtils();
                }
            }
        }
        return okHttpUtils;
    }

    /**
     * 构造Get请求,封装对用的Request请求,实现方法
     * @param url  访问路径
     * @param realCallback  接口回调
     */
    private void getRequest(String url, final RealCallback realCallback){

        Request request = new Request.Builder().url(url).get().build();
        deliveryResult(realCallback, okHttpClient.newCall(request));
    }

    /**
     * 构造post 请求,封装对用的Request请求,实现方法
     * @param url 请求的url
     * @param requestBody 请求参数
     * @param realCallback 结果回调的方法
     */
    private void postRequest(String url, RequestBody requestBody, final RealCallback realCallback){

        Request request = new Request.Builder().url(url).post(requestBody).build();
        deliveryResult(realCallback, okHttpClient.newCall(request));
    }

    /**
     * 处理请求结果的回调:主线程切换
     * @param realCallback
     * @param call
     */
    private void deliveryResult(final RealCallback realCallback, Call call) {
        call.enqueue(new Callback() {
            @Override
            public void onFailure(final Call call, final IOException e) {
                sendFailureThread(call, e, realCallback);
            }

            @Override
            public void onResponse(final Call call, final Response response) throws IOException {
                sendSuccessThread(call, response, realCallback);
            }
        });
    }

    /**
     * 发送成功的回调
     * @param call
     * @param response
     * @param realCallback
     */
    private void sendSuccessThread(final Call call, final Response response, final RealCallback
            realCallback) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                realCallback.onResponse(call,response);
            }
        });
    }

    /**
     * 发送失败的回调
     * @param call
     * @param e
     * @param realCallback
     */
    private void sendFailureThread(final Call call, final IOException e, final RealCallback realCallback) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                realCallback.onFailure(call,e);
            }
        });
    }

    //-----------对外公共访问的get/post方法-----------
    /**
     * get请求
     * @param url  请求url
     * @param realCallback  请求回调
     */
    public void get(String url, final RealCallback realCallback){
        getRequest(url,realCallback);
    }

    /**
     * post请求
     * @param url       请求url
     * @param realCallback  请求回调
     * @param requestBody    请求参数
     */
    public void post(String url, RequestBody requestBody, final RealCallback realCallback){
        postRequest(url,requestBody,realCallback);
    }

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

推荐阅读更多精彩内容