网络性能优化完全指南

核心主题:系统掌握DNS优化(HttpDNS、预解析)、连接优化(连接池、预热、HTTP/2/3)、传输优化(GZIP、Protobuf、分片传输)、弱网优化(重试、降级、断点续传)、流量优化(图片压缩、增量更新、多级缓存)等全方位网络性能调优技术


关键知识点速查

1. DNS优化

HttpDNS原理
传统DNS问题:
  1. 运营商DNS劫持(返回错误IP)
  2. DNS污染(缓存被篡改)
  3. 解析慢(递归查询耗时长)
  4. 调度不准确(基于LocalDNS,非用户真实IP)

HttpDNS优势:
  1. 绕过运营商LocalDNS
  2. 直接请求权威DNS服务器
  3. 基于HTTP/HTTPS(更可靠)
  4. 精准调度(基于客户端IP)

传统DNS流程:
  App -> LocalDNS -> 根DNS -> .com DNS -> 权威DNS -> LocalDNS -> App
  耗时:200-500ms

HttpDNS流程:
  App -> HttpDNS服务器 -> App
  耗时:50-150ms
HttpDNS实现
class HttpDnsResolver {

    private val dnsCache = ConcurrentHashMap<String, DnsCacheEntry>()
    private val httpClient = OkHttpClient()

    data class DnsCacheEntry(
        val ip: String,
        val ttl: Long,
        val timestamp: Long
    )

    // HttpDNS解析(阿里云为例)
    suspend fun resolve(hostname: String): String? = withContext(Dispatchers.IO) {
        // 1. 检查缓存
        val cached = dnsCache[hostname]
        if (cached != null && !isExpired(cached)) {
            println("DNS缓存命中: $hostname -> ${cached.ip}")
            return@withContext cached.ip
        }

        try {
            // 2. 请求HttpDNS(使用IP直接请求,避免域名解析)
            val accountId = "12345"  // 阿里云账号ID
            val url = "http://203.107.1.1/$accountId/d?host=$hostname"

            val request = Request.Builder()
                .url(url)
                .build()

            val response = httpClient.newCall(request).execute()
            val json = JSONObject(response.body?.string() ?: "{}")

            // 3. 解析结果
            val ips = json.getJSONArray("ips")
            if (ips.length() > 0) {
                val ip = ips.getString(0)
                val ttl = json.optLong("ttl", 60L)

                // 4. 缓存结果
                dnsCache[hostname] = DnsCacheEntry(
                    ip = ip,
                    ttl = ttl,
                    timestamp = System.currentTimeMillis()
                )

                println("HttpDNS解析: $hostname -> $ip (TTL=${ttl}s)")
                return@withContext ip
            }

        } catch (e: Exception) {
            println("HttpDNS解析失败: ${e.message},降级到系统DNS")
        }

        // 5. 降级到系统DNS
        return@withContext systemDnsResolve(hostname)
    }

    // 系统DNS解析
    private fun systemDnsResolve(hostname: String): String? {
        return try {
            InetAddress.getByName(hostname).hostAddress
        } catch (e: Exception) {
            println("系统DNS解析失败: ${e.message}")
            null
        }
    }

    // 检查缓存是否过期
    private fun isExpired(entry: DnsCacheEntry): Boolean {
        val age = (System.currentTimeMillis() - entry.timestamp) / 1000
        return age > entry.ttl
    }

    // 预解析(App启动时)
    fun preResolve(hostnames: List<String>) {
        GlobalScope.launch(Dispatchers.IO) {
            hostnames.forEach { hostname ->
                try {
                    resolve(hostname)
                } catch (e: Exception) {
                    println("预解析失败[$hostname]: ${e.message}")
                }
            }
        }
    }

    // 清理过期缓存
    fun cleanExpiredCache() {
        val iterator = dnsCache.iterator()
        while (iterator.hasNext()) {
            val entry = iterator.next()
            if (isExpired(entry.value)) {
                iterator.remove()
            }
        }
    }
}

// OkHttp集成HttpDNS
class HttpDnsIntegration(private val dnsResolver: HttpDnsResolver) : Dns {
    override fun lookup(hostname: String): List<InetAddress> {
        // HttpDNS解析
        val ip = runBlocking { dnsResolver.resolve(hostname) }

        return if (ip != null) {
            listOf(InetAddress.getByName(ip))
        } else {
            // 降级到系统DNS
            Dns.SYSTEM.lookup(hostname)
        }
    }
}

// 使用示例
val dnsResolver = HttpDnsResolver()

// App启动时预解析
dnsResolver.preResolve(listOf(
    "api.T8000.com",
    "device.T8000.com",
    "cdn.T8000.com"
))

// OkHttp配置
val client = OkHttpClient.Builder()
    .dns(HttpDnsIntegration(dnsResolver))
    .build()
HTTPDNS + IP直连 + Host Header
// 问题:使用IP直连会导致HTTPS证书验证失败
// 解决:保留Host Header,使用IP直连

class HttpDnsWithHostHeader(private val dnsResolver: HttpDnsResolver) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        val originalUrl = request.url

        // 获取IP
        val ip = runBlocking { dnsResolver.resolve(originalUrl.host) }

        if (ip != null) {
            // 替换Host为IP
            val newUrl = originalUrl.newBuilder()
                .host(ip)
                .build()

            // 保留原始Host Header
            request = request.newBuilder()
                .url(newUrl)
                .header("Host", originalUrl.host)  // 关键:保留Host
                .build()

            println("IP直连: ${originalUrl.host} -> $ip")
        }

        return chain.proceed(request)
    }
}

// 使用
val client = OkHttpClient.Builder()
    .addInterceptor(HttpDnsWithHostHeader(dnsResolver))
    .build()

2. 连接优化

连接复用(Keep-Alive)
// HTTP/1.1默认启用Keep-Alive
// 一个TCP连接可以发送多个HTTP请求

// OkHttp自动管理连接池
val connectionPool = ConnectionPool(
    maxIdleConnections = 10,      // 最大空闲连接数
    keepAliveDuration = 5,        // 连接保活时长(分钟)
    timeUnit = TimeUnit.MINUTES
)

val client = OkHttpClient.Builder()
    .connectionPool(connectionPool)
    .build()

// 连接复用效果:
// 第一次请求:DNS解析(100ms) + TCP握手(150ms) + SSL握手(200ms) + HTTP请求(50ms) = 500ms
// 第二次请求:HTTP请求(50ms) = 50ms(复用连接,节省450ms)
连接预热(Warm-up)
class ConnectionWarmer(private val client: OkHttpClient) {

    // 预建立连接
    suspend fun warmup(urls: List<String>) = withContext(Dispatchers.IO) {
        urls.forEach { url ->
            try {
                // 发送HEAD请求(不获取Body,只建立连接)
                val request = Request.Builder()
                    .url(url)
                    .head()
                    .build()

                client.newCall(request).execute().use { response ->
                    if (response.isSuccessful) {
                        println("连接预热成功: $url")
                    }
                }
            } catch (e: Exception) {
                println("连接预热失败[$url]: ${e.message}")
            }
        }
    }
}

// 使用(App启动时)
val warmer = ConnectionWarmer(client)

GlobalScope.launch {
    warmer.warmup(listOf(
        "https://api.T8000.com",
        "https://device.T8000.com",
        "https://cdn.T8000.com"
    ))
}
HTTP/2支持
// OkHttp默认支持HTTP/2

// HTTP/2优势:
// 1. 多路复用:一个TCP连接支持多个并发请求
// 2. 头部压缩:HPACK算法压缩Header
// 3. 服务器推送:服务器主动推送资源
// 4. 二进制分帧:更高效的传输

// 对比:
// HTTP/1.1:
//   请求1 -----> [连接1] -----> 服务器
//   请求2 -----> [连接2] -----> 服务器
//   请求3 -----> [连接3] -----> 服务器
//   (需要3个TCP连接)

// HTTP/2:
//   请求1 ---|
//   请求2 ---|--> [连接1] -----> 服务器
//   请求3 ---|
//   (只需1个TCP连接)

// 检查是否使用HTTP/2
client.newCall(request).execute().use { response ->
    println("协议: ${response.protocol}")  // h2 = HTTP/2, http/1.1 = HTTP/1.1
}

3. 传输优化

GZIP压缩
// OkHttp自动处理GZIP压缩

// 请求头:
// Accept-Encoding: gzip

// 响应头:
// Content-Encoding: gzip

// 压缩效果(JSON数据):
// 原始大小:100KB
// GZIP压缩后:20KB(压缩率80%)

// 手动启用GZIP(默认已启用)
val client = OkHttpClient.Builder()
    .addInterceptor { chain ->
        val request = chain.request().newBuilder()
            .header("Accept-Encoding", "gzip")
            .build()
        chain.proceed(request)
    }
    .build()
Protocol Buffers(Protobuf)
// Protobuf vs JSON

// JSON(文本格式):
// {"deviceId":"camera001","online":true,"battery":80}
// 大小:55字节

// Protobuf(二进制格式):
// [0a 0a 63 61 6d 65 72 61 30 30 31 10 01 18 50]
// 大小:15字节(节省73%)

// 定义Protobuf消息
syntax = "proto3";

message DeviceStatus {
    string device_id = 1;
    bool online = 2;
    int32 battery = 3;
}

// 生成Kotlin代码
// protoc --kotlin_out=. device.proto

// 使用Protobuf
class ProtobufConverter : Converter.Factory() {
    override fun responseBodyConverter(
        type: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *> {
        return ProtobufResponseConverter()
    }

    override fun requestBodyConverter(
        type: Type,
        parameterAnnotations: Array<Annotation>,
        methodAnnotations: Array<Annotation>,
        retrofit: Retrofit
    ): Converter<*, RequestBody> {
        return ProtobufRequestConverter()
    }
}

class ProtobufResponseConverter : Converter<ResponseBody, Any> {
    override fun convert(value: ResponseBody): Any {
        val bytes = value.bytes()
        return DeviceStatus.parseFrom(bytes)
    }
}

class ProtobufRequestConverter : Converter<Any, RequestBody> {
    override fun convert(value: Any): RequestBody {
        val message = value as GeneratedMessageV3
        val bytes = message.toByteArray()
        return bytes.toRequestBody("application/x-protobuf".toMediaType())
    }
}

// Retrofit配置
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.T8000.com/")
    .addConverterFactory(ProtobufConverter())
    .build()
数据分片(Chunked Transfer)
// 大文件上传使用分片传输

class ChunkedUploader(private val client: OkHttpClient) {

    suspend fun upload(
        file: File,
        uploadUrl: String,
        chunkSize: Int = 1024 * 1024,  // 1MB
        onProgress: (Int) -> Unit
    ) = withContext(Dispatchers.IO) {

        val totalSize = file.length()
        var uploadedSize = 0L

        file.inputStream().use { input ->
            val buffer = ByteArray(chunkSize)
            var chunkIndex = 0

            while (true) {
                val bytesRead = input.read(buffer)
                if (bytesRead == -1) break

                // 上传分片
                val chunk = buffer.copyOf(bytesRead)
                uploadChunk(uploadUrl, chunk, chunkIndex, totalSize)

                // 更新进度
                uploadedSize += bytesRead
                val progress = (uploadedSize * 100 / totalSize).toInt()
                withContext(Dispatchers.Main) {
                    onProgress(progress)
                }

                chunkIndex++
            }
        }
    }

    private fun uploadChunk(url: String, chunk: ByteArray, index: Int, totalSize: Long) {
        val requestBody = chunk.toRequestBody("application/octet-stream".toMediaType())

        val request = Request.Builder()
            .url(url)
            .post(requestBody)
            .header("Content-Range", "bytes ${index * chunk.size}-${index * chunk.size + chunk.size - 1}/$totalSize")
            .header("X-Chunk-Index", index.toString())
            .build()

        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful) {
                throw IOException("分片上传失败: ${response.code}")
            }
        }
    }
}

// 使用
val uploader = ChunkedUploader(client)

GlobalScope.launch {
    uploader.upload(
        file = File("/sdcard/log.zip"),
        uploadUrl = "https://api.T8000.com/upload"
    ) { progress ->
        println("上传进度: $progress%")
    }
}

4. 弱网优化

超时重试
class RetryInterceptor(
    private val maxRetry: Int = 3,
    private val retryDelay: Long = 1000L
) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        var response: Response? = null
        var lastException: Exception? = null

        repeat(maxRetry) { attempt ->
            try {
                response?.close()
                response = chain.proceed(request)

                // 成功或客户端错误不重试
                if (response!!.isSuccessful || response!!.code < 500) {
                    return response!!
                }

                println("请求失败[${response!!.code}],重试 ${attempt + 1}/$maxRetry")
                Thread.sleep(retryDelay * (attempt + 1))  // 线性增长延迟

            } catch (e: SocketTimeoutException) {
                lastException = e
                println("请求超时,重试 ${attempt + 1}/$maxRetry")
                Thread.sleep(retryDelay * (attempt + 1))

            } catch (e: IOException) {
                lastException = e
                println("网络错误,重试 ${attempt + 1}/$maxRetry")
                Thread.sleep(retryDelay * (attempt + 1))
            }
        }

        return response ?: throw lastException ?: IOException("请求失败")
    }
}

// 使用
val client = OkHttpClient.Builder()
    .addInterceptor(RetryInterceptor(maxRetry = 3))
    .build()
降级策略
class DegradationManager {

    // 网络质量评估
    enum class NetworkQuality {
        EXCELLENT,  // 优秀(4G/5G/Wi-Fi强信号)
        GOOD,       // 良好(4G/Wi-Fi弱信号)
        POOR,       // 较差(3G)
        BAD         // 很差(2G/无信号)
    }

    fun getNetworkQuality(context: Context): NetworkQuality {
        val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val network = cm.activeNetwork ?: return NetworkQuality.BAD

        val capabilities = cm.getNetworkCapabilities(network) ?: return NetworkQuality.BAD

        return when {
            capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> {
                val signalStrength = getWifiSignalStrength(context)
                if (signalStrength > -60) NetworkQuality.EXCELLENT
                else NetworkQuality.GOOD
            }
            capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> {
                val networkType = getNetworkType(context)
                when (networkType) {
                    "5G", "4G" -> NetworkQuality.GOOD
                    "3G" -> NetworkQuality.POOR
                    else -> NetworkQuality.BAD
                }
            }
            else -> NetworkQuality.BAD
        }
    }

    // 根据网络质量调整策略
    fun getOptimizedConfig(quality: NetworkQuality): NetworkConfig {
        return when (quality) {
            NetworkQuality.EXCELLENT -> NetworkConfig(
                imageQuality = ImageQuality.HIGH,
                videoQuality = VideoQuality.HD,
                enablePreload = true,
                timeout = 30_000
            )
            NetworkQuality.GOOD -> NetworkConfig(
                imageQuality = ImageQuality.MEDIUM,
                videoQuality = VideoQuality.SD,
                enablePreload = true,
                timeout = 45_000
            )
            NetworkQuality.POOR -> NetworkConfig(
                imageQuality = ImageQuality.LOW,
                videoQuality = VideoQuality.LD,
                enablePreload = false,
                timeout = 60_000
            )
            NetworkQuality.BAD -> NetworkConfig(
                imageQuality = ImageQuality.THUMBNAIL,
                videoQuality = VideoQuality.LD,
                enablePreload = false,
                timeout = 90_000
            )
        }
    }

    private fun getWifiSignalStrength(context: Context): Int {
        val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
        return wifiManager.connectionInfo.rssi
    }

    private fun getNetworkType(context: Context): String {
        val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        return when (tm.dataNetworkType) {
            TelephonyManager.NETWORK_TYPE_NR -> "5G"
            TelephonyManager.NETWORK_TYPE_LTE -> "4G"
            TelephonyManager.NETWORK_TYPE_UMTS, TelephonyManager.NETWORK_TYPE_HSDPA -> "3G"
            else -> "2G"
        }
    }
}

data class NetworkConfig(
    val imageQuality: ImageQuality,
    val videoQuality: VideoQuality,
    val enablePreload: Boolean,
    val timeout: Long
)

enum class ImageQuality { HIGH, MEDIUM, LOW, THUMBNAIL }
enum class VideoQuality { HD, SD, LD }

// 使用
val degradationManager = DegradationManager()
val quality = degradationManager.getNetworkQuality(context)
val config = degradationManager.getOptimizedConfig(quality)

println("网络质量: $quality")
println("图片质量: ${config.imageQuality}")
println("视频质量: ${config.videoQuality}")
断点续传
class ResumeDownloader(private val client: OkHttpClient) {

    suspend fun download(
        url: String,
        destFile: File,
        onProgress: (downloaded: Long, total: Long) -> Unit
    ): Boolean = withContext(Dispatchers.IO) {

        var downloadedBytes = 0L

        // 检查已下载部分
        if (destFile.exists()) {
            downloadedBytes = destFile.length()
            println("继续下载,已下载: ${downloadedBytes / 1024}KB")
        }

        while (true) {
            try {
                // Range请求(断点续传)
                val request = Request.Builder()
                    .url(url)
                    .header("Range", "bytes=$downloadedBytes-")  // 关键:Range Header
                    .build()

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

                when (response.code) {
                    206 -> {
                        // 部分内容(断点续传成功)
                        val contentLength = response.body?.contentLength() ?: 0
                        val totalLength = downloadedBytes + contentLength

                        response.body?.byteStream()?.use { input ->
                            FileOutputStream(destFile, true).use { output ->  // append模式
                                val buffer = ByteArray(8192)
                                var bytes = input.read(buffer)

                                while (bytes >= 0) {
                                    output.write(buffer, 0, bytes)
                                    downloadedBytes += bytes

                                    // 通知进度
                                    withContext(Dispatchers.Main) {
                                        onProgress(downloadedBytes, totalLength)
                                    }

                                    bytes = input.read(buffer)
                                }
                            }
                        }

                        println("下载完成: ${destFile.absolutePath}")
                        return@withContext true
                    }

                    200 -> {
                        // 完整内容(服务器不支持断点续传,重新下载)
                        println("服务器不支持断点续传,重新下载")
                        destFile.delete()
                        downloadedBytes = 0L
                    }

                    416 -> {
                        // 文件已完整下载
                        println("文件已完整")
                        return@withContext true
                    }

                    else -> {
                        throw IOException("响应码错误: ${response.code}")
                    }
                }

            } catch (e: Exception) {
                println("下载异常: ${e.message},3秒后重试...")
                delay(3000)
            }
        }
    }
}

// 使用
val downloader = ResumeDownloader(client)

GlobalScope.launch {
    val success = downloader.download(
        url = "https://cdn.T8000.com/firmware/v1.2.3.bin",
        destFile = File(context.filesDir, "firmware.bin")
    ) { downloaded, total ->
        val progress = (downloaded * 100 / total).toInt()
        println("下载进度: $progress%")
    }
}

5. 流量优化

图片压缩
class ImageCompressor {

    // 压缩图片
    fun compress(
        bitmap: Bitmap,
        quality: Int = 80,  // 0-100
        maxWidth: Int = 1080,
        maxHeight: Int = 1920
    ): ByteArray {
        // 1. 缩放
        val scaled = if (bitmap.width > maxWidth || bitmap.height > maxHeight) {
            val ratio = min(
                maxWidth.toFloat() / bitmap.width,
                maxHeight.toFloat() / bitmap.height
            )
            val targetWidth = (bitmap.width * ratio).toInt()
            val targetHeight = (bitmap.height * ratio).toInt()

            Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, true)
        } else {
            bitmap
        }

        // 2. 压缩
        val output = ByteArrayOutputStream()
        scaled.compress(Bitmap.CompressFormat.JPEG, quality, output)

        return output.toByteArray()
    }

    // 压缩文件
    fun compressFile(
        inputFile: File,
        outputFile: File,
        quality: Int = 80,
        maxWidth: Int = 1080,
        maxHeight: Int = 1920
    ) {
        val bitmap = BitmapFactory.decodeFile(inputFile.absolutePath)
        val compressed = compress(bitmap, quality, maxWidth, maxHeight)

        outputFile.outputStream().use { it.write(compressed) }

        val originalSize = inputFile.length() / 1024
        val compressedSize = outputFile.length() / 1024
        val ratio = (compressedSize * 100 / originalSize).toInt()

        println("压缩完成: ${originalSize}KB -> ${compressedSize}KB (${100 - ratio}%)")
    }

    // Glide集成(自动压缩)
    fun loadCompressedImage(context: Context, url: String, imageView: ImageView) {
        Glide.with(context)
            .load(url)
            .override(1080, 1920)  // 最大尺寸
            .format(DecodeFormat.PREFER_RGB_565)  // 使用RGB_565(节省50%内存)
            .diskCacheStrategy(DiskCacheStrategy.ALL)
            .into(imageView)
    }
}
增量更新
// 使用bsdiff算法生成差分包

// 服务器端生成差分包:
// bsdiff old.apk new.apk patch.bin

// 客户端应用差分包:
class IncrementalUpdater {

    suspend fun update(
        oldApkPath: String,
        patchUrl: String,
        newApkPath: String,
        onProgress: (Int) -> Unit
    ) = withContext(Dispatchers.IO) {

        // 1. 下载差分包
        val patchFile = File(context.cacheDir, "patch.bin")
        downloadFile(patchUrl, patchFile, onProgress)

        // 2. 应用差分包(使用bspatch库)
        BsPatch.patch(
            oldFile = oldApkPath,
            newFile = newApkPath,
            patchFile = patchFile.absolutePath
        )

        // 3. 验证新APK
        if (verifyApk(newApkPath)) {
            println("增量更新成功")
            installApk(newApkPath)
        } else {
            throw Exception("APK校验失败")
        }
    }

    private fun verifyApk(apkPath: String): Boolean {
        // 计算MD5并验证
        val md5 = calculateMd5(File(apkPath))
        val expectedMd5 = getExpectedMd5()
        return md5 == expectedMd5
    }

    private fun calculateMd5(file: File): String {
        val digest = MessageDigest.getInstance("MD5")
        file.inputStream().use { input ->
            val buffer = ByteArray(8192)
            var bytes = input.read(buffer)

            while (bytes >= 0) {
                digest.update(buffer, 0, bytes)
                bytes = input.read(buffer)
            }
        }

        return digest.digest().joinToString("") { "%02x".format(it) }
    }
}

// 优势:
// 完整APK:20MB
// 差分包:2MB(节省90%流量)
缓存策略
class CacheManager {

    // 三级缓存
    // 1. 内存缓存(LruCache)
    private val memoryCache = object : LruCache<String, Bitmap>(
        (Runtime.getRuntime().maxMemory() / 8).toInt()  // 最大内存的1/8
    ) {
        override fun sizeOf(key: String, value: Bitmap): Int {
            return value.byteCount
        }
    }

    // 2. 磁盘缓存(DiskLruCache)
    private val diskCache = DiskLruCache.open(
        File(context.cacheDir, "image_cache"),
        1,
        1,
        50 * 1024 * 1024  // 50MB
    )

    // 3. 网络请求

    fun getBitmap(url: String): Bitmap? {
        // 1. 从内存缓存获取
        memoryCache.get(url)?.let {
            println("内存缓存命中")
            return it
        }

        // 2. 从磁盘缓存获取
        val key = hashKeyForDisk(url)
        diskCache.get(key)?.let { snapshot ->
            val bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0))
            memoryCache.put(url, bitmap)  // 写入内存缓存
            println("磁盘缓存命中")
            return bitmap
        }

        // 3. 从网络获取
        println("网络请求")
        val bitmap = downloadBitmap(url)
        if (bitmap != null) {
            memoryCache.put(url, bitmap)
            saveToDiskCache(url, bitmap)
        }

        return bitmap
    }

    private fun saveToDiskCache(url: String, bitmap: Bitmap) {
        val key = hashKeyForDisk(url)
        val editor = diskCache.edit(key) ?: return

        try {
            val output = editor.newOutputStream(0)
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)
            editor.commit()
        } catch (e: Exception) {
            editor.abort()
        }
    }

    private fun hashKeyForDisk(url: String): String {
        return MessageDigest.getInstance("MD5")
            .digest(url.toByteArray())
            .joinToString("") { "%02x".format(it) }
    }

    private fun downloadBitmap(url: String): Bitmap? {
        // 网络下载
        return null
    }
}

// 缓存策略对比
/*
无缓存:
  每次请求都从网络下载
  流量:100MB
  耗时:30秒

内存缓存:
  第二次加载相同图片快速显示
  流量:100MB(首次)
  耗时:30秒(首次),0.1秒(后续)

磁盘缓存:
  重启App后依然有效
  流量:100MB(首次)
  耗时:30秒(首次),0.5秒(磁盘读取)

三级缓存:
  最优体验
  流量:100MB(首次)
  耗时:30秒(首次),0.1秒(内存),0.5秒(磁盘)
*/

6. IPv6支持

检测IPv6可用性
class IPv6Support {

    fun isIPv6Available(): Boolean {
        return try {
            val interfaces = NetworkInterface.getNetworkInterfaces()
            while (interfaces.hasMoreElements()) {
                val networkInterface = interfaces.nextElement()
                val addresses = networkInterface.inetAddresses

                while (addresses.hasMoreElements()) {
                    val address = addresses.nextElement()
                    if (address is Inet6Address && !address.isLoopbackAddress) {
                        println("IPv6地址: ${address.hostAddress}")
                        return true
                    }
                }
            }
            false
        } catch (e: Exception) {
            false
        }
    }

    // DNS解析(支持IPv6)
    fun resolveHost(hostname: String): List<InetAddress> {
        return InetAddress.getAllByName(hostname).toList()
        // 返回IPv4和IPv6地址
        // [203.0.113.1, 2001:0db8:85a3::8a2e:0370:7334]
    }

    // 优先使用IPv6
    fun preferIPv6(addresses: List<InetAddress>): InetAddress {
        return addresses.firstOrNull { it is Inet6Address }
            ?: addresses.first()
    }
}

// OkHttp自动支持IPv6
// 无需特殊配置

7. HTTP/2和HTTP/3

HTTP/2
// OkHttp默认支持HTTP/2

// HTTP/2特性验证
val client = OkHttpClient.Builder()
    .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1))
    .build()

val request = Request.Builder()
    .url("https://api.T8000.com/devices")
    .build()

client.newCall(request).execute().use { response ->
    println("协议: ${response.protocol}")  // HTTP_2

    // HTTP/2服务器推送
    response.request.url.newBuilder().build()
}

// HTTP/2性能对比(10个并发请求)
/*
HTTP/1.1:
  - 需要6个TCP连接(Chrome限制)
  - 总耗时:2.5秒
  - 连接开销大

HTTP/2:
  - 只需1个TCP连接
  - 总耗时:1.2秒
  - 节省52%时间
*/
HTTP/3 (QUIC)
// OkHttp暂不支持HTTP/3
// Cronet库支持QUIC

// 添加依赖
implementation 'org.chromium.net:cronet-api:119.6045.31'
implementation 'org.chromium.net:cronet-common:119.6045.31'

// 使用Cronet
val cronetEngine = CronetEngine.Builder(context)
    .enableQuic(true)  // 启用QUIC
    .build()

// HTTP/3 (QUIC) 优势:
// 1. 0-RTT连接(首次1-RTT,后续0-RTT)
// 2. 改进的拥塞控制
// 3. 连接迁移(Wi-Fi切换到4G不断开)
// 4. 基于UDP(避免TCP队头阻塞)

架构图/流程图

HttpDNS流程

┌──────────┐
│   App    │
└────┬─────┘
     │ 1. 解析api.T8000.com
     ▼
┌─────────────────┐
│  DNS缓存检查     │ --命中--> 返回IP
└────┬────────────┘
     │ 未命中
     ▼
┌──────────────────────┐
│ HttpDNS服务器         │
│ (203.107.1.1)        │
└────┬─────────────────┘
     │ 2. HTTP请求
     ▼
┌──────────────────────┐
│ 返回IP + TTL          │
│ {"ip":"203.0.113.1",│
│  "ttl":60}           │
└────┬─────────────────┘
     │ 3. 缓存结果
     ▼
┌─────────────────┐
│  使用IP建立连接  │
└─────────────────┘

三级缓存架构

请求图片
   ↓
内存缓存(LruCache)
   ├─ 命中 → 返回Bitmap
   └─ 未命中
        ↓
   磁盘缓存(DiskLruCache)
      ├─ 命中 → 返回Bitmap → 写入内存缓存
      └─ 未命中
           ↓
      网络请求
           ↓
      下载图片
           ↓
      写入磁盘缓存 + 内存缓存
           ↓
      返回Bitmap

面试高频问题

Q1: 如何优化App首次网络请求的速度?

:首次请求延迟 = DNS解析 + TCP握手 + SSL握手 + HTTP请求,优化策略:

  1. DNS预解析:App启动时预解析核心域名,将DNS耗时(100-500ms)降为0
  2. 连接预热:启动时发送HEAD请求预建立TCP+SSL连接
  3. HttpDNS:绕过运营商LocalDNS,解析时间从200-500ms降至50-150ms
  4. HTTP/2:多路复用减少连接数,首次建连后所有请求共享一个连接
  5. IP直连:跳过DNS解析直接使用IP,需配合Host Header解决证书问题

Q2: 弱网环境下如何保证用户体验?

:分层降级策略:

  1. 检测层:实时检测网络质量(RTT、丢包率、带宽),分为优/良/差/极差四级
  2. 请求层:动态调整超时时间(优30s→极差90s)、启用重试(指数退避)
  3. 数据层:降低图片质量、关闭预加载、使用缓存优先策略
  4. UI层:显示网络状态提示、支持离线模式、队列化操作(恢复网络后自动执行)
  5. 协议层:大数据传输使用断点续传、考虑使用QUIC(0-RTT连接、连接迁移)

Q3: HTTP/2相比HTTP/1.1有哪些优化?

特性 HTTP/1.1 HTTP/2
传输格式 文本 二进制分帧
连接复用 每域名6个连接 一个连接多路复用
头部 每次发完整头部 HPACK压缩(减少85%+)
服务器推送 不支持 支持Server Push
请求优先级 不支持 支持流优先级
队头阻塞 有(HTTP层) 无HTTP层队头阻塞(仍有TCP层)

Q4: 三级缓存的设计原理?

  1. 内存缓存(LruCache):读取速度~0.1ms,App进程内有效,容量为最大内存的1/8
  2. 磁盘缓存(DiskLruCache):读取速度~5ms,App生命周期内有效,通常50-100MB
  3. 网络请求:读取速度>100ms,始终最新

命中策略:内存→磁盘→网络,每层命中后回写上层缓存
淘汰策略:LRU(最近最少使用),定期清理过期数据

Q5: Protobuf相比JSON有什么优势?什么时候该用?

维度 JSON Protobuf
格式 文本 二进制
体积 基准 减少60-80%
解析速度 基准 快3-10倍
可读性 差(需要proto文件)
Schema 强类型定义
兼容性 通用 需要双端集成

适用场景:高频请求(设备状态轮询)、大数据量传输、带宽敏感(移动网络),不适合调试阶段和简单API


最佳实践总结

网络优化检查清单

优化项 方案 预期收益
DNS优化 HttpDNS + 预解析 + 缓存 首次请求减少200-400ms
连接优化 连接池 + 预热 + HTTP/2 后续请求减少350ms
传输优化 GZIP + Protobuf 流量减少60-80%
弱网优化 重试 + 降级 + 断点续传 弱网成功率提升50%+
缓存优化 三级缓存 + HTTP缓存 二次访问速度提升95%+
流量优化 图片压缩 + 增量更新 总流量减少40-60%
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐