思维脑图:请点击查看 网络框架.xmind
二、网络重试
三、网络缓存优化
四、网络耗时
五、网络使用过程中的优化
网络基础知识
见 《4线程与网络-③网络.md》
一、网络权限与初始化
系统网络权限弹窗机制:有代码执行到网络相关事项时候才会系统自动弹出。常见的场景:1监听网络状态变化;2进行任一网络请求。
验证代码:详见 CJNetworkDemo
图片来源于 《app启动与页面加载.graffle》 中的 【app启动(含网络与用户协议)(详细)】
问:如何确保网络初始化过程的安卓安全合规问题?
答:核心是将除网络库外的三方库统一放到同意用户协议后,避免其他三方库也可能调用获取敏感信息。至于网络库是否也放到同意协议后可用有两种方式。
处理方式一:同意协议前就可以初始化网络库,但是使用中涉及的敏感信息在未同意前使用自定义的特殊值(其他三方库先不初始化),且同意协议前仅网络库可初始化,其他三方库统一还是放到同意协议后,避免其他三方库也可能调用获取敏感信息。
优点:可以避免监听蜂窝的方式没弹出网络授权弹窗,导致网络请求始终调不通。(因为网络授权弹窗只有要么发起网络请求、要么监听网络请求的时候才可能弹出。但触发请求的次数明显比监听次数多)
缺点:代码里容易遗忘敏感信息的获取如安卓deviceId要先判断是否同意了用户协议,这是经常安卓审核合规不通过的常见原因。
使用注意:使用中一旦涉及敏感信息,一定要先判断是不是同意了用户协议,如果没同意则不要去获取。如安卓deviceId未同意用户协议前使用deviceId_not_agree_user_privacy
处理方式二:同意协议后才初始化三方库(含网络库)
优点:可以避免一些如deviceId的提前获取导致不符合安卓合规。
以下功能一般会牵涉到附中提到的 网络拦截器
二、网络重试
图片来源于 《网络框架的一生.graffle》 中的 【网络重试】
三、网络接口缓存流程
API缓存过程涉及附文中提到的 promise的resolve与next的流程
图片来源于 《网络框架的一生.graffle》 中的 【网络缓存优化】
1、API缓存+可考虑加:Last-Modified/ETag
其他参考文档: ETag 、 Last-Modified 的介绍
使用 Last-Modified、ETag 其实就是在原本(见《001-UIKit-CQDemo-Flutter》 中的 【flutter_network_kit 库里判断responseModel.isSameToBefore部分】)缓存的基础上省去了这个判断。如果返回304,那就是代表没有新数据,使用beforeResponseModel。
1 2 3 4 5
| if (beforeResponseModel != null) { if (responseModel.isEqualToResponse(beforeResponseModel)) { responseModel.isSameToBefore = true; } }
|
request.cachePolicy = NSURLRequestUseProtocolCachePolicy; 这行代码设置了请求的缓存策略,告诉NSURLSession根据HTTP协议的缓存相关头部(如Cache-Control、ETag、Last-Modified等)来决定是否使用缓存。
“If-Modified-Since”:服务器返回的 “Last-Modified”
“If-None-Match”:服务器返回的 “ETag”
1.1、在iOS上使用Last-Modified和ETag实现缓存
后端返回:
1 2 3 4 5 6 7
| app.get('/your-endpoint', function(req, res) { const lastModified = xxx; const etag = 'unique-etag-value'; res.set('Last-Modified', lastModified.toUTCString()); res.set('ETag', etag); res.send('Your resource content'); });
|
前端使用并回传:
1.1.1、自定义缓存逻辑 NSCache,写出使用Last-Modified和ETag来实现缓存的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| NSURL *url = [NSURL URLWithString:@"http://example.com/resource"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60];
NSString *cachedLastModified = NSString *cachedETag = if (cachedLastModified) { [request setValue:cachedLastModified forHTTPHeaderField:@"If-Modified-Since"]; } if (cachedETag) { [request setValue:cachedETag forHTTPHeaderField:@"If-None-Match"]; }
NSString *storageKey = "LastModified_url和已去除timestamp等无关参数的参数 组成的key" NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; NSString *newLastModified = httpResponse.allHeaderFields[@"Last-Modified"]; NSString *newETag = httpResponse.allHeaderFields[@"ETag"]; if (httpResponse.statusCode == 200) { [self saveData:data LastModified:newLastModified ETag:newETag forKey:storageKey] callback(data, isCacheData:false); } else if (httpResponse.statusCode == 304) { cacheData = [self loadCachedDataForKey:storageKey]; callback(data, isCacheData:true); } }];
[dataTask resume];
|
1.1.2、在不自定义缓存逻辑,且完全依赖 NSURLCache 来管理缓存的情况,写出使用Last-Modified和ETag来实现缓存的代码。(httpResponse.statusCode == 304, 资源未修改,从 NSURLCache 获取缓存的数据)
当使用 NSURLCache 并依赖系统的缓存机制时,通常不需要手动设置 Last-Modified 和 ETag 请求头字段。NSURLCache 会自动处理这些缓存相关的 HTTP 头字段,包括:
- 在条件 GET 请求中自动添加
If-Modified-Since 和 If-None-Match 请求头,使用之前响应中收到的 Last-Modified 和 ETag 值。
- 当接收到来自服务器的响应时,根据响应中的
Last-Modified 和 ETag 头字段更新缓存条目。
如果你完全依赖 NSURLCache 来处理缓存逻辑,你的代码中不需要显式设置这些字段。NSURLCache 会在内部管理这些值,并根据 HTTP 协议标准执行适当的缓存行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| NSUInteger memoryCapacity = 4 * 1024 * 1024; NSUInteger diskCapacity = 20 * 1024 * 1024; NSURLCache *urlCache = [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:nil]; NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; sessionConfig.URLCache = urlCache;
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
NSURL *url = [NSURL URLWithString:@"http://example.com/resource"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error != nil) { ...... return; } NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; switch (httpResponse.statusCode) { case 200: callback(data, isCacheData:false); break; case 304: NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; if (cachedResponse) { NSData *cachedData = cachedResponse.data; callback(cachedData, isCacheData:true); } break; default: break; } }];
[dataTask resume];
|
四、网络耗时及其优化
网络耗时及其优化.md
五、网络安全
请在安全篇章中查看
文档:《网络加密.md》
流程图: 《网络加密.graffle》
六、文件分片上传与分片下载
《文件分片上传与分片下载.md》
七、网络模拟
网络模拟文档请查看:网络ApiMock
附1:网络拦截器(责任链模式)
| 类型 |
时机 |
顺序: |
| 请求拦截器(Request Interceptor) |
请求开始前拦截 |
按照它们被添加的顺序,依次执行,修改即将发出的请求。 |
| 响应拦截器(Response Interceptor) |
请求成功后拦截 |
|
| 错误拦截器(Error Interceptor) |
请求失败后拦截 |
|
拦截器的执行顺序是根据它们各自被添加到请求实例的顺序决定的。(顺序只发生在同种类型间,不同类型间不影响。)
问1:如果第一个请求拦截器把参数修改了,第二个请求拦截器会怎么样?
答:如果第一个请求拦截器修改了请求参数,第二个请求拦截器接收到的将是修改后的请求参数。每个请求拦截器都会接收到前一个请求拦截器返回的RequestOptions对象,并且可以对其进行进一步的修改。
问2:如果是先添加响应拦截器,再添加请求拦截器会怎么样?
答:即使响应拦截器是先添加的,当实际发送请求时,请求拦截器会先执行,响应拦截器会在请求完成后执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| Dio dio = Dio();
dio.interceptors.add(InterceptorsWrapper( onRequest: (RequestOptions options) async { options.queryParameters['firstInterceptor'] = 'modified'; print("第一个请求拦截器修改后的参数: ${options.queryParameters}"); return options; }, ));
dio.interceptors.add(InterceptorsWrapper( onRequest: (RequestOptions options) async { options.queryParameters['secondInterceptor'] = 'added'; print("第二个请求拦截器接收到的参数: ${options.queryParameters}"); return options; }, ));
dio.get('https://example.com') .then((response) { print("请求发送成功,并接收到响应: ${response.data}"); }).catchError((error) { print("请求发送失败,并接收到错误: $error"); });
dio.interceptors.add(InterceptorsWrapper( onResponse: (Response response) { print("响应拦截器"); return response; }, ));
dio.interceptors.add(InterceptorsWrapper( onError: (DioError err, ErrorInterceptorHandler handler) { print("错误拦截器"); return handler.next(err); }, ));
|
例如,Dio提供了三种类型的拦截器:请求拦截器(Request Interceptor)、响应拦截器(Response Interceptor)和错误拦截器(Error Interceptor)。每种类型的拦截器内部的执行顺序是固定的,但是不同类型拦截器之间的执行顺序则取决于它们被添加到Dio实例的顺序。
附2、promise的resolve与next的流程
使用 resolve 方法可以中断当前执行中的请求拦截器链,至于是否执行后续的响应拦截器取决于resolve 方法的第二个参数 callFollowingResponseInterceptor ,并直接返回一个 Response 对象作为请求的结果。
在 Dio 的 InterceptorsWrapper 中,使用 handler.resolve 并不一定会直接将响应返回给最初的请求调用者,而是将控制权交回给 Dio实例,由 Dio 来决定接下来的流程。resolve 方法实际上并不会退出拦截器链,而是根据 callFollowingResponseInterceptor 参数的值来决定是否执行后续的响应拦截器。但无论 callFollowingResponseInterceptor 设置为 true 还是 false,调用者最终都能得到 Response 对象。这是因为响应拦截器的目的是处理和可能修改响应,但最终仍然需要将响应传递回调用者。不同之处在于,设置为 true 时会先执行响应拦截器,而设置为 false 时会直接返回响应给调用者,跳过响应拦截器。
图片来源于 《网络框架的一生.graffle》 中的 【网络缓存优化】版面里的【promise的resolve与next】图层
End
其他参考文档: