flutter 专题 五十六 Google 2020开发者大会Flutter专题

由于疫情的原因,今年的Google 开发者大会 (Google Developer Summit) 在线上举行,本次大会以"代码不止"为主题,全面介绍了产品更新以及一系列面向本地开发者的技术支持内容。我比较关注的是移动开发,在本次大会上,关于Flutter 主题的演讲主要从 Flutter 性能方面优化和新功能进行展开。

作为全球增长速度第二的开源项目,越来越多国内开发者使用 Flutter 实现跨平台开发,包括腾讯英语君团队、阿里闲鱼团队等等。其在 开放性上的进步,得益于开源社区、生态建设、对 Web 的支持。

有兴趣的读者可以通过Google Developer官网进行学习:Google Developer官网

下面我们就来看一下这些新功能和性能上的优化。

Flutter 性能优化

首先为我们带来演讲的是Google 软件工程师李宇骞,他是Flutter 团队的一位软件工程师,主要专注于提升其性能。下面是具体的演讲内容:

2019 下半年,Flutter 团队共收到 23 个量化的性能提升;2020 上半年,Flutter 团队共收到 27 个量化的性能提升。2020 上半年 Flutter 团队共收到来自 78 位开发者的 49 个性能改进。

工具的性能十分重要,性能测试也同样至关重要,拥有良好的性能测试可以:

  • 快速重现问题;
  • 迭代和验证解决方案;
  • 提供数据,激励进一步的工作并防止倒退。

通常,能耗与渲染速度相关,每一帧渲染时间越长则能耗就越高,但能耗并不能衡量渲染速度,因为在某些情况下渲染速度快也可能会导致能耗升高,渲染速度慢也可能不耗能。

CPU 上运行时间虽然短,但由于新的算法利用了更多的 GPU 核心,所以 GPU 能耗反而增加;有些 CPU 上的任务被别的 I/O 或 GPU 任务阻塞,进行了长时间的等待,而等待的时间内并无过多能耗。

因此,在速度之外增加能耗测试是十分必要的。因为 Flutter 团队在 GitHub 上收到的大部分能耗问题都和 iOS 相关,所以此次 Flutter 首先加入了 iOS 的能耗测试,Android 的能耗测试工具会于后续加入。

开发者可以使用 Flutter Gallery App 在 Timeline 中查看 CPU/GPU 的使用率,也可以用集成测试自动检测 CPU/GPU 的使用率。

Flutter 还新加入了 SkSL 着色器编译预热功能,来帮助开发者消除着色器编译卡顿。如果一个 Flutter 程序第一次渲染某类动画时出现明显的卡顿,但是之后渲染这些动画时,卡顿完全消失,那么这就很可能是着色器编译卡顿。开发者可以使用 --trace-skia,然后检查 Timeline 来确认是否为着色器卡顿。

值得一提的是,SkSL 可以实现自动化生成与测试,这对于需要持续更新的 Flutter App 来说,可以节省很多的人力。

内存和包体积的测试工具

接下来,是由Flutter 用户体验研究员侯悠扬带来的测试工具专题。侯悠扬于 2017 年加入 Google,并于 2019 年加入 Flutter 团队。她是 Flutter 团队一名用户体验研究员,关注提升 Flutter 产品和开发工具的程序员体验。

此次,Flutter 团队更新了Dart开发工具。Dart 开发工具是面向 Flutter 和 Dart 开发人员的工具套件,包括如下一些小工具:

  • 布局检查(Inspector)
  • 性能调试(Performance)
  • 内存调试(Memory)
  • 网络调试(Network)
  • 包体积调试(App Size)
  • 调试器(Debugger)
  • 日志(Logging)

连接上设备然后运行Flutter应用,点击Android Studio底部工具栏中的【Open DevTools】按钮即可开启调试功能。

内存调试器功能

Flutter的内存调试器提供如下功能:

  • 事件窗格(Dart 和 Android 内存)
  • 手动和自动快照(snapshot)和垃圾回收(GC)
  • 内存分析
  • 内存堆分配累加器(Heap Allocation Accumulators)
  • 通过命令行界面将内存统计信息到处到 JSON 文件

内存测试

内存测试提供如下功能:

  • 通过 ADB 交互直接进行内存测试
  • Dart 开发工具内存测试
  • iOS 内存测试

更多信息可以通过这篇由 Flutter 工程师撰写的文章进行了解:怎么进行Flutter内存测试

包体积调试器功能

包体积调试器提供如下功能:

  • 可视化了应用程序的总大小,包括功能级别的 Dart AOT 快照;
  • 分析快照和应用包(APK,IPA 等);
  • 分析快照或应用程序包(APK,IPA 等)的差异;
  • 查看软件包级别的应用大小归因数据。

Pigeon与Flutter混合开发

什么是Pigeon

在早期的hybird开发模式中,前端和Native交互时需要native双端为JS提供接口。这种情况下如何规范命名,参数等就成了一个问题,如果单独维护一份协议文件,三端依照协议文件进行开发,很容易出现协议更改后,没有及时同步,又或者在实际开发过程没有按照规范,可能导致各种意外情况。

同样,在Flutter插件包的开发中,因为涉及到Native双端代码开发能力,Dart侧暴露统一的接口给使用者,也会出现同样的问题,此时Pigeon应运而生,Pigeon是Flutter官方推荐插件管理工具,可以使用来解决和优化 Native 插件开发上 platform channel 相关的问题。

Flutter官方提供的Pigeon插件,通过dart入口,生成双端通用的模板代码,Native部分只需通过重写模板内的接口,无需关心methodChannel部分的具体实现,入参,出参也均通过生成的模板代码进行约束。接口新增,或者参数修改,只需要在dart侧更新协议文件,生成双端模板,即可达到同步更新,有效的避免了参数修改,参数新增带来的双端代码不同步的问题,下面是Pigeon工作原理示意图。

下面是Pigeon给出的示例:

可以看到接入Pigeon后整体代码简洁了不少,而且规范了类型定义。

Pigeon接入

接下来我们看一下如何从零接入Pigeon。截止目前,Pigeon已经发布了0.1.15版本,如下图所示。

首先,新建一个名为testpigeon的Flutter项目,打开项目的pubspec.yaml文件,并添加如下依赖代码。

复制代码
dependencies:
  pigeon: ^0.1.15

然后,按照官方的要求在项目目录下新建一个pigeons目录,作为存放dart侧的入口文件,内容为接口、参数、返回值的定义等,以及后面通过pigeon的命令,生产native端代码。接下来,新建一个message.dart 文件,并添加如下。

复制代码
import 'package:pigeon/pigeon.dart';

class SearchRequest {
  String query;
}

class SearchReply {
  String result;
}

@HostApi()
abstract class Api {
  SearchReply search(SearchRequest request);
}

在上面的message.dart 文件中,通过 @HostApi() 注解标示了通信对象和接口,之后我们只需要执行如下命令,就可以生成对应代码到工程中。

复制代码
flutter pub run pigeon  --input pigeons/message.dart

其实上面的命令是下面命令的简写方式:

复制代码
flutter pub run pigeon  --input pigeons/message.dart  --dart_out lib/pigeon.dart  --objc_header_out ios/Runner/pigeon.h --objc_source_out ios/Runner/pigeon.m --java_out android/app/src/main/java/Pigeon.java --java_package "com.xzh.testpigeon"

命令的参数的含义如下:

  • --input:引入了我们创建的 message.dart 文件;
  • --dart_out:输出了 dart 模板文件;
  • --objc_header_out 和 --objc_source_out 输出了 object-c 文件;
  • --java_out 输出了 java 文件;

命令执行后 dart 文件输出到 lib 目录下, object-c 文件输出到了 ios/Runner 目录下,java 文件输出到指定的 com.xzh.testpigeon" 包名路径下,之后就可以开始正式接入。然后我们分别使用Android Studio和Xcode打开原生工程代码。

Android 工程代码

使用Android Studio打开Flutter项目的原生Android工程,生成的代码如下:

复制代码
// Autogenerated from Pigeon (v0.1.15), do not edit directly.
// See also: https://pub.dev/packages/pigeon

package com.xzh.testpigeon;

import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.StandardMessageCodec;
import java.util.ArrayList;
import java.util.HashMap;

/** Generated class from Pigeon. */
@SuppressWarnings("unused")
public class Pigeon {

  /** Generated class from Pigeon that represents data sent in messages. */
  public static class SearchReply {
    private String result;
    public String getResult() { return result; }
    public void setResult(String setterArg) { this.result = setterArg; }

    HashMap toMap() {
      HashMap<String, Object> toMapResult = new HashMap<>();
      toMapResult.put("result", result);
      return toMapResult;
    }
    static SearchReply fromMap(HashMap map) {
      SearchReply fromMapResult = new SearchReply();
      Object result = map.get("result");
      fromMapResult.result = (String)result;
      return fromMapResult;
    }
  }

  /** Generated class from Pigeon that represents data sent in messages. */
  public static class SearchRequest {
    private String query;
    public String getQuery() { return query; }
    public void setQuery(String setterArg) { this.query = setterArg; }

    HashMap toMap() {
      HashMap<String, Object> toMapResult = new HashMap<>();
      toMapResult.put("query", query);
      return toMapResult;
    }
    static SearchRequest fromMap(HashMap map) {
      SearchRequest fromMapResult = new SearchRequest();
      Object query = map.get("query");
      fromMapResult.query = (String)query;
      return fromMapResult;
    }
  }

  /** Generated interface from Pigeon that represents a handler of messages from Flutter.*/
  public interface Api {
    SearchReply search(SearchRequest arg);

    /** Sets up an instance of `Api` to handle messages through the `binaryMessenger` */
    static void setup(BinaryMessenger binaryMessenger, Api api) {
      {
        BasicMessageChannel<Object> channel =
            new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.Api.search", new StandardMessageCodec());
        if (api != null) {
          channel.setMessageHandler((message, reply) -> {
            HashMap<String, HashMap> wrapped = new HashMap<>();
            try {
              @SuppressWarnings("ConstantConditions")
              SearchRequest input = SearchRequest.fromMap((HashMap)message);
              SearchReply output = api.search(input);
              wrapped.put("result", output.toMap());
            }
            catch (Exception exception) {
              wrapped.put("error", wrapError(exception));
            }
            reply.reply(wrapped);
          });
        } else {
          channel.setMessageHandler(null);
        }
      }
    }
  }
  private static HashMap wrapError(Exception exception) {
    HashMap<String, Object> errorMap = new HashMap<>();
    errorMap.put("message", exception.toString());
    errorMap.put("code", exception.getClass().getSimpleName());
    errorMap.put("details", null);
    return errorMap;
  }
}

上面生成的 Pigeon.java 代码中包含了 Api 接口用于开发者实现交互逻辑,同时开发者可以通过 SearchRequest 获取 dart 发送过来的请求,通过 SearchReply 返回数据给 dart 。然后,还需要在Android的入口文件MainActivity 中实现 Api 接口来完成数据交互,代码如下。

复制代码
public class MainActivity extends FlutterActivity {

  private class MyApi implements Pigeon.Api {
    @Override
    public Pigeon.SearchReply search(Pigeon.SearchRequest request) {
      Pigeon.SearchReply reply = new Pigeon.SearchReply();
      reply.setResult(String.format("Hi %s!", request.getQuery()));
      return reply;
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
    Pigeon.Api.setup(getFlutterView(), new MyApi());
  }
}

首先,我们继承 Pigeon.Api 实现了 MyApi 对象,然后在 search() 方法中通过 request.getQuery() 获取 dart 的请求数据,并且通过 Pigeon.SearchReply 的 setResult 返回 数据给dart 端,最后通过Pigeon.Api.setup(getFlutterView(), new MyApi())启动。

iOS

使用Xcode打开Flutter项目的iOS工程,把生成的 pigeon.h 和 pigeon.m 文件 link 到 Xcode 工程里,之后如下代码所示在 AppDelegate.h 引入 Api 协议。

复制代码
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "pigeon.h"

@interface AppDelegate : FlutterAppDelegate<Api>

@end

接下来,在 AppDelegate.m 中实现 search 接口,并在收到的 dart 消息后基于回复,最后调用 ApiSetup()方法将完成注册。

复制代码
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];
  // Override point for customization after application launch.
  FlutterViewController* controller =
      (FlutterViewController*)self.window.rootViewController;
  ApiSetup(controller.binaryMessenger, self);
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}


-(SearchReply *)search:(SearchRequest*)input error:(FlutterError **)error {
    SearchReply* result = [[SearchReply alloc] init];
    result.result  = [NSString stringWithFormat:@"%s%@","Hi ",input.query];
    return result;
}


@end

Dart测试

最后我们在 Dart 代码中新建一个测试的代码,如下所示。

复制代码
import 'pigeon.dart';

void main() {
  testWidgets("test pigeon", (WidgetTester tester) async {
    SearchRequest request = SearchRequest()..query = "Aaron";
    Api api = Api();
    SearchReply reply = await api.search(request);
    expect(reply.result, equals("Hi Aaron!"));
  });
}

Flutter 在阿里巴巴的应用

首先,主持人为我们介绍了Flutter的历史,介绍围绕美观、高效、流程和开放等几个方面来介绍Flutter。

接下来,阿里巴巴的无线技术专家门柳介绍Flutter在阿里巴巴的应用,闲鱼是阿里巴巴Flutter技术实践的先驱,也是国内最早尝试Flutter技术的大型互联网公司,而阿里巴巴旗下的淘宝也不甘示弱,也在某些模块结成Flutter,不过大多是业务级别的模块,而没有像闲鱼那样大规模使用。我们可以从下图看到Flutter在阿里巴巴的使用情况。

那为什么,这么多的移动应用开始使用Flutter来进行开发呢?首先,让我们来了解下跨平台技术的发展历程。

可以发现,移动跨平台开发经历了大约四个阶段:

  • 早期的WebView加载方案
  • 原生API桥接的Hybrid方案
  • 原生渲染方案(Web语法+原生UI)
  • 自绘渲染(独立布局/渲染)

而Flutter就是采用的自绘渲染方案,有兴趣的童鞋可以研究以下Flutter的架构。为什么选择Flutter进行跨平台应用开发呢,下面是Flutter所具有的一些优势:

不过,Flutter也不是万能的,Flutter目前处于快速迭代的阶段,所以保险起见,我们只在一些常规的业务开发和模块化的UI界面开发和部分游戏中使用Flutter。

总结起来,就是在一些富交互类应用和新型的应用中使用Flutter,对于视频、直播等渲染要求高的则继续使用原生进行开发。

那使用Flutter进行应用开发时,有哪些经验和问题需要注意呢?下图显示了阿里巴巴在使用Flutter进行应用开发时遇到的一些问题,大家使用时需要规避。

首先遇到的问题是,由于Flutter使用的是Dart进行开发,无疑增加了开发者的学习成本。其次,对于大型应用来说,如何保证代码质量,如何在多个平台运行自动化测试脚本也是一个问题;并且由于Flutter作为一门新的技术,如何快速的将老得业务迁移过来也是大家需要考虑的问题。总结一下,就是调试、测试、状态管理、缓和导航栈管理、跨平台兼容以及如何寻找解决方案的问题。

尽管Flutter已经提供了很多的工具,但是如何将它融入到阿里巴巴的客户端开发工作流中,是大家需要考虑的问题。

首先,为了提升开发效率,降低初期的接入成本,我们将Flutter Toolkit融入到Alibab DevOps工作流中,并自研了一些工具、打包和发布平台以及搭建调试环境。接下来,我们基于现存的技术积累,研发了一些中间件。

下面来看一个实例,即如何解决多图列表页面的内存占用问题。这类问题的特征如下:

  • 页面很长,图片很多,首次加载时间很长
  • 大量图片同时加载并生成纹理,内存飙升
  • Sliver中每项Cell拆分粒度很大,单个Cell占用多屏,难以回收

    对于列表Flutter列表内存回收的问题,大家可以阅读 细化 Flutter List 内存回收,解决大 Cell 问题这篇文章。

对于上面的多图长列表的内存问题,我们可以从以下几个方面着手进行优化:

  • 拆分Cell,使每一项变得更小
  • 根据坐标判断图片是否在屏幕内,进而进行图片的懒加载和回收
  • 提前获取图片的宽高大小,减少布局和重绘
  • 以图片为单位进行纹理回收,而不是Sliver中的每项Cell为单位
  • 外接原生图片库,实现共享本地缓存

最后,我们来看一下Flutter在阿里巴巴的体系化建设。首先,Flutter的体系化建设主要从基础能力建设、研发平台和可持续迭代等几个方面着手。

下面是Flutter在阿里巴巴平台建设的具体的一些方案。

目前,Flutter在阿里巴巴已经经过了大规模的应用,并且我们自己的技术体系建设也在稳步推荐中,后面会将建设的一些成果通过社区分享出来。

附:Google 开发者大会

相关推荐
程序员老刘2 小时前
一杯奶茶钱,PicGo + 阿里云 OSS 搭建永久稳定的个人图床
flutter·markdown
奋斗的小青年!!5 小时前
OpenHarmony Flutter 拖拽排序组件性能优化与跨平台适配指南
flutter·harmonyos·鸿蒙
小雨下雨的雨7 小时前
Flutter 框架跨平台鸿蒙开发 —— Stack 控件之三维层叠艺术
flutter·华为·harmonyos
行者968 小时前
OpenHarmony平台Flutter手风琴菜单组件的跨平台适配实践
flutter·harmonyos·鸿蒙
小雨下雨的雨9 小时前
Flutter 框架跨平台鸿蒙开发 —— Flex 控件之响应式弹性布局
flutter·ui·华为·harmonyos·鸿蒙系统
cn_mengbei9 小时前
Flutter for OpenHarmony 实战:CheckboxListTile 复选框列表项详解
flutter
cn_mengbei9 小时前
Flutter for OpenHarmony 实战:Switch 开关按钮详解
flutter
奋斗的小青年!!9 小时前
OpenHarmony Flutter实战:打造高性能订单确认流程步骤条
flutter·harmonyos·鸿蒙
Coder_Boy_10 小时前
Flutter基础介绍-跨平台移动应用开发框架
spring boot·flutter
cn_mengbei10 小时前
Flutter for OpenHarmony 实战:Slider 滑块控件详解
flutter