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 //通过热流进行实时传输
            }
        }
    }
}
相关推荐
深念Y2 小时前
Nginx和Spring Cloud Gateway
运维·服务器·网络·网关·nginx·spring cloud·微服务
常利兵2 小时前
Android 字体字重设置:从XML到Kotlin的奇妙之旅
android·xml·kotlin
熬夜的咕噜猫2 小时前
Nginx 安全防护与 HTTPS 部署实战
网络·数据库
神的孩子都在歌唱2 小时前
无线网络基础:802.11协议、信道干扰与加密方式
网络
乾元2 小时前
未来展望: 当 AGI(通用人工智能)出现,网络安全是否会消失?
网络·人工智能·安全·机器学习·网络安全·架构·安全架构
hnlgzb2 小时前
kotlin安卓app中,当一个类继承ViewModel类的时候,这个类是想干什么?
android·开发语言·kotlin
zh_xuan2 小时前
Android compose测试数据双向绑定
android·compose
hnlgzb2 小时前
kotlin类 继承android.app.Activity 和androidx.activity.ComponentActivity 有什么区别?
android·kotlin·androidx
OidEncoder2 小时前
工业安全选型避坑|安全编码器与双编码器方案,各有适配场景(含参数指南)
网络·人工智能·安全