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()
   }
}

项目结构说明

依赖技术

使用要求

  • Android API 21+
  • Kotlin 1.5+
  • Android Studio Arctic Fox或更高版本
posted @ 2025-10-18 14:27  laujiangtao  阅读(4)  评论(0)    收藏  举报