一、移动端网络特点
移动网络面临诸多挑战:
网络特点:
- 高延迟(2G: 400ms, 3G: 100ms, 4G: 20ms)
- 不稳定(频繁切换、信号强弱变化)
- 带宽有限
- 流量成本高
二、弱网检测
1. 网络状态监听
javascript
// React Native网络检测
import NetInfo from "@react-native-community/netinfo";
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
console.log("Connection type:", state.type);
console.log("Is connected?:", state.isConnected);
// 检测网络类型
if (state.type === 'cellular') {
console.log("当前使用移动网络");
if (state.details.isConnectionExpensive) {
console.log("节省流量模式");
}
}
});
return () => unsubscribe();
}, []);
// 获取当前网络信息
const checkNetwork = async () => {
const state = await NetInfo.fetch();
return state;
};
2. 智能重试机制
java
// Android OkHttp拦截器
public class RetryInterceptor implements Interceptor {
private static final int MAX_RETRIES = 3;
private static final long RETRY_DELAY_MS = 1000;
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
IOException lastException = null;
for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
response = chain.proceed(request);
if (response.isSuccessful()) {
return response;
}
// 如果是可重试的错误码
if (isRetryable(response.code())) {
response.close();
continue;
}
return response;
} catch (IOException e) {
lastException = e;
// 检测网络是否可用
if (!isNetworkAvailable()) {
throw new NoNetworkException("网络不可用");
}
// 指数退避
if (attempt < MAX_RETRIES - 1) {
try {
Thread.sleep(RETRY_DELAY_MS * (attempt + 1));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
throw lastException;
}
private boolean isRetryable(int code) {
return code >= 500 || code == 408 || code == 429;
}
}
三、请求优化
1. 请求合并
typescript
// Flutter请求合并
class RequestBatcher {
private pendingRequests = new Map<string, Promise<any>>();
private batchTimer: NodeJS.Timeout | null = null;
private readonly BATCH_DELAY = 50; // 50ms内合并请求
async batchRequest<T>(key: string, request: () => Promise<T>): Promise<T> {
// 如果已有相同请求在处理中,返回同一个Promise
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key) as Promise<T>;
}
const promise = request();
this.pendingRequests.set(key, promise);
// 延迟清理
setTimeout(() => {
this.pendingRequests.delete(key);
}, this.BATCH_DELAY);
return promise;
}
}
2. 请求优先级
typescript
// 请求优先级队列
enum RequestPriority {
HIGH = 0, // 用户操作相关,如点击、滑动
NORMAL = 1, // 普通数据请求
LOW = 2 // 预加载、统计等
}
class PriorityRequestQueue {
private queues: Map<RequestPriority, Queue[]> = new Map([
[RequestPriority.HIGH, []],
[RequestPriority.NORMAL, []],
[RequestPriority.LOW, []]
]);
addRequest(request: () => Promise<any>, priority: RequestPriority) {
const queue = this.queues.get(priority)!;
return new Promise((resolve, reject) => {
queue.push({ request, resolve, reject });
this.processQueue();
});
}
private async processQueue() {
for (const [priority, queue] of this.queues) {
while (queue.length > 0) {
const { request, resolve, reject } = queue.shift()!;
try {
const result = await request();
resolve(result);
} catch (e) {
reject(e);
}
// 高优先级请求可以中断低优先级
if (priority === RequestPriority.HIGH) {
break;
}
}
}
}
}
四、数据压缩
1. 请求体压缩
typescript
// Flutter请求压缩
import { gzip } from 'pako';
class CompressedHttpClient {
async postCompressed<T>(url: string, data: any): Promise<T> {
const jsonString = JSON.stringify(data);
const compressed = gzip(jsonString);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Encoding': 'gzip',
'Content-Type': 'application/json'
},
body: compressed
});
return response.json();
}
}
2. 响应压缩处理
typescript
// 处理压缩响应
async function fetchDecompressed(url: string): Promise<any> {
const response = await fetch(url);
const contentEncoding = response.headers.get('Content-Encoding');
let data;
if (contentEncoding === 'gzip') {
const buffer = await response.arrayBuffer();
const decompressed = ungzip(buffer);
const text = new TextDecoder().decode(decompressed);
data = JSON.parse(text);
} else if (contentEncoding === 'br') {
const buffer = await response.arrayBuffer();
const decompressed = brotliDecompress(buffer);
const text = new TextDecoder().decode(decompressed);
data = JSON.parse(text);
} else {
data = await response.json();
}
return data;
}
五、本地缓存
1. SQLite缓存
java
// Android Room缓存
@Entity(tableName = "api_cache")
public class ApiCache {
@PrimaryKey
public String key;
public String response;
public long timestamp;
public long expireTime;
}
@Dao
public interface ApiCacheDao {
@Query("SELECT * FROM api_cache WHERE key = :key AND timestamp + expireTime > :currentTime")
ApiCache getValidCache(String key, long currentTime);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(ApiCache cache);
@Query("DELETE FROM api_cache WHERE timestamp + expireTime < :currentTime")
void deleteExpired(long currentTime);
}
public class CacheRepository {
public <T> T getOrFetch(String key, long expireMs, Supplier<T> fetcher) {
// 查缓存
long currentTime = System.currentTimeMillis();
ApiCache cache = cacheDao.getValidCache(key, currentTime);
if (cache != null) {
return parseJson(cache.response);
}
// 缓存未命中,请求网络
T result = fetcher.get();
// 存入缓存
ApiCache newCache = new ApiCache();
newCache.key = key;
newCache.response = toJson(result);
newCache.timestamp = currentTime;
newCache.expireTime = expireMs;
cacheDao.insert(newCache);
return result;
}
}
2. MMKV高速缓存
typescript
// React Native MMKV
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV({
id: 'api-cache'
});
class MMKVCache {
set<T>(key: string, value: T, expireMs?: number): void {
const data = {
value,
expireTime: expireMs ? Date.now() + expireMs : null
};
storage.set(key, JSON.stringify(data));
}
get<T>(key: string): T | null {
const json = storage.getString(key);
if (!json) return null;
const data = JSON.parse(json);
if (data.expireTime && Date.now() > data.expireTime) {
storage.delete(key);
return null;
}
return data.value;
}
}
六、离线优先
1. Service Worker缓存策略
javascript
// Flutter Service Worker
const CACHE_NAME = 'app-cache-v1';
const OFFLINE_URL = '/offline.html';
// 缓存策略:Cache First
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(() => {
return caches.match(OFFLINE_URL);
})
);
return;
}
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request).then(fetchResponse => {
return caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, fetchResponse.clone());
return fetchResponse;
});
});
})
);
});
2. 离线数据同步
typescript
// 离线数据队列
class OfflineQueue {
private queue: OfflineRequest[] = [];
// 网络离线时,存入队列
async enqueue(request: OfflineRequest) {
this.queue.push(request);
await this.persist();
}
// 网络恢复后,同步队列
async sync() {
const pending = [...this.queue];
for (const request of pending) {
try {
await this.execute(request);
// 成功后移除
this.queue = this.queue.filter(r => r.id !== request.id);
} catch (e) {
// 失败后重试
request.retryCount++;
if (request.retryCount >= 3) {
// 重试超过3次,标记失败
await this.markFailed(request);
this.queue = this.queue.filter(r => r.id !== request.id);
}
}
}
await this.persist();
}
}
七、弱网体验优化
1.骨架屏
tsx
// React骨架屏
const ProductCardSkeleton = () => (
<View style={styles.card}>
<View style={styles.imageSkeleton} />
<View style={styles.titleSkeleton} />
<View style={styles.priceSkeleton} />
</View>
);
const ProductCard = ({ product, isLoading }) => {
if (isLoading) {
return <ProductCardSkeleton />;
}
return (
<View style={styles.card}>
<Image source={{ uri: product.image }} />
<Text>{product.title}</Text>
<Text>¥{product.price}</Text>
</View>
);
};
2. 渐进式加载
tsx
// 图片渐进式加载
const ProgressiveImage = ({ uri, style }) => {
const [loaded, setLoaded] = useState(false);
const [error, setError] = useState(false);
return (
<View style={style}>
{/* 先显示缩略图 */}
<Image
source={{ uri: uri + '?w=50&h=50&fit=cover' }}
style={[style, { opacity: loaded ? 1 : 0 }]}
onLoad={() => setLoaded(true)}
/>
{/* 占位图 */}
{!loaded && !error && (
<View style={[styles.placeholder, style]} />
)}
</View>
);
};
八、总结
移动端网络优化提升用户体验:
- 网络检测:实时感知网络状态
- 智能重试:指数退避提高成功率
- 数据压缩:减少流量消耗
- 本地缓存:减少网络请求
- 离线优先:弱网也能使用
最佳实践:
- 做好网络状态检测
- 实现智能重试机制
- 使用本地缓存减少请求
- 优化弱网下的用户体验
个人观点,仅供参考