花式封装:Kotlin+协程+Flow+Retrofit+OkHttp +Repository,倾囊相授,彻底减少模版代码进阶之路
前言 :众里寻它千百度, 蓦然回首,此种代码却在灯火阑珊处。
一、前言
- 本文介绍思路:
本文重点介绍思路:四种方式花式解决Repository
中模版式的代码,逐级递增
1.1 :涉及到Kotlin
、协程
、Flow、viewModel、Retrofit、Okhttp
相关用法
1.2 :涉及到注解
、反射
、泛型
、注解处理器
相关用法
1.3 :涉及到动态代理
,kotlin
中suspend
方法反射调用及反射中异常处理
1.4 :本示例4个项目如图: - 网络框架搭建的封装,到目前为止最为流行又很优雅的的是
Kotlin
+协程
+Flow
+Retrofit
+OkHttp
+Repository
- 先来看看中间各个类的职责:
- 从上图可以看出
单一职责:
NetApi:
负责网络接口配置,包括 请求地址,请求头,请求方式,参数等等所有配置Flow+Retrofit+Okhttp:
联合起来负责把NetApi
中的各种配置组装成网络请求行为,并且通过Flow 组装成流,通过它可以控制该行为的异步方式,异步开始结束等等一系列的流行为。Repository:
负责Flow+Retrofit+Okhttp
请求结果的数据流,进行加工处理成我们想要的数据,大多数不需要处理的,可以直接给到ViewModel
ViewModel:
负责调用Repository
,拿到想要的数据然后提供给UI方展示使用或者相关使用也可以看到 它的 持有链 从右向左 一条线性持有:
ViewModel
持有Repository
,Repository
持有Flow+Retrofit+Okhttp
,Flow+Retrofit+Okhttp
持有NetApi
- 最终我们可以得到:
5.1. 网络请求行为 会根据NetApi
写出模板式的代码,这块解决模版式的代码在Retrofit
中它通过动态代理,把所有模版式的代码统一成了一个
5.2. 同理:Repository
也是根据NetApi
配置的接口,写成模版式的代码转换成流
二、花式封装(一)
NetApi
的配置:
interface NetApi {
// 示例get 请求
@GET("https://www.wanandroid.com/article/list/0/json")
suspend fun getHomeList(): CommonResult
// 示例get 请求2
@GET("https://www.wanandroid.com/article/list/{path}/json")
suspend fun getHomeList(@Path("path") page: Int): CommonResult
@GET("https://www.wanandroid.com/article/list/{path}/json")
suspend fun getHomeList(@Path("path") page: Int, @Path("path") a: Int): CommonResult
@GET("https://www.wanandroid.com/article/list/{path}/json")
suspend fun getHomeList(@Path("path") page: Int, @Path("path") f: Float): CommonResult
// 示例get 请求2
@GET("https://www.wanandroid.com/article/list/{path}/json")
suspend fun getHomeList2222(@Path("path") page: Int): CommonResult
@GET("https://www.wanandroid.com/article/list/{path}/json")
suspend fun getHomeList3333(@Path("path") page: Int): CommonResult
@GET("https://www.wanandroid.com/article/list/{path}/json")
suspend fun getHomeList5555(@Path("path") page: Int, @Query("d") ss: String, @HeaderMap map: Map): CommonResult
@GET("https://www.wanandroid.com/article/list/{path}/json")
suspend fun getHomeList6666(
@Path("path") page: Int,
@Query("d") float: Float,
@Query("d") long: Long,
@Query("d") double: Double,
@Query("d") byte: Byte,
@Query("d") short: Short,
@Query("d") char: Char,
@Query("d") boolean: Boolean,
@Query("d") string: String,
@Body body: RequestBodyWrapper
): CommonResult
//示例post 请求
@FormUrlEncoded
@POST("https://www.wanandroid.com/user/register")
suspend fun register(
@Field("username") username: String,
@Field("password") password: String,
@Field("repassword") repassword: String
): String
/************************* 以下只 示例写法,接口调不通,因为找不到那么多 公开接口 全是 Retrofit的用法 来测试 *****************************************************/
// @FormUrlEncoded
@Headers("Content-Type: application/x-www-form-urlencoded") //todo 固定 header
@POST("https://xxxxxxx")
suspend fun post1(@Body body: RequestBody): String
// @FormUrlEncoded
@Headers("Content-Type: application/x-www-form-urlencoded")
@POST("https://xxxxxxx22222")
suspend fun post12(@Body body: RequestBody, @HeaderMap map: Map): String //todo HeaderMap 多个请求头部自己填写
suspend fun post1222(@Body body: RequestBody, @HeaderMap map: Map): String //todo HeaderMap 多个请求头部自己填写
}
,>,>,>
2. NetRepository
中是 根据 NetApi
写出下面类似的全模版式的代码:都是返回 Flow
流
class NetRepository private constructor() {
val service by lazy { RetrofitUtils.instance.create(NetApi::class.java) }
companion object {
val instance by lazy { NetRepository() }
}
// 示例get 请求
fun getHomeList() = flow { emit(service.getHomeList()) }
// 示例get 请求2
fun getHomeList(page: Int) = flow { emit(service.getHomeList(page)) }
fun getHomeList(page: Int, a: Int) = flow { emit(service.getHomeList(page, a)) }
fun getHomeList(page: Int, f: Float) = flow { emit(service.getHomeList(page, f)) }
// 示例get 请求2
fun getHomeList2222(page: Int) = flow { emit(service.getHomeList2222(page)) }
fun getHomeList3333(page: Int) = flow { emit(service.getHomeList3333(page)) }
fun getHomeList5555(page: Int, ss: String, map: Map<String, String>) = flow { emit(service.getHomeList5555(page, ss, map)) }
fun getHomeList6666(
page: Int, float: Float, long: Long, double: Double, byte: Byte,
short: Short, char: Char, boolean: Boolean, string: String, body: RequestBodyWrapper
) = flow {
emit(service.getHomeList6666(page, float, long, double, byte, short, char, boolean, string, body))
}
fun register(username: String, password: String, repassword: String) = flow { emit(service.register(username, password, repassword)) }
//
// /************************* 以下只 示例写法,接口调不通,因为找不到那么多 公开接口 全是 Retrofit的用法 来测试 *****************************************************/
//
//
fun post1(body: RequestBody) = flow { emit(service.post1(body)) }
fun post12(body: RequestBody, map: Map<String, String>) = flow { emit(service.post12(body, map)) }
fun post1222(id: Long, asr: String) = flow {
val map = mutableMapOf()
map["id"] = id
map["asr"] = asr
val mapHeader = HashMap()
mapHeader["v"] = 1000
mapHeader["device_sn"] = "Avidfasfa1213"
emit(service.post1222(RequestBodyWrapper(Gson().toJson(map)), mapHeader))
}
}
,>,>
3. viewModel
调用端:
class MainViewModel : BaseViewModel() {
private val repository by lazy { NetRepository.instance }
fun getHomeList(page: Int) {
flowAsyncWorkOnViewModelScopeLaunch {
repository.getHomeList(page).onEach {
android.util.Log.e("MainViewModel", "one 111 ${it.data?.datas!![0].title}")
}
}
}
}
—————————————————我是分割线君—————————————————
上面花式玩法(一):
此种写法被广泛称作最优雅的一套网络封装
框架,绝大多数中、大厂
基本也就封装到此为止了可能还有些人想着:你的
repository
中就返回了Flow
, 里面就全是简单的emit(xxx)
,我项目里面不是这样的,我的还封装了成功,失败,或者其他的,但总体还是全是模版式的,除了特殊的一些方法,需要在请求前 ,请求后做些处理,有规律有模版的还是占大多数吧,只要大多数都一样的规律模版,都是可以处理的,里面稍微修改下细节,思路都是一样的。哪还能有什么玩法?
可能会有人想到 借助
Hilt
,Dagger2
,Koin
来创建Retrofit
,和创建repository
,创建ViewModel
这里不是讨论依赖注入创建对象的事情哪还有什么玩法?
有,必须有的。
三、花式封装(二)
- 既然上面是
Repository
类中,所有写法都是固定模版式的代码,那么让其根据NetApi:
自动生成Repository
类,我们这里借用注解处理器。 - 具体怎么使用介绍,请参考:
注解处理器在架构,框架中实战应用:MVVM中数据源提供Repository类的自动生成 - 本项目中只需要编译
app_wx2
工程 - 在下图中找到
5. viewModel调用端
class MainViewModel : BaseViewModel() {
private val repository by lazy { RNetApiRepository() }
fun getHomeList(page: Int) {
flowAsyncWorkOnViewModelScopeLaunch {
val time = System.currentTimeMillis()
repository.getHomeList(page).onEach {
android.util.Log.e("MainViewModel", "two 222 ${it.data?.datas!![0].title}")
android.util.Log.e("MainViewModel", "耗时:${(System.currentTimeMillis() - time)} ms")
}
}
}
}
6. 如果 Repository
中某个接口方法需要特殊处理怎么办?比如下图,请求前处理一下,从 拿到数据后我需要再次转化处理之后再给到 viewModel
怎么办?
//我这个接口 ,请求前需要 判断处理一下,拿到数据后也需要再处理一下
fun post333(id: Long, asr: String, m: String, n: String, list: List<String>) = flow {
val map = mutableMapOf()
map["id"] = id
map["asr"] = asr
val mapHeader = HashMap()
mapHeader["v"] = 1000
mapHeader["device_sn"] = "Avidfasfa1213"
//接口调用前 根据 需要处理操作
list.forEach {
if (map.containsKey(id.toString())) {
///
}
}
val result = service.post1222(RequestBodyWrapper(Gson().toJson(map)), mapHeader)
// 拿到数据后需要处理操作
val result1 = result
emit(result1)
}.map {
//需要再转化一下
it
}.filter {
//过滤一下
it.length == 3
}
,>,>
7. 可以在 接口 NetApi
中该方法上配置 @Filter
注解过滤 ,该方法需要自己特殊处理,不自动生成,如下
@Filter
@POST("https://xxxxxxx22222")
suspend fun post333(@Body body: RequestBody, @HeaderMap map: Map): String
,>
- 如果想 post请求的
RequestBody
内部参数单独出来进入方法传参,可以加上 在NetApi
中方法加上@PostBody
:如下:
@PostBody("{"ID":"Long","name":"String"}")
@POST("https://www.wanandroid.com/user/register")
suspend fun testPostBody222(@Body body: RequestBody): String
这样 该方法生成出来的对应方法就是:
public suspend fun testPostBody222(ID: Long, name: java.lang.String): Flow =
kotlinx.coroutines.flow.flow {
val map = mutableMapOf()
map["ID"] = ID
map["name"] = name
val result = service.testPostBody222(com.wx.test.api.retrofit.RequestBodyCreate.toBody(com.google.gson.Gson().toJson(map)))
emit(result)
}
,>
怎么特殊处理,单独手动建一个Repository,针对该方法,单独写,特殊就要特殊手动处理,但是大多数模版式的代码,都可以让其自动生成。
—————————————————我是分割线君—————————————————
到了这里,我们再想, NetApi
是一个接口类,
但是实际上没有写接口实现类啊, 它怎么实现的呢?
我们上面 花式玩法(二)
中虽然是自动生成的,但是还是有方法体,
可不可以再省略点?
可以,必须有!
四、花式玩法(三)
- 我们可以根据
NetApi
里面的配置,自动生成INetApiRepository
接口类, 接口名和参数 都和NetApi
保持一致,唯一区别就是返回的对象变成了Flow
了,
这样在Repository
中就把数据转变为flow
流了 - 配置让代码自动生成的类:
@AutoCreateRepositoryInterface(interfaceApi = "com.wx.test.api.net.NetApi")
class KaptInterface {
}
生成的接口类 INetApiRepository
代码如下:
public interface INetApiRepository {
public fun getHomeList(): Flow>
public fun getHomeList(page: Int): Flow>
public fun getHomeList(page: Int, f: Float): Flow>
public fun getHomeList(page: Int, a: Int): Flow>
public fun getHomeList2222(page: Int): Flow>
public fun getHomeList3333(page: Int): Flow>
public fun getHomeList5555(
page: Int,
ss: String,
map: Map<String, String>
): Flow>
public fun getHomeList6666(
page: Int,
float: Float,
long: Long,
double: Double,
byte: Byte,
short: Short,
char: Char,
boolean: Boolean,
string: String,
body: RequestBodyWrapper
): Flow>
public fun getHomeListA(page: Int): Flow>
public fun getHomeListB(page: Int): Flow
public fun post1(body: RequestBody): Flow
public fun post12(body: RequestBody, map: Map<String, String>): Flow
public fun post1222(body: RequestBody, map: Map<String, Any>): Flow
public fun register(
username: String,
password: String,
repassword: String
): Flow
public fun testPostBody222(ID: Long, name: java.lang.String): Flow
}
Repository
职责承担的调用端:用动态代理:
class RepositoryPoxy private constructor() : BaseRepositoryProxy() {
val service = NetApi::class.java
val api by lazy { RetrofitUtils.instance.create(service) }
companion object {
val instance by lazy { RepositoryPoxy() }
}
fun callApiMethod(serviceR: Class<R>): R {
return Proxy.newProxyInstance(serviceR.classLoader, arrayOf(serviceR)) { proxy, method, args ->
flow {
val funcds = findSuspendMethod(service, method.name, args)
if (args == null) {
emit(funcds?.callSuspend(api))
} else {
emit(funcds?.callSuspend(api, *args))
}
// emit((service.getMethod(method.name, *parameterTypes)?.invoke(api, *(args ?: emptyArray())) as Call).execute().body())
}.catch {
if (it is InvocationTargetException) {
throw Throwable(it.targetException)
} else {
it.printStackTrace()
throw it
}
}
} as R
}
}
BaseRepositoryProxy
中内容:
open class BaseRepositoryProxy {
private val map by lazy { mutableMapOf?>() }
private val sb by lazy { StringBuffer() }
@OptIn(ExperimentalStdlibApi::class)
fun findSuspendMethod(service: Class<T>, methodName: String, args: Array<out Any>): KFunction<*>? {
sb.delete(0, sb.length)
sb.append(service.name)
.append(methodName)
args.forEach {
sb.append(it.javaClass.typeName)
}
val key = sb.toString()
if (!map.containsKey(key)) {
val function = service.kotlin.memberFunctions.find { f ->
var isRight = 0
if (f.name == methodName && f.isSuspend) {
if (args.size == 0 && f.parameters.size == 1) {
isRight = 2
} else {
f.parameters.forEachIndexed { index, it ->
if (index > 0 && args.size > 0) {
if (args.size == 0) {
isRight = 2
return@forEachIndexed
}
if (it.type.javaType.typeName == javaClassTransform(args[index - 1].javaClass).typeName) {
isRight = 2
} else {
isRight = 1
return@forEachIndexed
}
}
}
}
}
//方法名一直 是挂起函数 方法参数个数一致, 参数类型一致
f.name == methodName && f.isSuspend && f.parameters.size - 1 == args.size && isRight == 2
}
map[key] = function
}
return map[key]
}
private fun javaClassTransform(clazz: Class<Any>) = when (clazz.typeName) {
"java.lang.Integer" -> Int::class.java
"java.lang.String" -> String::class.java
"java.lang.Float" -> Float::class.java
"java.lang.Long" -> Long::class.java
"java.lang.Boolean" -> Boolean::class.java
"java.lang.Double" -> Double::class.java
"java.lang.Byte" -> Byte::class.java
"java.lang.Short" -> Short::class.java
"java.lang.Character" -> Char::class.java
"SingletonMap" -> Map::class.java
"LinkedHashMap" -> MutableMap::class.java
"HashMap" -> HashMap::class.java
"Part" -> MultipartBody.Part::class.java
"RequestBody" -> RequestBody::class.java
else -> {
if ("RequestBody" == clazz.superclass.simpleName) {
RequestBody::class.java
} else {
Any::class.java
}
}
}
}
,>
- ViewModel中调用端:
class MainViewModel : BaseViewModel() {
private val repository by lazy { RepositoryPoxy.instance }
fun getHomeList(page: Int) {
flowAsyncWorkOnViewModelScopeLaunch {
val time = System.currentTimeMillis()
repository.callApiMethod(INetApiRepository::class.java).getHomeList(page).onEach {
android.util.Log.e("MainViewModel", "three 333 ${it.data?.datas!![0].title}")
android.util.Log.e("MainViewModel", "耗时:${(System.currentTimeMillis() - time)} ms")
}
}
}
}
—————————————————我是分割线君—————————————————
- 上面生成的接口类
INetApiRepository
其实方法和NetApi
拥有相似的模版,唯一区别就是返回类型,一个是对象,一个是Flow 流的对象还能省略吗?
有,必须有
五、花式玩法(四)
- 直接修改
RepositoryPoxy
,作为Reposttory的职责 ,连上面的INetApiRepository
的接口类全部省略了, 如下:
class RepositoryPoxy private constructor() : BaseRepositoryProxy() {
val service = NetApi::class.java
val api by lazy { RetrofitUtils.instance.create(service) }
companion object {
val instance by lazy { RepositoryPoxy() }
}
fun callApiMethod(clazzR: Class<R>, methodName: String, vararg args: Any): Flow {
return flow {
val clssss = mutableListOfout Any>>()
args?.forEach {
clssss.add(javaClassTransform(it.javaClass))
}
val parameterTypes = clssss.toTypedArray()
val call = (service.getMethod(methodName, *parameterTypes)?.invoke(api, *(args ?: emptyArray())) as Call)
call?.execute()?.body()?.let {
emit(it as R)
}
}
}
@OptIn(ExperimentalStdlibApi::class)
fun callApiSuspendMethod(clazzR: Class<R>, methodName: String, vararg args: Any): Flow {
return flow {
val funcds = findSuspendMethod(service, methodName, args)
if (args == null) {
emit(funcds?.callSuspend(api) as R)
} else {
emit(funcds?.callSuspend(api, *args) as R)
}
}
}
}
<
2. ViewModel中调用入下:
class MainViewModel : BaseViewModel() {
private val repository by lazy { RepositoryPoxy.instance }
fun getHomeList(page: Int) {
flowAsyncWorkOnViewModelScopeLaunch {
val time = System.currentTimeMillis()
repository.callApiSuspendMethod(HomeData::class.java, "getHomeListB", page).onEach {
android.util.Log.e("MainViewModel", "four 444 ${it.data?.datas!![0].title}")
android.util.Log.e("MainViewModel", "耗时:${(System.currentTimeMillis() - time)} ms")
}
}
}
}
六、总结
通过上面4中花式玩法:
- 花式玩法1: 我们知道了最常见最优雅的写法,但是模版式
repository
代码太多,而且需要手动写 - 花式玩法2: 把花式玩法1中的模版式
repository
,让其自动生成,对于特殊的方法,单独手动再写个repository
,这样让大多数模版式代码全自动生成 - 花式玩法3:
NetApi
,可以根据配置,动态代理生成网络请求行为,该行为统一为动态代理实现,无需对接口类NetApi
单独实现,那么我们的repository
也可以 生成一个接口类INetApiRepository
,然后动态代理实现其内部 方法体逻辑 - 花式玩法4:我连花式玩法3中的接口类
INetApiRepository
都不需要了,直接反射搞定所有。 - 同时可以学习到,注解、反射、泛型、注解处理器、动态代理
项目地址
感谢阅读:
欢迎 点赞、收藏、关注
来源:juejin.cn/post/7417847546323042345