flutter網路請求框架dio基本使用

早起的年輕人發表於2020-03-04

題記 —— 執劍天涯,從你的點滴積累開始,所及之處,必精益求精,即是折騰每一天。

重要訊息


本文章將講述 1.使用dio傳送基本的get請求 2.使用dio傳送get請求的傳參方式 3.解析響應json資料 4.使用dio傳送post請求並提交FormData引數 5.使用dio傳送post請求並提交json引數 6.使用dio上傳檔案並實現進度監聽 7.使用dio下載檔案並實現進度監聽 8.配製dio的攔截器 9.配製dio網路代理抓包 10.配製dio請求header以及公共請求引數 11.dio 取消網路請求

1 引言

dio用來在flutter跨平臺開發中訪問網路的框架,在使用的時候,我們首先是引入依賴

 dio: 3.0.9
複製程式碼
2 Dio get請求
2.1 Dio get 請求無引數
  //get請求無引數
  void getRequestFunction1() async {
    ///建立Dio物件
    Dio dio = new Dio();
    ///請求地址 獲取使用者列表
    String url = "http://192.168.0.102:8080/getUserList";
    ///發起get請求
    Response response = await dio.get(url);
    ///響應資料
    var data = response.data;

    setState(() {
      result = data.toString();
    });
  }
複製程式碼

資料響應結果

{
    "code": 200,
    "data": [
        {
            "id": 3,
            "userName": "測試人員",
            "realName": "張三",
            "age": 22
        }
    ],
    "message": "請求成功"
}
複製程式碼

斷點除錯如下

在這裡插入圖片描述

2.2 Dio get 請求有引數
  ///get請求有引數
  ///根據使用者ID來獲取使用者資訊
  void getRequestFunction2() async {
    ///使用者id
    int userId =3;
    ///建立 dio
    Dio dio = new Dio();
    ///請求地址
    ///傳參方式1
    String url = "http://192.168.0.102:8080/getUser/$userId";
    ///傳參方式2 
    String url2 = "http://192.168.0.102:8080/getUser?userId=$userId";
    ///傳參方式 3
    String url3 = "http://192.168.0.102:8080/getUser";

    Map<String,dynamic> map = Map();
    map["userId"]= userId;
    ///發起get請求
    Response response = await dio.get(url3,queryParameters: map);

    ///響應資料
    Map<String,dynamic> data = response.data;
    /// 將響應資料解析為 UserBean
    UserBean userBean = UserBean.fromJson(data);
  }

}
複製程式碼

在上述程式碼中,傳參方式1與傳參方式2是在請求連結中拼接引數,請求方式3是將引數放在一個 map 中,然後通過 Dio 的queryParameters 來配製引數,上述返回的資料結構為

{
    "code": 200,
    "data": {
        "id": 3,
        "userName": "測試人員",
        "realName": "張三",
        "age": 22
    },
    "message": "請求成功"
}
複製程式碼

斷點除錯

在這裡插入圖片描述
對於這裡使用到的資料模型 UserBean 物件來說


class UserBean{
  String userName;
  String realName;
  int age;
  int id;

  static UserBean fromJson(Map<String,dynamic> rootData){
    ///解析第一層
    Map<String,dynamic> data = rootData["data"];
    ///解析第二層
    UserBean userBean = new UserBean();

    userBean.id = data["id"];
    userBean.age = data["age"];
    userBean.userName= data["userName"];
    userBean.realName = data["realName"];
    return userBean;
    
  }
}
複製程式碼

對於 UserBean 中的資料解析如下圖所示

在這裡插入圖片描述

3 Dio post請求
2.1 Dio post 請求提交 FormData 表單資料

FormData 將提交的引數 name與value進行組合,實現表單資料的序列化,從而減少表單元素的拼接 也可以這樣來描述:FormData 介面提供了一種表示表單資料的鍵值對的構造方式,通過FormData發出的請求編碼型別被設為 "multipart/form-data",而在網路請求訪問中,通過 Content-Type 來記錄這個值,可以理解為Content-Type 表示具體請求中的媒體型別資訊。

而我們在實際開發中常用的 Content-Type如下

multipart/form-data
application/json     JSON資料格式
application/x-www-form-urlencoded 表單資料格式
複製程式碼

下面我們將使用 dio 來發起一個post請求,提交引數的格式為 FromData

  void postRequestFunction() async {
    ///建立Dio
    Dio dio = new Dio();
    ///傳送 FormData:
    FormData formData = FormData.fromMap({"name": "張三", "age": 22});
    String url ="http://192.168.200.68:8080/registerUser";
    ///發起 post 請求 如這裡的註冊使用者資訊
    Response response = await dio
        .post(url, data: formData);
    result = response.data.toString();
    setState(() {});
  }

複製程式碼

抓包所得如下

在這裡插入圖片描述
我們也可以看到引數的格式
在這裡插入圖片描述
在這裡我們可以看到 Content-type 是 text/plain 而並不是我們上面所說的 multipart/form-data ,這是因為在通過Dio 的 FormData 封裝引數時,會進行一步預設的設定如下圖所示

在這裡插入圖片描述

2.2 Dio post 請求提交 json 資料

下面我們使用 dio 發起一個post請求,提交json格式的引數

  ///post請求傳送json
  void postRequestFunction2() async{
    String url = "http://192.168.0.102:8080/registerUser2";
    ///建立Dio
    Dio dio = new Dio();
    ///建立Map 封裝引數
    Map<String,dynamic> map = Map();
    map['userName']="小明";
    map['userAge']=44;

    ///發起post請求
    Response response =  await dio.post(url,data: map);

    var data = response.data;
  }
複製程式碼

抓包所得如下

在這裡插入圖片描述
從上圖中,我們可以看到 Content-Type 標識了傳參方式是以 json 格式來傳送的,下圖中我們可以看到具體的引數
在這裡插入圖片描述

4 Dio 檔案上傳並實現進度監聽

  ///手機中的圖片
  String localImagePath ="/storage/emulated/0/Download/17306285.jpg";
  ///上傳的伺服器地址
  String netUploadUrl = "http://192.168.0.102:8080/fileupload";

  ///dio 實現檔案上傳
  void fileUplod() async{
    ///建立Dio
    Dio dio = new Dio();

    Map<String ,dynamic> map = Map();
    map["auth"]="12345";
    map["file"] = await MultipartFile.fromFile(localImagePath,filename: "xxx23.png");
    ///通過FormData
    FormData formData = FormData.fromMap(map);
    ///傳送post
    Response response = await dio.post(netUploadUrl, data: formData,
      ///這裡是傳送請求回撥函式
      ///[progress] 當前的進度
      ///[total] 總進度
      onSendProgress: (int progress, int total) {
        print("當前進度是 $progress 總進度是 $total");
      },);
    ///伺服器響應結果
    var data = response.data;

  }

複製程式碼

通過斷點除錯

在這裡插入圖片描述
這裡的上傳圖片請求介面返回了圖片的儲存路徑,我們開啟本地伺服器的目錄
在這裡插入圖片描述

5 Dio 檔案下載並實現進度監聽

  ///當前進度進度百分比  當前進度/總進度 從0-1
  double currentProgress =0.0;
  ///下載檔案的網路路徑
  String apkUrl ="";
  ///使用dio 下載檔案
  void downApkFunction() async{
    /// 申請寫檔案許可權
    bool isPermiss =  await checkPermissFunction();
    if(isPermiss) {
      ///手機儲存目錄
      String savePath = await getPhoneLocalPath();
      String appName = "rk.apk";

      ///建立DIO
      Dio dio = new Dio();

      ///引數一 檔案的網路儲存URL
      ///引數二 下載的本地目錄檔案
      ///引數三 下載監聽
      Response response = await dio.download(
          apkUrl, "$savePath$appName", onReceiveProgress: (received, total) {
        if (total != -1) {
          ///當前下載的百分比例
          print((received / total * 100).toStringAsFixed(0) + "%");
          // CircularProgressIndicator(value: currentProgress,) 進度 0-1
          currentProgress = received / total;
          setState(() {

          });
        }
      });
    }else{
      ///提示使用者請同意許可權申請
    }
  }

複製程式碼

Android許可權目前分為三種:正常許可權、危險許可權、特殊許可權

正常許可權 直接在AndroidManifest中配置即可獲得的許可權。大部分許可權都歸於此。 危險許可權,Android 6.0之後將部分許可權定義於此。 危險許可權不僅需要需要在AndroidManifest中配置,還需要在使用前check是否真正擁有許可權,以動態申請。

在ios中,使用xcode開啟本目錄

選中Xcode 工程中的 info.plist檔案,右鍵選擇Open As - Source Code,將許可權配置的程式碼copy到裡面即可,鍵值對中的內容可按專案需求相應修改。

<!-- 相簿 --> 
<key>NSPhotoLibraryUsageDescription</key> 
<string>需要您的同意,APP才能訪問相簿</string> 
<!-- 相機 --> 
<key>NSCameraUsageDescription</key> 
<string>需要您的同意,APP才能訪問相機</string> 
<!-- 麥克風 --> 
<key>NSMicrophoneUsageDescription</key> 
<string>需要您的同意,APP才能訪問麥克風</string> 
<!-- 位置 --> 
<key>NSLocationUsageDescription</key> 
<string>需要您的同意, APP才能訪問位置</string> 
<!-- 在使用期間訪問位置 --> 
<key>NSLocationWhenInUseUsageDescription</key> 
<string>App需要您的同意, APP才能在使用期間訪問位置</string> 
<!-- 始終訪問位置 --> 
<key>NSLocationAlwaysUsageDescription</key> 
<string>App需要您的同意, APP才能始終訪問位置</string> 
<!-- 日曆 --> 
<key>NSCalendarsUsageDescription</key> 
<string>App需要您的同意, APP才能訪問日曆</string> 
<!-- 提醒事項 --> 
<key>NSRemindersUsageDescription</key> 
<string>需要您的同意, APP才能訪問提醒事項</string> 
<!-- 運動與健身 --> 
<key>NSMotionUsageDescription</key> 
<string>需要您的同意, APP才能訪問運動與健身</string> 
<!-- 健康更新 --> 
<key>NSHealthUpdateUsageDescription</key> 
<string>需要您的同意, APP才能訪問健康更新 </string> 
<!-- 健康分享 --> 
<key>NSHealthShareUsageDescription</key> 
<string>需要您的同意, APP才能訪問健康分享</string> 
<!-- 藍芽 --> 
<key>NSBluetoothPeripheralUsageDescription</key> 
<string>需要您的同意, APP才能訪問藍芽</string> 
<!-- 媒體資料庫 --> 
<key>NSAppleMusicUsageDescription</key> 
<string>需要您的同意, APP才能訪問媒體資料庫</string>
複製程式碼

在 flutter 專案目錄中,我們也可以開啟 info.plist 檔案配置,如下圖所示

在這裡插入圖片描述
在這裡使用的是 permission_handler 外掛來申請許可權的

permission_handler: ^4.3.0
複製程式碼

申請許可權程式碼如下

  ///PermissionGroup.storage 對應的是 
  ///android 的外部儲存 (External Storage)
  ///ios 的Documents` or `Downloads`
  checkPermissFunction() async {
    if (Theme.of(context).platform == TargetPlatform.android) {
      ///安卓平臺中 checkPermissionStatus方法校驗是否有儲存卡的讀寫許可權 
      PermissionStatus permission = await PermissionHandler()
          .checkPermissionStatus(PermissionGroup.storage);
      if (permission != PermissionStatus.granted) {
        ///無許可權那麼 呼叫方法 requestPermissions 申請許可權
        Map<PermissionGroup, PermissionStatus> permissions =
            await PermissionHandler()
                .requestPermissions([PermissionGroup.storage]);
        ///校驗使用者對許可權申請的處理
        if (permissions[PermissionGroup.storage] == PermissionStatus.granted) {
          return true;
        }
      } else {
        return true;
      }
    } else {
      return true;
    }
    return false;
  }
複製程式碼

申請好許可權後,我們需要確定下來儲存卡的路徑,在這裡使用的是 path_provider 外掛

path_provider: 1.6.0
複製程式碼
  ///獲取手機的儲存目錄路徑
  ///getExternalStorageDirectory() 獲取的是  android 的外部儲存 (External Storage)
  ///  getApplicationDocumentsDirectory 獲取的是 ios 的Documents` or `Downloads` 目錄
  Future<String> getPhoneLocalPath() async {
    final directory = Theme.of(context).platform == TargetPlatform.android
        ? await getExternalStorageDirectory()
        : await getApplicationDocumentsDirectory();
    return directory.path;
  }
複製程式碼
6 dio 配製網路代理抓包
  _setupPROXY(Dio dio) {
    (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
        (HttpClient client) {
      client.findProxy = (uri) {
        ///這裡的 192.168.0.102:8888就是我們的代理服務地址
        return "PROXY 192.168.0.102:8888";
      };
      client.badCertificateCallback =
          (X509Certificate cert, String host, int port) {
        return true;
      };
    };
  }
複製程式碼
7 dio 配製公共請求引數

在實際應用開發中,我們會有像 token、appVersionCode 等等這些每個介面請求都需要傳的引數 ,稱之為公共請求引數,那麼在這裡 dio 的請求中我們可以考慮這樣來配製

  String application = "V 1.2.2";
  int appVersionCode = 122;
  ///[url]網路請求連結 
  ///[data] post 請求時傳的json資料
  ///[queryParameters] get請求時傳的引數
  void configCommonPar(url,data,Map<String, dynamic> queryParameters){
    ///配製統一引數
    if (data != null) {
      data['application'] = application;
      data['appVersionCode'] = appVersionCode.toString();
    } else if (queryParameters != null) {
      queryParameters['application'] = application;
      queryParameters['appVersionCode'] = appVersionCode.toString();
    } else {
      ///url中有可能拼接著其他引數
      if (url.contains("?")) {
        url += "&application=$application&appVersionCode=$appVersionCode";
      } else {
        url += "?application=$application&appVersionCode=$appVersionCode";
      }
    }
  }
}

複製程式碼
8 dio 配製Content-Type 與請求 header

我們在建立 Dio物件時,會初始化一個 BaseOptions 來建立 Dio

      BaseOptions options = BaseOptions();
      ///請求header的配置
      options.headers["appVersionCode"]=406;
      options.headers["appVersionName"]="V 4.0.6";
      
      options.contentType="application/json";
      options.method="GET";
      options.connectTimeout=30000;
      ///建立 dio
      Dio dio = new Dio(options);
複製程式碼

我們也可以在每次傳送 get 、post 等不同的請求時,通過 dio 獲取到 預設的 options 然後修改一下

void getRequestFunction2() async {
    ///使用者id
    int userId = 3;
    ///建立 dio
    Dio dio = new Dio();

    ///請求地址
    ///傳參方式1
    String url = "http://192.168.0.102:8080/getUser/$userId";
    ///在這裡修改 contentType
    dio.options.contentType="application/json";
    ///請求header的配置
    dio.options.headers["appVersionCode"]=406;
    dio.options.headers["appVersionName"]="V 4.0.6";
    ///發起get請求
    Response response = await dio.get(url);

  ...
  }
複製程式碼
9 dio 取消網路請求

實際開發中,例如我們退出一個頁面時,如果網路請求沒完成,就會行成記憶體洩露,所以需要在頁面消毀時,取消網路請求,或者是在下載一個檔案時,時間太長了,使用者點選取消,就需要取消網路連線

///建立取消標誌
CancelToken cancelToken = new CancelToken();
void getRequestFunction2() async {
    ///使用者id
    int userId = 3;
    ///建立 dio
    Dio dio = new Dio();
    ///請求地址
    ///傳參方式1
    String url = "http://192.168.0.102:8080/getUser/$userId";
    ///發起get請求 並設定 CancelToken 取消標誌
    Response response = await dio.get(url,cancelToken: cancelToken);

  ...
  }
複製程式碼

那麼當我們需要手動取消這個網路請求時,只需要呼叫如下方法

   ///取消網路請求
   if(cancelToken!=null&&!cancelToken.isCancelled){
      cancelToken.cancel();
      cancelToken=null;
    }
複製程式碼

需要注意的是,一個 cancelToken 只能對就一個網路請求。


完畢

相關文章