Flutter是google近年來新推出的跨平臺移動UI框架,可以在ios和Android系統上快速構建出高質量,體驗較高的原生介面,同時Flutter還將會作為google新一代作業系統Fuchsia的Toolchain,這對Flutter的未來發展前景是一個強有力的支撐。寫這篇文章時,中國 GDG 2018 剛剛落幕,Flutter團隊在大會上釋出了release之前的最後一個preview版本。扇貝移動團隊近期也對Flutter如何應用到扇貝產品上做了一番探索和實踐。
Hello Flutter
Flutter專案可以用同一套程式碼同時在ios和Android作業系統上執行,在此之前,市面上已經存在了很多跨平臺解決方案,扇貝的移動產品上在跨平臺開發上一直採用Webview加React Native的解決方案。
Webview的優缺點都非常明顯,優勢是可以完全用Web開發的技術棧去實現,app只需要提供webview元件承載即可,同時可以隨時更新頁面,動態性很高,並且這些頁面可以完全交由web開發人員開發迭代,移動端不需要太關心,但是Webview的缺點又很多,渲染的效能一直被開發者詬病,內建的Chrome核心在國內的機型上也會有不相容的情況發生。
React Native在活動頁面和商品頁面得到了使用,RN相對於Webview來說,效能和渲染效率都得到了不小的提升,它是將編寫好的web介面利用JSCore轉換成原生控制元件,同時配合動態更新系統可以釋出新版本的RN包來實現動態更新介面,但是RN和系統的關聯性較強,很多功能需要依賴系統特性開發,debug成本也相對比較高,移動端的同學也需要對RN有一些瞭解來配合前端開發。
因此我們將目光投向了Flutter。
Flutter的特點
- Dart可以執行前編譯(AOT),在開發flutter應用的時候佈局檔案會直接通過原始碼編寫node tree,從而避免了大量的解析轉譯時間,使得Dart的效率比JS更高。
- Dart語言同樣支援JIT編譯,因此flutter可以hot reload,為開發週期提速。
- Dart沒有鎖的概念,可以做到物件回收和GC,Dart中的執行緒叫做isolates,因為不共享記憶體的原因,同時和js一樣是單執行緒操作,所以不會出現搶佔排程和鎖死的問題。開發者控制執行緒的時候需要顯式建立執行緒,最常用的是async和await。
- Flutter用Dart語言開發,因為Flutter主要用來開發使用者介面,Dart語言的特性適合了使用者構建使用者介面時的操作邏輯,沒有像Android的xml檔案和前端的html檔案這樣的單獨佈局檔案,使得開發更簡潔,預覽更方便。
- Flutter不再受限於native,自己開發了一套渲染邏輯,因此在未來的效能優化和跨平臺相比RN優勢會更加明顯。
關於Flutter的具體使用可以前往官網學習 flutter.io
Flutter的依賴是在Pub倉庫中管理,專案的依賴在根目錄下的pubspec.yaml中進行配置,例如如下配置:
dependencies:
flutter:
sdk: flutter
json_annotation: ^1.0.0
intl: ^0.15.7
複製程式碼
關於依賴的package版本可以有兩種寫法
- packagename: ^version 引入某個版本的package
- packagename: ‘>=version’ 引入某個版本之後的package,用來約束最低或最高版本
在pubspec.yaml檔案中宣告依賴之後需要執行package get命令,國內開發者可能需要科學上網才能拉下來依賴包,Flutter為國內開發者提供了本地化的網站和映象。只需要簡單配置即可。 using Flutter In China
技術預研
在對Flutter方案做技術預研的時候,我們羅列出了一些需要探索和解決的問題。
- 重寫介面的成本
- Flutter和原生互相通訊問題
- 圖片資源增加包體積
- Flutter如何構建到現有專案
重寫介面成本
既然開始選用Flutter作為預備跨平臺方案,而且是Android和ios團隊負責開發維護,就需要了解一下Flutter的學習和開發成本。
Dart是Flutter前期在對十幾種語言進行調研之後最後選擇的語言,它天然支援響應式程式設計,Dart2的升級是進一步優化客戶端的開發,編寫出的程式碼結構清晰,有一定程式設計基礎的開發人員不需要經過系統的學習就可以上手進行Flutter開發。
Flutter提供了大量基於Android material design風格和ios Cupertino風格的控制元件,可以通過組合巢狀的方式構建介面,需要注意的是在Flutter裡面屬性也是控制元件的概念,例如Padding、Center、Align等。具體檢視Widgets Catalog。
Flutter和原生互相通訊
以Android為例,Dart呼叫Java程式碼可以通過MethodChannel來實現,Java呼叫dart則用EventChannel來實現。
因為需要用Flutter重寫的頁面有涉及到網路請求的問題,之前的Android專案裡實現了一套完整的網路請求框架,封裝了專案需要的一些附帶請求資訊和處理邏輯等,我們目前還不需要為Flutter專案單獨實現一個完整網路請求框架,所以將網路請求通過dart呼叫java的方式在java層進行網路請求然後callback到flutter頁面上,有因為網路請求是非同步的,dart的async/await關鍵字可以提供非同步支援。
MethodChannel
首先在dart程式碼中例項化MethodChannel物件,然後在需要呼叫java程式碼的時候呼叫invokeMethod方法。
static const methodChannel = const MethodChannel('com.shanbay.shared.data/method'); Future<ProfileModel> _getProfileJson() async {
mProfileJson = await platform.invokeMethod("getProfileJson");
if (mProfileJson != null) {
setState(() {
Map profileMap = json.decode(mProfileJson);
mProfileData = new ProfileModel.fromJson(profileMap);
});
}
}
複製程式碼
在java中註冊MethodChannel,注意channel的名字需要相同。
private static final String METHOD_CHANNEL = "com.shanbay.shared.data/method";
private MethodChannel mMethodChannel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
mMethodChannel = new MethodChannel(getFlutterView(), METHOD_CHANNEL);
......
}
mMethodChannel.setMethodCallHandler(
MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, final MethodChannel.Result result) {
if(call.method.equals("getProfileJson"){
//do something
result.success(json);
}
}
複製程式碼
EventChannel
有時候在頁面接收到了一個event事件要動態重新整理或者修改頁面,需要java呼叫dart。 在dart程式碼中例項化EventChannel物件
static const eventChannel = const EventChannel('com.shanbay.shared.data/event');
StreamSubscription _subscription = null;
@override
void initState() {
super.initState();
//開啟監聽
if (_subscription == null) {
_subscription = eventChannel
.receiveBroadcastStream()
.listen(_onEvent, onError: _onError);
}
}
@override
void dispose() {
super.dispose();
//取消監聽
if (_subscription != null) {
_subscription.cancel();
}
}
void _onEvent(Object event) {
setState(() {
if (event.toString() == "refresh") {
_getProfileJson();
}
});
}
void _onError(Object error) {
setState(() {
print(error);
});
}
複製程式碼
在java程式碼中例項化相同一個EventChannel物件獲得event例項用來呼叫dart。
private static final String EVENT_CHANNEL = "com.shanbay.shared.data/event";
private EventChannel mChannel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
mChannel = new EventChannel(getFlutterView(), EVENT_CHANNEL);
mChannel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
mEvent = events;
}
@Override
public void onCancel(Object arguments) {
}
});
}
onEvent(event) {
mEvent.success("refresh");
}
複製程式碼
圖片資源增加包體積
在我們現有的Android專案中往往只提供了xxhdpi解析度的圖片資源,這樣做是為了減小apk包體積,讓其他解析度的手機通過裝置自適應。Flutter對於圖片資源需要提供1x、2x、3x三種解析度格式,這樣如果需要大量本地圖片資源,會增加一定的包體積。
美團技術發表過一篇文章,他們針對flutter中需要複用的圖片資源採取的方案是對移動app assets中的圖片按螢幕密度縮放並且存放到本地,然後dart中呼叫本地圖片。
這樣的方案在啟動Flutter頁面之前就需要知道哪些圖片需要被儲存到本地,還要做一個圖片縮放和儲存的操作,我認為這樣可能不能確保圖片的準確性,同時需要Android和ios兩端同時支援,寫兩份程式碼,並且還要針對不同的flutter頁面記錄一個資原始檔到頁面的對映表,成本有點過高,在我們團隊內實現有些繁瑣,而且我們要實踐的頁面只用到了少量的本地圖片,佔用體積也不是很大,針對這種情況,可以直接用官方的三種解析度,保證了Android、ios的兩端的同步。
Flutter如何構建到現有專案
這個版塊我們計劃是採用將flutter專案打包成aar到Android專案中整合的方式構建,其中踩到了一些坑,正在梳理,《Flutter在混合專案中的構建和整合》會詳細說明。
在個人資訊頁的實踐
這個是我們flutter專案結構,host是一個debug工程,可以直接編譯執行,lib資料夾則是dart程式碼。最後形成的個人資訊頁效果圖如下:
總結
Flutter的控制元件目前只提供了md風格的基礎元件,想要自定義控制元件相對於Android和ios來說還是有一些複雜度,但是Flutter團隊通過原生渲染介面確實打破了原有的跨平臺解決方案的思路,效能和效率的提升是顯而易見的,相比RN來說具有很大的優勢。
dart目前在Android Studio上還不能支援程式碼塊摺疊,同時格式化還要手動點選,沒有快捷鍵,程式碼塊摺疊這一點對dart組合巢狀寫介面來說實在是太有必要了,希望可以後續不斷優化開發體驗。
Flutter的引入無疑會增加包體積,preview2的釋出,官方宣佈release包體積將會再減小30%,一個空的release專案只需要4.7MB 的體積,對於現在流量吃到飽的情況,其實包體積壓縮這個話題可以慢慢的弱化了,優化使用者體驗是最重要的。
Flutter在扇貝聽力上的實踐已經打出了release包等待發布,我們會逐漸完善Flutter專案,在更多跨平臺場景上使用Flutter開發。