课程概述
-
网络通信
- 网络库框架对比
- Retrofit使用&原理介绍
- TTNet介绍,了解字节跳动网络请求框架
-
数据存储
- Android数据存储方式对比,了解不同场景该使用的工具
- 数据库框架对比
- Room数据库使用与原理介绍
1.网络请求
1.1 简介
- 客户端向服务端发起请求,服务端返回数据给到客户端
- 由于网络请求在大型App里使用非常频繁,为了更好的支持业务迭代,一般会进行网络请求的封装
1.2 网络框架对比
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":"三丰"
}
}
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)
- SOURCE:只有在源码中有效,编译时抛弃,例如前面的@Override
- CLASS:编译class.文件时有效,一般会使用到注解处理器。
- RUNTIME:在运行期间,获取对应的注解,并做相关的处理
GET注解的定义
注解的获取和使用: 通过反射获取到Method对象后,有以下一些接口来获取注解内容
- Method.getGenericReturnType()获取返回类型
- Method.getAnnotations()获取方法的注解
- Method.getParameterAnnotations()获取参数注解
Retrofit是在运行期间,配合Java动态代理,获取方法和参数的注解,并构造Requesti对象的。
java动态代理Proxy.newProxyInstance
利用Java的反射技术(Java Reflection),代理某个interface,一旦调用interface.里的某个方法时,实际通过代理调用InvocationHandler的invoke方法
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 这个方法进行一个网络请求
主流程如下
- 通过Builder模式创建RetrofitConfig,保存baseUrls等内容
- 创建动态代理对象
- 创建OkHttpCall
- 发起网络请求
1.4.3.2 Retrofit调用底层OkHttp的方式
在主流程图中,okhttp
网络库的api,在Retrofit
中是何时发起请求的,对应api已列出,相同颜色即意味着调用关系。
如:
绿色代码:标记1处,创建Retrofit
对象时,对应创建了OkHttpClient
对象;
红色代码:标记4处,使用Retrofit
的Call
对象发起请求时,对应创建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.callFactory
为OkHttpClient
实例。通过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 |
---|---|
1.5 TTNet实现原理
TTNet基于Retrofit
的进行二次开发,核心主要是替换Retrofit其中的2点
- 替换底层用到的
OKHttpClient
- 替换底层用到的
OKHttpCall
1.5.1 TTNet主流程
1.5.2 更多TTNet相关的解析,可以看TTNet 源码分析
\
1.5 网络请求总结
\
-
数据存储
2.1 简介
- 数据存储的方式有很多种,其使用场景也不一样,本文主要介绍其中4种最为常见的数据存储方式和使用场景
- 数据库是4种常见数据存储方式中较为复杂的能力,本文也会重点介绍
2.2 存储方式对比
持久性的本地数据存储是Android中常见的能力,可以在应用被杀死的情况下,而保持数据不会被清除。我们可以根据不同场景的诉求,可以选用不同的存储方式,常见的数据存储主要有以下4种。
2.3 数据库开源框架对比
数据库 相对来说,其使用会比较复杂些,我们单独进行探索,下面是几个主流的数据库框架对比
不同的产品,对功能的诉求不太应用,头条因为也有用到LiveData,同时考虑到是Google出品,其流行度和稳定性都有较好的保障,所以更倾向于使用Room数据库,下面会重点介绍下Room数据库。
\
2.4 Room数据库的使用
Room是 Google Jetpack 家族里的一员,Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库
主要的3个组件
- 数据库类(
Database
),用于保存数据库并作为应用持久性数据底层连接的主要访问点。
- 数据实体(
Entity
),用于表示应用的数据库中的表。
- 数据访问对象(
DAO
),提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。
2.4.1 Room接入
Gradle目录的build.gradle文件里添加如下:
2.4.2 数据表设计
下面假设设计一个表,表名为user
,数据表包含uid
、first_name
、 last_name
3个字段
2.4.3 新建Entity
定义一个User
数据实体,User的每个实例都代表App数据库中的user表的一行
2.4.4 新增DAO
定义一个名为UserDao
的DAO。用来对User表的增删改查
2.4.5 新建数据库类
进行数据库配置,并需满足以下几个条件:
- 新增一个
RoomDatabase
的abstract
子类
- 子类需加注解
@Database(entities = [xxx], version = n)
,entities
包含数据实体,将会在这个数据库中创建对应的表,version
是数据的版本号
- 对于与数据库关联的每个DAO类,数据库类必须定义一个无参的抽象方法,并返回DAO类实例
2.4.6 获取dao对象
可进行数据库的增删改查操作
2.5 Room原理介绍
核心
- 编译期,通过kapt处理
@``Dao
、@Database
注解,动态生成对应的实现类
- 底层使用Android提供的
SupportSQLiteOpenHelper
实现数据库的增删改查等操作
\
2.5.1 kapt注解处理
Room在编译期,通过kapt处理@Dao和@Database注解,生成DAO和Database的实现类 AppDatabase
--> AppDatabase_Impl
UserDao
-->UserDao_Impl
kapt生成的代码在build/generated/source/kapt/
2.5.2 实现类利用Android SQLite进行数据库操作
AppDatabase_Impl
:数据库实例的具体实现,自动生成,主要有以下几个方法
createOpenHelper()
: Room.databaseBuilder().build()
创建Database时,会调用实现类的 createOpenHelper()
创建SupportSQLiteOpenHelper
,此Helper用来创建DB以及管理版本 userDao()
:创建UserDao_Impl
UserDao_Impl
: UserDao
的具体实现,自动生成,主要有以下3个属性以及UserDao里定义的接口
__db
:RoomDatabase
的实例 __insertionAdapterOfUser
:EntityInsertionAdapterd
实例,用于数据insert
__deletionAdapterOfUser
:EntityDeletionOrUpdateAdapter
实例,用于数据的update/delete
以下几个是UserDao
里我们自己定义的接口
insertAll()
: 使用__db
开启事务,使用__insertionAdapterOfUser
执行插入操作
delete()
:使用__db
开启事务,使用__deletionAdapterOfUser
执行删除操作 getAll()
:使用Cursor
循环读取数据库的每条记录,并将结果保存在List<User>
中返回 loadAllByIds()
:和getAll()
类似,查询语句不同 findByNames()
:和getAll()
类似,查询语句不同
\
2.6 数据库总结
课后
- 进一步了解
Retrofit
的其他使用方式 square.github.io/retrofit/
- 进一步了解
OkHttp
的用法和原理 square.github.io/okhttp/
- 进一步了解
Room
数据库的用法和原理 developer.android.com/training/da…
- 自己尝试使用
Retrofit
和Room
数据库,进行简单demo的编写
参考资料
- 使用
Room
将数据保存到本地数据库developer.android.com/training/da…
Room
原理详解:juejin.cn/post/692378…