Android 监控软键盘确定 搜索 按钮并赋予点击事件

android的实践开发中,为了界面的美观,往往那些搜索框并没有带搜索按钮,而是调用了软键盘的搜索按钮,完成这次时间
  • 1
  • 2

这里写图片描述
这里写图片描述

好吧!直接上代码!

<EditText
        android:id="@+id/my_chat_seach"
        android:layout_width="fill_parent"
        android:layout_height="23dp"
        android:layout_centerVertical="true"
        android:layout_marginRight="6dip"
        android:layout_toRightOf="@id/my_seach_item_1_button"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:hint="@string/search"
        android:imeOptions="actionSearch"
        android:singleLine="true"
        android:textColor="#8e8787"
        android:textSize="13sp" />

xml配置文件中 *重要的一个属性是: android:imeOptions=”actionSearch”,从而调用软键盘时,回车键就会显示搜索二字。
同时在androidMainfest.xml文件中在此Activity中写入 android:windowSoftInputMode=”adjustPan”,可以防止软键盘会把原来的界面挤上去的问题。
那么在该activity中,如何操作呢?

seachEditText = (EditText) findViewById(R.id.my_chat_seach);
watchSearch();

然后

/**
     * @方法说明:监控软键盘的的搜索按钮
     * @方法名称:watchSearch
     * @返回值:void
     */
    public void watchSearch() {
        seachEditText.setOnEditorActionListener(new OnEditorActionListener() {

            @Override
            public boolean onEditorAction(TextView v, int actionId,
                    KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                    // 先隐藏键盘
                    ((InputMethodManager) seachEditText.getContext()
                            .getSystemService(Context.INPUT_METHOD_SERVICE))
                            .hideSoftInputFromWindow(ChatFriendsGroudSeach.this
                                    .getCurrentFocus().getWindowToken(),
                                    InputMethodManager.HIDE_NOT_ALWAYS);
                    // 搜索,进行自己要的操作...
                    seachList(viewIndex);//这里是我要做的操作!
                    return true;
                }
                return false;
            }
        });
    }

好的!完成!

初始加载时edittext不自动获取焦点的方法

在做一个搜索功能时,刚进入界面,edittext直接自动获取焦点,这显然不是我想要的。

那么问题来了:如何关闭EditText自动获取焦点的?

尝试了好几个方法,在Java代码添加focus控制,在xml文件中的EditText控件中添加focusable约束等,都不适用于我的这个问题。

 

解决办法:在xml文件中EditText外框架里添加上android:focusable=”true” android:focusableInTouchMode=”true”,这样问题就解决了。

 

[html] view plain copy
  1. <RelativeLayout    
  2.         android:id=“@+id/title”    
  3.         android:layout_width=“fill_parent”    
  4.         android:layout_height=“wrap_content”    
  5.         android:layout_alignParentLeft=“true”    
  6.         android:layout_alignParentRight=“true”    
  7.         android:layout_alignParentTop=“true”    
  8.         android:background=“#F8F8FF”    
  9.         android:focusable=“true”    
  10.         android:focusableInTouchMode=“true”    
  11.         android:paddingBottom=“6dp” >    
  12.         <EditText    
  13.             android:id=“@+id/search”    
  14.             android:layout_width=“wrap_content”    
  15.             android:layout_height=“wrap_content”    
  16.             android:layout_alignParentLeft=“true”    
  17.             android:layout_marginLeft=“10dp”    
  18.             android:layout_marginRight=“5dp”    
  19.             android:layout_marginTop=“5dp”    
  20.             android:layout_toLeftOf=“@+id/auto_add”    
  21.             android:background=“@drawable/rounded_edittext”    
  22.             android:drawableLeft=“@drawable/search”    
  23.             android:ems=“10”    
  24.             android:hint=“@string/hint_search”    
  25.             android:imeOptions=“actionSearch”    
  26.             android:inputType=“text”    
  27.             android:singleLine=“true”    
  28.             android:textSize=“15sp” >    
  29.         </EditText>   

 

这是一份很详细的 Retrofit 2.0 使用教程

前言
  • Andrroid开发中,网络请求十分常用
  • 而在Android网络请求库中,Retrofit是当下*热的一个网络请求库

Github截图

  • 今天,我将献上一份非常详细Retrofit v2.0的使用教程,希望你们会喜欢。

如果对Retrofit v2.0的源码感兴趣,可看文章:Android:手把手带你深入剖析 Retrofit 2.0 源码


目录

目录


1. 简介

Retrofit简介

特别注意:

  • 准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。
  • 原因:网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装

本质过程

  • App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作
  • 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析

2. 与其他开源请求库对比

除了Retrofit,如今Android中主流的网络请求框架有:

  • Android-Async-Http
  • Volley
  • OkHttp

下面是简单介绍:

网络请求加载 - 介绍

一图让你了解全部的网络请求库和他们之间的区别!

网络请求库 - 对比


附:各个主流网络请求库的Github地址

  • Android-Async-Http
  • Volley
  • OkHttp
  • Retrofit

3. 使用介绍

使用 Retrofit 的步骤共有7个:

步骤1:添加Retrofit库的依赖
步骤2:创建 接收服务器返回数据 的类
步骤3:创建 用于描述网络请求 的接口
步骤4:创建 Retrofit 实例
步骤5:创建 网络请求接口实例 并 配置网络请求参数
步骤6:发送网络请求(异步 / 同步)

封装了 数据转换、线程切换的操作

步骤7: 处理服务器返回的数据

接下来,我们一步步进行讲解。

步骤1:添加Retrofit库的依赖

1. 在 Gradle加入Retrofit库的依赖

由于Retrofit是基于OkHttp,所以还需要添加OkHttp库依赖

build.gradle

dependencies {
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    // Retrofit库
    compile 'com.squareup.okhttp3:okhttp:3.1.2'
    // Okhttp库
  }

 

2. 添加 网络权限
AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

 

步骤2:创建 接收服务器返回数据 的类

Reception.java

public class Reception {
    ...
    // 根据返回数据的格式和数据解析方式(Json、XML等)定义
    // 下面会在实例进行说明
        }

 

步骤3:创建 用于描述网络请求 的接口

  • Retrofit将 Http请求 抽象成 Java接口:采用 注解 描述网络请求参数 和配置网络请求参数
    1. 用 动态代理 动态 将该接口的注解“翻译”成一个 Http 请求,*后再执行 Http 请求
    2. 注:接口中的每个方法的参数都需要使用注解标注,否则会报错

GetRequest_Interface.interface

public interface GetRequest_Interface {

    @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
    Call<Translation>  getCall();
    // @GET注解的作用:采用Get方法发送网络请求

    // getCall() = 接收网络请求数据的方法
    // 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类)
    // 如果想直接获得Responsebody中的内容,可以定义网络请求返回值为Call<ResponseBody>
}

 

下面详细介绍Retrofit 网络请求接口 的注解类型。

注解类型

注解类型

注解说明

*类:网络请求方法

网络请求方法注解

详细说明:
a. @GET、@POST、@PUT、@DELETE、@HEAD
以上方法分别对应 HTTP中的网络请求方式

public interface GetRequest_Interface {

    @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
    Call<Translation>  getCall();
    // @GET注解的作用:采用Get方法发送网络请求
    // getCall() = 接收网络请求数据的方法
    // 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类)
}

 

此处特意说明URL的组成:Retrofit把 网络请求的URL 分成了两部分设置:

// 第1部分:在网络请求接口的注解设置
 @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
Call<Translation>  getCall();

// 第2部分:在创建Retrofit实例时通过.baseUrl()设置
Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fanyi.youdao.com/") //设置网络请求的Url地址
                .addConverterFactory(GsonConverterFactory.create()) //设置数据解析器
                .build();

// 从上面看出:一个请求的URL可以通过 替换块 和 请求方法的参数 来进行动态的URL更新。
// 替换块是由 被{}包裹起来的字符串构成
// 即:Retrofit支持动态改变网络请求根目录

 

  • 网络请求的完整 Url =在创建Retrofit实例时通过.baseUrl()设置 +网络请求接口的注解设置(下面称 “path“ )
  • 具体整合的规则如下:

URL整合规则

建议采用第三种方式来配置,并尽量使用同一种路径形式。

b. @HTTP

  • 作用:替换@GET、@POST、@PUT、@DELETE、@HEAD注解的作用 及 更多功能拓展
  • 具体使用:通过属性method、path、hasBody进行设置
public interface GetRequest_Interface {
    /**
     * method:网络请求的方法(区分大小写)
     * path:网络请求地址路径
     * hasBody:是否有请求体
     */
    @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
    Call<ResponseBody> getCall(@Path("id") int id);
    // {id} 表示是一个变量
    // method 的值 retrofit 不会做处理,所以要自行保证准确
}

 

第二类:标记

标记类注解

a. @FormUrlEncoded

  • 作用:表示发送form-encoded的数据

每个键值对需要用@Filed来注解键名,随后的对象需要提供值。

b. @Multipart

 

  • 作用:表示发送form-encoded的数据(适用于 有文件 上传的场景)

每个键值对需要用@Part来注解键名,随后的对象需要提供值。
具体使用如下:
GetRequest_Interface

public interface GetRequest_Interface {
        /**
         *表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
         * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
         */
        @POST("/form")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);

        /**
         * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
         * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
         */
        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);

}

// 具体使用
       GetRequest_Interface service = retrofit.create(GetRequest_Interface.class);
        // @FormUrlEncoded 
        Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);

        //  @Multipart
        RequestBody name = RequestBody.create(textType, "Carson");
        RequestBody age = RequestBody.create(textType, "24");

        MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
        Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);

 

第三类:网络请求参数

网络请求参数注解

详细说明

a. @Header & @Headers

  • 作用:添加请求头 &添加不固定的请求头
  • 具体使用如下:
// @Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

// @Headers
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()

// 以上的效果是一致的。
// 区别在于使用场景和使用方式
// 1. 使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
// 2. 使用方式:@Header作用于方法的参数;@Headers作用于方法

 

b. @Body

 

  • 作用:以 Post方式 传递 自定义数据类型 给服务器
  • 特别注意:如果提交的是一个Map,那么作用相当于 @Field

不过Map要经过 FormBody.Builder 类处理成为符合 Okhttp 格式的表单,如:

FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");

 

c. @Field & @FieldMap

  • 作用:发送 Post请求 时提交请求的表单字段
  • 具体使用:与 @FormUrlEncoded 注解配合使用
public interface GetRequest_Interface {
        /**
         *表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
         * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
         */
        @POST("/form")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);

/**
         * Map的key作为表单的键
         */
        @POST("/form")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);

}

// 具体使用
         // @Field
        Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);

        // @FieldMap
        // 实现的效果与上面相同,但要传入Map
        Map<String, Object> map = new HashMap<>();
        map.put("username", "Carson");
        map.put("age", 24);
        Call<ResponseBody> call2 = service.testFormUrlEncoded2(map);

 

d. @Part & @PartMap

  • 作用:发送 Post请求 时提交请求的表单字段

    与@Field的区别:功能相同,但携带的参数类型更加丰富,包括数据流,所以适用于 有文件上传 的场景

  • 具体使用:与 @Multipart 注解配合使用
public interface GetRequest_Interface {

          /**
         * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
         * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
         */
        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);

        /**
         * PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
         * 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
         * 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
         */
        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);

        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
}

// 具体使用
 MediaType textType = MediaType.parse("text/plain");
        RequestBody name = RequestBody.create(textType, "Carson");
        RequestBody age = RequestBody.create(textType, "24");
        RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容");

        // @Part
        MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
        Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
        ResponseBodyPrinter.printResponseBody(call3);

        // @PartMap
        // 实现和上面同样的效果
        Map<String, RequestBody> fileUpload2Args = new HashMap<>();
        fileUpload2Args.put("name", name);
        fileUpload2Args.put("age", age);
        //这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有
        //fileUpload2Args.put("file", file);
        Call<ResponseBody> call4 = service.testFileUpload2(fileUpload2Args, filePart); //单独处理文件
        ResponseBodyPrinter.printResponseBody(call4);
}

 

e. @Query和@QueryMap

  • 作用:用于 @GET 方法的查询参数(Query = Url 中 ‘?’ 后面的 key-value)

    如:url = http://www.println.net/?cate=android,其中,Query = cate

  • 具体使用:配置时只需要在接口方法中增加一个参数即可:
   @GET("/")    
   Call<String> cate(@Query("cate") String cate);
}

// 其使用方式同 @Field与@FieldMap,这里不作过多描述

 

f. @Path

  • 作用:URL地址的缺省值
  • 具体使用:
public interface GetRequest_Interface {

        @GET("users/{user}/repos")
        Call<ResponseBody>  getBlog(@Path("user") String user );
        // 访问的API是:https://api.github.com/users/{user}/repos
        // 在发起请求时, {user} 会被替换为方法的*个参数 user(被@Path注解作用)
    }

 

g. @Url

  • 作用:直接传入一个请求的 URL变量 用于URL设置
  • 具体使用:
public interface GetRequest_Interface {

        @GET
        Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
       // 当有URL注解时,@GET传入的URL就可以省略
       // 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供

}

 

汇总

汇总

步骤4:创建 Retrofit 实例

 Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fanyi.youdao.com/") // 设置网络请求的Url地址
                .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平台
                .build();

 

a. 关于数据解析器(Converter)

  • Retrofit支持多种数据解析方式
  • 使用时需要在Gradle添加依赖
数据解析器 Gradle依赖
Gson com.squareup.retrofit2:converter-gson:2.0.2
Jackson com.squareup.retrofit2:converter-jackson:2.0.2
Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
Moshi com.squareup.retrofit2:converter-moshi:2.0.2
Wire com.squareup.retrofit2:converter-wire:2.0.2
Scalars com.squareup.retrofit2:converter-scalars:2.0.2

b. 关于网络请求适配器(CallAdapter)

  • Retrofit支持多种网络请求适配器方式:guava、Java8和rxjava

    使用时如使用的是 Android 默认的 CallAdapter,则不需要添加网络请求适配器的依赖,否则则需要按照需求进行添加
    Retrofit 提供的 CallAdapter

  • 使用时需要在Gradle添加依赖:
网络请求适配器 Gradle依赖
guava com.squareup.retrofit2:adapter-guava:2.0.2
Java8 com.squareup.retrofit2:adapter-java8:2.0.2
rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2

步骤5:创建 网络请求接口实例

        // 创建 网络请求接口 的实例
        GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);

        //对 发送请求 进行封装
        Call<Reception> call = request.getCall();

 

步骤6:发送网络请求(异步 / 同步)

封装了 数据转换、线程切换的操作

//发送网络请求(异步)
        call.enqueue(new Callback<Translation>() {
            //请求成功时回调
            @Override
            public void onResponse(Call<Translation> call, Response<Translation> response) {
                //请求处理,输出结果
                response.body().show();
            }

            //请求失败时候的回调
            @Override
            public void onFailure(Call<Translation> call, Throwable throwable) {
                System.out.println("连接失败");
            }
        });

// 发送网络请求(同步)
Response<Reception> response = call.execute();

 

步骤7:处理返回数据

通过response类的 body()对返回的数据进行处理

//发送网络请求(异步)
        call.enqueue(new Callback<Translation>() {
            //请求成功时回调
            @Override
            public void onResponse(Call<Translation> call, Response<Translation> response) {
                // 对返回数据进行处理
                response.body().show();
            }

            //请求失败时候的回调
            @Override
            public void onFailure(Call<Translation> call, Throwable throwable) {
                System.out.println("连接失败");
            }
        });

// 发送网络请求(同步)
  Response<Reception> response = call.execute();
  // 对返回数据进行处理
  response.body().show();

 


4. 实例讲解

接下来,我将用两个实例分别对 Retrofit GET方式 和 POST方式进行 网络请求 讲解。

4.1 实例1

  • 实现功能:将中文翻译成英文
  • 实现方案:采用Get方法对 金山词霸API 发送网络请求

    采用 Gson 进行数据解析

金山词典

  • 步骤说明

步骤1:添加Retrofit库的依赖
步骤2:创建 接收服务器返回数据 的类
步骤3:创建 用于描述网络请求 的接口
步骤4:创建 Retrofit 实例
步骤5:创建 网络请求接口实例 并 配置网络请求参数
步骤6:发送网络请求(采用*常用的异步方式)

封装了 数据转换、线程切换的操作

步骤7: 处理服务器返回的数据

接下来,我们一步步进行讲解。

  • 具体使用

步骤1:添加Retrofit库的依赖

1. 在 Gradle加入Retrofit库的依赖

由于Retrofit是基于OkHttp,所以还需要添加OkHttp库依赖

build.gradle

dependencies {
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    // Retrofit库
    compile 'com.squareup.okhttp3:okhttp:3.1.2'
    // Okhttp库
  }

 

2. 添加 网络权限
AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

 

步骤2:创建 接收服务器返回数据 的类

  • 金山词霸API 的数据格式说明如下:
// URL模板
http://fy.iciba.com/ajax.php

// URL实例
http://fy.iciba.com/ajax.php?a=fy&f=auto&t=auto&w=hello%20world

// 参数说明:
// a:固定值 fy
// f:原文内容类型,日语取 ja,中文取 zh,英语取 en,韩语取 ko,德语取 de,西班牙语取 es,法语取 fr,自动则取 auto
// t:译文内容类型,日语取 ja,中文取 zh,英语取 en,韩语取 ko,德语取 de,西班牙语取 es,法语取 fr,自动则取 auto
// w:查询内容

 

API格式说明

  • 根据 金山词霸API 的数据格式,创建 接收服务器返回数据 的类:

Translation.java

public class Translation {
        private int status;

    private content content;
    private static class content {
        private String from;
        private String to;
        private String vendor;
        private String out;
        private int errNo;
    }

    //定义 输出返回数据 的方法
    public void show() {
        System.out.println(status);

        System.out.println(content.from);
        System.out.println(content.to);
        System.out.println(content.vendor);
        System.out.println(content.out);
        System.out.println(content.errNo);
    }
}

 

步骤3:创建 用于描述网络请求 的接口

采用 注解 描述 网络请求参数。
GetRequest_Interface.java

public interface GetRequest_Interface {

 @GET("ajax.php?a=fy&f=auto&t=auto&w=hello%20world")
    Call<Translation> getCall();
    // 注解里传入 网络请求 的部分URL地址
    // Retrofit把网络请求的URL分成了两部分:一部分放在Retrofit对象里,另一部分放在网络请求接口里
    // 如果接口里的url是一个完整的网址,那么放在Retrofit对象里的URL可以忽略
    // getCall()是接受网络请求数据的方法
}

 

接下来的步骤均在GetRequest.java内实现(看注释)

步骤4:创建Retrofit对象
步骤5:创建 网络请求接口 的实例
步骤6:发送网络请求

以*常用的 异步请求 为例

步骤7:处理返回数据

GetRequest.java

public class GetRequest extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        request();
        // 使用Retrofit封装的方法
    }
    public void request() {

        //步骤4:创建Retrofit对象
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fy.iciba.com/") // 设置 网络请求 Url
                .addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
                .build();

        // 步骤5:创建 网络请求接口 的实例
        GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);

        //对 发送请求 进行封装
        Call<Translation> call = request.getCall();

        //步骤6:发送网络请求(异步)
        call.enqueue(new Callback<Translation>() {
            //请求成功时回调
            @Override
            public void onResponse(Call<Translation> call, Response<Translation> response) {
                // 步骤7:处理返回的数据结果
                response.body().show();
            }

            //请求失败时回调
            @Override
            public void onFailure(Call<Translation> call, Throwable throwable) {
                System.out.println("连接失败");
            }
        });
    }
}

 

由于此处采用了 Gson 解析,所以需要在 Gradle加入依赖
build.gradle

compile 'com.squareup.retrofit2:converter-gson:2.0.2'
  • 1

运行结果

运行结果

Demo地址

Carson_Ho的Github:https://github.com/Carson-Ho/RetrofitDemo


4.2 实例2

  • 实现的功能:将 英文 翻译成 中文
  • 实现方法:采用Post方法对 有道API 发送网络请求

    采用 Gson 进行数据解析

有道翻译

  • 使用步骤

步骤1:添加Retrofit库的依赖
步骤2:创建 接收服务器返回数据 的类
步骤3:创建 用于描述网络请求 的接口
步骤4:创建 Retrofit 实例
步骤5:创建 网络请求接口实例 并 配置网络请求参数
步骤6:发送网络请求(采用*常用的异步方式)

封装了 数据转换、线程切换的操作

步骤7: 处理服务器返回的数据

接下来,我们一步步进行Retrofit的使用。

  • 具体使用

步骤1:添加Retrofit库的依赖

1. 在 Gradle加入Retrofit库的依赖

由于Retrofit是基于OkHttp,所以还需要添加OkHttp库依赖

build.gradle

dependencies {
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    // Retrofit库
    compile 'com.squareup.okhttp3:okhttp:3.1.2'
    // Okhttp库
  }

 

2. 添加 网络权限
AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

步骤2:创建 接收服务器返回数据 的类

  • API 的数据格式说明如下:
// URL
http://fanyi.youdao.com/translate

// URL实例
http://fanyi.youdao.com/translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=


// 参数说明
// doctype:json 或 xml
// jsonversion:如果 doctype 值是 xml,则去除该值,若 doctype 值是 json,该值为空即可
// xmlVersion:如果 doctype 值是 json,则去除该值,若 doctype 值是 xml,该值为空即可
// type:语言自动检测时为 null,为 null 时可为空。英译中为 EN2ZH_CN,中译英为 ZH_CN2EN,日译中为 JA2ZH_CN,中译日为 ZH_CN2JA,韩译中为 KR2ZH_CN,中译韩为 ZH_CN2KR,中译法为 ZH_CN2FR,法译中为 FR2ZH_CN
// keyform:mdict. + 版本号 + .手机平台。可为空
// model:手机型号。可为空
// mid:平台版本。可为空
// imei:???。可为空
// vendor:应用下载平台。可为空
// screen:屏幕宽高。可为空
// ssid:用户名。可为空
// abtest:???。可为空

// 请求方式说明
// 请求方式:POST
// 请求体:i
// 请求格式:x-www-form-urlencoded

 

数据格式说明

  • 根据 有道API 的数据格式,创建 接收服务器返回数据 的类:

Translation.java

public class Translation1 {

    private String type;
    private int errorCode;
    private int elapsedTime;
    private List<List<TranslateResultBean>> translateResult;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public int getElapsedTime() {
        return elapsedTime;
    }

    public void setElapsedTime(int elapsedTime) {
        this.elapsedTime = elapsedTime;
    }

    public List<List<TranslateResultBean>> getTranslateResult() {
        return translateResult;
    }

    public void setTranslateResult(List<List<TranslateResultBean>> translateResult) {
        this.translateResult = translateResult;
    }

    public static class TranslateResultBean {
        /**
         * src : merry me
         * tgt : 我快乐
         */

        public String src;
        public String tgt;

        public String getSrc() {
            return src;
        }

        public void setSrc(String src) {
            this.src = src;
        }

        public String getTgt() {
            return tgt;
        }

        public void setTgt(String tgt) {
            this.tgt = tgt;
        }
    }

}

 

步骤3:创建 用于描述网络请求 的接口

采用 注解 描述 网络请求参数。

PostRequest_Interface.java

public interface PostRequest_Interface {

    @POST("translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=")
    @FormUrlEncoded
    Call<Translation1> getCall(@Field("i") String targetSentence);
    //采用@Post表示Post方法进行请求(传入部分url地址)
    // 采用@FormUrlEncoded注解的原因:API规定采用请求格式x-www-form-urlencoded,即表单形式
    // 需要配合@Field 向服务器提交需要的字段
}

 

接下来的步骤均在PostRequest.java内实现(看注释)

步骤4:创建Retrofit对象
步骤5:创建 网络请求接口 的实例
步骤6:发送网络请求

以*常用的 异步请求 为例

步骤7:处理返回数据

PostRequest.java

public class PostRequest extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        request();
    }
    public void request() {

        //步骤4:创建Retrofit对象
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fanyi.youdao.com/") // 设置 网络请求 Url
                .addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
                .build();

        // 步骤5:创建 网络请求接口 的实例
        PostRequest_Interface request = retrofit.create(PostRequest_Interface.class);

        //对 发送请求 进行封装(设置需要翻译的内容)
        Call<Translation1> call = request.getCall("I love you");

        //步骤6:发送网络请求(异步)
        call.enqueue(new Callback<Translation1>() {

            //请求成功时回调
            @Override
            public void onResponse(Call<Translation1> call, Response<Translation1> response) {
                // 步骤7:处理返回的数据结果:输出翻译的内容
                System.out.println(response.body().getTranslateResult().get(0).get(0).getTgt());
            }

            //请求失败时回调
            @Override
            public void onFailure(Call<Translation1> call, Throwable throwable) {
                System.out.println("请求失败");
                System.out.println(throwable.getMessage());
            }
        });
    }


}

 

由于此处采用了 Gson 解析,所以需要在 Gradle 加入依赖
build.gradle

compile 'com.squareup.retrofit2:converter-gson:2.0.2'
  • 1

运行结果

运行结果

Demo地址

Carson_Ho的Github:https://github.com/Carson-Ho/RetrofitDemo


5. Retrofit 的拓展使用

  • Retrofit的使用场景非常丰富,如支持RxJavaPrototocobuff
  • 具体设置也非常简单 & 方便:
<-- 主要在创建Retrofit对象中设置 -->
Retrofit retrofit = new Retrofit.Builder()
  .baseUrl(""http://fanyi.youdao.com/"")
  .addConverterFactory(ProtoConverterFactory.create()) // 支持Prototocobuff解析
  .addConverterFactory(GsonConverterFactory.create()) // 支持Gson解析
  .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava
  .build();

 

具体关于 RxJava的使用这里就不展开,请期待下篇关于 Rxjava的文章。


6. 总结

  • 看完本文,相信你已经非常熟悉 Retrofit 2.0 的使用
  • 如果你希望继续阅读 Retrofit 2.0 的源码,请看我写的文章:Android:手把手带你深入剖析 Retrofit 2.0 源码
  • 接下来,我将继续分析与 Retrofit 配合使用的 RxJava,有兴趣可以继续关注Carson_Ho的安卓开发笔记

Android:深入剖析 Retrofit 2.0 源码

前言

  • Andrroid开发中,网络请求十分常用
  • 而在Android网络请求库中,Retrofit是当下*热的一个网络请求库

Github截图

  • 今天,我将手把手带你深入剖析Retrofit v2.0的源码,希望你们会喜欢

在阅读本文前,建议先阅读文章:这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)


目录

目录


1. 简介

Retrofit简介

特别注意:

  • 准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。
  • 原因:网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装

流程图

  • App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作
  • 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析

2. 与其他网络请求开源库对比

除了Retrofit,如今Android中主流的网络请求框架有:

  • Android-Async-Http
  • Volley
  • OkHttp

下面是简单介绍:

网络请求加载 - 介绍

一图让你了解全部的网络请求库和他们之间的区别!

网络请求库 - 对比


附:各个主流网络请求库的Github地址

  • Android-Async-Http
  • Volley
  • OkHttp
  • Retrofit

3. Retrofit 的具体使用

具体请看我写的文章:这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)


4. 源码分析

4.1 Retrofit的本质流程

一般从网络通信过程如下图:

网络请求的过程

  • 其实Retrofit的本质和上面是一样的套路
  • 只是Retrofit通过使用大量的设计模式进行功能模块的解耦,使得上面的过程进行得更加简单 & 流畅

如下图:

Retrofit的本质

具体过程解释如下:

  1. 通过解析 网络请求接口的注解 配置 网络请求参数
  2. 通过 动态代理 生成 网络请求对象
  3. 通过 网络请求适配器 将 网络请求对象 进行平台适配

    平台包括:Android、Rxjava、Guava和java8

  4. 通过 网络请求执行器 发送网络请求
  5. 通过 数据转换器 解析服务器返回的数据
  6. 通过 回调执行器 切换线程(子线程 ->>主线程)
  7. 用户在主线程处理返回结果

下面介绍上面提到的几个角色

角色说明

特别注意:因下面的 源码分析 是根据 使用步骤 逐步带你debug进去的,所以必须先看文章这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)

4.2 源码分析

先来回忆Retrofit的使用步骤:
1. 创建Retrofit实例
2. 创建 网络请求接口实例 并 配置网络请求参数
3. 发送网络请求

封装了 数据转换、线程切换的操作
4. 处理服务器返回的数据

4.2.1 创建Retrofit实例

a. 使用步骤

 Retrofit retrofit = new Retrofit.Builder()
                                 .baseUrl("http://fanyi.youdao.com/")
                                 .addConverterFactory(GsonConverterFactory.create())
                                 .build();

 

b. 源码分析

Retrofit实例是使用建造者模式通过Builder类进行创建的

建造者模式:将一个复杂对象的构建与表示分离,使得用户在不知道对象的创建细节情况下就可以直接创建复杂的对象。具体请看文章:建造者模式(Builder Pattern)- *易懂的设计模式解析

接下来,我将分五个步骤对创建Retrofit实例进行逐步分析

分析步骤

步骤1

步骤1

<-- Retrofit类 -->
 public final class Retrofit {

  private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
  // 网络请求配置对象(对网络请求接口中方法注解进行解析后得到的对象)
  // 作用:存储网络请求相关的配置,如网络请求的方法、数据转换器、网络请求适配器、网络请求工厂、基地址等

  private final HttpUrl baseUrl;
  // 网络请求的url地址

  private final okhttp3.Call.Factory callFactory;
  // 网络请求器的工厂
  // 作用:生产网络请求器(Call)
  // Retrofit是默认使用okhttp

   private final List<CallAdapter.Factory> adapterFactories;
  // 网络请求适配器工厂的集合
  // 作用:放置网络请求适配器工厂
  // 网络请求适配器工厂作用:生产网络请求适配器(CallAdapter)
  // 下面会详细说明


  private final List<Converter.Factory> converterFactories;
  // 数据转换器工厂的集合
  // 作用:放置数据转换器工厂
  // 数据转换器工厂作用:生产数据转换器(converter)

  private final Executor callbackExecutor;
  // 回调方法执行器

private final boolean validateEagerly; 
// 标志位
// 作用:是否提前对业务接口中的注解进行验证转换的标志位


<-- Retrofit类的构造函数 -->
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,  
      List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories,  
      Executor callbackExecutor, boolean validateEagerly) {  
    this.callFactory = callFactory;  
    this.baseUrl = baseUrl;  
    this.converterFactories = unmodifiableList(converterFactories); 
    this.adapterFactories = unmodifiableList(adapterFactories);   
    // unmodifiableList(list)近似于UnmodifiableList<E>(list)
    // 作用:创建的新对象能够对list数据进行访问,但不可通过该对象对list集合中的元素进行修改
    this.callbackExecutor = callbackExecutor;  
    this.validateEagerly = validateEagerly;  
  ...
  // 仅贴出关键代码
}

 

成功建立一个Retrofit对象的标准:配置好Retrofit类里的成员变量,即配置好:

  • serviceMethod:包含所有网络请求信息的对象
  • baseUrl:网络请求的url地址
  • callFactory:网络请求工厂
  • adapterFactories:网络请求适配器工厂的集合
  • converterFactories:数据转换器工厂的集合
  • callbackExecutor:回调方法执行器

所谓xxxFactory、“xxx工厂”其实是设计模式中工厂模式的体现:将“类实例化的操作”与“使用对象的操作”分开,使得使用者不用知道具体参数就可以实例化出所需要的“产品”类。

具体请看我写的文章
简单工厂模式(SimpleFactoryPattern)- *易懂的设计模式解析
工厂方法模式(Factory Method)- *易懂的设计模式解析
抽象工厂模式(Abstract Factory)- *易懂的设计模式解析

这里详细介绍一下:CallAdapterFactory:该Factory生产的是CallAdapter,那么CallAdapter又是什么呢?

CallAdapter详细介绍

  • 定义:网络请求执行器(Call)的适配器
    1. Call在Retrofit里默认是OkHttpCall
    2. 在Retrofit中提供了四种CallAdapterFactory: ExecutorCallAdapterFactory(默认)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory
  • 作用:将默认的网络请求执行器(OkHttpCall)转换成适合被不同平台来调用的网络请求执行器形式
    1. 如:一开始Retrofit只打算利用OkHttpCall通过ExecutorCallbackCall切换线程;但后来发现使用Rxjava更加方便(不需要Handler来切换线程)。想要实现Rxjava的情况,那就得使用RxJavaCallAdapterFactoryCallAdapterOkHttpCall转换成Rxjava(Scheduler)
// 把response封装成rxjava的Observeble,然后进行流式操作
Retrofit.Builder.addCallAdapterFactory(newRxJavaCallAdapterFactory().create()); 
// 关于RxJava的使用这里不作更多的展开

 

  1. Retrofit还支持java8、Guava平台。
  • 好处:用*小代价兼容更多平台,即能适配更多的使用场景

所以,接下来需要分析的步骤2、步骤3、步骤4、步骤4的目的是配置好上述所有成员变量

步骤2

步骤2

我们先来看Builder类

请按下面提示的步骤进行查看

<-- Builder类-->
public static final class Builder {
    private Platform platform;
    private okhttp3.Call.Factory callFactory;
    private HttpUrl baseUrl;
    private List<Converter.Factory> converterFactories = new ArrayList<>();
    private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
    private Executor callbackExecutor;
    private boolean validateEagerly;

// 从上面可以发现, Builder类的成员变量与Retrofit类的成员变量是对应的
// 所以Retrofit类的成员变量基本上是通过Builder类进行配置
// 开始看步骤1

<-- 步骤1 -->
// Builder的构造方法(无参)
 public Builder() {
      this(Platform.get());
// 用this调用自己的有参构造方法public Builder(Platform platform) ->>步骤5(看完步骤2、3、4再看)
// 并通过调用Platform.get()传入了Platform对象
// 继续看Platform.get()方法 ->>步骤2
// 记得*后继续看步骤5的Builder有参构造方法
    }
...
}

<-- 步骤2 -->
class Platform {

  private static final Platform PLATFORM = findPlatform();
  // 将findPlatform()赋给静态变量

  static Platform get() {
    return PLATFORM;    
    // 返回静态变量PLATFORM,即findPlatform() ->>步骤3
  }

<-- 步骤3 -->
private static Platform findPlatform() {
    try {

      Class.forName("android.os.Build");
      // Class.forName(xxx.xx.xx)的作用:要求JVM查找并加载指定的类(即JVM会执行该类的静态代码段)
      if (Build.VERSION.SDK_INT != 0) {
        return new Android(); 
        // 此处表示:如果是Android平台,就创建并返回一个Android对象 ->>步骤4
      }
    } catch (ClassNotFoundException ignored) {
    }

    try {
      // 支持Java平台
      Class.forName("java.util.Optional");
      return new Java8();
    } catch (ClassNotFoundException ignored) {
    }

    try {
      // 支持iOS平台
      Class.forName("org.robovm.apple.foundation.NSObject");
      return new IOS();
    } catch (ClassNotFoundException ignored) {
    }

// 从上面看出:Retrofit2.0支持3个平台:Android平台、Java平台、IOS平台
// *后返回一个Platform对象(指定了Android平台)给Builder的有参构造方法public Builder(Platform platform)  --> 步骤5
// 说明Builder指定了运行平台为Android
    return new Platform();
  }
...
}

<-- 步骤4 -->
// 用于接收服务器返回数据后进行线程切换在主线程显示结果

static class Android extends Platform {

    @Override
      CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {

      return new ExecutorCallAdapterFactory(callbackExecutor);
    // 创建默认的网络请求适配器工厂
    // 该默认工厂生产的 adapter 会使得Call在异步调用时在指定的 Executor 上执行回调
    // 在Retrofit中提供了四种CallAdapterFactory: ExecutorCallAdapterFactory(默认)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory
    // 采用了策略模式

    }

    @Override 
      public Executor defaultCallbackExecutor() {
      // 返回一个默认的回调方法执行器
      // 该执行器作用:切换线程(子->>主线程),并在主线程(UI线程)中执行回调方法
      return new MainThreadExecutor();
    }

    static class MainThreadExecutor implements Executor {

      private final Handler handler = new Handler(Looper.getMainLooper());
      // 获取与Android 主线程绑定的Handler 

      @Override 
      public void execute(Runnable r) {


        handler.post(r);
        // 该Handler是上面获取的与Android 主线程绑定的Handler 
        // 在UI线程进行对网络请求返回数据处理等操作。
      }
    }

// 切换线程的流程:
// 1. 回调ExecutorCallAdapterFactory生成了一个ExecutorCallbackCall对象
//2. 通过调用ExecutorCallbackCall.enqueue(CallBack)从而调用MainThreadExecutor的execute()通过handler切换到主线程
  }

// 下面继续看步骤5的Builder有参构造方法
<-- 步骤5 -->
//  Builder类的构造函数2(有参)
  public  Builder(Platform platform) {

  // 接收Platform对象(Android平台)
      this.platform = platform;

// 通过传入BuiltInConverters()对象配置数据转换器工厂(converterFactories)

// converterFactories是一个存放数据转换器Converter.Factory的数组
// 配置converterFactories即配置里面的数据转换器
      converterFactories.add(new BuiltInConverters());

// BuiltInConverters是一个内置的数据转换器工厂(继承Converter.Factory类)
// new BuiltInConverters()是为了初始化数据转换器
    }

 

对Builder类分析完毕,总结:Builder设置了默认的

  • 平台类型对象:Android
  • 网络请求适配器工厂:CallAdapterFactory

    CallAdapter用于对原始Call进行再次封装,如Call到Observable

  • 数据转换器工厂: converterFactory
  • 回调执行器:callbackExecutor

特别注意,这里只是设置了默认值,但未真正配置到具体的Retrofit类的成员变量当中

步骤3

步骤3

还是按部就班按步骤来观看

<-- 步骤1 -->
public Builder baseUrl(String baseUrl) {

      // 把String类型的url参数转化为适合OKhttp的HttpUrl类型
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);     

    // *终返回带httpUrl类型参数的baseUrl()
    // 下面继续看baseUrl(httpUrl) ->> 步骤2
      return baseUrl(httpUrl);
    }


<-- 步骤2 -->
    public Builder baseUrl(HttpUrl baseUrl) {

      //把URL参数分割成几个路径碎片
      List<String> pathSegments = baseUrl.pathSegments();   

      // 检测*后一个碎片来检查URL参数是不是以"/"结尾
      // 不是就抛出异常    
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }     
      this.baseUrl = baseUrl;
      return this;
    }

 

  • 至此,步骤3分析完毕
  • 总结:baseUrl()用于配置Retrofit类的网络请求url地址

    将传入的String类型url转化为适合OKhttp的HttpUrl类型的url

步骤4

步骤4

我们从里往外看,即先看GsonConverterFactory.creat()

public final class GsonConverterFactory extends Converter.Factory {

<-- 步骤1 -->
  public static GsonConverterFactory create() {
    // 创建一个Gson对象
    return create(new Gson()); ->>步骤2
  }

<-- 步骤2 -->
  public static GsonConverterFactory create(Gson gson) {
    // 创建了一个含有Gson对象实例的GsonConverterFactory
    return new GsonConverterFactory(gson); ->>步骤3
  }

  private final Gson gson;

<-- 步骤3 -->
  private GsonConverterFactory(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    this.gson = gson;
  }

 

  • 所以,GsonConverterFactory.creat()是创建了一个含有Gson对象实例的GsonConverterFactory,并返回给addConverterFactory()
  • 接下来继续看:addConverterFactory()

// 将上面创建的GsonConverterFactory放入到 converterFactories数组
// 在第二步放入一个内置的数据转换器工厂BuiltInConverters()后又放入了一个GsonConverterFactory
  public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

 

  • 至此,分析完毕
  • 总结:步骤4用于创建一个含有Gson对象实例的GsonConverterFactory并放入到数据转换器工厂converterFactories里
    1. 即Retrofit默认使用Gson进行解析
    2. 若使用其他解析方式(如Json、XML或Protocobuf),也可通过自定义数据解析器来实现(必须继承 Converter.Factory)

 

步骤5

步骤5

终于到了*后一个步骤了。

    public Retrofit build() {

 <--  配置网络请求执行器(callFactory)-->
      okhttp3.Call.Factory callFactory = this.callFactory;
      // 如果没指定,则默认使用okhttp
      // 所以Retrofit默认使用okhttp进行网络请求
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

 <--  配置回调方法执行器(callbackExecutor)-->
      Executor callbackExecutor = this.callbackExecutor;
      // 如果没指定,则默认使用Platform检测环境时的默认callbackExecutor
      // 即Android默认的callbackExecutor
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

 <--  配置网络请求适配器工厂(CallAdapterFactory)-->
      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      // 向该集合中添加了步骤2中创建的CallAdapter.Factory请求适配器(添加在集合器末尾)
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
    // 请求适配器工厂集合存储顺序:自定义1适配器工厂、自定义2适配器工厂...默认适配器工厂(ExecutorCallAdapterFactory)

 <--  配置数据转换器工厂:converterFactory -->
      // 在步骤2中已经添加了内置的数据转换器BuiltInConverters()(添加到集合器的首位)
      // 在步骤4中又插入了一个Gson的转换器 - GsonConverterFactory(添加到集合器的首二位)
      List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
      // 数据转换器工厂集合存储的是:默认数据转换器工厂( BuiltInConverters)、自定义1数据转换器工厂(GsonConverterFactory)、自定义2数据转换器工厂....

// 注:
//1. 获取合适的网络请求适配器和数据转换器都是从adapterFactories和converterFactories集合的首位-末位开始遍历
// 因此集合中的工厂位置越靠前就拥有越高的使用权限

      // *终返回一个Retrofit的对象,并传入上述已经配置好的成员变量
      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }

 

  • 至此,步骤5分析完毕
  • 总结:在*后一步中,通过前面步骤设置的变量,将Retrofit类的所有成员变量都配置完毕。
  • 所以,成功创建了Retrofit的实例

总结

Retrofit 使用建造者模式通过Builder类建立了一个Retrofit实例,具体创建细节是配置了:

 

  • 平台类型对象(Platform – Android)
  • 网络请求的url地址(baseUrl)
  • 网络请求工厂(callFactory)

默认使用OkHttpCall

  • 网络请求适配器工厂的集合(adapterFactories)

    本质是配置了网络请求适配器工厂- 默认是ExecutorCallAdapterFactory

  • 数据转换器工厂的集合(converterFactories)

    本质是配置了数据转换器工厂

  • 回调方法执行器(callbackExecutor)
    默认回调方法执行器作用是:切换线程(子线程 – 主线程)

由于使用了建造者模式,所以开发者并不需要关心配置细节就可以创建好Retrofit实例,建造者模式get。在创建Retrofit对象时,你可以通过更多更灵活的方式去处理你的需求,如使用不同的Converter、使用不同的CallAdapter,这也就提供了你使用RxJava来调用Retrofit的可能


2. 创建网络请求接口的实例

2.1 使用步骤

<-- 步骤1:定义接收网络数据的类 -->
<-- JavaBean.java -->
public class JavaBean {
  .. // 这里就不介绍了
  }

<-- 步骤2:定义网络请求的接口类 -->
<-- AccessApi.java -->
public interface AccessApi {
    // 注解GET:采用Get方法发送网络请求
    // Retrofit把网络请求的URL分成了2部分:1部分baseurl放在创建Retrofit对象时设置;另一部分在网络请求接口设置(即这里)
    // 如果接口里的URL是一个完整的网址,那么放在创建Retrofit对象时设置的部分可以不设置
    @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")

    // 接受网络请求数据的方法
    Call<JavaBean> getCall();
    // 返回类型为Call<*>,*是解析得到的数据类型,即JavaBean
}

<-- 步骤3:在MainActivity创建接口类实例  -->
AccessApi NetService = retrofit.create(AccessApi.class);

<-- 步骤4:对发送请求的url进行封装,即生成*终的网络请求对象  --> 
        Call<JavaBean> call = NetService.getCall();

2.2 源码分析

  • 结论:Retrofit是通过外观模式 & 代理模式 使用create()方法创建网络请求接口的实例(同时,通过网络请求接口里设置的注解进行了网络请求参数的配置)
    1. 外观模式:定义一个统一接口,外部与通过该统一的接口对子系统里的其他接口进行访问。具体请看:外观模式(Facade Pattern) – *易懂的设计模式解析
    2. 代理模式:通过访问代理对象的方式来间接访问目标对象。具体请看:代理模式(Proxy Pattern)- *易懂的设计模式解析
    3. 下面主要分析步骤3和步骤4:
<-- 步骤3:在MainActivity创建接口类实例  -->
AccessApi NetService = retrofit.create(NetService.class);

<-- 步骤4:对发送请求的url进行封装,即生成*终的网络请求对象  --> 
        Call<JavaBean> call = NetService.getCall();

步骤3讲解:AccessApi NetService = retrofit.create(NetService.class);

 public <T> T create(final Class<T> service) {

       if (validateEagerly) {  
      // 判断是否需要提前验证
      eagerlyValidateMethods(service); 
      // 具体方法作用:
      // 1. 给接口中每个方法的注解进行解析并得到一个ServiceMethod对象
      // 2. 以Method为键将该对象存入LinkedHashMap集合中
     // 特别注意:如果不是提前验证则进行动态解析对应方法(下面会详细说明),得到一个ServiceMethod对象,*后存入到LinkedHashMap集合中,类似延迟加载(默认)
    }  


        // 创建了网络请求接口的动态代理对象,即通过动态代理创建网络请求接口的实例 (并*终返回)
        // 该动态代理是为了拿到网络请求接口实例上所有注解
    return (T) Proxy.newProxyInstance(
          service.getClassLoader(),      // 动态生成接口的实现类 
          new Class<?>[] { service },    // 动态创建实例
          new InvocationHandler() {     // 将代理类的实现交给 InvocationHandler类作为具体的实现(下面会解释)
          private final Platform platform = Platform.get();

         // 在 InvocationHandler类的invoke()实现中,除了执行真正的逻辑(如再次转发给真正的实现类对象),还可以进行一些有用的操作
         // 如统计执行时间、进行初始化和清理、对接口调用进行检查等。
          @Override 
           public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {

            // 下面会详细介绍 invoke()的实现
            // 即下面三行代码
            ServiceMethod serviceMethod = loadServiceMethod(method);     
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

// 特别注意
// return (T) roxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler invocationHandler)
// 可以解读为:getProxyClass(loader, interfaces) .getConstructor(InvocationHandler.class).newInstance(invocationHandler);
// 即通过动态生成的代理类,调用interfaces接口的方法实际上是通过调用InvocationHandler对象的invoke()来完成指定的功能
// 先记住结论,在讲解步骤4的时候会再次详细说明


<-- 关注点1:eagerlyValidateMethods() -->
private void eagerlyValidateMethods(Class<?> service) {  
    Platform platform = Platform.get();  
    for (Method method : service.getDeclaredMethods()) {  
      if (!platform.isDefaultMethod(method)) {  loadServiceMethod(method); } 
      // 将传入的ServiceMethod对象加入LinkedHashMap<Method, ServiceMethod>集合
     // 使用LinkedHashMap集合的好处:lruEntries.values().iterator().next()获取到的是集合*不经常用到的元素,提供了一种Lru算法的实现
    }  
}  

创建网络接口实例用了外观模式 & 代理模式:

使用外观模式进行访问,里面用了代理模式

1. 外观模式

  • 外观模式:定义一个统一接口,外部与通过该统一的接口对子系统里的其他接口进行访问。具体请看:外观模式(Facade Pattern) – *易懂的设计模式解析
  • Retrofit对象的外观(门店) = retrofit.create()
  • 通过这一外观方法就可以在内部调用各个方法创建网络请求接口的实例配置网络请求参数

    大大降低了系统的耦合度

2. 代理模式

  • 代理模式:通过访问代理对象的方式来间接访问目标对象

    分为静态代理 & 动态代理:
    1. 静态代理:代理类在程序运行前已经存在的代理方式
    2. 动态代理:代理类在程序运行前不存在、运行时由程序动态生成的代理方式
    具体请看文章代理模式(Proxy Pattern)- *易懂的设计模式解析

  • return (T) roxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler)通过代理模式中的动态代理模式,动态生成网络请求接口的代理类,并将代理类的实例创建交给InvocationHandler类 作为具体的实现,并*终返回一个动态代理对象。
    生成实例过程中含有生成实现类的缓存机制(单例模式),下面会详细分析

使用动态代理的好处:

  • NetService对象调用getCall()接口中方法时会进行拦截,调用都会集中转发到 InvocationHandler#invoke (),可集中进行处理
  • 获得网络请求接口实例上的所有注解
  • 更方便封装ServiceMethod

下面看源码分析

下面将详细分析`InvocationHandler类 # invoke()`里的具体实现

 new InvocationHandler() {   
          private final Platform platform = Platform.get();

  @Override 
           public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {

            // 将详细介绍下面代码
            // 关注点1
            // 作用:读取网络请求接口里的方法,并根据前面配置好的属性配置serviceMethod对象
            ServiceMethod serviceMethod = loadServiceMethod(method);     

            // 关注点2
            // 作用:根据配置好的serviceMethod对象创建okHttpCall对象 
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);

            // 关注点3
            // 作用:调用OkHttp,并根据okHttpCall返回rejava的Observe对象或者返回Call
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }

下面将详细介绍3个关注点的代码。

关注点1: ServiceMethod serviceMethod = loadServiceMethod(method);

<-- loadServiceMethod(method)方法讲解 -->
// 一个 ServiceMethod 对象对应于网络请求接口里的一个方法
// loadServiceMethod(method)负责加载 ServiceMethod:

  ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result;
      // 设置线程同步锁
    synchronized (serviceMethodCache) {

      result = serviceMethodCache.get(method);
      // ServiceMethod类对象采用了单例模式进行创建
      // 即创建ServiceMethod对象前,先看serviceMethodCache有没有缓存之前创建过的网络请求实例

      // 若没缓存,则通过建造者模式创建 serviceMethod 对象
      if (result == null) {
      // 下面会详细介绍ServiceMethod生成实例的过程
        result = new ServiceMethod.Builder(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }
// 这里就是上面说的创建实例的缓存机制:采用单例模式从而实现一个 ServiceMethod 对象对应于网络请求接口里的一个方法
// 注:由于每次获取接口实例都是传入 class 对象
// 而 class 对象在进程内单例的,所以获取到它的同一个方法 Method 实例也是单例的,所以这里的缓存是有效的。

下面,我将分3个步骤详细分析serviceMethod实例的创建过程:
Paste_Image.png

步骤1:ServiceMethod类 构造函数

Paste_Image.png


<-- ServiceMethod 类 -->
public final class ServiceMethod {
final okhttp3.Call.Factory callFactory;   // 网络请求工厂  
final CallAdapter<?> callAdapter;  
// 网络请求适配器工厂
// 具体创建是在new ServiceMethod.Builder(this, method).build()*后的build()中
// 下面会详细说明

private final Converter<ResponseBody, T> responseConverter; 
// Response内容转换器  
// 作用:负责把服务器返回的数据(JSON或者其他格式,由 ResponseBody 封装)转化为 T 类型的对象;

private final HttpUrl baseUrl; // 网络请求地址  
private final String relativeUrl; // 网络请求的相对地址  
private final String httpMethod;   // 网络请求的Http方法  
private final Headers headers;  // 网络请求的http请求头 键值对  
private final MediaType contentType; // 网络请求的http报文body的类型  

private final ParameterHandler<?>[] parameterHandlers;  
  // 方法参数处理器
  // 作用:负责解析 API 定义时每个方法的参数,并在构造 HTTP 请求时设置参数;
  // 下面会详细说明

// 说明:从上面的成员变量可以看出,ServiceMethod对象包含了访问网络的所有基本信息

<-- ServiceMethod 类的构造函数 -->
// 作用:传入各种网络请求参数
ServiceMethod(Builder<T> builder) {

    this.callFactory = builder.retrofit.callFactory();  
    this.callAdapter = builder.callAdapter;   
    this.responseConverter = builder.responseConverter;   

    this.baseUrl = builder.retrofit.baseUrl();   
    this.relativeUrl = builder.relativeUrl;   
    this.httpMethod = builder.httpMethod;  
    this.headers = builder.headers;  
    this.contentType = builder.contentType; .  
    this.hasBody = builder.hasBody; y  
    this.isFormEncoded = builder.isFormEncoded;   
    this.isMultipart = builder.isMultipart;  
    this.parameterHandlers = builder.parameterHandlers;  
}

步骤2:ServiceMethod的Builder()

Paste_Image.png


   public Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;

      // 获取网络请求接口方法里的注释
      this.methodAnnotations = method.getAnnotations();
      // 获取网络请求接口方法里的参数类型       
      this.parameterTypes = method.getGenericParameterTypes();  
      //获取网络请求接口方法里的注解内容    
      this.parameterAnnotationsArray = method.getParameterAnnotations();    
    }

步骤3:ServiceMethod的build()

Paste_Image.png


// 作用:控制ServiceMethod对象的生成流程

 public ServiceMethod build() {

      callAdapter = createCallAdapter();    
      // 根据网络请求接口方法的返回值和注解类型,从Retrofit对象中获取对应的网络请求适配器  -->关注点1

      responseType = callAdapter.responseType();    
     // 根据网络请求接口方法的返回值和注解类型,从Retrofit对象中获取该网络适配器返回的数据类型

      responseConverter = createResponseConverter();    
      // 根据网络请求接口方法的返回值和注解类型,从Retrofit对象中获取对应的数据转换器  -->关注点3
      // 构造 HTTP 请求时,我们传递的参数都是String
      // Retrofit 类提供 converter把传递的参数都转化为 String 
      // 其余类型的参数都利用 Converter.Factory 的stringConverter 进行转换
      // @Body 和 @Part 类型的参数利用Converter.Factory 提供的 requestBodyConverter 进行转换
      // 这三种 converter 都是通过“询问”工厂列表进行提供,而工厂列表我们可以在构造 Retrofit 对象时进行添加。


       for (Annotation annotation : methodAnnotations) {
        parseMethodAnnotation(annotation);
      }
      // 解析网络请求接口中方法的注解
      // 主要是解析获取Http请求的方法
     // 注解包括:DELETE、GET、POST、HEAD、PATCH、PUT、OPTIONS、HTTP、retrofit2.http.Headers、Multipart、FormUrlEncoded
     // 处理主要是调用方法 parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) ServiceMethod中的httpMethod、hasBody、relativeUrl、relativeUrlParamNames域进行赋值

     int parameterCount = parameterAnnotationsArray.length;
     // 获取当前方法的参数数量

      parameterHandlers = new ParameterHandler<?>[parameterCount];
      for (int p = 0; p < parameterCount; p++) {
        Type parameterType = parameterTypes[p];
        Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
        // 为方法中的每个参数创建一个ParameterHandler<?>对象并解析每个参数使用的注解类型
        // 该对象的创建过程就是对方法参数中注解进行解析
        // 这里的注解包括:Body、PartMap、Part、FieldMap、Field、Header、QueryMap、Query、Path、Url 
        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      } 
      return new ServiceMethod<>(this);

<-- 总结 -->
// 1. 根据返回值类型和方法标注从Retrofit对象的的网络请求适配器工厂集合和内容转换器工厂集合中分别获取到该方法对应的网络请求适配器和Response内容转换器;
// 2. 根据方法的标注对ServiceMethod的域进行赋值
// 3. *后为每个方法的参数的标注进行解析,获得一个ParameterHandler<?>对象
// 该对象保存有一个Request内容转换器——根据参数的类型从Retrofit的内容转换器工厂集合中获取一个Request内容转换器或者一个String内容转换器。
    }


<-- 关注点1:createCallAdapter() -->
 private CallAdapter<?> createCallAdapter() {

      // 获取网络请求接口里方法的返回值类型
      Type returnType = method.getGenericReturnType();      

      // 获取网络请求接口接口里的注解
      // 此处使用的是@Get
      Annotation[] annotations = method.getAnnotations();       
      try {

      return retrofit.callAdapter(returnType, annotations); 
      // 根据网络请求接口方法的返回值和注解类型,从Retrofit对象中获取对应的网络请求适配器
      // 下面会详细说明retrofit.callAdapter() -- >关注点2
      }
...


<-- 关注点2:retrofit.callAdapter()  -->
 public CallAdapter<?> callAdapter(Type returnType, Annotation[] annotations) {
    return nextCallAdapter(null, returnType, annotations);
  }

 public CallAdapter<?> nextCallAdapter(CallAdapter.Factory skipPast, Type returnType,
      Annotation[] annotations) {

    // 创建 CallAdapter 如下
    // 遍历 CallAdapter.Factory 集合寻找合适的工厂(该工厂集合在*步构造 Retrofit 对象时进行添加(*步时已经说明))
    // 如果*终没有工厂提供需要的 CallAdapter,将抛出异常
    for (int i = start, count = adapterFactories.size(); i < count; i++) {
      CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this);      
      if (adapter != null) {
        return adapter;
      }
    }


<--   关注点3:createResponseConverter() -->

 private Converter<ResponseBody, T> createResponseConverter() {
      Annotation[] annotations = method.getAnnotations();
      try {

        // responseConverter 还是由 Retrofit 类提供  -->关注点4
        return retrofit.responseBodyConverter(responseType, annotations);
      } catch (RuntimeException e) { 
        throw methodError(e, "Unable to create converter for %s", responseType);
      }
    }

<--   关注点4:responseBodyConverter() -->
  public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
    return nextResponseBodyConverter(null, type, annotations);
  }

 public <T> Converter<ResponseBody, T> nextResponseBodyConverter(Converter.Factory skipPast,

    int start = converterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = converterFactories.size(); i < count; i++) {

       // 获取Converter 过程:(和获取 callAdapter 基本一致)
         Converter<ResponseBody, ?> converter =
          converterFactories.get(i).responseBodyConverter(type, annotations, this); 
       // 遍历 Converter.Factory 集合并寻找合适的工厂(该工厂集合在构造 Retrofit 对象时进行添加(*步时已经说明))
       // 由于构造Retroifit采用的是Gson解析方式,所以取出的是GsonResponseBodyConverter
       // Retrofit - Converters 还提供了 JSON,XML,ProtoBuf 等类型数据的转换功能。
       // 继续看responseBodyConverter() -->关注点5    
    }


<--   关注点5:responseBodyConverter() -->
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, 
    Annotation[] annotations, Retrofit retrofit) {


  TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
  // 根据目标类型,利用 Gson#getAdapter 获取相应的 adapter
  return new GsonResponseBodyConverter<>(gson, adapter);
}

// 做数据转换时调用 Gson 的 API 即可。
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override 
   public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      return adapter.read(jsonReader);
    } finally {
      value.close();
    }
  }
}
  • 当选择了RxjavaCallAdapterFactory后,Rxjava通过策略模式选择对应的adapter

    关于策略模式的讲解,请看文章策略模式(Strategy Pattern)- *易懂的设计模式解析

  • 具体过程是:根据网络接口方法的返回值类型来选择具体要用哪种CallAdapterFactory,然后创建具体的CallAdapter实例

采用工厂模式使得各功能模块高度解耦

  • 上面提到了两种工厂:CallAdapter.Factory & Converter.Factory分别负责提供不同的功能模块
  • 工厂负责如何提供、提供何种功能模块
  • Retrofit 只负责提供选择何种工厂的决策信息(如网络接口方法的参数、返回值类型、注解等)

这正是所谓的高内聚低耦合,工厂模式get。

关于工厂模式请看我写的文章:
简单工厂模式(SimpleFactoryPattern)- *易懂的设计模式解析
工厂方法模式(Factory Method)- *易懂的设计模式解析
抽象工厂模式(Abstract Factory)- *易懂的设计模式解析

终于配置完网络请求参数(即配置好ServiceMethod对象)。接下来将讲解第二行代码:okHttpCall对象的创建

第二行:OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);

根据*步配置好的ServiceMethod对象和输入的请求参数创建okHttpCall对象

<--OkHttpCall类 -->
public class OkHttpCall {
    private final ServiceMethod<T> serviceMethod; // 含有所有网络请求参数信息的对象  
    private final Object[] args; // 网络请求接口的参数 
    private okhttp3.Call rawCall; //实际进行网络访问的类  
    private Throwable creationFailure; //几个状态标志位  
    private boolean executed;  
    private volatile boolean canceled;  

<--OkHttpCall构造函数 -->
  public OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {  
    // 传入了配置好的ServiceMethod对象和输入的请求参数
    this.serviceMethod = serviceMethod;  
    this.args = args;  
}  

第三行:return serviceMethod.callAdapter.adapt(okHttpCall);

将第二步创建的OkHttpCall对象传给*步创建的serviceMethod对象中对应的网络请求适配器工厂的adapt()

返回对象类型:Android默认的是Call<>;若设置了RxJavaCallAdapterFactory,返回的则是Observable<>

<--  adapt()详解-->
public <R> Call<R> adapt(Call<R> call) {
        return new ExecutorCallbackCall<>(callbackExecutor, call);  
      }

   ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.delegate = delegate; 
      // 把上面创建并配置好参数的OkhttpCall对象交给静态代理delegate
      // 静态代理和动态代理都属于代理模式
     // 静态代理作用:代理执行被代理者的方法,且可在要执行的方法前后加入自己的动作,进行对系统功能的拓展

      this.callbackExecutor = callbackExecutor;
      // 传入上面定义的回调方法执行器
      // 用于进行线程切换   
    }
  • 采用了装饰模式:ExecutorCallbackCall = 装饰者,而里面真正去执行网络请求的还是OkHttpCall
  • 使用装饰模式的原因:希望在OkHttpCall发送请求时做一些额外操作。这里的额外操作是线程转换,即将子线程切换到主线程
    1. OkHttpCall的enqueue()是进行网络异步请求的:当你调用OkHttpCall.enqueue()时,回调的callback是在子线程中,需要通过Handler转换到主线程进行回调。ExecutorCallbackCall就是用于线程回调;
    2. 当然以上是原生Retrofit使用的切换线程方式。如果你用Rxjava,那就不会用到这个ExecutorCallbackCall而是RxJava的Call,此处不过多展开

步骤4讲解:Call<JavaBean> call = NetService.getCall();

  • NetService对象实际上是动态代理对象Proxy.newProxyInstance()(步骤3中已说明),并不是真正的网络请求接口创建的对象
  • NetService对象调用getCall()时会被动态代理对象Proxy.newProxyInstance()拦截,然后调用自身的InvocationHandler # invoke()
  • invoke(Object proxy, Method method, Object... args)会传入3个参数:Object proxy:(代理对象)、
    Method method(调用的getCall()
    Object... args(方法的参数,即getCall(*)中的*)
  • 接下来利用Java反射获取到getCall()的注解信息,配合args参数创建ServiceMethod对象

    如上面步骤3描述,此处不再次讲解

*终创建并返回一个OkHttpCall类型的Call对象
1. OkHttpCall类是OkHttp的包装类
2. 创建了OkHttpCall类型的Call对象还不能发送网络请求,需要创建Request对象才能发送网络请求

总结

Retrofit采用了 外观模式 统一调用创建网络请求接口实例和网络请求参数配置的方法,具体细节是:

  • 动态创建网络请求接口的实例(代理模式 – 动态代理)
  • 创建 serviceMethod 对象(建造者模式 & 单例模式(缓存机制))
  • 对 serviceMethod 对象进行网络请求参数配置:通过解析网络请求接口方法的参数、返回值和注解类型,从Retrofit对象中获取对应的网络请求的url地址、网络请求执行器、网络请求适配器 & 数据转换器。(策略模式)
  • 对 serviceMethod 对象加入线程切换的操作,便于接收数据后通过Handler从子线程切换到主线程从而对返回数据结果进行处理(装饰模式)
  • *终创建并返回一个OkHttpCall类型的网络请求对象

3. 执行网络请求

  • Retrofit默认使用OkHttp,即OkHttpCall类(实现了 retrofit2.Call<T>接口)

    但可以自定义选择自己需要的Call类

  • OkHttpCall提供了两种网络请求方式:
    1. 同步请求:OkHttpCall.execute()
    2. 异步请求:OkHttpCall.enqueue()

下面将详细介绍这两种网络请求方式。
对于OkHttpCall的enqueue()、execute()此处不往下分析,有兴趣的读者可以看OkHttp的源码

3.1 同步请求OkHttpCall.execute()

3.1.1 发送请求过程

  • 步骤1:对网络请求接口的方法中的每个参数利用对应ParameterHandler进行解析,再根据ServiceMethod对象创建一个OkHttpRequest对象
  • 步骤2:使用OkHttpRequest发送网络请求;
  • 步骤3:对返回的数据使用之前设置的数据转换器(GsonConverterFactory)解析返回的数据,*终得到一个Response<T>对象

3.1.2 具体使用

Response<JavaBean> response = call.execute();  
  • 1

上面简单的一行代码,其实包含了整个发送网络同步请求的三个步骤。

3.1.3 源码分析

@Override 
public Response<T> execute() throws IOException {
  okhttp3.Call call;

 // 设置同步锁
  synchronized (this) {
    call = rawCall;
    if (call == null) {
      try {
        call = rawCall = createRawCall();
        // 步骤1:创建一个OkHttp的Request对象请求 -->关注1
      } catch (IOException | RuntimeException e) {
        creationFailure = e;
        throw e;
      }
    }
  }

  return parseResponse(call.execute());
  // 步骤2:调用OkHttpCall的execute()发送网络请求(同步)
  // 步骤3:解析网络请求返回的数据parseResponse() -->关注2
}

<-- 关注1:createRawCall()  -->
private okhttp3.Call createRawCall() throws IOException {

  Request request = serviceMethod.toRequest(args);
  // 从ServiceMethod的toRequest()返回一个Request对象
  okhttp3.Call call = serviceMethod.callFactory.newCall(request);
  // 根据serviceMethod和request对象创建 一个okhttp3.Request

  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}

<--  关注2:parseResponse()-->
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
  ResponseBody rawBody = rawResponse.body();

  rawResponse = rawResponse.newBuilder()
      .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
      .build();
  // 收到返回数据后进行状态码检查
  // 具体关于状态码说明下面会详细介绍
  int code = rawResponse.code();
  if (code < 200 || code >= 300) {
  }

  if (code == 204 || code == 205) {
    return Response.success(null, rawResponse);
  }

  ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
  try {
    T body = serviceMethod.toResponse(catchingBody);
   // 等Http请求返回后 & 通过状态码检查后,将response body传入ServiceMethod中,ServiceMethod通过调用Converter接口(之前设置的GsonConverterFactory)将response body转成一个Java对象,即解析返回的数据


// 生成Response类
    return Response.success(body, rawResponse);
  } catch (RuntimeException e) {
    ... // 异常处理
  }
}

特别注意:

  • ServiceMethod几乎保存了一个网络请求所需要的数据
  • 发送网络请求时,OkHttpCall需要从ServiceMethod中获得一个Request对象
  • 解析数据时,还需要通过ServiceMethod使用Converter(数据转换器)转换成Java对象进行数据解析

    为了提高效率,Retrofit还会对解析过的请求ServiceMethod进行缓存,存放在Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();对象中,即第二步提到的单例模式

  • 关于状态码检查时的状态码说明:

Paste_Image.png

以上便是整个以同步的方式发送网络请求的过程。

3.2 异步请求OkHttpCall.enqueue()

3.2.1 发送请求过程
  • 步骤1:对网络请求接口的方法中的每个参数利用对应ParameterHandler进行解析,再根据ServiceMethod对象创建一个OkHttpRequest对象
  • 步骤2:使用OkHttpRequest发送网络请求;
  • 步骤3:对返回的数据使用之前设置的数据转换器(GsonConverterFactory)解析返回的数据,*终得到一个Response<T>对象
  • 步骤4:进行线程切换从而在主线程处理返回的数据结果

    若使用了RxJava,则直接回调到主线程

异步请求的过程跟同步请求类似,唯一不同之处在于:异步请求会将回调方法交给回调执行器在指定的线程中执行。
指定的线程此处是指主线程(UI线程)

3.2.2 具体使用
call.enqueue(new Callback<JavaBean>() {
            @Override
            public void onResponse(Call<JavaBean> call, Response<JavaBean> response) {
                System.out.println(response.isSuccessful());
                if (response.isSuccessful()) {
                    response.body().show();
                }
                else {
                    try {
                        System.out.println(response.errorBody().string());
                    } catch (IOException e) {
                        e.printStackTrace();
                    } ;
                }
            }
  • 从上面分析有:call是一个静态代理
  • 使用静态代理的作用是:在okhttpCall发送网络请求的前后进行额外操作

    这里的额外操作是:线程切换,即将子线程切换到主线程,从而在主线程对返回的数据结果进行处理

3.2.3 源码分析
<--  call.enqueue()解析  -->
@Override 
public void enqueue(final Callback<T> callback) {

      delegate.enqueue(new Callback<T>() {
     // 使用静态代理 delegate进行异步请求 ->>分析1
     // 等下记得回来
        @Override 
        public void onResponse(Call<T> call, final Response<T> response) {
          // 步骤4:线程切换,从而在主线程显示结果
          callbackExecutor.execute(new Runnable() {
          // *后Okhttp的异步请求结果返回到callbackExecutor
          // callbackExecutor.execute()通过Handler异步回调将结果传回到主线程进行处理(如显示在Activity等等),即进行了线程切换
          // 具体是如何做线程切换 ->>分析2
              @Override 
               public void run() {
              if (delegate.isCanceled()) {
                callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
              } else {
                callback.onResponse(ExecutorCallbackCall.this, response);
              }
            }
          });
        }

        @Override 
        public void onFailure(Call<T> call, final Throwable t) {
          callbackExecutor.execute(new Runnable() {
            @Override public void run() {
              callback.onFailure(ExecutorCallbackCall.this, t);
            }
          });
        }
      });
    }


<-- 分析1:delegate.enqueue()解析 -->
@Override 
public void enqueue(final Callback<T> callback) {

    okhttp3.Call call;
    Throwable failure;

// 步骤1:创建OkHttp的Request对象,再封装成OkHttp.call
     // delegate代理在网络请求前的动作:创建OkHttp的Request对象,再封装成OkHttp.call
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {

          call = rawCall = createRawCall(); 
          // 创建OkHttp的Request对象,再封装成OkHttp.call
         // 方法同发送同步请求,此处不作过多描述  
        } catch (Throwable t) {
          failure = creationFailure = t;
        }
      }

// 步骤2:发送网络请求
    // delegate是OkHttpcall的静态代理
    // delegate静态代理*终还是调用Okhttp.enqueue进行网络请求
    call.enqueue(new okhttp3.Callback() {
      @Override 
        public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
          throws IOException {
        Response<T> response;
        try {

          // 步骤3:解析返回数据
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          callFailure(e);
          return;
        }
        callSuccess(response);
      }

      @Override 
         public void onFailure(okhttp3.Call call, IOException e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }

      private void callFailure(Throwable e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }

      private void callSuccess(Response<T> response) {
        try {
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
    });
  }

// 请回去上面分析1的起点

<-- 分析2:异步请求后的线程切换-->
// 线程切换是通过一开始创建Retrofit对象时Platform在检测到运行环境是Android时进行创建的:(之前已分析过)
// 采用适配器模式
static class Android extends Platform {

    // 创建默认的回调执行器工厂
    // 如果不将RxJava和Retrofit一起使用,一般都是使用该默认的CallAdapter.Factory
    // 后面会对RxJava和Retrofit一起使用的情况进行分析
    @Override
      CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
      return new ExecutorCallAdapterFactory(callbackExecutor);
    }

    @Override 
      public Executor defaultCallbackExecutor() {
      // 返回一个默认的回调方法执行器
      // 该执行器负责在主线程(UI线程)中执行回调方法
      return new MainThreadExecutor();
    }

    // 获取主线程Handler
    static class MainThreadExecutor implements Executor {
      private final Handler handler = new Handler(Looper.getMainLooper());


      @Override 
      public void execute(Runnable r) {
        // Retrofit获取了主线程的handler
        // 然后在UI线程执行网络请求回调后的数据显示等操作。
        handler.post(r);
      }
    }

// 切换线程的流程:
// 1. 回调ExecutorCallAdapterFactory生成了一个ExecutorCallbackCall对象
// 2. 通过调用ExecutorCallbackCall.enqueue(CallBack)从而调用MainThreadExecutor的execute()通过handler切换到主线程处理返回结果(如显示在Activity等等)
  }

以上便是整个以 异步方式发送网络请求的过程。


5. 总结

Retrofit 本质上是一个 RESTful 的HTTP 网络请求框架的封装,即通过 大量的设计模式 封装了 OkHttp ,使得简洁易用。具体过程如下:

  1. Retrofit 将 Http请求 抽象 成 Java接口
  2. 在接口里用 注解 描述和配置 网络请求参数
  3. 用动态代理 的方式,动态将网络请求接口的注解 解析 成HTTP请求
  4. *后执行HTTP请求

*后贴一张非常详细的Retrofit源码分析图:

Retrofit源码分析图

6. *后

  • 看完本文,相信你已经非常熟悉 Retrofit 2.0 的源码分析
  • 关于Retrofit 2.0的详细使用教程,请看文章这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)
  • 接下来,我将继续分析与 Retrofit 配合使用的 RxJava,有兴趣可以继续关注Carson_Ho的安卓开发笔记

深入解析OkHttp3

OkHttp是一个精巧的网络请求库,有如下特性: 
1)支持http2,对一台机器的所有请求共享同一个socket 
2)内置连接池,支持连接复用,减少延迟 
3)支持透明的gzip压缩响应体 
4)通过缓存避免重复的请求 
5)请求失败时自动重试主机的其他ip,自动重定向 
6)好用的API
其本身就是一个很强大的库,再加上Retrofit2、Picasso的这一套组合拳,使其愈发的受到开发者的关注。本篇博客,我将对Okhttp3进行分析(源码基于Okhttp3.4)。
如何引入Okhttp3?
配置Okhttp3非常简单,只需要在Android Studio 的gradle进行如下的配置:
compile ‘com.squareup.okhttp3:okhttp:3.4.1′
* 1
添加网络权限:
<uses-permission android:name=”android.permission.INTERNET”/>
* 1
Okhttp3的基本使用
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);
}
});

}


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”);
* 1
* 2
将文件上传到服务器上:
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);
* 1
BaseCallback中多定义2个抽象方法
public abstract  void onFailure(Request request, Exception e) ;

/**
*请求成功时调用此方法
* @param response
*/
public abstract  void onResponse(Response response);

由于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>
* 1
BaseCallback中需要将泛型转换为Type,所以要声明Type类型
public   Type mType;
* 1
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源码分析
请求处理分析 
当我们要请求网络的时候我们需要用OkHttpClient.newCall(request)进行execute或者enqueue操作,当我们调用newCall时:
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}

实际返回的是一个RealCall类,我们调用enqueue异步请求网络实际上是调用了RealCall的enqueue方法:
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException(“Already Executed”);
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
*终的请求是dispatcher来完成的。
Dispatcher任务调度
Dispatcher的本质是异步请求的管理器,控制*大请求并发数和单个主机的*大并发数,并持有一个线程池负责执行异步请求。对同步的请求只是用作统计。他是如何做到控制并发呢,其实原理就在上面的2个execute代码里面,真正网络请求执行前后会调用executed和finished方法,而对于AsyncCall的finished方法后,会根据当前并发数目选择是否执行队列中等待的AsyncCall。并且如果修改Dispatcher的maxRequests或者maxRequestsPerHost也会触发这个过程。 
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比较适合执行大量的耗时比较少的任务。
异步请求
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}

当正在运行的异步请求队列中的数量小于64并且正在运行的请求主机数小于5时则把请求加载到runningAsyncCalls中并在线程池中执行,否则就再入到readyAsyncCalls中进行缓存等待。
AsyncCall 
线程池中传进来的参数就是AsyncCall它是RealCall的内部类,内部也实现了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);
}
}
}

首先我们来看看*后一行, 无论这个请求的结果如何都会执行client.dispatcher().finished(this);
/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}

/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}

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();
}
}
finished方法将此次请求从runningAsyncCalls移除后还执行了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.
}
}

可以看到*关键的点就是会从readyAsyncCalls取出下一个请求,并加入runningAsyncCalls中并交由线程池处理。好了让我们再回到上面的AsyncCall的execute方法,我们会发getResponseWithInterceptorChain方法返回了Response,很明显这是在请求网络。
Interceptor拦截器 
在回到RealCall中,我们看到无论是execute还是enqueue,真正的Response是通过这个函数getResponseWithInterceptorChain获取的,其他的代码都是用作控制与回调。而这里就是真正请求的入口,也是到了OkHttp的一个很精彩的设计:Interceptor与Chain 
看一下RealCall中的getResponseWithInterceptorChain方法
private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));

Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}

这也是与旧版本不一致的地方,在3.4.x以前,没有这些内部的这些拦截器,只有用户的拦截器与网络拦截器。而Request和Response是通过HttpEngine来完成的。在RealCall实现了用户拦截器与RetryAndFollowUp的过程,而在HttpEngine内部处理了请求转换、Cookie、Cache、网络拦截器、连接网络的过程。值得一提的是,在旧版是获取到Response后调用网络拦截器的拦截。 
而在这里,RealInterceptorChain会递归的创建并以此调用拦截器,去掉诸多异常,简化版代码如下:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream,
Connection 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.httpStream != null && !sameConnection(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.httpStream != 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, httpStream, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);

// Confirm that the next interceptor made its required call to chain.proceed().
if (httpStream != 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请求
连接与通信 
在RetryAndFollowUpInterceptor中,会创建StreamAllocation,然后交给下游的ConnectInterceptor
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();

// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals(“GET”);
HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();

return realChain.proceed(request, streamAllocation, httpStream, connection);
}

这里会创建一个HttpStream,并且取到一个RealConnection,继续交给下游的CallServerInterceptor。 
我们跟踪进去看看,StreamAllocation里面做了什么
public HttpStream newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();

try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);

HttpStream resultStream;
if (resultConnection.framedConnection != null) {
resultStream = new Http2xStream(client, this, resultConnection.framedConnection);
} else {
resultConnection.socket().setSoTimeout(readTimeout);
resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
resultStream = new Http1xStream(
client, this, resultConnection.source, resultConnection.sink);
}

synchronized (connectionPool) {
stream = resultStream;
return resultStream;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
这里的代码逻辑是这样的,找一个健康的连接,设置超时时间,然后根据协议创建一个HttpStream并返回。 
继续跟进去看findHealthyConnection:
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);

// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}

// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn’t, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}

return candidate;
}
}

上面的逻辑也很简单,在findConnection中找一个连接,然后做健康检查,如果不健康就回收,并再次循环,那么真正寻找连接的代码就在findConnection里面了:
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
*/
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
if (released) throw new IllegalStateException(“released”);
if (stream != null) throw new IllegalStateException(“stream != null”);
if (canceled) throw new IOException(“Canceled”);

RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}

// Attempt to get a connection from the pool.
RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
if (pooledConnection != null) {
this.connection = pooledConnection;
return pooledConnection;
}

selectedRoute = route;
}

if (selectedRoute == null) {
selectedRoute = routeSelector.next();
synchronized (connectionPool) {
route = selectedRoute;
refusedStreamCount = 0;
}
}
RealConnection newConnection = new RealConnection(selectedRoute);
acquire(newConnection);

synchronized (connectionPool) {
Internal.instance.put(connectionPool, newConnection);
this.connection = newConnection;
if (canceled) throw new IOException(“Canceled”);
}

newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),
connectionRetryEnabled);
routeDatabase().connected(newConnection.route());

return newConnection;
}

这里大概分成分成3大步: 
1)如果当前有连接并且符合要求的话,就直接返回 
2)如果线程池能取到一个符合要求的连接的话,就直接返回 
3)如果Route为空,从RouteSelector取一个Route,然后新建一个RealConnection,并放入ConnectionPool,随后调用connect,再返回
也就是说不管当前走的是步骤1还是2,一开始一定是从3开始的,也就是在RealConnection的connect中真正完成了socket连接。 
connect里面代码比较长,真正要做的就是一件事,如果是https请求并且是http代理,则建立隧道连接,隧道连接请参考RFC2817,否则建立普通连接。 
这两者都调用了2个函数:connectSocket(connectTimeout, readTimeout); establishProtocol(readTimeout, writeTimeout, connectionSpecSelector); 
但是隧道连接则多了一个代理认证的过程,可能会反复的connectSocket和构造请求。 
看一下connectSocket:
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();

rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);

rawSocket.setSoTimeout(readTimeout);
try {
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
throw new ConnectException(“Failed to connect to ” + route.socketAddress());
}
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}

就是根据Route来创建socket,在connect,随后将rawSocket的InputStream与OutputStream包装成Source与Sink。这里提一下,OkHttp是依赖Okio的,Okio封装了Java的IO API,如这里的Source与Sink,非常简洁实用。
而establishProtocol里,如果是https则走TLS协议,生成一个SSLSocket,并进行握手和验证,同时如果是HTTP2或者SPDY3的话,则生成一个FrameConnection。这里不再多提,HTTP2和HTTP1.X大相径庭,我们这里主要是分析HTTP1.X的连接,后面有机会我们会单独开篇讲HTTP2。同时TLS相关的话题这里也一并略过,想了解的朋友可以看一看相应的Java API和HTTPS连接的资料。
再回到StreamAllcation.newStream的代码resultStream = new Http1xStream( client, this, resultConnection.source, resultConnection.sink);实质上HttpStream其实就是Request和Response读写Socket的抽象,我们看到Http1xStream取到了Socket输入输出流,随后在CallServerInterceptor可以拿来做读写。
我们看CallServerInterceptor做了什么:
@Override public Response intercept(Chain chain) throws IOException {
HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();

long sentRequestMillis = System.currentTimeMillis();
httpStream.writeRequestHeaders(request);

if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}

httpStream.finishRequest();

Response response = httpStream.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();

if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpStream.openResponseBody(response))
.build();
}

if (“close”.equalsIgnoreCase(response.request().header(“Connection”))
|| “close”.equalsIgnoreCase(response.header(“Connection”))) {
streamAllocation.noNewStreams();
}

int code = response.code();
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
“HTTP ” + code + ” had non-zero Content-Length: ” + response.body().contentLength());
}

return response;
}

CallServerInterceptor顾名思义,就是真正和Server进行通信的地方。这里也是按照HTTP协议,依次写入请求头,还有根据情况决定是否写入请求体。随后读响应头闭构造一个Response。 
里面具体是如何实现呢,我们看Http1xStream: 
首先是写头:
@Override public void writeRequestHeaders(Request request) throws IOException {
String requestLine = RequestLine.get(
request, streamAllocation.connection().route().proxy().type());
writeRequest(request.headers(), requestLine);
}

构造好请求行,进入writeRequest:
/** Returns bytes of a request header for sending on an HTTP transport. */
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException(“state: ” + state);
sink.writeUtf8(requestLine).writeUtf8(“\r\n”);
for (int i = 0, size = headers.size(); i < size; i++) {
sink.writeUtf8(headers.name(i))
.writeUtf8(“: “)
.writeUtf8(headers.value(i))
.writeUtf8(“\r\n”);
}
sink.writeUtf8(“\r\n”);
state = STATE_OPEN_REQUEST_BODY;
}

这里就一目了然了,就是一行行的写请求行和请求头到sink中 
再看readResponse:
/** Parses bytes of a response header from an HTTP transport. */
public Response.Builder readResponse() throws IOException {
if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
throw new IllegalStateException(“state: ” + state);
}

try {
while (true) {
StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());

Response.Builder responseBuilder = new Response.Builder()
.protocol(statusLine.protocol)
.code(statusLine.code)
.message(statusLine.message)
.headers(readHeaders());

if (statusLine.code != HTTP_CONTINUE) {
state = STATE_OPEN_RESPONSE_BODY;
return responseBuilder;
}
}
} catch (EOFException e) {
// Provide more context if the server ends the stream before sending a response.
IOException exception = new IOException(“unexpected end of stream on ” + streamAllocation);
exception.initCause(e);
throw exception;
}
}

也是一样的,从source中读请求行和请求头 
*后看openResponseBody:
@Override public ResponseBody openResponseBody(Response response) throws IOException {
Source source = getTransferStream(response);
return new RealResponseBody(response.headers(), Okio.buffer(source));
}

这里说一下就是根据请求的响应把包裹InputStream的source再次封装,里面做一些控制逻辑,然后再封装成ResponseBody。 
例如FiexdLengthSource,就是期望获取到byte的长度是固定的值:
/** An HTTP body with a fixed length specified in advance. */
private class FixedLengthSource extends AbstractSource {
private long bytesRemaining;

public FixedLengthSource(long length) throws IOException {
bytesRemaining = length;
if (bytesRemaining == 0) {
endOfInput(true);
}
}

@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException(“byteCount < 0: ” + byteCount);
if (closed) throw new IllegalStateException(“closed”);
if (bytesRemaining == 0) return -1;

long read = source.read(sink, Math.min(bytesRemaining, byteCount));
if (read == -1) {
endOfInput(false); // The server didn’t supply the promised content length.
throw new ProtocolException(“unexpected end of stream”);
}

bytesRemaining -= read;
if (bytesRemaining == 0) {
endOfInput(true);
}
return read;
}

@Override public void close() throws IOException {
if (closed) return;

if (bytesRemaining != 0 && !Util.discard(this, DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
endOfInput(false);
}

closed = true;
}
}

当读完期望的长度时就把这个RealConnection回收,如果少于期望的长度则抛异常。
ConnectionPool 
到了OkHttp3时代,ConnectionPool就是每个Client独享的了,我们刚才提到了ConnectionPool,那么他到底是如何运作呢。 
ConnectionPool持有一个静态的线程池。 
StreamAllocation不管通过什么方式,在获取到RealConnection后,RealConnection会添加一个对StreamAllocation的引用。 
在每个RealConnection加入ConnectionPool后,如果当前没有在清理,就会把cleanUpRunnable加入线程池。 
cleanUpRunnable里面是一个while(true),一个循环包括: 
调用一次cleanUp方法进行清理并返回一个long, 如果是-1则退出,否则调用wait方法等待这个long值的时间 
cleanUp代码如下:
ong cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;

// Find either a connection to evict, or the time that the next eviction is due.
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();

// If the connection is in use, keep searching.
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}

idleConnectionCount++;

// If the connection is ready to be evicted, we’re done.
long idleDurationNs = now – connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}

if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We’ve found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// A connection will be ready to evict soon.
return keepAliveDurationNs – longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// All connections are in use. It’ll be at least the keep alive duration ’til we run again.
return keepAliveDurationNs;
} else {
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
}

closeQuietly(longestIdleConnection.socket());

// Cleanup again immediately.
return 0;
}

遍历每一个RealConnection,通过引用数目确定哪些是空闲的,哪些是在使用中,同时找到空闲时间*长的RealConnection。 
如果空闲数目超过*大空闲数或者空闲时间超过*大空闲时间,则清理掉这个RealConnection,并返回0,表示需要立刻再次清理 
否则如果空闲的数目大于0个,则等待*大空闲时间-已有的*长空闲时间 
否则如果使用中的数目大于0,则等待*大空闲时间 
否则 返回 -1,并标识退出清除状态 
同时如果某个RealConnection空闲后,会进入ConnectionPool.connectionBecameIdle方法,如果不可被复用,则被移除,否则立刻唤醒上面cleanUp的wait,再次清理,因为可能超过了*大空闲数目 
这样通过一个静态的线程池,ConnectionPool做到了每个实例定期清理,保证不会超过*大空闲时间和*大空闲数目的策略。
OkHttp3分析就到此结束了。

Android面试题-OkHttp3源码分析

本文配套视频:

  • okhttp内核分析配套视频一
  • okhttp内核分析配套视频二
  • okhttp内核分析配套视频三

源码分析相关面试题

  • Volley源码分析
  • 注解框架实现原理

基本使用

从使用方法出发,首先是怎么使用,其次是我们使用的功能在内部是如何实现的.建议大家下载 OkHttp 源码之后,跟着本文,过一遍源码。

官方博客栗子:http://square.github.io/okhttp/#examples

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

 

Request、Response、Call 基本概念

上面的代码中涉及到几个常用的类:Request、Response和Call。下面分别介绍:

Request

每一个HTTP请求包含一个URL、一个方法(GET或POST或其他)、一些HTTP头。请求还可能包含一个特定内容类型的数据类的主体部分。

Response

响应是对请求的回复,包含状态码、HTTP头和主体部分。

Call

OkHttp使用Call抽象出一个满足请求的模型,尽管中间可能会有多个请求或响应。执行Call有两种方式,同步或异步

*步:创建 OkHttpClient对象,进行源码分析:

OkHttpClient client = new OkHttpClient();`

 

通过okhttp源码分析,直接创建的 OkHttpClient对象并且默认构造builder对象进行初始化

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
  public OkHttpClient() {
       this(new Builder());
  }
  OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.eventListenerFactory = builder.eventListenerFactory;
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;

    boolean isTLS = false;
    ......

    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool;
    this.dns = builder.dns;
    this.followSslRedirects = builder.followSslRedirects;
    this.followRedirects = builder.followRedirects;
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;
  }
}

 

第二步:接下来发起 HTTP 请求

Request request = new Request.Builder().url("url").build();
okHttpClient.newCall(request).enqueue(new Callback() {
  @Override
  public void onFailure(Call call, IOException e) {

 }

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

}
});

 

第二步:代码流程分析:

Request request = new Request.Builder().url("url").build();
  • 1

初始化构建者模式和请求对象,并且用URL替换Web套接字URL。

public final class Request {
    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }
    public Builder url(String url) {
      ......

      // Silently replace web socket URLs with HTTP URLs.
      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
        url = "http:" + url.substring(3);
      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
        url = "https:" + url.substring(4);
      }

      HttpUrl parsed = HttpUrl.parse(url);
      ......
      return url(parsed);
    }
    public Request build() {
      ......
      return new Request(this);
    }
}

 

第三步:方法解析:

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

}

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

}
});

 

源码分析:

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
   @Override 
   public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
   }



}

 

RealCall实现了Call.Factory接口创建了一个RealCall的实例,而RealCall是Call接口的实现。

异步请求的执行流程

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

 

由以上源码得知:

1) 检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完全一样的 call,可以利用 call#clone 方法进行克隆。

2)利用 client.dispatcher().enqueue(this) 来进行实际执行,dispatcher 是刚才看到的 OkHttpClient.Builder 的成员之一

3)AsyncCall是RealCall的一个内部类并且继承NamedRunnable,那么首先看NamedRunnable类是什么样的,如下:

public abstract class NamedRunnable implements Runnable {
  ......

  @Override 
  public final void run() {
   ......
    try {
      execute();
    }
    ......
  }

  protected abstract void execute();
}

 

可以看到NamedRunnable实现了Runnbale接口并且是个抽象类,其抽象方法是execute(),该方法是在run方法中被调用的,这也就意味着NamedRunnable是一个任务,并且其子类应该实现execute方法。下面再看AsyncCall的实现:

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    ......
final class RealCall implements Call {
  @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) {
  ......
  responseCallback.onFailure(RealCall.this, e);

} finally {
    client.dispatcher().finished(this);
  }
}

 

AsyncCall实现了execute方法,首先是调用getResponseWithInterceptorChain()方法获取响应,然后获取成功后,就调用回调的onReponse方法,如果失败,就调用回调的onFailure方法。*后,调用Dispatcher的finished方法。

关键代码:

responseCallback.onFailure(RealCall.this, new IOException(“Canceled”));

responseCallback.onResponse(RealCall.this, response);

走完这两句代码会进行回调到刚刚我们初始化Okhttp的地方,如下:

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

   }

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

   }
});

 

核心重点类Dispatcher线程池介绍

public final class Dispatcher {
  /** *大并发请求数为64 */
  private int maxRequests = 64;
  /** 每个主机*大请求数为5 */
  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<>();

 

在OkHttp,使用如下构造了单例线程池

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;
  }

 

构造一个线程池ExecutorService:

executorService = new ThreadPoolExecutor(
//corePoolSize *小并发线程数,如果是0的话,空闲一段时间后所有线程将全部被销毁
    0, 
//maximumPoolSize: *大线程数,当任务进来时可以扩充的线程*大值,当大于了这个值就会根据丢弃处理机制来处理
    Integer.MAX_VALUE, 
//keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的*大存活时间
    60, 
//单位秒
    TimeUnit.SECONDS,
//工作队列,先进先出
    new SynchronousQueue<Runnable>(),   
//单个线程的工厂         
   Util.threadFactory("OkHttp Dispatcher", false));

 

可以看出,在Okhttp中,构建了一个核心为[0, Integer.MAX_VALUE]的线程池,它不保留任何*小线程数,随时创建更多的线程数,当线程空闲时只能活60秒,它使用了一个不存储元素的阻塞工作队列,一个叫做”OkHttp Dispatcher”的线程工厂。

也就是说,在实际运行中,当收到10个并发请求时,线程池会创建十个线程,当工作完成后,线程池会在60s后相继关闭所有线程。

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

 

从上述源码分析,如果当前还能执行一个并发请求,则加入 runningAsyncCalls ,立即执行,否则加入 readyAsyncCalls 队列。

Dispatcher线程池总结

1)调度线程池Disptcher实现了高并发,低阻塞的实现
2)采用Deque作为缓存,先进先出的顺序执行
3)任务在try/finally中调用了finished函数,控制任务队列的执行顺序,而不是采用锁,减少了编码复杂性提高性能

这里是分析OkHttp源码,并不详细讲线程池原理,如对线程池不了解请参考如下链接

点我,线程池原理,在文章性能优化*后有视频对线程池原理讲解


 try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } finally {
        client.dispatcher().finished(this);
      }

 

当任务执行完成后,无论是否有异常,finally代码段总会被执行,也就是会调用Dispatcher的finished函数

 void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

 

从上面的代码可以看出,*个参数传入的是正在运行的异步队列,第三个参数为true,下面再看有是三个参数的finished方法:

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();
    }
  }

 

打开源码,发现它将正在运行的任务Call从队列runningAsyncCalls中移除后,获取运行数量判断是否进入了Idle状态,接着执行promoteCalls()函数,下面是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.
    }
  }

 

主要就是遍历等待队列,并且需要满足同一主机的请求小于maxRequestsPerHost时,就移到运行队列中并交给线程池运行。就主动的把缓存队列向前走了一步,而没有使用互斥锁等复杂编码

核心重点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);
  }

 

%title插图%num

1)在配置 OkHttpClient 时设置的 interceptors;
2)负责失败重试以及重定向的 RetryAndFollowUpInterceptor;
3)负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor;
4)负责读取缓存直接返回、更新缓存的 CacheInterceptor;
5)负责和服务器建立连接的 ConnectInterceptor;
6)配置 OkHttpClient 时设置的 networkInterceptors;
7)负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。

OkHttp的这种拦截器链采用的是责任链模式,这样的好处是将请求的发送和处理分开,并且可以动态添加中间的处理方实现对请求的处理、短路等操作。

从上述源码得知,不管okhttp有多少拦截器*后都会走,如下方法:

Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);

 

从方法名字基本可以猜到是干嘛的,调用 chain.proceed(originalRequest); 将request传递进来,从拦截器链里拿到返回结果。那么拦截器Interceptor是干嘛的,Chain是干嘛的呢?继续往下看RealInterceptorChain

RealInterceptorChain类

下面是RealInterceptorChain的定义,该类实现了Chain接口,在getResponseWithInterceptorChain调用时好几个参数都传的null。

public final class RealInterceptorChain implements Interceptor.Chain {

   public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
        HttpCodec httpCodec, RealConnection connection, int index, Request request) {
        this.interceptors = interceptors;
        this.connection = connection;
        this.streamAllocation = streamAllocation;
        this.httpCodec = httpCodec;
        this.index = index;
        this.request = request;
  }
  ......

 @Override 
 public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    ......

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

   ......

    return response;
  }

  protected abstract void execute();
}

 

主要看proceed方法,proceed方法中判断index(此时为0)是否大于或者等于client.interceptors(List )的大小。由于httpStream为null,所以首先创建next拦截器链,主需要把索引置为index+1即可;然后获取*个拦截器,调用其intercept方法。

Interceptor 代码如下:

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    Connection connection();
  }
}

 

BridgeInterceptor

BridgeInterceptor从用户的请求构建网络请求,然后提交给网络,*后从网络响应中提取出用户响应。从*上面的图可以看出,BridgeInterceptor实现了适配的功能。下面是其intercept方法:

public final class BridgeInterceptor implements Interceptor {
  ......

@Override 
public Response intercept(Chain chain) throws IOException {
  Request userRequest = chain.request();
  Request.Builder requestBuilder = userRequest.newBuilder();

 RequestBody body = userRequest.body();
 //如果存在请求主体部分,那么需要添加Content-Type、Content-Length首部
 if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

  if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
  }

Response networkResponse = chain.proceed(requestBuilder.build());

HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }

  /** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */
  private String cookieHeader(List<Cookie> cookies) {
    StringBuilder cookieHeader = new StringBuilder();
    for (int i = 0, size = cookies.size(); i < size; i++) {
      if (i > 0) {
        cookieHeader.append("; ");
      }
      Cookie cookie = cookies.get(i);
      cookieHeader.append(cookie.name()).append('=').append(cookie.value());
    }
    return cookieHeader.toString();
  }
}

 

从上面的代码可以看出,首先获取原请求,然后在请求中添加头,比如Host、Connection、Accept-Encoding参数等,然后根据看是否需要填充Cookie,在对原始请求做出处理后,使用chain的procced方法得到响应,接下来对响应做处理得到用户响应,*后返回响应。接下来再看下一个拦截器ConnectInterceptor的处理。

public final class ConnectInterceptor implements Interceptor {
  ......

 @Override 
 public Response intercept(Chain chain) throws IOException {
 RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();

 // We need the network to satisfy this request. Possibly for validating a conditional GET.
 boolean doExtensiveHealthChecks = !request.method().equals("GET");
 HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
 RealConnection connection = streamAllocation.connection();

 return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

 

实际上建立连接就是创建了一个 HttpCodec 对象,它利用 Okio 对 Socket 的读写操作进行封装,Okio 以后有机会再进行分析,现在让我们对它们保持一个简单地认识:它对 java.io 和 java.nio 进行了封装,让我们更便捷高效的进行 IO 操作。

CallServerInterceptor

CallServerInterceptor是拦截器链中*后一个拦截器,负责将网络请求提交给服务器。它的intercept方法实现如下:

@Override 
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return what
      // we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      } else if (!connection.isMultiplexed()) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
        // being reused. Otherwise we're still obligated to transmit the request body to leave the
        // connection in a consistent state.
        streamAllocation.noNewStreams();
      }
    }

    httpCodec.finishRequest();

    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

 

从上面的代码中可以看出,首先获取HttpStream对象,然后调用writeRequestHeaders方法写入请求的头部,然后判断是否需要写入请求的body部分,*后调用finishRequest()方法将所有数据刷新给底层的Socket,接下来尝试调用readResponseHeaders()方法读取响应的头部,然后再调用openResponseBody()方法得到响应的body部分,*后返回响应。

*后总结

OkHttp的底层是通过Java的Socket发送HTTP请求与接受响应的(这也好理解,HTTP就是基于TCP协议的),但是OkHttp实现了连接池的概念,即对于同一主机的多个请求,其实可以公用一个Socket连接,而不是每次发送完HTTP请求就关闭底层的Socket,这样就实现了连接池的概念。而OkHttp对Socket的读写操作使用的OkIo库进行了一层封装。

%title插图%num

 

238. 除自身以外数组的乘积(JS实现)

238. 除自身以外数组的乘积(JS实现)
1 题目
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
链接:https://leetcode-cn.com/problems/product-of-array-except-self
2 思路
这道题我们用两个数组分别存储乘积,对于第i个元素,*个数组arr1[i]存储0…i-1的乘积,第二个数组arr1[i]存储i+1…n的乘积,*后的结果将二者相乘即可
3代码
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var productExceptSelf = function(nums) {
  const arr1 = [1];
  const arr2 = [];
  for (let i=1;i<nums.length;i++) {
    arr1[i] = arr1[i-1] * nums[i-1];
  }
  arr2[nums.length-1] = 1;
  for (let i=nums.length-2;i>=0;i–) {
    arr2[i] = arr2[i+1] * nums[i+1];
  }
  const res = [];
  for (let i=0;i<nums.length;i++) {
    res[i] = arr1[i] * arr2[i];
  }
  return res;
};

240. 搜索二维矩阵 II(JS实现)

240. 搜索二维矩阵 II(JS实现)
1 题目
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
链接:https://leetcode-cn.com/problems/search-a-2d-matrix-ii
2 思路
**我是用二分法做的,并不是*优解,题解中有一种双指针法很巧妙
首先,我们初始化一个指向矩阵左下角的 (row,col)(row,col) 指针。然后,直到找到目标并返回 true(或者指针指向矩阵维度之外的 (row,col)(row,col) 为止,我们执行以下操作:如果当前指向的值大于目标值,则可以 “向上” 移动一行。 否则,如果当前指向的值小于目标值,则可以移动一列。不难理解为什么这样做永远不会删减正确的答案;因为行是从左到右排序的,所以我们知道当前值右侧的每个值都较大。 因此,如果当前值已经大于目标值,我们知道它右边的每个值会比较大。也可以对列进行非常类似的论证,因此这种搜索方式将始终在矩阵中找到目标(如果存在)
**
3代码
/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
var searchMatrix = function(matrix, target) {
  let rows = matrix.length;
  if (rows === 0) return false;
  let cols = matrix[0].length;
  if (cols === 0) return false;
  if (target < matrix[0][0] || target > matrix[rows-1][cols-1]) return false;
  let low = 0;
  let high = rows – 1;
  while(low < high) {
    let mid = Math.floor((low + high) / 2);
    if (matrix[mid][0] > target) {
      high = mid – 1;
    } else if (matrix[mid][cols-1] < target) {
      low = mid + 1;
    } else {
      break;
    }
  }
  let currentRow = low;
  while(currentRow <= high) {
    let low1 = 0;
    let high1 = cols – 1;
    while(low1 <= high1) {
      let mid = Math.floor((low1 + high1) / 2);
      if (matrix[currentRow][mid] > target) {
        high1 = mid – 1;
      } else if (matrix[currentRow][mid] < target) {
        low1 = mid + 1;
      } else {
        return true;
      }
    }
    currentRow++;
  }
  return false;
};

Mac下sublime3的常用快捷键

Sublime Text 3是Sublime Text 2的升级版。Sublime Text 是一款流行的文本编辑器软件,有点类似于TextMate,跨平台,可运行在Linux,Windows和Mac OS X。也是许多程序员喜欢使用的一款文本编辑器软件。本文将简单介绍 Sublime Text 3 的快捷键操作。

获取已保存的项目:

mac: cmd+ctrl+p

win: alt+ctrl+p

搜索打开的文件:

mac:cmd+p

win:ctrl+p

标签控制:

mac:cmd+alt+左右箭头:前后标签切换

选择相同内容:

一行接一行:

mac:cmd+d

win:ctrl+d

全部选中:

mac:cmd+ctrl+g

win:alt+F3

按行选择:

mac: cmd+l

win: ctrl+l

选择全部子元素:

mac: cmd+shift+j

跳转到函数:

mac: cmd+R

复制当前行:

mac: cmd+shift+D

win:ctrl+shift+D

上下移动行:

mac: cmd+ctrl+上下

win: ctrl+shift+上下

关闭标签:

mac: cmd+alt+.

win: alt+.

隐藏显示侧边拦:

mac: cmd+B+K

win: ctrl+B+K

多个光标:

mac: cmd + 需要放光标的位置鼠标左键

win: ctrl+ 需要放光标的位置鼠标左键

光标定位:

mac: cmd+方向键 可定位光标到页面开始、结束,行开始、结束

 

mac版Sublime Text3快捷键大全

一、符号说明

  • ⌘:command
  • ⌃:control
  • ⌥:option
  • ⇧:shift
  • ↩:enter
  • ⌫:delete

二、常用快捷键

2.1 打开、关闭、前往

  • ⌘⇧N 打开一个新的sublime窗口
  • ⌘N 新建文件
  • ⌘⇧W 关闭sublime,关闭所有文件
  • ⌘W 关闭当前文件
  • ⌘P 跳转、前往文件、前往项目、命令提示、前往method等等(Goto anything)
  • ⌘⇧T 重新打开*近关闭的文件
  • ⌘T 前往文件
  • ⌘⌃P 前往项目
  • ⌘R 前往method
  • ⌘⇧P 命令提示
  • ⌃G 前往行
  • ⌘KB 开关侧栏
  • ⌃` 打开控制台
  • ⌃- 光标跳回上一个位置
  • ⌃⇧- 光标恢复位置

2.2 编辑

  • ⌘A 全选
  • ⌘L 选择行(重复按下将下一行加入选择)
  • ⌘D 选择词(重复按下时多重选择相同的词进行多重编辑)
  • ⌃⇧M 选择括号的内容
  • ⌘⇧↩ 在当前行前插入新行
  • ⌘↩ 在当前行后插入新行
  • ⌃⇧K 删除行
  • ⌘KK 从光标处删除至行尾
  • ⌘K⌫ 从光标处删除至行首
  • ⌘⇧D 复制(多)行
  • ⌘J 合并(多)行
  • ⌘KU 改为大写
  • ⌘KL 改为小写
  • ⌘C 复制
  • ⌘X 剪切
  • ⌘V 粘贴
  • ⌘/ 注释
  • ⌘⌥/ 块注释
  • ⌘Z 撤销
  • ⌘Y 恢复撤销
  • ⌘⇧V 粘贴并自动缩进
  • ⌘⌥V 从历史中选择粘贴
  • ⌃M 跳转至对应的括号
  • ⌘U 软撤销(可撤销光标移动)
  • ⌘⇧U 软重做(可重做光标移动)
  • ⌘⇧S 保存所有文件
  • ⌘] 向右缩进
  • ⌘[ 向左缩进
  • ⌘⌥T 特殊符号集
  • ⌘⇧L 将选区转换成多个单行选区
  • ⌘ →  光标跳转到行尾
  • ⌘ ←  光标跳转到行首

2.3 查找替换功能

  • ⌘F 查找
  • ⌘⌥F 查找并替换
  • ⌘⌥G 查找下一个符合当前所选的内容
  • ⌘⌃G 查找所有符合当前选择的内容进行多重编辑
  • ⌘⇧F 在所有打开的文件中进行查找

2.4 拆分窗口/标签页

  • ⌘⌥[1,2,3,4] 单列、双列、三列、四列
  • ⌘⌥5 网格(4组)
  • ⌃[1,2,3,4] 焦点移动到相应的组(分屏编号)
  • ⌃⇧[1,2,3,4] 将当前文件移动到相应的组(分屏编号)
  • ⌘[1,2,3,4] 选择相应的标签页