Google 2020開發者大會Flutter專題

因爲疫情的緣由,今年的Google 開發者大會 (Google Developer Summit) 在線上舉行,本次大會以「代碼不止」爲主題,全面介紹了產品更新以及一系列面向本地開發者的技術支持內容。我比較關注的是移動開發,在本次大會上,關於Flutter 主題的演講主要從 Flutter 性能方面優化和新功能進行展開。前端

做爲全球增加速度第二的開源項目,愈來愈多國內開發者使用 Flutter 實現跨平臺開發,包括騰訊英語君團隊、阿里閒魚團隊等等。其在 開放性上的進步,得益於開源社區、生態建設、對 Web 的支持。
在這裏插入圖片描述
有興趣的讀者能夠經過Google Developer官網進行學習:Google Developer官網java

下面咱們就來看一下這些新功能和性能上的優化。android

Flutter 性能優化

首先爲咱們帶來演講的是Google 軟件工程師李宇騫,他是Flutter 團隊的一位軟件工程師,主要專一於提高其性能。下面是具體的演講內容:ios

2019 下半年,Flutter 團隊共收到 23 個量化的性能提高;2020 上半年,Flutter 團隊共收到 27 個量化的性能提高。2020 上半年 Flutter 團隊共收到來自 78 位開發者的 49 個性能改進。git

工具的性能十分重要,性能測試也一樣相當重要,擁有良好的性能測試能夠:程序員

  • 快速重現問題;
  • 迭代和驗證解決方案;
  • 提供數據,激勵進一步的工做並防止倒退。

一般,能耗與渲染速度相關,每一幀渲染時間越長則能耗就越高,但能耗並不能衡量渲染速度,由於在某些狀況下渲染速度快也可能會致使能耗升高,渲染速度慢也可能不耗能。github

CPU 上運行時間雖然短,但因爲新的算法利用了更多的 GPU 核心,因此 GPU 能耗反而增長;有些 CPU 上的任務被別的 I/O 或 GPU 任務阻塞,進行了長時間的等待,而等待的時間內並沒有過多能耗。算法

所以,在速度以外增長能耗測試是十分必要的。由於 Flutter 團隊在 GitHub 上收到的大部分能耗問題都和 iOS 相關,因此這次 Flutter 首先加入了 iOS 的能耗測試,Android 的能耗測試工具會於後續加入。api

開發者可使用 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 開發者大會

相關文章
相關標籤/搜索