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

“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);

/// JS引擎是否已初始化。

bool isJSEngineInit();

/// 设置JS引擎已初始化。

void setJSEngineInit();

/// JS引擎初始化结果。

Map<String, dynamic> jsEngineInitResult();

/// 重新创建MXJSFlutter,包括通道,属性。

void resetup();

/// 当前flutterApp。

MXJSFlutterApp get currentApp;

}

Kraken


Kraken是阿里开源的一款基于W3C标准的高性能渲染引擎。也是目前几个大厂框架内维护力度最高的库,详见GitHub。Kraken的优势在于其能够基于W3C进行开发,而且引入npm生态,支持使用Vue、React框架开发,一般前端人员都能进行开发,学习成本很低。

使用介绍

pubspec引入,然后直接使用Widget Kraken传入脚本的url就可以了。

kraken: ^0.9.0

Widget build(BuildContext context) {

// 我们只需要维护js脚本就可以了

Kraken kraken = Kraken(

bundleURL:

‘http://kraken.oss-cn-hangzhou.aliyuncs.com/demo/guide-styles.js’);

return Scaffold(

appBar: PreferredSize(

preferredSize: Size.fromHeight(40),

child: AppBar(

centerTitle: true,

title: new Text(

‘商品详情’,

style: Theme.of(context).textTheme.headline6,

),

),

),

body: kraken,

);

}

可以看到,重点在于我们如何去维护带有动态运营内容的js文件,这是Kraken相对于其他框架最有竞争力的点,官方api写的非常详细,基于W3C标准,能够使用Rax、Vue、React这些主流框架进行开发。

/// Vue代码

demo

Kraken的缺点是不支持css样式,使Vue开发的体验也相对一般。但总体而言已经很不错了,官方维护力度大,满足前端生态,使用方便,是动态化技术很不错的选择。

Webview增强优化


几乎所有的移动应用中,都会用到webview来作为h5的容器。通过运营平台配置生成h5,app直接显示即可。但很遗憾,webview的体验性、稳定性/兼容性有很多的问题。

体验上加载过程白屏,加载中、出错状态没法定义等;兼容性上iOS还好,浏览器内核都是WKWebView,但是Android的设备多种多样,浏览器内核也参差不齐,所以在兼容性上经常存在问题。 为了解决以上问题,我们基于官方插件webview_flutter,做了以下方案:

  • 体验上修改webview插件为可配置透明背景,去除加载条;Flutter层开发webview的增强容器,实现可定义加载中、加载失败的视图,达到基本符合app的加载效果

  • 稳定性上,我们采取统一植入X5内核的方法

为何采取X5内核?

目前开源的浏览器内核sdk不多,主要有以下几个:ChromeView、Crosswalk、TbsX5。

  1. 基于Chromium内核的开源ChromeView 目前基本没有维护,另一个问题是编译出来的动态库太大,ARM-29M,x86-38M,这无疑对app体积来说是个大难题。因此放弃采用基于Chromium的ChromeView。

  2. Crosswalk同样是基于Chromium内核,同样存在上述app体积问题,因此也放弃。

  3. TbsX5 基于谷歌Blink内核,生态在国内是很成熟的,只要装有微信的手机,都支持X5。X5 提供两种集成方案:

  • 只共享微信手Q空间的x5内核(for share)

  • 独立下载x5内核(with download)

优化体验

最终业务层代码:

WebView(

initialUrl: ‘https://www.baidu.com’,

transparentBackground: true

)

  • 构建webview容器。webview背景处理为透明后,通过Stack布局,以及监听onProgress回调,赋予webview容器加载中、加载失败的效果,让用户的体验达到与原生应用类似。

/// 我们用的是flutter_bloc进行状态管理

Stack(

alignment: Alignment.center,

children: [

WebView(

transparentBackground: widget.transparentBackground,

onProgress: (int progress) {

if (progress >= 100) {

context.read().loadSuccess(progress);

}

},

onWebResourceError: (error) {

context.read().loadError();

},

),

if (state is WebViewLoading)

Center(

child: widget.loadingView ?? const LoadingView(),

),

],

)

再看看bloc层,非常简单的状态切换。

/// Cubit

class WebViewContainerCubit extends Cubit {

WebViewContainerCubit() : super(WebViewLoading());

loadSuccess(int progress) {

if (state != WebViewLoadSuccess()) {

emit(WebViewLoadSuccess());

}

}

loadError() {

emit(WebViewLoadError());

}

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
adSuccess());

}

}

loadError() {

emit(WebViewLoadError());

}

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-VrkTFnuz-1715531227737)]

[外链图片转存中…(img-rLjvgB0P-1715531227738)]

[外链图片转存中…(img-LgRmLOgd-1715531227739)]

[外链图片转存中…(img-SOVl9G5t-1715531227741)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值