flutter網絡請求dio的get、post、上傳文件、下載文件總結

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

重要消息


本文章將講述
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跨平臺開發中訪問網絡的框架,在使用的時候,我們首先是引入依賴

dependencies:
 dio: 3.0.9

也可以訪問國內pub倉庫來查看 dio的最新版本。

一般添加依賴如下所示

dependencies:
  dio: ^3.0.9

兩種寫法的差別是 ^在每次 flutter pub get 是會有小版本的自動升級,不添加這個符號就不會有自動小升級

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 只能對就一個網絡請求。


完畢

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章