写点什么

Flutter For Web:人人都是大前端开发

  • 2019 年 11 月 06 日
  • 本文字数:4391 字

    阅读完需:约 14 分钟

Flutter For Web:人人都是大前端开发

Flutter For Web 已经发布半年多时间,跑在 Flutter 实践道路上的腾讯企鹅辅导团队是如何应用的?


今年 9 月,作为腾讯 Flutter 实践团队之一的我们,有幸参与了 GDD 大会上 Flutter 应用视频的录制,感受到国内众多开发者对 Flutter 的热情。两天时间,讲道理,其实没有太多的干货,但收获还是满满的。有那么点空闲的时间,也关注了一下其他的同学,大家讨论的重心还是第一天主会场的内容。我想吸引大家的一直是 Flutter 新版本 stable1.9 的重磅发布。


新版本一个重要功能就是 Flutter For Web 仓库合入 Flutter master 主仓库,意味着我们可以真正地使用一套代码、一套资源部署大前端。辅导团队经过一段时间的准备,使用 Flutter 开发的 Web 页面也即将发布,希望和大家分享下实践过程和踩坑实例,欢迎一起交流探讨。


一、发布准备

我们对辅导 App 中上课页学习简报页面进行改造,支持浏览器中打开。


00:00 / 00:00
    1.0x
    • 2.0x
    • 1.5x
    • 1.25x
    • 1.0x
    • 0.5x
    网页全屏
    全屏
    00:00


    iOS 中打开 Flutter Web 页面


    00:00 / 00:00
      1.0x
      • 2.0x
      • 1.5x
      • 1.25x
      • 1.0x
      • 0.5x
      网页全屏
      全屏
      00:00


      浏览器中打开 Flutter Web 页面


      网络 cgi 接口现网暂时没有配置,先使用假数据展示。开发过程中,我们涉及到 Flutter 页面转换、网络请求、与 Native 交互、cookie 等,最终的页面发布我们还需要在发布系统中配置。有关基础开发及发布的知识,大家可以关注互动视频大神 weiwei 的一篇分享:《如何使用 Flutter For Web 开发一个需求并上线》,文章介绍的很清楚,最终发布上线需要的产物是:


      • index.html:h5 访问入口;

      • main.dart.js:dart 代码转化成 js 后的产物;

      • assets:静态资源,包括图片、字体,以及我们定制的 js 文件。



      二、页面改造

      Flutter For Web 谷歌官方一直建议暂时不要在生产环境使用。但如果不尝试,我们永远不知道这里会藏有什么样的秘密,什么样的场景适合。对现有 Flutter 改造,增加对浏览器的支持,突破而不失稳重。


      1. 增加 Web 支持

      我们原先的版本使用 Flutter 1.5.4,增加 Web 支持,我们需要切换到最新的 master 分支,改造运行。



      如果我们只是新建一个新的项目,只需切换到最新的 master 分支,创建一个新的项目即可。创建完成之后,根目录下会多出一个 web 文件夹,里面只有一个 web 入口文件 index.html。



      2. 修改 dart:io 库

      原有项目中如果直接运行 flutter run -d chrome,会发现控制器中 import 报错,原因是 dart: io 库不支持 web。io 库是 Flutter 中非常常用的库,主要是平台相关的一些 api,改造的第二步我们就需要屏蔽 dart:io 的引入。


      Flutter For Web 最终运行的是在浏览器中的 js 代码,Flutter For Phone 使用,Platform 引擎与 Native 通信。js 在平台上是通过其他的系统支持(iOS 中的 JavaScriptCore) 与 Native 通信,二者是完全不同的方式,所以 dart: io 无法继续支持。


      既然 dart: io 不支持 web,那我们仍然想使用原先的 Flutter 业务 UI 代码,该 如何实现(上文我们说过,我们想使用同一套代码、同一套资源整合大前端)?我们使用不同平台下支持的能力库区分。


      import 'main_web.dart' if (dart.library.io) "main_io.dart"; //dart.library.io
      复制代码



      UI 侧我们仍然使用同一套代码,逻辑侧通过 library 库分区 native 还是 web,逻辑侧对应不同的平台实现。这几天,我看到一个新的区分方法,简单而且实用:web 侧我们可以判断 0 和 0.0 是否是一个对象。



      区分完不同平台,我们还需要辨别不同设备,这里我建议搭建 app 初始化的时候配置完成,后续方便实用。


      //initConfig.inWeb = identical(0, 0.0);/////////////////////////////////////
      enum K12Platform {iPhone, iPad, android}
      class Config { static bool inWeb = false;
      static K12Platform k12platform = K12Platform.iPhone;
      static bool inProduction = bool.fromEnvironment("dart.vm.product");}
      复制代码


      3. 网络请求

      原有 Flutter 项目我们通过 MJFlutter 调用 Native 的网络接口,实现数据请求。辅导 web 侧是通过 CGI 请求获得后台数据,现在 web 需要另一种方式,给大家推荐几种方式:


      a. package:http/http.dart


      pub 链接:https://pub.dev/packages/http#-readme-tab-


      import 'dart:convert' as convert;import 'package:http/http.dart' as http;
      main(List<String> arguments) async { // This example uses the Google Books API to search for books about http. // https://developers.google.com/books/docs/overview var url = "https://www.googleapis.com/books/v1/volumes?q={http}";
      // Await the http get response, then decode the json-formatted responce. var response = await http.get(url); if (response.statusCode == 200) { var jsonResponse = convert.jsonDecode(response.body); var itemCount = jsonResponse['totalItems']; print("Number of books about http: $itemCount."); } else { print("Request failed with status: ${response.statusCode}."); }}
      复制代码


      b. package:http/html.dart


      import 'dart:html' as html;
      var req = html.HttpRequest.getString("https://www.googleapis.com/books/v1/volumes?q={http}");
      复制代码


      c. package:dio/dio.dart


      pub 链接:https://pub.dev/packages/dio#-installing-tab-


      dio 这个网络库,大家之前可能用过,需要注意一点,3.0 之前的版本 Flutter For Web 不支持 (dart:io)。3.0 之后的版本,这个库经过官方大改造,现已支持 Flutter For Web 开发。


      我推荐的是 dio 这个库,使用起来会比较方便,参数简单好理解。


      var url = 'https://fudao.qq.com/xxx';    BaseOptions options = BaseOptions(        method: 'get',        baseUrl: url,        connectTimeout: 5000,        receiveTimeout: 100000,        contentType: 'json',        responseType: ResponseType.plain,        headers: {          'host' : "fudao.qq.com", // 一般就是 webview 的 url 的 host          'cookieHeader': getWebCookie(),          'accept': "*",          'referer': "https://fudao.qq.com/"        }      );    Dio dio = Dio(options);
      try { Response response = await dio.get(url); if (callback != null) { callback(response.statusCode, response.statusMessage, response.data); } } on DioError catch(e) { if(e.response != null) { print(e.response.data); print(e.response.headers); print(e.response.request); } else{ print(e.request); print(e.message); } }
      复制代码


      有了基本的网路请求,配置完 CGI,接下来就是界面的展示。但是你会发现,所有的业务请求都没有回包,无法得到数据。如果作为移动端开发,没有经常接触到 http 请求,一下子发蒙,我觉得可以理解。这个是 Web 开发中经常遇到的 跨域问题。前端开发们这里可以举手了,这个问题我会。简单的处理方式就是开发过程中挂上一个代理,具体就不详细展开了。


      网路问题解决,进入发布调试,上文我们介绍过 Flutter For Web 的最终产物是三个文件。如何发布,前端同学又可以举手了。有一个简单的方式:github pages(配置方法:https://pages.github.com/)。


      三、JavaScript 扩展

      与使用 Flutter 实现的页面不同,Flutter For Web 的页面使用 Http 请求获取后台数据。Http 请求中很重要的一点是 header 中的 cookie,这里就需要通过 dart 与 js 之间的交互,还是要称赞下 Flutter 团队的实力。


      官方说明:https://dart.dev/web/js-interop


      pub 地址:https://pub.dev/packages/js



      新建 native_api.js 文件



      dart 封装 js 方法



      index 入口文件中引入 js 文件



      添加完 js 支持后,dart 侧调用 getStringFromJS 方法,并打印结果。


      通过 js.dart 这个库,可以实现 web 侧的主要功能。例如 cookie、localStorage。



      native_api.js 中声明基本方法



      dart 封装 js 方法


      处理完 Web 侧的基本能力后,需要部署 dart 转成 js 后,与 native 交互的能力。我们知道,目前 app 中 native 与 web 交互的方式主要通过 jsBridge。Native 侧我们已经有了成熟的体系主动调用和接收调用 web 的能力,这部分我们可以不需要修改,减少开发工作量。在 Flutter 侧我们需要添加交互支持,也就是需要在 native_api.js 中添加与 native 交互的能力。


      JavaScript 调用 Native 的方式,主要有两种:注入 API 和 拦截 URL SCHEME,你可以根据自己的业务能力,选择合适的方式。相较于 JavaScript 调用 Native,Native 调用 JavaScript 比较简单,不管是 iOS 的 UIWebView 还是 WKWebView,还是 Android 的 WebView 组件,都以子组件的形式存在于 View/Activity 中,直接调用相应的 API 即可。


      例如 iOS 中 JavaScript 调用 Native,设置 webView 的 title:


      function callNativeFunction() {  var url = 'jsbridge://edu/setCenterTitle?p=%7B%22text%22%3A%22%E9%B1%BC%E9%A5%BC%22%7D#2?title=aaa&desc=bbb&link=http%3A%2F%2Fwww.baidu.com';
      var jsbridgeNode = document.createElement('iframe'); var removeTimeStamp; jsbridgeNode.style.cssText = 'display:none;width:0px;height:0px;'; jsbridgeNode.onerror = function(e) { // 在 android 4.0-4.3 中,script 节点的 src 赋值成 jsbridge://ui/showDialog 的形式会报错 e.stopPropagation(); } /* ios 必须先赋值, 然后 append, 否者连续的 api 调用会间隔着失败 也就是 api1(); api2(); api3(); api4(); 的连续调用, 只有 api1 和 api3 会真正调用到客户端 */ jsbridgeNode.src = url;
      var root = document.body || document.documentElement; root.appendChild && root.appendChild(jsbridgeNode);
      setTimeout(function() { jsbridgeNode && jsbridgeNode.parentNode && jsbridgeNode.parentNode.removeChild(jsbridgeNode); }, 500);}
      复制代码


      四、展望

      整体来说,Flutter For Web 达到成熟还有一段时间。在这段时间里,尽早布局,未免不是一件好事。Flutter For Web 现阶段的使用场景并不多,不如直接使用 Web 开发便捷、稳定。但我们可以通过 Flutter,拉近与大前端的距离,感受大前端的魅力,做一个真正的大前端开发。


      腾讯企鹅辅导团队会继续实践,不管是 Flutter 实现的页面,还是 Flutter 转 Web 的页面。Flutter For Web 一个复杂的场景,我们会尝试降级及热更新能力,与 Web 同学通力合作,封装 App 和 Web 的基础 API、集成 CI 等,欢迎与我们一起交流。



      技术服务于业务,业务推动技术,二者并行,提升用户体验,Flutter 道路上期待大家的反馈。


      专家介绍:


      涂金林,来自江苏苏州。2014 年和 2017 年分别获得东南大学本科和硕士学位。2017 年加入腾讯在线教育部,主要从事移动客户端开发。入职以来,参与腾讯企鹅辅导、腾讯课堂 iOS 和 Android 开发,腾讯企鹅辅导 iOS 负责人。目前主要担任腾讯在线教育 Flutter 项目技术负责人。


      2019 年 11 月 06 日 16:484911

      评论

      发布
      暂无评论
      发现更多内容

      带你掌握4种Python排序算法

      华为云开发者社区

      Python 编程 算法 排序 冒泡排序

      拍乐云 x 美上美学|监管当下,如何回归教育本质,打造品质和体验?

      拍乐云Pano

      RTC

      anyRTC 重磅推出在线实时 K 歌解决方案

      anyRTC开发者

      音视频 WebRTC 实时通讯 在线KTV

      超清音质实时会议系统的背后 ,深入剖析 AliCloudDenoise 语音增强算法

      阿里云视频云

      阿里云 音视频 语音 视频会议 算法实践

      浪潮云说丨叮!这是一份浪潮云物联网平台的简历,请查收!

      浪潮云

      云计算

      react源码解析19.手写迷你版react

      全栈潇晨

      react.js

      Pandas高级教程之:处理缺失数据

      程序那些事

      Python 数据分析 pandas 程序那些事

      深入浅出 LVS 负载均衡(四)实操 DR 模型、Keepalived DR 模型的高可用

      UCloud技术

      负载均衡

      融云年中大促 新老用户同享超值优惠

      融云 RongCloud

      面试官:谈谈你对geohash的理解和如何实现附近人功能呢?

      我是阿沐

      redis 面试 geohash

      云小课 | 云硬盘不用了如何处理?

      华为云开发者社区

      华为云 云硬盘 退订 删除 回收站

      如何设计好一个接口

      🎄新

      架构 设计 接口

      百度与张江集团达成战略合作,AI助推上海城市数字化转型

      百度大脑

      人工智能

      JS完美收官之——js加载时间线

      法医

      大前端 js 6月日更

      使用 Java 编写 Apache APISIX 插件

      Apache APISIX 中国社区

      Java 云原生 后端 插件 网关

      作为新时代的Java工程师,你需要具备什么能力?

      卢卡多多

      Java 能力提升 6月日更 六月

      想要做好微服务化,这个核心对象要管好

      BoCloud博云

      微服务

      ☕【JVM监控实战】教会你使用Arthas(监控ElasticSearch服务)

      浩宇天尚

      JVM 故障定位 Arthas 6月日更

      Kafka 源码解析:Server 端的运行过程

      华为云开发者社区

      kafka 网络 Server 端 SocketServer

      破局团伙作案风险——图卷积神经网络(GCN)算法

      索信达控股

      金融科技 数字化转型 数据建模 风险管理 图卷积神经网络

      令人头疼的 Java 异常知识点总结

      村雨遥

      Java 面试 6月日更

      不知道我写的链表是否能看懂

      八点半的Bruce.D

      php 数据结构 链表

      RS485通信如何设计EMC电路?

      不脱发的程序猿

      电路设计 通信总线 RS485 EMC设计 通信抗干扰

      “云智技术论坛”即将召开,百度智能云带来端边云全面智能化平台

      百度大脑

      人工智能 物联网 云智一体

      zookeeper原生api操作

      赵镇

      zookeeper

      HarmonyOS 实战—服务卡片初体验

      爱吃土豆丝的打工人

      HarmonyOS 服务卡片 鸿蒙卡片

      原来 spring.xml 配置的 destroy-method 需要用到向虚拟机注册钩子来实现!

      小傅哥

      Java spring 注册虚拟机钩子 init-method destroy-method

      Docker被谁干掉了?

      BUG侦探

      Docker 云计算 Go 语言

      bzz|chia矿池挖矿系统APP开发搭建

      薇電13242772558

      区块链

      同样都是使用接口,JAVA和Go差距咋就这么大呢?

      面向加薪学习

      JAVA面向对象(十一)--多态

      加百利

      Java 6月日更 多态

      Flutter For Web:人人都是大前端开发-InfoQ