Android 网络请求:多功能网络请求库
Android 网络请求:多功能网络请求库
介绍
这是一个基于现代Android技术栈的网络请求库示例项目,集成了OkHttp、Retrofit和Kotlin Flow,提供了一套完整的网络请求解决方案。项目展示了如何在Android应用中优雅地处理网络请求,包括基本请求、接口缓存、文件上传下载、断点续传等高级功能。
核心特性
- 现代化架构:基于OkHttp + Retrofit + Kotlin Flow构建
- 双重API风格:支持Flow响应式编程和传统挂起函数两种方式
- 统一错误处理:提供一致的错误处理机制
- 文件操作支持:完整的文件上传、下载和断点续传功能
- 模块化设计:网络层高度封装,便于复用和维护
- 多种数据格式:支持JSON对象、原始字符串等多种响应格式
- 多个服务器支持:支持配置多个服务器地址,请求不同服务器数据
软件架构
app
├── base # 基础组件
├── bean # 数据模型
├── net # 网络层核心
│ ├── api # API接口定义
│ ├── base # 基础响应类
│ └── ext.kt # 扩展函数
├── ui # 界面层
└── vm # ViewModel层
功能演示
项目包含三个主要功能演示页面:
1. Flow风格请求 (FlowRequestActivity)
- 基础响应式请求(BaseResponse格式)
- 对象响应式请求(直接解析为对象)
- 字符串响应式请求(原始数据)
- 组合多个API请求
2. 挂起函数风格请求 (SuspendRequestActivity)
- 传统挂起函数方式请求
- 同步风格的数据获取
- 多请求组合处理
3. 文件操作 (FileOperationActivity)
- 文件选择和上传
- 普通文件下载
- 断点续传下载
- 文件信息获取
使用说明
0.快速引用
repositories {
...
maven(url = "https://gitee.com/laujiangtao/maven-repo/raw/main/")
...
}
dependencies {
...
implementation("me.laujiangtao.net:easynet:1.0.0")
...
}
1. 初始化网络库
在Application中初始化网络模块:
多服务器配置
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 初始化网络模块
val server1Config = HttpConfig(serverUrl = server1Url, cacheDir = cacheDir)
HttpClient.init(server1Url, server1Config)
// 初始化网络模块
val server2Config = HttpConfig(serverUrl = server2Url)
HttpClient.init(server2Config)
// 初始化网络模块
HttpClient.init(server3Url)
}
}
2. 定义API接口
/**
* @author jiangtao on 2025/9/20
* 网络请求API接口定义
* 定义了应用程序所需的各种网络请求方法
*/
interface ApiService {
/**
* 查询域名的Whois信息
* @param domain 域名参数
* @return 返回封装了Whois信息的BaseResponse对象
*/
@GET("/api/whois")
@Cacheable(
ttl = 30 * 1000,
strategy = CacheStrategy.CACHE_FIRST,
includeQueryParams = true
)
suspend fun whois(@Query("domain") domain: String): BaseResponse<Whois?>?
/**
* 查询城市天气信息
* @param city 城市名称参数
* @return 返回天气信息对象
*/
@GET("/api/weather")
suspend fun tianqi(@Query("city") city: String): Tianqi
/**
* 获取美女图片信息(示例接口)
* @return 返回任意类型的数据
*/
@GET("/api/pcmeinvpic")
suspend fun pcmeinv(): Any?
}
2.1 动态添加请求头
interface ApiService {
@Headers("X-Force-Network: true")
@GET("users")
suspend fun getUsersForceNetwork(): BaseResponse<List<User>>
}
interface ApiService {
@GET("users")
suspend fun getUsers(@Header("X-Force-Network") forceNetwork: Boolean = false): BaseResponse<List<User>>
}
// 使用时强制从网络获取
apiService.getUsers("true")
3. 创建Repository
/**
* @author jiangtao on 2025/9/20
* 网络请求仓库类
* 继承自NetRepository,提供应用程序所需的网络请求方法
* 包含Flow风格和普通挂起函数风格的网络请求方法
*/
class MyNetworkRepository : NetworkRepository {
/**
* 创建单个实例
*/
private val service = RetrofitClient.create(server1Url)
private val apiService1 = service.createService(ApiService::class.java)
private val apiService2 = service.createService<ApiService>()
//请求返回非json数据
private val apiService3 = service.createService<ApiService>(false)
/**
* 通过 HttpClient 从 Application创建的实例获取
*/
// 注入具体的API服务
private val apiService: ApiService = HttpClient.createService(server1Url)
//用于请求返回非json数据
private val rawApiService = HttpClient.createService<RawApiService>(server1Url, false)
private val fileApiService = HttpClient.createService(server1Url, FileApiService::class.java)
// ===================================================================
// Flow 风格的网络请求方法
// ===================================================================
/**
* 查询域名信息
* @param domain 域名
* @return 返回包含Whois信息的Flow
*/
fun whois(domain: String): Flow<FlowResult<BaseResponse<Whois?>?>> = apiCall {
apiService.whois(domain)
}
/**
* 查询天气信息
* @param city 城市名
* @return 返回包含天气信息的Flow
*/
fun tianqi(city: String): Flow<FlowResult<Tianqi?>> {
return apiCall {
apiService.tianqi(city)
}
}
/**
* 获取美女图片接口(示例接口)
* @return 返回图片数据的Flow
*/
fun pcmeinv(): Flow<FlowResult<String?>> {
return apiCall {
rawApiService.pcmeinv()
}
}
// ===================================================================
// 普通挂起函数风格的网络请求方法
// ===================================================================
/**
* 同步风格的挂起函数 - 查询域名信息
* @param domain 域名
* @return 返回Whois信息的BaseResponse包装对象
*/
suspend fun getWhoIsInfo(domain: String): BaseResponse<Whois?>? {
// 这里应该是实际的 API 调用
return apiService.whois(domain)
}
/**
* 同步风格的挂起函数 - 查询天气信息
* @param city 城市名
* @return 返回天气信息对象
*/
suspend fun getTianqiInfo(city: String): Tianqi? {
// 这里应该是实际的 API 调用
return apiService.tianqi(city)
}
/**
* 同步风格的挂起函数 - 获取美女图片接口
* @return 返回图片数据字符串
*/
suspend fun getPcmeinvInfo(): String? {
// 这里应该是实际的 API 调用
return rawApiService.pcmeinv()
}
suspend fun fetchMultipleDataDirect(domain: String, city: String): MultipleDataResult {
// 并行执行所有API调用
val whoisResult = apiService.whois(domain)
val tianqiResult = apiService.tianqi(city)
val pcmeinvResult = rawApiService.pcmeinv()
// 等待所有结果
val whoisData = whoisResult?.data
val tianqiData = tianqiResult
val pcmeinvData = pcmeinvResult
// 组合结果并返回
return MultipleDataResult(whoisData, tianqiData, pcmeinvData)
}
/**
* 合并多个网络请求结果的Flow方法
*/
fun fetchMultipleData(domain: String, city: String): Flow<FlowResult<MultipleDataResult?>> =
combineApiCalls(
{ getWhoIsInfo(domain) },
{ getTianqiInfo(city) },
{ getPcmeinvInfo() },
// ... 可以继续添加更多API调用
) { results ->
// 在这里处理所有结果并组合成最终数据
val data1 = results[0] as? BaseResponse<Whois?>?
val data2 = results[1] as? Tianqi
val data3 = results[2]
// ... 处理其他结果
MultipleDataResult(data1?.data, data2, data3)
}
/**
* 使用Flow方式合并多个网络请求结果
*/
fun fetchMultipleDataWithFlow(
domain: String,
city: String
): Flow<FlowResult<MultipleDataResult>> =
combineFlows(
whois(domain),
tianqi(city),
pcmeinv(),
// ... 添加更多Flow
) { results ->
// 在这里处理所有结果并组合成最终数据
val data1 = results[0] as? BaseResponse<Whois?>?
val data2 = results[1] as? Tianqi
val data3 = results[2]
// ... 处理其他结果
MultipleDataResult(data1?.data, data2, data3)
}
fun uploadFile(request: FileUploadBean): Flow<FlowResult<String?>> {
return apiCall {
fileApiService.upload(request.build())
}
}
/**
* 普通文件下载
* @param fileUrl 文件下载地址
* @return Flow<FlowResult<ResponseBody>> 返回文件流的Flow
*/
fun downloadFile(fileUrl: String): Flow<FlowResult<ResponseBody?>> {
return apiCall {
fileApiService.downloadFile(fileUrl)
}
}
/**
* 带参数的文件下载
* @param fileUrl 文件下载地址
* @param params 下载参数
* @return Flow<FlowResult<ResponseBody>> 返回文件流的Flow
*/
fun downloadFileWithParams(
fileUrl: String,
params: Map<String, String>
): Flow<FlowResult<ResponseBody?>> {
return apiCall {
fileApiService.downloadFileWithParams(fileUrl, params)
}
}
/**
* 断点续传下载
* @param fileUrl 文件下载地址
* @param startByte 开始下载的字节位置
* @return Flow<FlowResult<ResponseBody>> 返回文件流的Flow
*/
fun downloadFileWithResume(
fileUrl: String,
startByte: Long = 0
): Flow<FlowResult<ResponseBody?>> {
return if (startByte > 0) {
// 断点续传
val rangeHeader = "bytes=$startByte-"
apiCall {
fileApiService.downloadFileWithRange(fileUrl, rangeHeader)
}
} else {
// 普通下载
apiCall {
fileApiService.downloadFile(fileUrl)
}
}
}
/**
* 获取文件信息(用于断点续传前检查)
* @param fileUrl 文件地址
* @return Flow<FlowResult<Response>> 返回响应信息的Flow
*/
fun getFileInfo(fileUrl: String): Flow<FlowResult<Response?>> {
return apiCall {
fileApiService.getFileInfo(fileUrl)
}
}
}
4. 在ViewModel中使用
/**
* @author jiangtao on 2025/9/20
*/
class MyViewModel : ViewModel() {
private val repository = MyNetworkRepository()
// ===================================================================
// Flow 风格的网络请求方法
// ===================================================================
fun getWhoIs(domain: String, cb: ((resp: FlowResult<BaseResponse<Whois?>?>?) -> Unit)? = null) {
viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
cb?.invoke(null)
}) {
repository.whois(domain).collect { result ->
cb?.invoke(result)
}
}
}
fun getTianqi(city: String, cb: ((resp: FlowResult<Tianqi?>?) -> Unit)? = null) {
viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
cb?.invoke(null)
}) {
repository.tianqi(city).collect { result ->
cb?.invoke(result)
}
}
}
fun getPcmeinvpic(cb: ((resp: FlowResult<String?>?) -> Unit)? = null) {
viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
cb?.invoke(null)
}) {
repository.pcmeinv().collect { result ->
cb?.invoke(result)
}
}
}
// ===================================================================
// 普通挂起函数风格的网络请求方法
// ===================================================================
// 提供同步风格的挂起函数
suspend fun getWhoIsSuspend(domain: String): BaseResponse<Whois?>? {
return try {
// 这里使用 withContext 确保在 IO 线程执行网络请求
withContext(Dispatchers.IO) {
// 实际调用 API 获取 whois 信息
repository.getWhoIsInfo(domain)
}
} catch (e: Exception) {
// 记录错误日志
Log.e("UserViewModel", "获取WhoIs信息失败", e)
null // 返回 null 表示失败
}
}
suspend fun getTianqiSuspend(city: String): Tianqi? {
return try {
withContext(Dispatchers.IO) {
repository.getTianqiInfo(city)
}
} catch (e: Exception) {
Log.e("UserViewModel", "获取天气信息失败", e)
null
}
}
suspend fun getPcmeinvpicSuspend(): String? {
return try {
withContext(Dispatchers.IO) {
repository.getPcmeinvInfo()
}
} catch (e: Exception) {
Log.e("UserViewModel", "获取PC妹纸图片失败", e)
null
}
}
// ===================================================================
// Flow 风格的多个网络请求合并
// ===================================================================
suspend fun fetchMultipleDataDirect(domain: String, city: String): MultipleDataResult? {
return try {
withContext(Dispatchers.IO) {
repository.fetchMultipleDataDirect(domain, city)
}
} catch (e: Exception) {
Log.e("UserViewModel", "获取PC妹纸图片失败", e)
null
}
}
// 使用 combineResults 方法合并多个网络请求
fun fetchMultipleData(
domain: String,
city: String,
cb: ((resp: FlowResult<MultipleDataResult?>?) -> Unit)? = null
) {
viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
cb?.invoke(null)
}) {
repository.fetchMultipleData(domain, city).collect { result ->
cb?.invoke(result)
}
}
}
// 使用 combineResultsWithFlow 方法合并多个网络请求
fun fetchMultipleDataWithFlow(
domain: String,
city: String,
cb: ((resp: FlowResult<MultipleDataResult?>?) -> Unit)? = null
) {
viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
cb?.invoke(null)
}) {
repository.fetchMultipleDataWithFlow(domain, city).collect { result ->
cb?.invoke(result)
}
}
}
fun uploadFile(file: File, params: Map<String, String>, cb: ((resp: FlowResult<String?>?) -> Unit)? = null) {
viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
cb?.invoke(null)
}) {
val uploadBean = FileUploadBean(file, params)
repository.uploadFile(uploadBean).collect { result ->
cb?.invoke(result)
}
}
}
fun downloadFile(fileUrl: String, cb: ((resp: FlowResult<ResponseBody?>?) -> Unit)? = null) {
viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
cb?.invoke(null)
}) {
repository.downloadFile(fileUrl).collect { result ->
cb?.invoke(result)
}
}
}
fun downloadFileWithParams(
fileUrl: String,
params: Map<String, String>,
cb: ((resp: FlowResult<ResponseBody?>?) -> Unit)? = null
) {
viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
cb?.invoke(null)
}) {
repository.downloadFileWithParams(fileUrl, params).collect { result ->
cb?.invoke(result)
}
}
}
fun downloadFileWithResume(
fileUrl: String,
startByte: Long = 0,
cb: ((resp: FlowResult<ResponseBody?>?) -> Unit)? = null
) {
viewModelScope.launch(CoroutineExceptionHandler { _, _ ->
cb?.invoke(null)
}) {
repository.downloadFileWithResume(fileUrl, startByte).collect { result ->
cb?.invoke(result)
}
}
}
}
5. 在Activity中调用
Flow风格网络请求
/**
* @author jiangtao on 2025/9/20
*/
class FlowRequestActivity : MyBaseActivity<ActivityFlowRequestBinding>() {
private val TAG = "FlowRequestActivity"
private lateinit var viewModel: MyViewModel
private var time: Long = 0
private var cost: Long = 0
override fun setup(savedInstanceState: Bundle?) {
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
binding.result.movementMethod = ScrollingMovementMethod.getInstance()
setOnClick()
}
private fun setOnClick() {
// ===================================================================
// Flow 风格的网络请求方法
// ===================================================================
binding.flowRequestBaseResponse.setOnClickListener {
viewModel.getWhoIs("xxhzm.cn") {
handlerData(it as FlowResult)
}
}
binding.flowRequestObject.setOnClickListener {
viewModel.getTianqi("上海") {
handlerData(it as FlowResult)
}
}
binding.flowRequestString.setOnClickListener {
viewModel.getPcmeinvpic() {
handlerData(it as FlowResult)
}
}
binding.flowRequestCombinedData1.setOnClickListener {
viewModel.fetchMultipleData("xxhzm.cn", "上海") {
handlerData(it as FlowResult)
}
}
binding.flowRequestCombinedData2.setOnClickListener {
viewModel.fetchMultipleDataWithFlow("xxhzm.cn", "上海") {
handlerData(it as FlowResult)
}
}
}
/**
* 简便起见,返回数据统一处理
*/
override fun <T> handlerData(result: FlowResult<T?>) {
result.handle(
onSuccess = {
hideProgress()
showData(it.toString())
},
onError = { code, message ->
hideProgress()
showError("错误: $code, $message")
},
onException = { e ->
hideProgress()
showError("异常: ${e.message}")
},
onPrepare = {
showProgress()
}
)
}
private fun showProgress() {
time = System.currentTimeMillis()
Log.i(TAG, "showProgress")
Toast.makeText(this, "showProgress", Toast.LENGTH_SHORT).show()
}
private fun hideProgress() {
cost = System.currentTimeMillis() - time
Log.i(TAG, "hideProgress")
Toast.makeText(this, "hideProgress", Toast.LENGTH_SHORT).show()
}
private fun showData(str: String?) {
Log.i(TAG, "showData: $str")
binding.result.text = "耗时: $cost ms\n"
val jsonObject: JSONObject = JSONObject(str)
binding.result.append(jsonObject.toString(4).toString())
}
private fun showError(message: String) {
Log.e(TAG, "showError: $message")
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
挂起函数风格网络请求
/**
* @author jiangtao on 2025/9/20
*/
class SuspendRequestActivity : MyBaseActivity<ActivitySuspendRequestBinding>() {
private val TAG = "SuspendRequestActivity"
private lateinit var viewModel: MyViewModel
private var time: Long = 0
private var cost: Long = 0
override fun setup(savedInstanceState: Bundle?) {
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
binding.result.movementMethod = ScrollingMovementMethod.getInstance()
setOnClick()
}
private fun setOnClick() {
// ===================================================================
// 普通挂起函数风格的网络请求方法
// ===================================================================
binding.requestBaseResponse.setOnClickListener {
lifecycleScope.launch {
showProgress()
val response = viewModel.getWhoIsSuspend("xxhzm.cn")
showData(response.toString())
}
}
binding.requestObject.setOnClickListener {
lifecycleScope.launch {
showProgress()
val response = viewModel.getTianqiSuspend("上海")
showData(response.toString())
}
}
binding.requestString.setOnClickListener {
lifecycleScope.launch {
showProgress()
val response = viewModel.getPcmeinvpicSuspend()
showData(response)
}
}
binding.requestCombinedData.setOnClickListener {
lifecycleScope.launch {
showProgress()
val response = viewModel.fetchMultipleDataDirect("xxhzm.cn", "上海")
showData(response.toString())
}
}
}
private fun showProgress() {
time = System.currentTimeMillis()
Log.i(TAG, "showProgress")
Toast.makeText(this, "showProgress", Toast.LENGTH_SHORT).show()
}
private fun hideProgress() {
cost = System.currentTimeMillis() - time
Log.i(TAG, "hideProgress")
Toast.makeText(this, "hideProgress", Toast.LENGTH_SHORT).show()
}
private fun showData(str: String?) {
hideProgress()
Log.i(TAG, "showData: $str")
binding.result.text = "耗时: $cost ms\n"
val jsonObject: JSONObject = JSONObject(str)
binding.result.append(jsonObject.toString(4).toString())
}
}
文件操作功能
文件上传
val uploadBean = FileUploadBean(file, params)
repository.uploadFile(uploadBean).collect { result ->
// 处理上传结果
}
文件下载
/**
* @author jiangtao on 2025/9/20
*/
class FileOperationActivity : MyBaseActivity<ActivityFileOperationBinding>() {
private val TAG = "FileOperationActivity"
private lateinit var viewModel: MyViewModel
private var selectedFileUri: Uri? = null
private lateinit var filePickerLauncher: ActivityResultLauncher<Intent>
private var time: Long = 0
private var cost: Long = 0
override fun setup(savedInstanceState: Bundle?) {
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
binding.result.movementMethod = ScrollingMovementMethod.getInstance()
registerFilePickerLauncher()
setOnClick()
}
/**
* 注册文件选择器的回调
*/
private fun registerFilePickerLauncher() {
filePickerLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val data = result.data
if (data != null && data.data != null) {
selectedFileUri = data.data
displaySelectedFileInfo(selectedFileUri!!)
binding.uploadFileButton.isEnabled = true
}
}
}
}
private fun setOnClick() {
binding.selectFileButton.setOnClickListener { v -> openFilePicker() }
binding.uploadFileButton.setOnClickListener { v -> uploadFile(selectedFileUri) }
// 添加下载按钮的点击事件
binding.downloadFileButton.setOnClickListener {
startDownload(false) // 普通下载
}
binding.resumeDownloadButton.setOnClickListener {
startDownload(true) // 断点续传下载
}
}
private fun openFilePicker() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*" // 可以选择所有类型的文件
intent.addCategory(Intent.CATEGORY_OPENABLE)
filePickerLauncher.launch(intent)
}
private fun displaySelectedFileInfo(fileUri: Uri) {
try {
val cursor = contentResolver.query(fileUri, null, null, null, null)
cursor?.use {
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE)
if (it.moveToFirst()) {
val fileName = it.getString(nameIndex)
val fileSize = it.getLong(sizeIndex)
binding.selectedFileInfo.text =
"文件名: $fileName\n大小: ${formatFileSize(this, fileSize)}"
}
}
} catch (e: Exception) {
// 如果无法获取文件信息,则只显示 URI
binding.selectedFileInfo.text = "已选择文件: $fileUri"
}
}
/**
* 通过 Uri 获取文件对象
*/
private fun getFileFromUri(uri: Uri): File? {
return try {
val cursor = contentResolver.query(uri, null, null, null, null)
val fileName = cursor?.use {
if (it.moveToFirst()) {
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
it.getString(nameIndex)
} else {
"unknown_file"
}
} ?: "unknown_file"
// 创建临时文件
val tempFile = File(cacheDir, fileName)
// 将 Uri 指向的内容复制到临时文件
contentResolver.openInputStream(uri)?.use { inputStream ->
FileOutputStream(tempFile).use { outputStream ->
inputStream.copyTo(outputStream)
}
}
tempFile
} catch (e: Exception) {
e.printStackTrace()
null
}
}
private fun uploadFile(fileUri: Uri?) {
if (fileUri == null) {
binding.result.text = "请先选择文件\n"
return
}
binding.result.text = "开始处理文件...\n"
try {
// 获取文件对象
val file = getFileFromUri(fileUri)
if (file != null && file.exists()) {
binding.result.append("文件获取成功!\n")
binding.result.append("文件名: ${file.name}\n")
binding.result.append("文件路径: ${file.absolutePath}\n")
binding.result.append(
"文件大小: ${
android.text.format.Formatter.formatFileSize(
this,
file.length()
)
}\n"
)
// 在这里可以使用 file 对象进行实际的上传操作
// 例如使用 OkHttp、Retrofit 等网络库上传文件
binding.result.append("文件准备就绪,可以进行上传操作\n")
val params = mutableMapOf<String, String>()
params["param1"] = "value1"
params["param2"] = "value2"
viewModel.uploadFile(file, params) {
handlerData(it as FlowResult)
}
} else {
binding.result.append("文件获取失败\n")
}
} catch (e: Exception) {
binding.result.append("文件处理失败: ${e.message}\n")
}
}
/**
* 开始下载文件
* @param isResume 是否断点续传下载
*/
private fun startDownload(isResume: Boolean) {
val fileUrl = "https://example.com/file.zip"
if (isResume) {
// 断点续传下载
viewModel.downloadFileWithResume(fileUrl, 128) {
handlerDownload(it as FlowResult)
}
} else {
// 普通下载
viewModel.downloadFile(fileUrl) {
handlerDownload(it as FlowResult)
}
}
}
private fun handlerDownload(result: FlowResult<ResponseBody?>) {
result.handle(
onSuccess = {
binding.result.text = "下载成功!\n"
val responseBody = it
responseBody?.saveFile(".", "file.zip")
},
onError = { code, message ->
binding.result.text = "下载失败!\n${code}, ${message}"
},
onPrepare = {
binding.result.text = "正在下载...\n"
},
onException = { e -> binding.result.text = "下载异常!\n$e" }
)
}
/**
* 简便起见,返回数据统一处理
*/
override fun <T> handlerData(result: FlowResult<T?>) {
result.handle(
onSuccess = {
hideProgress()
showData(it.toString())
},
onError = { code, message ->
hideProgress()
showError("错误: $code, $message")
},
onException = { e ->
hideProgress()
showError("异常: ${e.message}")
},
onPrepare = {
showProgress()
}
)
}
private fun showProgress() {
Log.i(TAG, "showProgress")
Toast.makeText(this, "showProgress", Toast.LENGTH_SHORT).show()
}
private fun hideProgress() {
Log.i(TAG, "hideProgress")
Toast.makeText(this, "hideProgress", Toast.LENGTH_SHORT).show()
}
private fun showData(str: String?) {
Log.i(TAG, "showData: $str")
val jsonObject: JSONObject = JSONObject(str)
binding.result.text = jsonObject.toString(4)
}
private fun showError(message: String) {
Log.e(TAG, "showError: $message")
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
项目结构说明
- ApiService.kt - 标准API接口定义
- RawApiService.kt - 原始数据API接口定义
- FileApiService.kt - 文件操作相关API接口
- MyNetworkRepository.kt - 网络请求仓库实现
- MyViewModel.kt - ViewModel层,连接UI和数据
- BaseResponse.kt - 统一响应数据结构
- ext.kt - 扩展函数
依赖技术
- OkHttp - HTTP客户端
- Retrofit - REST API客户端
- Kotlin Coroutines - 协程支持
- Kotlin Flow - 响应式流处理
使用要求
- Android API 21+
- Kotlin 1.5+
- Android Studio Arctic Fox或更高版本