这是我参与「第四届青训营 」笔记创作活动的第2天
8.3 android第三课
一、网络请求
1、网络请求框架对比
| 请求方式 | 作者 | 包体积增量 | 使用成本 | 特点 | 使用场景 |
|---|---|---|---|---|---|
| HttpURLConnection | Android sdk | 0KB | 2n | 需要自己做封装,例如线程池管理、返回的数据解析 | 只有少量的网络请求的工具类App |
| Volley | 57KB | n | 1、适合网络请求频繁,传输数据量小 2、不适合用来上传文件和下载,因为不支持输入输出流,是用一个数组来保存,数组保存有可能因为过大浪费控件甚至OOM 3、已停更 | 之前使用volley,且无需大文件下载的App | |
| OkHttp | square公司 | 262KB | 1.5n | 1、可以设置拦截器,支持大文件上传和下载 2、OkHttp基于NIO和Okio,性能更好 3、一般需要二次封装使用 | 一般比较少直接使用,可搭配Volley或Retrofit |
| Retrofit | square公司 | 343KB | 2n | 1、具备OkHttp所有的优点,且更出色 2、restful api设计风格 3、通过注解配置请求,包括请求方法,请求参数、请求头等 4、可以搭配多种Converter将获得的数据解析,支持Gson、jackson、Protobur等 | 团队内有研发人员对Retrofit比较熟悉时可用 |
2、Retrofit
Retrofit是目前Android平台上,可以说最热门的网络请求框架,是对OkHttp的一个封装
1)Retrofit的使用
-
Retrofit库的引入
-
创建用于描述网络请求的接口
interface IUserInfoService{ @GET("user/{uid}/name") fun getUserName(@Path("uid")uid:Int):Call<ResponseBody> //后续可以增加其他的接口,一个接口对应一个api请求 }定义说明:
-
接口类名:自定义,尽量和请求的业务逻辑相关
-
@GET注解:用于指定该接口的相对路径,并采用Get方法发起请求
-
@Path注解:需要外部调用时,传入一个uid,该uid会替换@GET注解里相对路径的{uid}
-
返回值Call :这里用ResponseBody,我们可以直接拿到请求的String内容
如果要自动转为Model类,如User类,这里直接替换为User就好
-
-
使用Retrofit实例发起网络请求
3.1、创建Retrofit实例
3.2、创建请求接口的实例,并获取到Call实例
3.3、调用call.enqueue进行异步请求
fun getUserName(view:View){ //创建Retrofit实例 val retrofit =Retrofit.Builder().baseUrl("https://www.bytedance.com/").build()//设置网络请求的Url地址 //创建网络请求接口的实例 val iUserInfoService=retrofit.create(IUserInfoService::class.java) val call=iUserInfoService.getUserName(1233); //enqueue是异步请求的接口 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 } }) }
2)注解类型:
字节跳动的网络库——TTNet
TTNet字节内部大部分App都在使用的网络请求框架
优点:
- 基于Retrofit开发,具有Retrofit的所有的优点
- 支持多个网络拦截配置:添加公共参数,动态切换协议及Host,动态选路等
- 支持流解析,json序列化
TTNet将Retrofit底层的OkHttp网络库更改的方法:
思路:
- 先明确Retrofit是怎么封装和使用OkHttp发起网络请求的
- 既然TTNet基于Retrofit修改,那可以在使用OkHttp的地方都改为使用Cronet来发网络请求
3)注解介绍
注解,可以理解为一个标签,这个标签可以加在类、方法、参数、成员变量上,并且可在合适的时机读取注解的内容,进行处理
-
注解的获取和处理,一般有三个时机(也就是注解的生命周期@Retention)
- SOURCE:只有在源码中有效,编译时抛弃,例如@Override
- CLASS:编辑class文件时有效,一般会使用到注解处理器(APT)
- RUNTIME:在运行期间,获取对应的注解,并做相关的处理
注解@GET定义
@Target:指定作用的对象,比如这里的METHOD,说明这个注解作用在方法上
其他枚举值:
- PARAMETER:参数
- FIELD:类成员
@Retention:指定注解的生命周期,这里是RUNTIME,说明这个注解要保留到运行时
注解的获取和使用:通过反射获取到Method对象后,有以下一些接口来获取注解内容
-
Method.getGenericReturnType():获取返回类型
-
Method.getAnnotations():获取方法的注解
-
Method.getParameterAnnotations():获取参数注解
Method[] declaredMethods=IUserInfoService.class.getDeclaredMethods(); for(Method method:declareMethods){ Type type=method.getGenericReturnType();//正式返回类型 Annotation[] methodAnnotations=method.getAnnotations();//方法注解 Annotation[][] parameterAnnotationsArray=method.getParameterAnnotations();//方法参数注解 }Retrofit是在运行期间,配合Java动态代理,获取方法和参数的注解,并构造Request对象的
java动态代理Proxy.newProxyInstance
-
利用Java的反射技术(Reflection),代理某个interface,一旦调用interface里的某个方法时,实际通过代理调用InvocationHandler的invoke方法
-
通过Method对象,就可以调用Method.getAnnotations()和Method.getParameterAnnotations()来获取该方法和方法参数的注解内容
public static Object newProxyInstance(ClassLoader loader,Class<?> [] interfaces,InvocationHandler h)throw IllegalArgumentException{}public interface InvocationHandler{ public Object invoke(Object proxy,Method method,Object[] args)throws Throwable; }
-
4)Retrofit主流程
重点是绿色部分:OkHttpCall的创建和调用
主流程:
- 通过Builder模式,创建RetrofitConfig,保存baseUrl等内容
- 创建动态代理对象
- 创建OkHttpCall
- 发起网络请求
Retrofit调用OkHttp
Retrofit里OkHttpClient创建时机
- Retrofit的Builder构造函数中如果未指定callFactory,则会自动创建一个OkHttpClient
这里的adapterFactories集合,里面包含一个默认的CallAdapter.Factory,它是ExecutorCallAdapterFactory的实例
- 创建好的OkHttpClient将会保存在Retrofit实例中
- ExecutorCallAdapterFactory主要是用力控制Retrofit在子线程触发请求,在主线程回调结果
OkHttpCall的创建
当我们通过代理对象调用我们的接口方法IUserInfoService#getUserName()时,会触发InvocationHandler#invoke方法
TTNet类图设计
核心替换:
- 替换底层用到的OkHttpClient
- 替换底层用到的OkHttpCall
网络通信总结
二、数据存储
1、存储方式
相对来说,数据库的使用会比较复杂些
2、数据库——框架对比
2、Room
1)Room是Google Jetpack中的一员,Room在SQLite上提供了一个抽象层,以便在充分利用SQLite的强大功能的同时,能够流畅地访问数据库
2)主要的三个组件:
- 数据库类(Database),用于保存数据库并作为应用持久性数据底层连接地主要访问点
- 数据实体(Entity),用于表示应用的数据库中的表
- 数据访问对象(DAO),提供增删改查数据库中的数据的方法
3)实践
-
Room接入
在Gradle目录下的build.gradle文件里添加如下
kapt:注解处理器,在编辑阶段进行注解的处理
-
数据表设计,如设计一个表,表名为user,数据表包含uid、first_name、last_name3个字段
-
新建Entity:定义一个User数据实体,User的每个实例都代表App数据库中的user表的一行
注:
- 所有的属性必须是public、或者有get、set方法
- @PrimaryKey:表示单个主键,当主键值为null且autoGenerate为true时可以帮助自动生成键值
- @ColumnInfo:列名的注解
-
新增DAO:定义一个名为UserDao的DAO。用来对User表的增删改查
-
新建数据库类,进行数据库配置,并需满足以下几个条件
- 新增一个RoomDatabase的abstract子类
- 子类需加注解@Database(entities=[xxx],version=n),entities包含数据实体,将会在这个数据库中创建对应的表,version是数据的版本号
- 对于与数据库关联的每个DAO类,数据库类必须定义一个无参的抽象方法,并返回DAO类实例
-
获取dao对象,即可进行数据库的增删改查操作
4)Room的原理
1.核心:
- 编译期,通过kapt处理@Dao、@Database注解,动态生成对应的是实现类
- 底层使用Android提供的SupportSQLiteOpenHelper实现数据库的增删改查等操作
2.kapt注解处理
Room在编译期,通过kapt处理@Dao和@Database注解,生成DAO和Database实现类
AppDatabase-->AppDatabase_Impl
UserDao-->UserDao_Impl
AppDatabase_Impl:数据库实例的具体实现,自动生成,主要有以下几个方法
- createOpenHelper():Room.databaseBuilder().build()创建Database时,会调用实现类的createOpenHelper()创建SupportSQLiteOpenHelper,此Helper用来创建DB以及管理版本
- userDao():创建UserDao_Impl
UserDao_Impl:UserDao的具体实现,自动实现,主要有以下3个成员变量以及UserDao里定义的接口
3个核心的成员变量:
- _db:RoomDatabase的实例
- _insertionAdapterOfUser:EntityInsertionAdapterd实例,用于数据insert
- _deletionAdapterOfUser:EntityDeletionOrUpdateAdapter实例,用于数据的update/delete
n个是UserDao里我们自己定义的接口的具体实现:
-
insertAll()
-
delete()
-
getAll()
-
loadAllByIds()
-
findByNames()
UserDao_Impl#insertAll():
使用__db开启事务
使用__insertionAdapterOfUser执行插入操作
使用__deletionAdapterOfUser执行删除操作
数据存储总结:
注:包增量越小越好
\