Appearance
05_网络优化
目录
网络性能概述
1.1 网络性能指标
关键指标:
TTFB (Time To First Byte) - 首字节时间
RTT (Round Trip Time) - 往返时间
Throughput - 吞吐量
Latency - 延迟
Jitter - 网络抖动
Packet Loss - 丢包率Android 中测量:
kotlin
// 使用 StrictMode 测量网络操作
StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder()
.detectNetwork()
.penaltyLog()
.build())
// 测量请求时间
val start = System.currentTimeMillis()
client.newCall(request).execute()
val duration = System.currentTimeMillis() - start
Log.d("Network", "Request took $duration ms")网络性能分类:
优秀:< 200ms
良好:200-500ms
一般:500-1000ms
较差:1000-3000ms
差:> 3000ms1.2 网络性能影响因素
1. 网络类型
- WiFi:速度快,稳定
- 4G/5G:速度快,可能不稳定
- 3G:速度中等
- 2G:速度慢
2. 网络质量
- 信号强度
- 丢包率
- 延迟
3. 服务器性能
- 响应时间
- 并发能力
- 带宽
4. 客户端性能
- 解析速度
- 缓存策略
- 并发管理1.3 性能优化思路
┌──────────────────────────────────────┐
│ 网络性能优化 │
├──────────────────────────────────────┤
│ 请求端优化 │
│ ├─ 请求合并 │
│ ├─ 请求压缩 │
│ ├─ 连接池 │
│ └─ DNS 优化 │
├──────────────────────────────────────┤
│ 传输端优化 │
│ ├─ HTTP/2 多路复用 │
│ ├─ 数据压缩(Gzip/Brotli) │
│ └─ 二进制协议(Protobuf) │
├──────────────────────────────────────┤
│ 缓存优化 │
│ ├─ 内存缓存 │
│ ├─ 磁盘缓存 │
│ └─ CDN 缓存 │
├──────────────────────────────────────┤
│ 弱网优化 │
│ ├─ 降级策略 │
│ ├─ 离线缓存 │
│ └─ 预加载 │
└──────────────────────────────────────┘请求优化
2.1 请求合并
问题场景:
kotlin
// ❌ 多个独立请求
fun loadUserDetails(userId: String) {
service.getUser(userId) // 请求 1
service.getUserProfile(userId) // 请求 2
service.getUserFriends(userId) // 请求 3
service.getUserPosts(userId) // 请求 4
}
// 4 次 HTTP 请求,4 次握手,4 次响应优化方案:
kotlin
// ✅ 合并请求
interface ApiService {
@GET("user/details")
suspend fun getUserDetails(@Query("id") userId: String): UserDetailsResponse
}
data class UserDetailsResponse(
val user: User,
val profile: UserProfile,
val friends: List<Friend>,
val posts: List<Post>
)
fun loadUserDetails(userId: String) {
service.getUserDetails(userId) // 1 次请求
}GraphQL 方案:
graphql
query GetUserDetails($userId: ID!) {
user(id: $userId) {
id
name
email
profile {
avatar
bio
}
friends {
id
name
}
posts {
id
title
content
}
}
}2.2 请求压缩(Gzip)
开启 Gzip 压缩:
kotlin
// OkHttp 默认支持 Gzip
val client = OkHttpClient()
// 验证是否启用
val request = Request.Builder()
.url("https://api.example.com/data")
.header("Accept-Encoding", "gzip, deflate")
.build()
val response = client.newCall(request).execute()
val encoding = response.header("Content-Encoding") // "gzip"Gzip 压缩效果:
原始数据:100KB
Gzip 压缩后:30-40KB
压缩率:60-70%Brotli 压缩(更高效的压缩):
kotlin
// 需要服务器支持
val client = OkHttpClient.Builder()
.protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1))
.build()
// Brotli 压缩率通常比 Gzip 高 15-20%2.3 连接池优化
默认配置:
kotlin
val client = OkHttpClient()
// 默认:maxIdleConnections=5, keepAliveDuration=5min优化配置:
kotlin
val connectionPool = ConnectionPool().apply {
// 增加最大空闲连接数
// 根据并发请求数调整
maxSize = 10
// 延长空闲连接保持时间
keepAliveDuration = 10L, TimeUnit.MINUTES
}
val client = OkHttpClient.Builder()
.connectionPool(connectionPool)
.build()连接池监控:
kotlin
connectionPool.evictionListener = { address, reason ->
when (reason) {
ConnectionPool.CLOSED -> {
Log.d("Pool", "Connection closed: $address")
}
ConnectionPool.IDLE -> {
Log.d("Pool", "Connection idle timeout: $address")
}
ConnectionPool.MAX_IDLE -> {
Log.d("Pool", "Connection max idle: $address")
}
}
}
// 获取连接池统计
fun getConnectionPoolStats(pool: ConnectionPool): ConnectionPoolStats {
// 通过反射获取内部状态
return ConnectionPoolStats(
connectionCount = pool.size(),
maxSize = pool.maxSize()
)
}2.4 DNS 优化
问题:DNS 解析耗时
DNS 解析:50-500ms优化方案 1:DNS 缓存:
kotlin
class DnsCache {
private val cache = LruCache<String, List<InetSocketAddress>>(100)
fun lookup(hostName: String): List<InetSocketAddress> {
return cache.get(hostName) ?: run {
val addresses = InetAddress.getAllByName(hostName)
.toList()
.map { InetSocketAddress(it, 443) }
cache.put(hostName, addresses)
addresses
}
}
}
val client = OkHttpClient.Builder()
.dns(DnsCache())
.build()优化方案 2:使用公共 DNS:
kotlin
// 使用更快的 DNS 服务器
// Google: 8.8.8.8, 8.8.4.4
// Cloudflare: 1.1.1.1优化方案 3:HTTP/2 减少 DNS 查询:
HTTP/1.1: 每个域名需要独立连接
HTTP/2: 多路复用,单连接处理多域名2.5 请求预加载
预加载策略:
kotlin
class PreloadManager {
private val preloadQueue = ArrayDeque<PreloadTask>()
fun addPreload(task: PreloadTask) {
preloadQueue.add(task)
executeNextPreload()
}
private fun executeNextPreload() {
val task = preloadQueue.firstOrNull() ?: return
// 在后台执行预加载
CoroutineScope(Dispatchers.IO).launch {
try {
task.execute()
// 存储到缓存
cache.put(task.key, task.result)
} finally {
preloadQueue.removeFirst()
executeNextPreload() // 执行下一个
}
}
}
}
data class PreloadTask(
val key: String,
val execute: suspend () -> Any
)
// 使用示例
class NextPagePreload : PreloadTask(
key = "page_${currentPage + 1}",
execute = {
service.getUsers(page = currentPage + 1)
}
)
// 用户滚动到页面底部时预加载下一页
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val visibleItemCount = recyclerView.childCount
val totalItemCount = recyclerView.adapter?.itemCount ?: 0
val lastVisibleItem =
(recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
// 到达底部前 2 页开始预加载
if (lastVisibleItem + visibleItemCount >= totalItemCount - 2) {
preloadManager.addPreload(NextPagePreload())
}
}
})2.6 减少请求头大小
自定义 User-Agent:
kotlin
// 过长的 User-Agent
val userAgent = "Mozilla/5.0 (Linux; Android 14; SM-G998B Build/UP1A.231005.007; " +
"wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.0.0 " +
"Mobile Safari/537.36" // 150+ 字符
// 优化的 User-Agent
val userAgent = "MyApp/1.0 (Android 14)" // 25 字符减少 Cookie:
kotlin
// 只携带必要的 Cookie
class CookieFilterInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val cookieHeader = request.header("Cookie")
val filteredCookies = cookieHeader
?.split(";")
?.filter { it.contains("session=") || it.contains("token=") }
?.joinToString("; ")
val builder = request.newBuilder()
if (filteredCookies.isNullOrBlank()) {
builder.removeHeader("Cookie")
} else {
builder.header("Cookie", filteredCookies)
}
return chain.proceed(builder.build())
}
}响应优化
3.1 使用轻量级序列化
JSON vs Protobuf:
kotlin
// JSON (1KB)
"""
{
"userId": 123,
"userName": "张三",
"userEmail": "zhangsan@example.com",
"userPosts": [
{
"postId": 1,
"postTitle": "标题",
"postContent": "内容"
}
]
}
"""
// Protobuf (约 200 字节)
// 二进制格式,解析更快Protobuf 定义:
proto
syntax = "proto3";
message User {
int32 user_id = 1;
string user_name = 2;
string user_email = 3;
repeated Post posts = 4;
}
message Post {
int32 post_id = 1;
string post_title = 2;
string post_content = 3;
}Android 中使用:
kotlin
// 生成 Kotlin 代码
val user = User.newBuilder()
.setUserId(123)
.setName("张三")
.build()
// 序列化
val data = user.toByteArray()
val size = data.size // 约 50 字节
// 反序列化
val parsed = User.parseFrom(data)性能对比:
JSON:
- 大小:1KB
- 解析时间:5ms
Protobuf:
- 大小:200 字节 (80% 节省)
- 解析时间:1ms (80% 更快)3.2 增量更新
问题场景:
kotlin
// 每次请求返回完整数据
GET /api/feed
返回:100 条完整帖子数据 (500KB)优化:增量更新:
kotlin
// 只返回变化的数据
data class IncrementalUpdate(
val lastUpdated: Long,
val newItems: List<Item>,
val updatedItems: List<Item>,
val removedItemIds: List<String>
)
// 客户端维护本地状态
class FeedManager {
private val items = mutableListOf<Item>()
private var lastUpdateTime = 0L
suspend fun refresh() {
val update = service.getIncrementalUpdate(lastUpdateTime)
// 更新本地数据
update.newItems.forEach { items.add(it) }
update.updatedItems.forEach { updated ->
val index = items.indexOfFirst { it.id == updated.id }
if (index != -1) {
items[index] = updated
}
}
update.removedItemIds.forEach { items.removeAll { item -> item.id == it } }
lastUpdateTime = update.lastUpdated
}
}3.3 服务端游标 vs 客户端分页
客户端分页(传统):
kotlin
GET /api/users?page=1&size=20
GET /api/users?page=2&size=20
GET /api/users?page=3&size=20
问题:
- 数据更新时页码可能错乱
- 不能获取特定位置的数据服务端游标(推荐):
kotlin
GET /api/users?cursor=abc123&limit=20
响应:
{
"data": [...],
"nextCursor": "xyz789",
"hasMore": true
}
GET /api/users?cursor=xyz789&limit=20实现:
kotlin
sealed class CursorPagedSource<out T> : PagingSource<String, T>() {
data class Config(val key: String, val limit: Int)
override suspend fun load(params: LoadParams<String>): LoadResult<String, T> {
val cursor = params.key ?: INITIAL_CURSOR
val response = service.getWithCursor(cursor, params.loadSize)
return LoadResult.Page(
data = response.data,
prevKey = response.prevCursor,
nextKey = response.nextCursor ?: null
)
}
}
// 使用 Paging 3
val pagingData = Pager(Config(INITIAL_CURSOR, 20)) {
CursorPagedSource()
}.flow3.4 响应体压缩
Gzip 配置:
kotlin
// OkHttp 自动处理 Gzip 解压
val response = client.newCall(request).execute()
val body = response.body?.string() // 自动解压自定义压缩:
kotlin
class CompressionInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
val encoding = response.header("Content-Encoding")
return when (encoding) {
"gzip" -> response
"br" -> response // OkHttp 自动处理
else -> response
}
}
}缓存策略
4.1 多级缓存架构
┌─────────────────────────────────────┐
│ 内存缓存 │
│ (LruCache) │
│ 快速访问 │
└──────────────┬──────────────────────┘
│ 未命中
▼
┌─────────────────────────────────────┐
│ 磁盘缓存 │
│ (OkHttp Cache) │
│ 持久化 │
└──────────────┬──────────────────────┘
│ 未命中
▼
┌─────────────────────────────────────┐
│ 网络请求 │
│ (服务器) │
│ 获取最新数据 │
└─────────────────────────────────────┘实现:
kotlin
class MultiLevelCache<T>(
private val memoryCache: LruCache<String, T>,
private val diskCache: Cache
) {
fun get(key: String): T? {
// 1. 检查内存缓存
memoryCache.get(key)?.let { return it }
// 2. 检查磁盘缓存
diskCache.get(key)?.let { data ->
val parsed = parse(data)
memoryCache.put(key, parsed)
return parsed
}
return null
}
fun put(key: String, value: T) {
memoryCache.put(key, value)
diskCache.put(key, serialize(value))
}
}4.2 OkHttp 缓存配置
基本配置:
kotlin
val cache = Cache(
directory = File(context.cacheDir, "http"),
maxSize = 100L * 1024 * 1024 // 100MB
)
val client = OkHttpClient.Builder()
.cache(cache)
.build()缓存控制策略:
kotlin
// 1. 强制缓存 1 小时
val request = Request.Builder()
.url("https://api.example.com/data")
.header("Cache-Control", "max-age=3600")
.build()
// 2. 不使用缓存
val request = Request.Builder()
.url("https://api.example.com/data")
.header("Cache-Control", "no-cache")
.build()
// 3. 离线模式(只使用缓存)
val request = Request.Builder()
.url("https://api.example.com/data")
.header("Cache-Control", "only-if-cached, max-stale=604800")
.build()缓存拦截器:
kotlin
class CacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
// 检查网络状态
val isConnected = isNetworkAvailable()
if (!isConnected) {
// 离线模式
return chain.proceed(
request.newBuilder()
.header("Cache-Control", "only-if-cached, max-stale=604800")
.build()
)
}
val response = chain.proceed(request)
// 根据响应类型设置缓存
return when {
response.code == 200 && shouldCache(request.url) -> {
response.newBuilder()
.header("Cache-Control", "max-age=3600")
.build()
}
else -> response
}
}
private fun shouldCache(url: HttpUrl): Boolean {
return url.host.contains("api.") && !url.pathSegments.contains("auth")
}
}4.3 缓存失效策略
时间-based 失效:
kotlin
class TimeBasedCache {
data class CacheEntry<T>(
val data: T,
val timestamp: Long,
val ttl: Long
) {
fun isExpired(): Boolean = System.currentTimeMillis() - timestamp > ttl
}
private val cache = LruCache<String, CacheEntry<Any>>(100)
fun <T> get(key: String, ttl: Long): T? {
val entry = cache.get(key) as? CacheEntry<T> ?: return null
return if (entry.isExpired()) {
cache.remove(key)
null
} else {
entry.data
}
}
fun <T> put(key: String, data: T, ttl: Long) {
cache.put(key, CacheEntry(data, System.currentTimeMillis(), ttl))
}
}版本-based 失效:
kotlin
class VersionedCache {
private var cacheVersion = 1
private val cache = LruCache<String, Any>(100)
fun invalidate() {
cacheVersion++
cache.evictAll()
}
fun get(key: String): Any? {
val versionedKey = "$cacheVersion:$key"
return cache.get(versionedKey)
}
fun put(key: String, value: Any) {
val versionedKey = "$cacheVersion:$key"
cache.put(versionedKey, value)
}
}手动失效:
kotlin
class ManualInvalidateCache {
private val cache = LruCache<String, Any>(100)
fun invalidate(key: String) {
cache.remove(key)
}
fun invalidateAll() {
cache.evictAll()
}
fun invalidatePattern(pattern: Regex) {
// 使用正则匹配失效
}
}弱网优化
5.1 弱网检测
网络质量检测:
kotlin
class NetworkQualityMonitor {
enum class NetworkQuality {
EXCELLENT, GOOD, FAIR, POOR, OFFLINE
}
private val _quality = MutableStateFlow(NetworkQuality.EXCELLENT)
val quality: StateFlow<NetworkQuality> = _quality
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
testNetworkQuality()
}
override fun onLost(network: Network) {
_quality.value = NetworkQuality.OFFLINE
}
}
private fun testNetworkQuality() {
CoroutineScope(Dispatchers.IO).launch {
val start = System.currentTimeMillis()
val result = try {
client.newCall(
Request.Builder()
.url("https://www.google.com/generate_204")
.build()
).execute()
val duration = System.currentTimeMillis() - start
when {
duration < 300 -> NetworkQuality.EXCELLENT
duration < 1000 -> NetworkQuality.GOOD
duration < 3000 -> NetworkQuality.FAIR
else -> NetworkQuality.Poor
}
} catch (e: Exception) {
NetworkQuality.OFFLINE
}
_quality.value = result
}
}
}5.2 降级策略
根据网络质量调整:
kotlin
class AdaptiveLoadManager(private val qualityMonitor: NetworkQualityMonitor) {
fun loadImage(url: String, imageView: ImageView) {
val quality = qualityMonitor.quality.value
val options = when (quality) {
NetworkQuality.EXCELLENT -> {
// 高质量图片
Glide.with(imageView.context)
.load(url)
.override(1080, 1920)
}
NetworkQuality.GOOD -> {
// 中等质量
Glide.with(imageView.context)
.load(url)
.override(720, 1280)
}
NetworkQuality.FAIR -> {
// 低质量
Glide.with(imageView.context)
.load(url)
.override(480, 800)
}
NetworkQuality.POOR -> {
// 只加载缩略图
Glide.with(imageView.context)
.load(url)
.override(240, 400)
}
NetworkQuality.OFFLINE -> {
// 显示缓存或占位图
Glide.with(imageView.context)
.load(R.drawable.offline_placeholder)
}
}
options.into(imageView)
}
fun loadList(size: Int): Int {
val quality = qualityMonitor.quality.value
return when (quality) {
NetworkQuality.EXCELLENT -> size
NetworkQuality.GOOD -> size
NetworkQuality.FAIR -> size / 2
NetworkQuality.POOR -> size / 4
NetworkQuality.OFFLINE -> 0 // 使用缓存
}
}
}5.3 离线缓存
离线数据管理:
kotlin
class OfflineDataManager {
private val database = Room.databaseBuilder(
context,
OfflineDatabase::class.java,
"offline.db"
).build()
suspend fun saveForOffline(page: Page) {
database.pageDao().insert(page)
}
suspend fun getOfflinePage(pageNumber: Int): Page? {
return database.pageDao().get(pageNumber)
}
fun isOfflineDataAvailable(pageNumber: Int): Boolean {
// 检查缓存
}
}
// 使用
class Repository {
private val offlineManager = OfflineDataManager()
suspend fun getData(page: Int): List<Item> {
return withContext(Dispatchers.IO) {
// 1. 尝试网络请求
try {
val data = networkService.getData(page)
offlineManager.saveForOffline(page)
data
} catch (e: Exception) {
// 2. 网络失败,返回缓存
offlineManager.getOfflinePage(page)?.items ?: emptyList()
}
}
}
}5.4 数据压缩传输
启用 Gzip:
kotlin
// 服务端配置
// Content-Encoding: gzip
// 客户端自动处理
val client = OkHttpClient() // 默认支持 Gzip自定义压缩:
kotlin
class CompressRequestBodyInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
if (request.body != null) {
val originalBody = request.body!!
val compressed = GzipCompressor.compress(originalBody.bytes())
val compressedBody = RequestBody.create(
originalBody.contentType(),
compressed
)
val newRequest = request.newBuilder()
.header("Content-Encoding", "gzip")
.body(compressedBody)
.build()
return chain.proceed(newRequest)
}
return chain.proceed(request)
}
}5.5 请求优先级
重要请求优先:
kotlin
class PriorityDispatcher {
enum class Priority { HIGH, NORMAL, LOW }
private val highQueue = LinkedBlockingQueue<Request>()
private val normalQueue = LinkedBlockingQueue<Request>()
private val lowQueue = LinkedBlockingQueue<Request>()
fun enqueue(request: Request, priority: Priority) {
when (priority) {
Priority.HIGH -> highQueue.add(request)
Priority.NORMAL -> normalQueue.add(request)
Priority.LOW -> lowQueue.add(request)
}
processNext()
}
private fun processNext() {
val request = when {
highQueue.isNotEmpty() -> highQueue.poll()
normalQueue.isNotEmpty() -> normalQueue.poll()
lowQueue.isNotEmpty() -> lowQueue.poll()
else -> return
}
CoroutineScope(Dispatchers.IO).launch {
execute(request)
processNext() // 处理下一个
}
}
}
// 使用
priorityDispatcher.enqueue(userRequest, Priority.HIGH)
priorityDispatcher.enqueue(imageRequest, Priority.LOW)5.6 超时和重试
配置超时:
kotlin
val client = OkHttpClient.Builder()
// 连接超时
.connectTimeout(5, TimeUnit.SECONDS)
// 读取超时
.readTimeout(15, TimeUnit.SECONDS)
// 写入超时
.writeTimeout(10, TimeUnit.SECONDS)
// DNS 超时
.dns(DnsCache())
.build()智能重试:
kotlin
class RetryInterceptor : Interceptor {
private val maxRetry = 3
private val retryDelay = 1000L
override fun intercept(chain: Interceptor.Chain): Response {
var attempt = 0
var response = chain.proceed(chain.request())
while (!response.isSuccessful && attempt < maxRetry) {
attempt++
// 可重试的状态码
if (response.code !in listOf(400, 401, 403, 404, 405)) {
// 指数退避
val delay = retryDelay * attempt
Thread.sleep(delay)
response = chain.proceed(chain.request())
} else {
break
}
}
return response
}
}图片加载优化
6.1 图片懒加载
RecyclerView 懒加载:
kotlin
class ImageAdapter(
private val items: List<Item>,
private val imageLoader: ImageLoader
) : RecyclerView.Adapter<ImageAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.image)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
// 检查是否可见
if (isViewVisible(holder.itemView)) {
imageLoader.load(item.imageUrl, holder.imageView)
} else {
// 显示占位图
holder.imageView.setImageResource(R.drawable.placeholder)
}
}
private fun isViewVisible(view: View): Boolean {
val layoutManager = view.parent as? RecyclerView?.layoutManager
return layoutManager?.findFirstVisibleItemPosition()?.let { first ->
val last = layoutManager.findLastVisibleItemPosition()
val position = (view.parent as RecyclerView.ViewHolder).position
position in first..last
} ?: false
}
override fun onViewRecycled(holder: ViewHolder) {
// 取消未完成的加载
imageLoader.cancel(holder.imageView)
super.onViewRecycled(holder)
}
}6.2 图片尺寸优化
按需加载:
kotlin
// ❌ 错误:加载完整尺寸图片
Glide.with(context)
.load("https://example.com/image.jpg")
.into(imageView)
// ✅ 正确:根据视图尺寸加载
val targetWidth = imageView.width
val targetHeight = imageView.height
Glide.with(context)
.load("https://example.com/image.jpg")
.override(targetWidth, targetHeight)
.into(imageView)多尺寸图片:
kotlin
// 服务器提供多尺寸
data class ImageUrls(
val thumbnail: String, // 100x100
val medium: String, // 400x400
val large: String // 1000x1000
)
fun loadImage(imageUrls: ImageUrls, targetWidth: Int, targetHeight: Int) {
val url = when {
targetWidth < 200 -> imageUrls.thumbnail
targetWidth < 600 -> imageUrls.medium
else -> imageUrls.large
}
Glide.with(context)
.load(url)
.into(imageView)
}6.3 图片格式优化
WebP 格式:
kotlin
// WebP 比 JPEG 小 30%
// 比 PNG 小 26%
// Glide 自动支持 WebP
Glide.with(context)
.load("https://example.com/image.webp")
.into(imageView)
// 优先加载 WebP
Glide.with(context)
.load("https://example.com/image")
.format(PictureFormat.WEBP)
.into(imageView)图片压缩:
kotlin
class ImageCompressor {
fun compress(image: Bitmap, maxWidth: Int, maxHeight: Int): Bitmap {
var options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeByteArray(image.toByteArray(), 0, image.toByteArray().size, options)
val outWidth = options.outWidth
val outHeight = options.outHeight
var sampleSize = 1
if (outWidth > maxWidth || outHeight > maxHeight) {
sampleSize = (outWidth.toFloat() / maxWidth).toInt()
}
options.inSampleSize = sampleSize
options.inJustDecodeBounds = false
return BitmapFactory.decodeByteArray(image.toByteArray(), 0, image.toByteArray().size, options)
}
}6.4 图片缓存优化
Glide 缓存配置:
kotlin
// 内存缓存配置
GlideMemoryCacheModule(
LruResourceCache(context),
LruBitmapCache(context)
)
// 磁盘缓存配置
Glide.with(context)
.setDiskCache(DiskLruCacheWrapper(
context.cacheDir.resolve("glide"),
100L * 1024 * 1024 // 100MB
))缓存策略:
kotlin
Glide.with(context)
.load(url)
.skipMemoryCache(false) // 保存到内存缓存
.diskCacheStrategy(DiskCacheStrategy.ALL) // 磁盘缓存策略
.into(imageView)性能监控
7.1 请求性能监控
拦截器监控:
kotlin
class PerformanceMonitoringInterceptor : Interceptor {
private val metrics = mutableListOf<NetworkMetric>()
override fun intercept(chain: Interceptor.Chain): Response {
val start = System.currentTimeMillis()
val url = chain.request().url.toString()
val response = chain.proceed(chain.request())
val end = System.currentTimeMillis()
val duration = end - start
val metric = NetworkMetric(
url = url,
method = chain.request().method,
code = response.code,
size = response.body?.contentLength() ?: 0,
duration = duration,
timestamp = start
)
synchronized(metrics) {
metrics.add(metric)
}
Log.d("Network", "$url took $duration ms (${metric.size} bytes)")
return response
}
}
data class NetworkMetric(
val url: String,
val method: String,
val code: Int,
val size: Long,
val duration: Long,
val timestamp: Long
)7.2 性能报告生成
生成性能报告:
kotlin
class PerformanceReportGenerator {
fun generateReport(metrics: List<NetworkMetric>): PerformanceReport {
val totalRequests = metrics.size
val avgDuration = metrics.average { it.duration }
val avgSize = metrics.average { it.size }
val slowRequests = metrics.filter { it.duration > 1000 }
val errors = metrics.filter { it.code >= 400 }
return PerformanceReport(
totalRequests = totalRequests,
avgDuration = avgDuration,
avgSize = avgSize,
slowRequestCount = slowRequests.size,
errorCount = errors.size,
slowRequests = slowRequests,
errors = errors
)
}
}
data class PerformanceReport(
val totalRequests: Int,
val avgDuration: Double,
val avgSize: Double,
val slowRequestCount: Int,
val errorCount: Int,
val slowRequests: List<NetworkMetric>,
val errors: List<NetworkMetric>
)7.3 实时性能监控
监控 Dashboard:
kotlin
class NetworkMonitor {
private val _metrics = MutableStateFlow<List<NetworkMetric>>(emptyList())
val metrics: StateFlow<List<NetworkMetric>> = _metrics
private val _currentRequests = MutableStateFlow(0)
val currentRequests: StateFlow<Int> = _currentRequests
fun onRequestStart() {
synchronized(_currentRequests) {
_currentRequests.value++
}
}
fun onRequestEnd(metric: NetworkMetric) {
synchronized(_currentRequests) {
_currentRequests.value--
}
synchronized(metrics) {
_metrics.value = _metrics.value + metric
}
}
fun getStats(): NetworkStats {
val allMetrics = _metrics.value
return NetworkStats(
totalRequests = allMetrics.size,
avgDuration = allMetrics.average { it.duration },
totalBytes = allMetrics.sumOf { it.size },
errorRate = allMetrics.count { it.code >= 400 }.toDouble() / allMetrics.size
)
}
}代码示例
8.1 完整的网络优化方案
kotlin
object OptimizedApiClient {
// 1. 连接池配置
private val connectionPool = ConnectionPool().apply {
maxSize = 10
keepAliveDuration = 10L, TimeUnit.MINUTES
}
// 2. 缓存配置
private val httpCache = Cache(
File(FileUtils.getCacheDir(), "http"),
100L * 1024 * 1024 // 100MB
)
// 3. DNS 缓存
private val dnsCache = DnsCache()
// 4. 创建 OkHttpClient
val client: OkHttpClient = OkHttpClient.Builder()
.connectionPool(connectionPool)
.cache(httpCache)
.dns(dnsCache)
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.addInterceptor(PerformanceMonitoringInterceptor())
.addInterceptor(CacheInterceptor())
.addInterceptor(RetryInterceptor())
.build()
// 5. 创建 Retrofit
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
// 6. API Service
fun <T> create(service: Class<T>): T = retrofit.create(service)
}
// 使用示例
class UserRepository {
private val api = OptimizedApiClient.retrofit.create(UserApi::class.java)
private val memoryCache = LruCache<User>(20)
suspend fun getUser(userId: String): User {
// 检查内存缓存
memoryCache.get(userId)?.let { return it }
// 网络请求(自动使用缓存)
val user = api.getUser(userId)
// 存入缓存
memoryCache.put(userId, user)
return user
}
suspend fun getUsers(page: Int, size: Int): List<User> {
return api.getUsers(page, size)
}
}8.2 图片加载优化示例
kotlin
class OptimizedImageLoader {
fun loadOptimized(
context: Context,
url: String,
imageView: ImageView,
placeholder: Int = R.drawable.placeholder,
error: Int = R.drawable.error
) {
// 获取目标尺寸
val requestOptions = RequestOptions()
.placeholder(placeholder)
.error(error)
.centerCrop()
.override(
imageView.width.takeIf { it > 0 },
imageView.height.takeIf { it > 0 }
)
// 启用 WebP
.format(PictureFormat.WEBP)
// 内存缓存
.skipMemoryCache(false)
// 磁盘缓存
.diskCacheStrategy(DiskCacheStrategy.ALL)
// 淡入动画
.transition(withCrossFade())
Glide.with(context)
.load(url)
.apply(requestOptions)
.into(imageView)
}
// 列表图片优化
fun loadForList(
context: Context,
url: String,
imageView: ImageView,
position: Int
) {
// 根据列表项尺寸加载
val targetWidth = imageView.width.takeIf { it > 0 } ?: 200
val targetHeight = imageView.height.takeIf { it > 0 } ?: 200
Glide.with(context)
.load(url)
.override(targetWidth, targetHeight)
.centerCrop()
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.tag("list_$position")
.into(imageView)
}
// 取消请求
fun cancel(view: ImageView) {
Glide.with(view.context)
.clear(view)
}
}
// RecyclerView 中使用
class OptimizedImageAdapter : RecyclerView.Adapter<OptimizedImageAdapter.ViewHolder>() {
private val items = mutableListOf<String>()
private val imageLoader = OptimizedImageLoader()
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.image)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_image, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val url = items[position]
imageLoader.loadForList(holder.itemView.context, url, holder.imageView, position)
}
override fun onViewRecycled(holder: ViewHolder) {
imageLoader.cancel(holder.imageView)
super.onViewRecycled(holder)
}
override fun getItemCount() = items.size
}面试考点
基础题
1. 如何优化网络请求速度?
1. 连接池:复用 TCP 连接
2. HTTP/2:多路复用
3. 压缩:Gzip/Brotli
4. 缓存:内存 + 磁盘
5. 减少请求数:合并请求
6. 懒加载:按需加载
7. CDN:就近访问2. 什么是弱网优化?
1. 降级策略:降低图片质量
2. 离线缓存:使用本地数据
3. 请求优先级:重要请求优先
4. 超时重试:智能重试机制
5. 数据压缩:减少传输量3. 缓存策略有哪些?
1. 内存缓存:快速访问
2. 磁盘缓存:持久化
3. CDN 缓存:就近访问
4. 浏览器缓存:客户端缓存
5. 组合缓存:多级缓存进阶题
1. 如何实现请求合并?
1. 批量请求:一次请求多个资源
2. GraphQL:按需提供数据
3. 接口聚合:服务端合并
4. 延迟合并:前端等待后合并2. HTTP/2 对性能的提升?
1. 多路复用:单连接并发
2. 头部压缩:减少传输
3. 服务器推送:主动推送
4. 二进制协议:解析更快3. 图片加载优化方案?
1. 按需尺寸:根据视图加载
2. 懒加载:可见区域加载
3. 格式优化:WebP 格式
4. 多级缓存:内存 + 磁盘
5. 占位图:提升体验高级题
1. 如何实现网络性能监控?
1. 拦截器:记录请求时间
2. 埋点:上报性能数据
3. 告警:异常检测
4. 分析:性能瓶颈定位
5. 优化:持续改进2. 弱网环境下的最佳实践?
1. 检测网络质量
2. 自适应降级
3. 离线优先
4. 预加载
5. 数据同步3. 如何优化列表加载性能?
1. 分页加载
2. 预加载下一页
3. 图片懒加载
4. 缓存利用
5. 异步加载总结
核心优化点
请求端优化
- 请求合并
- 连接池
- DNS 优化
传输优化
- 数据压缩
- HTTP/2
- 二进制协议
缓存策略
- 多级缓存
- 缓存失效
- 离线缓存
弱网优化
- 降级策略
- 离线优先
- 智能重试
性能监控
- 请求监控
- 性能报告
- 持续优化
最佳实践
kotlin
// 1. 全局共享 OkHttpClient
object ApiClient {
val client: OkHttpClient = OkHttpClient.Builder()
.connectionPool(ConnectionPool(10, 10, TimeUnit.MINUTES))
.cache(Cache(cacheDir, 100 * 1024 * 1024))
.build()
}
// 2. 使用缓存
client.cache().edit().apply {
// 配置缓存策略
}
// 3. 图片优化
Glide.with(context)
.load(url)
.override(width, height)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView)
// 4. 监控性能
client.addInterceptor(MonitoringInterceptor())
// 5. 弱网降级
when (networkQuality) {
EXCELLENT -> loadFullQuality()
POOR -> loadLowQuality()
OFFLINE -> loadCached()
}本文约 15000 字,涵盖了网络优化的核心知识点。