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

[TOC]

前言

本文一个比 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

第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"),
);

第2章:Shell高级加密可执行

[TOC]

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语言,然后在打包成二进制文件。

第2章:Shell高级技巧

[TOC]

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

第1章:Shell入门

[TOC]

Shell入门

基础知识

  • shell数组

    MentionedList=(“zhangsan” “lisi”)

一、主要用法

1、获取值

1
2


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
    2
    > OUTPUT_ARCHIVE_PATH="/User/xxx/output/abc"
    >

    d

    1
    2
    3
    4
    5
    6
    > #!/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
2


获取当前文件所在的目录

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

第4节:详解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

第3节:详解Platform Channel

参考文章:

一、基础知识

1、Channel

Flutter定义了三种不同类型的Channel,分别如下:

Channel类型 用途
BasicMessageChannel 用于传递字符串和半结构化的信息
MethodChannel 用于传递方法调用(method invocation)
EventChannel 用于数据流(event streams)的通信
  • MethodChannel传递的数据支持什么类型?
  • Dart数据类型与Android,iOS类型的对应关系是怎样的?

这两个问题的答案同样来自官方文档:

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int if 32 bits not enough java.lang.Long NSNumber numberWithLong:
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary
Class Note
BinaryCodec 二进制数据类型,泛型对应 ByteBuffer,传递 byte 数组时使用
JSONMessageCodec Json消息类型,泛型对应 Object。传递 Json时可使用
StringCodec 字符串消息类型,泛型对应 String。传递字符串
StandardMessageCodec 标准消息类型,泛型对应 Object。传递 Map 等的时候可使用此类型

第1节:详解布局

参考文章:

一、基础知识

1.spaceEvently

一行中如果有2个widget(五个五角星暂且看成1个widget),那么除去2widget所占据的宽度,3个红色方块宽度是一样的, 如下图所示

img

2.spaceBetween

一行中如果有2个widget,那么分布2端,如下图所示

img

3.spaceAround

一行中如果有2个widget,那么每个widget左右都是相同的方块,如下图所示

img

类型 含义 row举例 column举例
start
(default)
从行首开始排列。每行第一个弹性元素与行首对齐,同时所有后续的弹性元素与前一个对齐 在这里插入图片描述 在这里插入图片描述
start
textDirection: TextDirection.rtl,
textDirection的值为从右至左 row_start-rtl 在这里插入图片描述
center 伸缩元素向每行中点排列。每行第一个元素到行首的距离将与每行最后一个元素到行尾的距离相同。 在这里插入图片描述 在这里插入图片描述
end 从行尾开始排列。每行最后一个弹性元素与行尾对齐,其他元素将与后一个对齐。 在这里插入图片描述 在这里插入图片描述
space-between 在每行上均匀分配弹性元素。相邻元素间距离相同。每行第一个元素与行首对齐,每行最后一个元素与行尾对齐。 在这里插入图片描述 在这里插入图片描述
space-around 在每行上均匀分配弹性元素。相邻元素间距离相同。每行第一个元素到行首的距离和每行最后一个元素到行尾的距离将会是相邻元素之间距离的一半。 在这里插入图片描述 在这里插入图片描述
spaceEvenly 将主轴空白区域均分,使各个子控件间距相等 在这里插入图片描述 在这里插入图片描述

expand 要再column或row中使用

层叠

4.6 层叠布局 Stack、Positioned

eg:在视图上,显示loading动画

问:flutter text 左对齐_如何在Flutter中将文本垂直和水平居中?

答:首先可以使用Center让控件居中,其次使用textAlign设置水平居中,示例代码如下:

1
2
3
4
5
6
child: Center(
child: Text(
"Hello World",
textAlign: TextAlign.center,
),
),

flutter 隐藏显示控件

1
2
3
4
Offstage(
offstage: true, //true隐藏,false显示
child: '子控件',
),