生成式AI领域的最新成果都在这里!抢 QCon 展区门票 了解详情
写点什么

Flutter Web 在美团外卖的实践

  • 2021-08-03
  • 本文字数:8724 字

    阅读完需:约 29 分钟

Flutter Web在美团外卖的实践

一、背景


1.1 业务背景


美团外卖商家端业务形态


美团外卖商家端业务围绕数百万商家,在 PC 和 App 上分别提供了交易履约、运营、广告、营销等一系列功能,且经常有外投 H5 的场景(如外卖学院、商家社区、营销活动等)。在这种多形态的业务场景下,如何保障多端体验的一致性,以及如何提升多端迭代的效率,一直是商家端产研关注的重点。


1.1.1 保障多端体验一致性


由于端能力的不同,导致了业务在 App 和 Web 上存在较大的表现差异,例如:App 上自带动画转场,而在 Web 中的实现成本却较高,往往也就降级舍弃了这部分功能。此外,即使我们可利用公司内部的 Roo、MTDUI 等多端 UI 组件库来尽量抹平各端的 UI 差异,但由于组件库在各端的实现不尽相同,很难做到完美的一致性体验。


1.1.2 提升多端迭代效率


由于各端技术体系的不同,涉及多端的需求往往需要不同的开发、测试团队各自完成开发、联调、测试、上线等流程,占用资源巨大,在各团队不可并行支持的情况下,甚至可能导致整个业务交付周期被拉长。虽然 React Native、Flutter 等跨平台方案解决了一部分复用的问题,但显然在商家端业务场景下是远远不够的,我们的目标是要达到全平台(Android、iOS、PC、H5)复用,最大化地提升多端的迭代效率。


1.2 技术背景


1.2.1 Flutter 在美团外卖商家端的储备


MTFlutter 是美团外卖搭建起的公司级 Flutter 研发生态,它的架构图如下图所示:


MTFlutter 架构图


如图所示,MTFlutter 已涵盖研发、调试、测试、发布、线上运维及工程管理整套闭环,同时落地了动态化解决方案,支撑了公司多个业务发展。在大前端融合的趋势下,美团外卖商家端持续在探索更优的多端复用方案,通过 MTFlutter 生态的建设,目前 Flutter 技术栈已覆盖商家端 App 中 90%以上的业务,同时具备 Flutter 开发能力的同学也达到 90% 以上。因此,在有足够技术“储备”的前提下,我们能够基于 Flutter 做全平台(Android、iOS、PC、H5)复用的探索。


1.2.2 Flutter Web 的支持


2018 年 Google 首次公开 Flutter Web Beta 版,旨在进一步实现一份代码、多端运行的愿景。目前,Flutter Web 已被正式合入 Master,期间经过无数工程师的努力,Flutter Web 已能提供与 Flutter Natvie 较统一的交互行为和视觉体验。


Flutter Native VS Flutter Web


如上图可知,Flutter Web 与 Flutter Native 的整体架构相似,二者共用 Framework 层(绿色部分),提供了包括动画、手势、基础 Widget 类,以及大部分应用所需的 Material/Cupertino 主题 Widget 集合。区别在于:Flutter Web 重写了 dart:ui 层(黄色部分),利用 DOM、Canvas 对齐了 Flutter Native 的 UI 渲染能力,使得 Flutter 编写的 UI 能够在现代浏览器上正常展示。


此外,得益于 dart2js 这个早已成熟的工具,Dart 逻辑能够很容易的转换为 JavaScript,进而在 Web 中被正常运行。


二、面临的挑战


综上所述,我们基于 Flutter Web 探索跨端(App\PC\H5)解决方案,真正实现“Write Once & Run AnyWhere”。当然,面临挑战也是巨大的,主要体现在 Flutter 和 MTFlutter 现阶段对 Web 支持还不是很充足。


2.1 Flutter Web 现状


Google 官方目前对 Flutter Web 的工作主要还集中在 dart:ui(Web)的对齐,工程化和性能相关的事项做的还比较少,例如:


  • Flutter Web 构建产物较简陋,只是简单的输出 main.dart.js(1.1M,未 Gzip) 和 图片等静态资源,缺少 JS 拆包、文件 Hash、资源上传 CDN 等优化工作,极大影响了页面的加载性能。

  • 由于 Flutter Web 自身实现了一套页面滚动机制,页面滚动过程中,会频繁计算位置信息,引起滚动区域内容被重新创建,最终导致页面滚动性能较差。


2.2 MTFlutter 现状


虽然 MTFlutter 做了诸多 Flutter Native 层面的定制与优化,但在 Flutter Web 上的建设才刚起步,具体表现在:


  • MTFlutter 现有的基础依赖如:Request(请求封装)、Router(路由)、埋点、容器桥、前端监控,尚未支持在 Web 中的实现。

  • MTFlutter 已实现了完整的 Flutter Module 的打包发布流程,但并不支持 Web 的构建与部署。


三、整体设计


MTFlutter 架构图


上图为 MTFlutter + Web 架构图,由图可知 Flutter Web 页面要满足投产要求,还有大量的工作(上图黄色部分所示),主要包括:


  • 扩展基础依赖(如:Request、Router、埋点等)在 Web 侧的支持。

  • 完善工程化建设,例如:静态资源优化、构建与部署自动化。

  • 深入滚动性能与页面加载性能优化,使得 Flutter Web 能够满足基本的投产要求。


四、详细设计


4.1 基础依赖建设


企业级应用的基础开发依赖(如:请求库、路由库、埋点库等),要重新在 Flutter 中用 Dart 搭建一套,时间成本、兼容性、风险等都是不可控的。而 MTFlutter 是基于原有 Native 基础依赖开发的 Plugin,因此并不支持 Web 端。此章节将展开介绍如何丝滑无感地扩展 MTFlutter 基础依赖在 Web 端的实现。


4.1.1 Flutter Package 分平台编程


在 Flutter 中通过使用 Package 可以创建易于共享的模块化代码。官方强烈推荐使用 Package 形式管理各种工具方法。在官方定义中 Package 包含以下两种类别:


  1. Dart Package:用 Dart 编写的常规 Package,其中一些可能包含依赖于 Flutter 框架的特定功能,其使用范围仅限于 Flutter,例如 path

  2. Plugin Package:用 Dart 编写 API 多个平台各自实现的特殊 Dart Package。Plugin Package 可以为 Android(使用 Kotlin 或 Java)、iOS(使用 Swift 或 Objective-C)、Web、macOS、Windows 或 Linux 或其任意组合编写插件包。


下面分别对这两种类型 Package 中如何分平台编程进行介绍。


(1) Dart Package


Dart Package 是纯 Dart 编写,因此大部分代码均可由 dart2js 直接编译出 Web 平台可运行的代码,但某些涉及 Native 能力的库(如 dart:io)是无法被转译的,因此需要有对平台进行兼容的方法,下面介绍两种在 Dart Package 中分平台编程的方案。


代码级别分平台


针对代码级别的分平台,我们可以借助 Flutter SDK 提供的一个常量 kIsWeb。使用方法如下:


查看源码可知,kIsWeb 之所以能被用于判断 Web 平台,是利用了 JavaScript 不支持整型的特征,在 Web 环境下,Dart 的 double 和 int 由相同类型的对象支持,浮点数 "0.0" 等于整数 "0",对于在 AOT 或 VM 上运行的 Dart 代码却并非如此。


import 'package:flutter/foundation.dart';if (kIsWeb) {  print('Web 端')} else {  print('其他端');}
复制代码


文件级别分平台


针对文件级别分平台,我们利用条件导入导出,其中条件导出具体用法如下:


// tool.dartexport 'src/tool_native.dart' // 兜底导出,即没有命中条件时导出的文件  if (dart.library.html) 'src/tool_web.dart'; // web 端导出的文件,该文件中可以使用 dart:html,也可以通过判断 dart.library.js 导出 Web 端文件。
复制代码


// 引入 tool.dartimport 'package:tool/tool.dart';void main() {  print('import tool');}
复制代码


条件导入和条件导出类似,仅需将 export 改为 import 即可。在业务开发中这也是一种非常实用的分平台编程方法。


(2) Plugin Package


Plugin Package(下文简称为 Plugin)在 Android 和 iOS 平台都是通过 MethodChannel 实现在 UI 层和 Platform 层传递消息从而达到特定平台支持的,官方文档中也全方位介绍了在 Android 和 iOS 平台的具体实现方法及例子,Web 平台的实现却介绍的较少。总结起来,Web 平台和 Native 平台实现方式的不同主要集中在下面两点。


首先,Web Plugin 推荐的方式不是以其平台特有的 JS 语言实现,而是通过 Dart Library 或 Package 实现,对于已有现成可用的 JS SDK 或需要大量使用 JS 实现功能的情况下,官方提供了 package:js 包调用 Javascript,从而实现与 Javascript 的交互。


其次,Web Plugin 不是通过注册 MethodChannel 传递消息的,Flutter 内部可直接调用通过官方指定形式(Federated Plugin )编写的 Flutter Web Plugin 类。


下图完整的展示了一个 Plugin 的整体架构:



4.1.2 基础依赖建设


整体来讲,MTFlutter 基础依赖都是使用 Plugin 的形式开发维护的。为处理依赖中的公共逻辑,提高 Plugin 的可扩展性,MTFlutter Plugin 在 Flutter Plugin 架构(各平台原生实现层和 Plugin Interface 层)之上又增加了公共逻辑处理层,最终暴露给用户是 Plugin API 层提供的接口。MTFlutter Plugin 架构图如下:


MTFlutter Plugin 架构图


在细节实现上,由于项目中各种依赖的类型之间存在着差异,因此在依赖处理上也略有不同,下面介绍拥有不同特点的依赖所对应解决方案。


(1)各平台实现能在 Web 侧对齐的场景,如埋点库


埋点库无论在 Native 端还是在 Web 端都是使用公司统一提供的 SDK,在 API 设计上具有天然的一致性,因此我们完全有能力在 Plugin Interface 层对齐所有接口,上层业务逻辑只需按需做些兼容处理即可。埋点库 Web 端扩展的整体设计思路如下:


  1. 在业务项目的 web/index.html 文件中直接引入 Script 脚本并且进行初始化 (注意:引入 Script 的位置,需要放在 main.dart.js 前面)。

  2. 借助 package:js 库调用埋点 JS SDK,对齐 Flutter 埋点库的 API ,实现 Flutter Plugin 的 Web 端支持,详细架构图如下图所示:


埋点库架构图

(2)各平台实现在 Web 侧无法对齐的场景,如路由库


MTFlutter 路由库是 Native 底层维护的一套全新的路由体系,依靠原生支持提供了强大的定制化功能,而在 Web 端无法这些无法在各平台原生实现层达到 100% 支持。由于 MTFlutter Plugin 最终暴露的是 Plugin API,因此我们选择直接对齐 Plugin API 实现路由库在 Web 端的支持(借助 Flutter Navigator、dart:html 用纯 Dart 语言完成了扩展),详细架构如下图所示:


路由库架构图


(3)Web 端需要通过大量 JS 实现功能的依赖库,如请求库


由于在现有的 Web 请求中统一封装着大量的业务处理逻辑(如拦截器、异常上报等),如果用 Dart 重新实现一遍,成本还是较高的。想复用原有基于 Axios ( JS 请求库) 封装的请求库就相当于让 Plugin 的 Web 平台实现使用 JS 语言。Dart 和 JS 交互是通过 package:js 进行接口调用,因此我们在公共逻辑处理层用 Dart 对齐了相应的 API,详细架构图如下图所示:


请求库架构图

4.2 性能优化


常规的 Web 项目中,为了保证页面有更好的加载和渲染性能,在静态资源文件的处理方面,我们需要做很多的工作,例如:资源文件 Hash 化、CDN 化、按需加载处理等,这些可以通过 Webpack、Rollup 等构建工具进行预处理。


但在 Flutter Web 中,这些预处理的操作目前官方还不支持,原因是 Flutter 暴露给我们的命令只有一个 flutter build web,导致我们无法直接进行更细粒度的个性化定制。如果想要让 Flutter Web 达到企业级应用的标准,我们需要更深层次的探索 Flutter SDK 的运行原理。下面我们列出目前遇到的性能问题及其解决方案。


4.2.1 目前存在的性能问题


Google 官方对 Flutter Web 性能优化所做的事项还比较少,编译输出的页面存在较大的性能问题,主要体现在以下两方面:


  1. 首屏渲染时间长。即使使用了 FutureBuilder 把业务代码拆分成 xxx.part.js 之后,main.dart.js 体积依然维持在 1.1M。单一文件加载、解析时间过长,且静态资源缺少 CDN 化的支持,势必会影响首屏的渲染时间。

  2. 滚动性能较差。 Flutter Web 自身实现了一套页面滚动机制,在页面滚动过程中,会频繁的创建 Canvas,最终导致滚动性能问题,甚至引起页面 Crash。


通过下图对浏览器网络监控情况的展示,可以清晰的反映出以上问题:


浏览器网络监控



页面滚动过程中,内存的占用情况

为了解决上述的性能问题,我们探索了 Flutter SDK 编译过程,总结出从 Flutter 业务代码到 Web 产物的整体流程,详细流程如下图所示:


编译流程


从流程中我们可以看到,Flutter 在 Web 端目前只支持 Dart-->JS 的转换,以及 UI 层的对齐,在工程化和性能优化方面做的工作并不多。


因此,我们必须解决以上的性能问题,才能保证我们的业务可以正常的交付。通过对编译流程的仔细分析与梳理,我们在 AOT 产物生成之前对 Flutter SDK 进行定制,分别进行加载性能优化内存性能优化,下面分别介绍这两部分的内容。


Flutter SDK 进行定制后的流程


4.2.2 加载性能优化


运行 flutter build web 命令之后,我们得到的主要静态资源有:主文件 main.dart.js(1.1M),各页面的业务代码 xxx.part.js(使用 FutureBuilder 后)、图片文件。直接应用这些资源到项目中,会遇到以下问题:


  1. 功能无法及时更新:浏览器对同名文件的缓存,可能导致程序代码不被及时更新或者出现执行错乱。

  2. 首屏渲染性能差:main.dart.js 文件过大,单一文件加载、解析时间过长,势必会影响首屏的渲染时间。

  3. 无法使用 CDN:Flutter 仅支持相对路径的加载方式,无法使用当前域名以外的 CDN 域名,导致无法享受 CDN 带来的优势。


为此,在加载部分我们对 Flutter SDK 增加了如下三方面的优化,以达到线上运行的标准,优化步骤如下图所示:


优化步骤


资源文件 Hash 化


除了 web/index.html 文件之外,我们要对所有的引用到文件进行 Hash 化。对 build_system/web.dart 的修改按以下步骤进行:


  1. 遍历产物目录,并建立 ResourceMap。

  2. 分别计算每个文件的 Hash 值。

  3. 为新文件命名为 name-[hash].xxx。

  4. 修改新文件名在对应文件中的引用关系。


大文件分片


Flutter Web 编译之后会生成 main.dart.js 这一主文件,体积为 1.1M(Gzip 之后约 400K),这给页面的加载性能带来很大的影响。为此,我们对代码进行分片,借助浏览器对多文件并行加载的特性,可以有效提升页面的加载性能。


具体实施步骤是:将 main.dart.js 在 Dart 侧拆分成多份纯文本文件,前端通过 XHR 的方式并行加载并按顺序拼接成 Javascript 代码置于 <script> 标签中,从而实现分片文件的并行加载。


Hash 化以及分片之后,静态资源的引用关系


资源文件 CDN 化


由于 Flutter Web 资源引用机制的不同,即使在资源文件 Hash 化的过程中,把文件的相对路径替换成带 CDN 域名的绝对路径,也无法实现 CDN 资源的加载。同时本地测试发现图片和 Javascript 资源的加载逻辑还不尽相同,为此针对各自的加载逻辑要分别进行优化。


  • 图片处理:经过对源码的大量阅读及梳理,我们发现图片请求的 URL 首先会读取 meta 标签中 assetBase 值进行 URL 路径拼接,根据拼接好的 URL 来获取资源。目前,在项目 web/index.html 模板文件中并没有 meta 标签,于是就会根据相对路径进行请求。解决方案是在编译过程中,根据请求环境增加 meta 标签并把 content 设置为 CDN 路径。

  • JavaScript 处理:为了解决图片资源文件的加载问题,我们虽然增加了 assetBase 的 meta 标签,但发现 xxx.part.js 文件依然使用当前域名进行加载,可见 Javascript 资源的加载和图片资源加载的逻辑不尽相同。对 main.dart.js 源码分析,我们发现请求 xxx.part.js 的域名取决于包含 main.dart.js 内容的 Script 标签的 src 属性。通过对 js_helper.dart 的动态编译,我们把读取 src 属性修改为读取 window.assetBase 这一全局变量(meta标签中assetBase值加工后的变量)来实现 xxx.part.js 文件的 CDN 加载。


4.2.3 滚动性能优化


当页面出现可滚动区域时,每次页面滚动会创建大量的 Canvas。使用 Safari 的 Canvas 分析工具,我们发现问题的根本原因是页面滚动的过程中,Flutter 会频繁的创建滚动区域的 Canvas,每次创建的 Canvas 内存都在 10~70M 不等,滚动的内容越多,内存的占用就会越大,这样滚动几帧之后,内存的占用就会超过浏览器的阈值。


Safari 图形工具中展示 Canvas 的占用


Flutter 对 Canvas 的管理有一个 ReusablePool 的概念,在初始过程中会创建一定的数量的 Canvas,页面交互过程中没有变化的部分,会优先使用 pool 中已经缓存过的 Canvas 以便能够节省内存。由于 Flutter Web 自身实现了一套页面滚动机制,页面滚动过程中,会频繁计算位置信息,引起滚动区域内容被重新创建,这就是为什么每次滚动都会创建 Canvas 的原因。


我们设计的解决方案是:修改 FlutterSDK,在滚动的过程中定义一个阈值,当滚动的高度在阈值范围内,我们就会把当前的 Canvas 缓存起来。这样选择性的创建和销毁 Canvas 可以有效的缓解内存压力,从而提升页面滚动性能。


优化之后浏览器创建和销毁 Canvas 的过程


4.3 构建与部署

4.3.1 Docker 镜像定制


由于 MTFlutter Web 环境安装步骤较固定,且整个安装过程耗时较长( > 80s ) 。因此将其定制为 Docker 镜像并集成至 Talos,Flutter Web 编译阶段便能免去安装流程,有效提升构建效率。Docker 镜像定制和发布的详细流程见官方文档,本文不再赘述。其中用于定制 Flutter Web 镜像的 Dockerfile 文件如下:


FROM $BaseImage \# 继承基础镜像RUN apt-get updateRUN apt-get install rubygems -yRUN gem install flutter-cliRUN flutter-cli installENV PATH="/$User/.flutter_sdk/bin:${PATH}"ENV PUB\_HOSTED\_URL="https://xxx.com" \# 私有pub服务ENV FLUTTER\_STORAGE\_BASE_URL="https://storage.flutter-io.cn"RUN ~/.flutter_sdk/bin/flutter config --enable-web
复制代码


4.3.2 持续交付与部署


为了实现持续交付与部署,我们建立起了 Flutter Web 在 Talos(美团内部前端持续交付解决方案) 中的发布流水线:


Talos 发布流水线


可以看到,流水线中已经免去了 MTFlutter Web 环境的安装流程,现有流水线中重要节点介绍如下:


  • Flutter-Web-Build 利用 Docker 内置的 MTFlutter 进行 Web 编译。

  • Flutter-Web-Publish 负责将编译产物上传美团资源存储服务器。


五、成果展示


5.1 效果展示


我们在美团外卖商家学院(一个以文章、视频等形式帮助商家学习外卖运营知识、了解行业发展和平台策略的平台,它有很强的传播属性,具有外部投放的场景)率先落地了 Flutter Web,现以商家学院视频内容页为例,对比 Flutter Native 和 Flutter Web 的展现效果:


Flutter Native


Flutter Web


可以看出,两者的交互、视觉体验是高度一致的,既保证了业务在 App 内接近 Native 的体验,又极大提高了 Web 与 Flutter Native 的体验一致性。


5.2 页面加载性能


如前文所述,我们实施了一系列针对 Flutter Web 的资源优化手段,使得页面加载性能有较大提升,其中页面完全加载时间大致由 1300ms(TP50) 降到了 580ms(TP50),更多的性能指标数据见下图:


某 7 日性能趋势图


可以看到 Flutter Web 与现有 Web 项目性能指标数据差距已不大,可满足日常业务要求。但加载性能数据仍有较大的优化空间,我们会持续对其进行探索。


5.3 滚动性能


针对滚动优化,我们通过修改 Flutter SDK,使得 Canvas 在页面滚动时无需重复创建,而是被缓存起来。这样大大节省了内存的开销(优化后页面内存占用稳定为 100M 左右,与常规 Web 页面无异),同时在一定程度上提升了滚动性能。以商家学院文章内容页为例,对比优化前后滚动 FPS :


优化前 FPS


优化后 FPS


可以看到,Flutter Web 页面滚动性能已得到较大提升,足以应对大部分业务场景。但由于 Flutter Web 页面滚动过程中会频繁进行位置信息的计算,在复杂的业务场景(如页面存在大量动画)仍然会暴露出一定的问题。因此对滚动性能的进一步优化也会是我们未来的工作重心。


5.4 业务迭代效率


基于团队对 Flutter Web 工程化能力的建设和 Flutter 良好的跨平台特性,Flutter Web 在美团外卖商家学院改版需求的落地,大大提升了迭代效率,估算人效提升 40% 以上,计算公式为:



其中 E 代表人效提升,Ci 指的是兼容和适配所耗费的时间,Np 表示业务跨端数量,目前美团外卖商家学院在 Native 和 H5 两端完成了复用,后续在 PC 侧需求的对齐中,效率提升数值会被放大,预计人效提升达 60% 以上。同时我们将在更多的业务中进行推广与应用,提升整体业务的迭代效率。


六、总结与展望


综上所述,美团外卖商家端多元的业务形态和足够的技术“储备”,使得基于 Flutter 实现多端复用成为了可能。而 Flutter Web 在美团外卖商家学院业务中也取得了阶段性的成果,实现了 App、H5 侧的体验一致性,为后续推动更多业务线实现 App-Web 一体化打下了坚实的基础。


可以预见的是,基于 Flutter Web 实现的多端复用,势必会有效缩短项目交付周期。但由于我们对页面加载性能、滚动性能做的仍不够完美,不足以应对更加复杂的业务场景,因此我们依然还有许多工作:


  • 页面滚动性能优化: 由于 Flutter 与 Web 的布局差异,使得 dart:ui(Web) 也受 Flutter Native 的布局约束,如何打破这样的约束,是解决滚动性能问题的关键。

  • 页面加载性能优化: 当前的页面加载性能仍有较大优化空间,需要对 Flutter 进行编译干预与优化(如按需分离 main.dart.js),减小资源包大小,有效提升页面加载性能。

  • Flutter Web 基建:完善并优化开发、调试、编译、构建、部署链路,使得新老项目能快速接入 Flutter Web。

  • Flutter Web 在 PC 侧的复用:与 UED 团队共同制订 PC 与 App 适配规范,同时基于 Dart2js 和 dart:ui(Web) 的强大能力,实现逻辑的抽象,完成组件、模块的适配,达到提效最大化;

  • 跟进 Flutter 官方动向:Flutter 2.0 的发布,稳定了对 Web 的支持,同时默认采用 Canvaskit 编译模式,此模式下对页面滚动性能有较大提升。但由于 canvaskit.wasm 文件过于庞大(2.5M),降低了加载性能,因此目前仍不建议在 Web 侧直接使用 Canvaskit。不过官方承诺会在 2021 年对性能进行整体优化,还是值得期待的,我们也将保持跟进和沟通。



头图:Unsplash

作者:典胜 凌霄 海阔

原文:https://mp.weixin.qq.com/s/GjFC5_85pIk9EbKPJXZsXg

原文:Flutter Web 在美团外卖的实践

来源:美团技术团队 - 微信公众号 [ID:meituantech]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-08-03 14:303638

评论 1 条评论

发布
用户头像
深度好文
2021-08-03 15:27
回复
没有更多了
发现更多内容

接口测试工具apipost3.0版本对于流程测试和引用参数变量

Xd

Java 后端 apipost 接口测试工具

细数攻防演练中十大关键防守点

穿过生命散发芬芳

6月月更 攻防演练

【建议收藏】通俗易懂图解网络知识-第一篇

利志分享

从源码解析 MobX 响应式刷新机制

岛上码农

flutter ios 前端 安卓 6月月更

模块八:设计消息队列存储消息数据的MySQL表

jiaoxn

「架构实战营」

数据库每日一题---第10天:组合两个表

知心宝贝

前端 后端 6月月更

【sql语句基础】——查(select)(单表查询)

写代码两年半

sql MySQL 数据库 数据库· 6月月更

Teambition 协作应用心得分享|社区征文

北洋

初夏征文

Flutter库推荐Sizer 可帮助您轻松创建响应式 UI

坚果

6月月更

iShot

IT蜗壳-Tango

6月日更 6月月更

合理地配置线程池

急需上岸的小谢

6月月更

vue计算属性

小恺

6月月更

搭建前端监控,如何采集异常数据?

杨成功

架构 大前端 监控系统

设计消息队列存储消息数据的 MySQL 表格

大眼喵

「架构实战营」

Android MaterialButton使用详解,告别shape、selector

yechaoa

android 6月月更 material design MaterialButton

什么是数据驱动

奔向架构师

大数据 数据仓库 数据驱动 6月月更

ConcurrentHashMap 源码分析-初始化

zarmnosaj

6月月更

设计消息队列存储消息数据的 MySQL 表格

踩着太阳看日出

架构训练营

应用最广泛的动态路由协议:OSPF

wljslmz

OSPF 动态路由 6月月更

居家办公期间如何提升沟通效率|社区征文

北洋

初夏征文

vue中mixins的使用方法和注意点

源字节1号

软件开发 前端开发

动态规划之如何将问题抽象转化为0-1背包问题(详解利用动态规划求方案数)

未见花闻

6月月更

flutter系列之:flutter中常用的GridView layout详解

程序那些事

flutter 程序那些事 6月月更

InfoQ 极客传媒 15 周年庆征文|简述构建微服务架构的四大挑战

阿泽🧸

微服务 6月月更 InfoQ极客传媒15周年庆

测试基础之:单元测试

甜甜的白桃

单元测试 测试用例 6月月更

selenium操作元素遇到的异常

红毛丹

selenium 6月月更

模块八作业

天琪实刚亮

架构训练营

Java—流 Stream

武师叔

6月月更

逐向双碳:东数西算中的绿色需求与竞争焦点

脑极体

【Python技能树共建】文件模块

梦想橡皮擦

Python 6月月更

linux环境下实现银行间算法

乌龟哥哥

6月月更

Flutter Web在美团外卖的实践_语言 & 开发_美团技术团队_InfoQ精选文章