OkHttp基本使用及源码简析

作者: 落魄的安卓开发 | 来源:发表于2017-08-09 17:38 被阅读104次

    使用一个框架要问自己你为什么选择这个框架,它的优势在哪里,以及它的基本原理是怎么样的?

    OkHttp的优势

    1. 支持SPDY,那这个SPDY又是什么呢?
      SPDY 是 Google 开发的基于传输控制协议 (TCP) 的应用层协议。SPDY 协议旨在通过压缩、多路复用和优先级来缩短网页的加载时间和提高安全性。(SPDY 是 Speedy 的昵音,意思是更快)

      SPDY 协议只是在性能上对 HTTP 做了很大的优化,其核心思想是尽量减少连接个数,而对于 HTTP 的语义并没有做太大的修 改。具体来说是,SPDY 使用了 HTTP 的方法和页眉,但是删除了一些头并重写了 HTTP 中管理连接和数据转移格式的部分,所以基本上是兼容 HTTP 的。

      更多关于SPDY

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

    3. 支持透明的gzip压缩响应体

    4. 通过缓存避免重复的请求

    5. 请求失败时自动重试主机的其他ip,自动重定向

    6. Api使用起来比较方便

    OkHttp使用

    get

    public void get(View view) {
        OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
        Request request = new Request.Builder().get().url("http:www.baidu.com").build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }
    
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                LogUtil.loge("获取是否成功:" + response.isSuccessful());
                if (response.isSuccessful()) {
                    LogUtil.loge("get获取的数据" + response.body().string());
                }
            }
        });
    }
    
    public void getWithParams(View view) {
        Uri.Builder builder = Uri.parse("http://api.qmyg.weiduanxian.com:7000/ebuyApp/indexPage/getAppVersion.json").buildUpon();
        builder.appendQueryParameter("platform", "android");
        String address = builder.build().toString();
    
        OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
        Request request = new Request.Builder().get().url(address).tag("get").build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                LogUtil.loge("onFailure" + e.getMessage());
            }
    
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                LogUtil.loge("获取是否成功:" + response.isSuccessful());
                if (response.isSuccessful()) {
                    LogUtil.loge("getWithParams获取的数据" + response.body().string());
                }
            }
        });
    }
    

    加载图片

    public void getImage(final OutputStream outputStream) {
        String imageUrl = "http://www.itlanbao.com/img/islider/a.jpg";
        OkHttpClient client = new OkHttpClient.Builder().build();
        Request.Builder requestBuild = new Request.Builder();
        Request request = requestBuild.get().url(imageUrl).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 {
                if (!response.isSuccessful()) {
                    return;
                }
                final InputStream inputStream = response.body().byteStream();
                ByteArrayOutputStream bios = new ByteArrayOutputStream();
                byte[] bytes = new byte[512];
                int len = -1;
                while ( (len = inputStream.read(bytes)) != -1 ){
                    bios.write(bytes,0,len);
                }
                bios.flush();
    
                final ByteArrayInputStream is =  new ByteArrayInputStream(bios.toByteArray());
    
                Log.e("result", "拿到数据了么" + inputStream);
                imageView.post(new Runnable() {
                    @Override
                    public void run() {
                        Bitmap bitmap = BitmapFactory.decodeStream(is);
                        Log.e("result", "拿到Bitmap了吗:" + bitmap);
                        imageView.setImageBitmap(bitmap);
                    }
                });
            }
        });
    }
    

    注意:这里使用OkHttp加载图片的时候,使用Response.body.byteStream()直接decodeStreame得到的bitmap是空的,为什么呢?因为: inputstream 被重复调用了,也就是被解析了多次,当你用response.getbody().byteStream() 时,其实已经被解析了,你这个时候获取获取的尾节点。怎么办?我们可以用 ByteArrayOutputStream 解析 response.getbody().byteStream() ,然后把它转成 inputstream 就可以解析了。

    post

    public void post(View view) {
        OkHttpClient okHttpClient = new OkHttpClient();
        RequestBody body = new FormBody.Builder().add("infoUpd", "2").build();
        Request request = new Request.Builder().url(HOST + "indexPage/getMsgList.json").post(body).build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }
    
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                LogUtil.loge("获取是否成功:" + response.isSuccessful());
                if (response.isSuccessful()) {
                    LogUtil.loge("post获取的数据" + response.body().string());
                }
            }
        });
    }
    

    上传文件

    public void uploadFile(View view) {
        ImageCache.getImageCache(this).putBitmap("test", BitmapFactory.decodeResource(getResources(), R.drawable.img_empty_address));
        File img = ImageCache.getImageCache(this).getImageFile("test");
        if (!img.exists()) {
            Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show();
            return;
        }
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new MultipartBody.Builder()
                .addFormDataPart("avatar", "app.jpg", RequestBody.create(MEDIA_TYPE_PNG, img))
                .addFormDataPart("userId", "9394D4AE36244264871FA32CE1757149")
                .build();
    
        final Request request = new Request.Builder().post(body).url(HOST + "users/editAvatar.json").tag("").build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                LogUtil.loge("上传失败");
            }
    
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response != null && response.isSuccessful()) {
                    LogUtil.loge("上传是否成功:" + response.body().string());
                }
            }
        });
    }
    

    下载文件

    public void downloadFile(View view) {
        String absolutePath = Environment.getExternalStorageDirectory().getAbsolutePath();
        final File file = new File(absolutePath + "/test.png");
        OkHttpClient client = new OkHttpClient();
        Call call = client.newCall(new Request.Builder().url("http://7xt07n.com2.z0.glb.qiniucdn.com/ebuy_user_avater_img_20170808142716_972_89573769").build());
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }
    
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                InputStream inputStream = response.body().byteStream();
                //内容总长度
                final long totalLength = response.body().contentLength();
                FileOutputStream fileOutputStream = null;
                try {
                    fileOutputStream = new FileOutputStream(file);
                    //一次读取的buffer
                    byte[] buffer = new byte[1024];
                    int len = 0;
                    while ((len = inputStream.read(buffer)) != -1) {
                        //读取的进度
                        read += len;
                        tvProgress.post(new Runnable() {
                            @Override
                            public void run() {
                                tvProgress.setText(read + "/" + totalLength);
                            }
                        });
                        fileOutputStream.write(buffer, 0, len);
                    }
                    //写完刷新
                    fileOutputStream.flush();
                } catch (Exception e) {
                    if (fileOutputStream != null) {
                        fileOutputStream.close();
                    }
                }
            }
        });
    }
    

    使用缓存

    public void cache(View view) {
        Uri.Builder builder = Uri.parse("http://api.qmyg.weiduanxian.com:7000/ebuyApp/indexPage/getAppVersion.json").buildUpon();
        builder.appendQueryParameter("platform", "android");
        String address = builder.build().toString();
    
        File file = new File(getCacheDir().getAbsolutePath());
        if (!file.exists()) {
            file.mkdirs();
        }
        Cache cache = new Cache(file, 1024 * 10 * 10);
        OkHttpClient okHttpClient = new OkHttpClient.Builder().cache(cache).build();
        Request request = new Request.Builder().url(address).build();
        Call call = okHttpClient.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());
            }
        });
    }
    

    简单封装

    1. 保证OkHttpClient是单例模式的
    2. 其实不同的请求都是在构建不同的Request
    3. 写一个公共的Callback,根据传入的类型来进行不同的返回,比如StringCallback、BitmapCallback

    代码如下:

    public static AreYouOk getAreYouOk() {
        if (areYouOk == null) {
            synchronized (AreYouOk.class) {
                if (areYouOk == null) {
                    areYouOk = new AreYouOk();
                }
            }
        }
        return areYouOk;
    }
    
    public Request buildGetRequest(String url) {
        Request.Builder getBuilder = new Request.Builder();
        return getBuilder.url(url).tag(url).build();
    }
    
    public void get(String url, AreYouCallback callback) {
        if (TextUtils.isEmpty(url) || callback == null)
            return;
        Call call = okHttpClient.newCall(buildGetRequest(url));
        execute(call, callback);
    }
    
     /**
     * 执行异步操作
     */
    public void execute(Call call, final AreYouCallback callback) {
        if (callback == null || call == null)
            return;
        callback.onBefore();
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                loadFailure(call, e, callback);
            }
    
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                try {
                    Object o = callback.parseResponse(response);
                    loadSuccess(o, response, callback);
                } catch (Exception e) {
                    callback.onError(e);
                }
            }
        });
    }
    
     private void loadSuccess(final Object o, final Response response, final AreYouCallback callback) {
        if (response.isSuccessful()) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    callback.onSusccess(o);
                    callback.onAfter();
                }
            });
        }
    }
    
    private void loadFailure(Call call, final IOException e, final AreYouCallback areYouCallback) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                areYouCallback.onError(e);
            }
        });
    }
    

    Demo AreYouOk地址


    源码简析

    OkHttp.newCall(request):内部调用了

    @Override 
    public Call newCall(Request request) {
        return new RealCall(this, request, false /* for web socket */);
    }
    

    所以Call.enqueue实际上就是RealCall执行的enqueue方法如下:

    @Override 
    public void enqueue(Callback responseCallback) {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        captureCallStackTrace();
        client.dispatcher().enqueue(new AsyncCall(responseCallback));
    }
    

    可见最终是调用了Dispatcher(任务调度类)来执行enqueue,传入的这个参数AsyncCall是RealCall的内部类,RealCall实现了Runnable。

    Dispatcher任务调度类

    Dispatcher主要用于控制并发的请求,它主要维护了以下变量:

      /** 最大并发请求数*/
      private int maxRequests = 64;
      /** 每个主机最大请求数*/
      private int maxRequestsPerHost = 5;
      /** 消费者线程池 */
      private ExecutorService executorService;
      /** 将要运行的异步请求队列 */
      private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
      /**正在运行的异步请求队列 */
      private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
      /** 正在运行的同步请求队列 */
      private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
    

    构造方法

     public Dispatcher(ExecutorService executorService) {
        this.executorService = executorService;
      }
    
      public Dispatcher() {
      }
    
      public synchronized ExecutorService executorService() {
        if (executorService == null) {
          executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
              new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
        }
        return executorService;
      }
    

    Dispatcher有两个构造函数,可以使用自己设定线程池,如果没有设定线程池则会在请求网络前自己创建线程池,这个线程池类似于CachedThreadPool比较适合执行大量的耗时比较少的任务。

    到这里我们再看Disptacher是如何执行的:

    synchronized void enqueue(AsyncCall call) {
        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
          runningAsyncCalls.add(call);
          executorService().execute(call);
        } else {
          readyAsyncCalls.add(call);
        }
    }
    

    只有当正在运行的任务数<64 && 同一个host访问数量 < 每个主机最大请求数5,就会将该任务添加到正在执行的任务中,并从线程池中开一个线程去执行,如果不满足条件就会添加到准备去执行的列表中去。

    去执行的这个AsyncCall是RealCall的内部类,是一个Runnable,实现了execute()方法,如下:

    @Override 
    protected void execute() {
        boolean signalledCallback = false;
        try {
          Response response = getResponseWithInterceptorChain();
            if (retryAndFollowUpInterceptor.isCanceled()) {
            signalledCallback = true;
            responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
          } else {
            signalledCallback = true;
            responseCallback.onResponse(RealCall.this, response);
          }
        } catch (IOException e) {
          if (signalledCallback) {
            // Do not signal the callback twice!
            Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
          } else {
            responseCallback.onFailure(RealCall.this, e);
          }
        } finally {
          client.dispatcher().finished(this);
        }
      }
    }
    

    看到其中:

       Response response = getResponseWithInterceptorChain();
    

    这里的response就是Callback中返回的Response,然后我们跟进去getResponseWithInterceptorChain()这个方法去看看里面具体怎么进行的网络请求:

    Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        interceptors.addAll(client.interceptors());
        interceptors.add(retryAndFollowUpInterceptor);
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        interceptors.add(new CacheInterceptor(client.internalCache()));
        interceptors.add(new ConnectInterceptor(client));
        if (!forWebSocket) {
            interceptors.addAll(client.networkInterceptors());
        }
        interceptors.add(new CallServerInterceptor(forWebSocket));
    
        Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
        return chain.proceed(originalRequest);
    }
    

    这里看到将多个拦截器添加到了拦截器集合中,然后作为构造参数初始化了RealInterceptorChin,主要看这个拦截器的proceed方法:

       public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
          RealConnection connection) throws IOException {
          if (index >= interceptors.size()) throw new AssertionError();
    
          calls++;
    
          // If we already have a stream, confirm that the incoming request will use it.
          if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
            throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
          }
    
          // If we already have a stream, confirm that this is the only call to chain.proceed().
          if (this.httpCodec != null && calls > 1) {
            throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
          }
    
          // Call the next interceptor in the chain.     
          RealInterceptorChain next = new RealInterceptorChain(
          interceptors, streamAllocation, httpCodec, connection, index + 1, request);
          //递归调用Chain.process()!!!!!!!!!!!!!!!!!!!!!!!!
          Interceptor interceptor = interceptors.get(index);
          Response response = interceptor.intercept(next);
    
         // Confirm that the next interceptor made its required call to chain.proceed().
         if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
              throw new IllegalStateException("network interceptor " + interceptor
                + " must call proceed() exactly once");
          }
    
         // Confirm that the intercepted response isn't null.
         if (response == null) {
            throw new NullPointerException("interceptor " + interceptor + " returned null");
         }
    
        return response;
      }
    

    Chain与Interceptor会互相递归调用,直到链的尽头。
    我们看到,通过职责链模式,清楚地切开了不同的逻辑,每个拦截器完成自己的职责,从而完成用户的网络请求。
    大概流程是:

    1. 先经过用户拦截器

    2. RetryAndFollowUpInterceptor负责自动重试和进行必要的重定向

    3. BridgeIntercetor负责将用户Request转换成一个实际的网络请求的Request,再调用下层的拦截器获取Response,最后再将网络Response转换成用户的Reponse

    4. CacheInterceptor负责控制缓存

    5. ConnectInterceptor负责进行连接主机

    6. 网络拦截器进行拦截

    7. CallServerInterceptor是真正和服务器通信,完成http请求

    回到上面的RealCall的execute方法,执行到最后可以看到:在AsyncCall的run()方法中进行网络请求,不管网络请求是否成功:最终在finally中都会调用Dispatcher.finish(this),结束掉当前任务,并且去runningAsyncCalls和readyAsyncCalls中去找接下来要执行的任务,代码如下:

    private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
          if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
          if (promoteCalls) promoteCalls();//去
          runningCallsCount = runningCallsCount();
          idleCallback = this.idleCallback;
        }
    
        if (runningCallsCount == 0 && idleCallback != null) {
          idleCallback.run();
        }
    }
    
    
    
    private void promoteCalls() {
        if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
        if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
    
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
          AsyncCall call = i.next();
    
          if (runningCallsForHost(call) < maxRequestsPerHost) {
            i.remove();
            runningAsyncCalls.add(call);
            executorService().execute(call);
          }
    
          if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
        }
     }
    
    okhttp_full_process.png

    一图了然。图是借用了下面链接大佬的图。

    访问网络整体流程:

    1. OkHttpClient.newCall()

    2. 实际上是初始化RealCall

    3. RealCall.enqueue 其实是调用Dispatch这个任务调度类来执行任务即:调用了Dispatch的enqueue

    4. 在Dispatch方法中 只有当正在运行的任务数<64 && 同一个host访问数量 < 每个主机最大请求数5,就会将该任务添加到正在执行的任务总,并从线程池中开一个线程去执行,如果不满足条件就会添加到准备去执行的列表中去。

    5. 在Dispatch中执行的Call是RealCall的一个内部类,AsyncCall extends Runable,在AsyncCall的run()方法中进行网络请求,不管网络请求是否成功:最终都会调用Dispatcher.finish(this),结束掉当前任务

    6. 在Dispatcher的结束方法中会调用:promoteCalls()如下:

       private void promoteCalls() {
         if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
         if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
      
         for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
           AsyncCall call = i.next();
      
           if (runningCallsForHost(call) < maxRequestsPerHost) {
             i.remove();
             runningAsyncCalls.add(call);
             executorService().execute(call);
           }
      
           if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
         }
       }
      

    这个方法中呢,就是如果有等待的任务就去执行任务,如果正在执行的任务数>最大请求数和如果等待请求的数目为0就返回。

    1. 那是如何进行请求数据的呢?我们

    Pisay

    深入理解

    还有这

    相关文章

      网友评论

        本文标题:OkHttp基本使用及源码简析

        本文链接:https://www.haomeiwen.com/subject/makxzttx.html