首页> 标签> Dart
"Dart"
共 660 条结果
全部 问答 文章 公开课 课程 电子书 技术圈 体验
Web前端工程师Flutter快速入门,大佬勿入!
跨端开发的前世今生大帅曾经是一名闪客。之所以提到这个,是因为我说Flash是曾经的Web1.0时代的跨端之王,应该没有人反对吧(我个人拿Flash做过网页,在当时还没有Adobe AIR的时候,我就拿.net + ActiveObjectX实现过桌面端),后来的ActionScript3.0更是ES4的唯一实现。不过那个时候还没有诞生移动互联网,直到乔帮主给Flash下了驱逐令。跨端真正变成一种开发方式,要从移动互联时代开始说起1.基于Webview的跨端模式就是使用网页浏览容器(Webview)来呈现html页面,配合Webview和原生系统的通讯api,可以调用原生方法。优点是开发效率高,成本最低,缺点是Webview的内存开销较高,启动白屏问题以及没有原生的页面跳转体验等(这个问题后来的引擎通过webview多开都解决了)。2.使用JS+受限的HTML和CSS的Native渲染模式为了解决Webview跨端的问题,另一种新的方案被提出来。代码仍然使用Javascript编写,运行在独立的JS内核(如JSCore,V8)中,使用受限的HTML,受限的CSS来编写界面,但最终会使用原生的UI框架来渲染(如UIKit)。Facebook旗下的RN,Apache基金会的Weex都属于这种方式。3.Hybrid渲染模式注意,不是混合开发,而是指混合渲染。是在一个页面里既使用Webview来渲染又使用原生的UI来渲染。比如微信小程序第2,3种方式都有一个问题,就是JS代码是运行在独立的JS内核中,和视图层的渲染不在一个线程里,那逻辑层和视图层里的数据要互通,会有通讯折损,这个时间差大部分时候我们感受不到。但我们去尝试做一个跟手的交互效果,就能明显感受到。为了解决这个问题,在RN/Weex里我们有了BindingX方案,微信小程序里有了WXS方案。自渲染的FlutterFlutter是自己“独创”的一套渲染模式,即不使用webview来渲染,也不使用原生UI来渲染,而是自己实现了一套声明式的UI体系,然后在画布里画出来。这套东西并不新鲜十几年前的Flash也是“自渲染”呀,当年就很多“传统工程师”反映在Flash里做UI太麻烦了,后来有了第三方的UI框架Aswing等,再后来有了Flex。各种知名的游戏引擎也是如此,比如Unity3D,Unreal。游戏里不也有各种UI吗?它们的跨端表现不也是一致的吗?游戏里要绘制那么多图形,浮点运算,3D渲染....效率不也挺高的吗?所以,别被这些新词搞蒙了,“自渲染”就是自己有一套规范约束声明UI,然后自己在一个独立的“画布”里把它们绘制出来。这个画布在游戏引擎里叫OpenGL,WebGL....在Flutter里,它叫SGL(SkiaGraphicsLibrary)。而Skia本身就很强大,Chrome浏览器,Android系统的底层绘制库都是它。Dart语言对于Web前端开发者而言,Dart绝对不是把你拦在门外的因素,毕竟Dart语言也是ECMA的标准。但Dart在提供一些新特性的同时,也有其特立独行的一面,这些差异会增加Web前端开发者的学习和记忆成本。举个简单的例子,获取一个0.0到1.0之间的随机浮点值//JS的随机浮点值 Math.random(); //Dart的随机浮点值 Random().nextDouble();//JS的数组 var list = []; list.push(1); list.push(2,3,4); //Dart的数组 var list = []; list.add(1); list.addAll([2,3,4]);像这样的差异还有挺多,如果你的工作需要在两种语言间反复切换,那就换个工作吧~~ 哈哈:p!开个玩笑,但你应该感受到了什么叫做记忆成本的增加。安装Flutter开发环境这里就不废话了,请查看 安装教程 。开发环境支持Win/Mac/Linux。不安装开发环境,我们也能在网页里 dartpad.cn 来编写调试感受一下Dart和Flutter的魅力IDE选择VSCode安装插件:Flutter,Dart,Bracket Pair Colorizer,Awesome Flutter SnippetsAndroid Studio无论是是否使用Android Studio来编写Dart代码,Android Studio都是Flutter开发环境安装的必选项。我平时属于经常在多个前端语言里切换的,主要使用的IDE就是VSCode,所以我基本不使用Android Studio。但我在看了很多其他大佬的视频后发现,Android Studio更配Flutter,有一些特性VSCode里暂时还没有插件可以实现。嵌套地狱?Flutter是一种声明式UI框架,每个组件,会有个build函数,这里会返回一个能够完整描述UI的对象结构。而Flutter的嵌套地狱问题,主要就来自这个声明式UI的特性。我们可以用拆解嵌套来解决嵌套过深的问题,但嵌套的究竟是什么?来,让我换一个说法给你介绍声明式UI 就是 虚拟Dom万物皆Widgets!像 万物皆Widgets ,一切皆组件 这样的话很多文章都说过了。我提一杯,不是,提一句子不教,父之过当儿子没有这个能力的时候,找他爹!他爹没有这个能力的时候,找他爷爷!他爹没有这个能力的时候,是不是可以换个爹!我把官方文档中列举的所有Widgets做成一个脑图微信搜索 大帅老猿 关注后回复 flutter 即可下载Flutter项目基本结构创建项目flutter create projectname配置文件pubspec.yaml这个文件就等于我们熟悉的packages.json配置依赖dependencies: flutter: sdk: flutter flame: ^1.0.0-rc5 dev_dependencies: flutter_test: sdk: flutter配置图片和字体assets: - assets/images/ - assets/images/xxx.jpg fonts: - family: MyFont fonts: - asset: assets/fonts/myfont.ttfpubspec.yaml的配置采用缩进式结构体,在配置时要注意缩进的规则。入口代码lib/main.dart你所有的代码都应该在lib这个文件夹下,你可以根据自己的习惯来组织你的代码。项目内常用命令查看当前可run的设备列表flutter devices将Flutter项目运行到设备列表中的默认项flutter run将Flutter项目运行到设备列中的指定设备flutter run -d macos入门demo之点不到的按钮界面里有一个按钮,点击后就随机改变坐标double left = 100;//x坐标 double top = 100;//y坐标 //爷爷是Stack,爸爸是Positioned,儿子是ElevatedButton Stack( children:[ Positioned( left:left, //x坐标 top:top, //y坐标 child:ElevatedButton( child:Text("点我呀"), onPressed:(){ setState((){ left = Random().nextDouble()*300; top = Random().nextDouble()*300; }) } ) ) ] )加入动画位置改变的时候加入动画效果?So easy~Flutter的Widget封装得太好了,记得我们前面说的。儿子不行找爸爸爸爸不行,换一个爸爸我们只需要把Positioned换成AnimatedPositioned,然后设置一下动画持续时长Stack( children:[ AnimatedPositioned( duration:Duration(millsecond:500),//动画持续500毫秒 left:left, //x坐标 top:top, //y坐标 child:ElevatedButton( child:Text("点我呀"), onPressed:(){ setState((){ left = Random().nextDouble()*300; top = Random().nextDouble()*300; }) } ) ) ] )Flutter里的路由使用Navigator推入新页面Navigator.push(context, MaterialPageRoute(builder: (BuildContext context){ return PageDetail(); }));退出当前页Navigator.pop(context);动画之神奇移动?两个路由页之间相同元素的动画怎么做?比如A页面是个通讯录列表页,B页面是点击之后的联系人详情页。A页面的头像和昵称要以动画的方式移动到B页面里的状态。好好感受一下这个动画,它在两个页面之间在其它框架里,要实现这个动画效果,要么你创建一个独立于两个页面的顶级元素,在A页面的动画开始时隐藏元素,B页面先隐藏元素推入,在动画结束后再将其显示出来。要么把两个页面整合到一个页面里。而在Flutter里,我们只需要用Hero动画将目标动画元素包裹起来,即可直接实现。//A页面 Hero( tag: "logo", child: FlutterLogo( size: 64, )//B页面 Hero( tag: "logo", child: FlutterLogo( size: 128, )就是这么简单!结束语简简单单的一个入门导读,并没有特别系统的介绍到Flutter的方方面面,但希望能让更多的Web前端工程师们对Flutter产生兴趣,加入进来吧!
文章
移动开发  ·  Dart  ·  前端开发  ·  JavaScript  ·  IDE  ·  小程序  ·  weex  ·  开发工具  ·  Android开发  ·  内存技术
2022-04-25
超多图手摸手,这次一定帮你搞定Win10下的Flutter开发环境 (二)
4:在Android Studio中安装Flutter插件完成上一步后会再次重新打开Android Studio,此时我们点击右下角的Configure选择Plugins然后搜索flutter,这里可能会比较慢,耐心等待吧。我在没有搭梯子的情况下,等了一会儿也出来了。选择Install后会弹出提示说一堆废话,直接Accept然后还会提示你这个Flutter插件依赖另一个Dart插件,当然是选Yes。然后就会进入下载过程,等待下载完成后,会提示我们重新启动Android Studio。我们也可以进入Installed(已安装)标签页确认一下点击Restart IDE5:Android模拟器此时我们回到 1 ,双击运行C:\flutter里的flutter_console.bat输入flutter doctor,我们会看到Android Studio前的惊叹号已经变成了✔️。但是仍然出现了一个×。我们根据提示,输入如下命令flutter doctor --android-licenses在这种界面里,通通输入 y 然后回车。一共是7个,数量不用在意,总之全部y直到我们看到All SDK package licenses accepted我们再次输入flutter doctor此时我们可以看到,flutter doctor需要验证的必备项已全部✔️(那个No Device Available暂时不用理会),不过此时flutter命令只能通过双击flutter_console.bat来运行。6:设置环境变量要随时打开命令提示符都可以使用flutter命令的话,我们就需要设置环境变量。打开控制面板,进入用户账户\用户账户点击更改我的环境变量输入C:\flutter\bin然后回车,点确定,关掉控制面板。此时我们再任意打开命令提示符,都可以使用flutter相关命令了7:安装VSCode以及Flutter插件安装VSCode没啥好说的,一路next,安装完成后打开VSCode,点击插件(扩展)面板搜索Flutter并安装,同样的,安装好Flutter插件时会自动帮我们安装Dart插件。
文章
Dart  ·  IDE  ·  开发工具  ·  Android开发
2022-04-25
在 Flutter 中使用iconfont...附一键生成Dart类的技巧
前言对于前端工程师来说,在项目中使用iconfont太习以为常了。而在Flutter中,内置了Icon组件,所有图标都来自MaterialDesign Icons,数量众多,完全是够用的。可我们在实际开发中还是会要使用到自定义图标,那如何在Flutter项目中使用自定义的IconFont,这就是本文要教给大家的。声明自定义字体前往 www.iconfont.cn/ 挑选图标,并添加至购物车(莫慌,是免费的)。然后选择添加至项目从我的项目中进入该项目,并选择下载至本地将下载好的zip包解压缩,复制其中的iconfont.ttf文件至你的flutter项目中,比如flutter项目名称/assets/fonts/内在Flutter中要使用自定义字体,我们需要在pubspec.yaml文件中添加以下内容fonts: - family: IconFont fonts: - asset: assets/fonts/iconfont.ttf请注意这个结构,不要写错了。通常情况下,在pubspec.yaml中有相关的注释,按照注释的结构写即可。使用IconData现在我们已经可以正常使用自定义的iconfont了,用法如下Icon( IconData(0xe7b4, fontFamily: 'IconFont'), size: 20, color: Colors.black )其中fontFamily的值'IconFont'就是我们刚才在pubspec.yaml中声明的新字体,但是代码中的0xe7b4是指什么呢?回到之前下载解压zip包的文件夹,双击demo_index.html文件在浏览器中打开后,我们可以看到下面的画面每个图标下面都标记出了这个图标对应的unicode编码,所以我们想用哪个图标,只要copy它的unicode编码到代码里就行了自定义Icon类可如果我们有多个Icon,使用unicode非常不友好...读到代码的时候根本不知道是什么吧!所以我们需要把这个东西变得更优雅一些。把它们统统装进一个dart类集中管理,并通过图标的名称来使用。class IconFontIcons { static const IconData iconRotateRight = IconData(0xe9b3,fontFamily:'IconFont'); static const IconData iconHeartFill = IconData(0xe8b3,fontFamily:'IconFont'); static const IconData iconMinusSquare = IconData(0xe7b3,fontFamily:'IconFont'); ...再使用的话,就非常简单直观了Icon(IconFontIcons.iconRotateRight)总结在Flutter中使用iconfont简单极了,希望本文有帮到你...对了,自动将iconfont项目生成.dart类的工具也为你准备好了请继续往下看一键生成IconFont类使用方法1、复制以下代码并添加到收藏javascript:function download(filename, text) { var element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element);}function toHump(name) {name = name.replace(/\s+/g,"-"); return name.replace(/\-(\w)/g, function(all, letter){ return letter.toUpperCase(); });}function getFlutterClass(){var str = "import 'package:flutter/widgets.dart';\r\n\r\n";str += "class IconFontIcons {\r\n";var arr = document.querySelectorAll(".icon-item");for (var i = arr.length - 1; i >= 0; i--) {var item = arr[i];var item_name = toHump(item.querySelectorAll(".icon-code")[1].textContent);var item_code = item.querySelectorAll(".icon-code")[0].textContent.replace(/\&\#/g,"0");item_code = item_code.replace(/\;/g,"");str += " static const IconData "+item_name+" = IconData("+item_code+",fontFamily:'IconFontIcons');";str += "\r\n";}str += "}";return str;} download("IconFontIcons.dart",getFlutterClass());Chrome浏览器:在收藏栏点击鼠标右键,选择添加网页将名称改为“生成IconFont.dart”,复制上面的代码并粘贴在网址里2、打开 www.iconfont.cn/3、进入到要生成IconFont类的项目4、点击第1步在收藏夹中添加的网址点击后就会自动生成并下载一个IconFont.dart类啦话说Chrome会怀疑自家.dart文件可能对计算机造成危害...记得点保留哦
文章
Web App开发  ·  Dart  ·  前端开发
2022-04-25
Flutter教程开坑篇,由简至难教你胡辣汤的31种做法...
写在开坑之前18年前我是一名设计师,做FLASH动画的。当时有一个FLASH编程的入门课程对我影响很大,直接导致了我后来“沦落为”一名程序员。那个入门课程作者叫ayychina,第一课叫《点不到的按钮》。在这个课程里,我掌握了入门任何新UI框架的武功秘籍,记得那会儿还用3.5寸软盘给我“梦情”做了个点不到的爱心...技术肥宅的浪漫真是很难get到吧...那个年代的FLASH可以做出很多酷炫的UI,是当时首屈一指的“跨端”技术,当然那会儿的还没有“跨端”这个概念,我们都被叫做多媒体设计师....直到FLASH犯下几个致命的错误,又被乔帮主判处死刑...无论什么框架,咱前端用起来不外乎就做几件事1. 创建显示对象,比如button,text,image等等... 2. 弄明白显示对象的层级关系...容器,层级 3. 设置显示对象的属性,宽高大小颜色等等... 4. 显示对象的事件系统,click,mousemove等等... 5. 绘制/渲染系统...重绘机制等等...在开始之前,我们先来了解下Flutter的架构和渲染机制可以看到Flutter的架构主要分为三层:Framework,Engine和Embedder。1.Framework使用dart实现,包括Material Design风格的Widget,Cupertino(针对iOS)风格的Widgets,文本/图片/按钮等基础Widgets,渲染,动画,手势等。此部分的核心代码是:flutter仓库下的flutter package,以及sky_engine仓库下的io,async,ui(dart:ui库提供了Flutter框架和引擎之间的接口)等package。2.Engine使用C++实现,主要包括:Skia,Dart和Text。Skia是开源的二维图形库,提供了适用于多种软硬件平台的通用API。3.Embedder是一个嵌入层,即把Flutter嵌入到各个平台上去,这里做的主要工作包括渲染Surface设置,线程设置,以及插件等。从这里可以看出,Flutter的平台相关层很低,平台(如iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在Flutter内部,这就使得它具有了很好的跨端一致性。从网上摘了一段,也是Flutter标志自己完全不同于RN类框架或其他Hybrid跨端框架的核心要素我试着用一句话讲明白flutter为什么可以跨端+高性能的原理这货就是个跨端“游戏”引擎呀(打了引号)一块画布(SGL),使用Dart语言编写Framework制定的API,由Framework调用Engine层实现绘制,Embedder层负责跨端运行和渲染。再想想那些能跨平台的游戏引擎unreal,unity3d,你想想是不是都是这么回事呀?创建一个Flutter应用原理就聊到这里,回到我们的课程,快速的让大家先来上个手《点不到的按钮》安装Flutter看这里:安装Flutterflutter create myapp“myapp”是你应用的名称完成后用vscode打开这个目录先搞清楚在哪里写代码lib/main.dart就是咱们新建的这个应用的入口代码先不说别的,run一下(按F5),第一次很慢,之后会快很多你可以选择使用苹果模拟器或是安卓模拟器来调试随便改改代码试试看调试模式下的热重载比如找到这一行代码 primarySwatch: Colors.blue, 改为 primarySwatch: Colors.red, 保存这次不需要经过编译,模拟器里的UI就实时改变了。所以热重载是一个非常高效的调试方式。(这样的特性还用跟Web前端开发者说嘛?是不是太out了)回到我们的课程案例上,需求是在界面里放置一个按钮,按钮上文字显示”点我呀“,点击之后按钮就跑到另外一个坐标。这就是点不到的按钮...其实原本课程里是mouseover后移动到另一个坐标,但移动端没有mouseover这个事儿啊...需求拆解:step1在放按钮之前,我们需要一个承载按钮的布局容器这个按钮点了之后要随机变换位置,所以肯定得用自由布局的组件查看所有布局组件总之看完所有布局组件的文档,我们知道要用到这个组合 Stack,一个可以随意堆放子元素的布局 Positioned,一个绝对定位组件,改变top,left参数就可以跑来跑去啦step2我们要在这个容器里放一个按钮,按钮上文字显示”点我呀“RaisedButton( onPressed: (){ }, color: Colors.red,//红色背景 child:Text( 'click me',//文本内容 style: TextStyle( color: Colors.white,//文字白色 fontSize: 12//文字字号 ) ) )step3按钮点击之后要更改其坐标在实现这个之前,我们先要了解,在Flutter中万物皆Widget,而Widget又分无状态组件(StatelessWidget)和有状态组件(StatefulWidget)。简单点说,组件在创建后,是否允许更改状态,一但状态更改,其UI就会重新渲染。这里显然我们要用有状态组件,接下来的代码是在组件创建时,就给与按钮一个随机屏幕坐标,那么之后只要改变状态,就会重新渲染RaisedButton( onPressed: (){ setState((){});//关键是这一句 }, color: Colors.red, child:Text( 'click me', style: TextStyle( color: Colors.white, fontSize: 12 ) ) )//获得当前设备屏幕尺寸,需要import 'dart:ui' var s = window.physicalSize/window.devicePixelRatio; //新建一个随机对象,import 'dart:math'; var rng = new Random(); //随机生成按钮的坐标 double btnTop = rng.nextDouble()*(s.height-40); double btnLeft = rng.nextDouble()*(s.width-100); return Container( color: Colors.white, child:Stack( children: [ Positioned( top: btnTop, left: btnLeft, width: 100, height: 40, ) ) ] ) );那么本课关键代码就齐全了
文章
Dart  ·  前端开发  ·  程序员  ·  API  ·  Android开发  ·  图形学  ·  iOS开发  ·  C++  ·  内存技术  ·  容器
2022-04-25
react Native 环境安装配置——图解版一目了然
✨原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下}原创不易,还希望各位大佬支持一下🔥 F l u t t e r 和 r e a c t N a t i v e 的 区 别 \textcolor{green}{Flutter和react Native的区别}Flutter和reactNative的区别🔥 r e a c t N a t i v e 的 环 境 安 装 \textcolor{green}{react Native的环境安装}reactNative的环境安装🔥 N o d e 、 J D K 、 A n d r o i d S t u d i o \textcolor{green}{Node、JDK、Android Studio}Node、JDK、AndroidStudio前言利用一点时间去关注了最近比较火爆的两款APP开发语言,分别是Flutte rn(React Native)除此之外小编还总结过一篇关于开发app各语言之间的差距如果感兴趣可以看一下 点击访问 里面涵盖 原生app、uni-app、react Native、Flutte、wap2app、Sass app等技术的利弊分析Flutte 还是 react Native?至于大家如何去选择呢,网络上很多对两者的对比,也比较全面了,下面就是一作者在文章中总结的对比图表嘻嘻 看到里面的第一项对比我就有了答案,react Native 是基于javaScript,作为前端的我当然会一如反顾的选择他,而且更有分量的是rn基于强大的父亲react,在这里大家可以自己去根据自己的爱好、更多方面的对比之后选择自己适合的一款语言。单从环境方面,无论是 React Native 还是 Flutter ,都需要 Android 和 IOS 的开发环境,也就是 JDK 、Android SDK、Xcode 等环境配置,而不同点在于 :React Native 需要 npm 、node 、react-native-cli 等配置 。Flutter 需要 flutter sdk 和 Android Studio / VSCode 上的 Dart 与 Flutter 插件。从配置环境上看, Flutter 的环境搭配相对简单,而 React Native 的环境配置相对复杂,而且由于 node_module 的“黑洞”属性和依赖复杂度等原因,目前在个人接触的例子中,首次配置运行成功率 Flutter 是高于 React Native 的,且 Flutter 失败的原因则大多归咎于网络。还有更多方面的对比 如:实现原理、编程开发、插件开发、编译和产物、性能、发展未来等几大方面去分析两者的区别,大家可以参考这篇文章 点击访问小编已经是被react Native吸引了,所以下面呢小编仅仅为大家带来的就是rn的环境安装配置,当时自己看的时候真的是头疼呢,所以总结下来给大家分享!React Native开发环境搭建小编参考的是官网的这篇搭建开发环境依赖安装这里可以看到必须安装的以来有 Node、JDK 和 Android Studio。Node这个对于前端人来说并不陌生,小编之前专门编写过一篇Node环境配置的文章大家可以根据文章步骤走就没有问题 点击进入JDK关于JDK呢 看到官方大大也已经是提供了链接我们可以去官网下载,但是会出现这个问题点击下载还得需要Orcle的账号登陆才可以,但是不要慌福利安排上,大家访问这个地址就可以了 点击进入在这里我们可以直接下载的下载下来后 我放到了自己喜欢的目录下然后下面就是JDK环境变量的配置了(为了大家能够看懂我直接图解)需要我们配置的有一下几项变量名:JAVA_HOME变量值:C:\Program Files (x86)\Java\jdk1.8.0_91 // 要根据自己的实际路径配置变量名:CLASSPATH变量值:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar; //记得前面有个"."变量名:Path(这是在Path中编辑去新增的)变量值:%JAVA_HOME%\bin;变量值:%JAVA_HOME%\jre\bin;需要填写的就是下面这两项(共新增两个)变量名:JAVA_HOME变量值:C:\Program Files (x86)\Java\jdk1.8.0_91 // 要根据自己的实际路径配置变量名:CLASSPATH变量值:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar; //记得前面有个"."配置成功后我们就有了后面我们在Path中新增变量值:%JAVA_HOME%\bin;变量值:%JAVA_HOME%\jre\bin;添加成功后我们确认 然后测试一下JDK是否安装成功打开cmd输入 java -version成功Android Studio我们访问这个地址直接下载就好 点击进入下载好我们直接安装文档上要我们确保选中这几项然后安装这些组件,但是我并没有发现这几项 然后是后面自己安装的,自己安装是这样的搜索Android SDK按照上面的寻找到对应的插件安装就好了配置 ANDROID_HOME 环境变量跟上面的JDK环境变量是一样的3. 配置 ANDROID_HOME 环境变量#React Native 需要通过环境变量来了解你的 Android SDK 装在什么路径,从而正常进行编译。打开控制面板 -> 系统和安全 -> 系统 -> 高级系统设置 -> 高级 -> 环境变量 -> 新建,创建一个名为ANDROID_HOME的环境变量(系统或用户变量均可),指向你的 Android SDK 所在的目录(具体的路径可能和下图不一致,请自行确认):ANDROID_HOME Environment VariableSDK 默认是安装在下面的目录:C:\Users\你的用户名\AppData\Local\Android\Sdk你可以在 Android Studio 的"Preferences"菜单中查看 SDK 的真实路径,具体是Appearance & Behavior → System Settings → Android SDK。你需要关闭现有的命令符提示窗口然后重新打开,这样新的环境变量才能生效。把一些工具目录添加到环境变量 Path#打开控制面板 -> 系统和安全 -> 系统 -> 高级系统设置 -> 高级 -> 环境变量,选中Path变量,然后点击编辑。点击新建然后把这些工具目录路径添加进去:platform-tools、emulator、tools、tools/bin%ANDROID_HOME%\platform-tools%ANDROID_HOME%\emulator%ANDROID_HOME%\tools%ANDROID_HOME%\tools\bin配置完成之后我们就可以创建新的项目了react Native启航在这里我使用的时真机调试启动项目yarn react-native run-android启动后他会弹起来一个终端(不要关闭)然后手机上也就跑起来我们的项目了✨原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下}原创不易,还希望各位大佬支持一下👍 点 赞 , 你 的 认 可 是 我 创 作 的 动 力 ! \textcolor{green}{点赞,你的认可是我创作的动力!}点赞,你的认可是我创作的动力!⭐️ 收 藏 , 你 的 青 睐 是 我 努 力 的 方 向 ! \textcolor{green}{收藏,你的青睐是我努力的方向!}收藏,你的青睐是我努力的方向!✏️ 评 论 , 你 的 意 见 是 我 进 步 的 财 富 ! \textcolor{green}{评论,你的意见是我进步的财富!}评论,你的意见是我进步的财富!
文章
移动开发  ·  资源调度  ·  Dart  ·  前端开发  ·  安全  ·  JavaScript  ·  Java  ·  开发工具  ·  Android开发  ·  iOS开发
2022-04-21
Markdown实用小技巧: 个个用着都很爽
文本字体强调Markdown预览*倾斜*倾斜**粗体**粗体~~删除线~~删除线的 ~ 符号一般位于键盘左上角位置。删除线> 引用引用字体,字号和颜色: <font face= 黑体 color=red size=3>红色z</font>红色锚点语法:需要跳转的地方:<a name="divtop"> 我是目标位置 </a> 定义锚记:[跳转指定位置](#divtop)PS:中括号里面是锚记文本,#divtop, 这个divtop 是页面内超链接的ID在需要跳转的地方加一个a标记,并且将它的ID设置成 锚记中定义的超链接即可。Typora中写完之后,鼠标直接点击锚记是没有反应的,需要按住Ctrl键再点击才会有效。图片图片下面显示一行文字标准md语法是![alt](address "title")alt是图片未成功加载的提示文字,title是悬浮显示的额外文字对于title文字,大部分编辑器(vscode、typora)使用这种语法,只支持悬浮图片显示标题,不支持在图片底下直接显示标题目前我就只发现思源笔记支持以这种语法添加居中的标题在typora中需要用添加center标签的方式。可以在图片下面加一行html,凑合着还行<center style="font-size:14px;color:#C0C0C0;text-decoration:underline">图1</center>背景色:Markdown本身不支持背景色设置,需要采用内置html的方式实现:借助 table, tr, td 等表格标签的 bgcolor 属性来实现背景色的功能。举例如下:<table><tr><td bgcolor=gray>背景色是:gray</td></tr></table>示例结果:背景色是: gray代码1.行内代码行内代码可以使用反引号来标记(反引号一般位于键盘左上角,要用英文):一句话 `行内代码` 一句话。预览:一句话 行内代码 一句话。2.多行代码多行代码使用 3 个反引号来标记(反引号一般位于键盘左上角,要用英文) ,在第一个 ``` 后面可以跟语言类型,没有语言类型可以省略不写:​``` java // 我是注释 int a = 5; ​```预览:// 我是注释 int a = 5;表格扩展的 Markdown 支持手写表格,格式也非常简单,第二行分割线部分可以使用 : 来控制内容状态。注意,Markdown 标准(原生)语法中没有表格支持,但现在多数平台已经支持了该语法,如 GitHub,CSDN,简书 等均支持,所以写在这里:Markdown:| 默认 | 靠右 | 居中 | 靠左 | | ---- | ---: | :--: | :--- | | 内容 | 内容 | 内容 | 内容 | | 内容 | 内容 | 条目 | 内容 |预览:默认靠右居中靠左内容内容内容内容内容内容条目内容表格中单元格的合并合并单元格还是要与HTML网页结合起来,来达到效果。这会用到HTML的标签:colspan:规定单元格可纵深的列数rowspan:规定单元格可横跨的行数<table> <tr> <td>类别</td> <td>名称</td> </tr> <tr> <td rowspan="2">颜色</td> <td>红色</td> </tr> <tr> <td>黄色</td> </tr> <tr> <td colspan="2">姓氏</td> </tr> <tr> <td>王</td> <td>张</td> </tr> </table> 效果图: 要想MarkDown中插入复杂表格时,可以先在word或excel中把表格写好,然后在如下网站进行转化为标记对形式:No-Cruft Excel to HTML Table Converter使用LaTex 插入数学公式排版方式行级元素(inline),行级元素使用$...$,两个$表示公式的首尾。块级元素(displayed),块级元素使用$$...$$。块级元素默认是居中显示的。常用西文符号\alpha, \beta, …, \omega代表α,β,…ω. \Gamma, \Delta, …, \Omega代表Γ,Δ,…,Ω.上标与下标使用 ^和 _ 表示上标和下标. 例如,x_i^2 ,\log_2 x。 用{}来消除二义性——优先级问题。例如10^10和10^{10}区别,还有个例子,x_i^2和x_{i^2}的区别。括号小括号和中括号直接使用,大括号由于用来分组,所以需要转义。\{1+2\}:{1+2}运算分数:\frac{}{}。例如,\frac{1+1}{2}+1求和:\sum_1^n积分:\int_1^n极限:lim_{x \to \infty:limx→∞矩阵:$$\begin{matrix}…\end{matrix}$$,使用&分隔同行元素,\\换行 常用公式线性模型$$ h(\theta) = \sum_{j=0} ^n \theta_j x_j $$均方误差$$ J(\theta) = \frac{1}{2m}\sum_{i = 0} ^m(y^i - h_\theta (x^i))^2 $$ 生成目录[toc]推荐工具typoraTypora最常用快捷键插入图片:直接拖动到指定位置即可或者ctrl+shift+i插入链接:ctrl+k代码块:Ctrl+Shift+K公式块:Ctrl+Shift+M引用:Ctrl+Shift+Q有序列表:Ctrl+Shift+[无序列表:Ctrl+Shift+]增加缩进:Ctrl+]减少缩进:Ctrl+[表格:Ctrl+T放大:Ctrl+Shift+=缩小:Ctrl+Shift+-显示隐藏侧边栏:Ctrl+Shift+L大纲视图:Ctrl+Shift+1文档列表视图:Ctrl+Shift+2文件树视图:Ctrl+Shift+3Typora Emoji图标People😄 :smile:😆 :laughing:😊 :blush:😃 :smiley:☺️ :relaxed:😏 :smirk:😍 :heart_eyes:😘 :kissing_heart:😚 :kissing_closed_eyes:😳 :flushed:😌 :relieved:😆 :satisfied:😁 :grin:😉 :wink:😜 :stuck_out_tongue_winking_eye:😝 :stuck_out_tongue_closed_eyes:😀 :grinning:😗 :kissing:😙 :kissing_smiling_eyes:😛 :stuck_out_tongue:😴 :sleeping:😟 :worried:😦 :frowning:😧 :anguished:😮 :open_mouth:😬 :grimacing:😕 :confused:😯 :hushed:😑 :expressionless:😒 :unamused:😅 :sweat_smile:😓 :sweat:😥 :disappointed_relieved:😩 :weary:😔 :pensive:😞 :disappointed:😖 :confounded:😨 :fearful:😰 :cold_sweat:😣 :persevere:😢 :cry:😭 :sob:😂 :joy:😲 :astonished:😱 :scream:😫 :tired_face:😠 :angry:😡 :rage:😤 :triumph:😪 :sleepy:😋 :yum:😷 :mask:😎 :sunglasses:😵 :dizzy_face:👿 :imp:😈 :smiling_imp:😐 :neutral_face:😶 :no_mouth:😇 :innocent:👽 :alien:💛 :yellow_heart:💙 :blue_heart:💜 :purple_heart:❤️ :heart:💚 :green_heart:💔 :broken_heart:💓 :heartbeat:💗 :heartpulse:💕 :two_hearts:💞 :revolving_hearts:💘 :cupid:💖 :sparkling_heart:✨ :sparkles:⭐️ :star:🌟 :star2:💫 :dizzy:💥 :boom:💥 :collision:💢 :anger:❗️ :exclamation:❓ :question:❕ :grey_exclamation:❔ :grey_question:💤 :zzz:💨 :dash:💦 :sweat_drops:🎶 :notes:🎵 :musical_note:🔥 :fire:💩 :hankey:💩 :poop:💩 :shit:👍 :+1:👍 :thumbsup:👎 :-1:👎 :thumbsdown:👌 :ok_hand:👊 :punch:👊 :facepunch:✊ :fist:✌️ :v:👋 :wave:✋ :hand:✋ :raised_hand:👐 :open_hands:☝️ :point_up:👇 :point_down:👈 :point_left:👉 :point_right:🙌 :raised_hands:🙏 :pray:👆 :point_up_2:👏 :clap:💪 :muscle:🤘 :metal:🖕 :fu:🚶 :walking:🏃 :runner:🏃 :running:👫 :couple:👪 :family:👬 :two_men_holding_hands:👭 :two_women_holding_hands:💃 :dancer:👯 :dancers:🙆 :ok_woman:🙅 :no_good:💁 :information_desk_person:🙋 :raising_hand:👰 :bride_with_veil:🙎 :person_with_pouting_face:🙍 :person_frowning:🙇 :bow:💏 :couplekiss:💑 :couple_with_heart:💆 :massage:💇 :haircut:💅 :nail_care:👦 :boy:👧 :girl:👩 :woman:👨 :man:👶 :baby:👵 :older_woman:👴 :older_man:👱 :person_with_blond_hair:👲 :man_with_gua_pi_mao:👳 :man_with_turban:👷 :construction_worker:👮 :cop:👼 :angel:👸 :princess:😺 :smiley_cat:😸 :smile_cat:😻 :heart_eyes_cat:😽 :kissing_cat:😼 :smirk_cat:🙀 :scream_cat:😿 :crying_cat_face:😹 :joy_cat:😾 :pouting_cat:👹 :japanese_ogre:👺 :japanese_goblin:🙈 :see_no_evil:🙉 :hear_no_evil:🙊 :speak_no_evil:💂 :guardsman:💀 :skull:🐾 :feet:👄 :lips:💋 :kiss:💧 :droplet:👂 :ear:👀 :eyes:👃 :nose:👅 :tongue:💌 :love_letter:👤 :bust_in_silhouette:👥 :busts_in_silhouette:💬 :speech_balloon:💭 :thought_balloon:Nature☀️ :sunny:☔️ :umbrella:☁️ :cloud:❄️ :snowflake:⛄️ :snowman:⚡️ :zap:🌀 :cyclone:🌁 :foggy:🌊 :ocean:🐱 :cat:🐶 :dog:🐭 :mouse:🐹 :hamster:🐰 :rabbit:🐺 :wolf:🐸 :frog:🐯 :tiger:🐨 :koala:🐻 :bear:🐷 :pig:🐽 :pig_nose:🐮 :cow:🐗 :boar:🐵 :monkey_face:🐒 :monkey:🐴 :horse:🐎 :racehorse:🐫 :camel:🐑 :sheep:🐘 :elephant:🐼 :panda_face:🐍 :snake:🐦 :bird:🐤 :baby_chick:🐥 :hatched_chick:🐣 :hatching_chick:🐔 :chicken:🐧 :penguin:🐢 :turtle:🐛 :bug:🐝 :honeybee:🐜 :ant:🐞 :beetle:🐌 :snail:🐙 :octopus:🐠 :tropical_fish:🐟 :fish:🐳 :whale:🐋 :whale2:🐬 :dolphin:🐄 :cow2:🐏 :ram:🐀 :rat:🐃 :water_buffalo:🐅 :tiger2:🐇 :rabbit2:🐉 :dragon:🐐 :goat:🐓 :rooster:🐕 :dog2:🐖 :pig2:🐁 :mouse2:🐂 :ox:🐲 :dragon_face:🐡 :blowfish:🐊 :crocodile:🐪 :dromedary_camel:🐆 :leopard:🐈 :cat2:🐩 :poodle:🐾 :paw_prints:💐 :bouquet:🌸 :cherry_blossom:🌷 :tulip:🍀 :four_leaf_clover:🌹 :rose:🌻 :sunflower:🌺 :hibiscus:🍁 :maple_leaf:🍃 :leaves:🍂 :fallen_leaf:🌿 :herb:🍄 :mushroom:🌵 :cactus:🌴 :palm_tree:🌲 :evergreen_tree:🌳 :deciduous_tree:🌰 :chestnut:🌱 :seedling:🌼 :blossom:🌾 :ear_of_rice:🐚 :shell:🌐 :globe_with_meridians:🌞 :sun_with_face:🌝 :full_moon_with_face:🌚 :new_moon_with_face:🌑 :new_moon:🌒 :waxing_crescent_moon:🌓 :first_quarter_moon:🌔 :waxing_gibbous_moon:🌕 :full_moon:🌖 :waning_gibbous_moon:🌗 :last_quarter_moon:🌘 :waning_crescent_moon:🌜 :last_quarter_moon_with_face:🌛 :first_quarter_moon_with_face:🌔 :moon:🌍 :earth_africa:🌎 :earth_americas:🌏 :earth_asia:🌋 :volcano:🌌 :milky_way:⛅️ :partly_sunny:Objects🎍 :bamboo:💝 :gift_heart:🎎 :dolls:🎒 :school_satchel:🎓 :mortar_board:🎏 :flags:🎆 :fireworks:🎇 :sparkler:🎐 :wind_chime:🎑 :rice_scene:🎃 :jack_o_lantern:👻 :ghost:🎅 :santa:🎄 :christmas_tree:🎁 :gift:🔔 :bell:🔕 :no_bell:🎋 :tanabata_tree:🎉 :tada:🎊 :confetti_ball:🎈 :balloon:🔮 :crystal_ball:💿 :cd:📀 :dvd:💾 :floppy_disk:📷 :camera:📹 :video_camera:🎥 :movie_camera:💻 :computer:📺 :tv:📱 :iphone:☎️ :phone:☎️ :telephone:📞 :telephone_receiver:📟 :pager:📠 :fax:💽 :minidisc:📼 :vhs:🔉 :sound:🔈 :speaker:🔇 :mute:📢 :loudspeaker:📣 :mega:⌛️ :hourglass:⏳ :hourglass_flowing_sand:⏰ :alarm_clock:⌚️ :watch:📻 :radio:📡 :satellite:➿ :loop:🔍 :mag:🔎 :mag_right:🔓 :unlock:🔒 :lock:🔏 :lock_with_ink_pen:🔐 :closed_lock_with_key:🔑 :key:💡 :bulb:🔦 :flashlight:🔆 :high_brightness:🔅 :low_brightness:🔌 :electric_plug:🔋 :battery:📲 :calling:✉️ :email:📫 :mailbox:📮 :postbox:🛀 :bath:🛁 :bathtub:🚿 :shower:🚽 :toilet:🔧 :wrench:🔩 :nut_and_bolt:🔨 :hammer:💺 :seat:💰 :moneybag:💴 :yen:💵 :dollar:💷 :pound:💶 :euro:💳 :credit_card:💸 :money_with_wings:📧 :e-mail:📥 :inbox_tray:📤 :outbox_tray:✉️ :envelope:📨 :incoming_envelope:📯 :postal_horn:📪 :mailbox_closed:📬 :mailbox_with_mail:📭 :mailbox_with_no_mail:🚪 :door:🚬 :smoking:💣 :bomb:🔫 :gun:🔪 :hocho:💊 :pill:💉 :syringe:📄 :page_facing_up:📃 :page_with_curl:📑 :bookmark_tabs:📊 :bar_chart:📈 :chart_with_upwards_trend:📉 :chart_with_downwards_trend:📜 :scroll:📋 :clipboard:📆 :calendar:📅 :date:📇 :card_index:📁 :file_folder:📂 :open_file_folder:✂️ :scissors:📌 :pushpin:📎 :paperclip:✒️ :black_nib:✏️ :pencil2:📏 :straight_ruler:📐 :triangular_ruler:📕 :closed_book:📗 :green_book:📘 :blue_book:📙 :orange_book:📓 :notebook:📔 :notebook_with_decorative_cover:📒 :ledger:📚 :books:🔖 :bookmark:📛 :name_badge:🔬 :microscope:🔭 :telescope:📰 :newspaper:🏈 :football:🏀 :basketball:⚽️ :soccer:⚾️ :baseball:🎾 :tennis:🎱 :8ball:🏉 :rugby_football:🎳 :bowling:⛳️ :golf:🚵 :mountain_bicyclist:🚴 :bicyclist:🏇 :horse_racing:🏂 :snowboarder:🏊 :swimmer:🏄 :surfer:🎿 :ski:♠️ :spades:♥️ :hearts:♣️ :clubs:♦️ :diamonds:💎 :gem:💍 :ring:🏆 :trophy:🎼 :musical_score:🎹 :musical_keyboard:🎻 :violin:👾 :space_invader:🎮 :video_game:🃏 :black_joker:🎴 :flower_playing_cards:🎲 :game_die:🎯 :dart:🀄️ :mahjong:🎬 :clapper:📝 :memo:📝 :pencil:📖 :book:🎨 :art:🎤 :microphone:🎧 :headphones:🎺 :trumpet:🎷 :saxophone:🎸 :guitar:👞 :shoe:👡 :sandal:👠 :high_heel:💄 :lipstick:👢 :boot:👕 :shirt:👕 :tshirt:👔 :necktie:👚 :womans_clothes:👗 :dress:🎽 :running_shirt_with_sash:👖 :jeans:👘 :kimono:👙 :bikini:🎀 :ribbon:🎩 :tophat:👑 :crown:👒 :womans_hat:👞 :mans_shoe:🌂 :closed_umbrella:💼 :briefcase:👜 :handbag:👝 :pouch:👛 :purse:👓 :eyeglasses:🎣 :fishing_pole_and_fish:☕️ :coffee:🍵 :tea:🍶 :sake:🍼 :baby_bottle:🍺 :beer:🍻 :beers:🍸 :cocktail:🍹 :tropical_drink:🍷 :wine_glass:🍴 :fork_and_knife:🍕 :pizza:🍔 :hamburger:🍟 :fries:🍗 :poultry_leg:🍖 :meat_on_bone:🍝 :spaghetti:🍛 :curry:🍤 :fried_shrimp:🍱 :bento:🍣 :sushi:🍥 :fish_cake:🍙 :rice_ball:🍘 :rice_cracker:🍚 :rice:🍜 :ramen:🍲 :stew:🍢 :oden:🍡 :dango:🥚 :egg:🍞 :bread:🍩 :doughnut:🍮 :custard:🍦 :icecream:🍨 :ice_cream:🍧 :shaved_ice:🎂 :birthday:🍰 :cake:🍪 :cookie:🍫 :chocolate_bar:🍬 :candy:🍭 :lollipop:🍯 :honey_pot:🍎 :apple:🍏 :green_apple:🍊 :tangerine:🍋 :lemon:🍒 :cherries:🍇 :grapes:🍉 :watermelon:🍓 :strawberry:🍑 :peach:🍈 :melon:🍌 :banana:🍐 :pear:🍍 :pineapple:🍠 :sweet_potato:🍆 :eggplant:🍅 :tomato:🌽 :corn:Places🏠 :house:🏡 :house_with_garden:🏫 :school:🏢 :office:🏣 :post_office:🏥 :hospital:🏦 :bank:🏪 :convenience_store:🏩 :love_hotel:🏨 :hotel:💒 :wedding:⛪️ :church:🏬 :department_store:🏤 :european_post_office:🌇 :city_sunrise:🌆 :city_sunset:🏯 :japanese_castle:🏰 :european_castle:⛺️ :tent:🏭 :factory:🗼 :tokyo_tower:🗾 :japan:🗻 :mount_fuji:🌄 :sunrise_over_mountains:🌅 :sunrise:🌠 :stars:🗽 :statue_of_liberty:🌉 :bridge_at_night:🎠 :carousel_horse:🌈 :rainbow:🎡 :ferris_wheel:⛲️ :fountain:🎢 :roller_coaster:🚢 :ship:🚤 :speedboat:⛵️ :boat:⛵️ :sailboat:🚣 :rowboat:⚓️ :anchor:🚀 :rocket:✈️ :airplane:🚁 :helicopter:🚂 :steam_locomotive:🚊 :tram:🚞 :mountain_railway:🚲 :bike:🚡 :aerial_tramway:🚟 :suspension_railway:🚠 :mountain_cableway:🚜 :tractor:🚙 :blue_car:🚘 :oncoming_automobile:🚗 :car:🚗 :red_car:🚕 :taxi:🚖 :oncoming_taxi:🚛 :articulated_lorry:🚌 :bus:🚍 :oncoming_bus:🚨 :rotating_light:🚓 :police_car:🚔 :oncoming_police_car:🚒 :fire_engine:🚑 :ambulance:🚐 :minibus:🚚 :truck:🚋 :train:🚉 :station:🚆 :train2:🚅 :bullettrain_front:🚄 :bullettrain_side:🚈 :light_rail:🚝 :monorail:🚃 :railway_car:🚎 :trolleybus:🎫 :ticket:⛽️ :fuelpump:🚦 :vertical_traffic_light:🚥 :traffic_light:⚠️ :warning:🚧 :construction:🔰 :beginner:🏧 :atm:🎰 :slot_machine:🚏 :busstop:💈 :barber:♨️ :hotsprings:🏁 :checkered_flag:🎌 :crossed_flags:🏮 :izakaya_lantern:🗿 :moyai:🎪 :circus_tent:🎭 :performing_arts:📍 :round_pushpin:🚩 :triangular_flag_on_post:🇯🇵 :jp:🇰🇷 :kr:🇨🇳 :cn:🇺🇸 :us:🇫🇷 :fr:🇪🇸 :es:🇮🇹 :it:🇷🇺 :ru:🇬🇧 :gb:🇬🇧 :uk:🇩🇪 :de:Symbols1️⃣ :one:2️⃣ :two:3️⃣ :three:4️⃣ :four:5️⃣ :five:6️⃣ :six:7️⃣ :seven:8️⃣ :eight:9️⃣ :nine:🔟 :keycap_ten:🔢 :1234:0️⃣ :zero:#️⃣ :hash:🔣 :symbols:◀️ :arrow_backward:⬇️ :arrow_down:▶️ :arrow_forward:⬅️ :arrow_left:🔠 :capital_abcd:🔡 :abcd:🔤 :abc:↙️ :arrow_lower_left:↘️ :arrow_lower_right:➡️ :arrow_right:⬆️ :arrow_up:↖️ :arrow_upper_left:↗️ :arrow_upper_right:⏬ :arrow_double_down:⏫ :arrow_double_up:🔽 :arrow_down_small:⤵️ :arrow_heading_down:⤴️ :arrow_heading_up:↩️:leftwards_arrow_with_hook:↪️ :arrow_right_hook:↔️ :left_right_arrow:↕️ :arrow_up_down:🔼 :arrow_up_small:🔃 :arrows_clockwise:🔄 :arrows_counterclockwise:⏪ :rewind:⏩ :fast_forward:ℹ️ :information_source:🆗 :ok:🔀 :twisted_rightwards_arrows:🔁 :repeat:🔂 :repeat_one:🆕 :new:🔝 :top:🆙 :up:🆒 :cool:🆓 :free:🆖 :ng:🎦 :cinema:🈁 :koko:📶 :signal_strength:🈹 :u5272:🈴 :u5408:🈺 :u55b6:🈯️ :u6307:🈷️ :u6708:🈶 :u6709:🈵 :u6e80:🈚️ :u7121:🈸 :u7533:🈳 :u7a7a:🈲 :u7981:🈂️ :sa:🚻 :restroom:🚹 :mens:🚺 :womens:🚼 :baby_symbol:🚭 :no_smoking:🅿️ :parking:♿️ :wheelchair:🚇 :metro:🛄 :baggage_claim:🉑 :accept:🚾 :wc:🚰 :potable_water:🚮 :put_litter_in_its_place:㊙️ :secret:㊗️ :congratulations:Ⓜ️ :m:🛂 :passport_control:🛅 :left_luggage:🛃 :customs:🉐 :ideograph_advantage:🆑 :cl:🆘 :sos:🆔 :id:🚫 :no_entry_sign:🔞 :underage:📵 :no_mobile_phones:🚯 :do_not_litter:🚱 :non-potable_water:🚳 :no_bicycles:🚷 :no_pedestrians:🚸 :children_crossing:⛔️ :no_entry:✳️ :eight_spoked_asterisk:✴️ :eight_pointed_black_star:💟 :heart_decoration:🆚 :vs:📳 :vibration_mode:📴 :mobile_phone_off:💹 :chart:💱 :currency_exchange:♈️ :aries:♉️ :taurus:♊️ :gemini:♋️ :cancer:♌️ :leo:♍️ :virgo:♎️ :libra:♏️ :scorpius:♐️ :sagittarius:♑️ :capricorn:♒️ :aquarius:♓️ :pisces:⛎ :ophiuchus:🔯 :six_pointed_star:❎:negative_squared_cross_mark:🅰️ :a:🅱️ :b:🆎 :ab:🅾️ :o2:💠:diamond_shape_with_a_dot_inside:♻️ :recycle:🔚 :end:🔛 :on:🔜 :soon:🕐 :clock1:🕜 :clock130:🕙 :clock10:🕥 :clock1030:🕚 :clock11:🕦 :clock1130:🕛 :clock12:🕧 :clock1230:🕑 :clock2:🕝 :clock230:🕒 :clock3:🕞 :clock330:🕓 :clock4:🕟 :clock430:🕔 :clock5:🕠 :clock530:🕕 :clock6:🕡 :clock630:🕖 :clock7:🕢 :clock730:🕗 :clock8:🕣 :clock830:🕘 :clock9:🕤 :clock930:💲 :heavy_dollar_sign:©️ :copyright:®️ :registered:™️ :tm:❌ :x:❗️ :heavy_exclamation_mark:‼️ :bangbang:⁉️ :interrobang:⭕️ :o:✖️ :heavy_multiplication_x:➕ :heavy_plus_sign:➖ :heavy_minus_sign:➗ :heavy_division_sign:💮 :white_flower:💯 :100:✔️ :heavy_check_mark:☑️ :ballot_box_with_check:🔘 :radio_button:🔗 :link:➰ :curly_loop:〰️ :wavy_dash:〽️ :part_alternation_mark:🔱 :trident::black_square: :black_square::white_square: :white_square:✅ :white_check_mark:🔲 :black_square_button:🔳 :white_square_button:⚫️ :black_circle:⚪️ :white_circle:🔴 :red_circle:🔵 :large_blue_circle:🔷 :large_blue_diamond:🔶 :large_orange_diamond:🔹 :small_blue_diamond:🔸 :small_orange_diamond:🔺 :small_red_triangle:🔻 :small_red_triangle_down:Typora常见问题1. 流程图中文字显示不全解决文件 --> 偏好设置 --> 外观 --> 打开主题文件夹找到当前主题对应的CSS文件,比如Github.css,用记事本或者VSCode打开,翻到最下面,另起一行,添加如下代码,保存后,关闭所有Typora窗口,重新启动便可解决问题。.md-require-zoom-fix foreignObject { font-size: unset; }参考链接: MathJax basic tutorial and quick reference - Mathematics Meta Stack Exchangehttps://blog.todaycoder.cn/2018/11/18/Typora-Emoji/
文章
Dart  ·  前端开发  ·  Shell  ·  pouch  ·  C#  ·  iOS开发  ·  C++  ·  容器
2022-04-17
Flutter Web在Liquid体系下的探索
Flutter Web可以解决Native页面无法外投的问题通过Flutter Web,Liquid研发体系可以在保障客户端体验最佳的前提下,实现一次开发三端投放Flutter Web与Liquid研发体系的结合方案成果展示▐  每平每屋Flutter Web商城二级页,可直接用手淘或者浏览器扫码打开体验二维码▐  滑动效果对比演示机型:mate40Pro▐  卡顿/帧率对比​测试工具TMQLab:红线表示帧率,黄线表示卡顿▐  结论Flutter web整体性能已经具备在生产环境使用的要求Flutter web在手淘(淘系App)中性能优于普通浏览器,可能是因为部分js库在淘系App中通过Native做了桥接在客户端中,Native页面体验依然有明显优势,在开启分页预加载和图片淡入动效之后,帧率依然非常平稳,始终保持在接近满帧客户端视角对Flutter的思考▐  Native端性能Flutter具有自渲染等特性,根据官网介绍的引擎原理,性能应当与原生接近。但是实际体验下来,无论是集团的闲鱼、淘特还是Flutter官网给出的国外案例,在Android端均依然存在不同程度的卡顿,和Native相比还是有一定的差距。后期通过深度优化也许最终能够真正实现和Native持平,但是在现阶段Native依然是体验的最佳选择。研发效率业务层的研发效率并非简单的由某一端的技术栈决定,而是更多的决定于整体研发体系。以Liquid研发体系为例,基础设施完善后,业务开发仅聚焦在UI模板和后端的BFF接口,客户端框架使用什么语言实现并不会产生大的差异。另一方面,Dart的跨平台特性可能对一些基础SDK更有价值。类似kotlin的KMM,我们也可以将端侧的纯逻辑通过Dart实现,在Android/ios双端复用,并且未来还可以通过FFW扩展到web上。▐  web端性能相比传统的webView渲染,FFW(Flutter for web)的自绘引擎理论上性能会更好,这点从集团内Kraken这样的web on flutter方案可以得到验证。并且FFW在2021年3月正式进入stable分支,已经具备在生产环境中使用的条件。研发效率FFW本质上依然属于前端研发范畴,可以直接调用前端已有的js库,编译后的产物也依然是js等前端文件,这样可以较为方便的复用整套前端研发体系。区别主要是通过dart语言开发,使用flutter的UI组件。对于客户端同学而言,使用dart和使用js无本质区别。▐  结论Native依然是客户端体验的首选,Flutter目前更大的价值在web端。现阶段,我们认为在客户端依然应当通过Native提供极致的体验,拉留存。在web端可以适当采用FFW解决Native页面的外投问题。每平每屋业务背景每平每屋业务涉及「每平每屋App」、「每平每屋门店导购App」、「每平每屋手淘轻应用」、「每平每屋微信小程序」等多个透出渠道,存在大量的跨App复用和外投场景。当前我们的策略是App侧重用户体验,优先使用Native开发;外投场景重灵活,优先使用前端开发。由于业务是不断变化的,遇到Native的页面需要追加外投场景时,必须再开发一套前端实现,这样不但延缓了业务上线节奏,也造成了开发资源的浪费。在上述背景下,我们需要一种可以同时满足App场景高用户体验、外投场景高灵活性的跨端方案。Flutter for web和Liquid研发体系的结合Liquid是每平每屋团队研发的一种前后端一体化的研发体系,具有高体验高迭代效率的特点,其承接了每平每屋App除交易外的全部Native页面,通过在客户端内置页面容器和DinamicX渲染引擎,实现双端的跨端开发。▐  引入FFW前的Liquid研发体系Liquid研发体系是以Liquid协议为基础的体系化研发框架,主要包括LDF(客户端SDK)、后端LMS(BFF框架)和LMC研发平台三部分。三部分之间没有耦合,唯一约束是需要使用Liquid协议。如果要将Liquid研发体系通过FFW扩展到web端,只需将LDF(客户端SDK)替换成Dart实现,后端LMS(BFF框架)和LMC研发平台这两部分可以直接复用。▐  整体设计上图为Liquid体系引入FFW后的整体架构图,红色部分为具体的开发项,主要包括以下部分:面向web的Dart版本LDF SDK,通过FFW将Liquid体系衍生到web对接前端工程化研发体系,通过DEF编译、管理、发布页面对接前端基础依赖,如mtop、埋点、监控、DX等。实际使用时,客户端内依然使用Native版本的LDF渲染页面,提供最佳的用户体验。对于需要高动态的外投场景,使用Dart版本的LDF渲染页面。详细设计▐  Dart版本LDF SDKDart 版本的LDF SDK和Native版本在功能与实现上保持完全一致,主要是将实现语言换成了Dart。两套SDK的输入都是Liquid协议,在客户端通过现有的Native SDK渲染页面,提供最佳体验。在Web端,通过Dart SDK渲染为前端页面,为业务提供外投能力。在业务扩展层,我们将部分比较轻量的纯逻辑扩展库以Dart库的形式实现,在三端复用,以降低研发成本。▐  对接前端工程研发体系集团的前端发布平台是DEF,FFW本质上依然属于前端开发范畴,因此需要能够在DEF平台上完成编译和发布,这样可以更好的对接前端工程研发体系。通过DEF编译和发布FFW工程实现在DEF平台编译和发布FFW工程的主要流程如下:其中Flutter环境镜像依赖DEF同学支持,目前部署的是最新的2.8.1稳定版FFW构建器支持通过shell自定义编译实现#! /bin/bash export PUB_HOSTED_URL=https://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn flutter build web --web-renderer=html --release --no-sound-null-safety rm ${BUILD_WORK_DIR}/build/web/.last_build_id rm ${BUILD_WORK_DIR}/build/web/flutter_service_worker.js mv ${BUILD_WORK_DIR}/build ${BUILD_DEST} 最后需要修改FFW工程中和DEF平台对接的打包配置文件abc.json,指定通过上面定制的构建器打包。▐  对接前端基础依赖web侧的二方库对接web侧的二方库是一系列的js文件,以mtop和login为例,需要在页面的index.xml中引入对应的js文件 <script src="//g.alicdn.com/mtb/lib-mtop/2.3.14/mtop.js"></script> <script src="//g.alicdn.com/mtb/lib-login/2.2.0/login.js"></script>或者在程序运行时动态添加到body节点 html.document.body!.append(html.ScriptElement() ..src = 'https://g.alicdn.com/mtb/lib-mtop/2.3.14/mtop.js' ..type = 'application/javascript' ); 接下来可以二次封装二方库中的接口,以团队习惯的方式向Dart层暴露接口,并通过dart:js进行调用。三端统一API层设计由于部分通用组件扩展需要以Dart库的形式在ios/android/web三端进行复用,因此我们在LDF和平台之上引入了一层通用容器层,整体架构如下:通过Flutter的Plugin框架对接不同平台上二方库实现,向上提供统一的Dart接口。差异性逻辑可以进行代码隔离或者文件隔离代码隔离:通过foundation中的kIsWeb判断是否是web端,参考:https://api.flutter.dev/flutter/foundation/kIsWeb-constant.html文件隔离:通过dart提供的条件导入实现,参考:https://dart.cn/guides/libraries/create-library-packages#conditionally-importing-and-exporting-library-files下一步目前我们完成了Liquid-FFW的最小集,跑通了整套研发链路,后续将继续完善Liquid研发体系和FFW的对接以及针对FFW进行一些更深度的性能优化。总结与展望综上所述,通过Flutter Web我们实现了Native页面的一次开发,三端投放,为业务提供了更灵活高效的技术选择。Flutter Web对客户端的意义是划时代的,除了Native页面外投,也可以部分缓解Native一定要发版的问题,针对线上版本可以使用动态发布的Flutter Web页面,新版本使用体验最优的Native页面。我们会持续对Flutter Web 进行探索,如您对Flutter Web感兴趣,也欢迎在文末留言或提供建议,感谢阅读!团队介绍我们是阿里巴巴淘系技术部的iHome技术团队,负责家装行业业务研发、技术平台建设、新技术探索等工作。这里有线上电商、直营门店、内容社交等丰富的业务场景,无论是客户端还是服务端都能给您提供巨大的成长空间和技术挑战,期待您的加入。简历投递: zhengyang.xzy@alibaba-inc.com
文章
Dart  ·  前端开发  ·  JavaScript  ·  Shell  ·  开发工具  ·  Android开发  ·  UED  ·  iOS开发  ·  Kotlin  ·  容器
2022-04-11
Dart中的内置数据类型简介
Dart中的内置数据类型简介最近也有在常使用flutter写一个简单的app,不为实现啥功能,就单纯走一个流程,把它部署到我的安卓和ios手机上。为此稍微看一下dart的内置数据类型。前言变量是一个引用,在Dart中一旦变量的类型被确定,就不能再被更改类型Dart中的一切变量皆是对象,所有的变量都指向一个对象。声明变量声明可以有以下两种方式,一种是不指定类型,使用var关键字。此外final和const都可以用于定义常量的, 定义之后值都不可以修改 var name = 'Bob'; final name = 'wang'; name = 'james'; // 错误 const age = 18; age = 20; // 错误另一种则是明确指定类型(Optional types)String name = 'Bob';正题void main() { var number = 42; // Declare and initialize a variable. print('The number is ${number}'); // Print to console. // 一、Number类型 var x = 1; var hex = 0xDEADBEEF; var exponent = 8e5; var y = 1.1; // 二、String类型 // 常见用法 var s1 = 'Single quotes work well for string literals.'; // 嵌套变量 var s2 = 'The number is ${number}'; // 多行字符串 var s3 = """This is also a multi-line string."""; // 双引号直接显示 var s4 = "It's even easier to use the other delimiter."; // r作为前缀避免转义 var s5 = r'In a raw string, not even \n gets special treatment.'; // 三、布尔类型 // true 和false 没啥好说的 // 四、Lists类型 var list = [1, 2, 3]; // 五、Set类型 看起来不太习惯,写多了就习惯了 var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'}; print(halogens); // 六、Map类型 跟js中的object一样,但是不同语言千万不要以为就是单纯的类似,这样自己在学习过程中容易产生紊乱。 var gifts = { // Key: Value 'first': 'partridge', 'second': 'turtledoves', 'fifth': 'golden rings' }; print(gifts); // 七、Symbol类型 var test = Symbol('name'); print(test); }
文章
Dart  ·  Android开发  ·  iOS开发
2022-03-20
编程语言一体化
编程语言一体化Typescript作为Javascript的超集,弥补了Javascript的静态类型检查,同时扩展了很多OOP的语法特性,使得TS跟dart在语法特性上有非常多相似的地方,为后面的转换提供了可能与便利。要实现语言层面转换背后都会有一个小型的编译器在支撑着,不过幸运的是Typescript官方已经提供语法解析器,通过它我们很容易就拿到一份可靠的AST,所以我们只需要实现一个dart generator就行了。生成器大致可以分为四个层面的工作:基础语法转换原生方法差异转换业务框架桥接依赖库与头文件桥接
文章
Dart  ·  JavaScript  ·  前端开发  ·  编译器
2022-03-08
深入探索Flutter性能优化
目录一、检测手段1、Flutter Inspector2、性能图层3、Raster 线程问题4、UI 线程问题定位5、检查多视图叠加的视图渲染开关 checkerboardOffscreenLayers6、检查缓存的图像开关 checkerboardRasterCacheImages二、关键优化指标1、页面异常率2、页面帧率3、页面加载时长三、布局加载优化1、常规优化2、深入优化四、启动速度优化1、引擎预加载2、Dart VM 预热五、内存优化1、const 实例化2、识别出消耗多余内存的图片3、针对 ListView item 中有 image 的情况来优化内存六、包体积优化1、图片优化2、移除冗余的二三库3、启用代码缩减和资源缩减4、构建单 ABI 架构的包七、总结前言Flutter 作为目前最火爆的移动端跨平台框架,能够帮助开发者通过一套代码库高效地构建多平台的精美应用,并支持移动、Web、桌面和嵌入式平台。对于 Android 来说,Flutter 能够创作媲美原生的高性能应用,但是,在较为复杂的 App 中,使用 Flutter 开发也很难避免产生各种各样的性能问题。在这篇文章中,我将和你一起全方位地深入探索 Flutter 性能优化的疆域。一、检测手段准备以 profile 模式启动应用,如果是混合 Flutter 应用,在 flutter/packages/flutter_tools/gradle/flutter.gradle 的 buildModeFor 方法中将 debug 模式改为 profile即可。为什么要在分析模式下来调试应用性能?分析模式在发布模式的基础之上,为分析工具提供了少量必要的应用追踪信息。那,为什么要在发布模式的基础上来调试应用性能?与调试代码可以在调试模式下检测 Bug 不同,性能问题需要在发布模式下使用真机进行检测。这是因为,相比发布模式而言,调试模式增加了很多额外的检查(比如断言),这些检查可能会耗费很多资源,而更重要的是,调试模式使用 JIT 模式运行应用,代码执行效率较低。这就使得调试模式运行的应用,无法真实反映出它的性能问题。而另一方面,模拟器使用的指令集为 x86,而真机使用的指令集是 ARM。这两种方式的二进制代码执行行为完全不同,因此,模拟器与真机的性能差异较大,例如,针对一些 x86 指令集擅长的操作,模拟器会比真机快,而另一些操作则会比真机慢。这也同时意味着,你无法使用模拟器来评估真机才能出现的性能问题。1、Flutter InspectorFlutter Inspector有很多功能,但你应该把注意力花在更有用的功能学习上,例如:“Select Widget Mode” 和 “Repaint Rainbow”。Select Widget Mode点击 “Select Widget Mode” 图标,可以在手机上查看当前页面的布局框架与容器类型。作用快速查看陌生页面的布局实现方式。Repaint Rainbow点击 “Repaint Rainbow” 图标,它会 为所有 RenderBox 绘制一层外框,并在它们重绘时会改变颜色。作用帮你找到 App 中频繁重绘导致性能消耗过大的部分。例如:一个小动画可能会导致整个页面重绘,这个时候使用 RepaintBoundary Widget 包裹它,可以将重绘范围缩小至本身所占用的区域,这样就可以减少绘制消耗。使用场景例如 页面的进度条动画刷新时会导致整个布局频繁重绘。缺点使用 RepaintBoundary Widget 会创建额外的绘制画布,这将会增加一定的内存消耗。2、性能图层性能图层会在当前应用的最上层,以 Flutter 引擎自绘的方式展示 Raster 与 UI 线程的执行图表,而其中每一张图表都代表当前线程最近 300 帧的表现,如果 UI 产生了卡顿(跳帧),这些图表可以帮助你分析并找到原因。蓝色垂直的线条表示已执行的正常帧,绿色的线条代表的是当前帧,如果其中有一帧处理时间过长,就会导致界面卡顿,图表中就会展示出一个红色竖条。如果红色竖条出现在 GPU 线程图表,意味着渲染的图形太复杂,导致无法快速渲染;而如果是出现在了 UI 线程图表,则表示 Dart 代码消耗了大量资源,需要优化代码的执行时间。如下图所示:3、Raster 线程问题定位它定位的是 渲染引擎底层渲染的异常。解决方案是 把需要静态缓存的图像加入到 RepaintBoundary。而 RepaintBoundary 可以确定 Widget 树的重绘边界,如果图像足够复杂,Flutter 引擎会自动将其缓存,避免重复刷新。当然,因为缓存资源有限,如果引擎认为图像不够复杂,也可能会忽略 RepaintBoundary。4、UI 线程问题定位问题场景在视图构建时,在 build 方法中使用了一些复杂的运算,或是在主 Isolate 中进行了同步的 I/O 操作。使用 Performance 进行检测点击 Android Studio 底部工具栏中的 “Open DevTools” 按钮,然后在打开的 Dart DevTools 网页中将顶部的 tab 切换到 Performance。与性能图层能够自动记录应用执行的情况不同,使用 Performance 来分析代码执行轨迹,你需要手动点击 “Record” 按钮去主动触发,在完成信息的抽样采集后,点击 “Stop” 按钮结束录制。这时,你就可以得到在这期间应用的执行情况了。使用 Performance 记录应用的执行情况,即 CPU 帧图,又被称为火焰图。火焰图是基于记录代码执行结果所产生的图片,用来展示 CPU 的调用栈,表示的是 CPU 的繁忙程度。其中:y 轴:表示调用栈,其每一层都是一个函数。调用栈越深,火焰就越高,底部就是正在执行的函数,上方都是它的父函数。x 轴:表示单位时间,一个函数在 x 轴占据的宽度越宽,就表示它被采样到的次数越多,即执行时间越长。所以,我们要 检测 CPU 耗时问题,皆可以查看火焰图底部的哪个函数占据的宽度最大。只要有 “平顶”,就表示该函数可能存在性能问题。如下图所示:一般的耗时问题,我们通常可以 使用 Isolate(或 compute)将这些耗时的操作挪到并发主 Isolate 之外去完成。dart 的单线程执行异步任务是怎么实现的?网络调用的执行是由操作系统提供的另外的底层线程做的,而在 event queue 里只会放一个网络调用的最终执行结果(成功或失败)和响应执行结果的处理回调。5、使用 checkerboardOffscreenLayers 检查多视图叠加的视图渲染只要在 MaterialApp 的初始化方法中,将 checkerboardOffscreenLayers 开关设置为 true,分析工具就会自动帮你检测多视图叠加的情况。这时,使用了 saveLayer 的 Widget 会自动显示为棋盘格式,并随着页面刷新而闪烁。而 saveLayer 一般会通过一些功能性 Widget,在涉及需要剪切或半透明蒙层的场景中间接地使用。6、使用 checkerboardRasterCacheImages 检查缓存的图像它也是用来检测在界面重绘时频繁闪烁的图像(即没有静态缓存)。解决方案是把需要静态缓存的图像加入到 RepaintBoundary。二、关键优化指标1、页面异常率页面异常率,即 页面渲染过程中出现异常的概率。它度量的是页面维度下功能不可用的情况,其统计公式为:页面异常率 = 异常发生次数 / 整体页面 PV 数。统计异常发生次数利用 Zone 与 FlutterError 这两个方法,然后在异常拦截的方法中,去累计异常的发生次数。统计整体页面 PV 数继承自 NavigatorObserver 的观察者,并在其 didPush 方法中,去累加页面的打开次数。2、页面帧率Flutter 在全局 Window 对象上提供了帧回调机制。我们可以在 Window 对象上注册 onReportTimings 方法,将最近绘制帧耗费的时间(即 FrameTiming),以回调的形式告诉我们。有了每一帧的绘制时间后,我们就可以计算 FPS 了。为了让 FPS 的计算更加平滑,我们需要保留最近 25 个 FrameTiming 用于求和计算。由于帧的渲染是依靠 VSync 信号驱动的,如果帧绘制的时间没有超过 16.67 ms,我们也需要把它当成 16.67 ms 来算,因为绘制完成的帧必须要等到下一次 VSync 信号来了之后才能渲染。而如果帧绘制时间超过了 16.67 ms,则会占用后续 VSync 的信号周期,从而打乱后续的绘制次序,产生卡顿现象。那么,页面帧率的统计公式就是:FPS = 60 * 实际渲染的帧数 / 本来应该在这个时间内渲染完成的帧数。首先,定义一个容量为 25 的列表,用于存储最近的帧绘制耗时 FrameTiming。然后,在 FPS 的计算函数中,你再将列表中每帧绘制时间与 VSync 周期 frameInterval 进行比较,得出本来应该绘制的帧数。最后,两者相除就得到了 FPS 指标。3、页面加载时长页面加载时长 = 页面可见的时间 - 页面创建的时间(包括网络加载时长)统计页面可见的时间WidgetsBinding 提供了单次 Frame 回调的 addPostFrameCallback 方法,它会在当前 Frame 绘制完成之后进行回调,并且只会回调一次。一旦监听到 Frame 绘制完成回调后,我们就可以确认页面已经被渲染出来了,因此我们可以借助这个方法去获取页面的渲染完成时间 endTime。统计页面创建的时间获取页面创建的时间比较容易,我们只需要在页面的初始化函数 initState() 里记录页面的创建时间 startTime。最后,再将这两个时间做减法,你就能得到页面的加载时长。需要注意的是,正常的页面加载时长一般都不应该超过2秒。如果超过了,则意味着有严重的性能问题。三、布局加载优化Flutter 为什么要使用声明书 UI 的编写方式?为了减轻开发人员的负担,无需编写如何在不同的 UI 状态之间进行切换的代码,Flutter 使用了声明式的 UI 编写方式,而不是 Android 和 iOS 中的命令式编写方式。这样的话,当用户界面发生变化时,Flutter 不会修改旧的 Widget 实例,而是会构造新的 Widget 实例。Fluuter 框架使用 RenderObjects 管理传统 UI 对象的职责(比如维护布局的状态)。 RenderObjects 在帧之间保持不变, Flutter 的轻量级 Widget 通知框架在状态之间修改 RenderObjects, 而 Flutter Framework 则负责处理其余部分。1、常规优化常规优化即针对 build() 进行优化,build() 方法中的性能问题一般有两种:耗时操作和 Widget 堆叠。1)、在 build() 方法中执行了耗时操作我们应该尽量避免在 build() 中执行耗时操作,因为 build() 会被频繁地调用,尤其是当 Widget 重建的时候。此外,我们不要在代码中进行阻塞式操作,可以将文件读取、数据库操作、网络请求等通过 Future 来转换成异步方式来完成。最后,对于 CPU 计算频繁的操作,例如图片压缩,可以使用 isolate 来充分利用多核心 CPU。isolate 作为 Flutter 中的多线程实现方式,之所以被称之为 isolate(隔离),是因为每一个 isolate 都有一份单独的内存。Flutter 会运行一个事件循环,它会从事件队列中取得最旧的事件,处理它,然后再返回下一个事件进行处理,依此类推,直到事件队列清空为止。每当动作中断时,线程就会等待下一个事件。实质上,不仅仅是 isolate,所有的高级 API 都能够应用于异步编程,例如 Futures、Streams、async 和 await,它们全部都是构建在这个简单的事件循环之上。而,async 和 await 实际上只是使用 futures 和 streams 的替代语法,它将代码编写形式从异步变为同步,主要用来帮助你编写更清晰、简洁的代码。此外,async 和 await 也能使用 try on catch finally 来进行异常处理,这能够帮助你处理一些数据解析方面的异常。2)、build() 方法中堆砌了大量的 Widget这将会导致三个问题:1、代码可读性差:画界面时需要一个 Widget 嵌套一个 Widget,但如果 Widget 嵌套太深,就会导致代码的可读性变差,也不利于后期的维护和扩展。2、复用难:由于所有的代码都在一个 build(),会导致无法将公共的 UI 代码复用到其它的页面或模块。3、影响性能:我们在 State 上调用 setState() 时,所有 build() 中的 Widget 都将被重建,因此 build() 中返回的 Widget 树越大,那么需要重建的 Widget 就越多,也就会对性能越不利。所以,你需要 控制 build 方法耗时,将 Widget 拆小,避免直接返回一个巨大的 Widget,这样 Widget 会享有更细粒度的重建和复用。3)、使用 Widget 而不是函数如果一个函数可以做同样的事情,Flutter 就不会有 StatelessWidget ,使用 StatelessWidget 的最大好处在于:能尽量避免不必要的重建。总的来说,它的优势有:1)、允许性能优化:const 构造函数,更细粒度的重建等等。2)、确保在两个不同的布局之间切换时,能够正确地处理资源(因为函数可能重用某些先前的状态)。3)、确保热重载正常工作,使用函数可能会破坏热重载。4)、在 flutter 自带的 Widget 显示工具中能看到 Widget 的状态和参数。5)、发生错误时,有更清晰的提示:此时,Flutter 框架将为你提供当前构建的 Widget 名称,更容易排查问题。6)、可以定义 key 和方便使用 context 的 API。4)、尽可能地使用 const如果某一个实例已经用 const 定义好了,那么其它地方再次使用 const 定义时,则会直接从常量池里取,这样便能够节省 RAM。5)、尽可能地使用 const 构造器当构建你自己的 Widget 或者使用 Flutter 的 Widget 时,这将会帮助 Flutter 仅仅去 rebuild 那些应当被更新的 Widget。因此,你应该尽量多用 const 组件,这样即使父组件更新了,子组件也不会重新进行 rebuild 操作。特别是针对一些长期不修改的组件,例如通用报错组件和通用 loading 组件等。6)、使用 nil 去替代 Container() 和 SizedBox()首先,你需要明白 nil 仅仅是一个基础的 Widget 元素 ,它的构建成本几乎没有。在某些情况下,如果你不想显示任何内容,且不能返回 null 的时候,你可能会返回类似 const SizedBox/Container 的 Widget,但是 SizedBox 会创建 RenderObject,而渲染树中的 RenderObject 会带来多余的生命周期控制和额外的计算消耗,即便你没有给 SizedBox 指定任何的参数。下面,是我平时使用 nil 的一套方式:// BEST text != null ? Text(text) : nil or if (text != null) Text(text) text != null ? Text(text) : const Container()/SizedBox()7)、列表优化在构建大型网格或列表的时候,我们要尽量避免使用 ListView(children: [],) 或 GridView(children: [],)。因为,在这种场景下,不管列表内容是否可见,会导致列表中所有的数据都会被一次性绘制出来,这种用法类似于 Android 的 ScrollView。如果我们列表数据比较大的时候,建议使用 ListView 和 GridView 的 builder 方法,它们只会绘制可见的列表内容,类似于 Android 的 RecyclerView。其实,本质上,就是对列表采用了懒加载而不是直接一次性创建所有的子 Widget,这样视图的初始化时间就减少了。8)、针对于长列表,记得在 ListView 中使用 itemExtent。有时候当我们有一个很长的列表,想要用滚动条来大跳时,使用 itemExtent 就很重要了,它会帮助 Flutter 去计算 ListView 的滚动位置而不是计算每一个 Widget 的高度,与此同时,它能够使滚动动画有更好的性能。9)、减少可折叠 ListView 的构建时间针对于可折叠的 ListView,未展开状态时,设置其 itemCount 为 0,这样 item 只会在展开状态下才进行构建,以减少页面第一次的打开构建时间。10)、尽量不要为 Widget 设置半透明效果考虑用图片的形式代替,这样被遮挡的部分 Widget 区域就不需要绘制了。除此之外,还有网络请求预加载优化、抽取文本 Theme 等常规的优化方式就不赘述了。2、深入优化1)、优化光栅线程所有的 Flutter 应用至少都会运行在两个并行的线程上:UI 线程和 Raster 线程。**UI 线程是你构建 Widgets 和运行应用逻辑的地方。**Raster 线程是 Flutter 用来栅格化你的应用的。它从 UI 线程获取指令并将它们转换为可以发送到图形卡的内容。在光栅线程中,会获取图片的字节,调整图像的大小,应用透明度、混合模式、模糊等等,直到产生最后的图形像素。然后,光栅线程会将其发送到图形卡,继而发送到屏幕上显示。使用 Flutter DevTools-Performance 进行检测,步骤如下:1、在 Performance Overlay 中,查看光栅线程和 UI 线程哪个负载过重。2、在 Timeline Events 中,找到那些耗费时间最长的事件,例如常见的 SkCanvas::Flush,它负责解决所有待处理的 GPU 操作。3、找到对应的代码区域,通过删除 Widgets 或方法的方式来看对性能的影响。2)、用 key 加速 Flutter 的性能优化光栅线程一个 element 是由 Widget 内部创建的,它的主要目的是,知道对应的 Widget 在 Widget 树中所处的位置。但是元素的创建是非常昂贵的,通过 Keys(ValueKeys 和 GlobalKeys),我们可以去重复使用它们。GlobalKey 与 ValueKey 的区别?GlobalKey 是全局使用的 key,在跨小部件的场景时,你就可以使用它去刷新其它小部件。但,它是很昂贵的,如果你不需要访问 BuildContext、Element 和 State,应该尽量使用 LocalKey。而 ValueKey 和 ObjectKey、UniqueKey 一样都归属于局部使用的 LocalKey,无法跨容器使用,ValueKey 比较的是 Widget 的值,而 ObjectKey 比较的是对象的 key,UniqueKey 则每次都会生成一个不同的值。元素的生命周期Mount:挂载,当元素第一次被添加到树上的时候调用。Active:当需要激活之前失活的元素时被调用。Update:用新数据去更新 RenderObject。Deactive:当元素从 Widget 树中被移除或移动时被调用。如果一个元素在同一帧期间被移动了且它有 GlobalKey,那么它仍然能够被激活。UnMount:卸载,如果一个元素在一帧期间没有被激活,它将会被卸载,并且再也不会被复用。优化方式为了去改善性能,你需要去尽可能让 Widget 使用 Activie 和 Update 操作,并且尽量避免让 Widget触发 UnMount 和 Mount。而使用 GlobayKeys 和 ValueKey 则能做到这一点:/// 1、给 MaterialApp 指定 GlobalKeys MaterialApp(key: global, home: child,); /// 2、通过把 ValueKey 分配到正在被卸载的根 Widget,你就能够 /// 减少 Widget 的平均构建时间。 Widget build(BuildContext context) { return Column( children: [ value ? const SizedBox(key: ValueKey('SizedBox')) : const Placeholder(key: ValueKey('Placeholder')), GestureDetector( key: ValueKey('GestureDetector'), onTap: () { setState(() { value = !value; }); }, child: Container( width: 100, height: 100, color: Colors.red, ), ), !value ? const SizedBox(key: ValueKey('SizedBox')) : const Placeholder(key: ValueKey('Placeholder')), ], ); }如何知道哪些 Widget 会被 Update,哪些 Widget会被 UnMount?只有 build 直接 return 的那个根 Widget 会自动更新,其它都有可能被 UnMount,因此都需要给其分配 ValueKey。为什么没有给 Container 分配 ValueKey?因为 Container 是 GestureDetector 的一个子 Widget,所以当给 GestureDetector 使用 ValueKey 去实现复用更新时,Container 也能被自动更新。优化效果优化前:优化后:可以看到,平均构建时间 由 5.5ms 减少到 1.6ms,优化效果还是很明显的。优势大幅度减少 Widget的平均构建时间。缺点过多使用 ValueKey 会让你的代码变得更冗余。如果你的根 Widget 是 MaterialApp 时,则需要使用 GlobalKey,但当你去重复使用 GlobalKey 时可能会导致一些错误,所以一定要避免滥用 Key。注意:在大部分场景下,Flutter 的性能都是足够的,不需要这么细致的优化,只有当产生了视觉上的问题,例如卡顿时才需要去分析优化。四、启动速度优化1、Flutter 引擎预加载使用它可以达到页面秒开的一个效果,具体实现为:在 HIFlutterCacheManager 类中定义一个 preLoad 方法,使用 Looper.myQueue().addIdleHandler 添加一个 idelHandler,当 CPU 空闲时会回调 queueIdle 方法,在这个方法里,你就可以去初始化 FlutterEngine,并把它缓存到集合中。预加载完成之后,你就可以通过 HIFlutterCacheManager 类的 getCachedFlutterEngine 方法从集合中获取到缓存好的引擎。2、Dart VM 预热对于 Native + Flutter 的混合场景,如果不想使用引擎预加载的方式,那么要提升 Flutter 的启动速度也可以通 过Dart VM 预热来完成,这种方式会提升一定的 Flutter 引擎加载速度,但整体对启动速度的提升没有预加载引擎提升的那么多。无论是引擎预加载还是 Dart VM 预热都是有一定的内存成本的,如果 App 内存压力不大,并且预判用户接下来会访问 Flutter 业务,那么使用这个优化就能带来很好的价值;反之,则可能造成资源浪费,意义不大。五、内存优化1、const 实例化优势const 对象只会创建一个编译时的常量值。在代码被加载进 Dart Vm 时,在编译时会存储在一个特殊的查询表里,由于 flutter 采用了 AoT 编译,const + values 的方式会提供一些小的性能优势。例如:const Color() 仅仅只分配一次内存给当前实例。应用场景Color()、GlobayKey() 等等。2、识别出消耗多余内存的图片Flutter Inspector:点击 “Invert Oversized Images”,它会识别出那些解码大小超过展示大小的图片,并且系统会将其倒置,这些你就能更容易在 App 页面中找到它。针对这些图片,你可以指定 cacheWidth 和 cacheHeight 为展示大小,这样可以让 flutter 引擎以指定大小解析图片,减少内存消耗。3、针对 ListView item 中有 image 的情况来优化内存ListView 不能够杀死那些在屏幕可视范围之外的那些 item,如果 item 使用了高分辨率的图片,那么它将会消耗非常多的内存。换言之,ListView 在默认情况下会在整个滑动/不滑动的过程中让子 Widget 保持活动状态,这一点是通过 AutomaticKeepAlive 来保证,在默认情况下,每个子 Widget 都会被这个 Widget 包裹,以使被包裹的子 Widget 保持活跃。其次,如果用户向后滚动,则不会再次重新绘制子 Widget,这一点是通过 RepaintBoundaries 来保证,在默认情况下,每个子 Widget 都会被这个 Widget 包裹,它会让被包裹的子 Widget 仅仅绘制一次,以此获得更高的性能。但,这样的问题在于,如果加载大量的图片,则会消耗大量的内存,最终可能使 App 崩溃。解决方案通过将这两个选项置为 false 来禁用它们,这样不可见的子元素就会被自动处理和 GC。ListView.builder( ... addAutomaticKeepAlives: false (true by default) addRepaintBoundaries: false (true by default) );由于重新绘制子元素和管理状态等操作会占用更多的 CPU 和 GPU 资源,但是它能够解决你 App 的内存问题,并且会得到一个高性能的视图列表。六、包体积优化1、图片优化对图片压缩或使用在线的网络图片。2、移除冗余的二三库随着业务的增加,项目中会引入越来越多的二三方库,其中有不少是功能重复的,甚至是已经不再使用的。移除不再使用的和将相同功能的库进行合并可以进一步减少包体积。3、启用代码缩减和资源缩减打开 minifyEnabled 和 shrinkResources,构建出来的 release 包会减少 10% 左右的大小,甚至更多。4、构建单 ABI 架构的包目前手机市场上,x86 / x86_64/armeabi/mips / mips6 的占有量很少,arm64-v8a 作为最新一代架构,是目前的主流,而 armeabi-v7a 只存在少部分的老旧手机中。所以,为了进一步优化包大小,你可以构建出单一架构的安装包,在 Flutter 中可以通过以下方式来构建出单一架构的安装包:cd <flutter应用的android目录> flutter build apk --split-per-abi如果想进一步压缩包体积可将 so 进行动态下发,将 so 放在远端进行动态加载,不仅能进一步减少包体积也可以实现代码的热修复和动态加载。七、总结在本篇文章中,我主要从以下 六个方面 讲解了 Flutter 性能优化相关的知识:1)、检测手段:Flutter Inspector、性能图层、Raster 和 UI 线程问题的定位使用 checkerboardOffscreenLayers 检查多视图叠加的视图渲染 、使用 checkerboardRasterCacheImages 检查缓存的图像。2)、关键优化指标:包括页面异常率、页面帧率、页面加载时长。3)、布局加载优化:十大常规优化、优化光栅线程、用 key 加速 Flutter 的性能。4)、启动速度优化:引擎预加载和 Dart VM 预热。5)、内存优化:const 实例化、识别出消耗多余内存的图片、针对 ListView item 中有 image 的情况来优化内存。6)、包体积优化:图片优化、移除冗余的二三库、启用代码缩减和资源缩减、构建单 ABI 架构的包。在近一年实践 Flutter 的过程中,越发发现一个人真正应该具备的核心能力应该是你的思考能力。思考能力,包括 结构化思考/系统性思考/迁移思考/层级思考/逆向思考/多元思考 等,使用这些思考能力分析问题时能快速地把握住问题的本质,在本质上做功夫,才是王道,才是真的 yyds。参考链接1、Flutter官方文档-性能优化2、Flutter youtube 频道3、Raster thread performance optimization tips4、Flutter Performance Tips5、Flutter Performance Optimization6、Elements, Keys and Flutter’s performance7、如何检测并优化Flutter App的整体性能表现?8、如何在命令式框架中修改 UI9、Flutter Layout Cheat Sheet10、Flutter: The Advanced Layout Rule Even Beginners Must Know11、Profiling Flutter Applications Using the Timeline12、说说Flutter中的RepaintBoundary13、说说Flutter中最熟悉的陌生人 —— Key14、Splitting widgets to methods is an antipattern15、What is the difference between functions and classes to create reusable widgets?16、How to improve the performance of your Flutter app17、nil: ^1.1.118、How to improve the performance of your Flutter app19、Flutter memory optimization series
文章
存储  ·  缓存  ·  Dart  ·  API  ·  数据库  ·  Android开发  ·  开发者  ·  iOS开发  ·  异构计算  ·  容器
2022-02-21
1
...
7 8 9 10 11 12
...
20
跳转至:
大淘宝技术
1429 人关注 | 655 讨论 | 598 内容
+ 订阅
  • 程序员最重要的能力是什么?
  • 程序员如何在业余时间提升自己?
  • 设计模式最佳套路5 —— 愉快地使用工厂方法模式
查看更多 >
开发与运维
5197 人关注 | 125229 讨论 | 188359 内容
+ 订阅
  • 从理论到工程实践——用户画像入门宝典(一)
  • RocketMQ学习环境搭建(RocketMQ安装与IDEA Debug环境搭建)
  • 一站式元数据治理平台——Datahub入门宝典(二)
查看更多 >
云原生
229802 人关注 | 9473 讨论 | 27448 内容
+ 订阅
  • 一站式元数据治理平台——Datahub入门宝典(二)
  • 在Centos 7上配置Pouch 镜像方法
  • 《菜农升职记》之 Docker网络
查看更多 >
人工智能
2599 人关注 | 9268 讨论 | 62799 内容
+ 订阅
  • 从理论到工程实践——用户画像入门宝典(一)
  • 掌握《网络》,见微才能知著
  • 【信号处理】基于matlab实现男女声识别
查看更多 >
安全
1034 人关注 | 23268 讨论 | 53020 内容
+ 订阅
  • 掌握《网络》,见微才能知著
  • 《 Socket.IO》 解决 WebSocket 通信!
  • 《菜农升职记》之 Docker网络
查看更多 >