題記 —— 執劍天涯,從你的點滴積累開始,所及之處,必精益求精,即是折騰每一天。
重要訊息
- flutter中網路請求dio使用分析 視訊教程在這裡
- Flutter 從入門實踐到開發一個APP之UI基礎篇 視訊
- Flutter 從入門實踐到開發一個APP之開發實戰基礎篇
- flutter跨平臺開發一點一滴分析系列文章系列文章 在這裡了
本文章將講述 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 只能對就一個網路請求。
完畢