Android网络框架封装 ---> Retrofit + OkHttp + 协程 + LiveData + 断点续传 + 多线程下载 + 进度框交互

目录

[1. 项目概述](#1. 项目概述)

[1.1 项目背景](#1.1 项目背景)

[1.2 技术选型](#1.2 技术选型)

[1.3 项目目标](#1.3 项目目标)

[2. 技术架构](#2. 技术架构)

[2.1 分层架构](#2.1 分层架构)

[2.2 设计原则](#2.2 设计原则)

[3. 核心功能实现](#3. 核心功能实现)

[3.1 网络配置管理器](#3.1 网络配置管理器)

[3.2 拦截器体系](#3.2 拦截器体系)

[3.3 API接口定义](#3.3 API接口定义)

[3.4 数据模型](#3.4 数据模型)

[4. 多BaseUrl管理](#4. 多BaseUrl管理)

[4.1 依赖注入配置](#4.1 依赖注入配置)

[4.2 Repository层实现](#4.2 Repository层实现)

[4.3 ViewModel层实现](#4.3 ViewModel层实现)

[5. 断点续传实现](#5. 断点续传实现)

[5.1 断点续传原理](#5.1 断点续传原理)

[5.2 断点续传管理器](#5.2 断点续传管理器)

[5.3 文件管理器](#5.3 文件管理器)

[6. 多线程下载](#6. 多线程下载)

[6.1 多线程下载原理](#6.1 多线程下载原理)

[6.2 多线程下载管理器](#6.2 多线程下载管理器)

[6.3 下载状态管理](#6.3 下载状态管理)

[7. 进度框交互](#7. 进度框交互)

[7.1 进度框状态管理](#7.1 进度框状态管理)

[7.2 自定义进度对话框](#7.2 自定义进度对话框)

[7.3 进度框布局文件](#7.3 进度框布局文件)

[7.4 Activity/Fragment使用示例](#7.4 Activity/Fragment使用示例)

[8. 错误处理机制](#8. 错误处理机制)

[8.1 统一错误处理](#8.1 统一错误处理)

[8.2 网络状态监听](#8.2 网络状态监听)

[9. 性能优化](#9. 性能优化)

[9.1 连接池与缓存优化](#9.1 连接池与缓存优化)

[9.2 内存优化](#9.2 内存优化)

[9.3 并发控制](#9.3 并发控制)

[10. 最佳实践](#10. 最佳实践)

[10.1 代码组织](#10.1 代码组织)

[10.2 错误处理](#10.2 错误处理)

[10.3 性能优化](#10.3 性能优化)

[10.4 用户体验](#10.4 用户体验)

[10.5 扩展性](#10.5 扩展性)

[11. 扩展功能](#11. 扩展功能)

[11.1 上传功能](#11.1 上传功能)

[11.2 任务队列](#11.2 任务队列)

[11.3 数据库持久化](#11.3 数据库持久化)

[12. 总结](#12. 总结)

[12.1 技术亮点](#12.1 技术亮点)

[12.2 应用场景](#12.2 应用场景)

[12.3 团队协作](#12.3 团队协作)

[12.4 未来扩展](#12.4 未来扩展)

[13. 常见问题与答疑](#13. 常见问题与答疑)

[13.1 断点续传相关](#13.1 断点续传相关)

[13.2 多线程下载相关](#13.2 多线程下载相关)

[13.3 性能优化相关](#13.3 性能优化相关)

[13.4 用户体验相关](#13.4 用户体验相关)

[14. 结语](#14. 结语)


1. 项目概述

1.1 项目背景

随着移动应用功能的不断丰富,网络通信需求日益复杂:

  • 多服务器架构(API、CDN、上传、下载服务分离)
  • 大文件下载/上传需要断点续传
  • 用户体验要求高(进度可视化、操作可控)
  • 代码可维护性和扩展性要求

1.2 技术选型

技术 版本 作用
Retrofit 2.9.0 HTTP客户端,API接口定义
OkHttp 4.9.0 底层网络库,拦截器支持
Kotlin协程 1.6.0 异步处理,替代回调
LiveData 2.5.0 响应式数据流
Hilt 2.44 依赖注入
Room 2.5.0 本地数据库缓存

1.3 项目目标

  • 统一网络请求管理
  • 支持多BaseUrl动态切换
  • 实现断点续传和多线程下载
  • 提供友好的进度框交互
  • 完善的错误处理和重试机制

2. 技术架构

2.1 分层架构

UI层(Activity/Fragment/Compose)

ViewModel层(业务逻辑、状态管理、进度反馈)

Repository层(数据获取、缓存、网络请求)

Network层(Retrofit+OkHttp+拦截器+多BaseUrl+下载管理)

Data层(数据模型、数据库、缓存)

2.2 设计原则

  • 单一职责原则:每个类只负责一个功能
  • 依赖倒置原则:高层模块不依赖低层模块
  • 开闭原则:对扩展开放,对修改关闭
  • 接口隔离原则:使用多个专门的接口

3. 核心功能实现

3.1 网络配置管理器

Kotlin 复制代码
@Singleton

class NetworkManager @Inject constructor(

    private val context: Context

) {

    private val networkConfigs = mutableMapOf<NetworkType, NetworkConfig>()

    private val okHttpClients = mutableMapOf<NetworkType, OkHttpClient>()

    private val retrofitInstances = mutableMapOf<NetworkType, Retrofit>()

    

    enum class NetworkType {

        API_SERVER,      // 主API服务器

        CDN_SERVER,      // CDN服务器

        UPLOAD_SERVER,   // 上传服务器

        DOWNLOAD_SERVER  // 下载服务器

    }

    

    data class NetworkConfig(

        val baseUrl: String,

        val timeout: Long = 30L,

        val enableLogging: Boolean = true,

        val enableAuth: Boolean = true,

        val customHeaders: Map<String, String> = emptyMap()

    )

    

    init {

        initializeNetworkConfigs()

    }

    

    private fun initializeNetworkConfigs() {

        networkConfigs[NetworkType.API_SERVER] = NetworkConfig(

            baseUrl = "https://api.example.com/",

            timeout = 30L,

            enableLogging = true,

            enableAuth = true,

            customHeaders = mapOf(

                "Accept" to "application/json",

                "User-Agent" to "AndroidApp/1.0"

            )

        )

        

        networkConfigs[NetworkType.CDN_SERVER] = NetworkConfig(

            baseUrl = "https://cdn.example.com/",

            timeout = 60L,

            enableLogging = false,

            enableAuth = false,

            customHeaders = mapOf(

                "Cache-Control" to "max-age=3600"

            )

        )

        

        networkConfigs[NetworkType.UPLOAD_SERVER] = NetworkConfig(

            baseUrl = "https://upload.example.com/",

            timeout = 120L,

            enableLogging = true,

            enableAuth = true,

            customHeaders = mapOf(

                "Content-Type" to "multipart/form-data"

            )

        )

        

        networkConfigs[NetworkType.DOWNLOAD_SERVER] = NetworkConfig(

            baseUrl = "https://download.example.com/",

            timeout = 300L,

            enableLogging = false,

            enableAuth = false,

            customHeaders = mapOf(

                "Accept-Ranges" to "bytes"

            )

        )

    }

    

    fun getOkHttpClient(type: NetworkType): OkHttpClient {

        return okHttpClients.getOrPut(type) {

            createOkHttpClient(type)

        }

    }

    

    fun getRetrofit(type: NetworkType): Retrofit {

        return retrofitInstances.getOrPut(type) {

            createRetrofit(type)

        }

    }

    

    private fun createOkHttpClient(type: NetworkType): OkHttpClient {

        val config = networkConfigs[type] ?: throw IllegalArgumentException("Unknown network type: $type")

        

        return OkHttpClient.Builder().apply {

            connectTimeout(config.timeout, TimeUnit.SECONDS)

            readTimeout(config.timeout, TimeUnit.SECONDS)

            writeTimeout(config.timeout, TimeUnit.SECONDS)

            

            if (config.enableLogging) {

                addInterceptor(LoggingInterceptor())

            }

            

            if (config.enableAuth) {

                addInterceptor(AuthInterceptor())

            }

            

            if (config.customHeaders.isNotEmpty()) {

                addInterceptor(CustomHeadersInterceptor(config.customHeaders))

            }

            

            addInterceptor(RetryInterceptor())

            addInterceptor(CacheInterceptor(context))

            

        }.build()

    }

    

    private fun createRetrofit(type: NetworkType): Retrofit {

        val config = networkConfigs[type] ?: throw IllegalArgumentException("Unknown network type: $type")

        

        return Retrofit.Builder()

            .baseUrl(config.baseUrl)

            .client(getOkHttpClient(type))

            .addConverterFactory(GsonConverterFactory.create())

            .build()

    }

}

3.2 拦截器体系

Kotlin 复制代码
// 日志拦截器

class LoggingInterceptor : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {

        val request = chain.request()

        Log.d("Network", "Request: ${request.url}")

        

        val response = chain.proceed(request)

        Log.d("Network", "Response: ${response.code}")

        

        return response

    }

}

// 认证拦截器

class AuthInterceptor : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {

        val originalRequest = chain.request()

        val newRequest = originalRequest.newBuilder()

            .addHeader("Authorization", "Bearer $token")

            .addHeader("Content-Type", "application/json")

            .build()

        return chain.proceed(newRequest)

    }

}

// 自定义头部拦截器

class CustomHeadersInterceptor(

    private val headers: Map<String, String>

) : Interceptor {

    

    override fun intercept(chain: Interceptor.Chain): Response {

        val originalRequest = chain.request()

        val newRequest = originalRequest.newBuilder().apply {

            headers.forEach { (key, value) ->

                addHeader(key, value)

            }

        }.build()

        return chain.proceed(newRequest)

    }

}

// 重试拦截器

class RetryInterceptor : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {

        val request = chain.request()

        var response: Response? = null

        var exception: Exception? = null

        

        repeat(3) { attempt ->

            try {

                response = chain.proceed(request)

                if (response?.isSuccessful == true) {

                    return response!!

                }

            } catch (e: Exception) {

                exception = e

                if (attempt == 2) throw e

            }

        }

        

        return response ?: throw exception ?: Exception("Request failed")

    }

}

// 缓存拦截器

class CacheInterceptor(context: Context) : Interceptor {

    private val cache = Cache(

        directory = File(context.cacheDir, "http_cache"),

        maxSize = 10 * 1024 * 1024 // 10MB

    )

    

    override fun intercept(chain: Interceptor.Chain): Response {

        val request = chain.request()

        val response = chain.proceed(request)

        

        return response.newBuilder()

            .header("Cache-Control", "public, max-age=3600")

            .build()

    }

}

3.3 API接口定义

Kotlin 复制代码
// 主API服务

interface ApiService {

    @GET("users")

    suspend fun getUsers(): Response<List<User>>

    

    @POST("users")

    suspend fun createUser(@Body user: User): Response<User>

    

    @GET("posts/{id}")

    suspend fun getPost(@Path("id") id: Int): Response<Post>

}

// CDN服务

interface CdnService {

    @GET("images/{imageId}")

    suspend fun getImage(@Path("imageId") imageId: String): Response<ResponseBody>

    

    @GET("videos/{videoId}")

    suspend fun getVideo(@Path("videoId") videoId: String): Response<ResponseBody>

}

// 上传服务

interface UploadService {

    @Multipart

    @POST("upload")

    suspend fun uploadFile(

        @Part file: MultipartBody.Part,

        @Part("description") description: RequestBody

    ): Response<UploadResponse>

    

    @Multipart

    @POST("upload/multiple")

    suspend fun uploadMultipleFiles(

        @Part files: List<MultipartBody.Part>

    ): Response<UploadResponse>

}

// 下载服务

interface DownloadService {

    @Streaming

    @GET

    suspend fun downloadFile(@Url url: String): Response<ResponseBody>

    

    @HEAD

    suspend fun getFileInfo(@Url url: String): Response<Unit>

}

3.4 数据模型

Kotlin 复制代码
// 用户模型

data class User(

    val id: Int,

    val name: String,

    val email: String,

    val avatar: String? = null

)

// 帖子模型

data class Post(

    val id: Int,

    val title: String,

    val content: String,

    val userId: Int,

    val createdAt: String

)

// 上传响应

data class UploadResponse(

    val success: Boolean,

    val url: String?,

    val message: String?

)

// 统一响应格式

data class ApiResponse<T>(

    val code: Int,

    val message: String,

    val data: T?

)

4. 多BaseUrl管理

4.1 依赖注入配置

Kotlin 复制代码
@Module

@InstallIn(SingletonComponent::class)

object NetworkModule {

    

    @Provides

    @Singleton

    fun provideNetworkManager(@ApplicationContext context: Context): NetworkManager {

        return NetworkManager(context)

    }

    

    @Provides

    @Singleton

    fun provideMultiThreadDownloader(

        networkManager: NetworkManager,

        fileManager: FileManager

    ): MultiThreadDownloader {

        return MultiThreadDownloader(networkManager, fileManager)

    }

    

    @Provides

    @Singleton

    fun provideFileManager(@ApplicationContext context: Context): FileManager {

        return FileManager(context)

    }

    

    // 提供不同类型的API服务

    @Provides

    @Singleton

    fun provideApiService(networkManager: NetworkManager): ApiService {

        return networkManager.getRetrofit(NetworkType.API_SERVER).create(ApiService::class.java)

    }

    

    @Provides

    @Singleton

    fun provideCdnService(networkManager: NetworkManager): CdnService {

        return networkManager.getRetrofit(NetworkType.CDN_SERVER).create(CdnService::class.java)

    }

    

    @Provides

    @Singleton

    fun provideUploadService(networkManager: NetworkManager): UploadService {

        return networkManager.getRetrofit(NetworkType.UPLOAD_SERVER).create(UploadService::class.java)

    }

    

    @Provides

    @Singleton

    fun provideDownloadService(networkManager: NetworkManager): DownloadService {

        return networkManager.getRetrofit(NetworkType.DOWNLOAD_SERVER).create(DownloadService::class.java)

    }

}

4.2 Repository层实现

Kotlin 复制代码
class UserRepository @Inject constructor(

    private val apiService: ApiService,

    private val userDao: UserDao

) {

    suspend fun getUsers(): Result<List<User>> {

        return try {

            // 先尝试从缓存获取

            val cachedUsers = userDao.getAllUsers()

            if (cachedUsers.isNotEmpty()) {

                return Result.success(cachedUsers)

            }

            

            // 从网络获取

            val response = apiService.getUsers()

            if (response.isSuccessful) {

                val users = response.body() ?: emptyList()

                // 缓存到数据库

                userDao.insertUsers(users)

                Result.success(users)

            } else {

                Result.failure(Exception("Network error: ${response.code()}"))

            }

        } catch (e: Exception) {

            Result.failure(e)

        }

    }

    

    suspend fun createUser(user: User): Result<User> {

        return try {

            val response = apiService.createUser(user)

            if (response.isSuccessful) {

                val createdUser = response.body()!!

                // 更新本地缓存

                userDao.insertUser(createdUser)

                Result.success(createdUser)

            } else {

                Result.failure(Exception("Create user failed"))

            }

        } catch (e: Exception) {

            Result.failure(e)

        }

    }

}

4.3 ViewModel层实现

Kotlin 复制代码
 

@HiltViewModel

class UserViewModel @Inject constructor(

    private val userRepository: UserRepository

) : ViewModel() {

    

    private val _users = MutableLiveData<List<User>>()

    val users: LiveData<List<User>> = _users

    

    private val _loading = MutableLiveData<Boolean>()

    val loading: LiveData<Boolean> = _loading

    

    private val _error = MutableLiveData<String>()

    val error: LiveData<String> = _error

    

    fun loadUsers() {

        viewModelScope.launch {

            _loading.value = true

            _error.value = null

            

            userRepository.getUsers()

                .onSuccess { users ->

                    _users.value = users

                }

                .onFailure { exception ->

                    _error.value = exception.message

                }

            

            _loading.value = false

        }

    }

    

    fun createUser(user: User) {

        viewModelScope.launch {

            _loading.value = true

            

            userRepository.createUser(user)

                .onSuccess { newUser ->

                    // 更新用户列表

                    val currentUsers = _users.value?.toMutableList() ?: mutableListOf()

                    currentUsers.add(newUser)

                    _users.value = currentUsers

                }

                .onFailure { exception ->

                    _error.value = exception.message

                }

            

            _loading.value = false

        }

    }

}

5. 断点续传实现

5.1 断点续传原理

断点续传的核心原理是HTTP的Range请求头:

  1. 检查服务器支持:通过HEAD请求检查Accept-Ranges: bytes
  1. 获取文件大小:通过Content-Length头获取文件总大小
  1. 记录已下载:保存已下载的字节数
  1. Range请求:使用Range: bytes=已下载字节数-继续下载
  1. 追加写入:将新下载的内容追加到临时文件

5.2 断点续传管理器

Kotlin 复制代码
@Singleton

class ResumableDownloader @Inject constructor(

    private val apiService: ApiService,

    private val fileManager: FileManager

) {

    

    // 下载任务状态

    sealed class DownloadState {

        object Idle : DownloadState()

        data class Downloading(val progress: Int, val downloadedBytes: Long, val totalBytes: Long) : DownloadState()

        data class Paused(val downloadedBytes: Long, val totalBytes: Long) : DownloadState()

        data class Completed(val file: File) : DownloadState()

        data class Error(val message: String) : DownloadState()

    }

    

    // 下载任务信息

    data class DownloadTask(

        val id: String,

        val url: String,

        val fileName: String,

        val filePath: String,

        var downloadedBytes: Long = 0,

        var totalBytes: Long = 0,

        var state: DownloadState = DownloadState.Idle

    )

    

    // 开始下载

    suspend fun downloadFile(

        url: String,

        fileName: String,

        onProgress: (Int, Long, Long) -> Unit,

        onComplete: (File) -> Unit,

        onError: (String) -> Unit

    ) {

        try {

            // 1. 检查服务器是否支持断点续传

            val rangeSupport = checkRangeSupport(url)

            if (!rangeSupport) {

                // 不支持断点续传,使用普通下载

                downloadWithoutResume(url, fileName, onProgress, onComplete, onError)

                return

            }

            

            // 2. 获取文件总大小

            val totalSize = getFileSize(url)

            

            // 3. 获取已下载大小

            val downloadedSize = fileManager.getDownloadedSize(fileName)

            

            // 4. 创建临时文件

            val tempFile = fileManager.createTempFile(fileName)

            

            // 5. 执行断点续传下载

            downloadWithResume(

                url = url,

                tempFile = tempFile,

                downloadedSize = downloadedSize,

                totalSize = totalSize,

                onProgress = onProgress,

                onComplete = { 

                    val finalFile = fileManager.renameTempFile("$fileName.tmp", fileName)

                    onComplete(finalFile)

                },

                onError = onError

            )

            

        } catch (e: Exception) {

            onError(e.message ?: "Download failed")

        }

    }

    

    // 检查服务器是否支持断点续传

    private suspend fun checkRangeSupport(url: String): Boolean {

        return try {

            val response = apiService.head(url)

            val acceptRanges = response.headers()["Accept-Ranges"]

            acceptRanges == "bytes"

        } catch (e: Exception) {

            false

        }

    }

    

    // 获取文件大小

    private suspend fun getFileSize(url: String): Long {

        val response = apiService.head(url)

        return response.headers()["Content-Length"]?.toLong() ?: -1L

    }

    

    // 断点续传下载实现

    private suspend fun downloadWithResume(

        url: String,

        tempFile: File,

        downloadedSize: Long,

        totalSize: Long,

        onProgress: (Int, Long, Long) -> Unit,

        onComplete: () -> Unit,

        onError: (String) -> Unit

    ) {

        try {

            // 创建带Range头的请求

            val request = Request.Builder()

                .url(url)

                .addHeader("Range", "bytes=$downloadedSize-")

                .build()

            

            val response = apiService.downloadWithRange(request)

            

            if (!response.isSuccessful) {

                onError("Download failed: ${response.code}")

                return

            }

            

            // 获取响应流

            val inputStream = response.body()?.byteStream()

            val outputStream = FileOutputStream(tempFile, true) // 追加模式

            

            val buffer = ByteArray(8192)

            var bytesRead: Int

            var totalDownloaded = downloadedSize

            

            while (inputStream?.read(buffer).also { bytesRead = it ?: -1 } != -1) {

                outputStream.write(buffer, 0, bytesRead)

                totalDownloaded += bytesRead

                

                // 计算进度

                val progress = ((totalDownloaded * 100) / totalSize).toInt()

                onProgress(progress, totalDownloaded, totalSize)

            }

            

            outputStream.close()

            inputStream?.close()

            

            onComplete()

            

        } catch (e: Exception) {

            onError(e.message ?: "Download failed")

        }

    }

    

    // 普通下载(不支持断点续传)

    private suspend fun downloadWithoutResume(

        url: String,

        fileName: String,

        onProgress: (Int, Long, Long) -> Unit,

        onComplete: (File) -> Unit,

        onError: (String) -> Unit

    ) {

        try {

            val response = apiService.download(url)

            val totalSize = response.body()?.contentLength() ?: -1L

            

            val tempFile = fileManager.createTempFile(fileName)

            val inputStream = response.body()?.byteStream()

            val outputStream = FileOutputStream(tempFile)

            

            val buffer = ByteArray(8192)

            var bytesRead: Int

            var totalDownloaded = 0L

            

            while (inputStream?.read(buffer).also { bytesRead = it ?: -1 } != -1) {

                outputStream.write(buffer, 0, bytesRead)

                totalDownloaded += bytesRead

                

                if (totalSize > 0) {

                    val progress = ((totalDownloaded * 100) / totalSize).toInt()

                    onProgress(progress, totalDownloaded, totalSize)

                }

            }

            

            outputStream.close()

            inputStream?.close()

            

            val finalFile = fileManager.renameTempFile("$fileName.tmp", fileName)

            onComplete(finalFile)

            

        } catch (e: Exception) {

            onError(e.message ?: "Download failed")

        }

    }

}

5.3 文件管理器

Kotlin 复制代码
@Singleton

class FileManager @Inject constructor(

    private val context: Context

) {

    

    // 获取下载目录

    fun getDownloadDirectory(): File {

        return context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)

            ?: File(context.filesDir, "downloads")

    }

    

    // 创建临时文件

    fun createTempFile(fileName: String): File {

        val downloadDir = getDownloadDirectory()

        if (!downloadDir.exists()) {

            downloadDir.mkdirs()

        }

        return File(downloadDir, "$fileName.tmp")

    }

    

    // 检查文件是否存在

    fun isFileExists(fileName: String): Boolean {

        val file = File(getDownloadDirectory(), fileName)

        return file.exists()

    }

    

    // 获取已下载的文件大小

    fun getDownloadedSize(fileName: String): Long {

        val tempFile = File(getDownloadDirectory(), "$fileName.tmp")

        return if (tempFile.exists()) tempFile.length() else 0L

    }

    

    // 重命名临时文件为最终文件

    fun renameTempFile(tempFileName: String, finalFileName: String): File {

        val tempFile = File(getDownloadDirectory(), tempFileName)

        val finalFile = File(getDownloadDirectory(), finalFileName)

        tempFile.renameTo(finalFile)

        return finalFile

    }

    

    // 删除文件

    fun deleteFile(fileName: String): Boolean {

        val file = File(getDownloadDirectory(), fileName)

        return file.delete()

    }

    

    // 获取文件大小

    fun getFileSize(fileName: String): Long {

        val file = File(getDownloadDirectory(), fileName)

        return if (file.exists()) file.length() else 0L

    }

    

    // 格式化文件大小

    fun formatFileSize(bytes: Long): String {

        return when {

            bytes < 1024 -> "$bytes B"

            bytes < 1024 * 1024 -> "${bytes / 1024} KB"

            bytes < 1024 * 1024 * 1024 -> "${bytes / (1024 * 1024)} MB"

            else -> "${bytes / (1024 * 1024 * 1024)} GB"

        }

    }

}

6. 多线程下载

6.1 多线程下载原理

多线程下载的核心思想是将大文件分割成多个小块,然后同时下载这些小块:

  1. 获取文件大小:通过HEAD请求获取文件总大小
  1. 分片计算:根据线程数计算每个分片的大小和范围
  1. 并发下载:多个协程同时下载不同的分片
  1. 进度统计:实时统计所有分片的下载进度
  1. 文件合并:所有分片下载完成后合并成完整文件

6.2 多线程下载管理器

Kotlin 复制代码
@Singleton

class MultiThreadDownloader @Inject constructor(

    private val networkManager: NetworkManager,

    private val fileManager: FileManager

) {

    

    private val downloadJobs = mutableMapOf<String, Job>()

    private val downloadTasks = mutableMapOf<String, DownloadTask>()

    

    // 下载分片信息

    data class DownloadChunk(

        val startByte: Long,

        val endByte: Long,

        val index: Int,

        var downloadedBytes: Long = 0,

        var isCompleted: Boolean = false

    )

    

    // 多线程下载配置

    data class MultiThreadConfig(

        val threadCount: Int = 3,

        val chunkSize: Long = 1024 * 1024, // 1MB per chunk

        val bufferSize: Int = 8192,

        val enableSpeedLimit: Boolean = false,

        val maxSpeed: Long = 1024 * 1024 // 1MB/s

    )

    

    // 开始多线程下载

    suspend fun startMultiThreadDownload(

        url: String,

        fileName: String,

        config: MultiThreadConfig = MultiThreadConfig(),

        onProgress: (DownloadState) -> Unit,

        onComplete: (File) -> Unit,

        onError: (String) -> Unit

    ) {

        val taskId = generateTaskId(url, fileName)

        

        try {

            // 1. 检查服务器是否支持Range请求

            val rangeSupport = checkRangeSupport(url)

            if (!rangeSupport) {

                // 不支持Range,使用单线程下载

                startSingleThreadDownload(url, fileName, onProgress, onComplete, onError)

                return

            }

            

            // 2. 获取文件大小

            val totalSize = getFileSize(url)

            if (totalSize <= 0) {

                onError("Cannot get file size")

                return

            }

            

            // 3. 创建下载任务

            val task = DownloadTask(

                id = taskId,

                url = url,

                fileName = fileName,

                totalBytes = totalSize

            )

            downloadTasks[taskId] = task

            

            // 4. 分片下载

            val chunks = createDownloadChunks(totalSize, config.chunkSize)

            val tempFiles = chunks.map { chunk ->

                File(fileManager.getDownloadDirectory(), "${fileName}_chunk_${chunk.index}")

            }

            

            // 5. 启动多线程下载

            val jobs = chunks.mapIndexed { index, chunk ->

                CoroutineScope(Dispatchers.IO).launch {

                    downloadChunk(

                        url = url,

                        chunk = chunk,

                        tempFile = tempFiles[index],

                        config = config,

                        onChunkProgress = { downloaded ->

                            updateTaskProgress(taskId, downloaded, totalSize, onProgress)

                        }

                    )

                }

            }

            

            // 6. 等待所有分片下载完成

            jobs.joinAll()

            

            // 7. 合并文件

            val finalFile = mergeChunkFiles(tempFiles, fileName)

            

            // 8. 清理临时文件

            tempFiles.forEach { it.delete() }

            

            // 9. 完成回调

            onComplete(finalFile)

            updateTaskCompleted(taskId, finalFile, onProgress)

            

        } catch (e: Exception) {

            onError(e.message ?: "Download failed")

            updateTaskError(taskId, e.message ?: "Download failed", onProgress)

        }

    }

    

    // 创建下载分片

    private fun createDownloadChunks(totalSize: Long, chunkSize: Long): List<DownloadChunk> {

        val chunks = mutableListOf<DownloadChunk>()

        var startByte = 0L

        var index = 0

        

        while (startByte < totalSize) {

            val endByte = minOf(startByte + chunkSize - 1, totalSize - 1)

            chunks.add(DownloadChunk(startByte, endByte, index))

            startByte = endByte + 1

            index++

        }

        

        return chunks

    }

    

    // 下载单个分片

    private suspend fun downloadChunk(

        url: String,

        chunk: DownloadChunk,

        tempFile: File,

        config: MultiThreadConfig,

        onChunkProgress: (Long) -> Unit

    ) {

        val client = networkManager.getOkHttpClient(NetworkType.DOWNLOAD_SERVER)

        

        val request = Request.Builder()

            .url(url)

            .addHeader("Range", "bytes=${chunk.startByte}-${chunk.endByte}")

            .build()

        

        val response = client.newCall(request).execute()

        

        if (!response.isSuccessful) {

            throw Exception("Download chunk failed: ${response.code}")

        }

        

        val inputStream = response.body?.byteStream()

        val outputStream = FileOutputStream(tempFile)

        val buffer = ByteArray(config.bufferSize)

        

        var bytesRead: Int

        var totalDownloaded = 0L

        

        while (inputStream?.read(buffer).also { bytesRead = it ?: -1 } != -1) {

            outputStream.write(buffer, 0, bytesRead)

            totalDownloaded += bytesRead

            chunk.downloadedBytes = totalDownloaded

            

            onChunkProgress(totalDownloaded)

            

            // 速度限制

            if (config.enableSpeedLimit) {

                delay(calculateDelay(bytesRead, config.maxSpeed))

            }

        }

        

        outputStream.close()

        inputStream?.close()

        chunk.isCompleted = true

    }

    

    // 合并分片文件

    private suspend fun mergeChunkFiles(chunkFiles: List<File>, fileName: String): File {

        val finalFile = File(fileManager.getDownloadDirectory(), fileName)

        val outputStream = FileOutputStream(finalFile)

        

        chunkFiles.forEach { chunkFile ->

            val inputStream = FileInputStream(chunkFile)

            inputStream.copyTo(outputStream)

            inputStream.close()

        }

        

        outputStream.close()

        return finalFile

    }

    

    // 暂停下载

    fun pauseDownload(taskId: String) {

        downloadJobs[taskId]?.cancel()

        val task = downloadTasks[taskId]

        task?.let {

            it.state = DownloadState.Paused(it.downloadedBytes, it.totalBytes)

        }

    }

    

    // 恢复下载

    fun resumeDownload(taskId: String) {

        val task = downloadTasks[taskId]

        task?.let {

            // 重新开始下载,支持断点续传

            startMultiThreadDownload(

                url = it.url,

                fileName = it.fileName,

                onProgress = { state -> /* 处理进度 */ },

                onComplete = { file -> /* 处理完成 */ },

                onError = { error -> /* 处理错误 */ }

            )

        }

    }

    

    // 取消下载

    fun cancelDownload(taskId: String) {

        downloadJobs[taskId]?.cancel()

        downloadJobs.remove(taskId)

        downloadTasks.remove(taskId)

    }

    

    // 更新任务进度

    private fun updateTaskProgress(

        taskId: String,

        downloaded: Long,

        total: Long,

        onProgress: (DownloadState) -> Unit

    ) {

        val task = downloadTasks[taskId] ?: return

        task.downloadedBytes = downloaded

        

        val progress = ((downloaded * 100) / total).toInt()

        val speed = calculateSpeed(downloaded, task.startTime)

        val remainingTime = calculateRemainingTime(downloaded, total, speed)

        

        val state = DownloadState.Downloading(progress, downloaded, total, speed, remainingTime)

        task.state = state

        onProgress(state)

    }

    

    // 计算下载速度

    private fun calculateSpeed(downloaded: Long, startTime: Long): Long {

        val elapsed = System.currentTimeMillis() - startTime

        return if (elapsed > 0) (downloaded * 1000) / elapsed else 0

    }

    

    // 计算剩余时间

    private fun calculateRemainingTime(downloaded: Long, total: Long, speed: Long): Long {

        return if (speed > 0) (total - downloaded) / speed else 0

    }

    

    // 计算延迟时间(用于速度限制)

    private fun calculateDelay(bytesRead: Int, maxSpeed: Long): Long {

        return if (maxSpeed > 0) (bytesRead * 1000) / maxSpeed else 0

    }

    

    private fun generateTaskId(url: String, fileName: String): String {

        return "${url.hashCode()}_${fileName.hashCode()}"

    }

}

6.3 下载状态管理

Kotlin 复制代码
sealed class DownloadState {

    object Idle : DownloadState()

    data class Downloading(

        val progress: Int,

        val downloadedBytes: Long,

        val totalBytes: Long,

        val speed: Long, // bytes per second

        val remainingTime: Long // seconds

    ) : DownloadState()

    data class Paused(val downloadedBytes: Long, val totalBytes: Long) : DownloadState()

    data class Completed(val file: File) : DownloadState()

    data class Error(val message: String) : DownloadState()

}

7. 进度框交互

7.1 进度框状态管理

Kotlin 复制代码
// 进度框状态

sealed class ProgressDialogState {

    object Hidden : ProgressDialogState()

    data class Loading(

        val title: String = "加载中...",

        val message: String = "请稍候",

        val progress: Int = 0,

        val maxProgress: Int = 100,

        val isIndeterminate: Boolean = true,

        val showCancelButton: Boolean = false

    ) : ProgressDialogState()

    data class Downloading(

        val title: String = "下载中...",

        val fileName: String,

        val progress: Int,

        val downloadedBytes: Long,

        val totalBytes: Long,

        val speed: String,

        val remainingTime: String,

        val showCancelButton: Boolean = true

    ) : ProgressDialogState()

    data class Uploading(

        val title: String = "上传中...",

        val fileName: String,

        val progress: Int,

        val uploadedBytes: Long,

        val totalBytes: Long,

        val speed: String,

        val remainingTime: String,

        val showCancelButton: Boolean = true

    ) : ProgressDialogState()

    data class Error(

        val title: String = "错误",

        val message: String,

        val showRetryButton: Boolean = true

    ) : ProgressDialogState()

    data class Success(

        val title: String = "完成",

        val message: String,

        val showOpenButton: Boolean = false,

        val filePath: String? = null

    ) : ProgressDialogState()

}

// 进度管理器

@Singleton

class ProgressManager @Inject constructor() {

    

    private val _progressState = MutableLiveData<ProgressDialogState>()

    val progressState: LiveData<ProgressDialogState> = _progressState

    

    private val _downloadProgress = MutableLiveData<DownloadProgress>()

    val downloadProgress: LiveData<DownloadProgress> = _downloadProgress

    

    private val _uploadProgress = MutableLiveData<UploadProgress>()

    val uploadProgress: LiveData<UploadProgress> = _uploadProgress

    

    // 下载进度数据类

    data class DownloadProgress(

        val fileName: String,

        val progress: Int,

        val downloadedBytes: Long,

        val totalBytes: Long,

        val speed: String,

        val remainingTime: String,

        val isPaused: Boolean = false

    )

    

    // 上传进度数据类

    data class UploadProgress(

        val fileName: String,

        val progress: Int,

        val uploadedBytes: Long,

        val totalBytes: Long,

        val speed: String,

        val remainingTime: String,

        val isPaused: Boolean = false

    )

    

    // 显示加载进度

    fun showLoading(

        title: String = "加载中...",

        message: String = "请稍候",

        showCancelButton: Boolean = false

    ) {

        _progressState.value = ProgressDialogState.Loading(

            title = title,

            message = message,

            showCancelButton = showCancelButton

        )

    }

    

    // 显示下载进度

    fun showDownloading(

        fileName: String,

        progress: Int,

        downloadedBytes: Long,

        totalBytes: Long,

        speed: String,

        remainingTime: String

    ) {

        _progressState.value = ProgressDialogState.Downloading(

            fileName = fileName,

            progress = progress,

            downloadedBytes = downloadedBytes,

            totalBytes = totalBytes,

            speed = speed,

            remainingTime = remainingTime

        )

        

        _downloadProgress.value = DownloadProgress(

            fileName = fileName,

            progress = progress,

            downloadedBytes = downloadedBytes,

            totalBytes = totalBytes,

            speed = speed,

            remainingTime = remainingTime

        )

    }

    

    // 显示上传进度

    fun showUploading(

        fileName: String,

        progress: Int,

        uploadedBytes: Long,

        totalBytes: Long,

        speed: String,

        remainingTime: String

    ) {

        _progressState.value = ProgressDialogState.Uploading(

            fileName = fileName,

            progress = progress,

            uploadedBytes = uploadedBytes,

            totalBytes = totalBytes,

            speed = speed,

            remainingTime = remainingTime

        )

        

        _uploadProgress.value = UploadProgress(

            fileName = fileName,

            progress = progress,

            uploadedBytes = uploadedBytes,

            totalBytes = totalBytes,

            speed = speed,

            remainingTime = remainingTime

        )

    }

    

    // 显示错误

    fun showError(

        title: String = "错误",

        message: String,

        showRetryButton: Boolean = true

    ) {

        _progressState.value = ProgressDialogState.Error(

            title = title,

            message = message,

            showRetryButton = showRetryButton

        )

    }

    

    // 显示成功

    fun showSuccess(

        title: String = "完成",

        message: String,

        showOpenButton: Boolean = false,

        filePath: String? = null

    ) {

        _progressState.value = ProgressDialogState.Success(

            title = title,

            message = message,

            showOpenButton = showOpenButton,

            filePath = filePath

        )

    }

    

    // 隐藏进度框

    fun hideProgress() {

        _progressState.value = ProgressDialogState.Hidden

    }

    

    // 格式化文件大小

    fun formatFileSize(bytes: Long): String {

        return when {

            bytes < 1024 -> "$bytes B"

            bytes < 1024 * 1024 -> "${bytes / 1024} KB"

            bytes < 1024 * 1024 * 1024 -> "${bytes / (1024 * 1024)} MB"

            else -> "${bytes / (1024 * 1024 * 1024)} GB"

        }

    }

    

    // 格式化速度

    fun formatSpeed(bytesPerSecond: Long): String {

        return when {

            bytesPerSecond < 1024 -> "$bytesPerSecond B/s"

            bytesPerSecond < 1024 * 1024 -> "${bytesPerSecond / 1024} KB/s"

            bytesPerSecond < 1024 * 1024 * 1024 -> "${bytesPerSecond / (1024 * 1024)} MB/s"

            else -> "${bytesPerSecond / (1024 * 1024 * 1024)} GB/s"

        }

    }

    

    // 格式化剩余时间

    fun formatRemainingTime(seconds: Long): String {

        return when {

            seconds < 60 -> "${seconds}秒"

            seconds < 3600 -> "${seconds / 60}分钟"

            else -> "${seconds / 3600}小时${(seconds % 3600) / 60}分钟"

        }

    }

}

7.2 自定义进度对话框

Kotlin 复制代码
class CustomProgressDialog @JvmOverloads constructor(

    context: Context,

    theme: Int = R.style.CustomProgressDialog

) : Dialog(context, theme) {

    

    private lateinit var binding: DialogProgressBinding

    private var onCancelClick: (() -> Unit)? = null

    private var onRetryClick: (() -> Unit)? = null

    private var onOpenClick: (() -> Unit)? = null

    

    init {

        initDialog()

    }

    

    private fun initDialog() {

        binding = DialogProgressBinding.inflate(layoutInflater)

        setContentView(binding.root)

        

        // 设置对话框属性

        setCancelable(false)

        setCanceledOnTouchOutside(false)

        

        // 设置窗口属性

        window?.apply {

            setLayout(

                ViewGroup.LayoutParams.MATCH_PARENT,

                ViewGroup.LayoutParams.WRAP_CONTENT

            )

            setGravity(Gravity.CENTER)

            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))

        }

        

        setupListeners()

    }

    

    private fun setupListeners() {

        binding.btnCancel.setOnClickListener {

            onCancelClick?.invoke()

        }

        

        binding.btnRetry.setOnClickListener {

            onRetryClick?.invoke()

        }

        

        binding.btnOpen.setOnClickListener {

            onOpenClick?.invoke()

        }

    }

    

    fun updateState(state: ProgressDialogState) {

        when (state) {

            is ProgressDialogState.Hidden -> {

                dismiss()

            }

            is ProgressDialogState.Loading -> {

                showLoadingState(state)

            }

            is ProgressDialogState.Downloading -> {

                showDownloadingState(state)

            }

            is ProgressDialogState.Uploading -> {

                showUploadingState(state)

            }

            is ProgressDialogState.Error -> {

                showErrorState(state)

            }

            is ProgressDialogState.Success -> {

                showSuccessState(state)

            }

        }

    }

    

    private fun showLoadingState(state: ProgressDialogState.Loading) {

        binding.apply {

            tvTitle.text = state.title

            tvMessage.text = state.message

            

            if (state.isIndeterminate) {

                progressBar.isIndeterminate = true

                progressBar.progress = 0

            } else {

                progressBar.isIndeterminate = false

                progressBar.max = state.maxProgress

                progressBar.progress = state.progress

            }

            

            tvProgress.text = "${state.progress}%"

            

            // 显示/隐藏取消按钮

            btnCancel.visibility = if (state.showCancelButton) View.VISIBLE else View.GONE

            

            // 隐藏其他按钮

            btnRetry.visibility = View.GONE

            btnOpen.visibility = View.GONE

        }

        

        if (!isShowing) show()

    }

    

    private fun showDownloadingState(state: ProgressDialogState.Downloading) {

        binding.apply {

            tvTitle.text = state.title

            tvMessage.text = state.fileName

            

            progressBar.isIndeterminate = false

            progressBar.max = 100

            progressBar.progress = state.progress

            

            tvProgress.text = "${state.progress}%"

            tvSpeed.text = state.speed

            tvRemainingTime.text = state.remainingTime

            

            // 显示取消按钮

            btnCancel.visibility = if (state.showCancelButton) View.VISIBLE else View.GONE

            

            // 隐藏其他按钮

            btnRetry.visibility = View.GONE

            btnOpen.visibility = View.GONE

        }

        

        if (!isShowing) show()

    }

    

    private fun showUploadingState(state: ProgressDialogState.Uploading) {

        binding.apply {

            tvTitle.text = state.title

            tvMessage.text = state.fileName

            

            progressBar.isIndeterminate = false

            progressBar.max = 100

            progressBar.progress = state.progress

            

            tvProgress.text = "${state.progress}%"

            tvSpeed.text = state.speed

            tvRemainingTime.text = state.remainingTime

            

            // 显示取消按钮

            btnCancel.visibility = if (state.showCancelButton) View.VISIBLE else View.GONE

            

            // 隐藏其他按钮

            btnRetry.visibility = View.GONE

            btnOpen.visibility = View.GONE

        }

        if (!isShowing) show()
    }
    
    private fun showErrorState(state: ProgressDialogState.Error) {
        binding.apply {
            tvTitle.text = state.title
            tvMessage.text = state.message
            
            // 隐藏进度条
            progressBar.visibility = View.GONE
            tvProgress.visibility = View.GONE
            tvSpeed.visibility = View.GONE
            tvRemainingTime.visibility = View.GONE
            
            // 显示重试按钮
            btnRetry.visibility = if (state.showRetryButton) View.VISIBLE else View.GONE
            
            // 隐藏其他按钮
            btnCancel.visibility = View.GONE
            btnOpen.visibility = View.GONE
        }
        
        if (!isShowing) show()
    }
    
    private fun showSuccessState(state: ProgressDialogState.Success) {
        binding.apply {
            tvTitle.text = state.title
            tvMessage.text = state.message
            
            // 隐藏进度条
            progressBar.visibility = View.GONE
            tvProgress.visibility = View.GONE
            tvSpeed.visibility = View.GONE
            tvRemainingTime.visibility = View.GONE
            
            // 显示打开按钮
            btnOpen.visibility = if (state.showOpenButton) View.VISIBLE else View.GONE
            
            // 隐藏其他按钮
            btnCancel.visibility = View.GONE
            btnRetry.visibility = View.GONE
        }
        
        if (!isShowing) show()
    }
    
    fun setOnCancelClickListener(listener: () -> Unit) {
        onCancelClick = listener
    }
    
    fun setOnRetryClickListener(listener: () -> Unit) {
        onRetryClick = listener
    }
    
    fun setOnOpenClickListener(listener: () -> Unit) {
        onOpenClick = listener
    }
}

7.3 进度框布局文件

Kotlin 复制代码
<!-- dialog_progress.xml -->

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:layout_margin="24dp"

    android:background="@drawable/bg_progress_dialog"

    android:orientation="vertical"

    android:padding="24dp">

    <!-- 标题 -->

    <TextView

        android:id="@+id/tvTitle"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="加载中..."

        android:textColor="@color/text_primary"

        android:textSize="18sp"

        android:textStyle="bold"

        android:layout_marginBottom="8dp" />

    <!-- 消息 -->

    <TextView

        android:id="@+id/tvMessage"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="请稍候"

        android:textColor="@color/text_secondary"

        android:textSize="14sp"

        android:layout_marginBottom="16dp" />

    <!-- 进度条 -->

    <ProgressBar

        android:id="@+id/progressBar"

        style="@style/Widget.AppCompat.ProgressBar.Horizontal"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:layout_marginBottom="8dp" />

    <!-- 进度文本 -->

    <TextView

        android:id="@+id/tvProgress"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="0%"

        android:textColor="@color/text_secondary"

        android:textSize="12sp"

        android:gravity="center"

        android:layout_marginBottom="8dp" />

    <!-- 速度信息 -->

    <LinearLayout

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:orientation="horizontal"

        android:layout_marginBottom="16dp">

        <TextView

            android:id="@+id/tvSpeed"

            android:layout_width="0dp"

            android:layout_height="wrap_content"

            android:layout_weight="1"

            android:text="0 KB/s"

            android:textColor="@color/text_secondary"

            android:textSize="12sp" />

        <TextView

            android:id="@+id/tvRemainingTime"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="剩余时间: --"

            android:textColor="@color/text_secondary"

            android:textSize="12sp" />

    </LinearLayout>

    <!-- 按钮容器 -->

    <LinearLayout

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:orientation="horizontal"

        android:gravity="end">

        <Button

            android:id="@+id/btnCancel"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="取消"

            android:textColor="@color/text_secondary"

            android:background="?android:attr/selectableItemBackground"

            android:visibility="gone"

            android:layout_marginEnd="8dp" />

        <Button

            android:id="@+id/btnRetry"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="重试"

            android:textColor="@color/colorPrimary"

            android:background="?android:attr/selectableItemBackground"

            android:visibility="gone"

            android:layout_marginEnd="8dp" />

        <Button

            android:id="@+id/btnOpen"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="打开"

            android:textColor="@color/colorPrimary"

            android:background="?android:attr/selectableItemBackground"

            android:visibility="gone" />

    </LinearLayout>

</LinearLayout>

7.4 Activity/Fragment使用示例

Kotlin 复制代码
 

@AndroidEntryPoint

class DownloadActivity : AppCompatActivity() {

    @Inject

    lateinit var enhancedDownloadManager: EnhancedDownloadManager

    @Inject

    lateinit var progressManager: ProgressManager

    private lateinit var progressDialog: CustomProgressDialog

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_download)

        progressDialog = CustomProgressDialog(this)

        progressDialog.setOnCancelClickListener {

            // 取消下载

            enhancedDownloadManager.cancelDownload(currentTaskId)

        }

        progressDialog.setOnRetryClickListener {

            // 重试下载

            enhancedDownloadManager.resumeDownload(currentTaskId)

        }

        progressDialog.setOnOpenClickListener {

            // 打开文件

            openDownloadedFile()

        }

        // 观察进度状态

        progressManager.progressState.observe(this) { state ->

            progressDialog.updateState(state)

        }

        // 启动下载

        findViewById<Button>(R.id.btnDownload).setOnClickListener {

            val url = "https://example.com/large-file.zip"

            val fileName = "large-file.zip"

            enhancedDownloadManager.startDownload(

                url = url,

                fileName = fileName,

                onComplete = { file -> /* 处理完成 */ },

                onError = { error -> /* 处理错误 */ }

            )

        }

    }

    private fun openDownloadedFile() {

        // 实现文件打开逻辑

    }

}

8. 错误处理机制

8.1 统一错误处理

Kotlin 复制代码
sealed class NetworkResult<T> {

    data class Success<T>(val data: T) : NetworkResult<T>()

    data class Error<T>(val message: String, val code: Int? = null) : NetworkResult<T>()

    class Loading<T> : NetworkResult<T>()

}

class NetworkBoundResource<T>(

    private val query: () -> LiveData<T>,

    private val fetch: suspend () -> T,

    private val saveFetchResult: suspend (T) -> Unit,

    private val shouldFetch: (T) -> Boolean = { true }

) {

    fun asLiveData(): LiveData<NetworkResult<T>> = liveData {

        emit(NetworkResult.Loading())

        

        val dbValue = query().value

        if (shouldFetch(dbValue)) {

            try {

                val fetchedValue = fetch()

                saveFetchResult(fetchedValue)

                emit(NetworkResult.Success(fetchedValue))

            } catch (e: Exception) {

                emit(NetworkResult.Error(e.message ?: "Unknown error"))

            }

        } else {

            emit(NetworkResult.Success(dbValue))

        }

    }

}

8.2 网络状态监听

Kotlin 复制代码
class NetworkStateMonitor @Inject constructor(

    private val context: Context

) {

    private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    

    fun isNetworkAvailable(): Boolean {

        val network = connectivityManager.activeNetwork

        val capabilities = connectivityManager.getNetworkCapabilities(network)

        return capabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true

    }

    

    fun getNetworkType(): String {

        val network = connectivityManager.activeNetwork

        val capabilities = connectivityManager.getNetworkCapabilities(network)

        

        return when {

            capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true -> "WiFi"

            capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> "Cellular"

            else -> "Unknown"

        }

    }

}

9. 性能优化

9.1 连接池与缓存优化

Kotlin 复制代码
private fun createOptimizedOkHttpClient(config: NetworkConfig): OkHttpClient {

    return OkHttpClient.Builder().apply {

        // 连接池配置

        connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES))

        

        // 超时配置

        connectTimeout(config.timeout, TimeUnit.SECONDS)

        readTimeout(config.timeout, TimeUnit.SECONDS)

        writeTimeout(config.timeout, TimeUnit.SECONDS)

        

        // 缓存配置

        cache(Cache(File(context.cacheDir, "http_cache"), 10 * 1024 * 1024))

        

        // 压缩

        addInterceptor { chain ->

            val request = chain.request().newBuilder()

                .header("Accept-Encoding", "gzip, deflate")

                .build()

            chain.proceed(request)

        }

        

    }.build()

}

9.2 内存优化

Kotlin 复制代码
class MemoryOptimizedDownloader {

    private val bufferSize = 8192

    private val maxMemoryUsage = 50 * 1024 * 1024 // 50MB

    

    suspend fun downloadWithMemoryOptimization(

        url: String,

        fileName: String,

        onProgress: (Int) -> Unit

    ) {

        val file = File(fileName)

        val totalSize = getFileSize(url)

        

        var downloadedBytes = 0L

        val buffer = ByteArray(bufferSize)

        

        val inputStream = getInputStream(url)

        val outputStream = FileOutputStream(file)

        

        try {

            while (true) {

                val bytesRead = inputStream.read(buffer)

                if (bytesRead == -1) break

                

                outputStream.write(buffer, 0, bytesRead)

                downloadedBytes += bytesRead

                

                val progress = ((downloadedBytes * 100) / totalSize).toInt()

                onProgress(progress)

                

                // 内存使用检查

                if (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() > maxMemoryUsage) {

                    System.gc()

                }

            }

        } finally {

            inputStream.close()

            outputStream.close()

        }

    }

}

9.3 并发控制

Kotlin 复制代码
class ConcurrentDownloadManager {

    private val downloadSemaphore = Semaphore(3) // 最多3个并发下载

    private val downloadJobs = mutableMapOf<String, Job>()

    

    suspend fun startDownload(

        url: String,

        fileName: String,

        onProgress: (Int) -> Unit,

        onComplete: (File) -> Unit,

        onError: (String) -> Unit

    ) {

        downloadSemaphore.acquire()

        try {

            val job = CoroutineScope(Dispatchers.IO).launch {

                try {

                    downloadFile(url, fileName, onProgress, onComplete)

                } catch (e: Exception) {

                    onError(e.message ?: "Download failed")

                } finally {

                    downloadSemaphore.release()

                }

            }

            downloadJobs[fileName] = job

        } catch (e: Exception) {

            downloadSemaphore.release()

            onError(e.message ?: "Failed to start download")

        }

    }

    

    fun cancelDownload(fileName: String) {

        downloadJobs[fileName]?.cancel()

        downloadJobs.remove(fileName)

    }

}

10. 最佳实践

10.1 代码组织

  • 分层清晰:UI、ViewModel、Repository、Network、Data各层职责明确
  • 依赖注入:使用Hilt统一管理依赖,便于测试和替换
  • 单一职责:每个类只负责一个功能,便于维护和扩展
  • 接口隔离:使用多个专门的接口,避免大而全的接口

10.2 错误处理

  • 统一错误格式:所有错误都有明确的错误码和错误信息
  • 分级处理:网络错误、业务错误、系统错误分别处理
  • 用户友好:错误信息对用户友好,便于理解和操作
  • 重试机制:关键操作支持自动重试

10.3 性能优化

  • 连接复用:使用OkHttp连接池复用连接
  • 缓存策略:合理使用HTTP缓存和本地缓存
  • 内存管理:大文件下载时分片处理,避免OOM
  • 并发控制:限制并发数量,避免资源争抢

10.4 用户体验

  • 进度反馈:所有耗时操作都有进度反馈
  • 操作可控:支持取消、暂停、恢复等操作
  • 状态清晰:用户能清楚知道当前操作的状态
  • 错误友好:错误信息对用户友好,提供解决建议

10.5 扩展性

  • 多BaseUrl支持:支持动态切换不同的服务器
  • 插件化设计:拦截器、下载器等都支持插件化扩展
  • 配置化:网络配置、下载配置等都支持动态配置
  • 版本兼容:支持不同版本的API和协议

11. 扩展功能

11.1 上传功能

Kotlin 复制代码
 

class UploadManager @Inject constructor(

    private val networkManager: NetworkManager,

    private val fileManager: FileManager

) {

    

    suspend fun uploadFile(

        file: File,

        uploadUrl: String,

        onProgress: (Int) -> Unit,

        onComplete: (UploadResponse) -> Unit,

        onError: (String) -> Unit

    ) {

        try {

            val requestBody = RequestBody.create("multipart/form-data".toMediaTypeOrNull(), file)

            val multipartBody = MultipartBody.Part.createFormData("file", file.name, requestBody)

            

            val uploadService = networkManager.getRetrofit(NetworkType.UPLOAD_SERVER)

                .create(UploadService::class.java)

            

            val response = uploadService.uploadFile(multipartBody, RequestBody.create("text/plain".toMediaTypeOrNull(), ""))

            

            if (response.isSuccessful) {

                onComplete(response.body()!!)

            } else {

                onError("Upload failed: ${response.code()}")

            }

        } catch (e: Exception) {

            onError(e.message ?: "Upload failed")

        }

    }

}

11.2 任务队列

Kotlin 复制代码
class DownloadTaskQueue @Inject constructor() {

    private val taskQueue = mutableListOf<DownloadTask>()

    private val isProcessing = AtomicBoolean(false)

    

    fun addTask(task: DownloadTask) {

        taskQueue.add(task)

        if (!isProcessing.get()) {

            processNextTask()

        }

    }

    

    private fun processNextTask() {

        if (taskQueue.isEmpty()) {

            isProcessing.set(false)

            return

        }

        

        isProcessing.set(true)

        val task = taskQueue.removeAt(0)

        

        // 执行下载任务

        // ...

        

        // 处理下一个任务

        processNextTask()

    }

}

11.3 数据库持久化

Kotlin 复制代码
@Entity(tableName = "download_tasks")

data class DownloadTaskEntity(

    @PrimaryKey val id: String,

    val url: String,

    val fileName: String,

    val filePath: String,

    val downloadedBytes: Long,

    val totalBytes: Long,

    val state: String, // 序列化的状态

    val createdAt: Long = System.currentTimeMillis()

)

@Dao

interface DownloadTaskDao {

    @Query("SELECT * FROM download_tasks")

    suspend fun getAllTasks(): List<DownloadTaskEntity>

    

    @Insert(onConflict = OnConflictStrategy.REPLACE)

    suspend fun insertTask(task: DownloadTaskEntity)

    

    @Update

    suspend fun updateTask(task: DownloadTaskEntity)

    

    @Delete

    suspend fun deleteTask(task: DownloadTaskEntity)

}

12. 总结

12.1 技术亮点

  • 现代Android开发最佳实践:协程+LiveData+Hilt+分层架构
  • 高可扩展性:多BaseUrl、多服务类型、动态配置
  • 极致用户体验:断点续传、多线程下载、进度框交互
  • 健壮性:完善的错误处理、重试机制、状态管理
  • 易维护:分层清晰、依赖注入、单一职责

12.2 应用场景

  • 普通API请求:RESTful接口调用
  • 文件上传/下载:大文件传输,支持断点续传
  • 多服务器架构:API、CDN、上传、下载服务分离
  • 离线缓存:本地数据库缓存,支持离线访问
  • 进度可视化:实时进度反馈,用户操作可控

12.3 团队协作

  • 统一接口:所有网络请求都通过统一的接口
  • 统一状态管理:进度、错误、取消等状态统一管理
  • 统一错误处理:所有错误都有统一的处理方式
  • 统一配置:网络配置、下载配置等统一管理

12.4 未来扩展

  • WebSocket支持:实时通信功能
  • GraphQL支持:更灵活的API查询
  • 离线同步:支持离线操作,网络恢复后同步
  • 智能缓存:根据网络状况智能调整缓存策略
  • 性能监控:网络性能监控和分析

13. 常见问题与答疑

13.1 断点续传相关

Q: 服务器不支持Range请求怎么办?

A: 降级为普通下载,不支持断点续传功能。

Q: 断点信息如何持久化?

A: 使用数据库或本地文件记录下载进度。

Q: 如何防止文件损坏?

A: 下载完成前使用.tmp后缀,合并后重命名。

13.2 多线程下载相关

Q: 进度如何统计?

A: 每个chunk单独统计,合并后汇总。

Q: 失败重试如何处理?

A: 每个chunk可单独重试,不影响其他chunk。

Q: 合并文件如何保证原子性?

A: 合并后校验MD5,确保文件完整性。

13.3 性能优化相关

Q: 内存使用过高怎么办?

A: 使用分片下载,流式写入,定期GC。

Q: 网络请求频繁怎么办?

A: 使用连接池复用连接,合理使用缓存。

Q: 并发下载过多怎么办?

A: 使用信号量控制并发数量。

13.4 用户体验相关

Q: 进度不流畅怎么办?

A: 使用主线程post更新,避免频繁UI更新。

Q: 进度丢失怎么办?

A: 断点续传时恢复进度,持久化进度信息。

Q: 多任务进度如何管理?

A: 使用Map<TaskId, State>管理多个任务状态。


14. 结语

通过本套网络框架封装,开发者可以专注于业务逻辑,无需重复造轮子,极大提升开发效率和App专业度。

核心价值:

  • 提升开发效率
  • 改善用户体验
  • 增强代码可维护性
  • 支持业务快速扩展

技术特色:

  • 现代化架构设计
  • 完善的错误处理
  • 友好的用户交互
  • 高性能的网络请求
相关推荐
白鹭24 分钟前
基于LNMP架构的分布式个人博客搭建
linux·运维·服务器·网络·分布式·apache
sx2436941 小时前
day33:零基础学嵌入式之网络——TCP并发服务器
网络·网络协议·http
Jackilina_Stone3 小时前
【faiss】用于高效相似性搜索和聚类的C++库 | 源码详解与编译安装
android·linux·c++·编译·faiss
棒棒AIT3 小时前
mac 苹果电脑 Intel 芯片(Mac X86) 安卓虚拟机 Android模拟器 的救命稻草(下载安装指南)
android·游戏·macos·安卓·mac
fishwheel4 小时前
Android:Reverse 实战 part 2 番外 IDA python
android·python·安全
wmm_会飞的@鱼5 小时前
FlexSim-汽车零部件仓库布局优化与仿真
服务器·前端·网络·数据库·数学建模·汽车
-XWB-6 小时前
【安全漏洞】网络守门员:深入理解与应用iptables,守护Linux服务器安全
linux·服务器·网络
还是朝夕6 小时前
OSPF路由协议 多区域
网络