宝塔面板FTP空间服务无法连接登录解决方法大全 - 知乎

mikel阅读(566)

来源: 宝塔面板FTP空间服务无法连接登录解决方法大全 – 知乎

前提准备:

正常情况下,我们都能顺利连接到宝塔面板创建的FTP空间,但是我这次就遇到了几个问题,所以这次记录下来,以后再次遇到同样的问题,就不要耗费时间去查资料了。日积月累,我的建站知识越来越丰富。

宝塔面板FTP启动失败问题

我的宝塔面板FTP插件启动标识一直是红色标识(停止状态),卸载插件重装也是一样的。所以去网上查找解决方法。

我查看了宝塔面板安装日志:日志太长,可以查看 宝塔面板安装ftp插件报错提示

日志关键报错信息:pureftpd.sh: line 75: StartUp: command not found

解决方法:
在FinalShell客户端执行以下命令

[root@iZuf6bm7y86rsciyk4lvriZ ~]# pkill -9 pure-ftpd
[root@iZuf6bm7y86rsciyk4lvriZ ~]# service pure-ftpd start
Starting Pure-FTPd... /etc/init.d/pure-ftpd: /www/server/pure-ftpd/sbin/pure-config.pl: /usr/bin/perl: bad interpreter: No such file or directory
 failed
[root@iZuf6bm7y86rsciyk4lvriZ ~]# yum install -y perl perl-devel
已加载插件:fastestmirror, product-id, search-disabled-repos, subscription-manager

# 执行 yum install -y perl perl-devel 之后等待一会
# perl安装成功之后...

[root@iZuf6bm7y86rsciyk4lvriZ ~]# service pure-ftpd start
Starting Pure-FTPd... Running: /www/server/pure-ftpd/sbin/pure-ftpd --daemonize -A -c50 -B -C10 -D -E -fftp -H -I15 -lpuredb:/www/server/pure-ftpd/etc/pureftpd.pdb -lunix -L20000:8 -m4 -p39000:40000 -s -U133:022 -u100 -g/var/run/pure-ftpd.pid -k99 -Z -Y1
 done
[root@iZuf6bm7y86rsciyk4lvriZ ~]# netstat -tap | grep ftp
tcp        0      0 0.0.0.0:ftp             0.0.0.0:*               LISTEN      552/pure-ftpd (SERV 
tcp6       0      0 [::]:ftp                [::]:*                  LISTEN      552/pure-ftpd (SERV

其中在ssh终端可以看出错误提示:

/usr/bin/perl: bad interpreter: No such file or directory  failed

然后查找资料:发现只需要安装perl
yum install -y perl perl-devel

安装完成之后,就去启动ftp服务。

service pure-ftpd start

用netstat查看ftp网络连接状况。

netstat -tap | grep ftp

这样的再去宝塔面板后台查看ftp服务是否能启动,结果发现能启动起来。这样就解决了这个问题。【宝塔面板FTp空间服务启动成功】

但是虽然ftp服务启动起来了,但是FileZilla客户无法连接ftp服务。没事下文一一来解答我遇到的问题。请认真仔细看下去。

本问题参考资料:

错误提示:无法连接到服务器

错误: 20 秒后无活动,连接超时
错误: 无法连接到服务器

遇到这个问题的时候,折腾了我很久。后面解决之后,发现问题很简单,只是有些东西误导了我的判断。

我首先怀疑的是宝塔面板的问题。然后去网上搜索答案。

找到了宝塔面板的官方论坛的一个回答:FTP连接不上的解决方法

这个回答告诉我以下解决方案:

1.注意内网IP和外网IP
2.检查ftp服务是否启动 (面板首页即可看到)
3.检查防火墙20端口 ftp 21端口及被动端口39000 – 40000是否放行 (如是腾讯云/阿里云等还需检查安全组)
4.是否主动/被动模式都不能连接
5.新建一个用户看是否能连接
6.修改ftp配置文件 将ForcePassiveIP前面的#去掉 将192.168.0.1替换成你服务器的外网IP

9.关闭ftp客户端的TLS尝试链接

其中第7,8点没有去尝试,就没有放入本文。

现在我们来谈谈我试过的方法。
首先我用的ftp客户端是:FileZilla客户端。
第一点:我填入FileZilla客户的站点信息为外网ip地址。所以这点没有解决我的问题。
第二点:去宝塔面板观察ftp服务是否开启,很明显,我开启了ftp服务。结果还是没有解决我的问题。

第三点:检查防火墙20端口 ftp 21端口及被动端口39000 – 40000是否放行 (如是腾讯云/阿里云等还需检查安全组)

看见没,三个关键端口,我都开放了哦。然后我就继续去试其它几点方案。但是有些事情没有想到啊,最坑的地方在这里。下文会讲这个。
第4,5,6,9点方法我都试过了,还是不能解决我的问题。还是报错:错误: 无法连接到服务器。

此时此刻,我还没有放弃去寻找方法。因为我不止有这一台阿里云服务器,我有十几台阿里云云服务器(都是在阿里云官网购买的,一次性买了三年),我现在就在想啊,我这台ftp空间连不上的云服务器,可能是前几天我服务器因为中了木马病毒导致出现了问题,因为木马病毒修改了Linux部分系统命令的权限以及chattr特殊属性。所以脑海里怀疑是这个原因。我就去测试我其它几台阿里云服务器,结果无一例外,它们创建的ftp空间全部可以连接成功,并可以访问。

我现在有点小郁闷,因为我自认为我这台出问题的服务器的ftp配置和其它几台云服务器是一模一样的,为什么这台云服务器不能成功连接?

好了,我现在还没有放弃,我现在去尝试看下这几台云服务器的防火墙情况。

先看看正常能成功连接ftp空间的这台云服务器(11号)的防火墙情况:

firewall-cmd –list-ports

阿里云服务器(10)不能连接ftp空间的这台服务器的防火墙情况

怎么回事?怎么回事?怎么回事?我都吃惊了好几遍,为啥宝塔面板放行了端口,而防火墙没有放行端口呢?难道是说宝塔面板的放行端口是”花拳绣腿”没啥用的。先不想这么多了。
赶紧在FinalShell客户端命令行里面写命令去放行防火墙的端口。

有时候你不清楚宝塔面板的端口是否放行了,你可以用网络端口扫描工具来检测,这个更能直观的清楚哪个端口究竟有没有开放。

端口扫描工具:coolaf.com/tool/port

需要放行的FTP端口:21 20 39000-40000

# 防火墙放行FTP需要用到的端口
firewall-cmd --zone=public --add-port=21/tcp --permanent
firewall-cmd --zone=public --add-port=20/tcp --permanent
firewall-cmd --zone=public --add-port=39000-40000/tcp --permanent

# 重新载入防火墙规则---重新加载后才能生效
firewall-cmd --reload

# 查看防火墙放行端口列表
firewall-cmd --list-ports

上面三处端口防火墙开放了之后,就可以用端口扫描工具扫描下。我的已经扫描过了,端口全部放行了。然后我立马去尝试连接这台云服务器的ftp空间。结果成功了!!!

下面的读取目录列表失败是由于20端口没有放开导致的,所以这一步,我们一次性把20,21,39000-40000端口都放开。要不然会连接出错的。

FTP空间连接成功示意图:

错误提示:读取目录列表失败

状态: 明文 FTP 不安全。请切换到 FTP over TLS。
状态: 已登录
状态: 读取目录列表…
状态: 服务器发回了不可路由的地址。使用服务器地址代替。
命令: MLSD
错误: 20 秒后无活动,连接超时
错误: 读取目录列表失败

这个问题是防火墙没有放开20端口导致的。你去命令行放行下20端口就好了。

题外话:

防火墙命令如果没有找到,可以根据你的Linux系统版本来选择命令安装。
我的是CentOS系统,所以选择命令:yum install firewalld

命令查询网站command-not-found.com/f

Debian
apt-get install firewalld
 Ubuntu
apt-get install firewalld
 Arch Linux
pacman -S firewalld
 Kali Linux
apt-get install firewalld
 CentOS
yum install firewalld
 Fedora
dnf install firewalld
 Raspbian
apt-get install firewalld

参考资料

Flutter之TextFormField组件 - 简书

mikel阅读(531)

来源: Flutter之TextFormField组件 – 简书

/**
* 大部分属性同TextFiled
*
* TextFormField({
Key key,
this.controller,//控制正在编辑的文本。如果为空,这个小部件将创建自己的TextEditingController并使用initialValue初始化它的TextEditingController.text
String initialValue,//初始值
FocusNode focusNode,
InputDecoration decoration = const InputDecoration(),//输入器装饰
TextInputType keyboardType,//弹出键盘的类型
TextCapitalization textCapitalization = TextCapitalization.none,//户输入中的字母大写的选项,TextCapitalization.sentences每个句子的首字母大写,TextCapitalization.characters:句子中的所有字符都大写,TextCapitalization.words : 将每个单词的首字母大写。
TextInputAction textInputAction,//更改TextField的textInputAction可以更改键盘右下角的操作按钮,搜索,完成
TextStyle style,
TextDirection textDirection,//文字显示方向
TextAlign textAlign = TextAlign.start,//文字显示位置
bool autofocus = false,//自动获取焦点
bool obscureText = false,//是否隐藏输入,true密码样式显示,false明文显示
bool autocorrect = true,
bool autovalidate = false,//是否自动验证值
bool maxLengthEnforced = true,
int maxLines = 1,//编辑框最多显示行数
int maxLength,//输入最大长度,并且默认情况下会将计数器添加到TextField
VoidCallback onEditingComplete,//当用户提交时调用
ValueChanged onFieldSubmitted,
FormFieldSetter onSaved,//当Form表单调用保存方法save时回调
FormFieldValidator validator,//Form表单验证
List inputFormatters,
bool enabled = true,
Brightness keyboardAppearance,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
bool enableInteractiveSelection = true,
})
*/

body: Container(
padding: EdgeInsets.all(12.0),
color: Color(0xfff1f1f1),
child: Form(
key: _globalKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(
labelText: "请输入用户名",
labelStyle: TextStyle(
color: Colors.green,
fontSize: 20.0
)
),
onSaved: (value) { //formState.save()方法执行完之后执行onSaved方法
print("!!!!!!!!!!!!!!!! onSaved");
name = value;
},
onFieldSubmitted: (value) {
print("!!!!!!!!!!!!!!!! onFieldSubmitted:$value");
},
onEditingComplete: () {
print("!!!!!!!!!!!!!!!! onEditingComplete");
},
),
TextFormField(
decoration: InputDecoration(
labelText: "请输入密码"
),
onSaved: (value) {
passWord = value;
},
obscureText: true,
autovalidate: true,
validator: (value) {
return value.length < 4 ? "密码长度不够4位" : null;
},
),
Container(
margin: EdgeInsets.only(top: 20.0),
width: 200,
height: 40,
child: RaisedButton(
onPressed: dologin,
child: Text("登录"),
),
)
],
),
),
),

/**
* 表单
*
* const Form({
Key key,
@required this.child,
this.autovalidate = false,//是否自动提交表单
this.onWillPop,
this.onChanged,//当FormFiled值改变时的回调函数
})
*/

【Gradle】Gradle报错:Using insecure protocols with repositories,without explicit opt-in,,is unsupported._alone_yue的博客-CSDN博客

mikel阅读(466)

来源: 【Gradle】Gradle报错:Using insecure protocols with repositories,without explicit opt-in,,is unsupported._alone_yue的博客-CSDN博客

1.问题描述
将gradle.build仓库更换为阿里云仓库后报错

 

2.解决方案
第一种:在仓库前添加关键字:
allowInsecureProtocol = true

plugins {
id ‘org.springframework.boot’ version ‘2.5.2’
id ‘io.spring.dependency-management’ version ‘1.0.11.RELEASE’
id ‘java’
}

group = ‘com.example’
version = ‘1.0.0’
sourceCompatibility = ‘1.8’

repositories {
// mavenCentral()
maven{
allowInsecureProtocol = true
url ‘http://maven.aliyun.com/nexus/content/groups/public/’}
}

dependencies {
implementation ‘org.springframework.boot:spring-boot-starter-web’
testImplementation ‘org.springframework.boot:spring-boot-starter-test’

implementation ‘org.apache.logging.log4j:log4j-core:2.14.1’
}

test {
useJUnitPlatform()
}

第二种将阿里云的连接http换成https

在这里插入图片描述

————————————————
版权声明:本文为CSDN博主「alone_yue」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_46514118/article/details/121746827

解决Android Studio模拟器无法联网_菜鸟的日常的博客-CSDN博客_android studio模拟器联网

mikel阅读(567)

来源: 解决Android Studio模拟器无法联网_菜鸟的日常的博客-CSDN博客_android studio模拟器联网

目录

一:问题描述

二:原因

三:解决方法

3.1永久修改DNS

3.2命令启动模拟器

四:参考链接

一:问题描述
Google推出了Android Q以后,兴致勃勃的下载了Android10的镜像,新建模拟器发现启动速度比以前快了简直不是一星半点,开机嗖嗖的。以前因为Android的模拟器实在是太慢了,都拿自己心爱的手机调试,时间长了,手机会变得特别卡,特别损真机,而且有时候因为真机不能做到运行各个版本的Android系统,尤其是Google新发布的android版本,导致一些bug无法用真机复现。现在终于可以用模拟器调试解决问题了。但是发现模拟器运行,应用无法连接网络,duang duang duang~~ 本来以为这个问题应该很容易解决,结果就去百度,搜出来全是下边的玩意儿!!!都是在胡扯,记录下来希望其他小伙伴不再采坑

看别人写的什么通过下边的adb shell命令就可以联网,全在胡扯!!!我用shell命令根本没有 [net.dns1] 这属性,另外shell命令设置dns也是提示failed(应该是需要root权限,但是我没找到怎么获取root权限,不知道其他小伙伴是否遇到跟我一样的问题)

Shit start———————–

adb shell

getprop

会列出系统当前的各项属性,可以看到下面格式的模拟器dns:

[net.dns1]: [10.0.2.3]
[net.dns2]: [10.0.2.4]
通过setprop net.dns1 192.168.1.1

Shit ending———————–

下边介绍我亲测的两种办法真实有效!!!

Android Studio新建模拟器,内置浏览器无法通过域名打开百度,但是通过IP可以打开百度。这导致我们调试代码特别不方便,看下图1-1的提示,猜测大致是因为dns的问题,图1-2可以通过ip访问百度印证了这个猜测

图1-1

图1-2
二:原因
查阅资料发现Android模拟器默认的地址是10.0.2.3,默认的DNS也是10.0.2.3。对于在家或者在公司运行Android模拟器来讲,家里的电脑IP都是192.168.1.112之类的,公司电脑IP类似172.30.14.249这样,我们可以通过dos命令ipconfig/all查看电脑的ip,一般情况下电脑和模拟器不在同一个网段,所以就会出现电脑可以上网但是模拟器不能上网的情况。我们只需要把模拟器的DNS和电脑的DNS改成一致,模拟器就可以正常上网了。下面是图2-1,模拟器的DNS和电脑不一致

图2-1
三:解决方法
3.1永久修改DNS
进入模拟器Setting->Network&Internet->Wi-Fi->AndroidWifi->右上角修改,手动选择IP setting->static,ip地址和网关还是设置成DHCP动态分配的ip和网关地址(大家可以在Network details查看动态分配的ip,然后记住默认分配的动态ip和网关,如下图3-1),我们只需要修改模拟器的dns,改为和电脑的dns一致就可以了,我这边dns和pc保持一致,改为了223.5.5.5、223.6.6.6。大家按照自己pc的dns设置保存,然后就可以愉快的联网了,如图3-2,你懂的!

图3-1

图3-2

3.2命令启动模拟器
1.找到你的模拟器名字,默认在C:\users\xx\.android\avd目录下,如图3-3。如果找不到可以在Android Studio->AVD Manager->show on disk定位到此目录如图3-4

​​​​图3-3​​​

图3-4​
2.找到SDK的emulator目录,目录如图3-5

图3-5
3.在dos命令窗口切换到SDK的emulator目录下运行命令,模拟器名字要对应上自己的模拟器名字,我的模拟器名字是Pixel_API_29

emulator -avd Pixel_API_29 -dns-server 8.8.8.8,114.114.114.114
回车之后,系统会帮你启动你电脑中名叫Pixel_API_29的模拟器,此时模拟器便可以上网了。这种方式最简单粗暴,但也最麻烦,因为这只是一次性的,如果下次不输入这行命令,模拟器还是不能上网,所以每次启动前都要输入这行命令。

四:参考链接
解决AndroidStudio 模拟器无网络连接
————————————————
版权声明:本文为CSDN博主「菜鸟的日常」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/AngryPotatoLucky/article/details/106198818

如何修复ERR_NAME_NOT_RESOLVED错误 - 闪电博

mikel阅读(1636)

来源: 如何修复ERR_NAME_NOT_RESOLVED错误 – 闪电博

DNS(域名系统)用于查找特定网站域名的IP地址。它使网页浏览成为可能,尤其是互联网上有超过十亿个网站

但是,有时系统可能找不到IP地址,导致出现ERR_NAME_NOT_RESOLVED DNS错误。本文将解释它是什么,为什么会出现这个错误,以及六种有效的解决方法。

是什么导致ERR_NAME_NOT_RESOLVED?

ERR_NAME_NOT_RESOLVED示例

当您的计算机或浏览器找不到托管您要访问的域名的IP地址时,会发生ERR_NAME_NOT_RESOLVED错误。

要详细了解ERR_NAME_NOT_RESOLVED错误是如何发生的,您必须了解DNS的工作原理。当您键入网站地址(例如wbolt.com)时,您的计算机会发送DNS请求以获取托管该网站的Web服务器的IP地址。

如果浏览器或Internet服务提供商已将IP地址存储在其缓存文件中,则查找过程会很快。如果在DNS条目中找不到匹配的地址,则会通过DNS域名解析过程获取IP地址。

有时,网站可能已移动到另一个IP地址,但系统仍在存储旧的缓存地址。浏览器会从缓存中获取过期地址,所以无法解析域名,会触发错误提示。

也就是说,其他可能的原因导致DNS无法解析域名,包括网络配置、浏览器或Internet设置以及DNSSEC应用程序。

在Windows、Mac和Linux上修复ERR_NAME_NOT_RESOLVED的6种方法

由于有多种可能的原因,我们将讨论修复ERR_NAME_NOT_RESOLVED错误的六种方法。它们包括简单的修复,例如清除浏览器缓存和数据,以及更多技术修复,例如修改主机文件和禁用DNSSEC。

1. 刷新设备上的DNS缓存

ERR_NAME_NOT_RESOLVED错误的最常见原因之一是缓存过时,因此最好从刷新操作系统上的 DNS 缓存开始。

DNS刷新方法因操作系统而异——我们为Microsoft Windows、macOS和Linux提供了完整的DNS刷新教程。本质上,它就像使用Windows命令提示符窗口或Linux和macOS上的终端执行命令一样简单。

我们建议定期清除和更新DNS缓存,以防止DNS欺骗和行为跟踪等安全漏洞。

2.检查谷歌Chrome浏览器设置

Google Chrome浏览器还存储DNS数据以提高重新访问时的页面加载速度。但是,如果网站已移动到不同的IP地址,DNS缓存中的过期条目可能会触发ERR_NAME_NOT_RESOLVED错误消息。

按照以下步骤清除Google Chrome上的主机缓存:

1. 打开Google Chrome并在浏览器的地址栏中输入chrome://net-internals/#dns并按Enter

2. 单击清除主机缓存

清除主机缓存

我们还建议定期从Google Chrome中清除浏览器cookie。这是因为浏览器cookie可以阻止您的Internet DNS,从而触发ERR_NAME_NOT_RESOLVED错误消息。

另一个值得检查的Google Chrome设置是页面预加载服务。此功能预取与您已访问过的网站相关的网页资源,包括DNS预加载。

主要目标是改善用户体验,因为预取页面将加载得更快。但是,此功能也已知会导致ERR_NAME_NOT_RESOLVED错误。

因此,最好禁用预测服务以防止错误发生。此外,预取页面会使用您的互联网带宽并占用本地资源。

请按照以下步骤执行此操作:

1. 单击Google Chrome窗口右上角的三个点,然后选择设置

Chrome浏览器设置

2. 单击隐私和安全

浏览器隐私和安全

3. 选择Cookies和其他网站数据

清除Cookies和其他网站数据

4. 向下滚动并找到预加载页面,以便更快地浏览和搜索。关闭切换开关。

预加载页面开关

3. 重置您的防火墙和DNS服务器地址

防火墙或安全软件可能会阻止Internet连接并导致ERR_NAME_NOT_RESOLVED DNS错误,尤其是在您不小心配置错误的情况下。快速解决方法是重置防火墙设置。

请按照以下步骤在Windows计算机上执行此操作:

1. 右键单击Windows图标,然后选择搜索

2. 键入控制面板并单击结果将其打开。

Windows控制面板

3. 选择系统和安全

Windows系统和安全

4. 单击Windows Defender防火墙

Defender防火墙

5. 选择恢复默认值

Defender防火墙恢复默认值

6. 单击恢复默认值按钮

重置防火墙默认值

ERR_NAME_NOT_RESOLVED错误的另一个可能原因是DNS服务器工作不正常。因此,当您尝试访问该网站时,无法解析DNS 查询。

在这种情况下,使用来自Google和Cloudflare公共服务器的以下DNS服务器地址之一应该可以解决问题。

DNS服务器 IPv4 IPv6
谷歌 8.8.8.8 2001:4860:4860::8888
谷歌 8.8.4.4 2001:4860:4860::8844
Cloudflare 1.1.1.1 2606:4700:4700::1111
Cloudflare 1.0.0.1 2606:4700:4700::1001

为此,您需要在计算机上配置DNS服务器。以下是在Windows计算机上更改DNS地址的步骤:

1. 打开控制面板

2. 转到网络和Internet ->网络和共享中心

3. 单击更改适配器设置

更改适配器设置

4. 右键单击用于连接到Internet的网络适配器,然后选择Properties

WIFI属性

5. 双击Internet协议版本 4 (TCP/IPv4)Internet协议版本 6 (TCP/IPv6)

6. 输入首选DNS服务器备用DNS服务器的公共DNS服务器地址。相应地使用IPv4和IPv6。

如果您使用的是macOS,请执行以下步骤:

1. 打开系统偏好设置并选择网络

2. 单击高级并打开DNS选项卡。

DNS服务器

3. 单击+按钮添加新的DNS服务器。

4. 键入其中一个公共DNS服务器。

5. 单击确定。将出现一条提示消息 – 单击应用

如果您使用的是Linux操作系统,则必须使用终端更改DNS地址。

1. 按CTRL+T打开终端。

2. DNS服务器配置存储在/etc/resolv.conf文件中。使用以下命令使用Nano文本编辑器打开它:

sudo nano /etc/resolv.conf

3. 键入以下命令来定义DNS服务器。将IP地址替换为您要使用的服务器:

nameserver 8.8.8.8
nameserver 8.8.4.4

4. 按CTRL+X然后按Y保存并关闭文件。

5. 键入以下命令以使文件不可更改:

sudo chattr +i /etc/resolv.conf

6. 重新启动计算机。

4.尝试修改您的主机文件

您的计算机中有一个hosts文件,其中包含域名及其IP地址。当您访问一个网站时,计算机会首先查看hosts文件以找到匹配的域名和IP地址。如果没有找到任何匹配项,系统只会发送DNS请求。

将网站的IP地址添加到hosts文件中可以解决由DNS引起的浏览问题。它对于找出错误源也很有用。如果您在将域名及其IP地址添加到主机文件后可以访问该网站,则问题出在您这边。

总之,在Windows系统文件夹中找到hosts文件并使用记事本打开它。对于Linux和macOS,使用终端和文本编辑器打开和编辑文件。

hosts文件

5. 使用不同的Internet连接进行测试

您的Internet连接和WiFi路由器问题可能是发生ERR_NAME_NOT_RESOLVED错误的另一个原因。

您可以进行两项测试来确认这一点。第一个是使用不同的稳定互联网连接重新访问该网站。

例如,从您的手机创建一个移动热点并使用它将您的计算机连接到网络。重新访问该网站,看看它是否正常加载。如果是这种情况,则以前的Internet连接有可能是ERR_NAME_NOT_RESOLVED错误的根本原因。

另一种选择是将各种设备添加到同一个互联网连接并使用它们打开同一个网站。如果所有设备都显示相同的错误,那么互联网连接一定是主要问题。

关闭路由器并断开电源。等待至少30秒,然后重新连接电源并再次打开路由器。

请记住,路由器需要一段时间才能完全正常工作。等到Internet指示灯变为绿色,然后再将计算机连接到网络并再次测试。

6. 为您的域禁用DNSSEC

如果您已尝试上述所有方法并且错误仍然存​​在,请检查WHOIS上的域以查看是否启用了DNSSEC。

它是使用加密签名的附加保护层。虽然它可以保护未经授权的用户访问DNS记录,但它还可以防止域正确传播,从而触发错误。

DNSSEC信息

如果是这种情况并且它不是您的域或网站,那么除了通知网站所有者有关ERR_NAME_NOT_RESOLVED DNS错误之外,您无能为力。但是,如果它是您的域或网站,请从您的域帐户中禁用DNSSEC。

如果您使用DNSPod,请通过访问您的域配置面板禁用DNSSEC(不同域名托管商,可能稍有不同)。

1. 登录您的DNSPod帐户。

2. 单击页面左侧菜单的域名解析-我的域名,然后选择要配置的域

3. 选择域名设置

DNSPod DNSSEC设置

4. 打开DNSSEC选项卡。如果列表中有DNSSEC记录,请将其删除。

推荐阅读

查看以下教程,了解如何解决您可能面临的其他问题。

小结

ERR_NAME_NOT_RESOLVED错误的发生仅仅是因为系统找不到正确的IP地址或计算机存储了旧的和过时的信息。

从系统中清除缓存数据是修复ERR_NAME_NOT_RESOLVED错误的主要方法。

但是,还有其他方法可以解决此问题,因为可能还有其他促成因素。这是一个回顾:

  1. 清除操作系统上的DNS缓存。
  2. 检查Google Chrome浏览器设置并清除浏览器历史记录、cookie和缓存。
  3. 重置防火墙并使用公共DNS服务器。
  4. 将网站的IP地址添加到hosts文件中。
  5. 使用不同的互联网连接。
  6. 为您的域禁用DNSSEC。

如果这些方法均未修复您网站上的ERR_NAME_NOT_RESOLVED错误,请联系您的托管服务提供商的客户支持团队以获得进一步帮助。

Windows构建Flutter环境,无法访问maven.google.com_ouhuanquan的博客-CSDN博客

mikel阅读(442)

来源: Windows构建Flutter环境,无法访问maven.google.com_ouhuanquan的博客-CSDN博客

flutter 环境变量配置好之后,运行flutter doctor出现问题如下:

 

X HTTP host https://maven.google.com/ is not reachable. Reason: An error occurred while checking the HTTP host,

尝试很多方法都无法解决,Google好久也没有解决办法,自己尝试修改http_host_validator.dart文件后得到解决,特此记录,解决办法如下:

1、找到flutter sdk的文件目录,打开flutter\packages\flutter_tools\lib\src\http_host_validator.dart

2、将https://maven.google.com/ 修改为https://dl.google.com/dl/Android/maven2/

3、关闭cmd命令窗口,重新打开cmd窗口

4、去到flutter\bin目录,删除cache文件夹

5、在cmd命令窗口重新运行flutter doctor,问题解决。
————————————————
版权声明:本文为CSDN博主「ouhuanquan」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ouhuanquan/article/details/123134340

Flutter源码阅读分析:Framework层的启动_董小虫的博客-CSDN博客

mikel阅读(473)

来源: Flutter源码阅读分析:Framework层的启动_董小虫的博客-CSDN博客

Framework的启动
本文也发布于本人的知乎专栏:https://zhuanlan.zhihu.com/p/394564792

0. 前言
在我之前的文章Flutter源码阅读分析:引擎初始化与启动的最后,提到了在引擎启动时,会以“main”方法作为主入口函数,执行Dart代码。那么本片文章就从“main”方法着手,分析Dart Framework具体做了什么。

Framework代码:https://github.com/flutter/flutter

1. runApp
首先我们从flutter官方给的例子来看:

// ./examples/hello_world/lib/main.dart
void main() =>
runApp( //[1]
const Center( // [2]
child:
Text(‘Hello, world!’,
key: Key(‘title’),
textDirection: TextDirection.ltr
)
)
);

[1] runApp方法的主要功能是填充给定的Widget并将其附到屏幕上。
[2] Center是一种将子节点置于中心的Align,这些都是Fllutter的Widget系统,这个在后文中详细分析。
此处先看runApp做了什么:

// ./packages/flutter/lib/src/widgets/binging.dart
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}

如果重复调用runApp方法,那么会将之前的根Widget从屏幕中移除,并将新的指定的Widget替换到该位置。这个新的Widget树与前者进行对比,并将区别的地方应用到后续的渲染树。这里涉及到Flutter的布局渲染机制,会在后续讲解。

WidgetsFlutterBinding类是基于Widget框架的应用程序的具体绑定。这将框架和Flutter引擎绑定起来。

// ./packages/flutter/lib/src/widgets/binding.dart
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}

ensureInitialized方法返回一个WidgetBinding的实例。
再看一下scheduleAttachRootWidget和scheduleWarmUpFrame方法:

// ./packages/flutter/lib/src/widgets/binding.dart
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {

// 通过Timer安排一个任务,用于执行附着根Widget
@protected
void scheduleAttachRootWidget(Widget rootWidget) {
Timer.run(() {
attachRootWidget(rootWidget); // [3]
});
}

}

// ./packages/flutter/lib/src/scheduler/binding.dart
mixin SchedulerBinding on BindingBase {

// 以最快速度安排一帧,而不是等待引擎为响应系统“Vsync”信号而请求一帧。
// 这个方法用于在应用启动时,这样第一帧可以得到多一些时间来运行。
void scheduleWarmUpFrame() {

// We use timers here to ensure that microtasks flush in between.
Timer.run(() {
handleBeginFrame(null); // [4]
});
Timer.run(() {
handleDrawFrame(); // [5]
resetEpoch();
_warmUpFrame = false;
if (hadScheduledFrame)
scheduleFrame(); // [6]
});

}

}

[3] attachRootWidget方法将一个Widget附着到renderViewElement。
// ./packages/flutter/lib/src/widgets/binding.dart
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
DebugShortDescription: ‘[root]’,
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}
这里涉及到的Widget、Element、Render,都属于Flutter的渲染机制,后续在渲染机制中详细分析。

[4] handleBeginFrame方法主要作用是让framework准备好,去创建一个新的帧。它会调用所有通过scheduleFrameCallback注册进来的临时回调函数。
[5] handleDrawFrame方法主要作用是创建一个新的帧。这个方法一般紧跟着handleBeginFrame方法后调用。
// ./packages/flutter/lib/src/scheduler/binding.dart
void handleDrawFrame() {

// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);

// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);

}

handleDrawFrame会调用所有通过addPersistentFrameCallback方法注册进来的回调函数(这些回调函数通常都是驱动渲染管线),以及通过addPostFrameCallback方法注册进来的回调函数。
在RendererBinding类的initInstantces方法中调用了addPersistentFrameCallback方法。其添加的回调函数如下:

// ./packages/flutter/lib/src/rendering/binding.dart
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
_mouseTracker.schedulePostFrameCheck();
}

void drawFrame() {
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
if (sendFramesToEngine) {
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = true;
}
}

drawFrame方法主要是让渲染管线产生出一帧。

[6] scheduleFrame则是在需要的时候,通过调用Window的scheduleFrame方法安排一个新的帧。
// ./packages/flutter/lib/src/scheduler/binding.dart
void scheduleFrame() {

ensureFrameCallbacksRegistered();
window.scheduleFrame();
_hasScheduledFrame = true;
}

这里的Window类是在引擎内实现的,通过Dart引擎扩展API,调用到C++的ScheduleFrame方法:

// (engine)./lib/ui/window/window.cc
void ScheduleFrame(Dart_NativeArguments args) {
UIDartState::Current()->window()->client()->ScheduleFrame();
}

2. 渲染管线
下面看一下PipelineOwner,这个类是渲染树的持有者,维护布局、合成、绘制和可达性的状态。
PipelineOwner主要方法有以下几个

// ./packages/flutter/lib/src/rendering/object.dart
void flushLayout() { //[7]

while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth – b.depth)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}

}

void flushCompositingBits() { // [8]

_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth – b.depth);
for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
node._updateCompositingBits();
}
_nodesNeedingCompositingBitsUpdate.clear();

}

void flushPaint() { // [9]

final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth – a.depth)) {
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}

}

void flushSemantics() { // [10]

final List<RenderObject> nodesToProcess = _nodesNeedingSemantics.toList()
..sort((RenderObject a, RenderObject b) => a.depth – b.depth);
_nodesNeedingSemantics.clear();
for (final RenderObject node in nodesToProcess) {
if (node._needsSemanticsUpdate && node.owner == this)
node._updateSemantics();
}
_semanticsOwner.sendSemanticsUpdate();

}

[7] flushLayout方法用于更新所有“脏”RenderObject的布局信息。该方法时渲染管线中的核心步骤之一。布局信息需要在绘制之前处理“干净”,这样RenderObject就可以在屏幕上出现在最新的位置。
[8] flushCompositingBits方法用于更新RenderObject的needsCompositing字位。
[9] flushPaint方法用于更新所有RenderObject的显示列表,是渲染管线核心步骤之一。该步骤在布局操作之后,场景合成之前。
[10] flushSemantics方法更新RenderObject的语义。该方法也是核心步骤。
4. 总结
Framework的启动从Flutter App的main方法开始,通过runApp方法启动。

将Flutter App中的Widget树附着到WidgetsBinding上;
驱动渲染管线绘制首帧;
再需要的情况下,通过Window的scheduleFrame方法驱动引擎发起新的一帧。
————————————————
版权声明:本文为CSDN博主「董小虫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dongzhong1990/article/details/105798049

Flutter渲染机制:Widget、Elment和RenderObject_董小虫的博客-CSDN博客

mikel阅读(520)

来源: Flutter渲染机制:Widget、Elment和RenderObject_董小虫的博客-CSDN博客

Widget、Elment和RenderObject
本文也发布于本人的知乎专栏:https://zhuanlan.zhihu.com/p/394568668

引子
在Flutter源码阅读分析:Framework层的启动中,我们分析了Framework层的启动流程,其中讲到了在runApp方法中,调用到了attchRootWidget方法:

// ./packages/flutter/lib/src/widgets/binding.dart
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
DebugShortDescription: ‘[root]’,
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}

这个方法获取一个Widget并将其附到renderViewElement上,在必要的时候创建这个renderViewElement。
其中涉及到了Widget、Element和Render,都属于Flutter渲染机制。本文将对Flutter渲染机制进行分析。

首先看一下RenderObjectToWidgetAdapter这个类和其构造方法:

class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
RenderObjectToWidgetAdapter({
this.child,
this.container,
this.DebugShortDescription,
}) : super(key: GlobalObjectKey(container));

}

这个类的作用是桥接RenderObject和Element树,其中container就是RenderObject,而Element树则插入在其中。类型参数T是一种RenderObject,是container期望其孩子的类型。
再看一下RenderObjectToWidgetAdapter类的attachToRenderTree方法:

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element.assignOwner(owner);
});
owner.buildScope(element, () {
element.mount(null, null);
});
// This is most likely the first time the framework is ready to produce
// a frame. Ensure that we are asked for one.
SchedulerBinding.instance.ensureVisualUpdate();
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}

这个方法填充了widget,并且将结果RenderObject设置为container的孩子。如果element为空,那么则会创建一个新的element,否则的话,给定的element会调度一个更新,使得与当前widget关联。

从上面可以看出,对于Flutter框架来说,主要关注的就是Widget、Element、RenderObject。下面我们来分析一下这三者的特点和关系。

Widget
在上文中的例子中,rootWidget是用户开发的Flutter应用的根节点,是一个Widget类。
在Widget类的注释中,官方给出的定位是用于描述Element的配置。Widget是Flutter框架的中心类结构。一个Widget是UI中一个固定不变的部分。可以被填充成Element,而Element又管理底层的渲染树。
Widget本身是没有可变状态的,所有的成员变量都是final的。如果需要和一个Widget关联的可变状态,可以使用StatefulWidget,这个类会创建一个StatefulWidget,而它又会在填充成element和合并到树中的时候创建一个State对象。
一个给定的Widget可以被包含到树中0次或更多次。特别是Widget可以被多次放置到树中。每次Widget被放置到树中,都会填充成一个Element。看一下Widget基类的方法声明:

@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });

final Key key;

@protected
Element createElement();

static bool canUpdate(Widget oldWidget, Widget newWidget) {

}

static int _debugConcreteSubtype(Widget widget) {

}
}

分别介绍一下这几个方法和成员变量。
首先是key这个成员变量,它用于控制在树中一个Widget如何替换另一个。主要有以下几种方式:更新Element、替换Element以及换位置。通常情况下,如果一个Widget是另一个的唯一孩子,那么不需要明确的key。
createElement方法用于将配置填充为一个具体的实例。
canUpdate方法用于判断newWidget能否用于更新当前以oldWidget为配置的Element。
_debugConcreteSubtype方法返回一个编码值,用于指示Widget的实际子类型,1表示StatefulWidget,2表示StatelessWidget。
StatefullWidget和StatelessWidget都是Widget的抽象子类,下面看一下这两个子类的具体情况。

StatelessWidget
StatelessWidget用于不需要可变状态的情况。一个无状态Widget通过建立一些列其他更完整描述UI的Widget的方式,来描述部分UI。这个构建过程是一个递归的过程,直到这个描述已经被完全的实现。
当部分UI依赖的只有其自身配置信息和BuildContext时,StatelessWidget就非常有用了。

abstract class StatelessWidget extends Widget {

@protected
Widget build(BuildContext context);
}

build方法会在当前Widget被插入到给定BuildContext内的树中时被调用。框架会用这个方法返回的Widget更新当前Widget的子树,可能是更新现有子树,也可能是移除子树。然后根据返回的Widget填充一个新的子树。
通常情况下,这个方法的实现,会返回一个新建的Widget系列,构建信息是根据从当前Widget构造函数和给定BuildContext中传递进来的信息来配置。

StatefulWidget
StatefulWidget拥有一个可变的状态。这个状态State在Widget建立时可以同步地被读取,而在Widget的整个声明周期中可能会改变。
StatefulWidget可用于可动态变化的用户节目口描述。比如说,依赖于一些系统状态或者时钟驱动的情况。

abstract class StatefulWidget extends Widget {

@protected
State createState();
}

StatefulWIdget实例本身时不可变的,但是其动态信息会保存在一切辅助类对象里,比如通过createState方法创建的State对象,或者是State订阅的对象。
框架会在填充一个StatefulWidget时调用createState方法。这意味着,当一个StatefulWidget在树中不同位置插入时,可能会有多个State对象与这个StatefulWidget关联。类似的,如果一个StatefulWidget先从树中移除,之后又重新插入到树中,那么框架会再次调用createState去创建一个新的State对象,便于简化State对象的声明周期。

可以看出,对于StatefulWidget来说,State类是关键辅助类。下面再看一下State类的详情。

State
State类用于表示StatefulWidget的逻辑和内部状态。
State对象有以下的声明周期:

框架通过调用StatefulWidget.createState方法创建State对象;
新创建的State对象与一个BuildContext关联。这个关联是不变的,也就是说,State对象不会改变它的BuildContext。不过,BuildContext本身是可以在沿着子树移动。这种情况下,State对象可以认为是mounted。
框架调用initState方法。State的子类都需要重载initState方法,来实现一次性初始化。这个初始化依赖于BuildContext或Widget,即分别对应于context和widget属性。
框架调用didChangeDependencies方法。State的子类需要重写该方法,来实现包括InderitedWidget在内的初始化。如果调用了BuildContext.dependOnInheritedWIdgetOfExactType方法,那么在后续InheritedWidget改变或当前Widget在树中移动时,didChangeDependencies方法会再次被调用。
此时State对象已经完全初始化,框架可能会调用任意次数的build方法来获取一个子树UI的描述。State对象会自发的通过调用setState方法来请求重建其子树。这个方法以为着其部分内部状态发生了改变,这可能会影响到子树中的UI。
在这期间,一个父Widget可能会重建和请求当前位置显式一个新的Widget。当这些发生时,框架会将widget属性更新为新的Widget,并且调用didUpdateWidget方法,将之前的Widget作为一个参数传入。State对象应该重载didUpdateWidget方法来应对其关联的Widget的变化。框架也会在didUpdateWidget方法之后调用build方法,这意味着在didUpdateWidget方法内调用setState方法是多余的。
在开发过程中,如果发生了重载,则会调用reassemble方法。这会使得在iniState方法中准备好的数据重新初始化。
如果包含State的子树从树中移除,框架会调用deactivate方法。子类需要重载这个方法,来清理当前对象和树中其他element的连接。
此时,框架可能会重新将该子树插入到树的另一个地方。框架会确保调用了build方法使得State对象适配新位置。以上操作会在子树移动所在的动画帧结束前完成,这意味着State对象可以延迟释放大部分资源,直到框架调用他们的dispose方法。
如果框架没有在动画帧结束前重新插入子树,那么框架会调用dispose方法,表示这个State对象不会再创建。子类需要重载这个方法来释放这个对象持有的资源。
在框架调用dispose之后,State对象可以认为是未安装状态,其mounted属性置为false。此时不能调用setState方法。生命周期终止:在State对象被处理后,将不再有机会重新挂载。
@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {

T _widget;

StatefulElement _element;
bool get mounted => _element != null;

@protected
@mustCallSuper
void initState() {
assert(_debugLifecycleState == _StateLifecycle.created);
}

@mustCallSuper
@protected
void didUpdateWidget(covariant T oldWidget) { }

@protected
@mustCallSuper
void reassemble() { }

@protected
void setState(VoidCallback fn) {

}

@protected
@mustCallSuper
void deactivate() { }

@protected
@mustCallSuper
void dispose() {

}

@protected
Widget build(BuildContext context);

@protected
@mustCallSuper
void didChangeDependencies() { }

}

InheritedWidget
InheritedWIdget可用于向下传播信息的Widget的基类。为了从BuildContext中获取最近的特定类型InheritedWidget实例,需要使用BuildContext.dependOnInheritedWidgetOfExactType。如果使用这种方式引用了InheritedWidget,那么在其状态发生改变时,会引发消费者重建。

abstract class InheritedWidget extends ProxyWidget {

@override
InheritedElement createElement() => InheritedElement(this);

@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

InheritedWIdget继承自ProxyWidget。ProxyWidget会有子Widget提供给它,而不需要新创建一个。

RenderObjectWidget
RenderObjectWidget为RenderObjectElement提供配置。RenderObjectElement用于包装RenderObject。而RenderObject则是提供了应用实际渲染。

abstract class RenderObjectWidget extends Widget {

@override
RenderObjectElement createElement();

@protected
RenderObject createRenderObject(BuildContext context);

@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}

这个类有三个重要子类,分别是LeafRenderObjectWidget、SingleChildRenderObjectWidget、MultiChildRenderObjectWidget,分别用于无子节点、有单个子节点和有多个子节点的RenderObjectWidget。

Widget小结
Widget构成了Flutter UI的最上层,直接面对开发者。开发者在开发Flutter应用时,都是通过Widget来实现应用的UI。
Widget类继承关系如图所示:

Element
上文讲过了Widget的作用是为Element的配置提供描述的。反过来讲,那就是Element其实可以说是Widget在树中特定位置的实例。
Widget用来描述如何配置一个子树,而且同一个Widget可以用来同时配置多个子树,因为Widget是不可变的。经过一段时间后,与给定Element相关联的Widget可能会发生改变。例如,当父节点Widget重建后,为当前位置创建了一个新Widget。
Element会形成一棵树。大部分的Element拥有单独的一个子节点,不过部分Widget(如RenderObjectElement的子类)会拥有多个子节点。
Element有如下的生命周期:

框架通过调用Widget.createElement方法创建一个Element。这个Widget被用来当作Element的初始化配置。
框架调用mount方法来将一个新创建的Element添加到树中给定父节点的指定槽中。mount方法负责填充所有的子Widget,以及在必要的时候调用attachRenderObject将关联的RenderObject附着到render树上。
此时,可以认为Element是“active”,可以出现在屏幕上了。
有些时候,父节点可能会变更该Element使用的配置Widget。在这种情况下,框架会调用update方法来更新Widget。新的Widget通常会有与老的Widget相同的runtimeType和key。如果父节点希望改变runtimeType或key,可以通过卸载该Element并填充新的Widget来实现。
还有些时候,一个祖先节点可能会通过调用自身的deactivateChild方法将当前Element从树中移除。停用间接祖先会导致将那个Element的RenderObject从渲染树中移除,并且将当前Element添加到owner的非活动Element列表中,最终引起框架对Element调用deactivate方法。
此时,可以认为Element是“inactive”的,且不再在屏幕上出现。一个Element可以在当前动画帧结束前保持在“inactive”状态。在动画帧结束时,所有仍然保持在“inactive”状态的Element会被卸载(unmount)。
如果这个Element重新合并入树中(如该Element或其祖先节点有一个global key,且重用时),框架会将这个Element从owner的非活跃Element列表中移除,然后将该Element的RenderObject重新附着到渲染树中。此时,这个Element重新被视为“active”,且可以出现在屏幕上。
如果这个Element在当前动画帧结束时没有重新合并到树中,框架就会对该Element调用unmount方法。
此时,这个Element可以认为是“defunct”状态,且不再会重新合并入树中。
abstract class Element extends DiagnosticableTree implements BuildContext {

@mustCallSuper
@protected
void reassemble() {

}

void visitChildren(ElementVisitor visitor) { }

@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {

}

@mustCallSuper
void mount(Element parent, dynamic newSlot) {

}

@mustCallSuper
void update(covariant Widget newWidget) {

}

void detachRenderObject() {

}

void attachRenderObject(dynamic newSlot) {

}

@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {

}

@protected
void deactivateChild(Element child) {

}

@mustCallSuper
void activate() {

}

@mustCallSuper
void deactivate() {

}

@mustCallSuper
void unmount() {

}

void markNeedsBuild() {

}

void rebuild() {

}

@protected
void performRebuild();
}

可以看到,Element类继承于BuildContext类,也就是说Element就是在Widget小节里经常提到的BuildContext。
在Element类的方法中,updateChild方法是Widget系统的核心。这个方法的作用是使用给定的新配置来更新指定的子节点。每当基于更新的配置来添加、更新、移除一个子节点时,都会调用这个方法。updateChild方法通过比较子节点和给定新配置,来判断如何处理。可由下表来表示其逻辑。

newWidget == null newWidget != null
child == null Returns null. Returns new [Element].
child != null Old child is removed, returns null. Old child updated if possible, returns child or new [Element].
Element是一个抽象类,其子类主要分为两种,ComponentElement和RenderObjectElement。下面分别看一下各自的情况。

ComponentElement
ComponentElement是组合其他Element的Element。其本身不直接创造RenderObject,但是会通过创造其他Element的方式间接地创建RenderObject。
ComponentElement的子类StatelessElement和StatefulElement分别是对应于StatelessWidget和StatefullWidget的Element。同样,InheritedElement也是ComponentElement的一种,对应于InheritedWidget。

RenderObjectElement
RenderObjectElement对象有一个关联的渲染树中的RenderObject。RenderObject实际执行布局、绘制、碰撞检测等操作。
RenderObject有三种子节点模型:

叶节点RenderObject,无子节点:LeafRenderObjectElement类处理这种情况
单独子节点:SingleChildRenderObjectElement类处理这种情况
多个子节点的链表:MultiChildRenderObjectElement类处理这种情况
有的时候,RenderObject的子节点模型会更复杂。可续会有一个二维数组的子节点,可能仅在需要的时候创建子节点,也可能形成多列表的形式。在这些情况发生时,就需要相应的新的RenderObjectElement子类。这样的子类需要能够管理子节点,特别时这个对象的Element子节点,以及其对应RenderObject的子节点。
RenderObjectElement还有一个特殊的子类RootRenderObjectElement,用于表示树的根节点。只有根节点Element可以显式的设置BuildOwner,其他的Element都只能继承父节点的BuildOwner。

RenderObject
RenderObject是渲染库的核心。每个RenderObject都有一个父节点,且有一个叫parentData的槽位用于供父RenderObject保存子节点相关数据,例如子节点位置等。RenderObject类还实现了基本的布局和绘制协议。
RenderObject类没有定义子节点模型(如一个节点有零个、一个还是多个子节点),也没有定义坐标系(如是在直角坐标系还是极坐标系中),同样也没有定义特定的布局协议。
RenderBox子类采用了直角坐标系布局系统。通常情况下,直接继承RenderObject类有点过度了,继承RenderBox类可能会更好一些。当然,如果实现的子类不想使用直角坐标系的话,那就得继承RenderObject类了。

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {

void reassemble() {

}

// LAYOUT

@override
void adoptChild(RenderObject child) {

}

@override
void dropChild(RenderObject child) {

}

void visitChildren(RenderObjectVisitor visitor) { }

@override
void attach(PipelineOwner owner) {

}

void markNeedsLayout() {

}

void scheduleInitialLayout() {

}

void layout(Constraints constraints, { bool parentUsesSize = false }) {

}

// PAINTING

void markNeedsCompositingBitsUpdate() {

}

void markNeedsPaint() {

}

void scheduleInitialPaint(ContainerLayer rootLayer) {

}

void paint(PaintingContext context, Offset offset) { }

void applyPaintTransform(covariant RenderObject child, Matrix4 transform) {

}

// SEMANTICS
void scheduleInitialSemantics() {

}

void markNeedsSemanticsUpdate() {

}

void assembleSemanticsNode(
SemanticsNode node,
SemanticsConfiguration config,
Iterable<SemanticsNode> children,
) {

}

// EVENTS
@override
void handleEvent(PointerEvent event, covariant HitTestEntry entry) { }

}

下面来看一下Flutter提供的实现子类RenderBox。

RenderBox
RenderBox是在二维直角坐标系内的RenderObject。
对于RenderBox来说,size表达为宽和高。每个RenderBox都有自己的坐标系,这个坐标系左上角坐标为(0, 0),右下角坐标为(width, height)。RenderBox中的点包含了左上角,但是不包含右下角。
盒布局通过向下传递BoxConstraints对象来实现布局。BoxContraints为子节点的宽高提供了最大值和最小值约束。子节点在确定自身尺寸时,必须遵守父节点给定的约束。
以上协议足够表达一系列普通盒布局数据流。例如,为了实现width-in-height-out数据流,在调用子节点layout方法时,传递有紧凑的宽度数值的盒约束。在子节点确定了其高度后,使用子节点的高度来确定自身的尺寸。

RenderView
RenderView是渲染树的根节点,其直接继承于RenderObject。
RenderVIew表示的是渲染树的整体输出surface。它也处理整个渲染管线的启动工作。RenderView只有一个单独的子节点,这个子节点是RenderBox类,它负责填满整个输出surface。

三者关系
我们仍然以Framework层的启动中的启动的app为例。

const Center( // [2]
child:
Text(‘Hello, world!’,
key: Key(‘title’),
textDirection: TextDirection.ltr
)
)

Center
首先看一下Center类。Center是将子节点置于其中心的Widget。

class Center extends Align {

}

Center类继承自Align类:

class Align extends SingleChildRenderObjectWidget {

@override
RenderPositionedBox createRenderObject(BuildContext context) {
return RenderPositionedBox(
alignment: alignment,
widthFactor: widthFactor,
heightFactor: heightFactor,
textDirection: Directionality.of(context),
);
}

}

Align类继承自SingleChildRenderObjectWidget,对应的Element则为SingleChildRenderObjectElement。Align重载了createRenderObject方法,创建的RenderObject为RenderPositionedBox。
接着看一下RenderPositionedBox类:

class RenderPositionedBox extends RenderAligningShiftedBox {

}

abstract class RenderAligningShiftedBox extends RenderShiftedBox {

}

abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {

}

RenderPositionedBox类最终继承自RenderBox。通过使用一个AlignmentGeometry来对子节点进行定位。

则对于Center来说,三者的关系如图所示:

Text
再来看一下Text类。该类用于表示一连串相同样式的文字。

class Text extends StatelessWidget {

@override
Widget build(BuildContext context) {

Widget result = RichText(

text: TextSpan(

),
);

return result;
}

}

Text类继承于StatelessWidget,对应StatelessElement。Text实现了build方法,创建了一个RichText。RichText也是一个Widget,继承关系如下:

class RichText extends MultiChildRenderObjectWidget {

@override
RenderParagraph createRenderObject(BuildContext context) {

}

}

RichText继承于MultiChildRenderObjectWidget,用于表示一个富文本段落。RichText可能有多个SizedBox类子节点,但这种类型的子节点是通过Text.rich方法创建的,该例子内不涉及,也就是说children属性是一个长度为0的列表。
RichText重载了createRenderObject,创建一个RenderParagraph。

class RenderParagraph extends RenderBox
with ContainerRenderObjectMixin<RenderBox, TextParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, TextParentData>,
RelayoutWhenSystemFontsChangeMixin {

}

RenderParagraph继承自RenderBox,是用于展示文字段落的RenderObject。
对于Text来说,三者关系如下图:

三棵树
根据上面的分析,可以得到例子的三棵树关系图。

总结
本文对Flutter框架中的Widget、Element和RenderObject做了简要讲解。这三个类系统是Flutter框架的核心。Widget负责UI部分,与开发者直接交互;Element负责在指定位置实例化Widget,并维护树结构;RenderObject则是渲染的核心,负责包括布局、测量、绘制等工作。
三棵树之间有一定的对应关系。一个Widget可能会对应多个Element,而一个Element则仅对应一个Widget;只有继承于RenderObjectElement的Element会维护RenderObject;而RenderObject的创建入口则是在RenderObjectWidget中。
后续文章中,会基于这三者的概念之上,详细分析Flutter的渲染管线。
————————————————
版权声明:本文为CSDN博主「董小虫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dongzhong1990/article/details/107042295

Flutter源码阅读分析:引擎初始化与启动_董小虫的博客-CSDN博客_flutter引擎初始化

mikel阅读(465)

来源: Flutter源码阅读分析:引擎初始化与启动_董小虫的博客-CSDN博客_flutter引擎初始化

引擎初始化与启动
本文也发布于本人的知乎专栏:https://zhuanlan.zhihu.com/p/394560540

0. 前言
Flutter是当前比较火热的前端开发框架,正好我最近也在做和Flutter引擎相关的工作,就顺手研究一下Flutter的源码。
源码主要分为两部分:

Engine,是可供Flutter宿主应用提供的一个可移植运行时。Engine实现了Flutter的核心库,包括动画、图形、文件、网络I/O、访问支持、插件架构和Dart的运行时、编译工具链;
Flutter Framework,大部分的Flutter开发者主要通过Flutter Framework交互。Framework提供了一个现代的、可交互的框架,以及一个丰富的平台、布局、基础部件的集合。
Flutter官方源码下载路径:
engine: https://github.com/flutter/engine
flutter framework: https://github.com/flutter/flutter

本文主要分析引擎的初始化流程和启动流程。

1. 初始化流程
1.1 Android平台适配层
众所周知,Android应用的入口基本都是Activity,那么我们就先从Flutter的Activity开始着手分析。

1.1.1 FlutterActivity
在Engine中存在两个FlutterActivity(shell/platform/Android/io/flutter/app/FlutterActivity.java和shell/platform/android/io/flutter/embedding/android/FlutterActivity.java),其中在2020年5月13日的代码提交中,前一个FlutterActivity注释修改为废弃Activity基类。所以现在可用的Activity基类是后一个。
根据FlutterActivity的注释,我们可知:
FlutterActivity是将Flutter集成到Android应用中的最简单最直接的方式,用于显示一个全屏的Flutter UI。主要职责是:

显示一个Android的lauch screen;
显示Flutter的splash screen;
设置状态栏;
选择Dart执行应用包路径和入口点;
选择Flutter的初始化路由;
如果需要的话,渲染透明度;
提供子类钩子,提供和配置FlutterEngine。
先从FlutterActivity定义看起:

public class FlutterActivity extends Activity
implements FlutterActivityAndFragmentDelegate.Host, LifecycleOwner
1
2
FlutterActivity继承于Activity。FlutterActivityAndFragmentDelegate.Host接口是在FlutterActivityAndFragmentDelegate中FlutterActivity持有类。

Activity的onCreate方法是入口关键方法,看一下FlutterActivity的onCreate方法:

// ./shell/platform/android/io/flutter/embedding/android/FlutterActivity.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
switchLaunchThemeForNormalTheme(); // 设置主题

super.onCreate(savedInstanceState);

lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); // 设置Android生命周期

delegate = new FlutterActivityAndFragmentDelegate(this); // [1]
delegate.onAttach(this);
delegate.onActivityCreated(savedInstanceState);

configureWindowForTransparency(); // 设置背景透明模式
setContentView(createFlutterView()); // [2]
configureStatusBarForFullscreenFlutterExperience(); // 配置系统状态栏
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[1] FlutterActivityAndFragmentDelegate类实现了FlutterActivity与FlutterFragment之间相同的逻辑。
[2] 通过调用FlutterActivityAndFragmentDelegate的onCreateView方法创建出FlutterView并将其添加到界面上。FlutterView用于在Android设备上显示Flutter UI。
1.1.2 FlutterActivityAndFragmentDelegate
下面就再分析一下FlutterActivityAndFragmentDelegate的源码逻辑:

// ./shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
final class FlutterActivityAndFragmentDelegate {

FlutterActivityAndFragmentDelegate(@NonNull Host host) {
this.host = host; // 保存FlutterActivity对象
}

// [3]
void onAttach(@NonNull Context context) {

setupFlutterEngine(); // 创建FlutterEngine

}

// [4]
@NonNull
View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

// 创建FlutterSurfaceView,用于提供绘制Flutter UI的Surface
FlutterSurfaceView flutterSurfaceView =
new FlutterSurfaceView(
host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);

flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);

flutterSplashView = new FlutterSplashView(host.getContext());

flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
flutterView.attachToFlutterEngine(flutterEngine);
return
}

return flutterSplashView;
}
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
[3] onAttach方法主要做了以下事情:1. 初始化Flutter系统;2. 获取或者创建一个FlutterEngine;3. 创建和配置PlatformPlugin;4. 将FlutterEngine附在Activity上;5. 通过Host的configureFlutterEngine方法配置FlutterEngine。
[4] ‘onCreateView’方法主要做以下事情:1. 在View树中创建一个新的FlutterView;2. 在FlutterView中添加一个FlutterUiDisplayListener;3. 将FlutterEngine附着到FlutterView上;4. 返回这个新的View树。
在这里出现了两个比较重要的类,FlutterView和FlutterEngine。

1.1.3 FlutterView
FlutterView的作用是将Flutter UI通过对应的FlutterEngine绘制后,显示在Android设备上。这里有两种渲染模式:surface和texture。
在一般情况,我们都使用surface模式,即将Flutter UI绘制在SurfaceView上,这种模式拥有最优的性能,缺点是无法置于两个其他Android View的z-index之间,同时也无法动画化或者进行变换。
下面我们看一下FlutterView的源码实现:

// ./shell/platform/android/io/flutter/embedding/android/FlutterView.java
public class FlutterView extends FrameLayout {

private FlutterView(
@NonNull Context context,
@Nullable AttributeSet attrs,
@NonNull FlutterSurfaceView flutterSurfaceView) {
super(context, attrs);

this.flutterSurfaceView = flutterSurfaceView;
this.renderSurface = flutterSurfaceView;

init();
}

private void init() {

addView(flutterSurfaceView);

// FlutterView needs to be focusable so that the InputMethodManager can interact with it.
setFocusable(true);
setFocusableInTouchMode(true);
}

// 将当前FlutterView连接到给定的FLutterEngine
// FlutterView将通过给定的FlutterEngine绘制UI,同时也将开始将后续的交互时间传递到FlutterEngine,
// 例如触摸事件、键盘事件等。
public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {

this.flutterEngine = flutterEngine;
// Instruct our FlutterRenderer that we are now its designated RenderSurface.
FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();

renderSurface.attachToRenderer(flutterRenderer); // [5]

// Initialize various components that know how to process Android View I/O
// in a way that Flutter understands.

// Push View and Context related information from Android to Flutter.
sendUserSettingsToFlutter();
sendLocalesToFlutter(getResources().getConfiguration());
sendViewportMetricsToFlutter();

flutterEngine.getPlatformViewsController().attachToView(this); // PlatformViewsController获取当前FlutterView

}
}
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
[5] 将FlutterSurfaceView的Surface提供给指定的FlutterRender,用于将Flutter UI绘制到当前的FlutterSurfaceView。
1.1.4 FlutterEngine
FlutterEngine是一个独立的Flutter运行环境,是Dart代码运行在Android应用的容器。

// ./shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
/** Fully configurable {@code FlutterEngine} constructor. */
public FlutterEngine(
@NonNull Context context,
@NonNull FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI,
@NonNull PlatformViewsController platformViewsController,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins) {
this.flutterJNI = flutterJNI;
flutterLoader.startInitialization(context.getApplicationContext());

attachToJni();
this.dartExecutor = new DartExecutor(flutterJNI, context.getAssets());
this.dartExecutor.onAttachedToJNI();
this.renderer = new FlutterRenderer(flutterJNI);

xxxChannel = new XxxChannel(…); // 创建各个消息通道,用于传递事件、消息

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DartExecutor用于配置、启动、执行Dart代码,在后续分析中再详细研究。
在FlutterActivityAndFragmentDelegate的setupFlutterEngine方法中调用的FlutterEngine构造方法没有传入FlutterJNI,在三参数构造方法中,会创建FlutterJNI对象,并传入全参数构造方法。FlutterJNI则是沟通Android的Java与Flutter引擎核心C++代码的桥梁。

1.1.5 FlutterJNI
FlutterJNI是Flutter内嵌Java代码和引擎C++代码之间的接口。
Flutter引擎的代码是使用C++实现的。Android Flutter嵌入则负责协调Android系统事件和应用用户交互事件。这些事件协调需要消息交流接口,这就需要用到JNI(Java Native Interface)来穿过Java/native边界。
在Flutter的设计中,所有的JNI接口都集中在FlutterJNI这个类中。这么做主要有以下几个原因:

JNI调用都是静态的,且没有Java实现,因此没有理由将调用与不同的类相关联;
所有的JNI调用必须在C/C++代码中注册,当增加额外的包含JNI调用时,这个注册会变得非常复杂;
很多Android开发者对native开发或者JNI不熟悉,因此在后续维护中减少JNI的接口是很有必要的。
大部分FlutterJNI中的调用都与特定的“platform view”相关,而“platform view”的数量可能会很多。所以,在执行了attachToNative方法后,每个FlutterJNI实例都持有一个本地“platform view”的ID,且这个ID与bendingC/C++引擎代码共享。这个ID会被传递到所有的具体platform view的本地方法。

// ./shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
public class FlutterJNI {

public FlutterJNI() {
// We cache the main looper so that we can ensure calls are made on the main thread
// without consistently paying the synchronization cost of getMainLooper().
mainLooper = Looper.getMainLooper();
}

@UiThread
public void attachToNative(boolean isBackgroundView) {
ensureRunningOnMainThread();
ensureNotAttachedToNative();
nativePlatformViewId = nativeAttach(this, isBackgroundView);
}

private native long nativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ./shell/platform/android/platform_view_android_jni.cc
bool RegisterApi(JNIEnv* env) {
static const JNINativeMethod flutter_jni_methods[] = {
{
.name = “nativeAttach”,
.signature = “(Lio/flutter/embedding/engine/FlutterJNI;Z)J”,
.fnPtr = reinterpret_cast<void*>(&AttachJNI),
},

};
if (env->RegisterNatives(g_flutter_jni_class->obj(), flutter_jni_methods,
fml::size(flutter_jni_methods)) != 0) {
FML_LOG(ERROR) << “Failed to RegisterNatives with FlutterJNI”;
return false;
}

}

static jlong AttachJNI(JNIEnv* env,
jclass clazz,
jobject flutterJNI,
jboolean is_background_view) {
fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI);
auto shell_holder = std::make_unique<AndroidShellHolder>( // [6]
FlutterMain::Get().GetSettings(), java_object, is_background_view);
if (shell_holder->IsValid()) {
return reinterpret_cast<jlong>(shell_holder.release());
} else {
return 0;
}
}
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
[6] AndroidShellHolder是C/C++的Shell持有类。
1.1.6 AndroidShellHolder
在AndroidShellHolder中保存有Flutter设置参数、FlutterJNI的Java引用、PlatformViewAndroid对象(该对象在后续创建)、Shell对象等。

// ./shell/platform/android/android_shell_holder.cc
AndroidShellHolder::AndroidShellHolder(
flutter::Settings settings,
fml::jni::JavaObjectWeakGlobalRef java_object,
bool is_background_view)
: settings_(std::move(settings)), java_object_(java_object) {

// 创建三个线程:UI线程、GPU线程、IO线程
thread_host_ = {thread_label, ThreadHost::Type::UI | ThreadHost::Type::GPU |
ThreadHost::Type::IO};

fml::WeakPtr<PlatformViewAndroid> weak_platform_view;
Shell::CreateCallback<PlatformView> on_create_platform_view =
[is_background_view, java_object, &weak_platform_view](Shell& shell) {
std::unique_ptr<PlatformViewAndroid> platform_view_android;

platform_view_android = std::make_unique<PlatformViewAndroid>( // [7]
shell, // delegate
shell.GetTaskRunners(), // task runners
java_object, // java object handle for JNI interop
shell.GetSettings()
.enable_software_rendering // use software rendering
);
weak_platform_view = platform_view_android->GetWeakPtr();
return platform_view_android;
};

// [8]
shell_ =
Shell::Create(task_runners, // task runners
GetDefaultWindowData(), // window data
settings_, // settings
on_create_platform_view, // platform view create callback
on_create_rasterizer // rasterizer create callback
);
platform_view_ = weak_platform_view;

}
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
[7] 创建PlatformViewAndroid,该类继承于PlatformView。各平台会根据自身特点继承PlatformView类,实现自身平台的PlatformView。
[8] 这里创建出了一个Shell对象,该类是Flutter引擎的关键类,此后的代码将于平台无关,为各个平台的通用代码。
1.2. 通用Shell层
1.2.1 Shell
Shell类是Flutter引擎中最重要的类之一。当嵌入应用创建了一个Flutter应用,都将创建一个Shell的实例,且嵌入者只持有一个Shell的unique指针。
Shell是Flutter应用的“中枢神经系统”,包含了多个组件,并继承它们相应的Delegate类。
下面看一下创建Shell的代码实现:

// ./shell/common/shell.cc
std::unique_ptr<Shell> Shell::Create(
TaskRunners task_runners,
const WindowData window_data,
Settings settings,
Shell::CreateCallback<PlatformView> on_create_platform_view,
Shell::CreateCallback<Rasterizer> on_create_rasterizer) {

auto vm = DartVMRef::Create(settings); // 创建Dart虚拟机
auto vm_data = vm->GetVMData();
return Shell::Create(std::move(task_runners), //
std::move(window_data), //
std::move(settings), //
vm_data->GetIsolateSnapshot(), // isolate snapshot
on_create_platform_view, //
on_create_rasterizer, //
std::move(vm) //
);
}

std::unique_ptr<Shell> Shell::Create(
TaskRunners task_runners,
const WindowData window_data,
Settings settings,
fml::RefPtr<const DartSnapshot> isolate_snapshot,
const Shell::CreateCallback<PlatformView>& on_create_platform_view,
const Shell::CreateCallback<Rasterizer>& on_create_rasterizer,
DartVMRef vm) {

std::unique_ptr<Shell> shell;
fml::TaskRunner::RunNowOrPostTask(
task_runners.GetPlatformTaskRunner(),
fml::MakeCopyable([&latch, //
vm = std::move(vm), //
&shell, //
task_runners = std::move(task_runners), //
window_data, //
settings, //
isolate_snapshot = std::move(isolate_snapshot), //
on_create_platform_view, //
on_create_rasterizer //
]() mutable {
shell = CreateShellOnPlatformThread(std::move(vm),
std::move(task_runners), //
window_data, //
settings, //
std::move(isolate_snapshot), //
on_create_platform_view, //
on_create_rasterizer //
);

}));
return shell;
}

std::unique_ptr<Shell> Shell::CreateShellOnPlatformThread(
DartVMRef vm,
TaskRunners task_runners,
const WindowData window_data,
Settings settings,
fml::RefPtr<const DartSnapshot> isolate_snapshot,
const Shell::CreateCallback<PlatformView>& on_create_platform_view,
const Shell::CreateCallback<Rasterizer>& on_create_rasterizer) {

auto shell =
std::unique_ptr<Shell>(new Shell(std::move(vm), task_runners, settings));

// Create the rasterizer on the raster thread.
fml::TaskRunner::RunNowOrPostTask(
task_runners.GetRasterTaskRunner(), [&rasterizer_promise, //
&snapshot_delegate_promise,
on_create_rasterizer, //
shell = shell.get() //
]() {
std::unique_ptr<Rasterizer> rasterizer(on_create_rasterizer(*shell));

});

// Create the platform view on the platform thread (this thread).
auto platform_view = on_create_platform_view(*shell.get());

// Ask the platform view for the vsync waiter. This will be used by the engine
// to create the animator.
auto vsync_waiter = platform_view->CreateVSyncWaiter();

// Create the IO manager on the IO thread.

fml::TaskRunner::RunNowOrPostTask(
io_task_runner,
[&io_manager_promise, //
&weak_io_manager_promise, //
&unref_queue_promise, //
platform_view = platform_view->GetWeakPtr(), //
io_task_runner, //
is_backgrounded_sync_switch = shell->GetIsGpuDisabledSyncSwitch() //
]() {
auto io_manager = std::make_unique<ShellIOManager>(
platform_view.getUnsafe()->CreateResourceContext(),
is_backgrounded_sync_switch, io_task_runner);

});

// Create the engine on the UI thread.

fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetUITaskRunner(),
fml::MakeCopyable([&engine_promise, //
shell = shell.get(), //
&dispatcher_maker, //
&window_data, //
isolate_snapshot = std::move(isolate_snapshot), //
vsync_waiter = std::move(vsync_waiter), //
&weak_io_manager_future, //
&snapshot_delegate_future, //
&unref_queue_future //
]() mutable {
const auto& task_runners = shell->GetTaskRunners();

// The animator is owned by the UI thread but it gets its vsync pulses
// from the platform.
auto animator = std::make_unique<Animator>(*shell, task_runners,
std::move(vsync_waiter));

engine_promise.set_value(std::make_unique<Engine>(
*shell, //
dispatcher_maker, //
*shell->GetDartVM(), //
std::move(isolate_snapshot), //
task_runners, //
window_data, //
shell->GetSettings(), //
std::move(animator), //
weak_io_manager_future.get(), //
unref_queue_future.get(), //
snapshot_delegate_future.get() //
));
}));
if (!shell->Setup(std::move(platform_view), //
engine_future.get(), //
rasterizer_future.get(), //
io_manager_future.get()) //
) {
return nullptr;
}
return shell;
}
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
从代码中可以看出,在platform线程中创建了Shell,之后分别在栅格化线程中创建Rasterizer,在platform线程中创建PlatformView,在IO线程中创建ShellIOManager,在UI线程中创建Engine,并将这四者设置到Shell中去。
Shell分别继承了四者的Delegate,四者通过相应的Delegate将事件传递到Shell。
下面分别看一下这四个类。

1.2.2 PlatformView
在Android平台中,真正实现的是PlarformViewAndroid类,主要方法实现的功能都是在栅格化线程中对AndroidSurface进行操作。看一下NotifyCreated方法的实现:

// ./shell/platform/android/platform_view_android.cc
void PlatformViewAndroid::NotifyCreated(
fml::RefPtr<AndroidNativeWindow> native_window) {
if (android_surface_) {
InstallFirstFrameCallback();

fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetRasterTaskRunner(),
[&latch, surface = android_surface_.get(),
native_window = std::move(native_window)]() {
surface->SetNativeWindow(native_window);

});

}

PlatformView::NotifyCreated(); // 此处调用了父类的对应方法
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ./shell/common/platform_view.cc
void PlatformView::NotifyCreated() {
std::unique_ptr<Surface> surface;
auto* platform_view = this;

fml::TaskRunner::RunNowOrPostTask(
task_runners_.GetRasterTaskRunner(), [platform_view, &surface, &latch]() {
surface = platform_view->CreateRenderingSurface();

});

delegate_.OnPlatformViewCreated(std::move(surface));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
主要实现的功能就是将native_window设置到surface中,再将这个surface通知到delegate(也就是Shell)中。也就是说,PlatformView主要起到一个沟通Surface和Shell的作用。

1.2.3 Rasterizer
Rasterizer是Shell的一个组成部分,运行在GPU线程中。每个Shell只能拥有一个Rasterizer实例。Rasterizer持有一个当前活动的在屏幕中显示的绘制Surface。Rasterizer在这个Surface上绘制从Engine中提交的layer tree。合成器上下文和屏上绘制Surface是Rasterizer的主要组成部分:合成器上下文包含绘制帧的所有必要的GPU状态。

on_create_rasterizer方法在AndroidShellHolder的构造方法中给出:

// ./shell/platform/android/android_shell_holder.cc
Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
return std::make_unique<Rasterizer>(shell, shell.GetTaskRunners());
};
1
2
3
4
// ./shell/common/rasterizer.cc
Rasterizer::Rasterizer(Delegate& delegate, TaskRunners task_runners)
: Rasterizer(delegate,
std::move(task_runners),
std::make_unique<flutter::CompositorContext>(
delegate.GetFrameBudget())) {}

Rasterizer::Rasterizer(
Delegate& delegate,
TaskRunners task_runners,
std::unique_ptr<flutter::CompositorContext> compositor_context)
: delegate_(delegate),
task_runners_(std::move(task_runners)),
compositor_context_(std::move(compositor_context)),
user_override_resource_cache_bytes_(false),
weak_factory_(this) {
FML_DCHECK(compositor_context_);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在构造方法中创建了一个CompositorContext,这个类会在后续的文章中分析渲染机制时进行分析。

1.2.4 ShellIOManager
// ./shell/common/shell_io_manager.cc
class ShellIOManager final : public IOManager {

void NotifyResourceContextAvailable(sk_sp<GrContext> resource_context);
void UpdateResourceContext(sk_sp<GrContext> resource_context);

fml::WeakPtr<GrContext> GetResourceContext() const override;
fml::RefPtr<flutter::SkiaUnrefQueue> GetSkiaUnrefQueue() const override;
}
1
2
3
4
5
6
7
8
9
ShellIOManager继承自IOManager类。IOManager是管理获取GrContext资源和Skia队列的方法的接口类。这两者都属于图形绘制相关内容,在后续文章中进行分析。
NotifyResourceContextAvailable和UpdateResourceContext方法是通知GrContext创建和获取的方法。

1.2.5 Engine
Engine类是Shell的组成部分之一,运行于UI线程。其主要功能是管理根Isolate和它的运行时。每个Shell只能拥有一个Engine实例。Flutter应用的根Isolate会获取“窗口”绑定。通过这些绑定,一个用可以调度帧、推送layer tree用于渲染、请求解压图片并提交到GPU等等。
Engine管理根Isolate的全部生命周期。当Engine被回收时,其持有者会认为根Isolate已经关闭且资源已经被回收。
下面看一下Engine的构造方法:

// ./shell/common/engine.cc
Engine::Engine(Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker,
DartVM& vm,
fml::RefPtr<const DartSnapshot> isolate_snapshot,
TaskRunners task_runners,
const WindowData window_data,
Settings settings,
std::unique_ptr<Animator> animator,
fml::WeakPtr<IOManager> io_manager,
fml::RefPtr<SkiaUnrefQueue> unref_queue,
fml::WeakPtr<SnapshotDelegate> snapshot_delegate)
: delegate_(delegate),
settings_(std::move(settings)),
animator_(std::move(animator)),
activity_running_(true),
have_surface_(false),
image_decoder_(task_runners,
vm.GetConcurrentWorkerTaskRunner(),
io_manager),
task_runners_(std::move(task_runners)),
weak_factory_(this) {
// Runtime controller is initialized here because it takes a reference to this
// object as its delegate. The delegate may be called in the constructor and
// we want to be fully initilazed by that point.
runtime_controller_ = std::make_unique<RuntimeController>(
*this, // runtime delegate
&vm, // VM
std::move(isolate_snapshot), // isolate snapshot
task_runners_, // task runners
std::move(snapshot_delegate),
std::move(io_manager), // io manager
std::move(unref_queue), // Skia unref queue
image_decoder_.GetWeakPtr(), // image decoder
settings_.advisory_script_uri, // advisory script uri
settings_.advisory_script_entrypoint, // advisory script entrypoint
settings_.idle_notification_callback, // idle notification callback
window_data, // window data
settings_.isolate_create_callback, // isolate create callback
settings_.isolate_shutdown_callback, // isolate shutdown callback
settings_.persistent_isolate_data // persistent isolate data
);

pointer_data_dispatcher_ = dispatcher_maker(*this);
}
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
当Engine创建时,会立刻创建一个RuntimeController,在RuntimeController中又会立刻创建一个DartIsolate。

2. 启动流程
启动流程从FLutterActivity的onStart方法开始分析。

先看onStart方法代码:

// ./shell/platform/android/io/flutter/embedding/android/FlutterActivity.java
@Override
protected void onStart() {
super.onStart();
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
delegate.onStart();
}
1
2
3
4
5
6
7
该方法先将生命周期调整为ON_START状态,然后调用FlutterActivityAndFragmentDelegate的onStart方法。

下面是FlutterActivityAndFragmentDelegate的onStart方法:

// ./shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
void onStart() {
ensureAlive();
doInitialFlutterViewRun();
}
1
2
3
4
5
这里先做了活动确认,后启动FlutterView内的Dart。

// ./shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
private void doInitialFlutterViewRun() {

if (flutterEngine.getDartExecutor().isExecutingDart()) {
// No warning is logged because this situation will happen on every config
// change if the developer does not choose to retain the Fragment instance.
// So this is expected behavior in many cases.
return;
}

// Configure the Dart entrypoint and execute it.
DartExecutor.DartEntrypoint entrypoint =
new DartExecutor.DartEntrypoint(
host.getAppBundlePath(), host.getDartEntrypointFunctionName());
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); // [9]
}

————————————————
版权声明:本文为CSDN博主「董小虫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dongzhong1990/article/details/105678124

ORM框架的发展历程 - 掘金

mikel阅读(474)

来源: ORM框架的发展历程 – 掘金

一、ORM框架的发展历程

image.png

1. JDBC操作

1.1 JDBC操作的特点

最初的时候我们肯定是直接通过jdbc来直接操作数据库的,本地数据库我们有一张t_user表,那么我们的操作流程是
复制代码
// 注册 JDBC 驱动
Class.forName("com.mysql.cj.jdbc.Driver");

// 打开连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf-8&serverTimezone=UTC", "root", "123456");

// 执行查询
stmt = conn.createStatement();
String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1";
ResultSet rs = stmt.executeQuery(sql);

// 获取结果集
while (rs.next()) {
    Integer id = rs.getInt("id");
    String userName = rs.getString("user_name");
    String realName = rs.getString("real_name");
    String password = rs.getString("password");
    Integer did = rs.getInt("d_id");
    user.setId(id);
    user.setUserName(userName);
    user.setRealName(realName);
    user.setPassword(password);
    user.setDId(did);

    System.out.println(user);
}
复制代码

具体的操作步骤是,首先在pom.xml中引入MySQL的驱动依赖,注意MySQL数据库的版本

  1. Class.forName注册驱动
  2. 获取一个Connection对象
  3. 创建一个Statement对象
  4. execute()方法执行SQL语句,获取ResultSet结果集
  5. 通过ResultSet结果集给POJO的属性赋值
  6. 最后关闭相关的资源
这种实现方式首先给我们的感觉就是操作步骤比较繁琐,在复杂的业务场景中会更麻烦。尤其是我们需要自己来维护管理资源的连接,如果忘记了,就很可能造成数据库服务连接耗尽。同时我们还能看到具体业务的SQL语句直接在代码中写死耦合性增强。每个连接都会经历这几个步骤,重复代码很多,总结上面的操作的特点:
复制代码
  1. 代码重复
  2. 资源管理
  3. 结果集处理
  4. SQL耦合

针对这些问题我们可以自己尝试解决下

1.2 JDBC优化1.0

针对常规jdbc操作的特点,我们可以先从代码重复和资源管理方面来优化,我们可以创建一个工具类来专门处理这个问题
复制代码
public class DBUtils {

    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf-8&serverTimezone=UTC";
    private static final String JDBC_NAME = "root";
    private static final String JDBC_PASSWORD = "123456";

    private static  Connection conn;

    /**
     * 对外提供获取数据库连接的方法
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        if(conn == null){
            try{
                conn = DriverManager.getConnection(JDBC_URL,JDBC_NAME,JDBC_PASSWORD);
            }catch (Exception e){
                e.printStackTrace();
                throw new Exception();
            }
        }
        return conn;
    }

    /**
     * 关闭资源
     * @param conn
     */
    public static void close(Connection conn ){
        close(conn,null);
    }

    public static void close(Connection conn, Statement sts ){
        close(conn,sts,null);
    }

    public static void close(Connection conn, Statement sts , ResultSet rs){
        if(rs != null){
            try {
                rs.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        if(sts != null){
            try {
                sts.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        if(conn != null){
            try {
                conn.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

复制代码

对应的jdbc操作代码可以简化如下

    /**
     *
     * 通过JDBC查询用户信息
     */
    public void queryUser(){
        Connection conn = null;
        Statement stmt = null;
        User user = new User();
        ResultSet rs = null;
        try {
            // 注册 JDBC 驱动
            // Class.forName("com.mysql.cj.jdbc.Driver");

            // 打开连接
            conn = DBUtils.getConnection();
            // 执行查询
            stmt = conn.createStatement();
            String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1";
            rs = stmt.executeQuery(sql);

            // 获取结果集
            while (rs.next()) {
                Integer id = rs.getInt("id");
                String userName = rs.getString("user_name");
                String realName = rs.getString("real_name");
                String password = rs.getString("password");
                Integer did = rs.getInt("d_id");
                user.setId(id);
                user.setUserName(userName);
                user.setRealName(realName);
                user.setPassword(password);
                user.setDId(did);
                System.out.println(user);
            }

        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn,stmt,rs);
        }
    }

   /**
     * 通过JDBC实现添加用户信息的操作
     */
    public void addUser(){
        Connection conn = null;
        Statement stmt = null;
        try {
            // 打开连接
            conn = DBUtils.getConnection();
            // 执行查询
            stmt = conn.createStatement();
            String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values('wangwu','王五','111',22,1001)";
            int i = stmt.executeUpdate(sql);
            System.out.println("影响的行数:" + i);
        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn,stmt);
        }
    }
复制代码

但是整体的操作步骤还是会显得比较复杂,这时我们可以进一步优化

1.3 JDBC优化2.0

我们可以针对DML操作的方法来优化,先解决SQL耦合的问题,在DBUtils中封装DML操作的方法

    /**
     * 执行数据库的DML操作
     * @return
     */
    public static Integer update(String sql,Object ... paramter) throws Exception{
        conn = getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        if(paramter != null && paramter.length > 0){
            for (int i = 0; i < paramter.length; i++) {
                ps.setObject(i+1,paramter[i]);
            }
        }
        int i = ps.executeUpdate();
        close(conn,ps);
        return i;
    }
复制代码

然后在DML操作的时候我们就可以简化为如下步骤

    /**
     * 通过JDBC实现添加用户信息的操作
     */
    public void addUser(){
        String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values(?,?,?,?,?)";
        try {
            DBUtils.update(sql,"wangwu","王五","111",22,1001);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
复制代码

显然这种方式会比最初的使用要简化很多,但是在查询处理的时候我们还是没有解决ResultSet结果集的处理问题,所以我们还需要继续优化

1.4 JDBC优化3.0

针对ResultSet的优化我们需要从反射和元数据两方面入手,具体如下
复制代码
    /**
     * 查询方法的简易封装
     * @param sql
     * @param clazz
     * @param parameter
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> List<T> query(String sql, Class clazz, Object ... parameter) throws  Exception{
        conn = getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        if(parameter != null && parameter.length > 0){
            for (int i = 0; i < parameter.length; i++) {
                ps.setObject(i+1,parameter[i]);
            }
        }
        ResultSet rs = ps.executeQuery();
        // 获取对应的表结构的元数据
        ResultSetMetaData metaData = ps.getMetaData();
        List<T> list = new ArrayList<>();
        while(rs.next()){
            // 根据 字段名称获取对应的值 然后将数据要封装到对应的对象中
            int columnCount = metaData.getColumnCount();
            Object o = clazz.newInstance();
            for (int i = 1; i < columnCount+1; i++) {
                // 根据每列的名称获取对应的值
                String columnName = metaData.getColumnName(i);
                Object columnValue = rs.getObject(columnName);
                setFieldValueForColumn(o,columnName,columnValue);
            }
            list.add((T) o);
        }
        return list;
    }

    /**
     * 根据字段名称设置 对象的属性
     * @param o
     * @param columnName
     */
    private static void setFieldValueForColumn(Object o, String columnName,Object columnValue) {
        Class<?> clazz = o.getClass();
        try {
            // 根据字段获取属性
            Field field = clazz.getDeclaredField(columnName);
            // 私有属性放开权限
            field.setAccessible(true);
            field.set(o,columnValue);
            field.setAccessible(false);
        }catch (Exception e){
            // 说明不存在 那就将 _ 转换为 驼峰命名法
            if(columnName.contains("_")){
                Pattern linePattern = Pattern.compile("_(\\w)");
                columnName = columnName.toLowerCase();
                Matcher matcher = linePattern.matcher(columnName);
                StringBuffer sb = new StringBuffer();
                while (matcher.find()) {
                    matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
                }
                matcher.appendTail(sb);
                // 再次调用复制操作
                setFieldValueForColumn(o,sb.toString(),columnValue);
            }
        }
    }
复制代码

封装了以上方法后我们的查询操作就可以简化为

    /**
     *
     * 通过JDBC查询用户信息
     */
    public void queryUser(){
        try {
            String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = ?";
            List<User> list = DBUtils.query(sql, User.class,2);
            System.out.println(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
复制代码

这样一来我们在操作数据库中数据的时候就只需要关注于核心的SQL操作了。当然以上的设计还比较粗糙,,这时Apache 下的 DbUtils是一个很好的选择

作者:请叫我黄同学
链接:https://juejin.cn/post/7134306563874881549
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。