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. 异常处理

通过 CoroutineExceptionHandlerRequestStatus 处理请求状态:

result.handle(
    onDone = {
        showData(it.toString())
    },
    onException = { e ->
        showError("异常: ${e.message}")
    },
    onStart = {
        showProgress()
    },
    onEnd = {
        hideProgress()
    }
)

10. 使用建议

  1. 选择合适的请求风格:简单请求可使用挂起函数风格,复杂状态处理建议使用Flow风格
  2. 合理使用缓存:对不常变化的数据添加缓存注解提高性能
  3. 异常处理:在ViewModel和UI层都要做好异常处理
  4. 文件操作:注意文件IO操作应在后台线程执行
  5. 内存管理:及时释放不需要的资源,避免内存泄漏

Demo

通过网盘分享的文件:easynet.zip
链接: https://pan.baidu.com/s/1BSR4vpMMKnm3pCuWQB8HHQ?pwd=fh97 提取码: fh97

posted @ 2025-10-25 14:28  laujiangtao  阅读(16)  评论(0)    收藏  举报