网络请求与数据存储|青训营笔记

126 阅读9分钟

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

8.3 android第三课

一、网络请求

1、网络请求框架对比

请求方式作者包体积增量使用成本特点使用场景
HttpURLConnectionAndroid sdk0KB2n需要自己做封装,例如线程池管理、返回的数据解析只有少量的网络请求的工具类App
VolleyGoogle57KBn1、适合网络请求频繁,传输数据量小 2、不适合用来上传文件和下载,因为不支持输入输出流,是用一个数组来保存,数组保存有可能因为过大浪费控件甚至OOM 3、已停更之前使用volley,且无需大文件下载的App
OkHttpsquare公司262KB1.5n1、可以设置拦截器,支持大文件上传和下载 2、OkHttp基于NIO和Okio,性能更好 3、一般需要二次封装使用一般比较少直接使用,可搭配Volley或Retrofit
Retrofitsquare公司343KB2n1、具备OkHttp所有的优点,且更出色 2、restful api设计风格 3、通过注解配置请求,包括请求方法,请求参数、请求头等 4、可以搭配多种Converter将获得的数据解析,支持Gson、jackson、Protobur等团队内有研发人员对Retrofit比较熟悉时可用

2、Retrofit

Retrofit是目前Android平台上,可以说最热门的网络请求框架,是对OkHttp的一个封装

1)Retrofit的使用

  1. Retrofit库的引入

  2. 创建用于描述网络请求的接口

    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就好

  3. 使用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)注解类型:image-20220803111401479

image-20220803112134296

字节跳动的网络库——TTNet

TTNet字节内部大部分App都在使用的网络请求框架

优点:

  • 基于Retrofit开发,具有Retrofit的所有的优点
  • 支持多个网络拦截配置:添加公共参数,动态切换协议及Host,动态选路等
  • 支持流解析,json序列化image-20220803112901624

TTNet将Retrofit底层的OkHttp网络库更改的方法:

思路:

  1. 先明确Retrofit是怎么封装和使用OkHttp发起网络请求的
  2. 既然TTNet基于Retrofit修改,那可以在使用OkHttp的地方都改为使用Cronet来发网络请求

3)注解介绍

注解,可以理解为一个标签,这个标签可以加在类、方法、参数、成员变量上,并且可在合适的时机读取注解的内容,进行处理

  • 注解的获取和处理,一般有三个时机(也就是注解的生命周期@Retention)

    1. SOURCE:只有在源码中有效,编译时抛弃,例如@Override
    2. CLASS:编辑class文件时有效,一般会使用到注解处理器(APT)
    3. RUNTIME:在运行期间,获取对应的注解,并做相关的处理image-20220803114152076

注解@GET定义image-20220803114457260

@Target:指定作用的对象,比如这里的METHOD,说明这个注解作用在方法上

其他枚举值:

  • PARAMETER:参数
  • FIELD:类成员

@Retention:指定注解的生命周期,这里是RUNTIME,说明这个注解要保留到运行时

注解的获取和使用:通过反射获取到Method对象后,有以下一些接口来获取注解内容

  1. Method.getGenericReturnType():获取返回类型

  2. Method.getAnnotations():获取方法的注解

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

      image-20220803194934409

4)Retrofit主流程

重点是绿色部分:OkHttpCall的创建和调用image-20220803195111021

主流程:

  1. 通过Builder模式,创建RetrofitConfig,保存baseUrl等内容
  2. 创建动态代理对象
  3. 创建OkHttpCall
  4. 发起网络请求

Retrofit调用OkHttpimage-20220803195437649

Retrofit里OkHttpClient创建时机

  • Retrofit的Builder构造函数中如果未指定callFactory,则会自动创建一个OkHttpClientimage-20220803200024893

这里的adapterFactories集合,里面包含一个默认的CallAdapter.Factory,它是ExecutorCallAdapterFactory的实例

  • 创建好的OkHttpClient将会保存在Retrofit实例中
  • ExecutorCallAdapterFactory主要是用力控制Retrofit在子线程触发请求,在主线程回调结果image-20220803200642043

OkHttpCall的创建

当我们通过代理对象调用我们的接口方法IUserInfoService#getUserName()时,会触发InvocationHandler#invoke方法image-20220803200939833

TTNet类图设计

核心替换:

  1. 替换底层用到的OkHttpClient
  2. 替换底层用到的OkHttpCallimage-20220803201226706

image-20220803201315084

网络通信总结image-20220803201353192

二、数据存储

1、存储方式

image-20220803201730169

相对来说,数据库的使用会比较复杂些

2、数据库——框架对比image-20220803202034881

2、Room

1)Room是Google Jetpack中的一员,Room在SQLite上提供了一个抽象层,以便在充分利用SQLite的强大功能的同时,能够流畅地访问数据库

image-20220803202320779

2)主要的三个组件:

  1. 数据库类(Database),用于保存数据库并作为应用持久性数据底层连接地主要访问点
  2. 数据实体(Entity),用于表示应用的数据库中的表
  3. 数据访问对象(DAO),提供增删改查数据库中的数据的方法image-20220803202644389

3)实践

  1. Room接入

    在Gradle目录下的build.gradle文件里添加如下image-20220803203120969

    kapt:注解处理器,在编辑阶段进行注解的处理

  2. 数据表设计,如设计一个表,表名为user,数据表包含uid、first_name、last_name3个字段image-20220803203346034

  3. 新建Entity:定义一个User数据实体,User的每个实例都代表App数据库中的user表的一行image-20220803203616576

    注:

    1. 所有的属性必须是public、或者有get、set方法
    2. @PrimaryKey:表示单个主键,当主键值为null且autoGenerate为true时可以帮助自动生成键值
    3. @ColumnInfo:列名的注解
  4. 新增DAO:定义一个名为UserDao的DAO。用来对User表的增删改查image-20220803204132991

  5. 新建数据库类,进行数据库配置,并需满足以下几个条件

    1. 新增一个RoomDatabase的abstract子类
    2. 子类需加注解@Database(entities=[xxx],version=n),entities包含数据实体,将会在这个数据库中创建对应的表,version是数据的版本号
    3. 对于与数据库关联的每个DAO类,数据库类必须定义一个无参的抽象方法,并返回DAO类实例image-20220803204822347
  6. 获取dao对象,即可进行数据库的增删改查操作image-20220803205003984

4)Room的原理

1.核心:

  1. 编译期,通过kapt处理@Dao、@Database注解,动态生成对应的是实现类
  2. 底层使用Android提供的SupportSQLiteOpenHelper实现数据库的增删改查等操作

2.kapt注解处理

Room在编译期,通过kapt处理@Dao和@Database注解,生成DAO和Database实现类

AppDatabase-->AppDatabase_Impl
UserDao-->UserDao_Impl

image-20220803205756817

AppDatabase_Impl:数据库实例的具体实现,自动生成,主要有以下几个方法

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

image-20220803210414841

UserDao_Impl:UserDao的具体实现,自动实现,主要有以下3个成员变量以及UserDao里定义的接口

3个核心的成员变量:

  • _db:RoomDatabase的实例
  • _insertionAdapterOfUser:EntityInsertionAdapterd实例,用于数据insert
  • _deletionAdapterOfUser:EntityDeletionOrUpdateAdapter实例,用于数据的update/delete

n个是UserDao里我们自己定义的接口的具体实现:

  • insertAll()

  • delete()

  • getAll()

  • loadAllByIds()

  • findByNames()

    image-20220803211306348

UserDao_Impl#insertAll():

使用__db开启事务

使用__insertionAdapterOfUser执行插入操作image-20220803211448052

使用__deletionAdapterOfUser执行删除操作image-20220803211633140

image-20220803211838792

数据存储总结:image-20220803211902946

注:包增量越小越好

\