Android中Mvvm+Retrofit的常用网络架构记录

文章目录


前言

本文记录使用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 //通过热流进行实时传输
            }
        }
    }
}
相关推荐
yunlaodacom10 小时前
华为云国际站服务代理商:BMS数据库延迟突然升高?磁盘I/O、网络与CPU排查方法
网络·数据库·华为云
换元不配限10 小时前
从 AsyncTask 到现代并发:Android 异步方案的演进与替代实践
android·asynctask·异步编程·方案对比·asynctask替代方案
张宇Joaquin10 小时前
自研SP600系列网卡驱动跟麒麟V10 SP3 2403配套关系
linux·运维·网络
用户20187928316711 小时前
你的 App 为什么是「电量刺客」?——从一条 WakeLock 追到 Linux 内核的 Android 省电原理全解
android
睡一觉就好了。11 小时前
Cookie与Session
网络
步行cgn11 小时前
HTTP 常用请求方法详解
网络·网络协议·http
mengge.cloud11 小时前
1019Namespace ,Contexts ,Kubernetes Pod
运维·服务器·网络·云原生·容器·kubernetes·云计算
mmsx11 小时前
Android 安全校验下沉到原生层:防 patch 与构建期自动化
android·安全·自动化
艾iYYY11 小时前
【无标题】
linux·开发语言·网络·操作系统