Flutter实现动态化更新-技术预研

  1. webview增强 【植入腾讯 X5内核,模型升级改造】

  2. UI库模板化

各大厂动态化架构对比


Flutter Fair


Fair是“58同城”为Flutter设计的动态化框架,通过Fair Compiler工具实现JSON配置和原生Dart源文件的自动转化,从而动态更新Widget Tree和State。

使用介绍

官方并没有维护pub上的Fair插件,我们需要去GitHub fork源码下来编写demo。58Fair

准备一份配置好的JSON文件,然后直接调用FairWidget传入文件路径即可显示,非常简单。动态化需求无非就是把JSON配置文件放到线上,然后FairWidget每次都会重新拉取下来展示,从而实现动态化。

/// 基本使用

return Container(

alignment: Alignment.centerLeft,

color: Colors.white,

constraints: BoxConstraints(minHeight: 80),

child: FairWidget(

name: item.id,

path: ‘assets/bundle/sample.json’,

data: {“fairProps”: json.encode({‘detail’: details})},

),

);

继续跟进源码,可以看到当我们传入的文件路径是以http开头的时候,会通过网络进行拉取

void _reload() {

var name = state2key;

var path = widget.path ?? _fairApp.pathOfBundle(widget.name);

bundleType = widget.path != null && widget.path.startsWith(‘http’)

? ‘Http’
‘Asset’;

parse(context, page: name, url: path, data: widget.data).then((value) {

if (mounted && value != null) {

setState(() => _child = value);

}

});

}

/// 再通过parse()方法逐层进入decoder → bundle_provider,查看onLoad方法

@override

Future onLoad(String path, FairDecoder decoder,

{bool cache = true, Map<String, String> h}) {

bool isFlexBuffer;

if (path.endsWith(FLEX)) {

isFlexBuffer = true;

} else if (path.endsWith(JSON)) {

isFlexBuffer = false;

} else {

throw ArgumentError(

‘unknown format, please use either $JSON or $FLEX;\n $path’);

}

if (path.startsWith(‘http’)) {

return _http(path, isFlexBuffer, headers: h, decode: decoder);

}

return _asset(path, isFlexBuffer, cache: cache, decode: decoder);

}

/// 重点查看以http开头的解析方法,用的是http库拉取

Future _http(String url, bool isFlexBuffer,

{Map<String, String> headers, FairDecoder decode}) async {

var start = DateTime.now().millisecondsSinceEpoch;

var response = await client.get(url, headers: headers);

var end = DateTime.now().millisecondsSinceEpoch;

if (response.statusCode != 200) {

throw FlutterError(‘code=${response.statusCode}, unable to load : $url’);

}

var data = response.bodyBytes;

if (data == null) {

throw FlutterError(‘bodyBytes=null, unable to load : $url’);

}

Map map;

map = await decode.decode(data, isFlexBuffer);

var end2 = DateTime.now().millisecondsSinceEpoch;

log(‘[Fair] load $url, time: ${end - start} ms, json parsing time: ${end2 - end} ms’);

return map;

}

看下依赖,其实都是非常旧的了,很明显维护力度不够;同时对Flutter版本也有限制,Flutter每出一个版本,58Fair官方就很可能需要做一次适配。

fair_annotation:

path: …/annotation

fair_version:

path: …/flutter_version/flutter_2_5_0

flat_buffers: ^1.12.0

url_launcher: ^5.7.2

http: ^0.12.2

最后怎么写JSON配置文件,肯定自带一套协议,跟着官方文档Api写就可以了。熟悉Flutter的同学看下面的示例代码应该能秒懂。

{

“className”: “Center”,

“na”: {

“child”: {

“className”: “Container”,

“na”: {

“child”: {

“className”: “Text”,

“pa”: [

“嵌套动态组件”

],

“na”: {

“style”: {

“className”: “TextStyle”,

“na”: {

“fontSize”: 30,

“color”: “#(Colors.yellow)”

}

}

}

},

“alignment”: “#(Alignment.center)”,

“margin”: {

“className”: “EdgeInsets.only”,

“na”: {

“top”: 30,

“bottom”: 30

}

},

“color”: “#(Colors.redAccent)”,

“width”: 300,

“height”: 300

}

}

}

}

利弊分析

  1. Fair的好处:用起来很简单,性能稳定;

  2. 缺点很明显:

  • 用JSON来配置UI,就注定了它是不支持逻辑的

  • Flutter的widget太多,Fair目前也只能匹配有限的静态UI

  • 脱离Dart生态,UI都用JSON写了…;

  • 团队维护力度非常有限,很多插件都没有更新,pub也没有更新。【但其实这是**所有Flutter动态化开源框架的通病** 😭】

MxFlutter


MxFlutter 同样也是维护力度有限,目前pub并不是最新版本。GitHub地址也换了,最新的请看github.com/Tencent/mxf…

MxFlutter通过JavaScript编写Dart,同样是通过加载线上js文件,通过引擎在运行时转化并显示,从而达到动态化效果。 官方在0.7.0版本开始接入TypeScript,引入npm生态,优化了js开发的成本,向前端生态进一步靠拢。

很遗憾,在对比各大厂的方案时,发现MxFlutter的性价比极低,学习成本也高,而且抛弃了js生态,又抛弃Dart生态。

所以针对MxFlutter我只对实现原理做简单剖析,不进行深入研究。

使用介绍

  • 初始化引擎

String jsBundlePath = await _copyBizBundelZipToMXPath();

if (jsBundlePath != null) {

// 启动 MXFlutter,加载JS库。

MXJSFlutter.runJSApp(jsAppPath: jsBundlePath);

}

  • 通过MXJSPageWidget传入js脚本,就能解析出来显示了。一般使用MxFlutter都是展示一整个使用MXFlutter框架编写的页面

Navigator.push(

context,

MaterialPageRoute(

builder: (context) => MXJSPageWidget(

jsWidgetName: “mxflutter-ts-demo”,

flutterPushParams: {

“widgetName”: “WidgetExamplesPage”

}),

),

);

  • 再来看看MxJsFlutter的接口定义,可以看到定义了很多协议,这就势必增加js的学习成本,同时对mxFlutter的引擎依赖度极高。而自己团队是否有能力hold住这里面的坑,是要慎重考虑的。

abstract class MXJSFlutter {

static MXJSFlutter _instance;

static String _localJSAppPath;

static String get localJSAppPath => _localJSAppPath;

/// 获取对外接口类MXJSFlutter。

/// MXFlutter的大部分接口通过MXJSFlutter来调用。

static MXJSFlutter getInstance() {

if (_instance == null) {

_instance = _MXJSFlutter();

}

return _instance;

}

/// 由Flutter 代码启动JSApp。 可以用在先显示Flutter页面,然后Push路由跳转到JS页面。

/// 启动JSApp之后,执行JS代码,JS代码可以主动调用Flutter显示自己的页面,也能接受Flutter的指令,显示对应页面。

///

/// @param jsAppPath jsApp root path ,JS业务代码放置在一个文件夹中。jsAppPath和jsAppAssetsKey根据场景二选一。

/// @param jsAppAssetsKey 使用pubspec.yaml里的AssetsKey配置来设置jsAppPath,默认为flutter工程下,与lib,ios同级目录的mxflutter_js_bundle/文件夹下。

/// @param jsExceptionHandler js异常回调。方法参数见 MXJSExceptionHandler 说明。

/// @param debugBizJSPath 目前iOS模拟器下才能使用!!!本地js目录放置路径,直接放置xxx/bundle-xxx.js文件,无需打包成bizBundle.zip。使用该参数后,jsAppPath不生效。

/// @returns Future

/// @throws Error if Path error

///

static Future runJSApp(

{String jsAppPath = ‘’,

String jsAppAssetsKey = defaultJSBundleAssetsKey,

MXJSExceptionHandler jsExceptionHandler,

String debugJSBundlePath = ‘’}) async {

WidgetsFlutterBinding.ensureInitialized();

MXJSFlutter.getInstance();

if(jsAppPath == null || jsAppPath.isEmpty){

jsAppPath = await defaultJSAppUpdatePath();

}

// 检查是否需要拷贝main.zip包。

MXBundleZipManager bundleManager = MXBundleZipManager(jsAppPath: jsAppPath);

MXBundleZipCheckResult result = await bundleManager.checkNeedCopyMainZip();

if (!result.success) {

MXJSLog.log(

‘MXJSFlutter.runJSApp: checkAppBundleZip failed: ${result.errorMessage}’);

// 引擎初始化的success回调

MXJSFlutter.getInstance().jsEngineInitCompletionHandler(

{‘success’: result.success, ‘errorMessage’: result.errorMessage});

return;

}

// 调试状态下,debugJSBundlePath不为空,则运行此目录下的js文件。

String realJSAppPath = jsAppPath;

if (debugJSBundlePath != null &&

debugJSBundlePath.isNotEmpty &&

await canDefineDebugJSBundlePath()) {

realJSAppPath = debugJSBundlePath;

}

_localJSAppPath = realJSAppPath;

// 加载main.js。

_callNativeRunJSApp(

jsAppPath: realJSAppPath, jsExceptionHandler: jsExceptionHandler);

}

/// 默认的 JSBundle 的热更新目录,用于放置下载的JS Bundle文件

static Future defaultJSAppUpdatePath() async {

// 如果业务没有指定目录,则使用默认目录

return await Utils.findLocalPath() +

Platform.pathSeparator +

mxJSAPPDefaultAssetsKey;

}

/// 是否允许定义debugJSBundlePath

static Future canDefineDebugJSBundlePath() async {

// 目前只支持场景:1)调试环境的iOS模拟器

if (kDebugMode && Platform.isIOS) {

DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();

IosDeviceInfo deviceData = await deviceInfoPlugin.iosInfo;

return !deviceData.isPhysicalDevice;

} else {

return false;

}

}

static _callNativeRunJSApp(

{String jsAppPath = “”, MXJSExceptionHandler jsExceptionHandler}) {

Map<String, dynamic> args = {“jsAppPath”: jsAppPath};

// 设置JS Exception Handler。

MXPlatformChannel.getInstance().setJSExceptionHandler((arguments) {

// 如果是main.js的错误,arguments[‘jsFileType’] 为 0 则执行js引擎的success回调。

if (arguments is Map && arguments[‘jsFileType’] == 0) {

MXJSFlutter.getInstance().jsEngineInitCompletionHandler(

{‘success’: false, ‘errorMessage’: arguments[‘errorMsg’]});

}

// 回调给业务侧。

if (jsExceptionHandler != null) {

jsExceptionHandler(arguments);

}

});

// 初始化MXFFICallbackManager。

MXFFICallbackManager.getInstance();

args[“flutterAppEnvironmentInfo”] = flutterAppEnvironmentInfo;

MXPlatformChannel.getInstance().invokeMethod(“callNativeRunJSApp”, args);

}

///从Flutter Push一个 JS写的页面

///@param widgetName: “widgetName”,在main.js MyApp::createJSWidgetWithName 函数中使用

///MyApp::createJSWidgetWithName 通过 widgetName 来创建对应的JSWidget

/// 通常你应该使用更高层的API MXJSPageWidget 包装类来显示JS Widget 请参考 MXJSPageWidget 的用法

dynamic navigatorPushWithName(

String widgetName, Key widgetKey, Map flutterPushParams,

{String bizPath});

/// 设置处理器,当JS页面加载时,定制Loading widget。

void setJSWidgetLoadingHandler(MXWidgetBuildHandler handler);

/// 设置处理器,当JS页面加载错误时,定制Error widget。

void setJSWidgetBuildErrorHandler(MXWidgetBuildHandler handler);

/// JS引擎初始化结束回调。

void jsEngineInitCompletionHandler(dynamic arguments);

资源分享

一线互联网面试专题

379页的Android进阶知识大全

379页的Android进阶知识大全

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2020年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

dler);

/// JS引擎初始化结束回调。

void jsEngineInitCompletionHandler(dynamic arguments);

资源分享

[外链图片转存中…(img-re7IeqYM-1714490512778)]

[外链图片转存中…(img-VLiegeLV-1714490512779)]

[外链图片转存中…(img-20tF5oNI-1714490512779)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

2020年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值