18 KiB
18 KiB
网络优化
目录
网络请求优化
1. 请求合并
问题:频繁请求
// ❌ 错误示例:频繁请求
for (int i = 0; i < 100; i++) {
api.getUserInfo(i).enqueue(callback);
}
解决方案:批量请求
// ✅ 正确做法:批量请求
List<Integer> userIds = new ArrayList<>();
for (int i = 0; i < 100; i++) {
userIds.add(i);
}
api.getBatchUserInfo(userIds).enqueue(callback);
2. 请求去重
public class RequestDeduplicator {
private final Map<String, Call> pendingRequests = new ConcurrentHashMap<>();
public <T> void execute(String key, Call<T> call, Callback<T> callback) {
Call<T> existingCall = (Call<T>) pendingRequests.get(key);
if (existingCall != null) {
// 已有相同请求,取消新请求
call.cancel();
return;
}
pendingRequests.put(key, call);
call.enqueue(new Callback<T>() {
@Override
public void onResponse(Call<T> call, Response<T> response) {
pendingRequests.remove(key);
callback.onResponse(call, response);
}
@Override
public void onFailure(Call<T> call, Throwable t) {
pendingRequests.remove(key);
callback.onFailure(call, t);
}
});
}
}
3. 请求优先级
public class PriorityInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 根据优先级设置
int priority = request.header("Priority") != null
? Integer.parseInt(request.header("Priority"))
: 0;
// 调整请求优先级
if (priority > 0) {
// 高优先级请求优先处理
}
return chain.proceed(request);
}
}
4. 请求重试
public class RetryInterceptor implements Interceptor {
private int maxRetries;
public RetryInterceptor(int maxRetries) {
this.maxRetries = maxRetries;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
IOException exception = null;
for (int i = 0; i <= maxRetries; i++) {
try {
response = chain.proceed(request);
if (response.isSuccessful()) {
return response;
}
} catch (IOException e) {
exception = e;
if (i == maxRetries) {
throw e;
}
}
// 等待后重试
try {
Thread.sleep(1000 * (i + 1));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
throw exception;
}
}
5. 连接池优化
OkHttpClient client = new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
.build();
6. 超时设置
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
图片加载优化
1. 使用图片加载库
// Glide:自动缓存、压缩、内存管理
Glide.with(context)
.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.into(imageView);
// 指定尺寸
Glide.with(context)
.load(url)
.override(200, 200)
.into(imageView);
2. 图片压缩
public Bitmap compressImage(Bitmap bitmap, int maxWidth, int maxHeight, int quality) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scale = Math.min((float) maxWidth / width, (float) maxHeight / height);
if (scale < 1.0f) {
int newWidth = Math.round(width * scale);
int newHeight = Math.round(height * scale);
bitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
byte[] data = baos.toByteArray();
return BitmapFactory.decodeByteArray(data, 0, data.length);
}
3. 使用 WebP 格式
// WebP 格式压缩率高
Glide.with(context)
.load(url)
.format(DecodeFormat.PREFER_WEBP)
.into(imageView);
4. 懒加载
public class LazyLoadImageView extends ImageView {
private String imageUrl;
public void setImageUrl(String url) {
this.imageUrl = url;
loadImage();
}
private void loadImage() {
if (isInViewport()) {
Glide.with(getContext())
.load(imageUrl)
.into(this);
}
}
private boolean isInViewport() {
Rect rect = new Rect();
getGlobalVisibleRect(rect);
return rect.width() > 0 && rect.height() > 0;
}
}
5. 预加载
// 预加载图片到缓存
Glide.with(context)
.load(url)
.preload();
// 预加载并获取 Bitmap
Glide.with(context)
.asBitmap()
.load(url)
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
// 预加载完成
}
});
数据压缩
1. Gzip 压缩
// 服务器端启用 Gzip
// 客户端自动解压(OkHttp 支持)
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new GzipRequestInterceptor())
.build();
public class GzipRequestInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest);
}
Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), gzip(originalRequest.body()))
.build();
return chain.proceed(compressedRequest);
}
private RequestBody gzip(final RequestBody body) {
return new RequestBody() {
@Override
public MediaType contentType() {
return body.contentType();
}
@Override
public long contentLength() {
return -1;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}
2. 数据格式优化
// 使用 Protobuf 替代 JSON
// Protobuf 体积更小,解析更快
// 使用 MessagePack 替代 JSON
// MessagePack 是二进制格式,体积更小
3. 分页加载
public class PagingHelper {
private int pageSize = 20;
private int currentPage = 0;
private boolean hasMore = true;
public void loadMore() {
if (!hasMore) {
return;
}
api.getData(currentPage, pageSize).enqueue(new Callback<DataResponse>() {
@Override
public void onResponse(Call<DataResponse> call, Response<DataResponse> response) {
if (response.isSuccessful()) {
DataResponse data = response.body();
if (data.getItems().size() < pageSize) {
hasMore = false;
}
currentPage++;
// 处理数据
}
}
@Override
public void onFailure(Call<DataResponse> call, Throwable t) {
// 处理错误
}
});
}
}
缓存策略
1. HTTP 缓存
// 使用 OkHttp 缓存
int cacheSize = 10 * 1024 * 1024; // 10MB
Cache cache = new Cache(context.getCacheDir(), cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
2. 内存缓存
public class MemoryCache {
private final LruCache<String, Bitmap> cache;
public MemoryCache() {
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
cache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
}
public Bitmap get(String key) {
return cache.get(key);
}
public void put(String key, Bitmap bitmap) {
cache.put(key, bitmap);
}
}
3. 磁盘缓存
public class DiskCache {
private final File cacheDir;
public DiskCache(Context context) {
cacheDir = new File(context.getCacheDir(), "images");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
}
public void put(String key, Bitmap bitmap) {
File file = new File(cacheDir, key);
try {
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public Bitmap get(String key) {
File file = new File(cacheDir, key);
if (file.exists()) {
return BitmapFactory.decodeFile(file.getAbsolutePath());
}
return null;
}
}
4. 三级缓存策略
public class ImageLoader {
private MemoryCache memoryCache;
private DiskCache diskCache;
private NetworkLoader networkLoader;
public void load(String url, ImageView imageView) {
// 1. 检查内存缓存
Bitmap bitmap = memoryCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
// 2. 检查磁盘缓存
bitmap = diskCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
memoryCache.put(url, bitmap);
return;
}
// 3. 从网络加载
networkLoader.load(url, new NetworkLoader.Callback() {
@Override
public void onSuccess(Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
memoryCache.put(url, bitmap);
diskCache.put(url, bitmap);
}
@Override
public void onFailure(Throwable t) {
// 处理错误
}
});
}
}
网络监控
1. 网络状态监听
public class NetworkMonitor {
private ConnectivityManager connectivityManager;
private NetworkCallback networkCallback;
public void register(Context context) {
connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
// 网络可用
}
@Override
public void onLost(Network network) {
// 网络不可用
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
// 网络能力变化
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
// WiFi 网络
} else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
// 移动网络
}
}
};
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build();
connectivityManager.registerNetworkCallback(request, networkCallback);
}
public void unregister() {
if (connectivityManager != null && networkCallback != null) {
connectivityManager.unregisterNetworkCallback(networkCallback);
}
}
}
2. 请求监控
public class NetworkMonitorInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long startTime = System.currentTimeMillis();
Response response = chain.proceed(request);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
// 记录请求信息
Log.d("Network", String.format(
"Request: %s, Duration: %dms, Size: %d bytes",
request.url(),
duration,
response.body() != null ? response.body().contentLength() : 0
));
return response;
}
}
3. 网络质量检测
public class NetworkQualityChecker {
public void checkNetworkQuality(Callback callback) {
long startTime = System.currentTimeMillis();
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.google.com")
.build();
client.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onResponse(Call call, Response response) {
long duration = System.currentTimeMillis() - startTime;
if (duration < 500) {
callback.onQuality(NetworkQuality.EXCELLENT);
} else if (duration < 1000) {
callback.onQuality(NetworkQuality.GOOD);
} else {
callback.onQuality(NetworkQuality.POOR);
}
}
@Override
public void onFailure(Call call, IOException e) {
callback.onQuality(NetworkQuality.POOR);
}
});
}
enum NetworkQuality {
EXCELLENT, GOOD, POOR
}
interface Callback {
void onQuality(NetworkQuality quality);
}
}
网络优化工具
1. Chrome DevTools
- 查看网络请求
- 分析请求时间
- 查看请求大小
- 检查缓存策略
2. Charles/Fiddler
- 抓包工具
- 查看请求响应
- 模拟慢网络
- 修改请求响应
3. Network Profiler
- Android Studio -> View -> Tool Windows -> Profiler
- 实时监控网络请求
- 查看请求时间
- 分析网络性能
4. Stetho
// 集成 Stetho
dependencies {
implementation 'com.facebook.stetho:stetho:1.5.1'
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
}
// 初始化
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Stetho.initializeWithDefaults(this);
}
}
// 添加到 OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new StethoInterceptor())
.build();
面试常见问题
Q1: 如何优化网络请求?
答案:
- 请求合并:将多个请求合并为一个批量请求
- 请求去重:避免重复请求相同数据
- 请求优先级:重要请求优先处理
- 请求重试:失败请求自动重试
- 连接池优化:复用连接,减少建立连接时间
- 超时设置:合理设置超时时间
Q2: 如何优化图片加载?
答案:
- 使用图片加载库(Glide、Picasso)
- 图片压缩:使用合适的尺寸和格式
- 使用 WebP 格式
- 懒加载:只在可见时加载
- 预加载:提前加载可能需要的图片
- 三级缓存:内存缓存 + 磁盘缓存 + 网络
Q3: 什么是三级缓存?
答案:
- 内存缓存:最快,但容量有限
- 磁盘缓存:速度中等,容量较大
- 网络加载:最慢,但数据最新
Q4: 如何实现数据压缩?
答案:
- Gzip 压缩:HTTP 请求响应压缩
- 数据格式优化:使用 Protobuf、MessagePack 替代 JSON
- 分页加载:减少单次请求数据量
- 字段精简:只请求必要字段
Q5: 缓存策略有哪些?
答案:
- HTTP 缓存:利用 HTTP 缓存头(Cache-Control、ETag)
- 内存缓存:使用 LruCache
- 磁盘缓存:持久化缓存
- 智能缓存:根据数据更新频率选择缓存策略
Q6: 如何监控网络性能?
答案:
- 使用 Network Profiler 实时监控
- 使用拦截器记录请求信息
- 监听网络状态变化
- 检测网络质量
- 使用 Chrome DevTools、Charles 等工具
Q7: 网络优化的最佳实践?
答案:
- 合并请求,减少请求次数
- 使用缓存,减少网络请求
- 压缩数据,减少传输量
- 优化图片,使用合适格式和尺寸
- 监控网络,及时发现问题
- 处理网络异常,提供降级方案
总结
网络优化是 Android 性能优化的重要部分,主要从以下几个方面入手:
- 请求优化:合并、去重、优先级、重试
- 图片优化:压缩、格式、懒加载、缓存
- 数据压缩:Gzip、格式优化、分页
- 缓存策略:三级缓存、智能缓存
- 网络监控:状态监听、请求监控、质量检测
通过合理的网络优化,可以减少网络请求,提升加载速度,改善用户体验。
最后更新:2024年