'真當Flutter不能熱更新?QQ團隊開源動態化Flutter'

JavaScript Dart iOS GitHub 騰訊QQ Google 碼個蛋 2019-09-06
""真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

架構也有了,方案也有了,要Run起來還有幾個麻煩事要忙活,DartVM 要抽出來,Dart JIT層的輕量級運行時庫,Dart AOT層把DSL轉成真正Widget的UIEngin也要寫哦,就是圖中黃色和紅色的三部分

抽離DartVM

無法簡單修改編譯條件抽離

Dart源代碼在進行編譯時會通過DART_PRECOMPILED_RUNTIME宏進行條件編譯從而在Debug版編譯JIT模式,Release版編譯AOT模式。並且這兩種模式是互斥的,無法同時存在。

簡單的解決方法是

我們單獨編譯出一個DartVM,打包成動態庫,修改導出符號,避免符合衝突

引入DartVM還需要的工作

  • 開發DartVM與Native互通接口,參考了Flutter,使用Native Extension和Dart_Invoke實現互相調用

  • 雙DartVM調試方案,兩個DartVM獨立運行,通過遠程端口單獨調試DartFlutter

  • 支持引入第三方庫,DartFlutter在打包發佈時會通過shell腳本分析.packages文件將依賴庫自動打包隨Dart File Zip一起隨包下發。常用庫可以預先打包的App本地,減少下發文件大小

一個暫時無法解決的問題

安裝包過大,DartVM增大安裝包30M,如果加上原本的AOT40M,整個Flutter安裝包會增大到70M,用DartVM不現實。怎麼辦呢。

01 最終方案 JavasSriptCore 替換 DartVM

可性能分析

  1. JavasSriptCore 是iOS官方庫,不增加安裝包

  2. Dart代碼和JS代碼非常相近,可以用工具轉換

  3. JavasSriptCore 與 Native有更方便的互調接口

  4. ReactNative 已驗證通過JS開發App能力是可行的

  5. JS的執行效率是DartVM的3倍編碼1M的JSON只需 2毫秒

需要解決的問題

用JS開發假的Flutter Runtime 封裝JavasSriptCore與Native、 Flutter互調接口

02 講解下 MXFlutter 的渲染原理

渲染樹

兩個重要的數據結構

  • MXScriptWidget

  • MXWidgetTree

MXScriptWidget管理一個Script頁面或控件,負責創建管理 ScriptWidgetTree,以自增ID與Flutter對應Widget相互調用 ,每次Build都會創建一個新的MXWidgetTree

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

架構也有了,方案也有了,要Run起來還有幾個麻煩事要忙活,DartVM 要抽出來,Dart JIT層的輕量級運行時庫,Dart AOT層把DSL轉成真正Widget的UIEngin也要寫哦,就是圖中黃色和紅色的三部分

抽離DartVM

無法簡單修改編譯條件抽離

Dart源代碼在進行編譯時會通過DART_PRECOMPILED_RUNTIME宏進行條件編譯從而在Debug版編譯JIT模式,Release版編譯AOT模式。並且這兩種模式是互斥的,無法同時存在。

簡單的解決方法是

我們單獨編譯出一個DartVM,打包成動態庫,修改導出符號,避免符合衝突

引入DartVM還需要的工作

  • 開發DartVM與Native互通接口,參考了Flutter,使用Native Extension和Dart_Invoke實現互相調用

  • 雙DartVM調試方案,兩個DartVM獨立運行,通過遠程端口單獨調試DartFlutter

  • 支持引入第三方庫,DartFlutter在打包發佈時會通過shell腳本分析.packages文件將依賴庫自動打包隨Dart File Zip一起隨包下發。常用庫可以預先打包的App本地,減少下發文件大小

一個暫時無法解決的問題

安裝包過大,DartVM增大安裝包30M,如果加上原本的AOT40M,整個Flutter安裝包會增大到70M,用DartVM不現實。怎麼辦呢。

01 最終方案 JavasSriptCore 替換 DartVM

可性能分析

  1. JavasSriptCore 是iOS官方庫,不增加安裝包

  2. Dart代碼和JS代碼非常相近,可以用工具轉換

  3. JavasSriptCore 與 Native有更方便的互調接口

  4. ReactNative 已驗證通過JS開發App能力是可行的

  5. JS的執行效率是DartVM的3倍編碼1M的JSON只需 2毫秒

需要解決的問題

用JS開發假的Flutter Runtime 封裝JavasSriptCore與Native、 Flutter互調接口

02 講解下 MXFlutter 的渲染原理

渲染樹

兩個重要的數據結構

  • MXScriptWidget

  • MXWidgetTree

MXScriptWidget管理一個Script頁面或控件,負責創建管理 ScriptWidgetTree,以自增ID與Flutter對應Widget相互調用 ,每次Build都會創建一個新的MXWidgetTree

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 事件

在 JS 側 buildWidget 時,我們會對 function 事件,生成自增的唯一 callbackID,並與 widgetID 組合拼接成 widgetID/callbackID,作為事件的唯一標識。用戶點擊界面某個 button 時,事件由 Flutter 側傳到 JS 側,通過解析 widgetID/callbackID,找到對應 widget 的 callback,完成事件處理。

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

架構也有了,方案也有了,要Run起來還有幾個麻煩事要忙活,DartVM 要抽出來,Dart JIT層的輕量級運行時庫,Dart AOT層把DSL轉成真正Widget的UIEngin也要寫哦,就是圖中黃色和紅色的三部分

抽離DartVM

無法簡單修改編譯條件抽離

Dart源代碼在進行編譯時會通過DART_PRECOMPILED_RUNTIME宏進行條件編譯從而在Debug版編譯JIT模式,Release版編譯AOT模式。並且這兩種模式是互斥的,無法同時存在。

簡單的解決方法是

我們單獨編譯出一個DartVM,打包成動態庫,修改導出符號,避免符合衝突

引入DartVM還需要的工作

  • 開發DartVM與Native互通接口,參考了Flutter,使用Native Extension和Dart_Invoke實現互相調用

  • 雙DartVM調試方案,兩個DartVM獨立運行,通過遠程端口單獨調試DartFlutter

  • 支持引入第三方庫,DartFlutter在打包發佈時會通過shell腳本分析.packages文件將依賴庫自動打包隨Dart File Zip一起隨包下發。常用庫可以預先打包的App本地,減少下發文件大小

一個暫時無法解決的問題

安裝包過大,DartVM增大安裝包30M,如果加上原本的AOT40M,整個Flutter安裝包會增大到70M,用DartVM不現實。怎麼辦呢。

01 最終方案 JavasSriptCore 替換 DartVM

可性能分析

  1. JavasSriptCore 是iOS官方庫,不增加安裝包

  2. Dart代碼和JS代碼非常相近,可以用工具轉換

  3. JavasSriptCore 與 Native有更方便的互調接口

  4. ReactNative 已驗證通過JS開發App能力是可行的

  5. JS的執行效率是DartVM的3倍編碼1M的JSON只需 2毫秒

需要解決的問題

用JS開發假的Flutter Runtime 封裝JavasSriptCore與Native、 Flutter互調接口

02 講解下 MXFlutter 的渲染原理

渲染樹

兩個重要的數據結構

  • MXScriptWidget

  • MXWidgetTree

MXScriptWidget管理一個Script頁面或控件,負責創建管理 ScriptWidgetTree,以自增ID與Flutter對應Widget相互調用 ,每次Build都會創建一個新的MXWidgetTree

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 事件

在 JS 側 buildWidget 時,我們會對 function 事件,生成自增的唯一 callbackID,並與 widgetID 組合拼接成 widgetID/callbackID,作為事件的唯一標識。用戶點擊界面某個 button 時,事件由 Flutter 側傳到 JS 側,通過解析 widgetID/callbackID,找到對應 widget 的 callback,完成事件處理。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 高效的動態列表

通過在 JS 側,ListView 調用 Build 方法時,提前展開 child, 併為 ListView 增加 children 成員變量。此時,因為僅有數據配置,不會有多餘的 Layout 過程,所以速度是非常快的。

preBuild(jsWidget, buildContext) {

if(this.builder) {

for (let i = 0; i < this.childCount; ++i) {

let w = this.builder(buildContext, i);

this.children.push(w);

}

delete this.builder;

}

super.preBuild(jsWidget, buildContext);

}


在 Flutter 側,ListView 仍然是動態創建,滑動列表,MXFlutter Engine 根據 Children 數組裡的配置數據,創建真正的 Flutter WidgetCell,效率與原生相同完全一致。


ListView.builder(

itemCount: children.length,

itemBuilder: (context, index) {

return UIEngine.toWidget(children[index]);

},

)

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

架構也有了,方案也有了,要Run起來還有幾個麻煩事要忙活,DartVM 要抽出來,Dart JIT層的輕量級運行時庫,Dart AOT層把DSL轉成真正Widget的UIEngin也要寫哦,就是圖中黃色和紅色的三部分

抽離DartVM

無法簡單修改編譯條件抽離

Dart源代碼在進行編譯時會通過DART_PRECOMPILED_RUNTIME宏進行條件編譯從而在Debug版編譯JIT模式,Release版編譯AOT模式。並且這兩種模式是互斥的,無法同時存在。

簡單的解決方法是

我們單獨編譯出一個DartVM,打包成動態庫,修改導出符號,避免符合衝突

引入DartVM還需要的工作

  • 開發DartVM與Native互通接口,參考了Flutter,使用Native Extension和Dart_Invoke實現互相調用

  • 雙DartVM調試方案,兩個DartVM獨立運行,通過遠程端口單獨調試DartFlutter

  • 支持引入第三方庫,DartFlutter在打包發佈時會通過shell腳本分析.packages文件將依賴庫自動打包隨Dart File Zip一起隨包下發。常用庫可以預先打包的App本地,減少下發文件大小

一個暫時無法解決的問題

安裝包過大,DartVM增大安裝包30M,如果加上原本的AOT40M,整個Flutter安裝包會增大到70M,用DartVM不現實。怎麼辦呢。

01 最終方案 JavasSriptCore 替換 DartVM

可性能分析

  1. JavasSriptCore 是iOS官方庫,不增加安裝包

  2. Dart代碼和JS代碼非常相近,可以用工具轉換

  3. JavasSriptCore 與 Native有更方便的互調接口

  4. ReactNative 已驗證通過JS開發App能力是可行的

  5. JS的執行效率是DartVM的3倍編碼1M的JSON只需 2毫秒

需要解決的問題

用JS開發假的Flutter Runtime 封裝JavasSriptCore與Native、 Flutter互調接口

02 講解下 MXFlutter 的渲染原理

渲染樹

兩個重要的數據結構

  • MXScriptWidget

  • MXWidgetTree

MXScriptWidget管理一個Script頁面或控件,負責創建管理 ScriptWidgetTree,以自增ID與Flutter對應Widget相互調用 ,每次Build都會創建一個新的MXWidgetTree

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 事件

在 JS 側 buildWidget 時,我們會對 function 事件,生成自增的唯一 callbackID,並與 widgetID 組合拼接成 widgetID/callbackID,作為事件的唯一標識。用戶點擊界面某個 button 時,事件由 Flutter 側傳到 JS 側,通過解析 widgetID/callbackID,找到對應 widget 的 callback,完成事件處理。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 高效的動態列表

通過在 JS 側,ListView 調用 Build 方法時,提前展開 child, 併為 ListView 增加 children 成員變量。此時,因為僅有數據配置,不會有多餘的 Layout 過程,所以速度是非常快的。

preBuild(jsWidget, buildContext) {

if(this.builder) {

for (let i = 0; i < this.childCount; ++i) {

let w = this.builder(buildContext, i);

this.children.push(w);

}

delete this.builder;

}

super.preBuild(jsWidget, buildContext);

}


在 Flutter 側,ListView 仍然是動態創建,滑動列表,MXFlutter Engine 根據 Children 數組裡的配置數據,創建真正的 Flutter WidgetCell,效率與原生相同完全一致。


ListView.builder(

itemCount: children.length,

itemBuilder: (context, index) {

return UIEngine.toWidget(children[index]);

},

)

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 動畫的方案

動畫參數在VM層配置一次,動畫開始後在Flutter層閉環循環rebuild,形成動畫效果,這個是比較通用的做法了。

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

架構也有了,方案也有了,要Run起來還有幾個麻煩事要忙活,DartVM 要抽出來,Dart JIT層的輕量級運行時庫,Dart AOT層把DSL轉成真正Widget的UIEngin也要寫哦,就是圖中黃色和紅色的三部分

抽離DartVM

無法簡單修改編譯條件抽離

Dart源代碼在進行編譯時會通過DART_PRECOMPILED_RUNTIME宏進行條件編譯從而在Debug版編譯JIT模式,Release版編譯AOT模式。並且這兩種模式是互斥的,無法同時存在。

簡單的解決方法是

我們單獨編譯出一個DartVM,打包成動態庫,修改導出符號,避免符合衝突

引入DartVM還需要的工作

  • 開發DartVM與Native互通接口,參考了Flutter,使用Native Extension和Dart_Invoke實現互相調用

  • 雙DartVM調試方案,兩個DartVM獨立運行,通過遠程端口單獨調試DartFlutter

  • 支持引入第三方庫,DartFlutter在打包發佈時會通過shell腳本分析.packages文件將依賴庫自動打包隨Dart File Zip一起隨包下發。常用庫可以預先打包的App本地,減少下發文件大小

一個暫時無法解決的問題

安裝包過大,DartVM增大安裝包30M,如果加上原本的AOT40M,整個Flutter安裝包會增大到70M,用DartVM不現實。怎麼辦呢。

01 最終方案 JavasSriptCore 替換 DartVM

可性能分析

  1. JavasSriptCore 是iOS官方庫,不增加安裝包

  2. Dart代碼和JS代碼非常相近,可以用工具轉換

  3. JavasSriptCore 與 Native有更方便的互調接口

  4. ReactNative 已驗證通過JS開發App能力是可行的

  5. JS的執行效率是DartVM的3倍編碼1M的JSON只需 2毫秒

需要解決的問題

用JS開發假的Flutter Runtime 封裝JavasSriptCore與Native、 Flutter互調接口

02 講解下 MXFlutter 的渲染原理

渲染樹

兩個重要的數據結構

  • MXScriptWidget

  • MXWidgetTree

MXScriptWidget管理一個Script頁面或控件,負責創建管理 ScriptWidgetTree,以自增ID與Flutter對應Widget相互調用 ,每次Build都會創建一個新的MXWidgetTree

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 事件

在 JS 側 buildWidget 時,我們會對 function 事件,生成自增的唯一 callbackID,並與 widgetID 組合拼接成 widgetID/callbackID,作為事件的唯一標識。用戶點擊界面某個 button 時,事件由 Flutter 側傳到 JS 側,通過解析 widgetID/callbackID,找到對應 widget 的 callback,完成事件處理。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 高效的動態列表

通過在 JS 側,ListView 調用 Build 方法時,提前展開 child, 併為 ListView 增加 children 成員變量。此時,因為僅有數據配置,不會有多餘的 Layout 過程,所以速度是非常快的。

preBuild(jsWidget, buildContext) {

if(this.builder) {

for (let i = 0; i < this.childCount; ++i) {

let w = this.builder(buildContext, i);

this.children.push(w);

}

delete this.builder;

}

super.preBuild(jsWidget, buildContext);

}


在 Flutter 側,ListView 仍然是動態創建,滑動列表,MXFlutter Engine 根據 Children 數組裡的配置數據,創建真正的 Flutter WidgetCell,效率與原生相同完全一致。


ListView.builder(

itemCount: children.length,

itemBuilder: (context, index) {

return UIEngine.toWidget(children[index]);

},

)

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 動畫的方案

動畫參數在VM層配置一次,動畫開始後在Flutter層閉環循環rebuild,形成動畫效果,這個是比較通用的做法了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

03 渲染優化

不管JSWidget創建有多快,總是有跨語言執行,所以減少Build次數和減小Build出來的DSL UI描述大小,可以優化性能。

渲染優化1-局部刷新:配置樹Diff

一個事實

自動對比兩次Widget 無論如何都沒有直接創建一個新的快,如果開發者不參與,由框架來自動計算Diff是得不償失的

可行的方法

犧牲響應式UI框架的設計模式 採用和Native、Web的方式,由開發者參與自己設置Diff的節點,即根據ID獲取對應Widget,修改Widget參數,Rebuild生成新DSL

渲染優化2-局部刷新-嵌套節點

  • MXScriptWidget 是一個具備Build WidgetTree,緩存Callback映射表,動畫支持的基本單位。可以作為普通FlutterWidget來使用。

  • 在Flutter層,如果Widget樹中節點有MXScriptWidget,則在對應節點上創建MXFlutterWidget自定義控件

  • 兩個子樹可以相互對應獲得局部刷新,callback回調,動畫支持,Rebuild時所生產的UI DSL 很大減少,加快刷新速率

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

架構也有了,方案也有了,要Run起來還有幾個麻煩事要忙活,DartVM 要抽出來,Dart JIT層的輕量級運行時庫,Dart AOT層把DSL轉成真正Widget的UIEngin也要寫哦,就是圖中黃色和紅色的三部分

抽離DartVM

無法簡單修改編譯條件抽離

Dart源代碼在進行編譯時會通過DART_PRECOMPILED_RUNTIME宏進行條件編譯從而在Debug版編譯JIT模式,Release版編譯AOT模式。並且這兩種模式是互斥的,無法同時存在。

簡單的解決方法是

我們單獨編譯出一個DartVM,打包成動態庫,修改導出符號,避免符合衝突

引入DartVM還需要的工作

  • 開發DartVM與Native互通接口,參考了Flutter,使用Native Extension和Dart_Invoke實現互相調用

  • 雙DartVM調試方案,兩個DartVM獨立運行,通過遠程端口單獨調試DartFlutter

  • 支持引入第三方庫,DartFlutter在打包發佈時會通過shell腳本分析.packages文件將依賴庫自動打包隨Dart File Zip一起隨包下發。常用庫可以預先打包的App本地,減少下發文件大小

一個暫時無法解決的問題

安裝包過大,DartVM增大安裝包30M,如果加上原本的AOT40M,整個Flutter安裝包會增大到70M,用DartVM不現實。怎麼辦呢。

01 最終方案 JavasSriptCore 替換 DartVM

可性能分析

  1. JavasSriptCore 是iOS官方庫,不增加安裝包

  2. Dart代碼和JS代碼非常相近,可以用工具轉換

  3. JavasSriptCore 與 Native有更方便的互調接口

  4. ReactNative 已驗證通過JS開發App能力是可行的

  5. JS的執行效率是DartVM的3倍編碼1M的JSON只需 2毫秒

需要解決的問題

用JS開發假的Flutter Runtime 封裝JavasSriptCore與Native、 Flutter互調接口

02 講解下 MXFlutter 的渲染原理

渲染樹

兩個重要的數據結構

  • MXScriptWidget

  • MXWidgetTree

MXScriptWidget管理一個Script頁面或控件,負責創建管理 ScriptWidgetTree,以自增ID與Flutter對應Widget相互調用 ,每次Build都會創建一個新的MXWidgetTree

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 事件

在 JS 側 buildWidget 時,我們會對 function 事件,生成自增的唯一 callbackID,並與 widgetID 組合拼接成 widgetID/callbackID,作為事件的唯一標識。用戶點擊界面某個 button 時,事件由 Flutter 側傳到 JS 側,通過解析 widgetID/callbackID,找到對應 widget 的 callback,完成事件處理。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 高效的動態列表

通過在 JS 側,ListView 調用 Build 方法時,提前展開 child, 併為 ListView 增加 children 成員變量。此時,因為僅有數據配置,不會有多餘的 Layout 過程,所以速度是非常快的。

preBuild(jsWidget, buildContext) {

if(this.builder) {

for (let i = 0; i < this.childCount; ++i) {

let w = this.builder(buildContext, i);

this.children.push(w);

}

delete this.builder;

}

super.preBuild(jsWidget, buildContext);

}


在 Flutter 側,ListView 仍然是動態創建,滑動列表,MXFlutter Engine 根據 Children 數組裡的配置數據,創建真正的 Flutter WidgetCell,效率與原生相同完全一致。


ListView.builder(

itemCount: children.length,

itemBuilder: (context, index) {

return UIEngine.toWidget(children[index]);

},

)

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 動畫的方案

動畫參數在VM層配置一次,動畫開始後在Flutter層閉環循環rebuild,形成動畫效果,這個是比較通用的做法了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

03 渲染優化

不管JSWidget創建有多快,總是有跨語言執行,所以減少Build次數和減小Build出來的DSL UI描述大小,可以優化性能。

渲染優化1-局部刷新:配置樹Diff

一個事實

自動對比兩次Widget 無論如何都沒有直接創建一個新的快,如果開發者不參與,由框架來自動計算Diff是得不償失的

可行的方法

犧牲響應式UI框架的設計模式 採用和Native、Web的方式,由開發者參與自己設置Diff的節點,即根據ID獲取對應Widget,修改Widget參數,Rebuild生成新DSL

渲染優化2-局部刷新-嵌套節點

  • MXScriptWidget 是一個具備Build WidgetTree,緩存Callback映射表,動畫支持的基本單位。可以作為普通FlutterWidget來使用。

  • 在Flutter層,如果Widget樹中節點有MXScriptWidget,則在對應節點上創建MXFlutterWidget自定義控件

  • 兩個子樹可以相互對應獲得局部刷新,callback回調,動畫支持,Rebuild時所生產的UI DSL 很大減少,加快刷新速率

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

渲染優化3-可以分離動態和靜態控件

MXStatelessWidget 可以通過使用無狀態的ScriptWidget來向框架標示,其下面的子樹,在每次build中不會變化,其build結果會被緩存,下次在Flutter層直接複用

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

架構也有了,方案也有了,要Run起來還有幾個麻煩事要忙活,DartVM 要抽出來,Dart JIT層的輕量級運行時庫,Dart AOT層把DSL轉成真正Widget的UIEngin也要寫哦,就是圖中黃色和紅色的三部分

抽離DartVM

無法簡單修改編譯條件抽離

Dart源代碼在進行編譯時會通過DART_PRECOMPILED_RUNTIME宏進行條件編譯從而在Debug版編譯JIT模式,Release版編譯AOT模式。並且這兩種模式是互斥的,無法同時存在。

簡單的解決方法是

我們單獨編譯出一個DartVM,打包成動態庫,修改導出符號,避免符合衝突

引入DartVM還需要的工作

  • 開發DartVM與Native互通接口,參考了Flutter,使用Native Extension和Dart_Invoke實現互相調用

  • 雙DartVM調試方案,兩個DartVM獨立運行,通過遠程端口單獨調試DartFlutter

  • 支持引入第三方庫,DartFlutter在打包發佈時會通過shell腳本分析.packages文件將依賴庫自動打包隨Dart File Zip一起隨包下發。常用庫可以預先打包的App本地,減少下發文件大小

一個暫時無法解決的問題

安裝包過大,DartVM增大安裝包30M,如果加上原本的AOT40M,整個Flutter安裝包會增大到70M,用DartVM不現實。怎麼辦呢。

01 最終方案 JavasSriptCore 替換 DartVM

可性能分析

  1. JavasSriptCore 是iOS官方庫,不增加安裝包

  2. Dart代碼和JS代碼非常相近,可以用工具轉換

  3. JavasSriptCore 與 Native有更方便的互調接口

  4. ReactNative 已驗證通過JS開發App能力是可行的

  5. JS的執行效率是DartVM的3倍編碼1M的JSON只需 2毫秒

需要解決的問題

用JS開發假的Flutter Runtime 封裝JavasSriptCore與Native、 Flutter互調接口

02 講解下 MXFlutter 的渲染原理

渲染樹

兩個重要的數據結構

  • MXScriptWidget

  • MXWidgetTree

MXScriptWidget管理一個Script頁面或控件,負責創建管理 ScriptWidgetTree,以自增ID與Flutter對應Widget相互調用 ,每次Build都會創建一個新的MXWidgetTree

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 事件

在 JS 側 buildWidget 時,我們會對 function 事件,生成自增的唯一 callbackID,並與 widgetID 組合拼接成 widgetID/callbackID,作為事件的唯一標識。用戶點擊界面某個 button 時,事件由 Flutter 側傳到 JS 側,通過解析 widgetID/callbackID,找到對應 widget 的 callback,完成事件處理。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 高效的動態列表

通過在 JS 側,ListView 調用 Build 方法時,提前展開 child, 併為 ListView 增加 children 成員變量。此時,因為僅有數據配置,不會有多餘的 Layout 過程,所以速度是非常快的。

preBuild(jsWidget, buildContext) {

if(this.builder) {

for (let i = 0; i < this.childCount; ++i) {

let w = this.builder(buildContext, i);

this.children.push(w);

}

delete this.builder;

}

super.preBuild(jsWidget, buildContext);

}


在 Flutter 側,ListView 仍然是動態創建,滑動列表,MXFlutter Engine 根據 Children 數組裡的配置數據,創建真正的 Flutter WidgetCell,效率與原生相同完全一致。


ListView.builder(

itemCount: children.length,

itemBuilder: (context, index) {

return UIEngine.toWidget(children[index]);

},

)

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 動畫的方案

動畫參數在VM層配置一次,動畫開始後在Flutter層閉環循環rebuild,形成動畫效果,這個是比較通用的做法了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

03 渲染優化

不管JSWidget創建有多快,總是有跨語言執行,所以減少Build次數和減小Build出來的DSL UI描述大小,可以優化性能。

渲染優化1-局部刷新:配置樹Diff

一個事實

自動對比兩次Widget 無論如何都沒有直接創建一個新的快,如果開發者不參與,由框架來自動計算Diff是得不償失的

可行的方法

犧牲響應式UI框架的設計模式 採用和Native、Web的方式,由開發者參與自己設置Diff的節點,即根據ID獲取對應Widget,修改Widget參數,Rebuild生成新DSL

渲染優化2-局部刷新-嵌套節點

  • MXScriptWidget 是一個具備Build WidgetTree,緩存Callback映射表,動畫支持的基本單位。可以作為普通FlutterWidget來使用。

  • 在Flutter層,如果Widget樹中節點有MXScriptWidget,則在對應節點上創建MXFlutterWidget自定義控件

  • 兩個子樹可以相互對應獲得局部刷新,callback回調,動畫支持,Rebuild時所生產的UI DSL 很大減少,加快刷新速率

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

渲染優化3-可以分離動態和靜態控件

MXStatelessWidget 可以通過使用無狀態的ScriptWidget來向框架標示,其下面的子樹,在每次build中不會變化,其build結果會被緩存,下次在Flutter層直接複用

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

內存-跨層鏡像對象的生命週期

VM層,Flutter層,Native層鏡像對象的生命週期如何控制?參考 iOS JavaScriptCore 和 Objective-C的解決方法

  1. 以Flutter層的對象生命週期為主

  2. 在VM層增加WeakMap支持,不增加對象引用計數,Flutter層釋放之後,釋放VM層對象

  3. 在Native層使用 JSManagerValue,VM層對象釋放後,Native的引用被自動置空

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

架構也有了,方案也有了,要Run起來還有幾個麻煩事要忙活,DartVM 要抽出來,Dart JIT層的輕量級運行時庫,Dart AOT層把DSL轉成真正Widget的UIEngin也要寫哦,就是圖中黃色和紅色的三部分

抽離DartVM

無法簡單修改編譯條件抽離

Dart源代碼在進行編譯時會通過DART_PRECOMPILED_RUNTIME宏進行條件編譯從而在Debug版編譯JIT模式,Release版編譯AOT模式。並且這兩種模式是互斥的,無法同時存在。

簡單的解決方法是

我們單獨編譯出一個DartVM,打包成動態庫,修改導出符號,避免符合衝突

引入DartVM還需要的工作

  • 開發DartVM與Native互通接口,參考了Flutter,使用Native Extension和Dart_Invoke實現互相調用

  • 雙DartVM調試方案,兩個DartVM獨立運行,通過遠程端口單獨調試DartFlutter

  • 支持引入第三方庫,DartFlutter在打包發佈時會通過shell腳本分析.packages文件將依賴庫自動打包隨Dart File Zip一起隨包下發。常用庫可以預先打包的App本地,減少下發文件大小

一個暫時無法解決的問題

安裝包過大,DartVM增大安裝包30M,如果加上原本的AOT40M,整個Flutter安裝包會增大到70M,用DartVM不現實。怎麼辦呢。

01 最終方案 JavasSriptCore 替換 DartVM

可性能分析

  1. JavasSriptCore 是iOS官方庫,不增加安裝包

  2. Dart代碼和JS代碼非常相近,可以用工具轉換

  3. JavasSriptCore 與 Native有更方便的互調接口

  4. ReactNative 已驗證通過JS開發App能力是可行的

  5. JS的執行效率是DartVM的3倍編碼1M的JSON只需 2毫秒

需要解決的問題

用JS開發假的Flutter Runtime 封裝JavasSriptCore與Native、 Flutter互調接口

02 講解下 MXFlutter 的渲染原理

渲染樹

兩個重要的數據結構

  • MXScriptWidget

  • MXWidgetTree

MXScriptWidget管理一個Script頁面或控件,負責創建管理 ScriptWidgetTree,以自增ID與Flutter對應Widget相互調用 ,每次Build都會創建一個新的MXWidgetTree

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 事件

在 JS 側 buildWidget 時,我們會對 function 事件,生成自增的唯一 callbackID,並與 widgetID 組合拼接成 widgetID/callbackID,作為事件的唯一標識。用戶點擊界面某個 button 時,事件由 Flutter 側傳到 JS 側,通過解析 widgetID/callbackID,找到對應 widget 的 callback,完成事件處理。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 高效的動態列表

通過在 JS 側,ListView 調用 Build 方法時,提前展開 child, 併為 ListView 增加 children 成員變量。此時,因為僅有數據配置,不會有多餘的 Layout 過程,所以速度是非常快的。

preBuild(jsWidget, buildContext) {

if(this.builder) {

for (let i = 0; i < this.childCount; ++i) {

let w = this.builder(buildContext, i);

this.children.push(w);

}

delete this.builder;

}

super.preBuild(jsWidget, buildContext);

}


在 Flutter 側,ListView 仍然是動態創建,滑動列表,MXFlutter Engine 根據 Children 數組裡的配置數據,創建真正的 Flutter WidgetCell,效率與原生相同完全一致。


ListView.builder(

itemCount: children.length,

itemBuilder: (context, index) {

return UIEngine.toWidget(children[index]);

},

)

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 動畫的方案

動畫參數在VM層配置一次,動畫開始後在Flutter層閉環循環rebuild,形成動畫效果,這個是比較通用的做法了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

03 渲染優化

不管JSWidget創建有多快,總是有跨語言執行,所以減少Build次數和減小Build出來的DSL UI描述大小,可以優化性能。

渲染優化1-局部刷新:配置樹Diff

一個事實

自動對比兩次Widget 無論如何都沒有直接創建一個新的快,如果開發者不參與,由框架來自動計算Diff是得不償失的

可行的方法

犧牲響應式UI框架的設計模式 採用和Native、Web的方式,由開發者參與自己設置Diff的節點,即根據ID獲取對應Widget,修改Widget參數,Rebuild生成新DSL

渲染優化2-局部刷新-嵌套節點

  • MXScriptWidget 是一個具備Build WidgetTree,緩存Callback映射表,動畫支持的基本單位。可以作為普通FlutterWidget來使用。

  • 在Flutter層,如果Widget樹中節點有MXScriptWidget,則在對應節點上創建MXFlutterWidget自定義控件

  • 兩個子樹可以相互對應獲得局部刷新,callback回調,動畫支持,Rebuild時所生產的UI DSL 很大減少,加快刷新速率

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

渲染優化3-可以分離動態和靜態控件

MXStatelessWidget 可以通過使用無狀態的ScriptWidget來向框架標示,其下面的子樹,在每次build中不會變化,其build結果會被緩存,下次在Flutter層直接複用

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

內存-跨層鏡像對象的生命週期

VM層,Flutter層,Native層鏡像對象的生命週期如何控制?參考 iOS JavaScriptCore 和 Objective-C的解決方法

  1. 以Flutter層的對象生命週期為主

  2. 在VM層增加WeakMap支持,不增加對象引用計數,Flutter層釋放之後,釋放VM層對象

  3. 在Native層使用 JSManagerValue,VM層對象釋放後,Native的引用被自動置空

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

線程問題

參照業界RN等框架的設計,VM層跑在一個單獨的後臺線程

  1. 從Flutter層通過Native通道調用到VM,發生兩次線程切換

  2. Flutter UI層和MXScript層是異步調用,限制動態控件的架構設計

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

架構也有了,方案也有了,要Run起來還有幾個麻煩事要忙活,DartVM 要抽出來,Dart JIT層的輕量級運行時庫,Dart AOT層把DSL轉成真正Widget的UIEngin也要寫哦,就是圖中黃色和紅色的三部分

抽離DartVM

無法簡單修改編譯條件抽離

Dart源代碼在進行編譯時會通過DART_PRECOMPILED_RUNTIME宏進行條件編譯從而在Debug版編譯JIT模式,Release版編譯AOT模式。並且這兩種模式是互斥的,無法同時存在。

簡單的解決方法是

我們單獨編譯出一個DartVM,打包成動態庫,修改導出符號,避免符合衝突

引入DartVM還需要的工作

  • 開發DartVM與Native互通接口,參考了Flutter,使用Native Extension和Dart_Invoke實現互相調用

  • 雙DartVM調試方案,兩個DartVM獨立運行,通過遠程端口單獨調試DartFlutter

  • 支持引入第三方庫,DartFlutter在打包發佈時會通過shell腳本分析.packages文件將依賴庫自動打包隨Dart File Zip一起隨包下發。常用庫可以預先打包的App本地,減少下發文件大小

一個暫時無法解決的問題

安裝包過大,DartVM增大安裝包30M,如果加上原本的AOT40M,整個Flutter安裝包會增大到70M,用DartVM不現實。怎麼辦呢。

01 最終方案 JavasSriptCore 替換 DartVM

可性能分析

  1. JavasSriptCore 是iOS官方庫,不增加安裝包

  2. Dart代碼和JS代碼非常相近,可以用工具轉換

  3. JavasSriptCore 與 Native有更方便的互調接口

  4. ReactNative 已驗證通過JS開發App能力是可行的

  5. JS的執行效率是DartVM的3倍編碼1M的JSON只需 2毫秒

需要解決的問題

用JS開發假的Flutter Runtime 封裝JavasSriptCore與Native、 Flutter互調接口

02 講解下 MXFlutter 的渲染原理

渲染樹

兩個重要的數據結構

  • MXScriptWidget

  • MXWidgetTree

MXScriptWidget管理一個Script頁面或控件,負責創建管理 ScriptWidgetTree,以自增ID與Flutter對應Widget相互調用 ,每次Build都會創建一個新的MXWidgetTree

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 事件

在 JS 側 buildWidget 時,我們會對 function 事件,生成自增的唯一 callbackID,並與 widgetID 組合拼接成 widgetID/callbackID,作為事件的唯一標識。用戶點擊界面某個 button 時,事件由 Flutter 側傳到 JS 側,通過解析 widgetID/callbackID,找到對應 widget 的 callback,完成事件處理。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 高效的動態列表

通過在 JS 側,ListView 調用 Build 方法時,提前展開 child, 併為 ListView 增加 children 成員變量。此時,因為僅有數據配置,不會有多餘的 Layout 過程,所以速度是非常快的。

preBuild(jsWidget, buildContext) {

if(this.builder) {

for (let i = 0; i < this.childCount; ++i) {

let w = this.builder(buildContext, i);

this.children.push(w);

}

delete this.builder;

}

super.preBuild(jsWidget, buildContext);

}


在 Flutter 側,ListView 仍然是動態創建,滑動列表,MXFlutter Engine 根據 Children 數組裡的配置數據,創建真正的 Flutter WidgetCell,效率與原生相同完全一致。


ListView.builder(

itemCount: children.length,

itemBuilder: (context, index) {

return UIEngine.toWidget(children[index]);

},

)

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 動畫的方案

動畫參數在VM層配置一次,動畫開始後在Flutter層閉環循環rebuild,形成動畫效果,這個是比較通用的做法了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

03 渲染優化

不管JSWidget創建有多快,總是有跨語言執行,所以減少Build次數和減小Build出來的DSL UI描述大小,可以優化性能。

渲染優化1-局部刷新:配置樹Diff

一個事實

自動對比兩次Widget 無論如何都沒有直接創建一個新的快,如果開發者不參與,由框架來自動計算Diff是得不償失的

可行的方法

犧牲響應式UI框架的設計模式 採用和Native、Web的方式,由開發者參與自己設置Diff的節點,即根據ID獲取對應Widget,修改Widget參數,Rebuild生成新DSL

渲染優化2-局部刷新-嵌套節點

  • MXScriptWidget 是一個具備Build WidgetTree,緩存Callback映射表,動畫支持的基本單位。可以作為普通FlutterWidget來使用。

  • 在Flutter層,如果Widget樹中節點有MXScriptWidget,則在對應節點上創建MXFlutterWidget自定義控件

  • 兩個子樹可以相互對應獲得局部刷新,callback回調,動畫支持,Rebuild時所生產的UI DSL 很大減少,加快刷新速率

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

渲染優化3-可以分離動態和靜態控件

MXStatelessWidget 可以通過使用無狀態的ScriptWidget來向框架標示,其下面的子樹,在每次build中不會變化,其build結果會被緩存,下次在Flutter層直接複用

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

內存-跨層鏡像對象的生命週期

VM層,Flutter層,Native層鏡像對象的生命週期如何控制?參考 iOS JavaScriptCore 和 Objective-C的解決方法

  1. 以Flutter層的對象生命週期為主

  2. 在VM層增加WeakMap支持,不增加對象引用計數,Flutter層釋放之後,釋放VM層對象

  3. 在Native層使用 JSManagerValue,VM層對象釋放後,Native的引用被自動置空

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

線程問題

參照業界RN等框架的設計,VM層跑在一個單獨的後臺線程

  1. 從Flutter層通過Native通道調用到VM,發生兩次線程切換

  2. Flutter UI層和MXScript層是異步調用,限制動態控件的架構設計

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

一個可行方案 修改FlutterEngine ,定製開發Dart->Native->VM 這個通道,調用到VM不切換線程 VM不新建線程,直接由Flutter UI Thread 消息循環驅動,這樣也同時支持了和Flutter UI 層的高效同步調用,但要注意從Native調用到VM,需要通過定製FlutterEngine的接口。

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

架構也有了,方案也有了,要Run起來還有幾個麻煩事要忙活,DartVM 要抽出來,Dart JIT層的輕量級運行時庫,Dart AOT層把DSL轉成真正Widget的UIEngin也要寫哦,就是圖中黃色和紅色的三部分

抽離DartVM

無法簡單修改編譯條件抽離

Dart源代碼在進行編譯時會通過DART_PRECOMPILED_RUNTIME宏進行條件編譯從而在Debug版編譯JIT模式,Release版編譯AOT模式。並且這兩種模式是互斥的,無法同時存在。

簡單的解決方法是

我們單獨編譯出一個DartVM,打包成動態庫,修改導出符號,避免符合衝突

引入DartVM還需要的工作

  • 開發DartVM與Native互通接口,參考了Flutter,使用Native Extension和Dart_Invoke實現互相調用

  • 雙DartVM調試方案,兩個DartVM獨立運行,通過遠程端口單獨調試DartFlutter

  • 支持引入第三方庫,DartFlutter在打包發佈時會通過shell腳本分析.packages文件將依賴庫自動打包隨Dart File Zip一起隨包下發。常用庫可以預先打包的App本地,減少下發文件大小

一個暫時無法解決的問題

安裝包過大,DartVM增大安裝包30M,如果加上原本的AOT40M,整個Flutter安裝包會增大到70M,用DartVM不現實。怎麼辦呢。

01 最終方案 JavasSriptCore 替換 DartVM

可性能分析

  1. JavasSriptCore 是iOS官方庫,不增加安裝包

  2. Dart代碼和JS代碼非常相近,可以用工具轉換

  3. JavasSriptCore 與 Native有更方便的互調接口

  4. ReactNative 已驗證通過JS開發App能力是可行的

  5. JS的執行效率是DartVM的3倍編碼1M的JSON只需 2毫秒

需要解決的問題

用JS開發假的Flutter Runtime 封裝JavasSriptCore與Native、 Flutter互調接口

02 講解下 MXFlutter 的渲染原理

渲染樹

兩個重要的數據結構

  • MXScriptWidget

  • MXWidgetTree

MXScriptWidget管理一個Script頁面或控件,負責創建管理 ScriptWidgetTree,以自增ID與Flutter對應Widget相互調用 ,每次Build都會創建一個新的MXWidgetTree

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 事件

在 JS 側 buildWidget 時,我們會對 function 事件,生成自增的唯一 callbackID,並與 widgetID 組合拼接成 widgetID/callbackID,作為事件的唯一標識。用戶點擊界面某個 button 時,事件由 Flutter 側傳到 JS 側,通過解析 widgetID/callbackID,找到對應 widget 的 callback,完成事件處理。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 高效的動態列表

通過在 JS 側,ListView 調用 Build 方法時,提前展開 child, 併為 ListView 增加 children 成員變量。此時,因為僅有數據配置,不會有多餘的 Layout 過程,所以速度是非常快的。

preBuild(jsWidget, buildContext) {

if(this.builder) {

for (let i = 0; i < this.childCount; ++i) {

let w = this.builder(buildContext, i);

this.children.push(w);

}

delete this.builder;

}

super.preBuild(jsWidget, buildContext);

}


在 Flutter 側,ListView 仍然是動態創建,滑動列表,MXFlutter Engine 根據 Children 數組裡的配置數據,創建真正的 Flutter WidgetCell,效率與原生相同完全一致。


ListView.builder(

itemCount: children.length,

itemBuilder: (context, index) {

return UIEngine.toWidget(children[index]);

},

)

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 動畫的方案

動畫參數在VM層配置一次,動畫開始後在Flutter層閉環循環rebuild,形成動畫效果,這個是比較通用的做法了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

03 渲染優化

不管JSWidget創建有多快,總是有跨語言執行,所以減少Build次數和減小Build出來的DSL UI描述大小,可以優化性能。

渲染優化1-局部刷新:配置樹Diff

一個事實

自動對比兩次Widget 無論如何都沒有直接創建一個新的快,如果開發者不參與,由框架來自動計算Diff是得不償失的

可行的方法

犧牲響應式UI框架的設計模式 採用和Native、Web的方式,由開發者參與自己設置Diff的節點,即根據ID獲取對應Widget,修改Widget參數,Rebuild生成新DSL

渲染優化2-局部刷新-嵌套節點

  • MXScriptWidget 是一個具備Build WidgetTree,緩存Callback映射表,動畫支持的基本單位。可以作為普通FlutterWidget來使用。

  • 在Flutter層,如果Widget樹中節點有MXScriptWidget,則在對應節點上創建MXFlutterWidget自定義控件

  • 兩個子樹可以相互對應獲得局部刷新,callback回調,動畫支持,Rebuild時所生產的UI DSL 很大減少,加快刷新速率

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

渲染優化3-可以分離動態和靜態控件

MXStatelessWidget 可以通過使用無狀態的ScriptWidget來向框架標示,其下面的子樹,在每次build中不會變化,其build結果會被緩存,下次在Flutter層直接複用

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

內存-跨層鏡像對象的生命週期

VM層,Flutter層,Native層鏡像對象的生命週期如何控制?參考 iOS JavaScriptCore 和 Objective-C的解決方法

  1. 以Flutter層的對象生命週期為主

  2. 在VM層增加WeakMap支持,不增加對象引用計數,Flutter層釋放之後,釋放VM層對象

  3. 在Native層使用 JSManagerValue,VM層對象釋放後,Native的引用被自動置空

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

線程問題

參照業界RN等框架的設計,VM層跑在一個單獨的後臺線程

  1. 從Flutter層通過Native通道調用到VM,發生兩次線程切換

  2. Flutter UI層和MXScript層是異步調用,限制動態控件的架構設計

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

一個可行方案 修改FlutterEngine ,定製開發Dart->Native->VM 這個通道,調用到VM不切換線程 VM不新建線程,直接由Flutter UI Thread 消息循環驅動,這樣也同時支持了和Flutter UI 層的高效同步調用,但要注意從Native調用到VM,需要通過定製FlutterEngine的接口。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

04 讓開發者寫出優雅的代碼

讓開發者寫出優雅的代碼,咳咳,這裡有點吹了,總之,我們想讓使用MXFlutter的開發同學寫出來的代碼看來正規一些,好看一些。

  • 完美支持Dart Flutter語法

  • 定義所有Flutter 中同名Widget類,構建Widget的參數類,支持相同的Build方式,SetState觸發刷新,事件響應函數

  • Callback函數自動生成CallbackID

  • Callback函數自動This綁定

  • ListView 像Dart層一樣開發,支持itemBuilder回調函數

參考JS示例源碼TGIF-iMatrix home_page.js(https://github.com/TGIF-iMatrix/MXFlutter/blob/master/js_flutter_src/app_test/home_page.js

05 MXFlutter 基礎建設

因為 JavaScript 不支持模塊化開發,不能引用其他文件代碼,我們參照 RN,使用 Node.js 的模塊化代碼,在Native 層支持 require 語法。開發時,IDE最好選用 VSCode,因為可以按裝JS插件,直接運行調試JS

另外,我們通過重定向模擬器 JS 路徑文件到開發機,用戶修改完 JS 文件,便可直接看到相應修改,實現模擬器的頁面熱更新。

結語

由於時間緊張,MXFlutter還有很多遺留的問題,作為一個技術探索,非常辛苦但非常有趣,期待各位大牛指導,期待小夥伴們提出問題一起討論解決。

要了解全部,一定要拉下源碼,運行起來看看,有問題可以留言一討論,MXFlutter會持續更新。

項目成員 luca 浪哥,nice,yockie 帥哥貢獻了動畫,控件,示例 APP 等核心實 現, chaodong 老師負責了 DartVM 方案,IP 老師幫忙提供了單元測試,健身大 神 yofer 老師負責了代碼維護,工具建設。

TGIF-iMatrix 是一個技術氛圍濃厚,有美女有帥哥有趣有愛的團隊,還有精通量子計算,5G 等前沿技術的數據分析 victor 老王。

對 MXFlutter 有興趣的小夥伴,可以加 QQ 群交流 QQ 群:747535761

今日問題:

大家需要熱更新的Flutter嗎?你應用裡用到熱更新沒?

"真當Flutter不能熱更新?QQ團隊開源動態化Flutter

碼個蛋(codeegg)第 671 次推文

作者:SoapY

原文:https://juejin.im/post/5d11a4f06fb9a07ec63b21ea

基於JS的高性能Flutter動態化框架

這可能是目前放出來的相對最完整的Flutter動態化方案

緣起:18年10月份,我們團隊的iOS產品嚐試引入 Flutter,做為iOS開發,一接觸到Flutter就馬上感受到,Flutter 雖然強大,但不能動態化是阻礙我們使用她的唯一障礙了。捨棄Native的開發方式,一個很大的訴求是獲取動態更新的能力。看Google團隊對動態化的措辭,應該指望不上了,自己動手豐衣足食。

簡介

項目代號:MXFlutter (Matrix Flutter)

核心思路是把 Flutter 的渲染邏輯中的三棵樹中的第一棵,放到 JavaScript 中生成。用 JavaScript 完整實現了 Flutter 控件層封裝,可以使用 JavaScript,用極其類似 Dart 的開發方式,開發Flutter應用,利用JavaScript版的輕量級Flutter Runtime,生成UI描述,傳遞給Dart層的UI引擎,UI引擎把UI描述生產真正的 Flutter 控件。所以,他在iOS上是完全動態化的 ,完整代碼在github:

如果能幫助到大家,請給MXFlutter點個Star,給我們有動力繼續更新下去^_*,也讓整個Flutter社區都能瞭解到我們開發者的貢獻。github TGIF-iMatrix MXFlutter(https://github.com/TGIF-iMatrix/MXFlutter

繼續前先瞥一眼整體的架構,一句話介紹MXFlutter,就是用JavaScript,以Flutter的寫法開發Flutter。汗...還是有點繞,大家看下面貼出來的代碼吧。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

效果

先看看使用效果,以下截圖是在MXFlutter框架下用JS開發,大家可以把上面的源碼下載下來,裡面有完整的JS代碼示例:

這個是APP示例截圖

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

下面是UI截圖對應的JS代碼,沒錯,你沒有眼花,這個是真的 JavaScript 代碼,可以在 MXFlutter 的運行時庫上渲染出 Flutter 的UI

class JSPestoPage extends MXJSWidget {

constructor {

super("JSPestoPage");

this.recipes = recipeList;

}

build(context) {

let statusBarHeight = 24;

let mq = MediaQuery.of(context);

if (mq) {

statusBarHeight = mq.padding.top

}

let w = new Scaffold({

appBar: new AppBar({

title: new Text("Pesto Demo")

}),

floatingActionButton: new FloatingActionButton({

child: new Icon(new IconData(0xe3c9)),

onPressed: this.createCallbackID(function {

}),

}),

body: new CustomScrollView({

semanticChildCount: this.recipes.length,

slivers: [

//this.buildAppBar(context, statusBarHeight),

this.buildBody(context, statusBarHeight),

],

}),

//body:this.buildItems[0]

});

return w;

}

buildAppBar(context, statusBarHeight) {

return SliverAppBar({

pinned: true,

expandedHeight: _kAppBarHeight,

actions: [

IconButton({

icon: new Icon(new IconData(1)),

tooltip: 'Search',

onPressed: this.createCallbackID(function () {

}),

}),

],

flexibleSpace: LayoutBuilder({

builder: function (context, constraints) {

size = constraints.biggest;

appBarHeight = size.height - statusBarHeight;

t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);

extraPadding = new Tween({ begin: 10.0, end: 24.0 }).transform(t);

logoHeight = appBarHeight - 1.5 * extraPadding;

return Padding({

padding: EdgeInsets.only({

top: statusBarHeight + 0.5 * extraPadding,

bottom: extraPadding,

}),

child: Center({

child: new Icon(new IconData(1))

}),

});

},

}),

});

}

buildBody(context, statusBarHeight) {

let mediaPadding = EdgeInsets.all(0);

let mq = MediaQuery.of(context);

if (mq) {

mediaPadding = MediaQuery.of(context).padding;

}

let padding = EdgeInsets.only({

top: 8.0,

left: 8.0 + mediaPadding.left,

right: 8.0 + mediaPadding.right,

bottom: 8.0

});

return new SliverPadding({

padding: padding,

sliver: new SliverGrid({

gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent({

maxCrossAxisExtent: _kRecipePageMaxWidth,

crossAxisSpacing: 8.0,

mainAxisSpacing: 8.0,

}),

delegate: new SliverChildBuilderDelegate(

function (context, index) {

let recipe = this.recipes[index];

let w = new RecipeCard({

recipe: recipe,

onTap: function { showRecipePage(context, recipe); },

});

return w;

},

{

childCount: this.recipes.length,

}),

}),

});

}

源碼中還有更豐滿的示例,高仿知乎頁面JSFlutter版github.com/TGIF-iMatri…,這是對應UI,已經接近在線上版直接使用了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

現狀

MXFlutter雖然各個模塊已相對完整,但投入生產還需要解決其中的BUG,由於19年初,小組啟動新項目,非常繁忙,幾乎沒有時間繼續開發,從3月份一直暫停,目前人力仍然很緊張,如果大家有興趣,期待小夥伴們一起加入,共同豐富 MXFlutter 動態化能力。

00 分享下動態化探索過程中的幾個炮灰方案

Flutter 動態化方案一:靜態解析Dart語言,生成UI描述

Dart 本身是描述語言,IDE 的 Outline 工具可以解析 Dart 代碼生成樹形結構,我們可以利用其源碼,生成 JSON UI 描述,相關代碼:github.com/flutter/flu…dart-sdk: analysis_server

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

靜態解析 Dart 缺點,不能寫邏輯,對編寫UI代碼有很多限制,不能寫判斷語句,不能寫函數,要支持這些成本很高。所以只好放棄。

快速介紹下Flutter的核心渲染模塊三棵樹

響應式UI框架

  • WidgetTree:Widget 裡面存儲了一個視圖的配置信息,可以高效的創建(build)和銷燬

  • Element 是分離 WidgetTree 和真正的渲染對象的中間層, WidgetTree 用來描述對應的Element 屬性

  • RenderObject 來執行 Diff, Hit Test 佈局、繪製

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

第一棵樹有完整的UI描述信息,那麼我只要JIT下通過 DartVM 創建第一棵樹,其他耗時的操作都丟到AOT裡去。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

Flutter 動態化方案二:動態運行 Dart 語言,生產UI描述

和方案一靜態解析Dart對比,第二個方案是寫一個極其輕量的運行時庫,讓編寫UI的Dart 代碼運行了起來,生成樹形結構,再序列化為 JSON(debug),FlatBuffers (release)UI 描述。可以稱之為動態解析方案

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

具體渲染邏輯

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

總體架構

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

架構也有了,方案也有了,要Run起來還有幾個麻煩事要忙活,DartVM 要抽出來,Dart JIT層的輕量級運行時庫,Dart AOT層把DSL轉成真正Widget的UIEngin也要寫哦,就是圖中黃色和紅色的三部分

抽離DartVM

無法簡單修改編譯條件抽離

Dart源代碼在進行編譯時會通過DART_PRECOMPILED_RUNTIME宏進行條件編譯從而在Debug版編譯JIT模式,Release版編譯AOT模式。並且這兩種模式是互斥的,無法同時存在。

簡單的解決方法是

我們單獨編譯出一個DartVM,打包成動態庫,修改導出符號,避免符合衝突

引入DartVM還需要的工作

  • 開發DartVM與Native互通接口,參考了Flutter,使用Native Extension和Dart_Invoke實現互相調用

  • 雙DartVM調試方案,兩個DartVM獨立運行,通過遠程端口單獨調試DartFlutter

  • 支持引入第三方庫,DartFlutter在打包發佈時會通過shell腳本分析.packages文件將依賴庫自動打包隨Dart File Zip一起隨包下發。常用庫可以預先打包的App本地,減少下發文件大小

一個暫時無法解決的問題

安裝包過大,DartVM增大安裝包30M,如果加上原本的AOT40M,整個Flutter安裝包會增大到70M,用DartVM不現實。怎麼辦呢。

01 最終方案 JavasSriptCore 替換 DartVM

可性能分析

  1. JavasSriptCore 是iOS官方庫,不增加安裝包

  2. Dart代碼和JS代碼非常相近,可以用工具轉換

  3. JavasSriptCore 與 Native有更方便的互調接口

  4. ReactNative 已驗證通過JS開發App能力是可行的

  5. JS的執行效率是DartVM的3倍編碼1M的JSON只需 2毫秒

需要解決的問題

用JS開發假的Flutter Runtime 封裝JavasSriptCore與Native、 Flutter互調接口

02 講解下 MXFlutter 的渲染原理

渲染樹

兩個重要的數據結構

  • MXScriptWidget

  • MXWidgetTree

MXScriptWidget管理一個Script頁面或控件,負責創建管理 ScriptWidgetTree,以自增ID與Flutter對應Widget相互調用 ,每次Build都會創建一個新的MXWidgetTree

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 事件

在 JS 側 buildWidget 時,我們會對 function 事件,生成自增的唯一 callbackID,並與 widgetID 組合拼接成 widgetID/callbackID,作為事件的唯一標識。用戶點擊界面某個 button 時,事件由 Flutter 側傳到 JS 側,通過解析 widgetID/callbackID,找到對應 widget 的 callback,完成事件處理。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 高效的動態列表

通過在 JS 側,ListView 調用 Build 方法時,提前展開 child, 併為 ListView 增加 children 成員變量。此時,因為僅有數據配置,不會有多餘的 Layout 過程,所以速度是非常快的。

preBuild(jsWidget, buildContext) {

if(this.builder) {

for (let i = 0; i < this.childCount; ++i) {

let w = this.builder(buildContext, i);

this.children.push(w);

}

delete this.builder;

}

super.preBuild(jsWidget, buildContext);

}


在 Flutter 側,ListView 仍然是動態創建,滑動列表,MXFlutter Engine 根據 Children 數組裡的配置數據,創建真正的 Flutter WidgetCell,效率與原生相同完全一致。


ListView.builder(

itemCount: children.length,

itemBuilder: (context, index) {

return UIEngine.toWidget(children[index]);

},

)

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

MXFlutter 動畫的方案

動畫參數在VM層配置一次,動畫開始後在Flutter層閉環循環rebuild,形成動畫效果,這個是比較通用的做法了。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

03 渲染優化

不管JSWidget創建有多快,總是有跨語言執行,所以減少Build次數和減小Build出來的DSL UI描述大小,可以優化性能。

渲染優化1-局部刷新:配置樹Diff

一個事實

自動對比兩次Widget 無論如何都沒有直接創建一個新的快,如果開發者不參與,由框架來自動計算Diff是得不償失的

可行的方法

犧牲響應式UI框架的設計模式 採用和Native、Web的方式,由開發者參與自己設置Diff的節點,即根據ID獲取對應Widget,修改Widget參數,Rebuild生成新DSL

渲染優化2-局部刷新-嵌套節點

  • MXScriptWidget 是一個具備Build WidgetTree,緩存Callback映射表,動畫支持的基本單位。可以作為普通FlutterWidget來使用。

  • 在Flutter層,如果Widget樹中節點有MXScriptWidget,則在對應節點上創建MXFlutterWidget自定義控件

  • 兩個子樹可以相互對應獲得局部刷新,callback回調,動畫支持,Rebuild時所生產的UI DSL 很大減少,加快刷新速率

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

渲染優化3-可以分離動態和靜態控件

MXStatelessWidget 可以通過使用無狀態的ScriptWidget來向框架標示,其下面的子樹,在每次build中不會變化,其build結果會被緩存,下次在Flutter層直接複用

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

內存-跨層鏡像對象的生命週期

VM層,Flutter層,Native層鏡像對象的生命週期如何控制?參考 iOS JavaScriptCore 和 Objective-C的解決方法

  1. 以Flutter層的對象生命週期為主

  2. 在VM層增加WeakMap支持,不增加對象引用計數,Flutter層釋放之後,釋放VM層對象

  3. 在Native層使用 JSManagerValue,VM層對象釋放後,Native的引用被自動置空

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

線程問題

參照業界RN等框架的設計,VM層跑在一個單獨的後臺線程

  1. 從Flutter層通過Native通道調用到VM,發生兩次線程切換

  2. Flutter UI層和MXScript層是異步調用,限制動態控件的架構設計

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

一個可行方案 修改FlutterEngine ,定製開發Dart->Native->VM 這個通道,調用到VM不切換線程 VM不新建線程,直接由Flutter UI Thread 消息循環驅動,這樣也同時支持了和Flutter UI 層的高效同步調用,但要注意從Native調用到VM,需要通過定製FlutterEngine的接口。

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

04 讓開發者寫出優雅的代碼

讓開發者寫出優雅的代碼,咳咳,這裡有點吹了,總之,我們想讓使用MXFlutter的開發同學寫出來的代碼看來正規一些,好看一些。

  • 完美支持Dart Flutter語法

  • 定義所有Flutter 中同名Widget類,構建Widget的參數類,支持相同的Build方式,SetState觸發刷新,事件響應函數

  • Callback函數自動生成CallbackID

  • Callback函數自動This綁定

  • ListView 像Dart層一樣開發,支持itemBuilder回調函數

參考JS示例源碼TGIF-iMatrix home_page.js(https://github.com/TGIF-iMatrix/MXFlutter/blob/master/js_flutter_src/app_test/home_page.js

05 MXFlutter 基礎建設

因為 JavaScript 不支持模塊化開發,不能引用其他文件代碼,我們參照 RN,使用 Node.js 的模塊化代碼,在Native 層支持 require 語法。開發時,IDE最好選用 VSCode,因為可以按裝JS插件,直接運行調試JS

另外,我們通過重定向模擬器 JS 路徑文件到開發機,用戶修改完 JS 文件,便可直接看到相應修改,實現模擬器的頁面熱更新。

結語

由於時間緊張,MXFlutter還有很多遺留的問題,作為一個技術探索,非常辛苦但非常有趣,期待各位大牛指導,期待小夥伴們提出問題一起討論解決。

要了解全部,一定要拉下源碼,運行起來看看,有問題可以留言一討論,MXFlutter會持續更新。

項目成員 luca 浪哥,nice,yockie 帥哥貢獻了動畫,控件,示例 APP 等核心實 現, chaodong 老師負責了 DartVM 方案,IP 老師幫忙提供了單元測試,健身大 神 yofer 老師負責了代碼維護,工具建設。

TGIF-iMatrix 是一個技術氛圍濃厚,有美女有帥哥有趣有愛的團隊,還有精通量子計算,5G 等前沿技術的數據分析 victor 老王。

對 MXFlutter 有興趣的小夥伴,可以加 QQ 群交流 QQ 群:747535761

今日問題:

大家需要熱更新的Flutter嗎?你應用裡用到熱更新沒?

真當Flutter不能熱更新?QQ團隊開源動態化Flutter

快來碼仔社群解鎖新姿勢吧!社群升級:Max你的學習效率

"

相關推薦

推薦中...