flutter dio上传文件、下载文件总结
本文章将讲述
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
- 1
- 2
也可以访问国内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
- 1
申请权限代码如下
///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
- 1
///获取手机的存储目录路径
///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 只能对就一个网络请求。
完毕