开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
Flutter 多引擎系列: 《Flutter 多引擎渲染,在稿定 App 的实践》 等等,专栏里可查看
背景
虽然 FGUIComponentAPI 方案中采用的内置 messageChannel 的方式来达到 Flutter 多引擎互不干扰的消息通信。
但市面上很多有名的/没名的第三方库都有使用 FlutterPlugin 的方式来与 Native 通信(源生路径、设备信息等等)。
那如果“跨端 UI 组件化”没有支持 FlutterPlugin 的加载,会导致在使用第三方库的时候要变的十分谨慎,这不利于开发效率,也不利于代码复用。
举个栗子:
path_provider 是在 Flutter 中十分常用的获取源生各种文件路径插件,而且会被很多的其他第三方库依赖(吐槽下它不没有兼容 Web)。
那如果 FlutterEngine 不支持 FlutterPlugin,在使用 cached_network_image(相当于 iOS 的 SDImage)第三方插件做图片缓存时,图片就显示不出来,因为拿不到 Native 路径、无法缓存。
(Debug 下,找到了图片显示不了的原因,如下图位置)
FlutterPlugin 加起来很容易吗?
理论上很容易
Flutter 编译后会生成一个 FlutterPluginRegistrant 库,提供 registerWithRegistry: 方法可以很容易的把整个 Flutter 库中的插件都注册到 FlutterEngine 上。
比如 flutter_boost 也是在它代码里这样做的:
以上代码是运行时调用,等价于
[GeneratedPluginRegistrant registerWithRegistry:self.engine];
也是我们自定义的插件能在基于 flutter_boost 的页面上正常使用的根本所在。
But
我们项目里的自实现的插件大哥们是存在内存循环引用问题的,具体是哪一个或哪几个也很难去细究出来。在 flutter_boost 上问题并不明显,毕竟插件由单例常持有且只有一份,这在单引擎渲染上也是合理,后续页面可以更快加载,单个的内存占用体量也在可忍受范围内。
但是在 Flutter 多引擎渲染下,如果使用上面全部加载的方式,FlutterEngine 根本就释放不掉,内存问题就变的十分十分恐怖。
测试一下:
同样的使用全部加载的方式,打开后关闭再打开同一个跨端 UI 组件,结果如下:
每次加载后都不能完全释放,只能释放一点点内存,boom!关闭重开后,内存基本是 double 增长。
为了证明是我们自研发的插件大哥们导致的问题,在测试方式一致的情况下,选择部分的插件加载,如下图示:
结果如下:
明显的每次加载都完全释放掉了,这就说明是我们自身开发的插件是有内存问题的。 O.O
解决方案
解决我们 plugins 中的循环引用问题
虽然可以解决问题,但也无法控制后续 plugin 开发上不再出现内存问题,一旦出现就容易变成灾难性的,无法做到防裂化。
而且有些 plugins 是有使用 ffi 的,这导致更难判断出内存泄漏发生在哪里。
更重要的是可能也不光是我们自己开发的插件,可能有些第三方插件也会有内存循环引用导致的不释放问题。
只注册第三方库的 plugins
那我们换一种思路,不要全部注册进去,在多引擎渲染中只关心自己需要的插件。
采用手动注册的方式,在 component_foundations 中提供插件名注册的地方,好处是后期可以增加我们自己安全的 plugins。
如上图,增加插件配置,即可在所有 UI 组件的地方使用注册过的插件能力。
(当然大家别被误导了,这个 registrar_plugins 是自定义的)
这在 iOS 上会根据 FGUIComponentAPI 自动生成到代码里。
- (void)registrarPlugins {
[NSClassFromString(@"FLTPathProviderPlugin") registerWithRegistrar:[self.engine registrarForPlugin:@"FLTPathProviderPlugin"]];
[NSClassFromString(@"SqflitePlugin") registerWithRegistrar:[self.engine registrarForPlugin:@"SqflitePlugin"]];
}
也放一下 FGUIComponentAPI 这一部分的逻辑
Ruby:
def OCGenerator.register_plugin
yaml_path = File.join($project_path, "components", "component_foundations", "pubspec.yaml")
map = YAML.load(File.open(yaml_path))
array = map["registrar_plugins"]["iOS"]["plugins"]
list = []
array.each do |name|
list << " [NSClassFromString(@\"#{name}\") registerWithRegistrar:[self.engine registrarForPlugin:@\"#{name}\"]];"
end
ouput_path = File.join($ios_root_path, "FGUIComponentAPI", "FGUIComponentAPI", "Classes", "common", "FGUIComponent.m")
content = File.read(ouput_path)
content = content.gsub("${PLUGINS}", list.join("\n"))
file = File.new(ouput_path, "w")
if file
file.syswrite(content)
end
file.close
end
随便说一句,可以看到 FGUIComponentAPI 实际上跟公司项目绑定很深
后续
Android 没有遇到类似的问题,这一部分的实现是怎么样的也没有具体了解,有了解的同学可以分享一下 ~
如果对你开发学习上有丝丝作用,请点个赞[开心] ~