Appearance
Android 网络优化
目录
引言
网络优化是 Android 性能优化的重要组成部分。在网络环境下,应用的性能表现直接影响用户体验。网络延迟、请求失败、流量消耗等问题都会导致用户流失。本文将深入探讨 Android 网络优化的各个方面,提供实用的优化方案和代码示例。
为什么需要网络优化?
- 提升用户体验:快速响应减少用户等待
- 节省流量:减少不必要的网络请求和数据传输
- 降低电量消耗:减少网络活动延长电池寿命
- 提高稳定性:处理弱网和异常情况
网络性能指标
| 指标 | 说明 | 目标值 |
|---|---|---|
| 首包时间 (TTFB) | 从请求到收到第一个字节的时间 | < 500ms |
| 完全缓冲时间 (TTFB+DL) | 从请求到所有数据接收完成 | < 1s |
| 连接建立时间 | DNS 解析 + TCP 握手 + TLS 握手 | < 300ms |
| 请求成功率 | 成功请求占比 | > 99% |
缓存策略
缓存层级架构
Android 网络缓存通常分为三层:
┌─────────────────────────────────────┐
│ Memory Cache │ 内存缓存 (最快)
│ (Glide, LruCache) │
├─────────────────────────────────────┤
│ Disk Cache │ 磁盘缓存 (持久化)
│ (OkHttp, Room) │
├─────────────────────────────────────┤
│ Network Cache │ 网络缓存 (服务器)
│ (HTTP Cache-Control, ETag) │
└─────────────────────────────────────┘HTTP 缓存机制
Cache-Control 指令
http
Cache-Control: max-age=3600, public
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: must-revalidate
Cache-Control: s-maxage=3600ETag 与 Last-Modified
http
# 服务器响应
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
# 客户端请求(协商缓存)
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMTOkHttp 缓存配置
kotlin
// 配置缓存
val cacheSize = 10 * 1024 * 1024L // 10MB
val cacheDir = context.cacheDir.resolve("http_cache")
val cache = Cache(cacheDir, cacheSize)
val client = OkHttpClient.Builder()
.cache(cache)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build()
// 缓存拦截器
class CacheIntercepter(
private val context: Context,
private val cacheEnableInterceptor: CacheEnableInterceptor
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
// 根据网络状态设置缓存策略
if (!NetworkUtil.isNetworkAvailable(context)) {
requestBuilder.addHeader("Cache-Control", "public, only-if-cached, max-stale=604800")
} else {
requestBuilder.removeHeader("Pragma")
requestBuilder.removeHeader("Cache-Control")
}
return chain.proceed(requestBuilder.build())
}
}图片缓存优化(Glide)
kotlin
// Glide 缓存配置
Glide.with(context)
.load(imageUrl)
.apply(
RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL) // 缓存内存和磁盘
.centerCrop()
)
.into(imageView)
// 自定义缓存策略
val diskCache = InternalCacheDiskCacheFactory(context)
val memoryCache = LruResourceCache(memorySizeBytes)
GlideBuilder()
.setDiskCache(diskCache)
.setMemoryCache(memoryCache)
.build()
.setContext(context)
// 不同场景的缓存策略
enum class DiskCacheStrategy(val strategy: DiskCacheStrategy) {
NONE(DiskCacheStrategy.NONE), // 不缓存
DATA(DiskCacheStrategy.DATA), // 只缓存原始数据
RESOURCE(DiskCacheStrategy.RESOURCE), // 只缓存处理后的资源
ALL(DiskCacheStrategy.ALL), // 全部缓存(默认)
AUTOMATIC(DiskCacheStrategy.AUTOMATIC) // 自动选择
}内存缓存实现
kotlin
// LRU 缓存实现
class LruCache<K, V>(private val maxSize: Int) {
private val cache = LinkedHashMap<K, V>(initialCapacity, loadFactor, true)
@Synchronized
fun get(key: K): V? {
return cache[key]
}
@Synchronized
fun put(key: K, value: V) {
cache[key] = value
if (cache.size > maxSize) {
cache.keys.iterator().next()?.let { cache.remove(it) }
}
}
@Synchronized
fun clear() {
cache.clear()
}
}
// 网络响应内存缓存
class ResponseCache {
private val memoryCache = LruCache<String, Response>(100)
private val diskCache = DiskLruCache.open(cacheDir, 1, 1, 100 * 1024 * 1024) // 100MB
fun get(key: String): Response? {
// 先从内存获取
memoryCache.get(key)?.let { return it }
// 从磁盘获取
diskCache.get(key)?.let { snapshot ->
val response = parseResponse(snapshot.get(0))
memoryCache.put(key, response)
return response
}
return null
}
fun put(key: String, response: Response) {
memoryCache.put(key, response)
// 异步写入磁盘
IOUtils.write(diskCache.edit(key), serializeResponse(response))
}
}请求合并
请求合并原理
请求合并是指将多个网络请求合并为一个请求,减少网络交互次数。
场景分析
1. 列表页多接口合并
kotlin
// 原始代码:多个独立请求
fun loadHomePage() {
api.getUserInfo()
api.getBannerList()
api.getRecommendList()
api.getNoticeList()
}
// 优化后:合并请求
data class HomeRequest(
val needUserInfo: Boolean = true,
val needBanner: Boolean = true,
val needRecommend: Boolean = true,
val needNotice: Boolean = true
)
data class HomeResponse(
val userInfo: UserInfo?,
val bannerList: List<Banner>,
val recommendList: List<Recommend>,
val noticeList: List<Notice>
)
fun loadHomePageOptimized() {
api.getHomeData(HomeRequest()).enqueue(object : Callback<HomeResponse> {
override fun onResponse(call: Call<HomeResponse>, response: Response<HomeResponse>) {
response.body()?.let {
updateUserInfo(it.userInfo)
updateBannerList(it.bannerList)
updateRecommendList(it.recommendList)
updateNoticeList(it.noticeList)
}
}
})
}2. 搜索请求防抖
kotlin
class SearchManager {
private val debounceTime = 500L
private var lastSearchTime = 0L
private val searchQueue = ConcurrentLinkedQueue<SearchRequest>()
fun search(query: String) {
val now = System.currentTimeMillis()
if (now - lastSearchTime < debounceTime) {
// 队列中已有的相同请求直接取消
searchQueue.removeIf { it.query == query }
searchQueue.add(SearchRequest(query, now))
return
}
lastSearchTime = now
performSearch(query)
}
private fun performSearch(query: String) {
api.search(query).enqueue(/* callback */)
}
}
// 使用 RxJava 实现防抖
class RxSearchManager {
private val searchSubject = PublishSubject.create<String>()
init {
searchSubject
.debounce(500, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.flatMap { query ->
Observable.fromCallable { api.search(query) }
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* callback */)
}
fun search(query: String) {
searchSubject.onNext(query)
}
}3. 批量操作合并
kotlin
// 批量删除
fun deleteItems(ids: List<Long>) {
// 原始:逐个删除
// ids.forEach { api.deleteItem(it).enqueue {} }
// 优化:批量删除
api.deleteItems(ids).enqueue(object : Callback<Void> {
override fun onResponse(call: Call<Void>, response: Response<Void>) {
// 处理成功
}
})
}
// 批量更新
data class ItemUpdate(val id: Long, val data: ItemData)
fun updateItems(updates: List<ItemUpdate>) {
api.updateItems(updates).enqueue(/* callback */)
}4. GraphQL 查询合并
kotlin
// GraphQL 查询合并示例
val query = """
query HomeQuery {
user {
id
name
avatar
}
bannerList {
id
imageUrl
url
}
recommendList {
id
title
summary
}
}
"""
val response = gqlClient.query(query).execute()请求队列管理
kotlin
class RequestQueue {
private val queue = ConcurrentLinkedQueue<NetworkRequest>()
private val running = AtomicInteger(0)
private val maxConcurrent = 5
fun add(request: NetworkRequest) {
queue.add(request)
schedule()
}
private fun schedule() {
while (running.get() < maxConcurrent) {
val request = queue.poll() ?: return
running.incrementAndGet()
Thread {
try {
request.execute()
} finally {
running.decrementAndGet()
schedule()
}
}.start()
}
}
}数据压缩
Gzip 压缩
OkHttp 自动压缩
kotlin
// OkHttp 默认支持 Gzip 压缩
val client = OkHttpClient.Builder()
.addInterceptor(object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
// 检查压缩状态
if (response.header("Content-Encoding") == "gzip") {
Log.d("Network", "Response is gzipped")
}
return response
}
})
.build()手动压缩配置
kotlin
// 服务器端配置(Spring Boot)
@Configuration
class CompressionConfig {
@Bean
fun contentCompression(): ContentCompressionConfigurer {
return ContentCompressionConfigurer { config ->
config.setMinSize(1024) // 超过 1KB 才压缩
config.setCompressableMimeTypes("application/json", "text/html", "text/css")
}
}
}
// 客户端强制请求压缩
class GzipRequestInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val request = originalRequest.newBuilder()
.header("Accept-Encoding", "gzip")
.method(originalRequest.method, originalRequest.body)
.build()
return chain.proceed(request)
}
}Protobuf 序列化
定义.proto 文件
protobuf
// user.proto
syntax = "proto3";
option java_package = "com.example.model";
option java_outer_classname = "UserProto";
message UserInfo {
string id = 1;
string name = 2;
string avatar = 3;
int32 age = 4;
repeated string tags = 5;
}
message HomeResponse {
UserInfo user = 1;
repeated Banner banners = 2;
repeated Recommend recommends = 3;
}
message Banner {
string id = 1;
string imageUrl = 2;
string url = 3;
}
message Recommend {
string id = 1;
string title = 2;
string summary = 3;
int32 type = 4;
}Gradle 配置
groovy
// build.gradle
plugins {
id "com.google.protobuf" version "0.9.4"
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.25.1"
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
}
dependencies {
implementation "com.google.protobuf:protobuf-javalite:3.25.1"
}使用 Protobuf 传输
kotlin
// 序列化
fun serializeToProtobuf(data: HomeResponse): ByteArray {
return data.toByteArray()
}
// 反序列化
fun deserializeFromProtobuf(data: ByteArray): HomeResponse {
return HomeResponse.parseFrom(data)
}
// OkHttp Protobuf 转换器
class ProtobufConverter : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *> {
return object : Converter<ResponseBody, HomeResponse> {
override fun convert(body: ResponseBody): HomeResponse {
return HomeResponse.parseFrom(body.bytes())
}
}
}
}
// Retrofit 配置
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(ProtobufConverter())
.build()压缩效果对比
| 格式 | 原始大小 | 压缩后大小 | 压缩率 | 解析时间 |
|---|---|---|---|---|
| JSON | 100KB | 30KB (Gzip) | 70% | 5ms |
| JSON | 100KB | 20KB (Protobuf) | 80% | 3ms |
| JSON | 100KB | 25KB (Protobuf + Gzip) | 75% | 4ms |
连接池优化
OkHttp 连接池配置
kotlin
val client = OkHttpClient.Builder()
.connectionPool(ConnectionPool(
maxSize = 5, // 空闲连接最大数
keepAliveDuration = 5, // 空闲连接保持时间
TimeUnit.MINUTES
))
.dns(Dns.SYSTEM)
.build()
// 全局连接池管理
object ConnectionPoolManager {
private val pool = ConnectionPool(5, 5, TimeUnit.MINUTES)
fun getClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectionPool(pool)
.build()
}
fun shutdown() {
pool.evictAll()
}
fun getStats(): ConnectionPoolStats {
return pool.run {
ConnectionPoolStats(
totalSize = this.size(),
connectionCount = this.connectionCount()
)
}
}
}多域名连接池
kotlin
// 为不同域名配置独立连接池
class MultiDomainConnectionPool {
private val pools = ConcurrentHashMap<String, ConnectionPool>()
fun getPool(domain: String): ConnectionPool {
return pools.computeIfAbsent(domain) {
ConnectionPool(5, 5, TimeUnit.MINUTES)
}
}
fun getClient(domain: String): OkHttpClient {
return OkHttpClient.Builder()
.connectionPool(getPool(domain))
.build()
}
}连接复用优化
kotlin
class ConnectionReuseInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val connection = (chain as RealInterceptorChain).connection()
if (connection != null) {
Log.d("Network", "Connection reused: ${connection.route()}")
} else {
Log.d("Network", "New connection created")
}
return chain.proceed(request)
}
}
// 配置 Keep-Alive
val client = OkHttpClient.Builder()
.addInterceptor(ConnectionReuseInterceptor())
.connectionPool(ConnectionPool(10, 5, TimeUnit.MINUTES))
.build()DNS 预解析
DNS 缓存配置
kotlin
// 自定义 DNS 解析器
class CachedDns : Dns {
private val cache = object : LinkedHashMap<String, List<InetSocketAddress>>(16, 0.75f, true) {
override fun removeEldestEntry(eldest: MutableEntry<String, List<InetSocketAddress>>): Boolean {
return size > MAX_CACHE_SIZE
}
}
private val expireTime = 5 * 60 * 1000L // 5 分钟
private val lock = Any()
override fun lookup(hostName: String): List<InetSocketAddress> {
synchronized(lock) {
val cached = cache[hostName]
if (cached != null && !isExpired(hostName)) {
return cached
}
}
// DNS 解析
val addresses = Dns.SYSTEM.lookup(hostName)
synchronized(lock) {
cache[hostName] = addresses
}
return addresses
}
private fun isExpired(hostName: String): Boolean {
// 实现过期检查
return false
}
}
// OkHttp 配置
val client = OkHttpClient.Builder()
.dns(CachedDns())
.build()预解析实现
kotlin
class DnsPreResolver {
private val client: OkHttpClient
private val dnsCache = ConcurrentHashMap<String, List<InetSocketAddress>>()
init {
client = OkHttpClient.Builder()
.dns(CachedDns())
.build()
}
fun preloadDns(hostnames: Collection<String>) {
hostnames.forEach { hostname ->
// 异步预解析
Executors.newSingleThreadExecutor().execute {
try {
val addresses = dnsCache.computeIfAbsent(hostname) {
InetAddress.getAllByName(hostname).map {
InetSocketAddress(it, 443)
}
}
Log.d("DnsPreResolver", "Pre-resolved $hostname: $addresses")
} catch (e: Exception) {
Log.e("DnsPreResolver", "Failed to preload $hostname", e)
}
}
}
}
// 应用启动时预解析常用域名
fun preloadCommonDomains() {
val commonDomains = listOf(
"api.example.com",
"cdn.example.com",
"static.example.com"
)
preloadDns(commonDomains)
}
}
// 在 Application 中使用
class MyApplication : Application() {
private val dnsPreResolver = DnsPreResolver()
override fun onCreate() {
super.onCreate()
dnsPreResolver.preloadCommonDomains()
}
}接口预加载
预加载策略
kotlin
class PreloadManager {
private val preloadQueue = ArrayDeque<PreloadTask>()
private val executor = Executors.newSingleThreadExecutor()
fun addPreload(task: PreloadTask) {
preloadQueue.add(task)
schedulePreload()
}
private fun schedulePreload() {
val task = preloadQueue.peekFirst() ?: return
if (shouldPreload()) {
preloadQueue.pollFirst()
executor.execute {
try {
task.execute()
} catch (e: Exception) {
Log.e("Preload", "Preload failed", e)
}
}
}
}
private fun shouldPreload(): Boolean {
// 只在 Wi-Fi 且电量充足时预加载
return isWifiConnected() && batteryLevel > 20
}
}
// 预加载任务
class PreloadTask(
private val url: String,
private val callback: (Response) -> Unit
) {
fun execute() {
val request = Request.Builder()
.url(url)
.get()
.build()
val response = client.newCall(request).execute()
callback(response)
}
}场景化预加载
kotlin
// 下一页预加载
class PaginationPreloader {
private var currentPage = 1
private val client: OkHttpClient
fun preloadNextPage(baseUrl: String) {
val nextPage = currentPage + 1
val url = "$baseUrl?page=$nextPage"
client.newCall(Request.Builder().url(url).build())
.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
// 缓存到内存
response.body()?.string()?.let {
memoryCache.put(url, it)
}
}
})
}
fun loadNextPage(): String? {
val url = "$baseUrl?page=$currentPage"
return memoryCache.get(url).apply {
currentPage++
}
}
}
// 推荐内容预加载
class RecommendPreloader {
fun preloadRecommend() {
api.getRecommendList(PreloadRequest()).enqueue(object : Callback<RecommendResponse> {
override fun onResponse(call: Call<RecommendResponse>, response: Response<RecommendResponse>) {
response.body()?.let {
cacheRecommend(it)
}
}
})
}
}弱网优化
网络质量检测
kotlin
class NetworkQualityMonitor {
enum class NetworkQuality {
EXCELLENT, GOOD, FAIR, POOR, OFFLINE
}
private var currentQuality = NetworkQuality.EXCELLENT
fun detectNetworkQuality(): NetworkQuality {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = connectivityManager.activeNetworkInfo ?: return NetworkQuality.OFFLINE
when (networkInfo.typeInfo?.subtype) {
// 5G/4G
TelephonyManager.NETWORK_TYPE_LTE,
TelephonyManager.NETWORK_TYPE_NR -> NetworkQuality.EXCELLENT
// 3G
TelephonyManager.NETWORK_TYPE_UMTS,
TelephonyManager.NETWORK_TYPE_EVDO_0 -> NetworkQuality.GOOD
// 2G
TelephonyManager.NETWORK_TYPE_GPRS,
TelephonyManager.NETWORK_TYPE_EDGE -> NetworkQuality.Poor
else -> NetworkQuality.FAIR
}
}
// 实际网络速度测试
fun testNetworkSpeed(): NetworkSpeed {
val startTime = System.currentTimeMillis()
val testUrl = "https://speedtest.example.com/testfile.bin"
val request = Request.Builder().url(testUrl).build()
val response = client.newCall(request).execute()
val bytes = response.body()?.bytes() ?: return NetworkSpeed(0)
val duration = System.currentTimeMillis() - startTime
val speedMbps = (bytes.size * 8.0 / duration / 1_000_000)
return NetworkSpeed(speedMbps)
}
}弱网优化策略
kotlin
class WeakNetworkOptimizer {
private val qualityMonitor = NetworkQualityMonitor()
fun optimizeRequest(request: Request, quality: NetworkQuality): Request {
return when (quality) {
NetworkQuality.OFFLINE -> {
// 离线模式,使用缓存
request.newBuilder()
.addHeader("Cache-Control", "public, only-if-cached, max-stale=604800")
.build()
}
NetworkQuality.POOR -> {
// 差网络,降低质量,增加超时
request.newBuilder()
.addHeader("Accept-Encoding", "identity") // 不压缩
.addHeader("X-Quality", "low")
.build()
}
NetworkQuality.FAIR -> {
// 一般网络,中等质量
request.newBuilder()
.addHeader("X-Quality", "medium")
.build()
}
else -> request
}
}
// 图片质量自适应
fun getImageQualityUrl(quality: NetworkQuality, baseUrl: String, width: Int): String {
val qualityParam = when (quality) {
NetworkQuality.OFFLINE -> "cache"
NetworkQuality.POOR -> "60"
NetworkQuality.FAIR -> "80"
else -> "100"
}
return "$baseUrl?width=$width&quality=$qualityParam"
}
}断点续传
kotlin
class ResumeDownloadManager {
private val cacheDir = context.filesDir.resolve("downloads")
fun downloadWithResume(url: String, fileName: String) {
val cacheFile = File(cacheDir, fileName)
val fileSize = cacheFile.length()
val request = Request.Builder()
.url(url)
.header("Range", "bytes=$fileSize-")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if (response.code == 206) { // Partial Content
val outputStream = FileOutputStream(cacheFile, true)
response.body()?.byteStream()?.copyTo(outputStream)
}
}
})
}
}重试机制
kotlin
class RetryInterceptor : Interceptor {
private val maxRetries = 3
private val retryDelay = 1000L
override fun intercept(chain: Interceptor.Chain): Response {
var attempt = 0
var response: Response? = null
while (attempt < maxRetries) {
try {
response = chain.proceed(chain.request())
if (response.isSuccessful) {
return response!!
}
} catch (e: IOException) {
// 网络错误,重试
}
attempt++
if (attempt < maxRetries) {
Thread.sleep(retryDelay * attempt) // 指数退避
}
}
throw IOException("Request failed after $maxRetries attempts")
}
}
// RxJava 重试
api.getData()
.retryWhen { errors ->
errors.withIndex()
.flatMapIterable { (index, error) ->
if (index < 3) {
val delay = (index + 1) * 1000L
listOf(delay)
} else {
throw error
}
}
.flatMapObservable { delay ->
Observable.timer(delay, TimeUnit.MILLISECONDS)
}
}
.subscribe(/* callback */)性能监控
网络监控拦截器
kotlin
class NetworkMonitorInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val startTime = System.currentTimeMillis()
val response = chain.proceed(request)
val endTime = System.currentTimeMillis()
val duration = endTime - startTime
// 记录网络请求
NetworkMonitor.record(
url = request.url.toString(),
method = request.method,
code = response.code,
duration = duration,
responseSize = response.body?.contentLength() ?: 0,
timestamp = System.currentTimeMillis()
)
// 性能告警
if (duration > 3000) {
Log.w("Network", "Slow request: ${request.url} took $duration ms")
}
return response
}
}
// 网络监控数据
data class NetworkMetrics(
val url: String,
val method: String,
val code: Int,
val duration: Long,
val responseSize: Long,
val timestamp: Long
)
object NetworkMonitor {
private val metrics = ConcurrentHashMap<String, MutableList<NetworkMetrics>>()
fun record(metrics: NetworkMetrics) {
val url = metrics.url.split("?")[0]
metrics.computeIfAbsent(url) { mutableListOf() }.add(metrics)
}
fun getAverageDuration(url: String): Long {
return metrics[url]?.map { it.duration }?.average()?.toLong() ?: 0
}
fun getErrorRate(url: String): Double {
val list = metrics[url] ?: return 0.0
val errors = list.count { it.code >= 400 }
return errors.toDouble() / list.size
}
}实时网络监控
kotlin
class RealTimeNetworkMonitor {
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
Log.d("Network", "Network available")
}
override fun onLost(network: Network) {
Log.d("Network", "Network lost")
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
val subtype = networkCapabilities.transportType
Log.d("Network", "Network changed: $subtype")
}
}
fun start() {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.registerDefaultNetworkCallback(networkCallback)
}
fun stop() {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.unregisterNetworkCallback(networkCallback)
}
}面试考点
基础考点
1. HTTP 缓存机制
问题: HTTP 缓存的强缓存和协商缓存有什么区别?
回答:
- 强缓存: 直接使用本地缓存,不请求服务器。通过 Cache-Control、Expires 控制
- 协商缓存: 先请求服务器,服务器判断缓存是否有效。通过 ETag、Last-Modified 控制
http
# 强缓存
Cache-Control: max-age=3600
# 协商缓存
ETag: "abc123"
If-None-Match: "abc123" # 返回 304 则使用缓存2. OkHttp 连接池
问题: OkHttp 连接池如何工作的?
回答:
- OkHttp 使用 ConnectionPool 管理空闲连接
- 默认保持 5 个空闲连接,5 分钟后关闭
- 相同域名的请求会复用连接
- 支持 HTTP/2 多路复用
3. 弱网优化
问题: 弱网环境下如何优化应用体验?
回答:
- 图片质量自适应
- 减少请求大小(Gzip/Protobuf)
- 实现断点续传
- 增加重试机制
- 离线缓存策略
进阶考点
1. 请求合并优化
问题: 如何实现多个 API 请求的合并?
回答:
- 后端合并接口(GraphQL 模式)
- 前端请求队列合并
- 使用单例 OkHttpClient 复用连接
- 防抖和节流机制
2. DNS 预解析
问题: DNS 预解析如何提升性能?
回答:
- 减少 DNS 查询时间(通常 50-200ms)
- 应用启动时预解析常用域名
- 实现 DNS 缓存机制
- 使用 DNS 预取(DNS Prefetch)
3. 连接复用
问题: 如何提高连接复用率?
回答:
- 使用单例 OkHttpClient
- 合理配置连接池参数
- 避免频繁创建新客户端
- 使用 HTTP/2 多路复用
高级考点
1. QUIC 协议
问题: QUIC 协议相比 TCP 有什么优势?
回答:
- 基于 UDP,减少握手延迟
- 支持多路复用,避免队头阻塞
- 连接迁移支持
- 加密更安全
2. 网络监控体系
问题: 如何设计完善的网络监控系统?
回答:
- 请求成功率监控
- 响应时间监控(P50/P95/P99)
- 错误类型分类
- 实时告警机制
- 性能趋势分析
3. 离线优先架构
问题: 如何实现离线优先的应用架构?
回答:
- 本地数据库同步
- 请求队列管理
- 数据冲突解决
- 后台同步机制
最佳实践
网络层架构
kotlin
// 网络层架构
interface NetworkService {
fun <T> request(url: String): Flow<T>
}
class NetworkServiceImpl : NetworkService {
private val client = OkHttpClient.Builder()
.connectionPool(ConnectionPool(10, 5, TimeUnit.MINUTES))
.addInterceptor(HeaderInterceptor())
.addInterceptor(RetryInterceptor())
.addInterceptor(CacheInterceptor())
.addInterceptor(MonitorInterceptor())
.build()
override fun <T> request(url: String): Flow<T> {
return flow {
val request = Request.Builder().url(url).build()
val response = client.newCall(request).execute()
emit(parseResponse(response))
}
}
}常见错误
1. 内存泄漏
kotlin
// ❌ 错误:未取消请求
class ViewModel : ViewModel() {
private val call = api.getData().enqueue(/* callback */)
}
// ✅ 正确:使用复合键取消
class ViewModel : ViewModel() {
private val compositeDisposable = CompositeDisposable()
init {
compositeDisposable.add(
api.getData()
.subscribe(/* callback */)
)
}
override fun onCleared() {
compositeDisposable.clear()
}
}2. 主线程网络请求
kotlin
// ❌ 错误:主线程请求
val response = client.newCall(request).execute()
// ✅ 正确:异步请求
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
// 处理响应
}
})
// ✅ 或使用协程
coroutineScope {
val response = withContext(Dispatchers.IO) {
client.newCall(request).execute()
}
}3. 未处理异常
kotlin
// ❌ 错误:未处理异常
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {}
override fun onFailure(call: Call, e: IOException) {} // 未处理
})
// ✅ 正确:完整异常处理
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {}
override fun onFailure(call: Call, e: IOException) {
showError("网络错误:${e.message}")
retryOrShowCache()
}
})总结
网络优化是 Android 性能优化的核心内容之一。通过缓存策略、请求合并、数据压缩、连接池优化、DNS 预解析、接口预加载、弱网优化和性能监控等手段,可以显著提升应用的网络性能。
关键要点
- 缓存策略:合理配置 HTTP 缓存和应用层缓存
- 请求合并:减少网络请求次数
- 数据压缩:使用 Gzip 和 Protobuf 减少传输量
- 连接池:复用连接,减少握手开销
- DNS 预解析:提前解析域名
- 接口预加载:提前加载可能需要的数据
- 弱网优化:适应不同网络环境
- 性能监控:实时监控网络性能
性能提升效果
| 优化项 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 首屏加载时间 | 3.5s | 1.2s | 65% |
| 网络请求次数 | 20 次 | 8 次 | 60% |
| 流量消耗 | 5MB | 2MB | 60% |
| 请求成功率 | 95% | 99.5% | 4.5% |
通过系统的网络优化,可以显著提升用户体验,减少流量消耗,降低电量消耗,提高应用稳定性。