一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

企業(yè)微信超大型工程-跨全平臺(tái)UI框架最佳實(shí)踐

 jerry_tom123 2021-09-23

一. 背景

企業(yè)微信的跨平臺(tái)之路


企業(yè)微信作為跨android、ios、mac、pc、web五個(gè)端,超千萬行代碼的超大型工程,每一個(gè)需求迭代周期,都需要5端同步開發(fā)、發(fā)版,不管是對(duì)于開發(fā),還是產(chǎn)品、設(shè)計(jì)、測試來說,都是一個(gè)巨大的挑戰(zhàn)。

企業(yè)微信初期架構(gòu)設(shè)計(jì)上就將底層網(wǎng)絡(luò)、db以及大部分業(yè)務(wù)邏輯都抽離到c++實(shí)現(xiàn),以供多平臺(tái)復(fù)用。但是UI還是各平臺(tái)獨(dú)自處理,從開發(fā)的角度來看,移動(dòng)端的android、ios,電腦端的mac、pc,同樣的界面布局,卻需要寫兩套邏輯代碼,因此,ui的跨平臺(tái)訴求是我們的一大痛點(diǎn)。企業(yè)微信內(nèi)對(duì)UI跨平臺(tái)的方案做了一些嘗試比方說h5 和 小程序,但是這兩種方案因?yàn)樾阅芑蛘唧w驗(yàn)的原因都不能覆蓋大部分的業(yè)務(wù)場景,因此我們一直在尋找一個(gè)高性能的跨平臺(tái)框架。

直到google推出了flutter,我們做了一些dem驗(yàn)證,不但體驗(yàn)效果比擬原生,而且底層采用skia自繪引擎渲染,能滿足高復(fù)雜度的需求場景,同時(shí)豐富的pub社區(qū)支持,也加速了框架的成熟。

因此,在時(shí)機(jī)成熟的時(shí)候,我們決定將flutter接入我們的項(xiàng)目工程中來。

二. 企業(yè)微信Flutter工程架構(gòu)

flutter 多模塊架構(gòu)

flutter為我們提供了四種不同的工程模塊

Appcalition(獨(dú)立app)

Module(add2app)

plugin(包含android/ios dart代碼)

package(dart)

在四種模式中,由于我們是已有的項(xiàng)目工程,因此使用Flutter Module的形式依賴flutter的工程,另外對(duì)于flutter module里面的模塊劃分,吸取native組件化的經(jīng)驗(yàn),如果我們要利用Flutter來開發(fā)新的業(yè)務(wù),我們希望各個(gè)業(yè)務(wù)之間是相互獨(dú)立的,方便進(jìn)行管理。
對(duì)于不同的業(yè)務(wù)模塊,避免不了需要native進(jìn)行交互,flutter plugin 可以提供原生的代碼,但plugin 也會(huì)有一些特點(diǎn):

1: 每個(gè)plugin 在項(xiàng)目下都會(huì)被打成一個(gè)aar,開發(fā)的過程又會(huì)源碼依賴plugin的代碼,現(xiàn)有的工程上管理會(huì)變得更加難以維護(hù)。
2: 在不修改編譯代碼的情況下,ios上每個(gè)plugin都會(huì)被打成的framework,framework的數(shù)量在一定程度上影響ios的啟動(dòng)速度。

最終我們的業(yè)務(wù)代碼通過package 純dart來實(shí)現(xiàn),通過channel 生成的雙端代碼,由native各自的模塊維護(hù),如果是第三方的sdk或者插件則由plugin的方式引入,減少aar或frameowk的產(chǎn)生。

另外在基礎(chǔ)庫上我們下層了一些ui控件庫,基礎(chǔ)工具,和路由相關(guān)的組件。通過channel pigeon 的方式實(shí)現(xiàn)了我們的線上crash監(jiān)控,我們最終的組件化架構(gòu)可以設(shè)計(jì)為:

對(duì)于pacage 組件中各個(gè)模塊之間的相互調(diào)用,可以設(shè)計(jì)dart api文件對(duì)應(yīng)要暴露出去的接口,文件主要在存放在lib 目錄下,組件提供一個(gè)統(tǒng)一個(gè)對(duì)外暴露的Dart文件,內(nèi)部的細(xì)粒度的Dart實(shí)現(xiàn)通過export導(dǎo)入,這種設(shè)計(jì)思想正是Flutter官方Api的設(shè)計(jì)。

library lib_flutter;
export 'src/cupertino/dialog.dart';export 'src/cupertino/nav_bar.dart';export 'src/cupertino/route.dart';

三. 混合棧開發(fā)

什么是混合棧?簡單來說,就是app中同時(shí)存在原生和flutter頁面,并且互相跳轉(zhuǎn)。

除了部分新的app,現(xiàn)在市面上大多數(shù)app引入flutter,都是以混合棧的形式引入。


FlutterBoost

企業(yè)微信

導(dǎo)航效率

實(shí)現(xiàn)/維護(hù)成本

性能開銷

應(yīng)用場景

全場景

2.0以前局部場景,2.0以后全場景

接入成本

成熟度

MAC/PC擴(kuò)展支持能力

對(duì)比FlutterBoost、FlutterThrio的混合棧方案,F(xiàn)lutterBoost入侵了原生Flutter navigator棧,將棧統(tǒng)一由原生或者flutter內(nèi)部管理的方式,而FlutterThrio則是直接使用flutter導(dǎo)航棧。

相比之下:

FlutterBoost方案棧管理更清晰,但開發(fā)、維護(hù)成本更高。
FlutterThrio直接使用flutter導(dǎo)航棧的方案,開發(fā)、維護(hù)成本更低,且比較好切換到Mac和PC的支持上,但文檔較少

FlutterBoost在企業(yè)微信的接入flutter 初期,一直停留在flutter低版本,并且對(duì)于flutter 的sdk 有一定的入侵性,經(jīng)過考量,企業(yè)微信實(shí)現(xiàn)一套flutter內(nèi)部導(dǎo)航棧的方案,并且遵循官方的路由設(shè)計(jì),設(shè)計(jì)AppContainer作為基礎(chǔ)容器,在engine初始化的時(shí)候,先預(yù)熱這個(gè)AppContainer容器,并進(jìn)行基礎(chǔ)配置、主題設(shè)置等操作,具體頁面打開時(shí)候,通過channel  來push 一個(gè)MaterialApp 的OverlayEntry 來做具體的路由棧,flutter內(nèi)部的跳轉(zhuǎn)由flutter 內(nèi)部實(shí)現(xiàn)。

這樣做的好處是收攏了基礎(chǔ)邏輯,業(yè)務(wù)開發(fā)時(shí)只需要關(guān)注業(yè)務(wù)邏輯,并且方便進(jìn)行全局層面的配置,提供了統(tǒng)一的導(dǎo)航棧插樁能力,對(duì)于flutter 的導(dǎo)航也沒有入侵性,都是通過Navigator 來控制路由。

除了棧管理之外,混合棧還有一個(gè)需要關(guān)注的問題,是engine的使用管理。

混合棧的頁面棧形式,棧中往往會(huì)出現(xiàn)多個(gè)flutter頁面,flutter的頁面和engine之間存在綁定關(guān)系,而flutter engine開銷很大,為每個(gè)flutter頁面綁定一個(gè)engine,不現(xiàn)實(shí)。

針對(duì)引擎的使用方案,企業(yè)微信從引入flutter至今,可以大致分為兩個(gè)階段:

1. 單引擎階段:

在flutter 2.0以前,我們使用單引擎模式,engine初始化后將被緩存下來,每個(gè)flutter頁面打開時(shí),都和這個(gè)engine綁定,這樣app中就只會(huì)有一個(gè)engine的開銷。

然而,混合棧的頁面棧形式,往往會(huì)出現(xiàn) 原生頁面->flutter頁面->flutter頁面 ,在flutter1.20版本的的前期,我們的這種路由設(shè)計(jì)無法支撐而多個(gè)flutter頁面共存于棧中,所以我們限制了flutter->flutter的場景不允許進(jìn)行容器之間跳轉(zhuǎn), 但是后面有一些浮窗的業(yè)務(wù)場景讓我們不得不打破這個(gè)限制,為了解決這種業(yè)務(wù)場景我們使用了獨(dú)立的flutter engine。

2. 多引擎階段: 


解決這個(gè)問題最好的方式,就是支持多引擎模式,并解決由此帶來的內(nèi)存開銷問題。此時(shí)業(yè)內(nèi)的解決方案,多是修改engine源碼,復(fù)用多個(gè)engine的內(nèi)存空間。但這樣帶來的問題也很多,修改的engine方式始終落后官方engine版本,適配成本高,且往往會(huì)出現(xiàn)很多難以預(yù)測的問題。

恰逢此時(shí),flutter發(fā)布了2.0版本,官方提供了FlutterEngineGroup,以支持engine內(nèi)存空間的復(fù)用,徹底解決了多engine的內(nèi)存開銷問題。我們對(duì)多引擎的效果進(jìn)行了分析,新增一個(gè)engine的內(nèi)存開銷大概在4MB左右。

不過,雖然內(nèi)存開銷問題得到了解決,但engine初始化的耗時(shí),仍是一個(gè)不可忽視的問題,為了優(yōu)化體驗(yàn),我們并沒有直接使用多引擎與flutter頁面進(jìn)行1對(duì)1的綁定。而是采用了主引擎+臨時(shí)引擎的多引擎復(fù)用模式。

對(duì)于flutter頁面打開時(shí),棧中不會(huì)存在其他flutter頁面的情況,使用主引擎;

對(duì)于flutter頁面打開時(shí),棧中可能存在其他flutter頁面的情況,使用臨時(shí)引擎,同時(shí),頁面自定義一個(gè)引擎名稱,臨時(shí)引擎初始化后也將被緩存,這個(gè)頁面再次打開時(shí)將繼續(xù)使用這個(gè)臨時(shí)引擎,以優(yōu)化頁面啟動(dòng)速度。

整體來看:

原生側(cè),我們通過引擎復(fù)用來減少性能的消耗,通過引擎預(yù)加載來減少首次啟動(dòng)的時(shí)間。

在flutter側(cè),通過一個(gè)統(tǒng)一的AppContainer容器來作為頁面載體,在引擎初始化的時(shí)候,即預(yù)熱該容器。在實(shí)際頁面打開的時(shí)候,根據(jù)不同的路由,使用AppContainer來切換不同的子頁面。這樣相比于官方每次打開flutter頁面,都進(jìn)入一個(gè)新的頁面的做法,統(tǒng)一了flutter頁面入口,減少了大量原生與flutter交互的成本。

四. Flutter通信建設(shè)

flutter與native的通信

1. 為什么需要pigeon

在flutter開發(fā)中,我們需要通過channel 的方式與native進(jìn)行通信,在多端的實(shí)踐過程中,我們發(fā)現(xiàn)channel存在一些問題:

1. 多端接口定義的一致性、可維護(hù)性差
2. 無法保證類型的安全,通信容錯(cuò)率低
3. 復(fù)雜的對(duì)象轉(zhuǎn)換需要手動(dòng)解碼與反解碼

因此官方推薦使用pigeon來維護(hù)我們的channel代碼,pigeon 將 我們定義的接口,通過dart的反射將class轉(zhuǎn)換成map的數(shù)據(jù)結(jié)構(gòu),并生成各端接口。簡化了我們平時(shí)手寫channel 和對(duì)接協(xié)議所帶來的成本。

2. pigeon的問題

企業(yè)微信是億萬級(jí)的項(xiàng)目,業(yè)務(wù)場景也十分復(fù)雜,在實(shí)際接入使用pigeon 的過程中,受到了非常大的業(yè)務(wù)挑戰(zhàn),在使用中發(fā)現(xiàn)pigeon還是存在著不少的問題。

比如:數(shù)據(jù)類型的支持較弱,不支持list和map

為什么不支持List和map呢?其實(shí)跟pigeon 傳輸?shù)臄?shù)據(jù)結(jié)構(gòu)有關(guān)。

channel 支持基礎(chǔ)的數(shù)據(jù)類型,其中就包含了map,pigeon在解析dart class的時(shí)候?qū)嶋H是將class轉(zhuǎn)換成map,再傳輸給native,native再以map的結(jié)構(gòu)反解成class,在正常的數(shù)據(jù)下似乎是沒什么問題,但是遇到List和map,由于沒有json那樣的反序列化工具,toMap和fromMap 的代碼的復(fù)雜度就會(huì)急劇上升,我們?cè)?jīng)為了支持list的結(jié)構(gòu),改造pigeon的部分源碼,直接映射List 的數(shù)據(jù)結(jié)構(gòu),對(duì)于一些基礎(chǔ)類型,并沒有什么很大的改造成本,但是遇到object 就需要繼續(xù)對(duì)object 進(jìn)行toMap 的操作:

如果List和map相互嵌套,對(duì)框架的生成來說代碼邏輯十分復(fù)雜,而且生成的代碼也會(huì)特別臃腫。

3. pigeon 的傳輸數(shù)據(jù)結(jié)構(gòu)優(yōu)化

List在我們實(shí)際的開發(fā)中使用的地方非常多,因此我們對(duì)pigeon 源碼進(jìn)行了改動(dòng)目的是為了:

1. 性能上更好,避免重復(fù)嵌套帶來的復(fù)雜計(jì)算的和性能問題。
2. 支持更多的數(shù)據(jù)結(jié)構(gòu),支持List/Map
3. 能夠復(fù)用已有的pb

由于protobuf在企業(yè)微信有大量地在使用,因此我們考慮能否將pigeon 的data class轉(zhuǎn)換成proto的數(shù)據(jù)結(jié)構(gòu),不僅能夠解決List/Map等數(shù)據(jù)的問題,對(duì)與已有的一些pb結(jié)構(gòu)也能起到很好的復(fù)用作用,因此我們沿著這個(gè)思路,優(yōu)化了pigeon 在生成代碼上的思路,具體流程如下:

在pigeon 生成class 的階段,我們hook 生成map的過程,改為生成proto,再編譯proto到各自的平臺(tái)上,由于proto 支持list和map,而且序列化和反序列化都有現(xiàn)成的工具,對(duì)于現(xiàn)有的工具鏈來說幾乎是零成本,而且我們還能復(fù)用已有的proto,避免了重復(fù)的數(shù)據(jù)轉(zhuǎn)換。

4. pigeon channel 注冊(cè)

pigeon生成的server,需要在activity中注冊(cè)后,flutter頁面才能通過channel調(diào)用native的實(shí)現(xiàn)。然而,業(yè)務(wù)產(chǎn)品功能的變更,往往會(huì)讓兩個(gè)一開始設(shè)計(jì)的兩個(gè)獨(dú)立頁面,需要相互跳轉(zhuǎn)。flutter頁面的跳轉(zhuǎn),在dart側(cè)通過flutter的navigator即可完成跳轉(zhuǎn),此時(shí)承載flutter頁面的activity容器還是原來的界面,這個(gè)activity容器并沒有注冊(cè)新的flutter頁面的channel server。 

  

Activity A  包含 Flutter頁面A

Activity B  包含 Flutter頁面B

此時(shí)打開Activity A,將注冊(cè)Flutter頁面A的channel server。

再從Flutter頁面A跳轉(zhuǎn)至Flutter頁面B,此時(shí)activity棧仍在Activity A中。

Flutter頁面B的channel server沒有得到注冊(cè),如果此時(shí)調(diào)用Flutter頁面B的channel,將因?yàn)檎也坏綄?shí)現(xiàn)類而拋異常。

設(shè)計(jì)方案:


問題的難點(diǎn),在于Anroid的channel server實(shí)現(xiàn)類,分散在不同的module中,跨module手動(dòng)注冊(cè)其他flutter頁面的channel server實(shí)現(xiàn)類,繁瑣且不夠優(yōu)雅,而且不同的flutter頁面,往往是由不同的開發(fā)同事完成,互相的調(diào)用往往并不清楚哪些需要注冊(cè)channel server,一旦遺漏,就會(huì)產(chǎn)生異常,且這種異常,由于業(yè)務(wù)路徑的特殊性,開發(fā)和測試都難以檢測出來,風(fēng)險(xiǎn)性更大。

因此,設(shè)計(jì)了channel server的自動(dòng)化注冊(cè)流程:

整體流程原理如下:


1. 通過pigeon自動(dòng)生成膠水代碼
2. flutter調(diào)用業(yè)務(wù)channel的時(shí)候,如果發(fā)現(xiàn)channel server未注冊(cè),自動(dòng)調(diào)用專門用來注冊(cè)的channel,通知native去注冊(cè)該channel server
3. native收到請(qǐng)求到,從manifest中獲取channel server的全路徑名(這個(gè)全路徑名會(huì)在編譯期自動(dòng)生成),然后通過反射,將實(shí)現(xiàn)類注冊(cè)到activity中,并通知flutter注冊(cè)成功
4. flutter收到注冊(cè)成功消息后,再次調(diào)用業(yè)務(wù)channel

五. dart與c++ 調(diào)用演進(jìn)

1. 企業(yè)微信客戶端的架構(gòu)

企業(yè)微信底層chroumin service 業(yè)務(wù)層級(jí)的跨平臺(tái)開發(fā)模式架構(gòu)已經(jīng)非常成熟和穩(wěn)定,而且擁有比較完善的工具鏈,如圖所示,Android和IOS主要負(fù)責(zé)UI繪制與聯(lián)調(diào),將與網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)處理等復(fù)雜的邏輯都交給c++層來處理。

 

在接入flutter 之后,重新在flutter上實(shí)現(xiàn)一套service和network無疑是巨大的成本,我們的首要目標(biāo)就是要復(fù)用底層跨平臺(tái)的邏輯,為了復(fù)用我們已有的工具鏈, 不可避免地需要解決dart與c++的相互調(diào)用問題。

2. flutter調(diào)用cpp

dartvm 提供了Dart_SetNativeResolver 的方法來加載dart上標(biāo)記了native的方法,  dart 與engine的通信方式也是基于這種方式來進(jìn)行的,在flutter engine 中我們能找到大量的RegisterNatives  方法,其中參數(shù)

DartLibraryNatives 里面就存儲(chǔ)著我們的方法簽名,最后再通過Dart_SetNativeResolver 來加載dart 上標(biāo)記了native的方法。

Dart_Handle result_code = Dart_SetNativeResolver(parent_library, ResolveName, NULL);

因此我們可以通過修改engine 的方式,將flutter engine 內(nèi)部

RegisterNatives 以及Dart_SetNativeResolver 方法暴露出來并在合適的時(shí)機(jī)裝載自己的c++ 模塊,但是這種模式需要維護(hù)engine,而且對(duì)我們后面的升級(jí)和維護(hù)帶來很大的不便。

3. dart::ffi 調(diào)用

dart 在2.5 之后實(shí)現(xiàn)了dart::ffi 來調(diào)用c++的接口,并且在flutter上也得到了支持,但是dart::ffi在實(shí)踐的過程中依然有一些限制條件: 

1. dart調(diào)用c++操作步驟繁瑣, 接口維護(hù)和約束困難
2. c++調(diào)用dart方法只支持靜態(tài)或者頂級(jí)函數(shù)
3. dart上開放了指針的分配和釋放,調(diào)用c++之后內(nèi)存管理混亂,容易造成內(nèi)存泄漏
4. 如果出現(xiàn)接口綁定不匹配的情況或者so 忘記更新,會(huì)導(dǎo)致全局的異常,影響正常開發(fā)流程

第一個(gè)問題,看下如果dart調(diào)用c++的同步接口,首先要在dart上綁定c++的方法,綁定過程包括范形和參數(shù)這些。

final loggerFunction = _dl.lookupFunction<    Void Function(Pointer<Uint8>, Int32,Int64),    void Function(Pointer<Uint8>, int,int)>("Logger");

c++的對(duì)應(yīng)實(shí)現(xiàn)如下

WE_DART_EXPORT void Logger(uint8_t * string, int32_t type,int64_t length)

可以看到其中理解需要一定的成本,而且在編寫代碼的過程一定要對(duì)齊參數(shù)。

第二個(gè)問題,如果c++的方法是一個(gè)異步接口,c++回調(diào)dart,異步回調(diào)的核心思路是在dart isolate 啟動(dòng)一個(gè)listenPort的監(jiān)聽函數(shù),在c++中,我們可以通過Dart_PostCObject 的方法來將某個(gè)function 的指針傳給dart,dart再通過ffi在flutter的ui線程上執(zhí)行這個(gè)function,其中的關(guān)系和邏輯相對(duì)復(fù)雜。

第三,如果dart與c++相互調(diào)用傳遞的數(shù)據(jù)是bytes,string這種,都是通過指針來傳遞,dart上提供了Pointer類,和malloc/free函數(shù),如果bytes的數(shù)據(jù)要傳遞到c++,則需要先在dart上分配堆上的uint8指針內(nèi)存,數(shù)據(jù)回調(diào)回來也類似,先將c++的pb數(shù)據(jù)轉(zhuǎn)換為 uint8 指針之后再回調(diào)給dart,內(nèi)存在c++分配之后,回調(diào)給dart,c++底層接口無法知道dart 上數(shù)據(jù)內(nèi)存什么時(shí)候用完,只能交給dart來處理,而且dart的開發(fā)者和c++的函數(shù)都要時(shí)刻保持著指針操作的風(fēng)險(xiǎn)。

4. ffi::gen

ffi::gen是官方后來推出的自動(dòng)生成ffi接口的工具,ffi::gen我們依然沒有采用的主要原因是,沒辦法解決c++層代碼維護(hù)困難,膠水代碼,以及線程安全等問題。

5. ffi接口自動(dòng)生成與管理

企業(yè)微信在2020年下開始使用flutter作為大型獨(dú)立應(yīng)用開發(fā),通過dart::ffi 的方式復(fù)用了原有底層的service 架構(gòu),在一定程度上提高了開發(fā)效率,但是在實(shí)際開發(fā)過程中,每一次的業(yè)務(wù)需求都伴隨著大量dart::ffi 的膠水代碼,并且dart::ffi的方式類似于jni 的開發(fā)方式,一方面需要在dart/c++ 寫一套中轉(zhuǎn)的膠水代碼,另一方面由于dart::ffi 的調(diào)用 方式需要進(jìn)行線程的切換,并且dart 提供了指針的分配與釋放,內(nèi)存的管理似乎變得不太安全。

綜合以上我們希望對(duì)dart調(diào)用c++,做一些業(yè)務(wù)調(diào)用上的改進(jìn),主要目的是為了:

1. 減少手寫膠水代碼,降低dart::ffi的復(fù)雜度
2. 內(nèi)存可控,由框架層管理,開發(fā)者不需要關(guān)心指針的問題
3. 線程安全,開發(fā)者不需要關(guān)心flutter 線程與native 主線程的關(guān)系

為了解決以上這些問題,我們希望能夠更加方便地調(diào)用c++的方法,因此參考grpc/trpc 實(shí)現(xiàn)了一套dart::ffi的簡單的rpc。在引入這套rpc工具后,對(duì)開發(fā)效率有顯著的提升。在proto上定義dart調(diào)用c++的接口,數(shù)據(jù)結(jié)構(gòu)統(tǒng)一為proto,c++層引入rpc的部分能力,dart層也引入相應(yīng)的stub,我們?nèi)サ魊pc的通信機(jī)制,改為dart::ffi來進(jìn)行client和server的通信,只在c++層引入至關(guān)重要的服務(wù)發(fā)現(xiàn)與服務(wù)調(diào)用。整體的架構(gòu)如下:

接下來我們需要調(diào)用c++的方法的過程為:

1. 在proto上定義rpc方法
2. 通過proro生成dart client service, c++的service 接口
3. 底層實(shí)現(xiàn)生成的接口,并將service 注冊(cè)到LanguageCallServer中
4. dart通過proto生成的RpcServiceApi調(diào)用c++的方法
final GovernRpcServiceApi api = GovernRpcServiceApi(WeRpcClient());final RpcResult<GetGovernMyReportListResp> result = await api.getGovernMyReportListFromServer(GetGovernMyReportListReq()..limit = 10);

dart調(diào)用c++的方法,就跟調(diào)用本地的異步方法一樣。

調(diào)用過程如下 :

從整體的流程看,除了虛函數(shù)的實(shí)現(xiàn)需要業(yè)務(wù)邏輯方自己處理之外,其他的能力幾乎是全自動(dòng)生成,后臺(tái)和客戶端也可以共用一份rpc的proto。

六.flutter性能優(yōu)化

1. flutter著色器卡頓

flutter著色器卡頓問題

在實(shí)際的flutter 體驗(yàn)中,我們注意到一些首次進(jìn)入復(fù)雜的頁面會(huì)存在卡頓以及首次進(jìn)入flutter白屏的問題。根據(jù)官方的資料

https://v/docs/perf/rendering/shader 通過trace-skia 跟蹤了主要的耗時(shí)點(diǎn):

在啟動(dòng)的過程中我們發(fā)現(xiàn)skia的GPURasterizer::Draw 有持續(xù)的耗時(shí),有些耗時(shí)甚至達(dá)到了 597.016 ms,存在嚴(yán)重的卡頓問題。

這屬于skia 著色器卡頓的一部分,但是在2.3 之前,ios skia 的持久緩存會(huì)失效,直到2.3 beta之后skia 支持了ios metal 渲染。

針對(duì)add2app的方式優(yōu)化

但是著色器的卡頓處于初級(jí)階段,針對(duì)于add2app的方式,很多命令行都不適用,我們跟蹤了flutter的編譯源碼,最終發(fā)現(xiàn)在ios上可以通過 launchArguments添加一些flutter 的啟動(dòng)變量,例如

flutter run --profile --cache-sksl --purge-persistent-cache

在add2app 的方式下在實(shí)現(xiàn)為:

生成相應(yīng)的著色器之后,我們只需要將io.flutter.shaders.json 放在項(xiàng)目的根目錄,并且加到asset 中

flutter:  assets:    - io.flutter.shaders.json

2. 圖片緩存框架建設(shè)

flutter本身沒有磁盤緩存能力,pub社區(qū)提供了很多解決方案,一般主流的 cached_image_network 緩存使用了 flutter_cache_manager 庫來實(shí)現(xiàn)

cached_image_network雖然提供了硬盤緩存能力,但flutter在項(xiàng)目中以混合棧形式集成,原生本身也已經(jīng)有緩存框架。如果使用cached_image_network,原生與flutter加載同一張圖片,仍然需要加載并存儲(chǔ)兩次,且原生的圖片下載,還有復(fù)雜的下載策略,cached_image_network框架無法支持定制化。

因此,我們自己實(shí)現(xiàn)了一套緩存框架,打通了flutter與native的圖片緩存,流程如下:

在無內(nèi)存緩存的情況下,通過channel通道,調(diào)起原生側(cè)的圖片緩存邏輯,加載硬盤緩存,如果硬盤緩存也沒有,再通過原生的網(wǎng)絡(luò)通道去加載圖片緩存

3. svg與iconFont轉(zhuǎn)換

flutter目前還沒有直接使用native圖片資源的辦法,所以大部分情況我們需要維護(hù)一套新的圖標(biāo)庫,但是經(jīng)過實(shí)踐發(fā)現(xiàn),flutter在渲染圖片的時(shí)候并不是特別完美:如果是在底部tab,點(diǎn)擊之后切換圖片這種情況,低端機(jī)型上,第一次點(diǎn)擊切換圖片的時(shí)候會(huì)稍微閃一下,而且png占的資源比較大,flutter上我們希望找一套穩(wěn)定好用的矢量圖標(biāo)。

svg不被官方所支持,依賴第三方的package, 在flutter里面運(yùn)用最多的就是字體圖標(biāo)(Icons),字體圖標(biāo)具備矢量,顏色可修改,并且渲染性能好等特點(diǎn),被flutter官方運(yùn)用于自身的MaterialIcons和CuptinoIcons中,我們因此也想實(shí)現(xiàn)一套屬于自己的Icon圖標(biāo)庫。


png

svg

iconfont

官方支持

-

x

-

應(yīng)用場景

豐富

部分

純色

渲染性能

包大小

具體的資源構(gòu)建主要是針對(duì)svg來的,我們?cè)谒{(lán)盾上部署nodejs環(huán)境以及安裝gulp,藍(lán)盾通過監(jiān)聽項(xiàng)目svg資源的變化自動(dòng)生成IconFont.dart的索引、ttf文件、以及相應(yīng)的靜態(tài)html。

在使用Iconfont圖標(biāo)之后,我們的圖片體積有所下降,只剩下多色圖的png資源,并且開發(fā)中通過字體圖標(biāo)定制顏色和大小都非常方便。

七:flutter 生態(tài)建設(shè)

1. 多語言框架建設(shè)

flutter本身沒有多語言框架支持,普通的做法是通過flutter_intl框架來管理多語言資源,但仍需要手動(dòng)篩選需要翻譯的資源,待翻譯后再手動(dòng)填入項(xiàng)目。


文字資源集中管理

多語言切換

增量提取待翻譯資源

翻譯腳本

翻譯后資源增量寫入

flutter_intl

Y

Y

N

N

N

為了讓多語言框架實(shí)現(xiàn)閉環(huán),最大程度地減少開發(fā)階段的工作,我們需要用腳本建設(shè)來補(bǔ)足框架缺失的能力。

針對(duì)英文、繁體翻譯,我們需要開發(fā)兩套插件。其中英文翻譯需要人工翻譯,繁體翻譯可以依賴api自動(dòng)翻譯。

同時(shí),為了更好地提高開發(fā)階段的代碼書寫效率,我們也期望允許開發(fā)階段將文本hardcode寫到代碼中,并通過腳本工具來自動(dòng)提取hardcode的文本資源。

總結(jié)來說,我們需要建設(shè)的腳本如下:

  • hardcode文本提取工具

  • 英文翻譯腳本(人工翻譯)

  • 繁體翻譯腳本(Api翻譯)

string_extractor  文本提取工具

通常來說,開發(fā)者在文字資源編寫的時(shí)候,為了節(jié)省開發(fā)時(shí)間,不中斷開發(fā)時(shí)的思路,往往會(huì)先將文字資源hardcode編寫到代碼中。待功能開發(fā)完之后,再將hardcode的文字資源統(tǒng)一提取到統(tǒng)一資源管理類中。這樣后期的提取工作費(fèi)時(shí)費(fèi)力,且容易遺漏。

框架提供了string_extractor自動(dòng)化hardcode文本資源提取的IDE工具,只需要安裝到IDE中,使用快捷鍵option+e即可自動(dòng)識(shí)別頁面中的hardcode文本,并提取到.arb文件中。

如圖為string_extractor插件界面,支持自定義索引id前綴:

      

增量翻譯腳本

1. rescan_flutter: 基于java實(shí)現(xiàn)的腳本工具,用來實(shí)現(xiàn)中譯英翻譯,主要提供了兩個(gè)命令:

  • findNeedTranslateRes: 自動(dòng)比對(duì)項(xiàng)目.arb文件中的中文和英文文字資源,針對(duì)未翻譯的中文文本,先從緩存中查找是否已有翻譯過的英文緩存,如果有則直接填入,沒有則提取出未翻譯的增量中文文本,寫入excel中。

  • merge2res: 將已翻譯的英文文字資源填入.arb文件中,并記錄到緩存中。

流程如圖:

2. conversion2_flutter:基于python實(shí)現(xiàn)的腳本工具,用來實(shí)現(xiàn)中譯繁翻譯,運(yùn)行后,將直接基于開源api,將項(xiàng)目中.arb文件中的中文文字資源翻譯為繁體文字資源,并自動(dòng)寫入.arb文件中。


2. flutter仿原生動(dòng)畫與ui組件

跨平臺(tái)的首要命題:體驗(yàn)

能否達(dá)到原生的體驗(yàn),是跨平臺(tái)的首要目標(biāo),目前flutter的應(yīng)用還是有比較明顯的特點(diǎn),這幾個(gè)特點(diǎn)主要集中表現(xiàn)在:

1. 頁面切換效果不佳,設(shè)計(jì)經(jīng)常提走查
2. 點(diǎn)擊態(tài)效果弱,要么沒有要么就是Android的效果
3. 導(dǎo)航欄動(dòng)畫跟原生差距較大

 flutter體驗(yàn)上的一些優(yōu)化

在flutter上我們實(shí)現(xiàn)了一套自己的ui控件庫,實(shí)現(xiàn)了一些仿原生ui和動(dòng)畫: 

3. 暗黑模式適配

企業(yè)微信Flutter暗黑模式的落地

系統(tǒng)主題Theme

Flutter 應(yīng)用的統(tǒng)一入口是MaterialApp, MaterialApp 提供了theme和darktheme來適配淺色模式和黑暗模式,F(xiàn)lutter提供的組件,比如Appbar,Button,頁面的默認(rèn)文字大小,如果用戶在沒有指定參數(shù)的情況下,會(huì)默認(rèn)從系統(tǒng)的主題里面讀取,與native不同的是,native大部分組件都是自己自定義的,flutter控件是通過組裝模式來生成新的控件的,其實(shí)就是說我們的組件大部分不過就是在官方的組件上套了一層。但是官方的組件又只會(huì)默認(rèn)讀取自己系統(tǒng)的主題,因此,我們只能通過修改官方主題的形式來達(dá)到盡可能地簡化組件的參數(shù)和適配黑暗模式目的。

以后在使用官方組件/實(shí)現(xiàn)與官方類似的控件的時(shí)候,如果是通用組件,優(yōu)先考慮在主題上設(shè)置通用參數(shù),然后才是自定義參數(shù)設(shè)置。

//主題定義dividerTheme: const DividerThemeData(  color: WWKLightColor.color_7,  space: 0.33,  thickness: 0.33,));dividerTheme: const DividerThemeData(  color: WWKDarkColor.color_7,  space: 0.33,  thickness: 0.33,),//?錯(cuò)誤寫法// const Divider(height:0.33,color: Darkcolors.color_7,)//使用方法const Divider();

自定義的顏色

CupertinoDynamicColor 提供了顏色的動(dòng)態(tài)切換,因此我們可以將我們的顏色定義成一個(gè)CupertinoDynamicColor,并且通過extension 的方式 添加在context里面。

Color get color73 => CupertinoDynamicColor.resolve(const CupertinoDynamicColor.withBrightness(    color: Color(0xff32c757),    darkColor: Color(0xff38c95c)), _context);

企業(yè)微信落地

八. 企業(yè)微信Flutter調(diào)試工具  UiInsight-Flutter

隨著企業(yè)微信在更多業(yè)務(wù)場景下使用Flutter技術(shù),擁有一款和原生的UiInsight相似的效率工具成了研發(fā)、測試、設(shè)計(jì)的迫切需求。

功能對(duì)比

功能

UiInsight-Flutter

DoKit Flutter

UME

widget信息查看

??

??

??

MethodChannel調(diào)用

??

??

?

內(nèi)存信息及泄露檢查

??

??

??

顏色拾取

??

??

??

頁面啟動(dòng)耗時(shí)

??

??

?

控件位置測量

??

?

??

控件間距離測量

??

?

??

全方法執(zhí)行耗時(shí)

??

?

?

圖片檢查/大圖告警

??

?

?

支持?jǐn)U展

??

?

??

FlutterInsight 分為三個(gè)功能塊,除內(nèi)部集成了效率工具和性能工具之外,也可根據(jù)各業(yè)務(wù)定制擴(kuò)展功能。

入口

接入FlutterInsight后,將在界面上懸浮展示fps和dart虛擬機(jī)的堆內(nèi)存大小,單擊后可展示更多信息,雙擊將彈出dialog,dialog中可開啟各工具。

     

效率工具

用于提升flutter開發(fā)效率、幫助還原設(shè)計(jì)稿

當(dāng)前頁面信息

可查看當(dāng)前頁面中Scaffold元素對(duì)應(yīng)的widget名和文件名及代碼行數(shù)。

由于所有頁面基本存在Scaffold作為一個(gè)頁面的主體,Scaffold元素的信息在大部分情況下也可反映當(dāng)前頁面的信息。以Scaffold的信息代表當(dāng)前頁面的信息,可避免對(duì)各業(yè)務(wù)頁面的侵入。

控件信息拾取

支持選中某widget獲取對(duì)應(yīng)widget的詳細(xì)信息,如類名、所在文件、所在行數(shù)、x/y定位信

位置拾取

拖拽選中環(huán)可得到選中環(huán)中心點(diǎn)的x/y位置信息。

控件間距離測量

這是一種全新的交互方式,主要用于測量控件A某邊和控件B某邊之間的距離。

   

如圖1,選中控件A的某條邊后長按,可彈出對(duì)話框,點(diǎn)擊確定后,將確定控件A的該邊作為開始邊,拖拽選中環(huán),可實(shí)時(shí)得到選中環(huán)對(duì)應(yīng)選中邊和開始邊的距離,若兩條邊的相互平行,可得到相對(duì)距離,若垂直,則得不到相應(yīng)距離。

圖片檢查

用于測量圖片源數(shù)據(jù)的寬高與控件本身的寬高,以確定是否加載了過大的圖片

顏色吸管

通過拖拽選中環(huán)選中屏幕內(nèi)某像素點(diǎn)并得到對(duì)應(yīng)的色值信息              

性能工具

幫助發(fā)現(xiàn)flutter應(yīng)用的性能問題

fps樹狀圖展示

為方便更直觀地查看fps的變化,支持以樹狀圖的形式查看fps


開啟大圖檢測

對(duì)Image組件配置了frameBuilder后,可在打開界面時(shí)候查看該Image是否出現(xiàn)加載的圖片遠(yuǎn)大于Image組件本身大小的情況:

Image.network(                         "https://img95.699pic.com/photo/40070/2524.jpg_wh860.jpg",     frameBuilder:      FlutterInsight.instance.checkImage,     width: 100,     height: 50,)

可在圖片寬高遠(yuǎn)大于控件寬高的Image組件中看到大圖警告的圖標(biāo):

內(nèi)存詳情及泄露

如圖,開啟1后,F(xiàn)lutterInsight將監(jiān)控頁面的Scaffold元素是否泄露,若發(fā)生泄露,將在左上角展示相關(guān)信息。

點(diǎn)擊查看泄露情況:

  

MethodChannel調(diào)用

如圖開啟methodchannel調(diào)用后,接下來發(fā)生的methodchannel調(diào)用均可查看:

           

頁面層級(jí)及加載耗時(shí)

在本工具的彈出框可開啟頁面層級(jí)及加載耗時(shí)監(jiān)聽,如1,開啟后,每進(jìn)入一個(gè)新頁面都將展示對(duì)應(yīng)頁面的加載耗時(shí)和widget數(shù)量深度信息。

基于aop的方法耗時(shí)排行

FlutterInsight 提供了特有的功能,統(tǒng)計(jì)flutter的方法耗時(shí):

flutter在編譯時(shí),首先由frontend_server將dart代碼轉(zhuǎn)換為中間文件app.dill,然后在debug打包下,轉(zhuǎn)換為kernel_blob.bin,release打包下,轉(zhuǎn)換為so或framwork。

flutter的Aop就是對(duì)app.dill進(jìn)行修改實(shí)現(xiàn)的。AspectD是閑魚針對(duì)Flutter實(shí)現(xiàn)的AOP開源庫,可實(shí)現(xiàn)對(duì)項(xiàng)目中的方法進(jìn)行插樁。全方法的插樁是我們基于AspectD進(jìn)行修改實(shí)現(xiàn)的:

方案一:在aop_impl.dart中,通過添加Execute注解對(duì)所有方法進(jìn)行插樁。在對(duì)類似build這種覆寫方法插樁時(shí),拿不到該方法對(duì)應(yīng)的library,將產(chǎn)生nonenull報(bào)錯(cuò),如: 

https://github.com/XianyuTech/aspectd/issues/124。

方案二:在aop_impl.dart中,通過添加Call注解對(duì)所有方法進(jìn)行插樁。這個(gè)方案可以得到工程中的所有方法被調(diào)用時(shí)的耗時(shí),但由于沒有調(diào)用點(diǎn),故無法得到如xxWidget的build方法的耗時(shí),也無法滿足我們的需求。

最終方案:

1. 首先app.dill將讀取為Component變量。

2. 通過遍歷該component中的library、class、procedure,可得到工程中寫的aop_map_help.dart文件,并保存其markStart和markEnd函數(shù)為procedure。以便后續(xù)添加markStart調(diào)用和markEnd調(diào)用時(shí)使用。

3. 考慮到一個(gè)方法的開始和結(jié)束存在以下幾種情況:

  • 帶返回的函數(shù),需要在這個(gè)函數(shù)主體的開始添加markStart調(diào)用,需要在這個(gè)函數(shù)的return語句前添加markEnd調(diào)用。

@overrideWidget build(BuildContext context) {  int aa = 0;  if(aa == 1)return Text("test");  return Container(); //結(jié)束時(shí)}
  • 不帶返回的函數(shù),需要在這個(gè)函數(shù)主體的開始添加markStart調(diào)用,需要在這個(gè)函數(shù)主體的結(jié)束添加markEnd調(diào)用,在這個(gè)的return語句前添加markEnd調(diào)用。

void test1() {  int add =0;  if(add == 0)return;}

4. 我們可以通過RecursiveVisitor 提供的api訪問app.dill中l(wèi)ibrary、class、blockreturnElement,并實(shí)現(xiàn)上述的插樁行為。

5. 插樁后解開app.dill可以看到:

method test() → void {      aop::MethodTrace::markStart("test", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart");      core::int* add = 0;      if(add.{core::num::==}(0)) {        aop::MethodTrace::markEnd("test", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart");        return;      }      aop::MethodTrace::markEnd("test", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart");    }
method build(fra::BuildContext* context) → fra::Widget* { aop::MethodTrace::markStart("build", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart"); core::int* aa = 0; if(aa.{core::num::==}(1)) { dynamic value_build_16; value_build_16 = new text::Text::·("test", $creationLocationd_0dea112b090073317d4: #C4290); aop::MethodTrace::markEnd("build", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart"); return value_build_16; } dynamic value_build_17; value_build_17 = new con2::Container::·($creationLocationd_0dea112b090073317d4: #C4291); aop::MethodTrace::markEnd("build", "Test", "file:///Users/shuushigeru/Documents/flutter/aspect_demo/lib/my_test_now.dart"); return value_build_17;    }

在test的函數(shù)開始、return處或結(jié)束處均插入了對(duì)應(yīng)統(tǒng)計(jì)代碼。

6. 考慮到一般來說,我們更關(guān)注未被async修飾函數(shù)的耗時(shí),可以在3、4操作前通過讀取 procedure.function.dartAsyncMarker.index 先判斷當(dāng)前function是否為async函數(shù),具體判斷方式參考官網(wǎng)AsyncMarker enum,若為async函數(shù),則不執(zhí)行插樁。

擴(kuò)展工具:

FlutterInsight支持各業(yè)務(wù)方根據(jù)自己的業(yè)務(wù)/技術(shù)特點(diǎn)增加入口,支持跳轉(zhuǎn)、展示、開關(guān)三種類型,如企業(yè)微信是通過底層native來訪問網(wǎng)絡(luò)和數(shù)據(jù)庫服務(wù),故而專為企業(yè)微信擴(kuò)展了native調(diào)用(方法名及耗時(shí))頁面的跳轉(zhuǎn)入口。

 FlutterInsight.instance.addDialogItem(UiItemWidget(    title: "native調(diào)用",    showMore: true,    onMorePressed: (context) {      // 跳轉(zhuǎn)    },  ));  FlutterInsight.instance.addDialogItem(UiItemWidget(    title: "打開測試模式",    checked: true,    onCheckBoxPressed: (isChecked){    },  ));

九:企業(yè)微信Flutter動(dòng)態(tài)化探索

雖然自 2018 年 Flutter 正式發(fā)布以來,以其良好的多端渲染一致性和優(yōu)異的渲染性能俘獲了很多開發(fā)者的心,但是也有不少人對(duì) Flutter 望而卻步,其中一個(gè)重要的原因是,F(xiàn)lutter 不具備其他跨平臺(tái)方案(比如:React Native、Hippy 等)擁有的動(dòng)態(tài)化能力,因?yàn)閯?dòng)態(tài)化代表著更短的上線路徑,更快的線上問題修復(fù)速度,同時(shí),無形中也優(yōu)化了應(yīng)用安裝包體積。在企業(yè)微信中,也一直在探索和實(shí)踐 Flutter 的動(dòng)態(tài)化能力。

1. 基于 Flutter 的動(dòng)態(tài)化方案

根據(jù) DSL 的不同,基于 Flutter 的動(dòng)態(tài)化方案可以分為兩大類:面向前端的解決方案和面向終端的解決方案。面向前端的解決方案主要基于 JS 或 TS 語言進(jìn)行開發(fā),對(duì)于前端同學(xué)更加友好,面向終端的解決方案主要使用 Dart 語言進(jìn)行開發(fā),使用 Android Studio、VSCode 等 IDE 進(jìn)行開發(fā),對(duì)終端同學(xué)更加友好,對(duì)于前端同學(xué)來講,有一定的學(xué)習(xí)成本。

面向前端的解決方案代表框架有 LiteApp 和 Kraken,LiteApp 由微信自研出品,Kraken 是阿里前段時(shí)間開源的;面向終端的解決方案代表框架是美團(tuán)出品的 MTFlutter(Flap),由于 MTFlutter 還未開源,短期內(nèi)也用不上,這里就不做過多介紹了,感興趣的同學(xué)可以自行查找資料學(xué)習(xí)。

下圖從開發(fā)語言/框架、通信效率、渲染效率、等四個(gè)角度,對(duì) LiteApp 和 Kraken 進(jìn)行了調(diào)研和對(duì)比:

1. 在上層業(yè)務(wù)開發(fā)時(shí),LiteApp和Kraken都提供了兼容W3C規(guī)范的DOM API,并將其暴露給 JS Engine,LiteApp 目前支持 Vue.js 的開發(fā),而Kraken支持HTML/CSS/React/Vue進(jìn)行開發(fā)。

2. 在跨端通信方面,Kraken 對(duì)官方的 dart:ffi 進(jìn)行了一定的改造,支持了 dart 和 c 的雙向調(diào)用;而 LiteApp 是對(duì) Flutter Engine 進(jìn)行改造,增加了 dart2cpp 模塊,暴露出部分 C++ 接口,使得外部的動(dòng)態(tài)庫可以基于這些接口通過 DartVM 調(diào)用到 dart 的接口。在 Dart 的運(yùn)行環(huán)境中 C++ 和 Dart 之間就可以像調(diào)用自身的接口一樣調(diào)用彼此的接口。

3. 在渲染效率方面,Kraken 不依賴 Flutter Widget,而是直接依賴 Render Object,這樣具備更短的渲染管線;LiteApp 是將解析生成的 Virtual DOM Tree 映射為 Flutter Widget Tree。從技術(shù)原理的角度看,Kraken 比 LiteApp 具備更優(yōu)秀的渲染效率。

4. 在兼容 W3C 規(guī)范方面,Kraken 對(duì) CSS 的支持比較弱,用于開發(fā)線上需求還不夠;相比之下,LiteApp 在這方面做的更好,比如:對(duì) CSS 的支持更加全面,并且可以寫在單獨(dú)的 CSS 文件中,支持富文本,支持 Store 等。

2. 企業(yè)微信 Flutter 動(dòng)態(tài)化方案 - LiteApp

如下圖所示是企業(yè)微信 Flutter 整體架構(gòu)示意圖,可以分為兩部分,底層是宿主企業(yè)微信主工程,上層包括兩塊,分別是基于 Flutter 的動(dòng)態(tài)化框架 LiteApp 和 Flutter 的原生開發(fā)。上層部分是從左至右的執(zhí)行順序,總共可以分為三個(gè)階段:

1. 前端同學(xué)使用 Vue.js 進(jìn)行業(yè)務(wù)開發(fā)(生成的 zip 包可以下發(fā)到終端),經(jīng)常 JSEngine(封裝后的 JavaScriptCor 和 V8)解析運(yùn)行,在內(nèi)置的 JS 基礎(chǔ)庫的支撐下生成 Virtual Dom Tree

2. 在 LuggageView 層映射為 LuggageView 樹,并進(jìn)行 CSS 屬性解析和布局,最后通過 dart2Cpp 模塊將 L?uggageView 樹傳輸?shù)?Flutter 層

3. Flutter 層解析生成對(duì)應(yīng)的 Element Tree → Component Tree → Widget Tree,這樣便可以在終端通過 Flutter Engine 渲染了

在企業(yè)微信中,目前已有小黑板、家校應(yīng)用、學(xué)習(xí)園地、設(shè)備巡檢四個(gè)業(yè)務(wù)使用 LiteApp 開發(fā)并上線(3.1.12 版本),目前也還有一些問題(比如:運(yùn)行內(nèi)存較高)正在優(yōu)化解決,期望后續(xù)會(huì)開源出來方便更多的開發(fā)者和業(yè)務(wù)。

回顧&展望

企業(yè)微信在開始大規(guī)模地使用flutter作為跨平臺(tái)開發(fā)后,承受住了各種業(yè)務(wù)需求的考驗(yàn),而且flutter頁面的占比也逐漸提高,以下是各版本flutter 使用占比率:

流程與效率提升:

實(shí)際項(xiàng)目迭代過程中,得益于flutter跨平臺(tái)的能力,各角色協(xié)同效率明顯提升

1. 研發(fā)側(cè):基于flutter各平臺(tái)技術(shù)棧統(tǒng)一,需求開發(fā)人力投入減少50%

2. 設(shè)計(jì)側(cè):基于flutter ui的一致性,設(shè)計(jì)側(cè)可以把主要精力放到ios平臺(tái),ui走查效率提升40%

3. 測試側(cè):對(duì)于flutter內(nèi)部閉環(huán)頁面單平臺(tái)人力就可以做到跨平臺(tái)覆蓋

對(duì)外影響力:

Google IO 大會(huì)介紹

企業(yè)微信客戶端團(tuán)隊(duì),包括 iOS、Andrroid、Windows、Mac、Web 五大平臺(tái)。我們重視跨平臺(tái)技術(shù)框架的研發(fā),各類原創(chuàng)技術(shù)專利,截止去年,僅數(shù)十人的技術(shù)團(tuán)隊(duì)在近3年內(nèi)提交技術(shù)專利百余項(xiàng)。團(tuán)隊(duì)招聘優(yōu)秀技術(shù)人才,崗位分布在成都、廣州、深圳。歡迎在官網(wǎng)投遞簡歷。

可在 hr.tencent.com 搜索企業(yè)微信相關(guān)崗位,或者掃碼聯(lián)系 HR

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    欧美激情床戏一区二区三| 亚洲永久一区二区三区在线| 欧美一级黄片欧美精品| 五月综合婷婷在线伊人| 成年女人下边潮喷毛片免费| 国产精品久久精品毛片| 亚洲精品中文字幕一二三| 亚洲中文字幕在线观看四区| 国产午夜精品在线免费看| 欧美一区二区三区十区| 国产亚洲不卡一区二区| 日韩精品中文在线观看| 亚洲欧美中文字幕精品| 日韩精品一区二区不卡| 一本久道久久综合中文字幕| 国产亚洲午夜高清国产拍精品| 大香蕉久久精品一区二区字幕| 97人妻人人揉人人躁人人| 国产又粗又猛又爽色噜噜| 成年女人下边潮喷毛片免费| 亚洲最新av在线观看| 91亚洲精品综合久久| 国产一区国产二区在线视频| 亚洲丁香婷婷久久一区| 国产成人免费激情视频| 久久黄片免费播放大全| 日韩精品区欧美在线一区| 亚洲精品成人午夜久久| 日韩日韩日韩日韩在线| 一个人的久久精彩视频| 日本男人女人干逼视频| 青草草在线视频免费视频| 久久精品中文字幕人妻中文| 亚洲最大福利在线观看| 91日韩欧美中文字幕| 成人三级视频在线观看不卡| 日本黄色录像韩国黄色录像| 亚洲国产成人爱av在线播放下载| 国产人妻精品区一区二区三区| 日韩欧美一区二区不卡看片| 久久福利视频在线观看|