专栏名称: 郭霖
Android技术分享平台,每天都有优质技术文章推送。你还可以向公众号投稿,将自己总结的技术心得分享给大家。
目录
相关文章推荐
郭霖  ·  使用Hilt来协助封装网络请求 ·  3 天前  
鸿洋  ·  一个大型 Android 项目的模块划分哲学 ·  3 天前  
鸿洋  ·  Android H5页面性能分析策略 ·  1 周前  
51好读  ›  专栏  ›  郭霖

使用Hilt来协助封装网络请求

郭霖  · 公众号  · android  · 2024-11-15 08:00

正文



/   今日科技快讯   /


近日,腾讯控股公布2024年三季度财报,显示腾讯Q3营收连续八季度增长,净利润同比大增47%,广告、游戏业务均表现强劲,云服务业务毛利显著改善。腾讯表示,旗舰款常青树游戏实现的健康的总流水和总收入增长。具体而言,王者荣耀的流水在三季度实现了同比增长,和平精英则延续了最近的强劲反弹趋势,本季度的总收入实现了两位数同比增长。


/   作者简介   /


明天周六啦,提前祝大家周末愉快!


本篇文章转自Wgllss的博客,文章主要分享了网络请求的封装,相信会对大家有所帮助!


原文地址:

https://juejin.cn/post/7435904232597372940


/   前言   /


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


网络框架的发展:


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


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


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


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


现在是2024年,往后面发展,随着http3.0的逐渐成熟,一定会出现更好的网络请求框架 我们怎么封装一套更容易扩展的框架,而不必每次重构这一层时,改动得那么困难。


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


示例项目



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


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


/   网络层代码设计   /


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


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


    suspend fun  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  postApi(url: String, clazzR: Class<R>, header: MutableMap<String, String>? = null, body: String? = null): R


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


class HttpUrlConnectionImpl  constructor() : INetApi {
    private val gson by lazy { Gson() }

    override suspend fun  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(json, clazzR)
    }

    override suspend fun  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(json, clazzR)
    }
}


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


buildscript {
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.42'
    }
}

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


先配置:


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

里面的 android 下面添加:


kapt {
    generateStubs = true
}

在 dependencies 里面引入 Hilt 使用


//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:


@HiltAndroidApp
class MyApp : Application() {

}

5.2 在使用的Activity上面添加注解 @AndroidEntryPoint


@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 :


@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 如下:


class HttpUrlConnectionImpl @Inject constructor() : INetApi {
    private val gson by lazy { Gson() }

    override suspend fun  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(json, clazzR)
    }

    override suspend fun  postApi(url: String, clazzR: Class<R>, header: MutableMap<String, String>?, body: String?): R {
        val json = HttpUrlConnectionRequest.postData(url, header, body)
        return gson.fromJson(json, clazzR)
    }
}

5.5 新建一个 annotation :BindHttpUrlConnection 如下:


@Qualifier
@Retention(RetentionPolicy.RUNTIME)
annotation class BindHttpUrlConnection()

5.6 再建一个绑定网络请求的 abstract 修饰的类 AbstractHttp 如下:让 @BindHttpUrlConnection 和 HttpUrlConnectionImpl 在如下方法中通过注解绑定


@InstallIn(SingletonComponent::class)
@Module
abstract class AbstractHttp {


    @BindHttpUrlConnection
    @Singleton
    @Binds
    abstract fun bindHttpUrlConnection(h: HttpUrlConnectionImpl): INetApi
}

5.7 在viewModel持有的仓库类 NetRepository 的构造方法中添加 注解 @Inject,并且申明 INetApi,并且绑定注解 @BindHttpUrlConnection 如下:然后即就可以开始调用 INetApi 的方法


class NetRepository @Inject constructor(@BindHttpUrlConnection val netHttp: INetApi) {

    suspend fun getHomeList(): Flow {
        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 如下:


class OkhttpImpl @Inject constructor() : INetApi {

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

    override suspend fun  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(json, clazzR)
            } else {
                throw RuntimeException("response fail")
            }
        } catch (e: Exception) {
            throw e
        }
    }

    override suspend fun  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(response.body.toString(), clazzR)
            } else {
                throw RuntimeException("response fail")
            }
        } catch (e: Exception) {
            throw e
        }
    }
}

5.9 再建一个注解 annotation 类型的 BindOkhttp 如下:


@Qualifier
@Retention(RetentionPolicy.RUNTIME)
annotation class BindOkhttp()

5.10 在 AbstractHttp 类中添加 @BindOkhttp 绑定到 OkhttpImpl,如下:


@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来实现了,如下:


//class NetRepository @Inject constructor(@BindHttpUrlConnection val netHttp: INetApi) {
class NetRepository @Inject constructor(@BindOkhttp val netHttp: INetApi) {

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


运行执行结果截图可见:



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


/   总结   /


本文重点介绍了,怎么对网络框架扩展型封装:即怎么可以封装成快速从一套网络请求框架,切换到另一套网络请求上去


借助于 Jetpack中成员 Hilt 对其整个持有链路进行切割,简单切换绑定网络实现框架1,框架2,框架xxx等。


项目地址


github地址:https://github.com/wgllss/WXNetArchitecture


gitee地址:https://gitee.com/wgllss888/WXNetArchitecture


推荐阅读:

我的新书,《第一行代码 第3版》已出版!

Android Studio Koala Feature Drop 稳定版现已推出

Android vold(卷管理)传记


欢迎关注我的公众号

学习技术或投稿


长按上图,识别图中二维码即可关注