引入flutter module,运行时flutter_webview_plugin报错:Unhandled Exception: MissingPluginException_yzpyzp的博客-CSDN博客

mikel阅读(599)

异常信息
Android原生项目引入了一个flutter module,flutter module用到了一个flutter写的sdk,sdk需要使用webview来执行js代码,因此sdk引用了flutter_webview_plugin,然后运行时报错:

E/flutter: [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: MissingPluginException(No implementation found for method launch on channel flutter_webview_plugin)
#0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:156:7)
<asynchronous suspension>
#1 FlutterWebviewPlugin.launch (package:flutter_webview_plugin/src/base.dart:225:5)
<asynchronous suspension>
1
2
3
4
5
看下使用的flutter_webview_plugin的版本:

执行flutter pub get:

$ flutter pub get
Running “flutter pub get” in polkawallet… 738ms
The plugin `flutter_aes_ecb_pkcs5` is built using an older version of the Android plugin API which assumes that it’s running in a full-Flutter environment. It may have undefined behaviors when Flutter is integrated into an existing app as a module.
The plugin can be updated to the v2 Android Plugin APIs by following https://flutter.dev/go/android-plugin-migration.
The plugin `flutter_qr_scan` is built using an older version of the Android plugin API which assumes that it’s running in a full-Flutter environment. It may have undefined behaviors when Flutter is integrated into an existing app as a module.
The plugin can be updated to the v2 Android Plugin APIs by following https://flutter.dev/go/android-plugin-migration.
The plugin `flutter_webview_plugin` is built using an older version of the Android plugin API which assumes that it’s running in a full-Flutter environment. It may have undefined behaviors when Flutter is integrated into an existing app as a module.
The plugin can be updated to the v2 Android Plugin APIs by following https://flutter.dev/go/android-plugin-migration.

1
2
3
4
5
6
7
8
9
注意到上面输出信息中的:

The plugin `flutter_webview_plugin` is built using an older version of the Android plugin API which assumes that it’s running in a full-Flutter environment. It may have undefined behaviors when Flutter is integrated into an existing app as a module.
The plugin can be updated to the v2 Android Plugin APIs by following https://flutter.dev/go/android-plugin-migration.
1
2
提示我们,这个 flutter_webview_plugin 是使用旧版本的 plugin API 开发的插件,它是假设在全flutter环境的项目中使用的,如果在一个Android app的module中使用,可能会出现未知的异常,要对插件进行升级可以参考:https://flutter.dev/docs/development/packages-and-plugins/plugin-api-migration

关于flutter_webview_plugin
github:https://github.com/fluttercommunity/flutter_webview_plugin/
所有发布的版本:https://github.com/fluttercommunity/flutter_webview_plugin/tags

在flutter中使用webview需要导入一个第三方插件:flutter_webview_plugin
(flutter_webview_plugin的介绍文档:https://pub.dartlang.org/packages/flutter_webview_plugin)

这个插件停更在2020年4月…不更新了

在app开发中经常需要到webview控件,但是flutter本身是没有webview相关控件的,因此需要插件来提供webview控件,flutter中比较常用的webview插件主要有2个,分别是:flutter_webview_plugin和webview_flutter。
flutter_webview_plugin:https://pub.dartlang.org/packages/flutter_webview_plugin
webview_flutter: https://pub.flutter-io.cn/packages/webview_flutter

flutter_webview_plugin插件的使用
使用方式很简单,按照文档 https://pub.dartlang.org/packages/flutter_webview_plugin 的说明一步步来就行了。

一、获得插件
首先,我们先在pubspec.yaml文件中添加依赖

dependencies:
flutter_webview_plugin: ^0.3.4
1
2
然后执行 flutter pub get 就会帮我们下载插件

二、在项目中引入
插件下载完成后就可以在项目中引入了

import ‘package:flutter_webview_plugin/flutter_webview_plugin.dart’;
1
三、代码中具体使用webview
解决
网上看了一大堆解决方式,有些参考吧…

阿里巴巴的flutter_boost项目的webview相关异常:flutter_webview_plugin异常 #653 看着比较靠谱,折腾了一通… 尝试使用 git log –grep=”webview” 和 git log –grep=”plugin”想查看能不能搜索到相关commit记录,git log –grep=”plugin”可以搜索到插件相关的修改,但查看代码发现好像最新版本的代码中又被改掉了?..本以为可以看下现成的解决方案的…

只好静下来自己分析异常…
仔细看下FlutterWebviewPlugin的源码:

/**
* FlutterWebviewPlugin
*/
public class FlutterWebviewPlugin implements MethodCallHandler, PluginRegistry.ActivityResultListener {
private Activity activity;
private WebviewManager webViewManager;
private Context context;
static MethodChannel channel;
private static final String CHANNEL_NAME = “flutter_webview_plugin”;
private static final String JS_CHANNEL_NAMES_FIELD = “JavaScriptChannelNames”;

public static void registerWith(PluginRegistry.Registrar registrar) {
if (registrar.activity() != null) {
channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
final FlutterWebviewPlugin instance = new FlutterWebviewPlugin(registrar.activity(), registrar.activeContext());
registrar.addActivityResultListener(instance);
channel.setMethodCallHandler(instance);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
好家伙… 必须要activity不为null时才进行注册插件,而项目中使用Flutter时并没有启动一个Activity,所以registerWith就不能成功注册插件了…
这种插件注册方式其实是flutter v1版本的插件注册方式,flutter v2版本的插件注册方式参看:https://flutter.dev/docs/development/packages-and-plugins/plugin-api-migration,其中说的非常清楚,

The new API has the advantage of providing a cleaner set of accessors for lifecycle dependent components compared to the old APIs. For instance PluginRegistry.Registrar.activity() could return null if Flutter isn’t attached to any activities.
1
new API在Flutter没有绑定到任何activity的情况下,PluginRegistry.Registrar.activity() 可以返回null。

然而flutter_webview_plugin插件停更在2020年4月…代码不更新了,还是使用旧的apiPluginRegistry.Registrar.activity()。

于是考虑用动态代理解决…

参考:

先stop app,再运行app:
Unhandled Exception: MissingPluginException(No implementation found for method launch on channel)

Flutter 解决 MissingPluginException(No implementation found for method xxx on channel xxx)

Unhandled Exception: MissingPluginException(No implementation found for method ****** on channel plugins.flutter.io/firebase_messaging) on IOS

MissingPluginException(No implementation found for method close on channel flutter_webview_plugin)

flutter_webview_plugin插件的使用
flutter插件推荐之 flutter_webview_plugin

Flutter应用开发之webview_flutter插件
在 Flutter 中使用 WebView

Unhandled Exception: MissingPluginException(No implementation found for method close on channel flutter_webview_plugin)

flutter和Android混编下出现“No implementation found for method xxxx on channel”错误

flutter_webview_plugin异常 #653

Flutter plugin registrar.context() and registrar.activity() always null

Javaweb学习(一)之动态代理(增强方法的返回值)
————————————————
版权声明:本文为CSDN博主「yzpyzp」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yzpbright/article/details/115758627

android - 您的 Flutter 应用程序是使用旧版本的 Android 嵌入创建的 - IT工具网

mikel阅读(601)

来源: android – 您的 Flutter 应用程序是使用旧版本的 Android 嵌入创建的 – IT工具网

最近我打开了我的旧项目,现在有一个警告说以前没有发生过这样的事情
警告看起来像

Warning
──────────────────────────────────────────────────────────────────────────────
Your Flutter application is created using an older version of the Android
embedding. It's being deprecated in favor of Android embedding v2. Follow the
steps at

https://flutter.dev/go/android-project-migration

to migrate your project.

flutter doctor -v 摘要

[] Flutter (Channel stable, 1.22.2, on Mac OS X 10.15.3 19D76, locale en-GB)
     Flutter version 1.22.2 at /Users/pkimac/Development/flutter
     Framework revision 84f3d28555 (6 weeks ago), 2020-10-15 16:26:19 -0700
     Engine revision b8752bbfff
     Dart version 2.10.2

 
[] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
     Android SDK at /Users/pkimac/Library/Android/sdk
     Platform android-30, build-tools 29.0.3
     Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
     Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
     All Android licenses accepted.

[] Xcode - develop for iOS and macOS (Xcode 11.3.1)
     Xcode at /Applications/Xcode.app/Contents/Developer
     Xcode 11.3.1, Build version 11C504
     CocoaPods version 1.10.0.rc.1

[!] Android Studio (version 4.1)
     Android Studio at /Applications/Android Studio.app/Contents
     Flutter plugin not installed; this adds Flutter specific functionality.
     Dart plugin not installed; this adds Dart specific functionality.
     Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)

[] Connected device (1 available)
     iPhone 11 Pro (mobile)  7A52F1D0-79F7-471C-AA62-3C106114A1A9  ios  com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator)
    ! Error: Paresh’s iPhone has recently restarted. Xcode will continue when Paresh’s iPhone is unlocked. (code -14)

! Doctor found issues in 1 category.

这是什么意思,我该如何解决这个警告? 

最佳答案

如果您在 1.12 版之前创建了项目,则会出现此警告
为了更好地支持将 Flutter 添加到现有项目的执行环境,旧的 Android 平台端包装器在 io.flutter.app.FlutterActivity 托管 Flutter 运行时并且它们的关联类现在已弃用。新包装在 io.flutter.embedding.Android.FlutterActivity和相关的类现在取代它们。
您现有的完整 Flutter 项目不会立即受到影响,并且在可预见的 future 将继续像以前一样工作。
要迁移您的项目,请按照以下步骤操作:

  • 移除 MainActivity.java 的 body 或 MainActivity.kt并更改 FlutterActivity进口。新款FlutterActivity不再需要手动注册您的插件。现在它会在下层 FlutterEngine 时自动执行注册。被 build 。你的文件应该是这样的
    package com.appname.app
    
    import io.flutter.embedding.android.FlutterActivity
    
    class MainActivity: FlutterActivity() {
    
    }
    

    如果您的 MainActivity.java 中有现有的自定义平台 channel handler 代码或 MainActivity.kt然后将 onCreate 中代码的 channel 注册部分移动到 FlutterActivity 的 configureFlutterEngine 覆盖中子类和使用 flutterEngine.getDartExecutor().getBinaryMessenger()作为二进制信使而不是 getFlutterView() .

  • 打开 android/app/src/main/AndroidManifest.xml。
  • 从应用程序标签中删除对 FlutterApplication 的引用。你的文件应该是这样的
    以前的配置:

    <application
       android:name="io.flutter.app.FlutterApplication"
       >
       <!-- code omitted -->
    </application>
    

    新配置:

    <application
      >
      <!-- code omitted -->
    </application>
    
  • 更新启动画面行为(如果需要启动画面行为)。
    在 AndroidManifest.xml删除所有 <meta-data>带 key 的标签 android:name="io.flutter.app.android.SplashScreenUntilFirstFrame" .
  • 新增 <meta-data>标签下 <application> .
    <meta-data
      android:name="flutterEmbedding"
      android:value="2" />
    

    enter image description here

更多信息请参见:https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects#full-flutter-app-migration 

关于android – 您的 Flutter 应用程序是使用旧版本的 Android 嵌入创建的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65018588/

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

mikel阅读(728)

来源: 宝塔面板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阅读(732)

来源: 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阅读(559)

来源: 【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阅读(783)

来源: 解决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阅读(2214)

来源: 如何修复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阅读(523)

来源: 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阅读(628)

来源: 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阅读(699)

来源: 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