终端命令安装

How to install apt-get or YUM on Mac OS X

参考文章:How to install apt-get or YUM on Mac OS X

如果要在Mac OS X上安装apt-get或yum类似的软件,你有两种选择:

  1. Homebrew:http://brew.sh
  2. Macports:http://www.macports.org

安装了上面的程序之后(一个就行),您可以使用brew install PACKAGE_NAMEport install PACKAGE_NAME安装可用的软件包。

rpm -qa |grep yum

Zentao发送邮件问题的终端检测

image-20190305171822375

-bash: rpm: command not found

brew install rpm

-bash: yum: command not found

Mac OS X是基于Unix的,它可以使用非常多Linux平台上开源的优秀工具,比如wget,比如dos2unix脚本工具等。
但是OS X系统本身却缺少Linux下得包管理器。比如Fedora的yum与dnf,比如Ubuntu的apt-get,比如ArchLinux的Pacman等。是这些优秀的开源软件在Mac上的安装只能通过下载源码,编译,安装,配置环境变量的步骤来完成安装。对于大部分的软件,在安装过程中是需要很多的依赖库的,手动去解决这些依赖库是十分痛苦的事情。包管理器干的就是这样的事情:解决软件安装过程中的依赖关系。
有一个开源的项目叫Homebrew,完美解决了Mac OS X上没有包管理器的尴尬。
参考文档:为什么要使用Homebrew

Mac下自己试的是无法安装yum,相应的yum install,改为了brew install。

image-20190305171015368

Zentao问题整理

[TOC]

一、Zentao的安装

请到https://www.zentao.net下载开源版

如:image-20190305105851487

在浏览器中访问项目地址:http://localhost/zentaopms/www/index.php,进入系统安装程序,点击开始安装。

二、Zentao访问问题

1、访问问题

请确保你的服务器是打开的,比如你使用的是XAMPP

Zentao的XAMPP

2、Zentao访问问题:Unknown database ‘zentao’

问题描述如下截图:

image-20190305102934706

原因是:http://localhost/phpmyadmin/下原本的zentao数据库缺失了,即原本的应该是有如下的zentao数据库的。

image-20190305102829575

或者你也可以看到xamppfiles\var\mysql下缺少了zentao数据库文件夹了,有的时候是如下样子的:

image-20190305104603201

解决办法:赋值一份原本的到xamppfiles\var\mysql下,或者你重新安装禅道吧。

三、如何配置email发信

请参考官网操作:官网:如何配置email发信

附:

1
2
我的网易授权码是`Auth0987`、
QQ邮箱授权码`**nazr****mrbz****naox****bbhg**`即nazr mrbz naox bbhg

image-20190305173747142

为看起来更好,你可以更改发信人

image-20190305174930068

四、备份禅道

备份禅道:https://www.zentao.net/book/zentaopmshelp/42.html

XAMPP问题整理

[TOC]

一、XAMPP 下载地址

点击进入:XAMPP 下载地址

XAMPP 下载地址1

进入后的页面如下

image-20190304181710320

注意:这里要下载-installer.dmg的安装,不要下载-vm.dmg的安装。

否则,会造成XAMPP安装后界面不一样

二、问题整理

问题一、mac XAMPP mysql无法启动的问题

可能原因一:/Applications/XAMPP/xamppfiles/xampp文件问题。

解决方法:
1、打开/Applications/XAMPP/xamppfiles/xampp进行编辑
2、找到这一行:$XAMPP_ROOT/bin/mysql.server start > /dev/null &
3、添加如下一行在找到的那一行上面:unset DYLD_LIBRARY_PATH
然后启动mysql,如果无效果跳到解决方法二

可能原因二:mysql进程已经存在

解决方法:杀死与mysql有关的进程后,重新尝试启动mysql

方法一:打开终端,输入ps aux | grep mysql,拿到进程号pid(如这里是pid=120)后,执行杀死进程的命令kill -9 进程号即如 kill -9 120即可重新启动mysql。

image-20190304220653289方法二:打开活动监视器,搜索mysql,关闭对应进程即可。
image-20190304183828300

mysql进程关不掉

有时候如果你发现活动监视器关掉mysql后,又自己生成了,而造成你的mysql一直关不掉,那么请检查是不是你开着如下的mysql。那么我们关掉后即可。

image-20190304223329375

image-20190304223959685

第1节:Flutter源码查找

[TOC]

前言

2、flutter external声明方法的源码查找

image-20230916193216119

图片2

image-20230916193753386

image-20230916194301820

image-20230916194442691

2、_network_image_io.dart

~/.pub-cache/hosted/pub.flutter-io.cn/extended_image_library-3.4.2/lib/src/_network_image_io.dart

其主要方法如下:

1、loadBuffer方法用于加载图片数据并返回ImageStreamCompleter对象。

2、_loadAsync方法是实际加载图片数据的核心逻辑。它首先尝试从缓存中加载图片数据,如果缓存中不存在,则从网络加载图片数据。

3、_loadCache方法用于从缓存中加载图片数据。

4、_loadNetwork方法用于从网络加载图片数据。

5、getNetworkImageData方法用于从缓存或网络获取图片数据。

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/// loadBuffer方法用于加载图片数据并返回ImageStreamCompleter对象。
@override
ImageStreamCompleter loadBuffer(
image_provider.ExtendedNetworkImageProvider key,
DecoderBufferCallback decode) {
// Ownership of this controller is handed off to [_loadAsync]; it is that
// method's responsibility to close the controller's stream when the image
// has been loaded or an error is thrown.
final StreamController<ImageChunkEvent> chunkEvents =
StreamController<ImageChunkEvent>();

return MultiFrameImageStreamCompleter(
codec: _loadAsync(
key as ExtendedNetworkImageProvider,
chunkEvents,
decode,
),
scale: key.scale,
chunkEvents: chunkEvents.stream,
debugLabel: key.url,
informationCollector: () {
return <DiagnosticsNode>[
DiagnosticsProperty<ImageProvider>('Image provider', this),
DiagnosticsProperty<image_provider.ExtendedNetworkImageProvider>(
'Image key', key),
];
},
);
}

/// _loadAsync方法是实际加载图片数据的核心逻辑。它首先尝试从缓存中加载图片数据,如果缓存中不存在,则从网络加载图片数据。
Future<ui.Codec> _loadAsync(
ExtendedNetworkImageProvider key,
StreamController<ImageChunkEvent> chunkEvents,
DecoderBufferCallback decode,
) async {
assert(key == this);
final String md5Key = cacheKey ?? keyToMd5(key.url);
ui.Codec? result;
if (cache) {
try {
final Uint8List? data = await _loadCache( /// _loadCache方法用于从缓存中加载图片数据。
key,
chunkEvents,
md5Key,
);
if (data != null) {
result = await instantiateImageCodec(data, decode);
}
} catch (e) {
if (printError) {
print(e);
}
}
}

if (result == null) {
try {
final Uint8List? data = await _loadNetwork(
key,
chunkEvents,
);
if (data != null) {
result = await instantiateImageCodec(data, decode);
}
} catch (e) {
if (printError) {
print(e);
}
}
}

//Failed to load
if (result == null) {
//result = await ui.instantiateImageCodec(kTransparentImage);
return Future<ui.Codec>.error(StateError('Failed to load $url.'));
}

return result;
}

/// _loadCache方法用于从缓存中加载图片数据。
/// Get the image from cache folder.
Future<Uint8List?> _loadCache(
ExtendedNetworkImageProvider key,
StreamController<ImageChunkEvent>? chunkEvents,
String md5Key,
) async {
final Directory _cacheImagesDirectory = Directory(
join((await getTemporaryDirectory()).path, cacheImageFolderName));
Uint8List? data;
// exist, try to find cache image file
if (_cacheImagesDirectory.existsSync()) { /// existsSync是一个同步方法,用于检查具有该路径的文件系统实体是否存在。
final File cacheFlie = File(join(_cacheImagesDirectory.path, md5Key));
if (cacheFlie.existsSync()) { /// existsSync是一个同步方法,用于检查具有该路径的文件系统实体是否存在。
if (key.cacheMaxAge != null) {
final DateTime now = DateTime.now();
final FileStat fs = cacheFlie.statSync();
if (now.subtract(key.cacheMaxAge!).isAfter(fs.changed)) {
cacheFlie.deleteSync(recursive: true);
} else {
data = await cacheFlie.readAsBytes();
}
} else {
data = await cacheFlie.readAsBytes();
}
}
}
// create folder
else {
await _cacheImagesDirectory.create();
}
// load from network
if (data == null) {
data = await _loadNetwork(
key,
chunkEvents,
);
if (data != null) {
// cache image file
await File(join(_cacheImagesDirectory.path, md5Key)).writeAsBytes(data);
}
}

return data;
}

existsSync() 方法是 dart:io 包中的一个方法,用于检查文件系统实体是否存在。

问:existsSync() 会有性能问题吗

existsSync() 方法是一个同步方法,它会阻塞当前线程,直到文件系统中的实体存在与否得到确认。在使用 existsSync() 方法时,如果要检查的文件系统实体数量较少,并且操作的频率不高,一般不会引起明显的性能问题。

然而,如果你需要频繁地调用 existsSync() 方法,或者需要检查大量的文件系统实体,那么它可能会对性能产生一定的影响。这是因为每次调用 existsSync() 都需要同步地与文件系统进行交互,这可能会导致线程阻塞、IO操作和文件系统访问开销增加。

如果你关注性能问题,建议考虑使用异步的方式来检查文件系统实体的存在性,例如使用 exists() 方法,它返回一个 Future<bool>,可以非阻塞地进行文件系统查询。异步方式可以提高并发性和响应性,并且在处理大量文件系统实体时具有更好的性能表现。

另外,如果你需要频繁地监视文件系统中的实体变化,例如文件的创建、修改或删除,可以考虑使用文件系统监听器(File System Watcher)来异步地接收文件系统事件通知,而不是反复地调用 existsSync() 方法。

总结起来,如果使用恰当并避免频繁调用,existsSync() 方法不会引起严重的性能问题。但在需要高性能和并发性的场景下,异步方式可能更为合适。

image-20230916201242976

第4节:Dart语言必备基础知识

[TOC]

前言

如果你只是写界面可能你看Flutter的Widget就够了。

可是当你写业务的时候,你不去了解Dart语言那么你讲寸步难行。

本文致力于以最小的篇幅介绍Flutter的敲门砖:Dart最必备的基础知识。

  • 在Dart中,一切都是对象,一切对象都是class的实例,哪怕是数字类型、方法甚至null都是对象,所有的对象都是继承自Object

  • 跟Java不同的是,Dart没有public protected private等关键字,如果某个变量以下划线(_)开头,代表这个变量在库中是私有的

一、变量

1、变量定义

1
2
3
4
int b = 10;
String s = "hello";
var a = 1;
dynamic c = 0.5;

可以明确指定某个变量的类型,如int bool String,也可以用var或 dynamic来声明一个变量,Dart会自动推断其数据类型。

dynamic 则是告诉编译器,我们知道自己在做什么,不用做类型检测

2、变量类型检测

为了在运行时检测进行类型检测,Dart 提供了一个关键字 is:

1
2
3
4
5
6
7
8
dynamic obj = {};
if (obj is Map) {
// 进过类型判断后,Dart 知道 obj 是一个 Map,所以这里再不用强制转换 obj 的类型为Map。
obj['foo'] = 42;
}

// 虽然 Dart 也提供了 as 让我们进行类型的强制转换,但为了进来更安全的转换,更推荐使用 is
var map = obj as Map;

3、类型互转

3.1 String与int互转

1
2
int.parse("100");
123.toString();

二、函数

1、函数的写法

以下三种函数的写法,都是正确的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 写法1:声明返回值(推荐)
int add(int a, int b) {
return a + b;
}

// 写法2:不声明返回值(不加返回值的函数同样可以正常工作)
add2(int a, int b) {
return a + b;
}

// 写法3:你还可以用`=>`代替return语句(=>是return语句的简写)
// 此写法只能执行单行
add3(a, b) => a + b;

main() {
print(add(1, 2)); // 3
print(add2(2, 3)); // 5
print(add3(1, 2)); // 3
}

所有的函数都有返回值,如果没有指定return语句,那么该函数的返回值为null。

1.1、类方法/实例方法

1
2
3
4
5
6
7
8
9
Class A {
int method1(int x); // 实例方法

static int method2(int y); // 类方法
}

// 调用
a = A().method1(1);
b = A.method2(2);

2、函数的参数(可选/默认值/必选)

2.1、基本的函数参数写法

1
2
3
4
5
6
7
8
void main() {
print(defalut_foo1(1, 2)); // 结果为3
}

// 这种常见的写法,参数默认都是必填的,而且没法给默认值
int defalut_foo1(int x, int y) {
return x + y;
}

2.2、标记参数为可选/默认值

知识点:

①要设置参数的默认值,只有该参数为可选的时候,才能够设置。

②参数可选的方法有两种,常规的参数加上[],或者使用具名参数

1、常规的参数加上[],标记为可选

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
void main() {
print(option_foo1(1)); // 结果为1, x
print(option_foo1(1, 1)); // 结果为11, x+10y,而不会是x+100z
print(option_foo1(1, 1, 1)); // 结果为111, x+10y+100z
}

// 可选参数
// 使用可选参数的函数写法1:含可选参数的函数
int option_foo1(int x, [int y, int z]) {
int iResult = x;
if (y != null) { // 是的,int 也可以是 null
iResult += 10*y;
}

if (z != null) { // 是的,int 也可以是 null
iResult += 100*z;
}

return iResult;
}

// 使用可选参数的函数写法2:可选参数支持有默认值
int option_foo2(int x, [int y = 0]) {
return x + y;
}

2、使用具名参数(named parameters),使得参数为可选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 具名参数(named parameters)的用法,调用时候具名参数的顺序可以是任意的
// 使用具名参数的函数参数写法1:
int named_foo1({int x, int y}) {
return x + y;
}


// 使用具名参数的函数参数写法2:所有的具名参数本身就都是可选的,而且具名参数也可以有默认参数
int named_foo2({int x = 0, int y = 0}) {
return x + y;
}

void main() {
// 具名参数的顺序可以是任意的
print(named_foo1(x: 1, y: 2));
print(named_foo1(y: 3, x: 4));
print(named_foo1()); //错误:会导致 foo() 在运行时抛异常
print(named_foo2(x: 1, y: 2));
print(named_foo2()); // 正确:不会导致 foo() 在运行时抛异常
}

2.3、如果想告诉用户某个参数是必须的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 必须参数(如果想告诉用户某个具名参数是必须的,可以使用注解 @require)
// 不是具名参数,默认就是 @required 必填。
int require_foo1(@required int x, @required int y) {
return x + y;
}

// 是具名参数,默认就是 非必填,非 @required
int require_foo2({@required int x, @required int y}) {
return x + y;
}

void main() {
print(require_foo1(1, 2)); // 结果为3

print(require_foo2(x:1, y:2)); // 结果为3
}

3、回调/函数做参数/返回值

3.1、回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 用typedef用于定义函数类型的别名
typedef ClickCallBack = int Function(int x, int y);

// 实例:将a和b两个数,经过指定的操作返回
String onButtonPress1(int a, int b, ClickCallBack callback) {
int iResult = callback(a, b);
return iResult.toString();
}

void main() {
String string = onButtonPress1(1, 2, (x, y) {
return x + y;
});
print("string = " + string);
}

3.2、函数参数

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
// 用typedef用于定义函数类型的别名
typedef Adder = int Function(int, int);

String Function(String msg) cancelHandle;// 实际场景中的应用

Adder makeAdder1(int extra) {
int adder(int x, int y) {
return x + y + extra;
}
return adder;
}

// Dart 里面不仅变量支持类型推断,lambda 的参数也支持自动推断。上面的代码还可以进一步简化为:
Adder makeAdder2(int extra) {
// 我们要返回的类型是 Adder,所以 Dart 知道 x, y 都是 int
return (x, y) => x + y + extra;
}

void main() {
var adder1 = makeAdder1(2);
print(adder1(1, 2));

var adder2 = makeAdder2(2);
print(adder2(1, 2));
}

4、函数类型

Dart是一个面向对象的编程语言,所以即使是函数也是一个对象,也有一种类型Function,这就意味着函数可以赋值给某个变量或者作为参数传给另外的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
printNum(int a) {
print("$a");
}

test(Function callback) {
callback("hello");
}


main() {
var f1 = printNum;
Function f2 = printNum;
var f3 = (int a) => print("a = $a");
f1(1);
f2(2);
f3(6);
}

三、类

1、类的定义

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
class User {
// 成员变量写法1:
String name;
int age;
String gender;
// 成员变量写法2:使用getter/setter声明的成员变量
String get uid => "24210853532539238";
set right(String uid) => left = value - width;

// 通过创建一个与其类同名的函数来声明构造函数。如果您未声明构造函数,则会为您提供默认构造函数。默认构造函数没有参数,并在超类中调用无参数构造函数。
// 构造方法写法1:跟类名相同的构造方法
User(this.name, this.age, this.gender);
// 构造方法写法2:命名的构造方法
User.defaultUser() {
name = "dvlp";
age = 20;
}

// 成员方法写法1:有方法体(是非抽象方法)
sayHello() {
print("hello, this is $name, I am $age years old, I am a $gender");
}
// 成员方法写法2:没有方法体(是抽象方法,需要子类去实现)
void doSomething();
}

// 抽象类(无法实例化的类),如果希望抽象类看起来是可实例化的,请定义工厂构造函数。
// 抽象类通常有抽象方法,如下:
// 此类声明为abstract,因此是抽象类,即也就无法实例化
abstract class Test {
//定义构造函数,字段,方法...

// 抽象方法
void test();
}

说明类中的构造方法Person(this.name, this.age, this.gender);的这种语法是Dart比较独特而简洁的构造方法声明方式,它等同于下面的代码:

1
2
3
4
5
User(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}

其他声明成员变量的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用getter/setter声明的成员变量
class Rectangle {
num left, top, width, height;

// 构造方法传入left, top, width, height几个参数
Rectangle(this.left, this.top, this.width, this.height);

// right, bottom两个成员变量提供getter/setter方法
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}

2、类的使用

1
2
3
4
5
6
7
8
main() {
var user1 = new Person("zhangsan", 20, "male");
user.age = 30;
user.gender = "female";
user.sayHello();

var user2 = new Person.defaultUser()
}

当我们调用一个不存在的方法时,会执行 noSuchMethod() 方法,默认情况下它会抛出 NoSuchMethodError。你也可以重写noSuchMethod(),改成你输出你希望的错误提示

1
2
3
4
5
6
7
8
9
10
class Test {
// 除非你重写noSuchMethod,否则使用不存在的成员会导致NoSuchMethodError
// Unless you override noSuchMethod, using a
// non-existent member results in a NoSuchMethodError.
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ' +
'${invocation.memberName}');
}
}

3、类的继承

Dart中使用extends关键字做类的继承。

1
2
3
4
5
6
class Developer extends User {
// 实现了父类的抽象方法
void doSomething() {
print("I'm developing...");
}
}

虽然 Dart 是单继承的,但它也提供了一定程度的多重继承支持:

使用@override注解声明你要重写的函数,在这个函数内部可以使用super调用重写的这个父类的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class FlutterDeveloper extends Developer {
@override // 表示doSomething是重写父类的方法
void doSomething() {
super.doSomething();
print("I'm developing Flutter...");
}
}

class iOSDeveloper extends Developer {
@override // 表示doSomething是重写父类的方法
void doSomething() {
super.doSomething();
print("I'm developing iOS...");
}
}

class FullStackDeveloper extends Developer with iOSDeveloper with FlutterDeveloper {
@override // 表示doSomething是重写父类的方法
void doSomething() {
super.doSomething();
print("I'm developing...");
}
}

mixins

mixins是一个重复使用类中代码的方式,比如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A {
a() {
print("A's a()");
}
}

class B {
b() {
print("B's b()");
}
}

// 使用with关键字,表示类C是由类A和类B混合而构成
class C = A with B;

main() {
C c = new C();
c.a(); // A's a()
c.b(); // B's b()
}

四、库

1
2
3
4
5
6
7
8
9
// 用import语句来导入某个包
// 如果你想导入自己写的某个代码文件,使用相对路径即可
import './util.dart';

// 你可以使用as关键字为导入的某个包设置一个前缀,或者说别名
import 'package:lib2/lib2.dart' as lib2;

// 导入包时使用deferred as可以让这个包懒加载,懒加载的包只会在该包被使用时得到加载,而不是一开始就加载,比如
import 'package:greetings/hello.dart' deferred as hello;

1、问题一:在同一工程中可否有两个同名的类

1
2
3
4
5
6
7
8
import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Uses Element from lib1.
Element element1 = Element();

// Uses Element from lib2.
lib2.Element element2 = lib2.Element();

2、问题二、如何只允许/只不允许使用某个包中的部分功能

1
2
3
4
5
// 只导入lib1中的foo功能
import 'package:lib1/lib1.dart' show foo;

// 导入lib2中的除了foo外的所有其他部分
import 'package:lib2/lib2.dart' hide foo;

五、运算符

1、常见的

运算符 含义
++var var=var+1表达式的值为var+1
var++ var=var+1表达式的值为var
--var var=var-1表达式的值为var-1
var-- var=var-1表达式的值为var

++a

a == b

b ? a : b

2、特殊的

2.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 Person {
eat() {
print("I am eating...");
}

sleep() {
print("I am sleeping...");
}

study() {
print("I am studying...");
}
}

main() {
// 依次打印
// I am eating...
// I am sleeping...
// I am studying...
new Person()..eat()
..sleep()
..study();
}

六、控制流程

语句 说明
if / else
switch 略(switch 也支持 String 和 enum)
for /while
try / catch
1
2
3
4
5
6
7
8
9
10
11
12
13
// switch语句
String a = "hello";
// case语句中的数据类型必须是跟switch中的类型一致
switch (a) {
case "hello":
print("haha");
break;
case "world":
print("heihei");
break;
default:
print("WTF");
}

七、异步

如果一个方法中有耗时的操作,你需要将这个方法设置成async,并给其中的耗时操作加上await关键字,如果这个方法有返回值,你需要将返回值塞到Future中并返回,如下代码所示:

1
2
3
4
Future<String> checkVersion() async {
var version = await lookUpVersion();
return version;
}

八、其他/奇技淫巧

1、静态成员变量和静态成员方法

1
2
3
4
5
6
7
8
9
10
11
12
// 类的静态成员变量和静态成员方法
class Cons {
static const name = "zhangsan";
static sayHello() {
print("hello, this is ${Cons.name}");
}
}

main() {
Cons.sayHello(); // hello, this is zhangsan
print(Cons.name); // zhangsan
}

2、修饰符final和const

final 跟 Java 里的 final 一样,表示一个运行时常量(在程序运行的时候赋值,赋值后值不再改变)。const 表示一个编译时常量,在程序编译的时候它的值就确定了。

修饰符 作用
final 一个被final修饰的变量只能被赋值一次,
const 一个被const修饰的变量是一个编译时常量(const常量毫无疑问也是final常量)

final和const的区别:

区别一:final 要求变量只能初始化一次,并不要求赋的值一定是编译时常量,可以是常量也可以不是。而 const 要求在声明时初始化,并且赋值必需为编译时常量。

区别二:final 是惰性初始化,即在运行时第一次使用前才初始化。而 const 是在编译时就确定值了。

3、泛型

1
var names = List<String>();

其他参考文档:

第3节:Package的使用、开发与发布

[TOC]

一、常用的第三方Package

参考文章:

二、第三方库的导入

  • 1、添加依赖:在pubspec.yaml文件中找到dependencies在里面添加要导入的第三方库。

Package使用1.png

注意①:导入的位置要与dependencies下面的flutter上下对齐,不然会不通过。

注意②:一般写在dependencies下面即可,dependencies 与dev_dependencies 分别是代表用户发布环境与本地环境开发。

  • 2、安装依赖:在 terminal中: 运行

    1
    2
    3
    flutter packages get
    或者
    flutter packages upgrade

    或者在IntelliJ等开发工具中点击pubspec.yaml文件顶部的Packages Get即可安装。

  • 3、使用依赖:在您的Dart代码中添加相应的import语句。

分割图1

三、如何依赖未发布的packages

即使未在Pub上发布,软件包也可以使用。对于不用于公开发布的专用插件,或者尚未准备好发布的软件包,可以使用其他依赖项选项。

  • 路径 依赖: 一个Flutter应用可以依赖一个插件通过文件系统的path:依赖。路径可以是相对的,也可以是绝对的。例如,要依赖位于应用相邻目录中的插件’plugin1’,请使用以下语法

    1
    2
    3
    dependencies:
    plugin1:
    path: ../plugin1/
  • Git 依赖: 你也可以依赖存储在Git仓库中的包。如果软件包位于仓库的根目录中,请使用以下语法:

    1
    2
    3
    4
    dependencies:
    plugin1:
    git:
    url: git://github.com/flutter/plugin1.git
  • Git 依赖于文件夹中的包: 默认情况下,Pub假定包位于Git存储库的根目录中。如果不是这种情况,您可以使用path参数指定位置,例如:

    1
    2
    3
    4
    5
    dependencies:
    package1:
    git:
    url: git://github.com/flutter/packages.git
    path: packages/package1

分割图1

四、开发Packages

Step 1: 创建package工程

要创建Dart包,请使用--template=package 来执行 flutter create,如:

1
flutter create --template=package adapt_cjhelper

image-20190306143736118

这将在adapt_cjhelper/文件夹下创建一个具有以下专用内容的package工程:

image-20190306143536586

Step 2: 实现package功能

对于纯Dart包,只需在主lib/<package name>.dart文件内或lib目录中的文件中添加功能 。

分割图1

五、发布Packages

1、处理pubspec.yaml

image-20190306154221495

问题1:处理包的相互依赖

如果您正在开发一个hello包,它依赖于另一个包,则需要将该依赖包添加到pubspec.yaml文件的dependencies部分

1
2
dependencies:
url_launcher: ^0.4.2

2、验证 packages

运行 dry-run 命令以查看是否都准备OK了:

1
$ flutter packages pub publish --dry-run

image-2019030615434995

3、发布 packages

最后, 运行发布命令:

1
2
3
4
$ flutter packages pub publish --server=https://pub.dev

# 不填的时候默认就是pub.dev,你不用特意的去发布到 https://pub.flutter-io.cn,因为你只要发布到pub.dev,就可以也在pub.flutter-io.cn中使用
$ flutter packages pub publish

3.1、如果你不是第一次上传时候,但又报了如下错 Authentication failed!

image-20230323183430698

则你需要重新登录flutter pub login

3.2、如果是第一次上传

第一次上传,会要求登录谷歌账号。这个时候终端会出现一个url地址,类似这样:

image-20190306155251926

将地址复制出来,在浏览器URL中输入打开这个地址,允许登录谷歌账号就可以了。确认登录后,就会从执行终端将开始连接谷歌并上传我们的插件包。

1
Waiting for your authorization...

变到

1
Authorization received, processing...

如下:

image-20190306160117171

此步有可能会执行失败,提示如下错误,这是因为谷歌被屏蔽而导致的失败。

image-20190306160634807

原因是虽然你网页成功了,但是终端ping google.com还没翻成功。

ping google 1

相关原理详细见:为什么ping不通google.com

此时你去执行curl ip.gs,得到的是不成功的超时结果。

image-20200821151008710

这个时候我们需要设置终端的HTTP代理。查看我们的代理,并在终端中设置对应的值,如下:

image-01903061622041

在终端中执行如下命令配置代理:

1
2
set http_proxy=http://127.0.0.1:1080
set https_proxy=https://127.0.0.1:1080

如果你使用的是ClashX可以直接复制终端命令:

image-20230319165811253

得到的结果会是:

1
export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890

配置好后重新执行发布命令

1
2
3
flutter packages pub publish --server=https://pub.dev

# 如果不写 --server=https://pub.dev ,你可能发布到其他服务

即可。

分割图1

附:macOS 终端设置http和https代理

1、运行命令 vim ~/.bashrc , 写入如下内容

1
2
3
4
export http_proxy="http://127.0.0.1:4780"
export https_proxy="https://127.0.0.1:4780"
export socket_proxy="socks://127.0.0.1:4781"
export socks_proxy="socks://127.0.0.1:4781"

4780为本地http代理端口,可能会有所偏差,需要查询代理配置。

当通过软件启动代理的时候,其启动按键,会为你的网络设置开启如下三个代理。而上面的代理接口为下:

image-20200821155621945

2、运行命令 vim ~/.bash_profile,写入如下内容

1
source ~/.bashrc

3、重启终端,运行命令curl ip.gs显示当前ip和所属地区查看是否代理成功。成功的话,国家是United States

image-20190306174914736

其他参考文章:终端连代理方法

其他

1、Flutter的pubspec.yaml是本地文件,怎么把路径抽出来

在 Flutter 中,如果你想将 pubspec.yaml 文件中的路径抽出来,可以使用 environmentdefinie 关键字来定义变量。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
environment:
sdk: ">=2.15.0 <3.0.0"

name: my_app
description: My Flutter app

# 抽出路径为变量
$lib_path: lib/

dependencies:
flutter:
sdk: flutter

# 使用抽出的路径变量
my_package:
path: $lib_path/my_package

dev_dependencies:
flutter_test:
sdk: flutter

flutter:
uses-material-design: true

在这个示例中,我们使用 $lib_path 定义了变量,并将 lib/ 赋值给它。然后,在 dependencies 部分中,我们使用 $lib_path 变量来引用 my_package 的路径。

这样做的好处是,如果你想要修改 lib 文件夹的路径,你只需要在 pubspec.yaml 文件中修改 $lib_path 变量的值即可,而不需要在整个文件中搜索和替换路径。