Flutter?將Dio請求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫的實現(xiàn)方案
背景
首先看到標題,相信大家已經(jīng)血壓飆升了,既然都用了Flutter,怎么還用原生的網(wǎng)絡(luò)庫呢?這不是多此一舉么。emmmmm,當我看到這個需求的時候一樣惱火。但因為我們項目中使用的Dio庫不支持查詢DNS、SSL等所需時間(至少我沒找到),而Okhttp卻可以。因此,不得以只能借助原生網(wǎng)絡(luò)庫去實現(xiàn)此功能。
實現(xiàn)方案
既然必須要去實現(xiàn),那擺在面前的問題就是如何去實現(xiàn)了。最簡單粗暴的一個方式,就是通過建立一個Flutter Channel將網(wǎng)絡(luò)請求的所有信息傳到原生,但腦補一波后,發(fā)現(xiàn)實現(xiàn)起來太過麻煩,一堆header,body,data等信息都需要自己組裝,并且致命的是需要改動現(xiàn)有項目的請求方式。因此為了避免項目大改,最后看中了Dio提供的interceptors(攔截器)功能,在攔截器中,我們可以對網(wǎng)絡(luò)請求進行攔截,攔截后再通過FlutterChannel發(fā)給原生進行網(wǎng)絡(luò)請求。這樣既避免了項目改動,也省去了數(shù)據(jù)組裝,很容易的達到了目標。
實現(xiàn)步驟
- 首先定義一個攔截器(NativeNetInterceptor),用來轉(zhuǎn)發(fā)網(wǎng)絡(luò)請求,在onRequest中,我們調(diào)用了NativeNet.sendRequestToNative方法,用來接收接口中的參數(shù)(url、header、body、data、等等等),方便下一步轉(zhuǎn)發(fā)。catch模塊主要用來捕獲異常,如果是DioError,直接
reject,如果是其他異常,可以調(diào)用handler.next(options)繼續(xù)走Dio請求,當作兜底方案。這里我注釋掉了。
class NativeNetInterceptor extends Interceptor {
final Dio dio;
final NativeNetOptions options;
NativeNetInterceptor({
required this.dio,
NativeNetOptions? options,
}) : options = options ?? NativeNetOptions();
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
try {
Response response = await NativeNet.sendRequestToNative(options);
return handler.resolve(response, true);
} catch (e) {
if (e.runtimeType == DioError) {
return handler.reject(e as DioError);
} else {
// TODO:如果擔心原生有問題,可打開下方注釋。
// handler.next(options);
rethrow;
}
}
}
}- NativeNet的實現(xiàn),這里主要是將Dio
options中所帶的信息發(fā)送給原生,并將原生返回信息進行組裝。
class NativeNet {
static const MethodChannel _channel = MethodChannel('nativeNet');
static Future<Response> sendRequestToNative(RequestOptions options) async {
final String url = options.baseUrl + options.path;
Map channelParams = _getChannelParams(
url,
options.method,
queryParameters: options.queryParameters,
data: options.data,
header: options.headers,
timeoutInterval: options.connectTimeout,
);
return await _sendRequestToNative(options, channelParams);
}
static Future<Response> _sendRequestToNative(
RequestOptions options, Map params) async {
final Map nativeResponse =
await _channel.invokeMapMethod('net', params) ?? {};
final Response response = Response(requestOptions: options);
if (nativeResponse.containsKey('headers')) {
final Map nativeResponseHaders = nativeResponse['headers'];
nativeResponseHaders.forEach((key, value) {
response.headers.set(key, value);
});
}
response.statusCode = nativeResponse['statusCode'] ?? -1;
response.statusMessage = nativeResponse['statusMessage'] ?? '無message';
if (Platform.isAndroid) {
if (nativeResponse.containsKey('data')) {
String jsonData = nativeResponse['data'];
try {
Map<String, dynamic> data = convert.jsonDecode(jsonData);
response.data = data;
} on FormatException catch (e) {
///轉(zhuǎn)換異常后手動構(gòu)造一下
debugPrint("Http FormatException");
Map map = {};
map["data"] = jsonData;
response.data = map;
}
} else {
response.data = {};
}
} else {
Map<String, dynamic> data =
Map<String, dynamic>.from(nativeResponse['data'] ?? {});
response.data = data;
}
if (nativeResponse.containsKey('error')) {
//網(wǎng)絡(luò)請求失敗
Map? errorData = nativeResponse['error'];
throw DioError(
requestOptions: options, response: response, error: errorData);
}
return response;
}
static Map<String, dynamic> _getChannelParams(
String url,
String method, {
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? data,
Map<String, dynamic>? header,
num? timeoutInterval,
num? retryCount,
}) {
Map<String, dynamic> channelParams = {
'url': url,
'method': method,
};
if (queryParameters != null) {
channelParams['queryParameters'] = queryParameters;
}
if (data != null) {
channelParams['data'] = data;
}
if (header != null) {
channelParams['header'] = header;
}
if (retryCount != null) {
channelParams['retryCount'] = retryCount;
}
if (timeoutInterval != null) {
channelParams['timeoutInterval'] = timeoutInterval;
}
return channelParams;
}
}- 原生實現(xiàn),這里貼出安卓代碼,請求完之后,無論成功失敗,都調(diào)用result.success將處理好的數(shù)據(jù)返回給flutter。具體的
ANDROID/IOS網(wǎng)絡(luò)請求這里就略過了,相信大家都用的很成熟了。
class NativeNetPlugin : FlutterPlugin, MethodCallHandler {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel: MethodChannel
private var gson: Gson = Gson()
private val mTag = "Android NativeNetPlugin"
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "nativeNet")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "net") {
val channelParams = call.arguments as Map<String, Any>
var method = ""
if (channelParams.containsKey("method")) {
method = channelParams["method"] as String
}
var url = ""
if (channelParams.containsKey("url")) {
url = channelParams["url"] as String
}
var header = HashMap<String, String>()
if (channelParams.containsKey("header")) {
header = channelParams["header"] as HashMap<String, String>
}
val headers = HttpHeaders()
headers.headersMap = getMapValueForLinkedHashMap(header)
// params
var queryParameters = HashMap<String, String>()
if (channelParams.containsKey("queryParameters")) {
queryParameters = channelParams["queryParameters"] as HashMap<String, String>
}
//post body
var data = HashMap<String, Any>()
if (channelParams.containsKey("data")) {
data = channelParams["data"] as HashMap<String, Any>
}
//超時時間
var timeoutInterval: Int = 1000 * 15
if (channelParams.containsKey("timeoutInterval")) {
timeoutInterval = channelParams["timeoutInterval"] as Int
}
val mTimeOut =
Timeout(timeoutInterval, timeoutInterval, timeoutInterval, TimeUnit.MILLISECONDS)
//重試次數(shù)
var retryCount = 0
if (channelParams.containsKey("retryCount")) {
retryCount = channelParams["retryCount"] as Int
}
when (method) {
"POST" -> {
...
//請求成功/失敗后調(diào)用此方法,
result.success(dealResponse(response))
}
"GET" -> {
...
}
"DELETE" -> {
...
}
"PUT" -> {
...
}
"HEAD" -> {
...
}
else -> {
result.notImplemented()
}
}
} else {
result.notImplemented()
}
}
private fun dealResponse(
response: Response<String>
): Map<String, Any?> {
val map = HashMap<String, Any?>()
if (BuildConfig.DEBUG) {
Log.e(mTag, "dealResponse isSuccessful: ${response.code()}")
Log.e(mTag, "dealResponse code: ${response.code()}")
Log.e(mTag, "dealResponse message: ${response.message()}")
Log.e(mTag, "dealResponse body: ${response.body()}")
Log.e(mTag, "dealResponse headers: ${response.headers()}")
}
map["statusCode"] = response.code()
response.message()?.let {
map["statusMessage"] = it
} ?: let {
map["statusMessage"] = ""
}
response.body()?.let {
map["data"] = it
} ?: let {
map["data"] = ""
}
response.headers()?.let {
map["headers"] = it.toMap()
} ?: let {
map["headers"] = HashMap<String, Any>()
}
if (response.code() != 200) {
//失敗
val errorMap = HashMap<String, Any?>()
response.exception?.let {
errorMap["code"] = response.code()
errorMap["domain"] = it.toString()
errorMap["description"] = it.message
} ?: let {
errorMap["code"] = response.code()
errorMap["domain"] = map["statusMessage"]
errorMap["description"] = "HttpException"
}
map["error"] = errorMap
}
return map
}
//map 轉(zhuǎn) LinkedHashMap
private fun getMapValueForLinkedHashMap(dataMap: Map<String, String>): LinkedHashMap<String, String> {
val returnMap: LinkedHashMap<String, String> = LinkedHashMap()
if (dataMap.isNullOrEmpty()) {
return returnMap
}
val iterator = dataMap.keys.iterator()
while (iterator.hasNext()) {
val objKey = iterator.next()
val objValue = dataMap[objKey]
returnMap[objKey] = objValue.toString()
}
return returnMap
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}4.最后調(diào)用dio.interceptors.add(NativeNetInterceptor(dio: dio));將我們寫好的NativeNetInterceptor加入Dio攔截器。
交互協(xié)議
Request(Flutter To Native)
{
"url": "https://www.baidu.com", // 必傳 請求地址
"method": "GET", //必傳 請求方式 GET POST HEAD PUT DELETE
"queryParameters":{}, //可選 query參數(shù)
"data":{}, //可選 body參數(shù)
"header":{}, //可選 請求頭
"timeoutInterval":15000, //可選 (毫秒) 鏈接超時時常
"retryCount": 0 //可選 重試次數(shù),默認0
}Response(Native To Flutter)
// native返回 成功
{
"data":{}, //接口原始返回內(nèi)容
"headers":{}, //返回頭
"statusCode":200, //http狀態(tài)碼
"statusMessage":"請求成功" //http狀態(tài)碼對應(yīng)文案
}
// native返回 失敗
{
"data":{}, //接口原始返回內(nèi)容
"headers":{}, //返回頭
"statusCode":404, //http狀態(tài)碼
"statusMessage":"找不到對象", //http狀態(tài)碼對應(yīng)文案
"error":{
"code":-3001, //錯誤碼
"domain":"URLDmain", // 錯誤大分類
"description":"錯誤詳情" // 錯誤詳情
}
}注意點
添加NativeNetInterceptor,如果有多個攔截器,例如LogInterceptors等等,需要將NativeNetInterceptor放到最后。
到此這篇關(guān)于Flutter 將Dio請求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫的文章就介紹到這了,更多相關(guān)Flutter Dio請求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android通過ImageView設(shè)置手指滑動控件縮放
這篇文章主要介紹了Android通過ImageView設(shè)置手指滑動控件縮放效果,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-12-12
Android深入探究自定義View之嵌套滑動的實現(xiàn)
什么是嵌套滑動?當我們向下滑動時,首先是外部的布局向下滑動,然后才是內(nèi)部的RecyclerView滑動,向上滑動也是如此。這就是嵌套滑動的效果2021-11-11
Android Studio使用教程(二):基本設(shè)置與運行
這篇文章主要介紹了Android Studio使用教程(二):基本設(shè)置與運行,本文講解了項目結(jié)構(gòu)、偏好設(shè)置、常用功能介紹、創(chuàng)建模擬器等內(nèi)容,需要的朋友可以參考下2015-05-05
淺談關(guān)于Android WebView上傳文件的解決方案
這篇文章主要介紹了淺談關(guān)于Android WebView上傳文件的解決方案 ,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09

