因爲疫情的緣由,今年的Google 開發者大會 (Google Developer Summit) 在線上舉行,本次大會以「代碼不止」爲主題,全面介紹了產品更新以及一系列面向本地開發者的技術支持內容。我比較關注的是移動開發,在本次大會上,關於Flutter 主題的演講主要從 Flutter 性能方面優化和新功能進行展開。前端
做爲全球增加速度第二的開源項目,愈來愈多國內開發者使用 Flutter 實現跨平臺開發,包括騰訊英語君團隊、阿里閒魚團隊等等。其在 開放性上的進步,得益於開源社區、生態建設、對 Web 的支持。
有興趣的讀者能夠經過Google Developer官網進行學習:Google Developer官網java
下面咱們就來看一下這些新功能和性能上的優化。android
首先爲咱們帶來演講的是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 開發人員的工具套件,包括以下一些小工具:
鏈接上設備而後運行Flutter應用,點擊Android Studio底部工具欄中的【Open DevTools】按鈕便可開啓調試功能。
Flutter的內存調試器提供以下功能:
內存測試提供以下功能:
更多信息能夠經過這篇由 Flutter 工程師撰寫的文章進行了解:怎麼進行Flutter內存測試
包體積調試器提供以下功能:
在早期的hybird開發模式中,前端和Native交互時須要native雙端爲JS提供接口。這種狀況下如何規範命名,參數等就成了一個問題,若是單獨維護一份協議文件,三端依照協議文件進行開發,很容易出現協議更改後,沒有及時同步,又或者在實際開發過程沒有按照規範,可能致使各類意外狀況。
一樣,在Flutter插件包的開發中,由於涉及到Native雙端代碼開發能力,Dart側暴露統一的接口給使用者,也會出現一樣的問題,此時Pigeon應運而生,Pigeon是Flutter官方推薦插件管理工具,可使用來解決和優化 Native 插件開發上 platform channel 相關的問題。
Flutter官方提供的Pigeon插件,經過dart入口,生成雙端通用的模板代碼,Native部分只需經過重寫模板內的接口,無需關心methodChannel部分的具體實現,入參,出參也均經過生成的模板代碼進行約束。接口新增,或者參數修改,只須要在dart側更新協議文件,生成雙端模板,便可達到同步更新,有效的避免了參數修改,參數新增帶來的雙端代碼不一樣步的問題,下面是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"
命令的參數的含義以下:
命令執行後 dart 文件輸出到 lib 目錄下, object-c 文件輸出到了 ios/Runner 目錄下,java 文件輸出到指定的 com.xzh.testpigeon" 包名路徑下,以後就能夠開始正式接入。而後咱們分別使用Android Studio和Xcode打開原生工程代碼。
使用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())
啓動。
使用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 代碼中新建一個測試的代碼,以下所示。
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就是採用的自繪渲染方案,有興趣的童鞋能夠研究如下Flutter的架構。爲何選擇Flutter進行跨平臺應用開發呢,下面是Flutter所具備的一些優點:
不過,Flutter也不是萬能的,Flutter目前處於快速迭代的階段,因此保險起見,咱們只在一些常規的業務開發和模塊化的UI界面開發和部分遊戲中使用Flutter。
總結起來,就是在一些富交互類應用和新型的應用中使用Flutter,對於視頻、直播等渲染要求高的則繼續使用原生進行開發。
那使用Flutter進行應用開發時,有哪些經驗和問題須要注意呢?下圖顯示了阿里巴巴在使用Flutter進行應用開發時遇到的一些問題,你們使用時須要規避。
首先遇到的問題是,因爲Flutter使用的是Dart進行開發,無疑增長了開發者的學習成本。其次,對於大型應用來講,如何保證代碼質量,如何在多個平臺運行自動化測試腳本也是一個問題;而且因爲Flutter做爲一門新的技術,如何快速的將老得業務遷移過來也是你們須要考慮的問題。總結一下,就是調試、測試、狀態管理、緩和導航棧管理、跨平臺兼容以及如何尋找解決方案的問題。
儘管Flutter已經提供了不少的工具,可是如何將它融入到阿里巴巴的客戶端開發工做流中,是你們須要考慮的問題。
首先,爲了提高開發效率,下降初期的接入成本,咱們將Flutter Toolkit融入到Alibab DevOps工做流中,並自研了一些工具、打包和發佈平臺以及搭建調試環境。接下來,咱們基於現存的技術積累,研發了一些中間件。
下面來看一個實例,即如何解決多圖列表頁面的內存佔用問題。這類問題的特徵以下:
對於列表Flutter列表內存回收的問題,你們能夠閱讀 細化 Flutter List 內存回收,解決大 Cell 問題這篇文章。
對於上面的多圖長列表的內存問題,咱們能夠從如下幾個方面着手進行優化:
最後,咱們來看一下Flutter在阿里巴巴的體系化建設。首先,Flutter的體系化建設主要從基礎能力建設、研發平臺和可持續迭代等幾個方面着手。
下面是Flutter在阿里巴巴平臺建設的具體的一些方案。
目前,Flutter在阿里巴巴已經通過了大規模的應用,而且咱們本身的技術體系建設也在穩步推薦中,後面會將建設的一些成果經過社區分享出來。
附: Google 開發者大會