Flutter(二十一)——dio庫

前言

前面介紹了dart語言自帶的網絡請求庫httpClient,以及官方推薦的網絡請求庫http,但我們的網絡請求其實千變萬化,並不僅僅只是請求一個網頁獲取某種數據這一種需求。

有時候,我們也需要在網絡請求之前以及之後做些準備工作,這就涉及到如果監聽我們的網絡請求過程,這個時候前面的網絡請求方式顯然不能滿足我們的需求,所以我們需要藉助強大的第三方網路請求庫dio。

dio庫是Flutter中文網提供的一個強大的http請求庫,在Github上它的star數量已經超過3000次,如果你用Java開發過Android程序,肯定用到過OkHttp庫,其實它們兩有些相似。它支持文件的上傳/下載,Cookie管理,FormData,請求/取消,攔截器等操作。下面博主將詳細介紹dio庫的用法。

基本用法

通過在網絡請求中,基本用法如前面一樣就是get與post請求。比如通過get請求獲取一個網址的內容,可以通過如下代碼實現:

_getData() async{
    try{
      Response response=await Dio().get("https://www.baidu.com/");
      print(response);
    }catch(e){
      print(e);
    }

代碼很簡單這裏就不做過多的贅述,接着我們再來看看post請求,代碼如下:

_postData() async{
    try{
      Response response=await Dio().post("https://www.baidu.com/",data: {});
      print(response);
    }catch(e){
      print(e);
    }
  }

與get請求唯一的區別是多了一個參數傳遞,通過鍵值對的方式。

dio單例

一個dio實例可以發起多個網絡請求。很多時候,在App裏只存在一個http數據源,且有一些公共的配置項,比如公共的頭文件,公共的請求地址,超時設置等。在這些情況下,我們可以考慮使用dio庫提供的單例模式,這也是dio庫推薦的用法,便於程序員管理,創建的代碼如下:

    var dio=new Dio(new BaseOptions(
      baseUrl: "http://liyuanjinglyj.com/demo",//鏈接
      connectTimeout: 5000,//鏈接超時
      receiveTimeout: 100000,//響應超時
      headers: {//請求頭
        HttpHeaders.userAgentHeader:"dio",
        "api": "1.0.0",
      },
      contentType: ContentType.json,//請求Json數據
      responseType: ResponseType.plain//響應數據爲文本
    ));

從上面的實例中我們可以看到,在初始化dio實例時,我們傳入了一個BaseOptions的配置項,裏面可以設定一些基本且共用的信息,上面註釋這些就是,這樣更便於我們對App網絡的請求進行統一的管理。

dio攔截器

開頭博主已經說過了一種需求就是監聽網絡請求,在之前以及之後處理一些實際的需求,這裏我們就需要用到dio庫的攔截器,使用代碼如下:

dio.interceptors.add(InterceptorsWrapper(
      onRequest: (RequestOptions options){
        //請求之前處理一些事情
        return options;
      },
      onResponse: (Response response){
        //請求返回數據之後處理一些事情
        return response;
      },
      onError: (DioError error){
        //當請求失敗時,處理事情
        return error;
      }
    ));

上面通過單例模式創建了一個dio變量,這裏直接使用起來。可以看到,在攔截器Request處理時,我們可以直接返回options,這個時候會繼續處理then方法裏的邏輯,如果你想完成請求並返回一些自定義的數據,可以返回一個response對象或返回dio.resolve(data),這樣請求將會被終止,上層的then會被調用,then中返回的數據將是你自定義的數據data。

如果你想終止請求並觸發一個錯誤,你可以返回一個DioError對象,或返回dio.reject(errMsg),這樣請求將會被終止並觸發異常。比如,也可以這樣寫:

dio.interceptors.add(InterceptorsWrapper(
      onRequest: (RequestOptions options){
        //請求之前處理一些事情
        return dio.resolve("error");
      },
));
Response response= await dio.get("網址");

涉及到攔截器,實際業務中我們可以還有一種需求,比如我們可能需要鎖定/解鎖攔截器,一旦請求/響應的攔截器被鎖定,接下來的請求/響應,將會在進入攔截器之前進行排隊等待,直到解鎖成功,這裏排隊的請求/響應纔會繼續執行,所以在一些串行化請求/響應的場景中,我們也需要用到攔截器,我們可以使用lock/unlock(也也就是操作系統中的鎖)來實現,代碼如下:

var crsfToken=null;
    var dio=new Dio();
    var tokenDio=new Dio();
    dio.interceptors.add(InterceptorsWrapper(
      onRequest: (Options options){
        print("發送請求之前");
        if(crsfToken==null){
          dio.lock();
          return tokenDio.get("網址").then((d){
            options.headers["csrfToken"]=crsfToken=d.data['data']['token'];
            return options;
          }).whenComplete(()=>dio.unlock());
        }else{
          options.headers["csrtToken"]=crsfToken;
          return options;
        }
      },
    ));

在上述代碼中,我們創建了一個實例用於請求token,在發送請求時,我們會被請求的攔截器攔截。如果我們首次訪問接口,在沒有攜帶token的情況下,代碼就會創建另一個dio實例並通過異步任務去獲取token,獲取完成之後,dio會把它設置到headers裏面,並把token作爲請求參數發送,然後調用unlock方法進行解鎖。

比如我們請求需要登錄後才能操作的網絡請求時,我們需要先獲取cookie,這裏就可以使用上面類似的代碼,而且你也可以在調用攔截器的clear()方法來清空等待隊列。

攔截器鏈

其實攔截器不止一個。在Android的OkHttp框架裏,就有攔截器鏈的概念,在dio庫中也一樣,你可以通過編譯器查看其源碼,其實攔截器都是放在ListMixin裏面的,最終需要執行_executeInterceptors來處理每一個攔截器,代碼如下:

Future _executeInterceptors<T>(T ob,f(Interceptor inter,T ob)) async{
	for(var inter in interceptors){
		var res=await _assureFutrue(f(inter,ob));
		if(res!=null){
			if(res is T){
				ob=res;
				continue;
			}
			if(res is Response || res is DioError) return res;
			return res;
		}
	}
	return ob;
}

通過上面代碼,就比較容易理解它的工作原理了。我們可以根據需求在代碼中添加多個攔截器,比如,我們可以添加日誌攔截器,代碼如下:

dio.interceptors.add(LogInterceptor(responseBody: false));

在比如,如果我們需要添加Cookie進行管理,也能放在攔截器裏面,可以這樣寫,代碼如下:

dio.interceptors.add(CookieManager(CookieJar()));

dio適配器

dio庫還能抽象出適配器來方便切換和定製頂層的網絡庫。比如,在Flutter中我們可以通過自定義HttpClientAdapter將http請求轉發到Native中,然後再由Native統一發送請求。再比如,將來某一天OkHttp也提供了Dart版本,這個時候可以通過適配器無縫切換到OkHttp庫,而不用修改之前的代碼。

dio使用DefaultHttpClientAdapter作爲其默認的HttpClientAdapter,DefaultHttpClientAdapter使用dart:io:HttpClient來發起網絡請求,代碼如下:

class LYJAdapter extends HttpClientAdapter{
  static const String host="liyuanjinglyj.com";
  static const String base="http://$host";
  DefaultHttpClientAdapter _adapter=DefaultHttpClientAdapter();

  @override
  Future<ResponseBody> fetch(RequestOptions options, Stream<List<int>> requestStream, Future cancelFuture) async{
    Uri uri=options.uri;
    if(uri.host==host){
      switch(uri.path){
        case "/demo":
          return ResponseBody.fromString(
              jsonEncode({
                "errCode":0,
                "data":{"path":uri.path}
              }),
              200,
              headers: DioHttpHeaders.fromMap({
                HttpHeaders.contentTypeHeader:ContentType.json,
              }),
          );
        case "/download":
          return ResponseBody(
            File("./README.MD").openRead(),
            200,
            headers: DioHttpHeaders.fromMap({
              HttpHeaders.contentLengthHeader:File("./README.MD").lengthSync(),
            }),
          );
        default:
          return ResponseBody.fromString(
              "",
              404,
              headers: DioHttpHeaders(),
          );
      }
    }
    return _adapter.fetch(
      options,requestStream,cancelFuture
    );
  }
}

這裏我們定義了一個LYJAdapter,並且設置了base作爲baseUrl,這個一般根據服務器更改。當“uri.host==host”時,則進入switch條件分支判斷邏輯。比如"/demo",就是請求的"http://liyuanjinglyj.com/demo"。最後通過_adapter.fetch來執行返回Future。

當然這裏我們還需要設置適配器,設置適配器的代碼如下:

var dio=new Dio();
dio.httpClientAdapter=LYJAdapter();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章