那些大厂架构师是怎样封装网络请求的?

好的设计是成功的一半,好的设计思想为后面扩展带来极大的方便

一、前言

网络请求在开发中是必不可少的一个功能,如何设计一套好的网络请求框架,可以为后面扩展及改版带来极大的方便,特别是一些长期维护的项目。作为一个深耕Android开发十几载的大龄码农,深深的体会到。

网络框架的发展:

1. 从最早的HttpClientHttpURLConnection ,那时候需要自己用线程池封装异步,Handler切换到UI线程,要想从网络层就返回接收实体对象,也需要自己去实现封装

2. 后来,谷歌的 Volley , 三方的 Afinal 再到 XUtils 都是基于上面1中的网络层再次封装实现

3. 再到后来,OkHttp 问世,Retrofit 空降,从那以后基本上网络请求应用层框架就是 OkHttp Retrofit 两套组合拳,基本打遍天下无敌手,最多的变化也就是在这两套组合拳里面秀出各种变化,但是思想实质上还是这两招。

我们试想:从当初的大概2010年,2011年,2012年开始,就启动一个App项目,就网络这一层的封装而言,随着时代的潮流,技术的演进,我们势必会经历上面三个阶段,这一层的封装就得重构三次。

现在是2024年,往后面发展,随着http3.0的逐渐成熟,一定会出现更好的网络请求框架

我们怎么封装一套更容易扩展的框架,而不必每次重构这一层时,改动得那么困难。

本文下面就示例这一思路如何封装,涉及到的知识,jetpack 中的手术刀: Hilt 成员来帮助我们实现。

二 、示例项目

  1. 上图截图圈出的就是本文重点介绍的内容:怎么快速封装一套可以切换网络框架的项目 及相关 Jetpack中的 Hilt 用法

  2. 其他的1,2,3,4是之前我写的:花式封装:Kotlin+协程+Flow+Retrofit+OkHttp +Repository,倾囊相授,彻底减少模版代码进阶之路,大家可以参考,也可以在它的基础上,再结合本文再次封装,可以作为 花式玩法五

三、网络层代码设计

1. 设计请求接口,包含请求地址 Url,请求头,请求参数,返回解析成的对象Class :

kotlin 复制代码
interface INetApi {
    /**
     * Get请求
     * @param url:请求地址
     * @param clazzR:返回对象类型
     * @param header:请求头
     * @param map:请求参数
     */

    suspend fun <R> getApi(url: String, clazzR: Class<R>, header: MutableMap<String, String>? = null, map: MutableMap<String, Any>? = null): R

    /**
     * Get请求
     * @param url:请求地址
     * @param clazzR:返回对象类型
     * @param header:请求头
     * @param map:请求参数
     * @param body:请求body
     */
    suspend fun <R> postApi(url: String, clazzR: Class<R>, header: MutableMap<String, String>? = null, body: String? = null): R
}

2. 先用早期 HttpURLConnection 对网络请求进行实现:

kotlin 复制代码
class HttpUrlConnectionImpl  constructor() : INetApi {
    private val gson by lazy { Gson() }

    override suspend fun <R> getApi(url: String, clazzR: Class<R>, header: MutableMap<String, String>?, map: MutableMap<String, Any>?): R {
        //这里HttpUrlConnectionRequest内部是HttpURLConnection的Get请求真正的实现
        val json = HttpUrlConnectionRequest.getResult(BuildParamUtils.buildParamUrl(url, map), header)
        android.util.Log.e("OkhttpImpl", "HttpUrlConnection 请求:${json}")
        return gson.fromJson<R>(json, clazzR)
    }

    override suspend fun <R> postApi(url: String, clazzR: Class<R>, header: MutableMap<String, String>?, body: String?): R {
        ////这里HttpUrlConnectionRequest内部是HttpURLConnection的Post请求真正的实现
        val json = HttpUrlConnectionRequest.postData(url, header, body)
        return gson.fromJson<R>(json, clazzR)
    }
}

3. 整个项目 build.gradle 下配置 Hilt插件

arduino 复制代码
buildscript {
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.42'
    }
}

4. 工程app的 build.gradle 下引入:

先配置:

bash 复制代码
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'dagger.hilt.android.plugin'//Hilt使用
    id 'kotlin-kapt'//
}

里面的 android 下面添加:

ini 复制代码
kapt {
    generateStubs = true
}

dependencies 里面引入 Hilt 使用

arduino 复制代码
//hilt
implementation "com.google.dagger:hilt-android:2.42"
kapt "com.google.dagger:hilt-android-compiler:2.42"
kapt 'androidx.hilt:hilt-compiler:1.0.0'

5. 使用 Hilt

5.1 在Application上添加注解 @HiltAndroidApp
kotlin 复制代码
@HiltAndroidApp
class MyApp : Application() {

}
5.2 在使用的Activity上面添加注解 @AndroidEntryPoint
kotlin 复制代码
@AndroidEntryPoint
class MainActivity : BaseViewModelActivity<MainViewModel>(R.layout.activity_main), View.OnClickListener {

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.btn1 -> {
                viewModel.getHomeList()
            }
            else -> {}
        }
    }
}
5.3 在使用的ViewModel上面添加注解 @HiltViewModel@Inject
kotlin 复制代码
@HiltViewModel
class MainViewModel @Inject constructor(private val repository: NetRepository) : BaseViewModel() {


    fun getHomeList() {
        flowAsyncWorkOnViewModelScopeLaunch {
            repository.getHomeList().onEach {
                val title = it.datas!![0].title
                android.util.Log.e("MainViewModel", "one 111 ${title}")
                errorMsgLiveData.postValue(title)
            }
        }
    }
}
5.4 在 HttpUrlConnectionImpl 构造方法上添加注解 @Inject 如下:
kotlin 复制代码
class HttpUrlConnectionImpl @Inject constructor() : INetApi {
    private val gson by lazy { Gson() }

    override suspend fun <R> getApi(url: String, clazzR: Class<R>, header: MutableMap<String, String>?, map: MutableMap<String, Any>?): R {
        val json = HttpUrlConnectionRequest.getResult(BuildParamUtils.buildParamUrl(url, map), header)
        android.util.Log.e("OkhttpImpl", "HttpUrlConnection 请求:${json}")
        return gson.fromJson<R>(json, clazzR)
    }

    override suspend fun <R> postApi(url: String, clazzR: Class<R>, header: MutableMap<String, String>?, body: String?): R {
        val json = HttpUrlConnectionRequest.postData(url, header, body)
        return gson.fromJson<R>(json, clazzR)
    }
}
5.5 新建一个 annotationBindHttpUrlConnection 如下:
less 复制代码
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
annotation class BindHttpUrlConnection()
5.6 再建一个绑定网络请求的 abstract 修饰的类 AbstractHttp 如下:让 @BindHttpUrlConnectionHttpUrlConnectionImpl 在如下方法中通过注解绑定
less 复制代码
@InstallIn(SingletonComponent::class)
@Module
abstract class AbstractHttp {


    @BindHttpUrlConnection
    @Singleton
    @Binds
    abstract fun bindHttpUrlConnection(h: HttpUrlConnectionImpl): INetApi
}
5.7 在viewModel持有的仓库类 NetRepository 的构造方法中添加 注解 @Inject ,并且申明 INetApi ,并且绑定注解 @BindHttpUrlConnection 如下: 然后即就可以开始调用 INetApi 的方法
kotlin 复制代码
class NetRepository @Inject constructor(@BindHttpUrlConnection val netHttp: INetApi) {

    suspend fun getHomeList(): Flow<WanAndroidHome> {
        return flow {
            netHttp.getApi("https://www.wanandroid.com/article/list/0/json", HomeData::class.java).data?.let { emit(it) }
        }
    }
}

到此:Hilt使用就配置完成了 ,那边调用 网络请求就直接执行到 网络实现 类 HttpUrlConnectionImpl 里面去了。

运行结果看到代码执行打印:

5.8 我们现在切换到 Okhttp 来实现网络请求:

新建 OkhttpImpl 实现 INetApi 并在其构造方法上添加 @Inject 如下:

kotlin 复制代码
class OkhttpImpl @Inject constructor() : INetApi {

    private val okHttpClient by lazy { OkHttpClient() }
    private val gson by lazy { Gson() }

    override suspend fun <R> getApi(url: String, clazzR: Class<R>, header: MutableMap<String, String>?, map: MutableMap<String, Any>?): R {
        try {
            val request = Request.Builder().url(buildParamUrl(url, map))
            header?.forEach {
                request.addHeader(it.key, it.value)
            }
            val response = okHttpClient.newCall(request.build()).execute()
            if (response.isSuccessful) {
                val json = response.body?.string()
                android.util.Log.e("OkhttpImpl","okhttp 请求:${json}")
                return gson.fromJson<R>(json, clazzR)
            } else {
                throw RuntimeException("response fail")
            }
        } catch (e: Exception) {
            throw e
        }
    }

    override suspend fun <R> postApi(url: String, clazzR: Class<R>, header: MutableMap<String, String>?, body: String?): R {
        try {
            val request = Request.Builder().url(url)
            header?.forEach {
                request.addHeader(it.key, it.value)
            }
            body?.let {
                request.post(RequestBodyCreate.toBody(it))
            }
            val response = okHttpClient.newCall(request.build()).execute()
            if (response.isSuccessful) {
                return gson.fromJson<R>(response.body.toString(), clazzR)
            } else {
                throw RuntimeException("response fail")
            }
        } catch (e: Exception) {
            throw e
        }
    }
}
5.9 再建一个注解 annotation 类型的 BindOkhttp 如下:
less 复制代码
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
annotation class BindOkhttp()
5.10 在 AbstractHttp 类中添加 @BindOkhttp 绑定到 OkhttpImpl,如下:
less 复制代码
@InstallIn(SingletonComponent::class)
@Module
abstract class AbstractHttp {

    @BindOkhttp
    @Singleton
    @Binds
    abstract fun bindOkhttp(h: OkhttpImpl): INetApi

    @BindHttpUrlConnection
    @Singleton
    @Binds
    abstract fun bindHttpUrlConnection(h: HttpUrlConnectionImpl): INetApi
}
5.11 现在只需要在 NetRepository 中持有的 INetApi 修改其绑定的 注解 @BindHttpUrlConnection 改成 @BindOkhttp 便可以将项目网络请求全部改成由 Okhttp来实现了,如下:
kotlin 复制代码
//class NetRepository @Inject constructor(@BindHttpUrlConnection val netHttp: INetApi) {
class NetRepository @Inject constructor(@BindOkhttp val netHttp: INetApi) {

    suspend fun getHomeList(): Flow<WanAndroidHome> {
        return flow {
            netHttp.getApi("https://www.wanandroid.com/article/list/0/json", HomeData::class.java).data?.let { emit(it) }
        }
    }
}

运行执行结果截图可见:

到此:网络框架切换就这样简单的完成了。

四、总结

  1. 本文重点介绍了,怎么对网络框架扩展型封装:即怎么可以封装成快速从一套网络请求框架,切换到另一套网络请求上去
  2. 借助于 Jetpack中成员 Hilt 对其整个持有链路进行切割,简单切换绑定网络实现框架1,框架2,框架xxx等。

项目地址

项目地址:
github地址
gitee地址

感谢阅读:

欢迎 点赞、收藏、关注

这里你会学到不一样的东西

相关推荐
前端切图仔0014 分钟前
Chrome 扩展程序上架指南
android·java·javascript·google
黄林晴6 分钟前
Compose Multiplatform 1.10.0 重磅发布!三大核心升级,跨平台开发效率再提升
android·android jetpack
锁我喉是吧8 分钟前
Android studio 编译faiss
android·android studio·faiss
鹏程十八少12 分钟前
3. Android 腾讯开源的 Shadow,凭什么成为插件化“终极方案”?
android·前端·面试
TheNextByte114 分钟前
如何通过蓝牙将联系人从Android传输到 iPhone
android·cocoa·iphone
Wpa.wk17 分钟前
性能测试-性能监控相关命令-基础篇
android·linux·运维·经验分享·测试工具·性能测试·性能监控
Kapaseker18 分钟前
必须要搞懂的 View 核心问题
android·kotlin
西红市杰出青年18 分钟前
crawl4ai------AsyncPlaywrightCrawlerStrategy使用教程
开发语言·python·架构·正则表达式·pandas
Qiuner26 分钟前
一文读懂 Lambda
java·spring boot·后端·架构
东方佑26 分钟前
思维自指:LLM推理架构的维度突破与意识雏形
人工智能·架构