文章目录
前言
本文记录使用Mvvm模式加上retrofit作为网络请求时的一种网络层结构,优点在于逻辑清晰,方便后期更多功能的拓展。
介绍
数据的流向:
UI(Activity)
↓ collect
ViewModel
↓ Flow
Repository
↓
Retrofit
↓
OkHttp
具体分类如下:

数据类:
BaseResponse数据类作为网络请求的最外层数据封装,可适用常用的返回格式
ResponseData数据类是不同功能中所需的具体数据封装
拦截器:
HeaderInterceptor拦截器为了给请求头添加需要的token等数据
LoggingInterceptor拦截器为了给请求前后添加日志,以便及时查看数据的内容是否正确
数据管理层
MyRepository作为首页数据展示的案例,决定数据的获取来源,通常更复杂的逻辑是从缓存、本地、网络三层逐个获取,当然,也可以根据需要只从对应的来源获取,再由ViewModel调用来实现数据来源的隔离
状态管理层
MyViewModel作为数据持有与状态管理类,通常不同的功能或页面会各自有不同的viewmodel类
网络层
ApiService是用于retrofit进行网络请求的接口,里面通过注释来定义不同的方法进行网络请求
FlowRequest封装了网络请求过程中的不同状态,可适用不同数据类型的网络请求,通过冷流及时返回当前的请求状态及内容
NetworkResult是用于管理网络请求过程中产生的不同状态的密封类,表示了当前的网络请求状态
RetrofitClient是对retrofit的封装,使用单例模式确保的对象的唯一性,通过成员变量直接返回请求的接口对象
UI
MainActivity作为案例的UI层,可以直接通过viewmodel获取到数据来使用
详细代码介绍
gitee项目地址为:MyMvvmDemo
kotlin
<uses-permission android:name="android.permission.INTERNET" />
kotlin
/**
* @class: BaseResponse.class
* @content: 进行网络请求的基础响应结构,用于统一的接口返回
* @author: byb
*/
data class BaseResponse<T>(
val code: Int, //状态码
val message: String, //提示的信息
val data: T? //真正的数据
)
kotlin
/**
* @class: ResponseData.class
* @content: 首页展示的数据类
*
* "data": [
* {
* "content_title": "情迷",
* "content_rank": 1,
* "content_type": "情感 爱情",
* "hot": 35952,
* "description": " 安明溪原本是豪门干金,后因安家破产,安明溪父亲又迎娶了暴发户姚芊芊的母亲。她的未婚夫顾淮曾救过她,因... ",
* "image_url": "https://image.uc.cn/s/ulive_fe/s/upload/2024/87c82e9c870856f09e2fa6f97343cdad.jpg?gyunoplist=,90,webp;3,400x,0"
* },
*
* @author: byb
*/
data class ResponseData (
val content_title:String,
val content_rank:Int,
val content_type:String,
val hot:Int,
val description:String,
val image_url:String,
)
kotlin
/**
* @class: HeaderInterceptor.class
* @content: 自定义请求头拦截器,用于添加网络请求时所需的token
*/
class HeaderInterceptor: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder() //获取原始请求后添加请求头重新构建
//.addHeader("token", "your_token_here") // 添加token
.build()
return chain.proceed(request) //将重新构建的请求继续执行
}
}
kotlin
/**
* @class: LoggingInterceptor.class
* @content:日志拦截器,用于查看请求前后的数据是否正确
*/
class LoggingInterceptor: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request() //获取原始请求
// ===== 请求日志 =====
Log.d("HTTP", "┌────── Request ──────")
Log.d("HTTP", "│ Method: ${request.method}")
Log.d("HTTP", "│ URL: ${request.url}")
// 打印请求头
request.headers.forEach { (name, value) ->
Log.d("HTTP", "│ Header: $name: $value")
}
// 打印请求体(如果有)
request.body?.let { body ->
val buffer = okio.Buffer()
body.writeTo(buffer)
Log.d("HTTP", "│ Body: ${buffer.readUtf8()}")
}
val startTime = System.nanoTime() //设置纳秒级的时间戳
// 执行请求
val response = chain.proceed(request)
val duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
// ===== 响应日志 =====
Log.d("HTTP", "├────── Response ──────")
Log.d("HTTP", "│ Code: ${response.code}")
Log.d("HTTP", "│ Duration: ${duration}ms")
// 打印响应体(注意:ResponseBody 只能读一次,防止此处读取后使用时获取不到相应内容做如下处理)
response.peekBody(Long.MAX_VALUE).let { peekBody -> //复制一个副本来避免消耗原响应
Log.d("HTTP", "│ Body: ${peekBody.string()}") //副本的读取
}
Log.d("HTTP", "└────────────────────")
return response
}
}
kotlin
/**
* @class: HomeRepository.class
* @content:数据源管理层,此处决定数据源从网络、本地、缓存等地方获取,viewmodel中
* 使用时直接调用该类,不用关心具体的数据来源,实现了逻辑分离
*/
class MyRepository {
/**
* @describe: 发起首页短剧的网络请求,返回flow的数据
* @params:
* @return:
*/
fun getHomeResponse(tag: String) = FlowRequest.requestFlow { //对请求结果进行处理
RetrofitClient.api.getHomeRequest(tag) //发起网络请求
}
}
kotlin
/**
* @class: ApiService.class
* @content: 用于retrofit中发起网络请求时所用的接口设置
*/
interface ApiService {
//网址https://api.kuleu.com/api/vtquark?tag=短剧
//该方法返回类型既不是Call也不是Flow,所以需要设置为挂起函数避免请求时主线程阻塞
@GET("api/vtquark") //携带请求参数
suspend fun getHomeRequest(@Query("tag") tag: String): BaseResponse<List<ResponseData>>
}
kotlin
/**
* @class: FlowRequest.class
* @content:将请求过程进行封装,使用flow冷流来设置数据返回逻辑与线程的切换
*/
object FlowRequest {
/**
* @describe: 设置网络请求的封装逻辑,并切换IO执行,该函数为冷流需要执行flow.collect{}才会去执行
* 此处flow{}里面的内容,泛型T由函数中返回的BaseResponse类型的T决定,此处为retrofit中接口方法里返回的类型
* @params: 接收基础返回数据类型,用于执行具体请求的函数
* @return:返回请求后的不同状态下的NetworkResult,使用了flow包裹
*/
fun <T> requestFlow(block: suspend () -> BaseResponse<T>): Flow<NetworkResult<T>> = flow {
emit(NetworkResult.Loading) //先发送
try {
val response = block() //获取函数类型的网络请求结果
if (response.code == 200 && response.data != null) { //请求结果成功
emit(NetworkResult.Success(response.data))
} else { //网络请求失败
emit(NetworkResult.Error(response.code, response.message))
}
} catch (e: Exception) { //请求出现异常
emit(NetworkResult.Error(-1, e.message.toString()))
}
}.flowOn(Dispatchers.IO) //将上面代码在IO线程中执行
}
kotlin
/**
* @class: NetworkResult.class
* @content: UI层统一状态
*/
sealed class NetworkResult<out T> { //此处密封类只是标记了协变,作用是让其中子类被编译器归于当前的父类
/**
* @describe: 获取数据成功
* @params:
* @return:
*/
data class Success<T>(val data: T) : NetworkResult<T>() //此处的泛型跟协变无关
/**
* @describe: 获取数据失败
* @params:
* @return:
*/
data class Error(val code: Int, val msg: String) : NetworkResult<Nothing>()
/**
* @describe: 获取数据中
* @params:
* @return:
*/
object Loading : NetworkResult<Nothing>()
}
kotlin
object RetrofitClient {
//配置okHttp
private val okhttpClient = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) // 连接超时
.readTimeout(10, TimeUnit.SECONDS) // 读取超时
// .addInterceptor(HeaderInterceptor()) // 加token的自定义拦截器
.addInterceptor(LoggingInterceptor()) // 日志的自定义拦截器
.build()
//直接返回对应的接口实例
val api: ApiService by lazy {
Retrofit.Builder()
.baseUrl("https://api.kuleu.com/")
.client(okhttpClient)
.addConverterFactory(GsonConverterFactory.create()) //添加gson中json的转化功能
.build()
.create(ApiService::class.java)
}
}
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
val myViewModel = MyViewModel()
lifecycleScope.launch { //主线程
myViewModel.homedata.collect {
Log.i("MainActivity", "onCreate: data=$it")
}
}
myViewModel.loadData("短剧")
}
}
kotlin
/**
* @class: HomeViewModel.class
* @content:作为首页数据的viewmodel层
*/
class MyViewModel : ViewModel() {
//使用flow热流来及时传递数据
private val _homeData =
MutableStateFlow<NetworkResult<List<ResponseData>>>(NetworkResult.Loading)
val homedata: StateFlow<NetworkResult<List<ResponseData>>> = _homeData
fun loadData(tag: String) {
viewModelScope.launch {
MyRepository().getHomeResponse(tag).collect { //开启冷流的传输
_homeData.value = it //通过热流进行实时传输
}
}
}
}