Android应用软件的网络通信与数据存储 | 青训营笔记

114 阅读6分钟

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

Android应用软件的网络通信与数据存储 | 青训营笔记

App通过网络请求或读取本地资源来获取内容。

网络请求框架对比

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

Retrofit

Retrofit是对OkHttp的封装。

使用方法:

  1. 引入Retrofit库
  2. 创建用于描述网络请求的接口
  3. 使用Retrofit实例发起网络请求
// 1. 引入Retrofit库
dependencies {
    implementation 'com.squareUp.retrofit2:retrofit:2.4.0'
}
// 最新版本见github
// 2. 创建用于描述网络请求的接口
interface IUserInfoService {
    @GET("users/{uid}/name")
    fun getUserName(@Path("uid") uid : Int): Call<ResponseBody>
    
    // 后续可以增加其他接口,一个接口对应一个api请求
}
/**
 * 定义说明:
 * 接口类名:可自定义,尽量和这类请求的含义相关
 * 函数名:可自定义,需要能识别出该接口的作用。interface里可以增加多个不同的函数
 * @GET注解:用于指定该接口的相对路径,并采用Get方法发起请求
 * @Path注解:需要外部调用时,传入一个uid,该uid会替换@Get注解里的{uid}
 * 返回值Call<ResponseBody>: 这里用ResponseBody,,可以直接拿到请求的String内容,如果要自动转为Model类,这里直接替换为Model类就好,比如User
 */
// 3. 发起网络请求

 

Retrofit注解类型.png

TTNet

注解

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

常见注解有:@Override``@Nullable

描述请求接口时,有修饰方法名的@GET注解,有修饰参数的@Path注解

注解的处理一般有3个时机,也就是注解的生命周期@Retention

  • SOURCE: 只有在源码中有效,编译时抛弃,例如@Override
  • CLASS: 编译class文件时有效,一般会使用到注解处理器
  • RUNTIME: 在运行期间,获取对应的注解,进行相关的处理

.java -> .class -> .dex -> .apk -> runtime

Retrofit注解@GET定义

@Document
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
    String value() default "";
}

注解的获取和使用

在运行阶段,通过反射获取到Method,有以下一些接口来获取注解的内容

  1. Method.getGenericReturnType()获取返回类型
  2. Method.getAnnotations()获取方法的注解
  3. Method.getParameterAnnotations()获取参数注解
Method[] declaredMethods = IUserInfoService.class.getDeclaredMethods();
for (Method method : declaredMethods) {
    Type type = method.getGenericReturnType();  // 正式返回类型
    Annotation[] methodAnnotations = method.getAnnotations();  // 方法注解
    Annotation[][] parameterAnnotationsArray = method.getparameterAnnotations();  // 方法参数注解
}

Retrofit是在运行期间,配合Java动态代理,获取方法和参数的注解,并构造Request对象的。

Java动态代理Proxy.newProxyInstance

  • 利用反射技术,代理某个interface,一旦调用interface里的某个方法时,实际通过代理调用InvocationHandler的invoke方法
  • 通过Method对象,调用Method.getAnnotations()和Method.getParameterAnnotation()来获取该方法和方法参数的注解内容。

Retrofit主流程

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

Retrofit调用OkHttp Retrofit调用OkHttp.png

网络通信总结

网络通信总结


数据存储

数据存储方式对比

持久性的本地存储可以保证在应用被杀死的情况下,保持数据不会被清楚。常见的数据存储方式有以下4种:

存储方式特点使用场景
SharedPreferences1.只能存boolean、int、float、long、String
2.键值对存储
记录app的各种配置信息,例如用户自己切换的开关、服务端下发的某个配置等
文件存储1.可以存各种格式的文件到手机中
2.默认情况下文件不能跨app共享
1.网络下载的zip包
2.txt文件的存储
ContentProvider1.可跨App进行数据共享
2.通过Uri对象进行访问
音频、视频、图片、通信录的读写
SQLite存储数据1.可存储结构化数据
2.对数据进行增删改查较为方便
保存feed流数据,并进行增删改查
数据库包增量n使用成本支持多表联合查询支持LiveDate、协程支持数据库加密作者
Room165KBnnYesYesYesGoogle
GreenDao151KB0.5nnYesNoYes三方
ObjectBox1879KB0.3n0.6nNoNoNo三方

Room介绍和实践案例

Room是在SQLite上提供的抽象层,以便在充分利用SQLite的强大功能的同时,能够流畅地访问数据库。

Room有3个主要组件:

  1. 数据库类Datebase,用于保存数据并作为应用持久性数据底层连接的主要访问点。
  2. 数据实体Entity,用于表示应用的数据库中的表。
  3. 数据访问对象DateAccessObjectDAO,提供可用于增删改查的方法

Room与其主要组件

Room接入: ① Gradle目录的build.gradle文件里添加

dependencies {
    def room_version = "2.4.2"
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
}

② 数据表设计

uid | first_name | last_name
----------------------------
1   |      傅     |   红雪
2   |      沈     |   孤雁
3   |      独孤   |   飞云
...
88  |      燕     |   南飞

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

@Entity
data class User(
    @PrimaryKey(autoGenerate = true) val uid: Int?,
    @ColumnInfo(name = "first_name") var firstName: String?,
    @ColumnInfo(name = "last_name") var lastName: String?)

PS:

  1. 所有属性必须是public、或者有get、set方法
  2. @PrimaryKey:表示单个主键,当主键值为null且autoGeneriate为true时可以帮助自动生成键值
  3. @ColumnInfo: 列名的注释

④ 新增DAO:定义一个名为UserDao的DAO,用来对User表的增删改查

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll(): List<User>?
    
    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    fun loadAllByIds(userIds: IntArray): List<User>?
    
    @Query("SELECT * FROM user WHERE first_name LIKE :first AND last_name LIKE :last")
    fun findByName(first: String, last: String): User?
    
    @Insert
    fun insertAll(vararg users: User)
    
    @Delete
    fun delete(user: User)
}

⑤ 新建数据库类,进行数据库配置,并需满足以下几个条件(配置数据库):

  1. 新增一个RoomDatebase的抽象子类
  2. 子类需加注解@Database(entities = [xxx], version = n),entities包含数据实体,将会在这个数据库中创建对应的表,version是数据的版本号
  3. 对于与数据库关联的每个DAO类,数据库必须定义一个无参抽象方法,并返回DAO实例
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

⑥获取DAO对象,即可进行数据库的增删改查操作

val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database-name").build()
userDao = db.userDao()

// 插入数据库
val user1 = User(1, "傅", "红雪")
val user2 = User(2, "沈", "孤雁")
val user3 = User(3, "独孤", "飞云")
userDao.insertAll(user1, user2, user3)

// 查询:获取User表中所有的数据
val userList = userDao.getAll()

// 条件查询
val user = userDao.findByName("沈", "孤雁")

// 删除
if (user != null) {
    userDao.delete(user)
}

数据存储总结

数据存储总结