第1节:Flutter源码查找

前言

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

Shell高级加密可执行

shc是一个加密shell脚本的工具.它的作用是把shell脚本转换为一个可执行的二进制文件.

1
brew install shc

使用方法:

1
shc -r -f script-name #注意:要有-r选项, -f 后跟要加密的脚本名.

运行后会生成两个文件,

1
2
3
4
script-name.x是加密后的可执行的二进制文件. (双加或者 ./script-name 即可运行.)


script-name.x.c是生成script-name.x的原文件(c语言)

shc -v -r -f echo1 打包成echo1.x二进制文件,同时生成 echo1.x.c文件,这个为C语言文件。猜想这个工具是将shell脚本转为C语言,然后在打包成二进制文件。

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

前言

如果你只是写界面可能你看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
9
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的使用、开发与发布

一、常用的第三方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 变量的值即可,而不需要在整个文件中搜索和替换路径。

第5节:Plugin插件的开发与发布

前言

本文一个比 Flutter中文网的开发Packages和插件《Flutter实战》的包与插件 都更简单的Plugin插件教学。

让你在毫无思想准备下3分钟就不知不觉不仅学会还理解了Plugin的开发和发布。

题外话:一个完整的Plugin插件本质上最后其实就是一个Package。

一、Plugin插件的创建

终端执行以下命令,进行Plugin插件的创建

1
flutter create --org com.dvlproad --template=plugin cj_monitor_flutter

执行完此条命令后,其实你的cj_monitor_flutter插件就已经开发完成,可以进行发布了。

1、理解命令所做的事情

该命令,会生成以下文件。其执行的效果为:

1、创建了一个package库,带有默认功能;

Plugin插件的开发与发布1

2、并创建一个example工程,来在工程中测试本地的package库;

Plugin插件的开发与发布2

2、使用该插件

2.1、本地工程使用可立即使用

此时就可以在你的本地其他工程中使用这个插件了。使用的方法和该命令中example对该插件的使用一致,即使用本地路径来引入库。

Plugin插件的开发与发布3

即:

1
2
3
4
5
6
dependencies:
flutter:
sdk: flutter

CJMonitorFlutter:
path: ../

2.2、非本地工程需发布后使用

package的发布,同前一章一样。这里只简单描述。即:

1、在发布之前,检查pubspec.yaml、README.md以及CHANGELOG.md文件,以确保其内容的完整性和正确性。

2、然后, 运行 dry-run 命令以查看是否都准备OK了:

1
$ flutter packages pub publish --dry-run

3、最后, 运行发布命令:

1
$ flutter packages pub publish

分割图1

二、Plugin插件的新功能开发

好了,下面我们来说重点。为我们刚才创建的Plugin插件开发新功能。

1、添加提供给【外部】的新功能dart方法。(这部很简单)

Plugin插件的开发与发布4

2、去android和ios中实现新功能呢方法的内部与原生交互代码(这部是功能代码的核心)。

Plugin插件的开发与发布5

至此,您的新功能就发布完毕,可以在其他本地工程中使用啦。

问:我的新功能已经用原生库实现了。怎么继续用库??

image-20200820020901951

附:如果是私有库呢?

答:同理。和你正常使用私有库一样。和这里相比只是多了在XXXX.xcworkspace中加入私有库的source而已。

End

第7.5节:详解Animation-5疑难杂症

一、疑难杂症

1、gif 如何停止播放

1
2
Image.asset("assets/tasky_logo.gif",
gaplessPlayback: false, fit: BoxFit.fill)

以上来源于:How to stop GIF loop in flutter?

1、重新打开对话框时,Flutter gif不会再次播放

我有一个设置,其中我有一个带有gif的警告对话框。当对话框打开时,我希望gif只播放一次,这是我用这篇文章中最好的答案实现的:

How to stop GIF loop in flutter?

问题是,当我重新打开对话框时,gif不会再次播放。解决如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AssetImage image;

@override
void initState() {
super.initState();
image = AssetImage(widget.asset);
print('${widget.asset} initState');
}

@override
void dispose() {
print('${widget.asset} dispose');
image.evict();
super.dispose();
}

以上参考来源于:Load gif animation multiple times, not play from the first frame

3、【Flutter】控制GIF播放

【Flutter】控制GIF播放

1
2
3
4
5
Visibility(
visible: isPlay,
child: Image.asset("images/gif_player_demo.gif"),
replacement: Image.asset("images/gif_player_demo.png"),
);

Shell高级技巧

1
2
3
4
5
# [shell替换和去掉换行符](http://www.noobyard.com/article/p-ahlemikj-nz.html)
featureBranceNamesString=$(echo ${featureBranceNamesString} |sed 's/ /\n/g'|awk '{{printf"%s",$0}}')

# [Shell 命令变量去除空格方法](https://blog.csdn.net/jjc120074203/article/details/126663391)
FileContent=${FileContent// /}

其他问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# [Mac环境下shell脚本中的map](https://www.jianshu.com/p/a55480b793b0)
# declare -A myMap=(["my00"]="00" ["my01"]="01")
# myMap["my02"]="02"
# myMap["my03"]="03"
# declare -A targetBranchConfig_realMap=()
# targetBranchConfig_realMap["uploadChannelShortcut"]="${packagePgyerChannelShortcutResult_upload}"
# targetBranchConfig_realMap["uploadChannelKey"]="${packagePgyerChannelKeyResult_upload}"

uploadChannelShortcut_key="uploadChannelShortcut"
uploadChannelKey_key="uploadChannelKey"
targetBranchConfig_realMap="{"
targetBranchConfig_realMap+="\"${uploadChannelShortcut_key}\":\"${packagePgyerChannelShortcutResult_upload}\", \"${uploadChannelKey_key}\":\"${packagePgyerChannelKeyResult_upload}\""
if [ -z "${packagePgyerChannelShortcutResult_download}" ] || [ "${packagePgyerChannelShortcutResult_download}" == "null" ]; then
packagePgyerChannelShortcutResult_download=${packagePgyerChannelShortcutResult_upload}
packagePgyerChannelKeyResult_download=${packagePgyerChannelKeyResult_upload}
fi
targetBranchConfig_realMap+=","
targetBranchConfig_realMap+="\"downloadChannelShortcut\":\"${packagePgyerChannelShortcutResult_download}\", \"downloadChannelKey\":\"${packagePgyerChannelKeyResult_download}\""
targetBranchConfig_realMap+="}"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#获取键值对的个数,或者数组长度:
cat json.txt | jq '.[0].employees|length'

# 取键foo的值:
echo '{"foo": 42, "bar": "less interesting data"}' | jq '.foo'
42
# 获取json所有键:
echo '{"foo": 42, "bar": "less interesting data"}' | jq 'keys'
[
"bar",
"foo"
]
# has判断是否存在某个key
echo '{"foo": 42, "bar": "less interesting data"}' | jq 'has("foo")'
true
# 获取键值对的个数,或者数组长度
echo '{"foo": 42, "bar": "less interesting data"}' | jq 'length'

一、主要用法

删除所有的空格

1
2
3
4
buildContainBranchsString=(`echo ${buildContainBranchsString} | sed s/[[:space:]]//g`) # 删除所有的空格,修复所填分支信息有空格

#echo "buildContainBranchsString=${buildContainBranchsString}"
buildContainBranchArray=(`echo ${buildContainBranchsString} | tr '#' ' '`) # 字符串拆分成数组

字符串转换数组

1
2
3
4
nocodeBranceNamesString=$(cat ${FILE_PATH} | ${JQ_EXEC} '.nocode_brances' | ${JQ_EXEC} '.[].name')
echo ${nocodeBranceNamesString}

nocodeBranceNamesArray=(${nocodeBranceNamesString//,/}) # 字符串转数组

shell 使用jq解析json字符串数组

如何在shell中使用jq cmd变量

1
2
3
#    branchMapArray=$(echo ${branchRootMap} | ${JQ_EXEC} -r '.online_brances') # -r 去除字符串引号
# [如何在shell中使用jq cmd变量](https://www.5axxw.com/questions/content/5ucfc4)
branchMapArray=$(echo ${branchRootMap} | jq -r --arg branchsKey "$branchsKey" '.[$branchsKey]')
1
brew install shc
1
2
3
shc -r -f hello_script.sh

shc -r -f hello_script.sh -o hello_script
1
2
3
4
5
6
7
8
9
10
11
12
shell 单括号运算符号:
a=$(date);
等同于:
a=date;



双括号运算符:
a=$((1+2));
echo $a;
等同于:
a=expr 1 + 2

删除

1
2
3
4
5
6
7
8
#删除所有的空格
sed s/[[:space:]]//g

#删除行末空格
sed 's/[ \t]*$//g'

#删除行首空格
sed 's/^[ \t]*//g'

shell中使用jq命令修改json文件(合并,修改等)

  1. 目标

文件package.json

1
2
3
4
5
6
7
8
9
10
{
"menus": {
"commandPalette": [
{
"command": "go.test.refresh",
"when": "false"
}
]
}
}

文件add.json

1
2
3
4
5
6
7
8
9
10
11
12
{
"add_menu": [
{
"command": "go.explorer.refresh",
"when": "false"
},
{
"command": "go.explorer.open",
"when": "false"
}
]
}

希望把add.json文件里的add_menu合并到package.json的 menus.commandPalette中, 得到一下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"menus": {
"commandPalette": [
{
"command": "go.test.refresh",
"when": "false"
},
{
"command": "go.explorer.refresh",
"when": "false"
},
{
"command": "go.explorer.open",
"when": "false"
}
]
}
}

2.上脚本

1
jq -s '.[0].menus.commandPalette = .[0].menus.commandPalette + .[1].add_menu | .[0]' ./package.json ./add.json

3.解释

参数-s

表示读取多个json对象, 放入一个数组里在一起出来, 开始的.表示整个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ jq -s '.' ./package.json ./add.json
[
{
"menus": {
"commandPalette": [
...
]
}
},
{
"add_menu": [
...
]
}
]

定位取值

数组使用中括号加索引读取等读取, 如 [0] [1], 原命令里的.[0]和.[1]就分别是表示package.json和info.json里的json对象, 其中的.则是表示合并后数组.

要获取json更深层级的对象时, key之间用点号拼接即可. 如

1
2
$ jq '.add_menu[0].command' ./add.json 
"go.explorer.refresh"

管道|

基本和shell里的管道概念是一致的, 管道左侧命令的输出, 将变成管道右侧命令的输入. 如原命令里, .[0].menus.commandPalette = .[0].menus.commandPalette + .[1].add_menu | .[0] 就有一个管道, 左侧的命令是修改合并后的数组中第一个单元的某些值, 然后输出整个数组(包含两个单元, 有package.json和info.json的信息), 然后管道右侧命令接收到整个数组后, 只筛选出第一个单元进行输出

加法

  • 针对数组, 是拼接效果(如原命令)
  • 针对数字, 则是正常加法
  • 针对{}对象, 合并, 相同key覆盖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 数组加法
$ echo '[1,2] [3,4]'|jq -s '.[0] + .[1]'
[
1,
2,
3,
4
]

# 数字加法
$ echo '1 2'|jq -s '.[0] + .[1]'
3

# {}对象, 相同key后面覆盖前面
$ echo '{"a":1, "b":2} {"b":21, "c":3}'|jq -s '.[0] + .[1]'
{
"a": 1,
"b": 21,
"c": 3
}

4.总结

从某个数据源读取一些json信息, 并以此针对某个json的制定位置进行修改, 基本思路就是使用-s参数, 先把所有数据合并为一个大数组, 然后就行修改, 过后通过管道筛选出需要的数据.

jq是一个非常强悍的处理json的命令行工具, 完全可以把它当做一门语言来学. 我也正在学习当中, 你若有jq相关需求解决不了, 我很乐意帮忙研究解决.

二、超时

MacOS超时指令-gtimeout

在linux下面可以使用timeout命令设置命令的超时时间,macos下同样也有,只不过不是timeout而是gtimeout命令

安装

1
brew install coreutils

使用

1
gtimeout 10 sh ./demo.sh # 指令脚本,10秒后超时退出

也可以使用别名,将gtimeout设置为timeout

1
alias timeout=gtimeout

Shell入门

基础知识

  • shell数组

    MentionedList=(“zhangsan” “lisi”)

一、主要用法

1、获取值

1

2、修改值

2.1、修改文件名

1、传参并修改app_info.json文件内容

如修改以下app_info.json文件内容

1
2
3
4
5
{
"version": 0,
"time": "unknow time",
"brance": "unknow brance",
}

修改命令如下:

1
#sed -i '' 's/unknow brance/dev_1.0.0_fix/g' app_info.json

如果要使用传入的参数,则命令如下:

1
2
3
4
5
# 使用传入的参数方法1:把单引号改为双引号(因为单引号会阻止转义)
sed -i '' "s/unknow brance/$1/g" app_info.json

# 使用传入的参数方法2:变量${}外的字符串全都用单引号''圈住
sed -i '' 's/unknow brance/'$1'/g' app_info.json

update_app_info.sh脚本的核心命令:

1
2
3
4
5
6
7
8
# update_app_info.sh
# app信息中加入打包的分支名称,便于知晓当前包的来源分支
BranceName=$1
echo "BranceName=$BranceName"
sed -i '' "s/unknow brance/$BranceName/g" app_info.json

# 将含信息的文件拷贝到app中,以便在app可以获取这些信息
cp app_info.json ../wish/asset/data

update_app_info.sh脚本的完整命令:

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
FullBranceName=$1
ShortBranceName=${FullBranceName##*/}
echo "ShortBranceName=$ShortBranceName"
sed -i '' 's/unknow brance/'${ShortBranceName}'/g' app_info.json

#cp app_info.json ../flutter_updateversion_kit/assets/data
app_info_dir_home="../flutter_updateversion_kit/assets"
if [ ! -d "$app_info_dir_home" ];then
mkdir $app_info_dir_home
#echo "assets文件夹创建成功"
#else
#echo "assets文件夹已经存在"
fi


app_info_dir="$app_info_dir_home/data"
if [ ! -d "$app_info_dir" ];then
mkdir $app_info_dir
#echo "assets/data文件夹创建成功"
#else
#echo "assets/data文件夹已经存在"
fi

fileName="app_info.json"
app_info_file_path=$app_info_dir/$fileNam
cp $fileName $app_info_file_path

jenkins中,直接取定义分支名时候的名称作为变量:

1
2
cd $WORKSPACE/bulidScript
sh update_app_info.sh $BRANCH

2、读取Json及其属性值

假设json文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"version": "1.0.0",
"package_create_time": "package unknow time",
"package_from_brance": "package unknow brance",
"package_default_env": "package unknow env",
"brances_record_time": "03.18",
"feature_brances": [
{
"name": "dev_1.0.0_fix",
"des": "线上版问题修复或优化"
}
]
}

前提:

必须安装jq,安装方法如下:

mac上安装brew后,执行brew install jq安装jq

2.1、如果是json文件

则读取 version、package_create_time、feature_brances.name 的值分别如下:

1
2
3
4
5
6
7
8
9
10
#! /bin/bash
JQ_EXEC=`which jq`
FILE_PATH=app_info.json

packageVersion=$(cat $FILE_PATH | ${JQ_EXEC} .version | sed 's/\"//g')
packageCreateTime=$(cat $FILE_PATH | ${JQ_EXEC} .package_create_time | sed 's/\"//g')
echo "packageVersion=$packageVersion,packageCreateTime=$packageCreateTime"

featureBranceName=$(cat $FILE_PATH | ${JQ_EXEC} .feature_brances.name | sed 's/\"//g')
echo "featureBranceName=$featureBranceName"

2.2、如果是json数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
responseResult=$(\
curl $ROBOTURL \
-H 'Content-Type: application/json' \
-d "
{
\"msgtype\": \"text\",
\"text\": {
\"content\": \"${LastNotificationText}\"
\"mentioned_list\":$MentionedList
}
}"
)

responseResultCode=$(**echo** ${responseResult} | jq '.errcode') # mac上安装brew后,执行brew install jq安装jq

Shell脚本8种字符串截取方法总结

一、shell 中的与、表达式

  • 逻辑与的表达:
1
2
3
4
1)、if [ $xxx=a -a $xx=b ]

注:-a表示and的意思
2)、if [ $xxx=a ] && [ $xx=b ]
  • 逻辑或的表达:
1
2
3
4
1)、if [ $xxx=a -o $xx=b ]

注:-o表示or的意思
2)、if [ $xxx=a ] || [ $xx=b ]

一、Shell的特殊变量

变量 含义
$0 当前脚本的文件名
$# 传递给脚本或函数的参数个数。
$n 传递给脚本或函数的第n个参数
$* 传递给脚本或函数的所有参数。
$@ 传递给脚本或函数的所有参数。被双引号(“ “)包含时,与 $* 稍有不同,下面将会讲到。
$? 上个命令的退出状态,或函数的返回值。
$$ 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。

1、 $* 和 $@ 的区别

$* 和 $@ 都表示传递给函数或脚本的所有参数

情况①:不被双引号(“ “)包含时,都以”$1” “$2” … “$n” 的形式输出所有参数。

情况②:但是当它们被双引号(“ “)包含时,”$*” 会将所有的参数作为一个整体,以”$1 $2 … $n”的形式输出所有参数;”$@” 会将各个参数分开,以”$1” “$2” … “$n” 的形式输出所有参数。

2、shell中的重定向 1>&2

二、echo

1、超级终端的字体背景和颜色显示

格式: echo -e “\033[字背景颜色;字体颜色m字符串\033[0m”

例如:
echo -e “\033[41;36m something here \033[0m”

其中41的位置代表底色, 36的位置是代表字的颜色

那些ascii code 是对颜色调用的始末.
\033[ ; m …… \033[0m

参考:

https://blog.csdn.net/panpan639944806/article/details/23930553

https://blog.csdn.net/u014470361/article/details/81512330

颜色 字色值 背景色值
黑色 30 40
紅色 31 41
綠色 32 42
黃色 33 43
藍色 34 44
紫紅色 35 45
青藍色 36 46
白色 37 47

三、判断语句

1、对字符串的判断

判断参数个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if [[ -z $2 ]]
then
echo 'Error:参数数量过少,请重新输入'
exit_script
fi
if [[ -n $3 ]]
then
echo 'Error:参数数量过多,请重新输入'
exit_script
fi
if [ -z ${xcarchive_file_path} ] \
|| [ -z ${APPENVIRONMENT} ];
then
echo 'Error:上述2个参数都不能为空'
echo '请重新执行:sh base_exportArchive.sh ${xcarchive_file_path} ${APPENVIRONMENT}'
exit_script
fi

1.1、截取字符串

注意赋值等号两边不能有空格

注意赋值等号两边不能有空格

注意赋值等号两边不能有空格

1.1.1、根据路径取目录和文件名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
file_path=d/dir1/dir2/file.png
file_dir=${file_path%/*}
file_name=${file_path##*/}
echo "file_dir = ${file_dir}"
echo "file_name = ${file_name}"
exit


APPPROJECT_NAME=${workspace_file_path##*/}


xcarchive_file_path=./aaa/bbb/ccc/App1Enterprise.xcarchive
xcarchive_file_dir=${xcarchive_file_path%/*} # 文件目录
xcarchive_file_name_withSuffix=${xcarchive_file_path##*/} # 文件主名+文件后缀名
xcarchive_file_name=${xcarchive_file_name_withSuffix%.*} # 文件主名
xcarchive_file_suffix=${xcarchive_file_name_withSuffix##*.}# 文件后缀名
echo "xcarchive_file_dir: ${xcarchive_file_dir}"
echo "xcarchive_file_name: ${xcarchive_file_name}"
echo "xcarchive_file_suffix: ${xcarchive_file_suffix}"
1.1.2、根据1.0.0+100去版本号和编译号
1
2
3
4
5
6
version=1.0.0+100
app_bundle_version=${version%+*} # 取+号左侧,结果1.0.0()
app_build_version=${version#*+} # 取+号右侧,结果100(注意赋值等号两边不能有空格)
echo "app_bundle_version = ${app_bundle_version}"
echo "app_build_version = ${app_build_version}"
exit

1、环境判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
APPENVIRONMENT=$2           # app的打包环境
echo "app的打包环境APPENVIRONMENT: ${APPENVIRONMENT}"


CUR_DIR=$PWD #$PWD代表获取当前路径,当cd后,$PWD也会跟着更新到新的cd路径。这个和在终端操作是一样的道理的
if [ $APPENVIRONMENT == "Product" ]; # 生产环境
then
ExportOptionsPlist_PATH=$CUR_DIR/ExportOptions_inhouse.plist
elif [ $APPENVIRONMENT == "PreProduct" ]; # 预生产环境
then
ExportOptionsPlist_PATH=$CUR_DIR/ExportOptions_adhoc.plist
elif [ $APPENVIRONMENT == "Develop1" -o $APPENVIRONMENT == "Develop2" -o $APPENVIRONMENT == "Develop3" ];
then # 测试环境
ExportOptionsPlist_PATH=$CUR_DIR/ExportOptions_dev.plist
else # 不是允许/支持的环境
echo "-------- Error:所执行的脚本命令中,输入的${APPENVIRONMENT}不是允许/支持的环境,故不再继续执行 --------"
exit
fi

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
# 判断字符串是否以某个字符串结尾
endWithSuffix() {
fullString=$1
suffixString=$2
echo "判断 ${fullString} 是否以 ${suffixString} 结尾"
file_name_withSuffix=${fullString##*/} # 文件主名+文件后缀名
#file_name=${file_name_withSuffix%.*} # 文件主名
file_suffix=${file_name_withSuffix##*.} # 文件后缀名
#echo "file_name: ${file_name}"
echo "file_suffix: ${file_suffix}"
echo "suffixString: ${suffixString}"
if [ ${file_suffix} == ${suffixString} ]; then
echo 'suffix correct'
return 1
else
echo 'suffix error'
return 0
fi
}
# 使用示例:
# endWithSuffix "./aaa/bbb/ccc/App1Enterprise.xcarchive" 'xcarchive'
endWithSuffix "./aaa/bbb/ccc/App1Enterprise.ipa" 'xcarchive'
if [ $? == 0 ]; then
echo '执行失败:文件后缀错误,请重新输入'
exit
fi
exit

1.1、判断是不是以http开头

if [[$1 =~^http.* ]]; then
commond
else
commond
fi

1、shell中[[]]和[]的主要区别

  • 实际上是bash 中 test 命令的简写。即所有的 [ expr ] 等于 test expr
  • [[ expr ]] 是bash中真正的条件判断语句

2、对文件名filename的判断

1、获取当前文件路径

1
2
curPath=$(cd "$(dirname "$0")"; pwd) # 获取当前文件路径
echo "curPath=${curPath}"
表达式 含义
-e filename 如果 filename存在,则为真
-d filename 如果 filename为目录,则为真
-f filename 如果 filename为常规文件,则为真
-L filename 如果 filename为符号链接,则为真
-h filename 如果文件是软链接,则为真

使用举例:

1
2
3
4
5
6
7
8
9
10
11
AssertExists() {
if [[ ! -e "$1" ]]; then
if [[ -h "$1" ]]; then
EchoError "The path $1 is a symlink to a path that does not exist"
else
EchoError "The path $1 does not exist"
fi
exit -1
fi
return 0
}

其他表达式:

表达式 含义
-r filename 如果 filename可读,则为真
-w filename 如果 filename可写,则为真
-x filename 如果 filename可执行,则为真
-s filename 如果文件长度不为0,则为真
filename1 -nt filename2 如果 filename1比 filename2新,则为真。
filename1 -ot filename2 如果 filename1比 filename2旧,则为真。

3、对字符串str的判断

表达式 含义
if [ str1 = str2 ] 当两个串有相同内容、长度时为真
if [ str1 != str2 ] 当串str1和str2不等时为真
if [ -n str1 ] 当串的长度大于0时为真(串非空)
if [ -z str1 ] 当串的长度为0时为真(空串)
if [ str1 ] 当串str1为非空时为真

shell 中利用 -n 来判定字符串非空。

使用示例:

1
2
3
4
5
6
7
8
9
RunCommand() {
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
echo "♦ $*"
fi
"$@"
return $?
}

# 如果$VERBOSE_SCRIPT_LOGGING值不为空,则通过"$*"打印传递给脚本或函数的所有参数(将所有的参数作为一个整体输出),如果为空,则通过"$@"传递给脚本或函数的所有参数(将所有的参数依次分开输出),并通过$?返回上个命令的退出状态,或函数的返回值。

注意

  • 使用[]和[[]]的时候不要吝啬空格,每一项两边都要有空格,[[ 1 == 2 ]]的结果为“假”,但[[ 1==2 ]]的结果为“真”!

判断

1、数组里是否包含某个元素

1
2
3
4
5
6
7
8
noBranchNames=("HEAD" "->")
if [[ "${noBranchNames[*]}" =~ ${devBranchName} ]]; then
#echo "devBranceMapString=${devBranceMapString}"
packageMergerBranchString+="${devBranceName}:${devBranceDes}#"
else
echo ${PackageErrorCode}:${PackageErrorMessage}
exit_script
fi

文件包含/公用代码

  • 权威知识请查看:

    Shell 文件包含

    testReturn1.sh

    1
    OUTPUT_ARCHIVE_PATH="/User/xxx/output/abc"

    d

    1
    2
    3
    4
    5
    #!/bin/bash
    . ./testReturn1.sh
    # source ./testReturn1.sh # 此种方式也可以
    # sh ./testReturn1.sh # 此种方式无法拿到另一个shell中的变量
    echo "OUTPUT_ARCHIVE_PATH=${OUTPUT_ARCHIVE_PATH}"

文件操作

1、mkdir -p中的 p有什么用

-p, –parents 需要时创建上层目录,如目录早已存在则不当作错误

命令格式:mkdir [-p] DirName

说明:建立一个子目录。

参数:-p 确保目录名称存在,如果目录不存在的就新创建一个。

1

获取当前文件所在的目录

1
2
3
4
5
# 注意:${0} 指的是执行这个方法的那个文件的所在目录。
# 所以当外部不是执行脚本,而只是引用脚本,即使用如 source ./xx.sh 的时候,所以无法获取我们真正想要的当前文件的所在目录
curdir=$(cd $(dirname $0); pwd)
echo "脚本$0当前文件所在的目录:${curdir}"
exit

详解Future

Flutter学习之三方库,网络库,轮播图库,下拉刷新库,跳转传值库,BaseWidght封装

Flutter的网络请求库:Dio

RefreshIndicator是Material风格的下拉刷新组件。

CupertinoSliverRefreshControl 是ios风格的下拉刷新控件。

参考文章:

1
2
3
4
5
import 'package:dio/dio.dart';

Future<String> getData() async {
return "123";
}

Flutter中的网络请求
网络请求是非常典型的异步任务,下面我们就来结合网络请求来看看Flutter中的异步是如何使用的。

网络请求的方式有很多,这里我就直接用目前比较好用的DIO网络请求库 了,你也可以使用官方文档中的网络请求 ,都是可以的。

下面我们来简单用一用网络请求。

这里我使用的聚合上的一个接口

接口地址:http://v.juhe.cn/toutiao/index?type=keji&key=4c52313fc9247e5b4176aed5ddd56ad7

关于DIO如何使用这里就不讲了,Github上文档很详细,使用起来也很简单。
下面我们直接用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'package:dio/dio.dart'; // 导包

/// 请求接口获取数据的方法
Future<Response> getData() async {
String url = "http://v.juhe.cn/toutiao/index";
String key = "4c52313fc9247e5b4176aed5ddd56ad7";
String type = "keji";

print("开始请求数据");
Response response = await Dio().get(url, queryParameters: {"type": type, "key": key});
print("请求完成");

return response;
}

注意一下几点:

网络请求是耗时操作
要使用async来标明getData这个函数是一个异步函数
await 用于等待请求返回的结果,此时会阻塞掉后面的代码,只有当请求结束后面的代码才会执行
async标注的函数其返回值类型是Future
然后我们就可以在main函数中来接收网络请求后的结果了:

1
2
3
4
5
6
7
8
9
10
11
main() {
getData().then((result) {
print("接口返回的数据是:${result}");
}).whenComplete((){
print("异步任务处理完成");
}).catchError((){
print("出现异常了");
});

print("我是在请求数据后面的代码呦!");
}

我们来看看执行的结果:

这样一来,我们就完成了Flutter中的异步操作了,可以看到,相对于原生Android来讲,Flutter中的异步是非常简单的。

Flutter 请求网络数据时显示加载中
一个很常见的需求,在首次进入页面时,此时数据还需要从网络上获取,我们希望在网络请求完成之前显示一个加载页面,请求完成之后再显示数据。此时,我们就可以使用FutureBuilder来完成了

首先是接口请求函数,为了更明显的能看到加载控件的显示,这里的异步请求函数中我延时3秒后再请求数据,代码如下

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
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_async/widget/loading.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '新闻列表',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: '新闻列表'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: _getNews(),
builder: (BuildContext context, AsyncSnapshot<Response> snapshot) {
/*表示数据成功返回*/
if (snapshot.hasData) {
Response response = snapshot.data;
return Text("${response.data.toString()}");
} else {
return LoadingWidget();
}
},
));
}
}


/**

* 请求接口获取数据
*/
Future<Response> _getNews() async {
await Future.delayed(Duration(seconds: 3), () {
print("延时三秒后请求数据");
});

String url = "http://v.juhe.cn/toutiao/index";
String key = "4c52313fc9247e5b4176aed5ddd56ad7";
String type = "keji";

print("开始请求数据");
Response response =
await Dio().get(url, queryParameters: {"type": type, "key": key});

print("请求完成");

return response;
}

运行效果:
在这里插入图片描述

下面是demo,需要的可以下载:
flutter_async Demo