Android客户端 | 青训营笔记

59 阅读5分钟

Android客户端 | 青训营笔记

这是我参与「第四届青训营 -Android场」笔记创作活动的的第6天

网络请求

简介

  • 客户端向服务端发起请求,服务端返回数据给到客户端

网络框架对比

image.png

说明:

  • Volley的Request和Response都是把数据方法放到byte[]数组里,不支持输入输出流,把数据放到数组中,如果大文件多了,数组就会非常大且多,消耗内存
  • 行业内,目前基本上都是Retrofit 和 OkHttp组合的这种方式来进行网络请求
  • IO 和 NIO这两个都是Java中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作这种是最简单的也叫阻塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是 NIO 的方式,非阻塞式, 所以NIO当然要比IO的性能要好了,而 Okio是 Square 公司基于IO和NIO基础上做的一个更简单、高效处理数据流的一个库。
  • 目前Retrofit和OkHttp的组合,功能更加全面,封装更加彻底,当下最为流行的网络请求方式,我们本文也会重点来关注Retrofit的使用和原理的介绍。

Retrofit的使用介绍

Retrofit其实是对OkHttp的一个封装,也是当前最为流行的一种网络请求组合方式。

使用举例

场景假设:客户端知道了一个用户的uid,想通过服务端查下这个用户的姓名

类型:GET请求 接口返回:

{
    "message": "success",
    "data": {
        "uid":"1123",
        "first_name":"张",
        "last_name":"三丰"
    }
}

image.png

使用

  • 添加Retrofit库的依赖

    • 在需要用到Retrofit接口的module中,新增依赖
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    //...其他依赖
}
  • 创建 用于描述网络请求 的接口
//接口类名:可自定义,尽量和这类请求的含义相关
interface IUserInfoService {

    @GET("users/{uid}/name")
    fun getUserName(@Path("uid") uid: Int): Call<ResponseBody>

    //@GET("users/{name}/uid")
    //fun getRequest(@Path("name" name:String)) Call<User>
    
    //后续可以增加其他的接口,一个接口对应一个api请求
}
  • 发起网络请求
fun getUserName(view: View) {
    //创建Retrofit实例
    val retrofit = Retrofit.Builder()
        .baseUrl("https://www.bytedance.com/")
        .build()
    //创建iUserInfoService实例
    val iUserInfoService = retrofit.create(IUserInfoService::class.java)
    //创建网络请求Call对象
    val call = iUserInfoService.getUserName(1123)
    //发起异步请求
    call.enqueue(object : Callback<ResponseBody> {

        override fun onResponse(call: Call<ResponseBody>,
                                response: Response<ResponseBody>) {
            //请求成功时回调
            request_result_tv.text = "请求成功:" + response.body()!!.string()
        }

        override fun onFailure(call: Call<ResponseBody>, e: Throwable) {
            //请求失败时候的回调
            request_result_tv.text = "请求失败:" + e.message
        }
    })
}

总结

  • 引入依赖库
  • 创建 用于描述网络请求 的接口
  • 发起网络请求

    • 创建Retrofit实例
    • 创建iUserInfoService实例
    • 创建网络请求Call对象
    • 使用Call对象发起异步请求

TTNet

TTNet是字节跳动通用的网络请求封装框架,用来向服务端发起请求。

  • 突出优点:
    • 基于Retrofit改造,具备了Retrofit所具有的优点
    • 支持多个Http网络库的动态切换(okhttp和cronet)
    • 支持网络拦截配置:添加公共参数,动态切换协议及Host,动态选路等
    • 支持流解析,json序列化
    • ......

TTNet与Retrofit的使用对比

image.png

image.png

Retrofit的主流程
  • 通过builder模式创建RetrofitConfig,保存BaseUrl等内容
  • 创建动态代理对象
  • 创建OKHttpCal
  • 发起网络请求 image.png
Retrofit调用底层OkHttp的方式

image.png

在主流程图中,okhttp网络库的api,在Retrofit中是何时发起请求的,对应api已列出,相同颜色即意味着调用关系。

Retrofit里的OkHttpClient的调用时机
public void getRequest() {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://www.bytedance.com/")
            //.addCallAdapterFactory(okhttp3.Call.Factory)
            .build() ;  // 标记(1)
}

// Retrofit$Builder
public Retrofit build() {
  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {
    callFactory = new OkHttpClient() ; // 标记(2)
  }
    
  List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
  // platform为Android的实例
  // 集合里的实例为ExecutorCallAdapterFactory的实例。这里要留意下
  adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
  return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
      callbackExecutor, validateEagerly);
}

static class Android extends Platform {
  @Override public Executor defaultCallbackExecutor() {
    return new MainThreadExecutor();
  }
  
  @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
    return new ExecutorCallAdapterFactory(callbackExecutor);
  } 
}

// Retrofit
public final class Retrofit {
  private  final okhttp3.Call.Factory callFactory;
  Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
      List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories,
      Executor callbackExecutor, boolean validateEagerly) {
    this .callFactory = callFactory; // 标记(3)
  }
}
OkHttpCall的创建

当我们通过代理对象调用我们的接口方法IUserNameService#getUserName时,会触发InvocationHandler#invoke方法。

public void getRequest(){
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://www.bytedance.com/")
            .build();
    IUserNameService iUserNameService = retrofit.create(IUserNameService.class);
    // 标记1
    Call<ResponseBody> call = iUserNameService.getUserName( 1123 );
}

// Retrofit
public <T> T create(final Class<T> service) {
  
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
      new InvocationHandler() {
        private final Platform platform = Platform.get();
        // 标记(2)
        @Override public Object invoke(Object proxy, Method method, Object... args) {
          ServiceMethod serviceMethod = loadServiceMethod(method);
          OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
          return serviceMethod.callAdapter.adapt(okHttpCall);
        }
      });
}
okhttp.Request对象和okhttp.Call对象的创建及请求发起
public void getRequest(){
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://www.bytedance.com/")
            .build();
    IUserNameService iUserNameService = retrofit.create(IUserNameService.class);
    // call对象的取值为InvocationHandler#invoke()方法的返回值,默认为ExecutorCallbackCall对象
    Call<ResponseBody> call = iUserNameService.getUserName(1123);
    // 标记1: 由请求物料的封装过程,我们知道 call为ExecutorCallbackCall的实例
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {

        }
    });
}

// Retrofit
public <T> T create(final Class<T> service) {
  
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
      new InvocationHandler() {
        private final Platform platform = Platform.get();
        // 标记(2)
        @Override public Object invoke(Object proxy, Method method, Object... args) {
          ServiceMethod serviceMethod = loadServiceMethod(method);
          OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
          return serviceMethod.callAdapter.adapt(okHttpCall);
        }
      });
}

// ExecutorCallbackCall
@Override 
public void enqueue(final Callback<T> callback) {
    // ExecutorCallbackCall实例所拥有的delegate为OkHttpCall对象。
  delegate.enqueue(new Callback<T>() {
    @Override 
    public void onResponse(Call<T> call, final Response<T> response) {
      // ...
    }
  });
}

//OkHttpCall 这里的内容是不是似曾相识,没错它就是OkHttp完成请求的过程。我们上面已贴出
public void enqueue(final Callback<T> callback) {
  // 这里完成的了Request的创建
Request request = serviceMethod.toRequest(args);
 // 这里完成了Call对象的创建
okhttp3.Call call = serviceMethod.callFactory.newCall(request);
  // 通过call对象发起异步请求
  call.enqueue( new okhttp3.Callback() {
 @Override 
 public  void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
throws IOException {
Response<T> response = parseResponse(rawResponse);
callSuccess(response);
}

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

});
}

// ServiceMethod
Request toRequest(Object... args) throws IOException {
  RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
      contentType, hasBody, isFormEncoded, isMultipart);
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
  for (int p = 0; p < argumentCount; p++) {
    handlers[p].apply(requestBuilder, args[p]);
  }
  return requestBuilder.build();
}

OkHttpCall#enqueue方法里,通过ServiceMethod#toRequest方法,完成了Request的创建。因为ServiceMethod拥有接口方法IUserNameService#getUserName的全部请求物料信息,传入请求实参后,便可直接构造request对象。

构建完request对象后,serviceMethod.callFactoryOkHttpClient实例。通过OkHttpClient#newCall方法构建Call对象。

构建Call对象完成后,通过Call对象发起异步请求。

请求完毕后,返回响应对象Response。到此OkHttp请求调用过程完毕。

Retrofit只支持OkHttp网络库:

通过代理对象对方法接口进行调用时,会在InvocationHandler#invoke方法回调里为serviceMethod.callAdapter.adapt()设置okHttpCall对象,进而发起请求时,委托OkHttpCall对象发起网络请求。而OkHttpCall#enqueue 调用okHttp网络库发起请求。

所以serviceMethod.callAdapter.adapt()设置死了为okHttpCall对象。Retrofit发起请求只能对OkHttp网络库进行支持了。

public <T> T create(final Class<T> service) {
  
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
      new InvocationHandler() {
        private final Platform platform = Platform.get();
        // 标记(2)
        @Override public Object invoke(Object proxy, Method method, Object... args) {
          ServiceMethod serviceMethod = loadServiceMethod(method);
          OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
          return serviceMethod.callAdapter.adapt(okHttpCall);
        }
      });
}

所以我们要支持多个网络库的改造,那么就在OkHttpCall的改造上做文章就好。

总结

  • 关于命名的大多数规范核心在于考虑上下文
  • 人们在阅读理解代码的时候也可以看成是计算机运行程序,好的命名能让人把关注点留在主流程上,清晰地理解程序的功能,避免频繁切换到分支细节,增加理解成本

TTNet实现原理

TTNet基于Retrofit的进行二次开发,核心主要是替换Retrofit其中的2点

  • 替换底层用到的OKHttpClient
  • 替换底层用到的OKHttpCall

image.png

TTNet主流程

image.png

1.5 网络请求总结

image.png