1. 程式人生 > >做了2個多月的設計和編碼,我梳理了Flutter動態化的方案對比及最佳實現

做了2個多月的設計和編碼,我梳理了Flutter動態化的方案對比及最佳實現

作者:閒魚技術-石磬

背景

在端上為了提升App的靈活性, 快速解決萬變的業務需求,開發者們探索了多種解決方案,如PhoneGap ,React Native ,Weex等,但在Flutter生態還沒有好的解決方案。未來閒魚都會基於Flutter 來跨端開發,如果突破發版週期,在不發版的情況下,完成業務需求,同時能相容效能體驗,無疑是更快的響應了業務需求。因此我們需要探索在Flutter生態下的動態化。

方案選擇

借鑑Android 和Ios上的動態性方案,我們也思考了多種Flutter動態性方案。

1.下載替換Flutter編譯產物

下載新的Flutter編譯產物,替換 App 安裝目錄下的編譯產物,來實現動態化,這在Android 端是可行的,但在Ios 端不可行。我們需要雙端一體的解決方案,所以這不是最好選擇。

2.類似React Native 框架

我們先來看看React Native 的架構

MacDown image

React Native 要轉為android(ios) 的原生元件,再進行渲染。
用React Native的設計思路,把XML DSL轉為Flutter 的原子widget元件,讓Flutter 來渲染。技術上說是可行的,但這個成本很大,這會是一個龐大的工程,從投入產出比看,不是很好的選擇

3.頁面動態元件框架

由粗粒度的Widget元件動態拼裝出頁面,Native端已經有很多成熟的框架,如天貓的Tangram,淘寶的DinamicX,它在效能、動態性,開發週期上取得較好平衡。關鍵它能滿足大部分的動態性需求,能解決問題。

三種方案的比較圖表如:
MacDown image

根據實際動態性需求,從兩端一致性,和效能,成本,動態性考慮,我們選擇一個折中方案,頁面動態元件的設計思路是一個不錯的選擇。

頁面動態元件框架

在Flutter上使用粗力度的元件動態拼裝來構建頁面,需要一整套的前後端服務和工具。本文我們重點介紹前端介面渲染引擎過程。

語法樹的選擇

Native端的Tangram ,DinamicX等框架他們有個共同點,都是Xml或者Html 做為DSL。但是Flutter 是React Style語法。他自己的語法已經能很好的表達頁面。無需要自定義的Xml 語法,自定義的邏輯表示式。用Flutter 原始碼做為DSL 能大大減輕開發,測試過程,不需要額外的工具支援。
所以選擇了Flutter 原始碼作為DSL,來實現動態化。

如何解析DSL

Flutter原始碼做為DSL,那我們需要對原始碼進行很好的解析和分析。Flutter analyzer給了我們一些思路,Flutter analyzer是一個程式碼風格檢測工具。它使用package:analyzer來解析dart 原始碼,拿到ASTNode。

看下Flutter analyze 原始碼結構,它使用了dart sdk 裡面的 package:analyzer

dart-sdk:  
     analysis_server: 
         analysis_server.dart
         handleRequest(Request request) 
         
     analyzer:
         parseCompilationUnit()
         parseDartFile 
         parseDirectives 
         

Flutter analyze 解析原始碼得到ASTNode過程。

MacDown image

外掛或者命令對analysis server發起請求,請求中帶需要分析的檔案path,和分析的型別,analysis_server經過使用 package:analyzer 獲取 commilationUnit (ASTNode),再對astNode,經過computer分析,返回一個分析結果list。

同樣我們也可以把使用 package:analyzer 把原始檔轉換為commilationUnit (ASTNode),ASTNode是一個抽象語法樹,抽象語法樹(abstract syntax tree或者縮寫為AST)是原始碼的抽象語法結構的樹狀表現形式.

所有利用抽象語法樹能很好的解析dart 原始碼。

解析渲染引擎

下面重點介紹渲染模組

架構圖:

MacDown image

1.原始碼解析過程

1.AST樹的結構

如下面這段Flutter元件原始碼:

import 'package:flutter/material.dart';

class FollowedTopicCard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 0.0),
      child: new InkWell(
        child: new Center(
          child: const Text('Plugin example app'),
        ),
        onTap: () {},
      ),
    );
  }
}

它的AST結構:

MacDown image

從AST結構看,他是有規律的.

2.AST 到widget Node

我們拿到了ASTNode,但ASTNode 和widget node tree 完全是兩個不一樣的概念,
需要遞迴ASTNode 轉化為 widget node tree.

widget Node 需要的元素

用Name 來記錄是什麼型別的widget

widget的arguments放在 map裡面

widget 的literals 放在list 裡面

widget 的children 放在lsit 裡面

widget 的觸發事件 函式map裡面

widget node 加fromjson ,tojson 方法

可以在遞迴astNode tree 時候,識別InstanceCreationExpression來建立一個widget node。

2.元件資料渲染

框架sdk 中註冊支援的元件,元件包括:

a.原子元件:Flutter sdk 中的 Flutter 的widget

b.本地元件:本地寫好到一個大顆粒的元件,卡片widget元件

c.邏輯元件:本地包裝了邏輯的widget元件

d.動態元件:通過原始碼dsl動態渲染的widget

具體程式碼如下:

 const Map<String, CreateDynamicApi> allWidget = <String,  
 CreateDynamicApi>{
  'Container': wrapContainer,
     ………….
}
 static Widget wrapContainer(Map<String, dynamic> pars) {
  return new Container(
    padding: pars['padding'],
    color: pars['color'],
    child: pars['child'],
    decoration: pars['decoration'],
    width: pars['width'],
    height: pars['height'],
    alignment: pars['alignment']
  );
}

一般我們通過網路請求拿到的資料是一個map。
比如原始碼中寫了這麼一個 '${data.urls[1]}'
AST 解析時候,拿到這麼一個string,或者AST 表示式,通過解析它 ,肯定能從map 中拿到對應的值。

3.邏輯和事件

a.支援邏輯

Flutter 概念萬物都是widget ,可以把表示式,邏輯封裝成一個自定義widget。如果在原始碼裡面寫了if else,變數等,會加重sdk解析的過程。所以把邏輯封裝到widget中。這些邏輯widget,當作元件當成框架元件。

b.支援事件

把頁面跳轉,彈框,等服務,註冊在sdk裡面。約定使用者僅限sdk 的服務。

4.規則和檢測工具

a.檢測規則

需要對原始碼的格式制定規則。比如不支援 直接寫if else ,需要使用邏輯wiget元件來代替if else 語句。如果不制定規則,那ast Node 到widget node 的解析過程會很複雜。理論上都可以解析,只要解析sdk 夠強大。制定規則,可以減輕sdk的解析邏輯。

b.工具檢測

用工具來檢測原始碼是否符合制定的規則,以保證所有的原始碼都能解析出來。

效能和效果

幀率大於50fps,體驗上看比weex相同功能的頁面更加流暢,Samsung galaxy s8上,感覺不出元件是通過動態渲染的.

資料結構

服務端請求到的資料,我們可以約定一種格式如下:

class DataModel {

  Map<dynamic, dynamic>  data;

  String type;

}

每個page 都是由元件組成的,每個元件的資料都是 DataModel來渲染。
根據type 來找到對應的模版,模版+data,渲染出界面。

動態模版管理模組

我們把Widget Node Tree 轉換為一個元件Json模版,它需要一套管理平臺,來支援版本控制,動態下載,升級,回滾,更新等。

框架的邊界

該框架是通過元件的組裝,元件佈局動態變更,頁面佈局動態變更來實現動態化。所以它適合運營變化較快的首頁,詳情,訂單,我的等頁面。一些複雜的邏輯需要封裝在元件裡面,把元件內建到框架中,當作本地元件。框架側重於動態元件的組裝,而引擎對於原始碼複雜的邏輯表示式的解析是弱化的。

後續拓展

1.和UI自動化的結合

UI自動化
,前面已經有文章介紹。UI自動化工具生成元件,再元件轉為模版,動態下發,來快速解決運營需求。

2.國際化的支援

App在不同國家會有不同的功能,我們可以根據區域,來動態拼裝我們的頁面。

3.千人千面

根據不同的人群,來動態渲染不一樣的介面。

總結

本文介紹動態化方案的渲染部分。該方案都在初探階段,還有很多需要完善,後續會繼續擴充套件和修改,等達到開源標準後,會考慮開源。動態方案是一個後端前端一體的方案,需要一整套工具配合,後續會有文章繼續介紹整體的動態化方案。敬請關注閒魚技術公共賬號,也邀請您加入閒魚一起探索有意思的技術。

參考資料:

Static Analysis:

https://www.dartlang.org/guides/language/analysis-options
https://www.dartlang.org/tools/analyzer

dart analyzer :

https://pub.dartlang.org/packages/analyzer
https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli#dartanalyzer

dartdevc:

https://webdev.dartlang.org/tools/dartdevc