Flutter?將Dio請(qǐng)求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫的實(shí)現(xiàn)方案
背景
首先看到標(biāo)題,相信大家已經(jīng)血壓飆升了,既然都用了Flutter
,怎么還用原生的網(wǎng)絡(luò)庫呢?這不是多此一舉么。emmmmm,當(dāng)我看到這個(gè)需求的時(shí)候一樣惱火。但因?yàn)槲覀冺?xiàng)目中使用的Dio庫不支持查詢DNS、SSL等所需時(shí)間(至少我沒找到),而Okhttp
卻可以。因此,不得以只能借助原生網(wǎng)絡(luò)庫去實(shí)現(xiàn)此功能。
實(shí)現(xiàn)方案
既然必須要去實(shí)現(xiàn),那擺在面前的問題就是如何去實(shí)現(xiàn)了。最簡單粗暴的一個(gè)方式,就是通過建立一個(gè)Flutter Channel將網(wǎng)絡(luò)請(qǐng)求的所有信息傳到原生,但腦補(bǔ)一波后,發(fā)現(xiàn)實(shí)現(xiàn)起來太過麻煩,一堆header,body,data等信息都需要自己組裝,并且致命的是需要改動(dòng)現(xiàn)有項(xiàng)目的請(qǐng)求方式。因此為了避免項(xiàng)目大改,最后看中了Dio
提供的interceptors(攔截器)功能,在攔截器中,我們可以對(duì)網(wǎng)絡(luò)請(qǐng)求進(jìn)行攔截,攔截后再通過FlutterChannel發(fā)給原生進(jìn)行網(wǎng)絡(luò)請(qǐng)求。這樣既避免了項(xiàng)目改動(dòng),也省去了數(shù)據(jù)組裝,很容易的達(dá)到了目標(biāo)。
實(shí)現(xiàn)步驟
- 首先定義一個(gè)攔截器(NativeNetInterceptor),用來轉(zhuǎn)發(fā)網(wǎng)絡(luò)請(qǐng)求,在onRequest中,我們調(diào)用了NativeNet.sendRequestToNative方法,用來接收接口中的參數(shù)(url、header、body、data、等等等),方便下一步轉(zhuǎn)發(fā)。catch模塊主要用來捕獲異常,如果是DioError,直接
reject
,如果是其他異常,可以調(diào)用handler.next(options)繼續(xù)走Dio請(qǐng)求,當(dāng)作兜底方案。這里我注釋掉了。
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:如果擔(dān)心原生有問題,可打開下方注釋。 // handler.next(options); rethrow; } } } }
- NativeNet的實(shí)現(xiàn),這里主要是將Dio
options
中所帶的信息發(fā)送給原生,并將原生返回信息進(jìn)行組裝。
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)換異常后手動(dòng)構(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ò)請(qǐng)求失敗 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; } }
- 原生實(shí)現(xiàn),這里貼出安卓代碼,請(qǐng)求完之后,無論成功失敗,都調(diào)用result.success將處理好的數(shù)據(jù)返回給flutter。具體的
ANDROID/IOS
網(wǎng)絡(luò)請(qǐng)求這里就略過了,相信大家都用的很成熟了。
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> } //超時(shí)時(shí)間 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" -> { ... //請(qǐng)求成功/失敗后調(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", // 必傳 請(qǐng)求地址 "method": "GET", //必傳 請(qǐng)求方式 GET POST HEAD PUT DELETE "queryParameters":{}, //可選 query參數(shù) "data":{}, //可選 body參數(shù) "header":{}, //可選 請(qǐng)求頭 "timeoutInterval":15000, //可選 (毫秒) 鏈接超時(shí)時(shí)常 "retryCount": 0 //可選 重試次數(shù),默認(rèn)0 }
Response
(Native To Flutter)
// native返回 成功 { "data":{}, //接口原始返回內(nèi)容 "headers":{}, //返回頭 "statusCode":200, //http狀態(tài)碼 "statusMessage":"請(qǐng)求成功" //http狀態(tài)碼對(duì)應(yīng)文案 } // native返回 失敗 { "data":{}, //接口原始返回內(nèi)容 "headers":{}, //返回頭 "statusCode":404, //http狀態(tài)碼 "statusMessage":"找不到對(duì)象", //http狀態(tài)碼對(duì)應(yīng)文案 "error":{ "code":-3001, //錯(cuò)誤碼 "domain":"URLDmain", // 錯(cuò)誤大分類 "description":"錯(cuò)誤詳情" // 錯(cuò)誤詳情 } }
注意點(diǎn)
添加NativeNetInterceptor,如果有多個(gè)攔截器,例如LogInterceptors等等,需要將NativeNetInterceptor放到最后。
到此這篇關(guān)于Flutter 將Dio請(qǐng)求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫的文章就介紹到這了,更多相關(guān)Flutter Dio請(qǐng)求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android通過ImageView設(shè)置手指滑動(dòng)控件縮放
這篇文章主要介紹了Android通過ImageView設(shè)置手指滑動(dòng)控件縮放效果,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-12-12Android實(shí)現(xiàn)計(jì)時(shí)與倒計(jì)時(shí)的方法匯總
這篇文章主要介紹了Android實(shí)現(xiàn)計(jì)時(shí)與倒計(jì)時(shí)的方法匯總,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-06-06Android深入探究自定義View之嵌套滑動(dòng)的實(shí)現(xiàn)
什么是嵌套滑動(dòng)?當(dāng)我們向下滑動(dòng)時(shí),首先是外部的布局向下滑動(dòng),然后才是內(nèi)部的RecyclerView滑動(dòng),向上滑動(dòng)也是如此。這就是嵌套滑動(dòng)的效果2021-11-11Android Studio使用教程(二):基本設(shè)置與運(yùn)行
這篇文章主要介紹了Android Studio使用教程(二):基本設(shè)置與運(yùn)行,本文講解了項(xiàng)目結(jié)構(gòu)、偏好設(shè)置、常用功能介紹、創(chuàng)建模擬器等內(nèi)容,需要的朋友可以參考下2015-05-05淺談關(guān)于Android WebView上傳文件的解決方案
這篇文章主要介紹了淺談關(guān)于Android WebView上傳文件的解決方案 ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09