Flutter第6天--异步-IO+网络访问+json

Flutter七日游第六天:2018-12-21 天气:雨-阴

零、前言

对于问我怎么学习的人,空口白牙说的是鸡汤,我不喜欢喝也不喜欢做。
文中根据实际情况,分享一些个人的编程心得,自己参考一下,取长补短


一、单线程模型下的异步操作

  • 为什么强调是单线程:Dart是单线程模型,单线程模型,单线程模型!!!

    什么是单线程:就是你是一个人在战斗
    什么是异步: 比如你要烧水(耗时操作),并不需要傻傻地等着水开才能去做下一件事(扫地)
    只要开火(方法调用),然后你就可以去扫地(执行异步任务下面的方法),水烧开鸣叫(回调), 去冲水(处理异步任务结果)。

  • Dart异步编程的方式:Future和Stream

    Future相当于40米大砍刀,Stream相当于一捆40米大砍刀
    dart提供了关键字async(异步)await(延迟执行),相当于普通的便捷的小匕首


1.asyncawait的简单使用

感觉网上一些教程上来就告诉你什么样是错的,然后一步步纠正…最后都没有完整代码总结一下
我想最起码应该先给个正确的示范吧…然后再说错误情况

1.1:最简单的文件读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//根据名称读取文件
readFile(name) {
//创建文件对象
var file = File(name);
return file.readAsString();
}

//读取文件成功
readOk() async{
var result = await readFile(r"C:\Users\Administrator\Desktop\应龙.txt");
print(result);
}

main() {
readOk();
print("我是第几?");
}

函数执行过程中看到了async(烧水)会先去执行下面的操作(扫地),水烧开await放行,print(result);(冲水)


1.2.asyncawait的分析

也许你就问,不加async或await会怎么样?不同时加又会怎么样?
不加async或await:就像平常代码一样顺序执行
加async不加await:然并卵
不加async加await:报错


2.去拿我40米大砍刀:Future

可以看出:file.readAsString()返回的是:Future<String>,

1
2
3
4
5
6
7
8
main() {
var file = File(r"C:\Users\Administrator\Desktop\应龙.txt");
Future<String> re = file.readAsString();
re.then((result) {
print(result);
});
print("我是第几?");
}

这样操作也能达到异步的效果,具体就不深入说了
有时间打算写一篇:基于Java,Python,JavaScript(ES6+),Dart,node(都是我曾涉及过的)
综合讨论一下单线程,多线程,同步,异步,毕竟这几个词让我挺烦心


二、Dart中的IO操作

1.文件操作的API测试构造函数

1
2
3
File(文件路径)
File.fromUri(Uri资源路径标识符)
File.fromRawPath(Uint8List rawPath)

[番外]:如何去认识一个类:Uri为例

也许看到File.fromUri(Uri uri)你会说Uri我不会,然后就不管了,如果有空就看两眼呗,又不会吃亏
我的经验是先看它的构造方法,然后再看字段,再总览一下方法名(Ctr+F12)
如果你对这个类一无所知,还是先看粗略瞄一下文档注释,至少知道干嘛的
一般都会有一句简洁的话介绍它(英文不会,词典查一下,读原文档:这道坎早晚要过的)
Android中对Uri有一定的认识,知道它是一个资源定位的标志,就像门牌号吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---->[注释第一句]------------
A parsed URI, such as a URL.
url我们都知道:http://192.168.43.60:8089/file/springboot/data.json
-------------------------------------------------------------
它是由一下部分构成的:
http(协议名)://+ 192.168.43.60(域名) + :8089(端口) + file/springboot/data.json(资源地址)

比如我是资源,你要找我:
中国://安徽:合肥/瑶海区/XXX路/XXX小区/张风捷特烈
-------------------------------------------------------------

---->[Uri核心属性]------------
factory Uri(
{String scheme,
String userInfo,
String host,
int port,
String path,
Iterable<String> pathSegments,
String query,
Map<String, dynamic /*String|Iterable<String>*/ > queryParameters,
String fragment})
不知道的东西去试试呗,反正跑一下又不要钱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var base = Uri.base;
print(base);//打印了跟路径--file:///I:/Java/Android/FlutterUnit/toly/


----->[.parse方法测试]
static Uri parse(String uri, [int start = 0, int end])
//既然Uri.parse返回一个Uri对象,那么它应该有Uri的相应属性
var parse = Uri.parse("http://192.168.43.60:8089/file/springboot/data.json");
print("host=${parse.host}");//192.168.43.60
print("port=${parse.port}");//8089
print("path=${parse.path}");//file/springboot/data.json
print("query=${parse.query}");//
print("fragment=${parse.fragment}");//

----->[.http方法测试]
Uri.http(String authority, String unencodedPath,[Map<String, String> queryParameters])
----->[.http方法测试,它的注释都写成这样了,你还不会用吗?]-------
//Creates a new `http` URI from authority, path and query
//http://example.org/path?q=dart.
//new Uri.http("example.org", "/path", { "q" : "dart" });
如果用File开一个网络的Uri会怎么样:

学会分析bug不要轻易否定
首先保证网址是正确的

1
2
3
4
5
6
7
var file = File.fromUri(new Uri.http("192.168.43.60:8089", "/file/springboot/data.json"));

Unhandled exception:
Unsupported operation: Cannot extract a file path from a http URI
#0 _Uri.toFilePath (dart:core/uri.dart:2617:7)
#1 new File.fromUri (dart:io/file.dart:265:49)
#2 readFile (file:///I:/Java/Android/FlutterUnit/toly/test/base/8_io.dart:11:19)

也许你到走了,会想(当然我也是这样):什么鬼,老子看半天,TM不能用,浪费时间!
也许你会愤然而去,而我则会去分析错误的原因(这就是面对错误的不同选择)
前者可能永远也不知道原因,而后者即使最后无果,路上也会有所收获(打字的现在,我还未去分析)


所以,一起去看看吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
---->[bug的链接处:]
String toFilePath({bool windows}) {
if (scheme != "" && scheme != "file") {
throw new UnsupportedError(
"Cannot extract a file path from a $scheme URI");
}

// 原来是scheme的锅------那scheme是什么呢?
var uri = new Uri.http("192.168.43.60:8089", "/file/springboot/data.json");
print(uri.scheme);//http

//可见-- new Uri.http的scheme是http,而这里不是file所以报错
至少你的知识库中多收录了一条信息:File.fromUri()不能访问非file类型的Uri
也知道了scheme大概是什么东西,知识库就是这样一点一点自己累积的

---->[这么重要的限制,方法上能不注明吗?]-------
/**
* Create a File object from a URI.
*
* If [uri] cannot reference a file this throws [UnsupportedError].
如果uri未涉及 file会报错: UnsupportedError
*/
factory File.fromUri(Uri uri) => new File(uri.toFilePath());

好吧,是一开始没注意,到此一个错误就可以画上句号了

错误不可怕,可怕的是你不知道为什么而导致以后还会犯,总之踩的坑多了,就会知道坑在那里
也许别人给说你那有坑,第一次小心的过去了,下一次没人提醒你,你可能就掉下去

file的Uri是什么鬼?

也许你不知道,文件拖到浏览器里,也是能打开的,你所见的就是feil类型的Uri

1
2
3
4
5
  //源码上面还有很多注释自己看....
* // file:///C:/xxx/yyy
* new Uri.file(r"C:\xxx\yyy", windows: true);
*/
factory Uri.file(String path, {bool windows})
1
2
然后你就会用File.fromUri了:
var file = File.fromUri(Uri.parse("file:///F:/SpringBootFiles/file/springboot/data.json"));

从一个小的API开始,让自己尽可能去多认识一些事物,并不是说你要把源码都理得很清楚
在自己接受范围的150%之内可以去尝试,失败了没有关系,总比看那些驳来驳去的文章有意义

如果你想提高自己(这句话也是自勉):
不要让自己总走在平坦的路上,有时登高望远方能窥见美景,也不要一心只走险峰,小心失足。
今天心情不佳,废话有点多,听得进去的就听,听不进去的就无视,如果要驳我,请在评论区!!

[番外结束]

2.File和Directory的常见Api

Java里文件夹也是File对象,Dart里区分了出来
很有意思,File和Directory的Api基本上都是同步,异步成对出现


2.1:递归创建文件夹

默认recursive是false,只能创建下一级

1
2
3
4
main() {
var dir = Directory(r"C:\Users\Administrator\Desktop\dart\test\all\li");
dir.createSync(recursive: true);
}

2.2:列出所有的文件
1
2
3
4
5
6
7
main() async {
var dir = Directory(r"E:\Material\MyUI");
var list = dir.list();//默认非递归及只列出一级
list.forEach((fs){
print(fs.path);
});
}

1
2
3
4
5
6
7
main() async {
var dir = Directory(r"E:\Material\MyUI");
var list = dir.list(recursive: true);//递归列出所有文件
list.forEach((fs){
print(fs.path);
});
}

目录下所有文件都列出来,就不贴图了


2.3:重命名

看一下就行了

1
2
var dir = Directory(r"C:\Users\Administrator\Desktop\dart\test\all\li");
dir.rename(r"C:\Users\Administrator\Desktop\dart\test\all\hello");

3.File对象的常用操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//根据名称读取文件
readFile(name) async {
//创建文件对象
var file = File(name);
try {
//判断是否存在
bool exists = await file.exists();
if (exists) {
//如果存在
print(await file.length()); //文件大小(字节)---137
print(await file.lastModified()); //最后修改时间---2018-12-21 13:49:35.000
print(file.parent.path); //获取父文件夹的路径---C:\Users\Administrator\Desktop\dart
return await file.readAsString(); //读取文件并返回
} else {
await file.create(recursive: true); //不存在则创建文件
return "未发现文件,已为您创建!Dart机器人:2333";
}
} catch (e) {
//异常处理
print(e);
}
}

另外还有几种不同的打开方式,基本上Java都包含了,看名字也知道是什么


4.文件的写入:

和java一样,默认全换:想要追加:参数加mode: FileMode.append

1
2
3
4
5
6
7
8
main() async {
wirte(r"C:\Users\Administrator\Desktop\dart\应龙.txt");
}

wirte(name) async{
var file = File(name);
file.writeAsString("海的彼岸有我未曾见证的风采");
}


三、关于移动端的文件读取问题

1.路径问题

path_provider: ^0.4.1:提供了三个路径,勉强用用吧

1
2
3
4
5
6
7
8
9
10
11
12
localPath() async {
try {
print('临时目录: ' + (await getTemporaryDirectory()).path);
//----/data/user/0/com.toly1994.toly/cache
print('文档目录: ' + (await getApplicationDocumentsDirectory()).path);
//----/data/user/0/com.toly1994.toly/app_flutter
print('sd卡目录: ' + (await getExternalStorageDirectory()).path);
//----/storage/emulated/0
} catch (err) {
print(err);
}
}

2.动态权限申请问题

simple_permissions: ^0.1.9:提供了动态权限申请

1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
readFormSD() async {
try {
var perm =
SimplePermissions.requestPermission(Permission.ReadExternalStorage);
var sdPath = getExternalStorageDirectory();
sdPath.then((file) {
perm.then((v) async {
var res = await readFile(file.path + "/应龙.txt");
print(res);
});
});
} catch (err) {
print(err);
}
}

好了,这样知识就对接完毕


3.小测试:列出sd卡的文件

比较基础,就是读取文件夹下的内容,设置给ListView的Item

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:simple_permissions/simple_permissions.dart';

class ListFilePage extends StatefulWidget {
@override
_ListFilePageState createState() => _ListFilePageState();
}

class _ListFilePageState extends State<ListFilePage>
with SingleTickerProviderStateMixin {
List<String> _files = [];

@override
void initState() {
super.initState();
localPath();
}

@override
Widget build(BuildContext context) {
//生成listView
var listview = ListView.builder(
itemCount: _files.length,
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[
Container(
color: Colors.white,
padding: EdgeInsets.all(15),
child: renderItem(index))
],
);
},
);

return Scaffold(
appBar: AppBar(
title: Text("张风捷特烈"),
),
body: listview,
);
}

// 添加所有SD卡文件名称
localPath() {
try {
var perm =
SimplePermissions.requestPermission(Permission.ReadExternalStorage);
var sdPath = getExternalStorageDirectory();
sdPath.then((file) {
perm.then((v) {
file.list().forEach((i) {
_files.add(i.path);
});
setState(() {});
});
});
} catch (err) {
print(err);
}
}

//渲染单条目
renderItem(index) {
return Row(
children: <Widget>[
Icon(
Icons.extension,
color: Colors.blue,
),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 20),
child: Text(
_files[index],
style: TextStyle(fontSize: 18),
),
)),
Icon(Icons.arrow_forward),
Divider(height: 1)
],
);
}
}

三、Dart中的网络请求操作:

0.添加依赖:在pubspec.yaml的dependencies下
1
http: ^0.11.3+17

我的服务器上提供了一些网络请求的Api,如果你想自己搭建服务器接口,请看这篇
来回顾一下接口的api:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
查询接口:GET请求--------------------------------------------
----查询所有:
http://www.toly1994.com:8089/api/android/note
----查询偏移12条,查询12条(即12条为一页的第2页):
http://www.toly1994.com:8089/api/android/note/12/12
----按区域查询(A为Android数据,SB为SpringBoot数据,Re为React数据)
http://www.toly1994.com:8089/api/android/note/area/A
http://www.toly1994.com:8089/api/android/note/area/A/12/12
----按部分名称查询
http://www.toly1994.com:8089/api/android/note/name/材料
http://www.toly1994.com:8089/api/android/note/name/材料/2/2
----按类型名称查询(类型定义表见第一篇)
http://www.toly1994.com:8089/api/android/note/name/ABCS
http://www.toly1994.com:8089/api/android/note/name/ABCS/2/2
----按id名称查
http://www.toly1994.com:8089/api/android/note/12

添改删接口---------------------------------------------------------------
添-POST请求:http://www.toly1994.com:8089/api/android/note
更新-PUT请求:http://www.toly1994.com:8089/api/android/note/1
删-DELETE请求:http://www.toly1994.com:8089/api/android/note/1

1.get请求

注:client你随便取什么名字都行,客户端访问服务端,所以我用client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 'package:http/http.dart' as client;

main() {
getData((data) {
print(data);
});
}

getData(cbk) async {
var api = 'http://www.toly1994.com:8089/api/android/note/100';
try {
final response = await client.get(api);
if (response.statusCode == 200) {
cbk(response.body);
}
} catch (e) {
print(e);
}
}

如果你觉得回调有点low,也完全可以用Future(用什么不是重点,怎么简洁怎么来)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
main() {
getData().then((data){
print(data);
});
}

Future<String> getData() async {
try {
final response = await client.get('http://www.toly1994.com:8089/api/android/note/100');
if (response.statusCode == 200) {
return response.body;
}
} catch (e) {
print(e);
}
}

2.post请求:插入数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
main() {
add((data) {
print(data);
});
}

add(cbk) async {
var api = 'http://www.toly1994.com:8089/api/android/note';
var item = {
"type": "C",
"name": "插入测试",
"localPath": "null",
"jianshuUrl": "https://www.jianshu.com/p/12f8ab32591a",
"juejinUrl": "null",
"imgUrl":
"http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png",
"createTime": "2018-09-06",
"info": "null",
"area": "A"
};
try {
final response = await client.post(api, body: item);
if (response.statusCode == 200) {
cbk(response.body);
}
} catch (e) {
print(e);
}
}


3.put请求:更新数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
main() {
set((data) {
print(data);
});
}

set(cbk) async {
var api = 'http://www.toly1994.com:8089/api/android/note/199';
var item = {
"type": "C",
"name": "修改测试",
"localPath": "null",
"jianshuUrl": "https://www.jianshu.com/p/12f8ab32591a",
"juejinUrl": "null",
"imgUrl":
"http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png",
"createTime": "2018-09-06",
"info": "null",
"area": "A"
};

try {
final response = await client.put(api, body: item);
if (response.statusCode == 200) {
cbk(response.body);
}
} catch (e) {
print(e);
}
}


4.delete请求:删除操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
main() {
delete((data) {
print(data);
});
}

delete(cbk) async {
var api = 'http://www.toly1994.com:8089/api/android/note/199';

try {
final response = await client.delete(api);

if (response.statusCode == 200) {
cbk(response.body);
}
} catch (e) {
print(e);
}
}


四、关于Json

一般都是解析服务器端传来的json,非后端基本不用生产json

1.将json转化为对象
1
2
3
4
5
6
7
8
9
10
11
12
{
"id": 100,
"type": "绘图相关",
"name": "D5-Android绘图之让图形动起来",
"localPath": "null",
"jianshuUrl": "https://www.jianshu.com/p/12f8ab32591a",
"juejinUrl": "null",
"imgUrl": "http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png",
"createTime": "2018-09-06",
"info": "以前在Html利用js控制SVG或canvas进行运动模拟。浏览器自带window.requestAnimationFrame能不断执行渲染在这...",
"area": "A"
}
1.1:创建实体类,创建构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class NoteBean {
int id;
String type;
String name;
String localPath;
String jianshuUrl;
String juejinUrl;
String imgUrl;
String createTime;
String info;
String area;

NoteBean.fromJson(Map<String, dynamic> map)
: id = map['id'],
name = map['name'],
localPath = map['localPath'],
jianshuUrl = map['jianshuUrl'],
juejinUrl = map['juejinUrl'],
imgUrl = map['imgUrl'],
createTime = map['createTime'],
info = map['info'],
area = map['area'];
}
1
2
3
4
var j =
'{"id":100,"type":"绘图相关","name":"D5-Android绘图之让图形动起来","localPath":"null","jianshuUrl":"https://www.jianshu.com/p/12f8ab32591a","juejinUrl":"null","imgUrl":"http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png","createTime":"2018-09-06","info":"以前在Html利用js控制SVG或canvas进行运动模拟。浏览器自带window.requestAnimationFrame能不断执行渲染在这...","area":"A"}';
var noteBean = NoteBean.fromJson(json.decode(j));
print(noteBean.name);//D5-Android绘图之让图形动起来

2.复对的Json转化(也就是Json里套Json)
2.1:待处理的Json字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 100,
"type": "绘图相关",
"name": "D5-Android绘图之让图形动起来",
"localPath": "null",
"jianshuUrl": "https://www.jianshu.com/p/12f8ab32591a",
"juejinUrl": "null",
"imgUrl": "http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png",
"createTime": "2018-09-06",
"info": "以前在Html利用js控制SVG或canvas进行运动模拟。浏览器自带window.requestAnimationFrame能不断执行渲染在这...",
"area": "A"
}
}

2.2:增加实体类ResultBean
1
2
3
4
5
6
7
8
9
10
class ResultBean {
String msg;
int code;
NoteBean data;

ResultBean.fromJson(Map<String, dynamic> map)
: msg = map['msg'],
code = map['code'],
data = NoteBean.fromJson(map['data']);
}

2.3:使用:
1
2
3
4
5
var j =
'{"code":200,"msg":"操作成功","data":{"id":100,"type":"绘图相关","name":"D5-Android绘图之让图形动起来","localPath":"null","jianshuUrl":"https://www.jianshu.com/p/12f8ab32591a","juejinUrl":"null","imgUrl":"http://toly1994.com:8089/imgs/android/c3af376135a7abe0655c908195b271db.png","createTime":"2018-09-06","info":"以前在Html利用js控制SVG或canvas进行运动模拟。浏览器自带window.requestAnimationFrame能不断执行渲染在这...","area":"A"}}';

var result = ResultBean.fromJson(json.decode(j));
print(result.data.name);//D5-Android绘图之让图形动起来

3.关于Json的内嵌数组

这里data是一个json的数组,这样访问的服务端接口的数据处理就搞定了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 198,
"type": "绘图相关",
"name": "",
"localPath": "---",
"jianshuUrl": "",
"juejinUrl": "---",
"imgUrl": "http://toly1994.com:8089/imgs/android/8a11d27d58f4c1fa4488cf39fdf68e76.png",
"createTime": "2021-02-18",
"info": "hh",
"area": "A"
},
{
"id": 200,
"type": "绘图相关",
"name": "",
"localPath": "---",
"jianshuUrl": "",
"juejinUrl": "---",
"imgUrl": "http://toly1994.com:8089/imgs/android/8a11d27d58f4c1fa4488cf39fdf68e76.png",
"createTime": "2018-12-21",
"info": "hh",
"area": "A"
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class ResultBean {
String msg;
int code;
var data;

ResultBean.fromJson(Map<String, dynamic> map)
: msg = map['msg'],
code = map['code'],
data = map['data'];
}

class NoteBean {
int id;
String type;
String name;
String localPath;
String jianshuUrl;
String juejinUrl;
String imgUrl;
String createTime;
String info;
String area;

NoteBean.fromJson(Map<String, dynamic> map)
: id = map['id'],
name = map['name'],
localPath = map['localPath'],
jianshuUrl = map['jianshuUrl'],
juejinUrl = map['juejinUrl'],
imgUrl = map['imgUrl'],
createTime = map['createTime'],
info = map['info'],
area = map['area'];
}
1
2
3
var j ='{"code":200,"msg":"操作成功","data":[{"id":198,"type":"绘图相关","name":"","localPath":"---","jianshuUrl":"","juejinUrl":"---","imgUrl":"http://toly1994.com:8089/imgs/android/8a11d27d58f4c1fa4488cf39fdf68e76.png","createTime":"2021-02-18","info":"hh","area":"A"},{"id":200,"type":"绘图相关","name":"","localPath":"---","jianshuUrl":"","juejinUrl":"---","imgUrl":"http://toly1994.com:8089/imgs/android/8a11d27d58f4c1fa4488cf39fdf68e76.png","createTime":"2018-12-21","info":"hh","area":"A"}]}';
var result = ResultBean.fromJson(json.decode(j));
print(NoteBean.fromJson(result.data[1]).imgUrl);//http://toly1994.com:8089/imgs/android/8a11d27d58f4c1fa4488cf39fdf68e76.png

好了,今天就到这里,明天最后一天,敬请期待

张风捷特烈 wechat
-------------本文结束感谢您的阅读 @张风捷特烈-------------
0%