Android 网络请求:EasyNet(Okhttp + retrofit + flow + gson + 缓存 + 文件下载 + 文件上传 + 人性化Loading窗)
Android 网络请求:EasyNet(Okhttp + retrofit + flow + gson + 缓存 + 文件下载 + 文件上传 + 人性化Loading窗)
介绍
该模块基于 okhttp + retrofit + flow 网络库封装,集成了OkHttp、Retrofit和Kotlin Flow,提供了一套完整的网络请求解决方案。项目展示了如何在Android应用中优雅地处理网络请求,包括基本请求、接口缓存、文件上传下载、断点续传等高级功能。
核心特性
- 现代化架构:基于OkHttp + Retrofit + Kotlin Flow构建
- 双重API风格:支持Flow响应式编程和传统挂起函数两种方式
- 统一错误处理:提供一致的错误处理机制
- 文件操作支持:完整的文件上传、下载和断点续传功能
- 模块化设计:网络层高度封装,便于复用和维护
- 多种数据格式:支持JSON对象、原始字符串等多种响应格式
- 多个服务器支持:支持配置多个服务器地址,请求不同服务器数据
0. 接口准备(运行本地接口测试)
在当前目录下放置待下载的文件,文件名test.zip,命令行运行 api-server-0.0.1.jar
java -jar api-server-0.0.1.jar
将手机与电脑置于同一网络环境中,源码中更改ip地址为电脑ip
1. 模块结构
net/
├── api/ # API接口定义
│ ├── ApiService.kt # 基础API接口
│ ├── FileApiService.kt # 文件操作相关API接口
│ └── RawApiService.kt # 原始数据API接口
├── base/ # 基础类
│ └── BaseResponse.kt # 基础响应数据类
├── HttpConst.kt # 网络常量
├── HttpEngine.kt # 网络请求引擎(Repository)
└── ext.kt # 扩展函数
2. 初始化配置
引入依赖:
repositories {
...
maven(url = "https://gitee.com/laujiangtao/maven-repo/raw/main/")
...
}
dependencies {
...
implementation("me.laujiangtao.net:easynet:1.0.1")
...
}
在 MyApplication 中初始化网络服务:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 注册API服务
HttpServiceManager.addServiceFactory("apiService") {
val config = HttpConfig(HttpConst.SERVER_URL, cacheDir = cacheDir)
HttpService.create<ApiService>(config)
}
// 注册原始数据API服务
HttpServiceManager.addServiceFactory("rawApiService") {
val rawConfig = HttpConfig(HttpConst.SERVER_URL, cacheDir = cacheDir, useGsonConverter = false)
HttpService.create<RawApiService>(rawConfig)
}
// 注册文件API服务
HttpServiceManager.addServiceFactory("fileApiService") {
val fileConfig = HttpConfig(HttpConst.SERVER_URL, isDebug = false)
HttpService.create(fileConfig, FileApiService::class.java)
}
}
}
3. API接口定义
3.1 基础API (ApiService.kt)
定义了基本的网络请求接口:
interface ApiService {
@GET("/index/helloworld")
suspend fun helloWorld(): String
@GET("/index/whois")
@Cacheable(ttl = 30 * 1000, strategy = CacheStrategy.CACHE_FIRST)
suspend fun whois(@Query("domain") domain: String): BaseResponse<Whois?>?
@GET("/index/tianqi")
@Cacheable(ttl = 30 * 1000, strategy = CacheStrategy.CACHE_FIRST)
suspend fun weather(@Query("city") city: String): Tianqi
}
3.2 文件操作API (FileApiService.kt)
提供了文件上传下载相关接口:
interface FileApiService {
@Multipart
@POST("/file/uploadFiles")
suspend fun upload(@Part parts: List<MultipartBody.Part>): String?
@Streaming
@GET("/file/download")
suspend fun downloadFile(@Query("filename") fileUrl: String): ResponseBody
// ... 其他文件操作接口
}
3.3 原始数据API (RawApiService.kt)
返回原始字符串数据的接口:
interface RawApiService {
@GET("/index/meinv")
@Cacheable(ttl = 30 * 1000, strategy = CacheStrategy.CACHE_FIRST)
suspend fun pcmeinv(): String?
}
4. 网络请求引擎 (HttpEngine.kt)
HttpEngine 是网络请求的核心类,提供了两种风格的请求方法:
4.1 Flow风格请求
class HttpEngine {
// Flow风格请求方法
fun getHelloWorld(): Flow<RequestStatus<String?>> = apiCall {
apiService?.helloWorld()
}
fun whois(domain: String): Flow<RequestStatus<BaseResponse<Whois?>?>> = apiCall {
apiService?.whois(domain)
}
fun weather(city: String): Flow<RequestStatus<Tianqi?>> {
return apiCall {
apiService?.weather(city)
}
}
// 文件上传下载
fun uploadFile(request: FileUploadBean): Flow<RequestStatus<String?>> {
return apiCall {
fileApiService?.upload(request.toMultipartParts())
}
}
fun downloadFile(fileUrl: String): Flow<RequestStatus<ResponseBody?>> {
return apiCall {
fileApiService?.downloadFile(fileUrl)
}
}
}
4.2 挂起函数风格请求
class HttpEngine {
// 挂起函数风格请求方法
suspend fun getWhoIsInfo(domain: String): BaseResponse<Whois?>? {
return apiService?.whois(domain)
}
suspend fun getTianqiInfo(city: String): Tianqi? {
return apiService?.weather(city)
}
suspend fun getPcmeinvInfo(): String? {
return rawApiService?.pcmeinv()
}
}
5. ViewModel层使用
在 MyViewModel 中使用 HttpEngine:
class MyViewModel : ViewModel() {
private val repository = HttpEngine()
// Flow风格
fun getWhoIs(domain: String, cb: ((resp: RequestStatus<BaseResponse<Whois?>?>?) -> Unit)? = null) {
viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
cb?.invoke(null)
}) {
repository.whois(domain).collect { result ->
cb?.invoke(result)
}
}
}
// 挂起函数风格
suspend fun getWhoIsSuspend(domain: String): BaseResponse<Whois?>? {
return try {
withContext(Dispatchers.IO) {
repository.getWhoIsInfo(domain)
}
} catch (e: Exception) {
Log.e("UserViewModel", "获取WhoIs信息失败", e)
null
}
}
}
6. UI层调用示例
6.1 Flow风格调用 (FlowRequestActivity.kt)
class FlowRequestActivity : MyBaseActivity<ActivityFlowRequestBinding>() {
private fun setOnClick() {
binding.flowRequestBaseResponse.setOnClickListener {
viewModel.getWhoIs("xxhzm.cn") { result ->
result?.handleBaseResponse(
onDone = {
showData(it.toString())
},
onException = {
showError("异常: ${it.message}")
},
onStart = {
showProgress()
},
onEnd = {
hideProgress()
}
)
}
}
}
}
只关注成功状态
class FlowRequestActivity : MyBaseActivity<ActivityFlowRequestBinding>() {
private fun setOnClick() {
binding.flowRequestBaseResponse.setOnClickListener {
viewModel.getWhoIs("xxhzm.cn") { result ->
result?.handleBaseResponse( onDone = {
showData(it.toString())
}
)
}
}
}
}
6.2 挂起函数风格调用 (SuspendRequestActivity.kt)
class SuspendRequestActivity : MyBaseActivity<ActivitySuspendRequestBinding>() {
private fun setOnClick() {
binding.requestBaseResponse.setOnClickListener {
lifecycleScope.launch {
showProgress()
val response = viewModel.getWhoIsSuspend("xxhzm.cn")
showData(response.toString())
}
}
}
}
7. 文件操作
7.1 文件上传 (FileOperationActivity.kt)
private fun uploadFile(fileUri: MutableList<Uri?>) {
try {
val files = fileUri.mapNotNull { fileUri ->
val file = getFileFromUri(fileUri!!)
// ... 文件处理
file
}
val params = mutableMapOf<String, String>()
params["param1"] = "value1"
params["param2"] = "value2"
viewModel.uploadFile(files, params, "files") {
handlerData(it as RequestStatus)
}
} catch (e: Exception) {
binding.result.append("文件处理失败: ${e.message}\n")
}
}
7.2 文件下载
private fun startDownload(isResume: Boolean) {
val fileUrl = "test.zip"
if (isResume) {
// 断点续传下载
viewModel.downloadFileWithResume(fileUrl, 128) {
handlerDownload(it as RequestStatus)
}
} else {
// 普通下载
viewModel.downloadFile(fileUrl) {
handlerDownload(it as RequestStatus)
}
}
}
8. 缓存机制
通过 @Cacheable 注解实现请求缓存:
@Cacheable(
ttl = 30 * 1000, // 缓存时间30秒
strategy = CacheStrategy.CACHE_FIRST, // 缓存策略
includeQueryParams = true // 包含查询参数
)
suspend fun whois(@Query("domain") domain: String): BaseResponse<Whois?>?
9. 异常处理
通过 CoroutineExceptionHandler 和 RequestStatus 处理请求状态:
result.handle(
onDone = {
showData(it.toString())
},
onException = { e ->
showError("异常: ${e.message}")
},
onStart = {
showProgress()
},
onEnd = {
hideProgress()
}
)
10. 使用建议
- 选择合适的请求风格:简单请求可使用挂起函数风格,复杂状态处理建议使用Flow风格
- 合理使用缓存:对不常变化的数据添加缓存注解提高性能
- 异常处理:在ViewModel和UI层都要做好异常处理
- 文件操作:注意文件IO操作应在后台线程执行
- 内存管理:及时释放不需要的资源,避免内存泄漏
Demo
通过网盘分享的文件:easynet.zip
链接: https://pan.baidu.com/s/1BSR4vpMMKnm3pCuWQB8HHQ?pwd=fh97 提取码: fh97
浙公网安备 33010602011771号