W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
本節(jié)我們會基于前面介紹過的 dio 網(wǎng)絡(luò)庫封裝 APP 中用到的網(wǎng)絡(luò)請求接口,并同時應用一個簡單的緩存策略。下面我們先介紹一下網(wǎng)絡(luò)接口緩存原理,然后再封裝 APP 的業(yè)務請求接口。
由于在國內(nèi)訪問 Github 服務器速度較慢,所以我們應用一些簡單的緩存策略:將請求的 url 作為 key,對請求的返回值在一個指定時間段類進行緩存,另外設(shè)置一個最大緩存數(shù),當超過最大緩存數(shù)后移除最早的一條緩存。但是也得提供一種針對特定接口或請求決定是否啟用緩存的機制,這種機制可以指定哪些接口或那次請求不應用緩存,這種機制是很有必要的,比如登錄接口就不應該緩存,又比如用戶在下拉刷新時就不應該再應用緩存。在實現(xiàn)緩存之前我們先定義保存緩存信息的CacheObject
類:
class CacheObject {
CacheObject(this.response)
: timeStamp = DateTime.now().millisecondsSinceEpoch;
Response response;
int timeStamp; // 緩存創(chuàng)建時間
@override
bool operator ==(other) {
return response.hashCode == other.hashCode;
}
//將請求uri作為緩存的key
@override
int get hashCode => response.realUri.hashCode;
}
接下來我們需要實現(xiàn)具體的緩存策略,由于我們使用的是 dio package,所以我們可以直接通過攔截器來實現(xiàn)緩存策略:
import 'dart:collection';
import 'package:dio/dio.dart';
import '../index.dart';
class CacheObject {
CacheObject(this.response)
: timeStamp = DateTime.now().millisecondsSinceEpoch;
Response response;
int timeStamp;
@override
bool operator ==(other) {
return response.hashCode == other.hashCode;
}
@override
int get hashCode => response.realUri.hashCode;
}
class NetCache extends Interceptor {
// 為確保迭代器順序和對象插入時間一致順序一致,我們使用LinkedHashMap
var cache = LinkedHashMap<String, CacheObject>();
@override
onRequest(RequestOptions options) async {
if (!Global.profile.cache.enable) return options;
// refresh標記是否是"下拉刷新"
bool refresh = options.extra["refresh"] == true;
//如果是下拉刷新,先刪除相關(guān)緩存
if (refresh) {
if (options.extra["list"] == true) {
//若是列表,則只要url中包含當前path的緩存全部刪除(簡單實現(xiàn),并不精準)
cache.removeWhere((key, v) => key.contains(options.path));
} else {
// 如果不是列表,則只刪除uri相同的緩存
delete(options.uri.toString());
}
return options;
}
if (options.extra["noCache"] != true &&
options.method.toLowerCase() == 'get') {
String key = options.extra["cacheKey"] ?? options.uri.toString();
var ob = cache[key];
if (ob != null) {
//若緩存未過期,則返回緩存內(nèi)容
if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <
Global.profile.cache.maxAge) {
return cache[key].response;
} else {
//若已過期則刪除緩存,繼續(xù)向服務器請求
cache.remove(key);
}
}
}
}
@override
onError(DioError err) async {
// 錯誤狀態(tài)不緩存
}
@override
onResponse(Response response) async {
// 如果啟用緩存,將返回結(jié)果保存到緩存
if (Global.profile.cache.enable) {
_saveCache(response);
}
}
_saveCache(Response object) {
RequestOptions options = object.request;
if (options.extra["noCache"] != true &&
options.method.toLowerCase() == "get") {
// 如果緩存數(shù)量超過最大數(shù)量限制,則先移除最早的一條記錄
if (cache.length == Global.profile.cache.maxCount) {
cache.remove(cache[cache.keys.first]);
}
String key = options.extra["cacheKey"] ?? options.uri.toString();
cache[key] = CacheObject(object);
}
}
void delete(String key) {
cache.remove(key);
}
}
關(guān)于代碼的解釋都在注釋中了,在此需要說明的是 dio 包的option.extra
是專門用于擴展請求參數(shù)的,我們通過定義了“refresh”和“noCache”兩個參數(shù)實現(xiàn)了“針對特定接口或請求決定是否啟用緩存的機制”,這兩個參數(shù)含義如下:
參數(shù)名 | 類型 | 解釋 |
---|---|---|
refresh | bool | 如果為 true,則本次請求不使用緩存,但新的請求結(jié)果依然會被緩存 |
noCache | bool | 本次請求禁用緩存,請求結(jié)果也不會被緩存。 |
一個完整的 APP,可能會涉及很多網(wǎng)絡(luò)請求,為了便于管理、收斂請求入口,工程上最好的作法就是將所有網(wǎng)絡(luò)請求放到同一個源碼文件中。由于我們的接口都是請求的 Github 開發(fā)平臺提供的 API,所以我們定義一個 Git 類,專門用于 Github API 接口調(diào)用。另外,在調(diào)試過程中,我們通常需要一些工具來查看網(wǎng)絡(luò)請求、響應報文,使用網(wǎng)絡(luò)代理工具來調(diào)試網(wǎng)絡(luò)數(shù)據(jù)問題是主流方式。配置代理需要在應用中指定代理服務器的地址和端口,另外 Github API 是 HTTPS 協(xié)議,所以在配置完代理后還應該禁用證書校驗,這些配置我們在 Git 類初始化時執(zhí)行(init()方法
)。下面是 Git 類的源碼:
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio/adapter.dart';
import 'package:flutter/material.dart';
import '../index.dart';
class Git {
// 在網(wǎng)絡(luò)請求過程中可能會需要使用當前的context信息,比如在請求失敗時
// 打開一個新路由,而打開新路由需要context信息。
Git([this.context]) {
_options = Options(extra: {"context": context});
}
BuildContext context;
Options _options;
static Dio dio = new Dio(BaseOptions(
baseUrl: 'https://api.github.com/',
headers: {
HttpHeaders.acceptHeader: "application/vnd.github.squirrel-girl-preview,"
"application/vnd.github.symmetra-preview+json",
},
));
static void init() {
// 添加緩存插件
dio.interceptors.add(Global.netCache);
// 設(shè)置用戶token(可能為null,代表未登錄)
dio.options.headers[HttpHeaders.authorizationHeader] = Global.profile.token;
// 在調(diào)試模式下需要抓包調(diào)試,所以我們使用代理,并禁用HTTPS證書校驗
if (!Global.isRelease) {
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
client.findProxy = (uri) {
return "PROXY 10.1.10.250:8888";
};
//代理工具會提供一個抓包的自簽名證書,會通不過證書校驗,所以我們禁用證書校驗
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
};
}
}
// 登錄接口,登錄成功后返回用戶信息
Future<User> login(String login, String pwd) async {
String basic = 'Basic ' + base64.encode(utf8.encode('$login:$pwd'));
var r = await dio.get(
"/users/$login",
options: _options.merge(headers: {
HttpHeaders.authorizationHeader: basic
}, extra: {
"noCache": true, //本接口禁用緩存
}),
);
//登錄成功后更新公共頭(authorization),此后的所有請求都會帶上用戶身份信息
dio.options.headers[HttpHeaders.authorizationHeader] = basic;
//清空所有緩存
Global.netCache.cache.clear();
//更新profile中的token信息
Global.profile.token = basic;
return User.fromJson(r.data);
}
//獲取用戶項目列表
Future<List<Repo>> getRepos(
{Map<String, dynamic> queryParameters, //query參數(shù),用于接收分頁信息
refresh = false}) async {
if (refresh) {
// 列表下拉刷新,需要刪除緩存(攔截器中會讀取這些信息)
_options.extra.addAll({"refresh": true, "list": true});
}
var r = await dio.get<List>(
"user/repos",
queryParameters: queryParameters,
options: _options,
);
return r.data.map((e) => Repo.fromJson(e)).toList();
}
}
可以看到我們在init()
方法中,我們判斷了是否是調(diào)試環(huán)境,然后做了一些針對調(diào)試環(huán)境的網(wǎng)絡(luò)配置(設(shè)置代理和禁用證書校驗)。而Git.init()
方法是應用啟動時被調(diào)用的(Global.init()
方法中會調(diào)用Git.init()
)。
另外需要注意,我們所有的網(wǎng)絡(luò)請求是通過同一個dio
實例(靜態(tài)變量)發(fā)出的,在創(chuàng)建該dio
實例時我們將 Github API 的基地址和 API 支持的 Header 進行了全局配置,這樣所有通過該dio
實例發(fā)出的請求都會默認使用者些配置。
在本實例中,我們只用到了登錄接口和獲取用戶項目的接口,所以在Git
類中只定義了login(…)
和getRepos(…)
方法,如果讀者要在本實例的基礎(chǔ)上擴充功能,讀者可以將其它的接口請求方法添加到Git
類中,這樣便實現(xiàn)了網(wǎng)絡(luò)請求接口在代碼層面的集中管理和維護。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: