Android OkHttp使用和源码详解(上)

简介: OkHttp 是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说,OkHttp 现在几乎已经占据了所有的网络请求操作,RetroFit + OkHttp 实现网络请求似乎成了一种标配。因此它也是每一个 Android 开发工程师的必备技能,了解其内部实现原理可以更好地进行功能扩展、封装以及优化。

介绍




     OkHttp 是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说,OkHttp 现在几乎已经占据了所有的网络请求操作,RetroFit + OkHttp 实现网络请求似乎成了一种标配。因此它也是每一个 Android 开发工程师的必备技能,了解其内部实现原理可以更好地进行功能扩展、封装以及优化。


       适用于 Android 和 Java 应用程序的 HTTP 和 HTTP/2 客户端。


       OkHttp的4.0.x版本已经全部由java替换到了Kotlin,API的一些使用也会有些不同。


     Github传送门


    文档和 API


要求


支持的版本


      4.0.x :Android 5.0+(API 级别 21+)和 Java 8+。


     3.12.x :Android 2.3+(API 级别 9+)和 Java 7+。平台可能不支持 TLSv1.2。(2021-12-31不再支持)


       OkHttp有一个库的依赖Okio,用于高性能I/O一个小型library。它适用于 Okio 1.x(用 Java 实现)或 Okio 2.x(升级到  Kotlin)。


  本文使用的OkHttp的版本为3.14.2,不是不会接入高版本,主要是4.0.x版本已经全部由java替换到了Kotlin,Kotlin不太熟怕理解错了,误导人民群众。


dependencies {
    //本文使用
    implementation 'com.squareup.okio:okio:1.15.0'
    implementation 'com.squareup.okhttp3:okhttp:3.14.2'
    //高版本
    // define a BOM and its version
    implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.0"))
    // define any required OkHttp artifacts without version
    implementation("com.squareup.okhttp3:okhttp")
    implementation("com.squareup.okhttp3:logging-interceptor")
}


网络请求流程分析


       OkHttp 经过几次迭代后,已经发生了很多变化。更好的 WebSocket 支持、更多的 Interceptor 责任链,甚至连最核心的 HttpEngine 也变成了 HttpCodec。本文会重新梳理整个网络请求的流程,以及实现机制。


       先看下 OkHttp 的基本使用:


public void okHttp(String url){
        //创建OkHttpClient对象
        OkHttpClient client = new OkHttpClient();
        //创建Request
        Request request = new Request.Builder()
                .url(url)
                .build();
        //创建Call对象client.newCall(request)
        //通过execute()方法获得请求响应的Response对象
        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()){
                    String result = response.body().string();
                    //处理UI需要切换到UI线程处理
                }
            }
        });
    }


除了直接 new OkHttpClient 之外,还可以使用内部工厂类 Builder 来设置 OkHttpClient。如下所示:


    public void buildHttp(String url){
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(15, TimeUnit.SECONDS)//设置超时
                .addInterceptor(interceptor)    //拦截器
                .proxy(proxy)       //设置代理
                .cache(cache);      //设置缓存
        Request request = new Request.Builder()
                .url(url)
                .build();
        builder.build().newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {}
            @Override
            public void onResponse(Call call, Response response) throws IOException {}
        });
    }


请求操作的起点从 OkHttpClient.newCall().enqueue() 方法开始


OkHttpClient.newCall


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


 RealCall.newRealCall.java


  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
  }


这个方法会返回一个 RealCall 对象,通过它将网络请求操作添加到请求队列中。


RealCall.enqueue


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


client.dispatcher()返回Dispatcher,调用 Dispatcher 的 enqueue 方法,执行一个异步网络请求的操作。


Dispatcher 是 OkHttpClient 的调度器,是一种门户模式。主要用来实现执行、取消异步请求操作。本质上是内部维护了一个线程池去执行异步操作,并且在 Dispatcher 内部根据一定的策略,保证最大并发个数、同一 host 主机允许执行请求的线程个数等。


Dispatcher.enqueue


1.  void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    promoteAndExecute();
  }


   实际上就是使用线程池执行了一个 AsyncCall,而 AsyncCall 继承了 NamedRunnable,NamedRunnable 实现了 Runnable 接口,因此整个操作会在一个子线程(非 UI 线程)中执行。


NamedRunnable


/**
 * Runnable implementation which always sets its thread name.
 */
public abstract class NamedRunnable implements Runnable {
  protected final String name;
  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }
  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }
  protected abstract void execute();
}


   在 run 方法中执行了 一个抽象方法 execute 这个抽象方法被 AsyncCall 实现。


AsyncCall.execute


@Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
        Response response = getResponseWithInterceptorChain();
        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);
      }
    }1.


从上面看出而真正获取请求结果的方法是在 getResponseWithInterceptorChain方法中,从名字也能看出其内部是一个拦截器的调用链。


RealCall.getResponseWithInterceptorChain


  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    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, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    boolean calledNoMoreExchanges = false;
    try {
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }


Interceptor:拦截器是一种强大的机制,可以监视、重写和重试调用。


每一个拦截器的作用如下:


  • BridgeInterceptor:主要对 Request 中的 Head 设置默认值,比如 Content-Type、Keep-Alive、Cookie 等。


  • CacheInterceptor:负责 HTTP 请求的缓存处理。


  • ConnectInterceptor:负责建立与服务器地址之间的连接,也就是 TCP 链接。


  • CallServerInterceptor:负责向服务器发送请求,并从服务器拿到远端数据结果。


  • RetryAndFollowUpInterceptor:此拦截器从故障中恢复,并根据需要执行重定向。如果呼叫被取消,它可能会引发IOException。


在添加上述几个拦截器之前,会调用 client.interceptors 将开发人员设置的拦截器添加到列表当中。


       对于 Request 的 Head 以及 TCP 链接,我们能控制修改的成分不是很多。所以咱们了解 CacheInterceptor CallServerInterceptor


相关文章
|
5天前
|
开发工具 Android开发 git
Windows下载android2.2完整源码(转)
Windows下载android2.2完整源码(转)
16 3
|
22天前
|
JSON 编译器 开发工具
VS Code阅读Android源码
VS Code阅读Android源码
31 1
|
1月前
|
XML Java Android开发
Android实现自定义进度条(源码+解析)
Android实现自定义进度条(源码+解析)
59 1
|
1月前
|
Java Android开发
Android反编译查看源码
Android反编译查看源码
37 0
|
21天前
|
Java Android开发
Android系统 修改无源码普通应用为默认Launcher和隐藏Settings中应用信息图标
Android系统 修改无源码普通应用为默认Launcher和隐藏Settings中应用信息图标
54 0
|
5天前
|
Java 开发工具 Android开发
如何在Eclipse中查看Android源码或者第三方组件包源码(转)
如何在Eclipse中查看Android源码或者第三方组件包源码(转)
13 4
|
7天前
|
Java Android开发
Android12 双击power键启动相机源码解析
Android12 双击power键启动相机源码解析
22 0
|
4天前
|
Android开发
在android源码中编译ADW_Launcher
在android源码中编译ADW_Launcher
10 2
|
7天前
|
Android开发
Android 高通平台集成无源码apk示例
Android 高通平台集成无源码apk示例
15 0
|
7天前
|
Android开发
Android修改源码实现root
Android修改源码实现root
9 0