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

发布于:2025-07-28 ⋅ 阅读:(23) ⋅ 点赞:(0)

目录

1. 项目概述

1.1 项目背景

1.2 技术选型

1.3 项目目标

2. 技术架构

2.1 分层架构

2.2 设计原则

3. 核心功能实现

3.1 网络配置管理器

3.2 拦截器体系

3.3 API接口定义

3.4 数据模型

4. 多BaseUrl管理

4.1 依赖注入配置

4.2 Repository层实现

4.3 ViewModel层实现

5. 断点续传实现

5.1 断点续传原理

5.2 断点续传管理器

5.3 文件管理器

6. 多线程下载

6.1 多线程下载原理

6.2 多线程下载管理器

6.3 下载状态管理

7. 进度框交互

7.1 进度框状态管理

7.2 自定义进度对话框

7.3 进度框布局文件

7.4 Activity/Fragment使用示例

8. 错误处理机制

8.1 统一错误处理

8.2 网络状态监听

9. 性能优化

9.1 连接池与缓存优化

9.2 内存优化

9.3 并发控制

10. 最佳实践

10.1 代码组织

10.2 错误处理

10.3 性能优化

10.4 用户体验

10.5 扩展性

11. 扩展功能

11.1 上传功能

11.2 任务队列

11.3 数据库持久化

12. 总结

12.1 技术亮点

12.2 应用场景

12.3 团队协作

12.4 未来扩展

13. 常见问题与答疑

13.1 断点续传相关

13.2 多线程下载相关

13.3 性能优化相关

13.4 用户体验相关

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 网络配置管理器

@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 拦截器体系

// 日志拦截器

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接口定义

// 主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 数据模型

// 用户模型

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 依赖注入配置

@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层实现

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层实现

 

@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 断点续传管理器

@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 文件管理器

@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 多线程下载管理器

@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 下载状态管理

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 进度框状态管理

// 进度框状态

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 自定义进度对话框

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 进度框布局文件

<!-- 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使用示例

 

@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 统一错误处理

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 网络状态监听

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 连接池与缓存优化

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 内存优化

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 并发控制

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 上传功能

 

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 任务队列

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 数据库持久化

@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专业度。

核心价值:

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

技术特色:

  • 现代化架构设计
  • 完善的错误处理
  • 友好的用户交互
  • 高性能的网络请求


网站公告

今日签到

点亮在社区的每一天
去签到