课程概述

  • 网络通信

    • 网络库结构比照
    • Retrofit运用&原理介绍
    • TTNet介绍,了解字节跳动网络恳求结构
  • 数据存储

    • Android数据存储办法比照,了解不同场景该运用的东西
    • 数据库结构比照
    • Room数据库运用与原理介绍

1.网络恳求

1.1 简介

  • 客户端向服务端建议恳求,服务端回来数据给到客户端
  • 由于网络恳求在大型App里运用非常频繁,为了更好的支撑业务迭代,一般会进行网络恳求的封装

1.2 网络结构比照

【Android】数据存储&网络通信

1.2.1 说明:

  • Volley的Request和Response都是把数据办法放到byte[]数组里,不支撑输入输出流,把数据放到数组中,假如大文件多了,数组就会非常大且多,耗费内存
  • 行业内,现在基本上都是Retrofit 和 OkHttp组合的这种办法来进行网络恳求
  • IO 和 NIO这两个都是Java中的概念,假如我从硬盘读取数据,第一种办法便是程序一直等,数据读完后才干持续操作这种是最简略的也叫堵塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种便是 NIO 的办法,非堵塞式, 所以NIO当然要比IO的功用要好了,而 Okio是 Square 公司基于IO和NIO基础上做的一个更简略、高效处理数据流的一个库。

1.2.2 总结:

  • 现在Retrofit和OkHttp的组合,功用愈加全面,封装愈加彻底,当下最为盛行的网络恳求办法,咱们本文也会要点来重视Retrofit的运用和原理的介绍。

1.3 Retrofit的运用介绍

Retrofit其实是对OkHttp的一个封装,也是当时最为盛行的一种网络恳求组合办法。

1.3.1 运用举例

场景假设:客户端知道了一个用户的uid,想经过服务端查下这个用户的姓名,经过Retrofit怎么完结呢? 接口:[www.bytedance.com/users/{uid}…] 其间{uid}要替换为实际的uid,例如1123,最终恳求为www.bytedance.com/users/1123/…

类型:GET恳求 接口回来:

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

【Android】数据存储&网络通信

1.3.2 运用介绍

  • 增加Retrofit库的依靠

    • 在需求用到Retrofit接口的module中,新增依靠(最新的版本可看GitHub )
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恳求
}
//函数名:可自界说,需求能识别出该接口的作用,该interface里能够增加多个不同的函数
//@GET 注解:用于指定该接口的相对路径,并选用Get办法建议恳求
//@Path 注解:需求外部调用时,传入一个uid,该uid会替换@GET注解里相对路径的{uid}
//回来值Call<ResponseBody>,这儿用ResponseBody,咱们能够直接拿到恳求的String内容
//假如要主动转为Model类,例如User,这儿直接替换为User就好。
  • 建议网络恳求
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
        }
    })
}

1.3.3 注解

注解,也能够了解为是一个标签 这个标签能够加在类、办法、参数、成员变量上,而且可在适宜的机遇读取注解的内容,进行处理

如@Override:标示一个办法是重写了父类的完结

注解的生命周期: 有界说和运用注解的地方,必定还需有获取注解并处理注解内容的地方

注解的处理,一般有3个机遇(也便是注解的生命周期@Retention)

  1. SOURCE:只要在源码中有用,编译时抛弃,例如前面的@Override
  2. CLASS:编译class.文件时有用,一般会运用到注解处理器。
  3. RUNTIME:在运转期间,获取对应的注解,并做相关的处理

【Android】数据存储&网络通信

【Android】数据存储&网络通信

GET注解的界说

【Android】数据存储&网络通信

注解的获取和运用: 经过反射获取到Method目标后,有以下一些接口来获取注解内容

  1. Method.getGenericReturnType()获取回来类型
  2. Method.getAnnotations()获取办法的注解
  3. Method.getParameterAnnotations()获取参数注解

【Android】数据存储&网络通信

Retrofit是在运转期间,配合Java动态署理,获取办法和参数的注解,并结构Requesti目标的。

【Android】数据存储&网络通信

java动态署理Proxy.newProxyInstance

运用Java的反射技能(Java Reflection),署理某个interface,一旦调用interface.里的某个办法时,实际经过署理调用InvocationHandler的invoke办法

【Android】数据存储&网络通信

1.3.4 总结

  • 引进依靠库
  • 创立 用于描绘网络恳求 的接口
  • 建议网络恳求

    • 创立Retrofit实例
    • 创立iUserInfoService实例
    • 创立网络恳求Call目标
    • 运用Call目标建议异步恳求
  • 其他更多的用法,更多的注解,能够看Retrofit官网square.github.io/retrofit/

1.4 Retrofit运用OkHttp的流程介绍

1.4.1 Retrofit的主流程

先上个图,成果先行方便大家了解主流程调用联系。

先记住两个点,一个便是说咱们会在这个 get username 的时分会去结构一个 okHTTP call 然后然后这 call 之后咱们会在 enqueue 的时分去底层,底层首要是去调用 okHttp call 的 enqueue 这个办法进行一个网络恳求

主流程如下

  1. 经过Builder模式创立RetrofitConfig,保存baseUrls等内容
  2. 创立动态署理目标
  3. 创立OkHttpCall
  4. 建议网络恳求
1.4.3.2 Retrofit调用底层OkHttp的办法

在主流程图中,okhttp网络库的api,在Retrofit中是何时建议恳求的,对应api已列出,相同色彩即意味着调用联系。

如:

绿色代码:符号1处,创立Retrofit目标时,对应创立了OkHttpClient目标;

赤色代码:符号4处,运用RetrofitCall目标建议恳求时,对应创立OkHttp网络库中Request目标和Call目标并建议恳求。

1.4.3.3 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)
  }
}

OkHttpClient目标的创立,咱们已经看到了。那么创立好之后,在何处完结调用呢? 咱们往下走,答案就在下面的流程整理里边。

不过,咱们先留意下,adapterFactories集合里的目标。里边有一个默许的CallAdapter.Factory实例,它便是子类ExecutorCallAdapterFactory的实例。咱们在后续解析恳求办法时,会用到它。

1.4.3.4 OkHttpCall的创立

当咱们经过署理目标调用咱们的接口办法IUserNameService#getUserName时,会触发InvocationHandler#invoke办法。恳求物料的封装细节较多,单独拉了一篇文档。能够参阅:13.2恳求物料的封装

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);
        }
      });
}
仿制代码

咱们需求要点重视invoke办法中OkHttpCall目标的创立,serviceMethod.callAdapter.adapt(okHttpCall)中办法调用的完结,serviceMethod.callAdapter.adapt(okHttpCall)的回来值。一瞬间会用到

1.4.3.5 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** 的改造上做文章就好。

\

总结

  • 关于命名的大多数规范中心在于考虑上下文
  • 人们在阅览了解代码的时分也能够看成是计算机运转程序,好的命名能让人把重视点留在主流程上,清晰地了解程序的功用,防止频繁切换到分支细节,增加了解成本

1.4 TTNet

TTNet是字节跳动通用的网络恳求封装结构,用来向服务端建议恳求。

供给了一整套Android客户端网络恳求解决方案。

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

1.4.1 为什么要做TTNet?

Retrofit是开源的网络结构,Retrofit只支撑OkHttp网络库,不支撑其他网络库。

咱们有更优秀的网络库cronet,咱们想运用它,可是咱们又沉迷Retrofit的优秀的封装特性和接口调用的简易特性?

1.4.2 TTNet与Retrofit的运用比照

Retrofit TTNet
【Android】数据存储&网络通信
【Android】数据存储&网络通信

1.5 TTNet完结原理

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

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

【Android】数据存储&网络通信

1.5.1 TTNet主流程

【Android】数据存储&网络通信

1.5.2 更多TTNet相关的解析,能够看TTNet 源码分析

\

1.5 网络恳求总结

【Android】数据存储&网络通信

\

  1. 数据存储

2.1 简介

  • 数据存储的办法有很多种,其运用场景也不一样,本文首要介绍其间4种最为常见的数据存储办法和运用场景
  • 数据库是4种常见数据存储办法中较为复杂的才能,本文也会要点介绍

2.2 存储办法比照

持久性的本地数据存储是Android中常见的才能,能够在运用被杀死的情况下,而坚持数据不会被清除。咱们能够根据不同场景的诉求,能够选用不同的存储办法,常见的数据存储首要有以下4种。

【Android】数据存储&网络通信

2.3 数据库开源结构比照

数据库 相对来说,其运用会比较复杂些,咱们单独进行探究,下面是几个主流的数据库结构比照

【Android】数据存储&网络通信

不同的产品,对功用的诉求不太运用,头条由于也有用到LiveData,一起考虑到是Google出品,其盛行度和稳定性都有较好的保障,所以更倾向于运用Room数据库,下面会要点介绍下Room数据库。

\

2.4 Room数据库的运用

Room是 Google Jetpack 家族里的一员,Room 在 SQLite 上供给了一个笼统层,以便在充分运用 SQLite 的强壮功用的一起,能够流畅地拜访数据库

【Android】数据存储&网络通信

首要的3个组件

  • 数据库类(Database),用于保存数据库并作为运用持久性数据底层衔接的首要拜访点。
  • 数据实体(Entity),用于表示运用的数据库中的表。
  • 数据拜访目标(DAO),供给您的运用可用于查询、更新、刺进和删去数据库中的数据的办法。

【Android】数据存储&网络通信

2.4.1 Room接入

Gradle目录的build.gradle文件里增加如下:

【Android】数据存储&网络通信

2.4.2 数据表设计

下面假设设计一个表,表名为user,数据表包含uidfirst_namelast_name 3个字段

【Android】数据存储&网络通信

2.4.3 新建Entity

界说一个User数据实体,User的每个实例都代表App数据库中的user表的一行

【Android】数据存储&网络通信

2.4.4 新增DAO

界说一个名为UserDao的DAO。用来对User表的增修改查

【Android】数据存储&网络通信

2.4.5 新建数据库类

进行数据库装备,并需满意以下几个条件:

  • 新增一个RoomDatabaseabstract子类
  • 子类需加注解@Database(entities = [xxx], version = n)entities包含数据实体,将会在这个数据库中创立对应的表,version是数据的版本号
  • 关于与数据库相关的每个DAO类,数据库类有必要界说一个无参的笼统办法,并回来DAO类实例

【Android】数据存储&网络通信

2.4.6 获取dao目标

可进行数据库的增修改查操作

【Android】数据存储&网络通信

2.5 Room原理介绍

中心

  • 编译期,经过kapt处理@``Dao@Database注解,动态生成对应的完结类
  • 底层运用Android供给的SupportSQLiteOpenHelper完结数据库的增修改查等操作

\

2.5.1 kapt注解处理

Room在编译期,经过kapt处理@Dao和@Database注解,生成DAO和Database的完结类 AppDatabase –> AppDatabase_Impl UserDao–>UserDao_Impl

【Android】数据存储&网络通信

kapt生成的代码在build/generated/source/kapt/

【Android】数据存储&网络通信

2.5.2 完结类运用Android SQLite进行数据库操作

AppDatabase_Impl:数据库实例的具体完结,主动生成,首要有以下几个办法

createOpenHelper()Room.databaseBuilder().build()创立Database时,会调用完结类的 createOpenHelper()创立SupportSQLiteOpenHelper,此Helper用来创立DB以及管理版本 userDao():创立UserDao_Impl

【Android】数据存储&网络通信

UserDao_ImplUserDao的具体完结,主动生成,首要有以下3个特点以及UserDao里界说的接口

__dbRoomDatabase的实例 __insertionAdapterOfUserEntityInsertionAdapterd实例,用于数据insert __deletionAdapterOfUserEntityDeletionOrUpdateAdapter实例,用于数据的update/delete

以下几个是UserDao里咱们自己界说的接口

insertAll(): 运用__db敞开业务,运用__insertionAdapterOfUser执行刺进操作

delete():运用__db敞开业务,运用__deletionAdapterOfUser执行删去操作 getAll():运用Cursor循环读取数据库的每条记载,并将成果保存在List<User>中回来 loadAllByIds():和getAll()相似,查询句子不同 findByNames():和getAll()相似,查询句子不同

【Android】数据存储&网络通信

【Android】数据存储&网络通信

【Android】数据存储&网络通信
【Android】数据存储&网络通信

\

2.6 数据库总结

【Android】数据存储&网络通信

课后

  • 进一步了解Retrofit的其他运用办法 square.github.io/retrofit/
  • 进一步了解OkHttp的用法和原理 square.github.io/okhttp/
  • 进一步了解Room数据库的用法和原理 developer.android.com/training/da…
  • 自己尝试运用RetrofitRoom数据库,进行简略demo的编写

参阅资料

  • TTNet 源码分析
  • 13.2恳求物料的封装
  • 运用Room将数据保存到本地数据库developer.android.com/training/da…
  • Room原理详解:/post/692378…