前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter混编工程之轻量化改造

Flutter混编工程之轻量化改造

作者头像
用户1907613
发布2022-03-31 21:44:47
6650
发布2022-03-31 21:44:47
举报
文章被收录于专栏:Android群英传Android群英传

轻量化改造的意义

轻量级Flutter渲染引擎的核心是将Flutter作为一个「渲染器」,它的唯一功能就是将Native端传来的数据绘制成相应的界面,其它所有交互操作,都通过Channel桥接到Native端进行处理,这样做的好处有下面几点:

  • 复用Native侧的所有网络请求逻辑,避免因为引入第二套网络库,导致多端请求不一致的问题
  • 复用Native的已有本地图片资源,减少重复的资源浪费
  • 降低混合栈逻辑复杂度,利用EngineGroup来管理混合栈渲染,而数据内容桥接于Native侧,从而解决EngineGroup数据隔离的问题

可能有人要说了,为什么要做轻量化改造,直接在Flutter中使用不好吗,就像网络请求,在Flutter中接入DIO等网络库,同样也不复杂。

的确,对于很多项目来说,引入Flutter的意义在于降低开发成本,提高开发效率,但是当你在一个相对比较成熟的原生项目上进行混编的时候,如果Flutter的所有能力都需要重新实现,那么前期代价是相对较高的,就拿网络请求来说,在Flutter内部将请求数据全部包掉后,Flutter需要实现原生网络请求的所有逻辑,例如拦截器,加密,重定向等等功能,同时,如果以后对网络逻辑有所改动,那么原生侧和Flutter都需要进行调整。

所以,Flutter轻量化改造重要原因,就是需要「尽可能多的复用原生已有的逻辑」,例如图片框架、网络、埋点,而不是在Flutter中去全部再实现一遍。

同时,Flutter轻量化改造也是对EngineGroup架构的最佳实践,在EngineGroup架构下,我们需要将数据源放到原生侧,从而保证多Engine的数据共享。

最后,Flutter轻量化改造,也是渐进式接入混编Flutter的最佳方式,这种方式可以以比较小的前期基建成本来快速接入Flutter来提高开发效率,同时在后期大量接入Flutter后替换为完全的Flutter开发,可以非常方便的将接口层替换。

轻量化改造实践

首先,我们通过Pigeon生成接口协议和调用代码,原生侧分别基于当前协议来进行开发。

不过,我们需要解决Pigeon CLI脚本只能有一个协议文件的问题。

根据前面的几篇文章,我们修改下之前的代码,先根目录下创建Pigeon文件夹,将不同的协议,分别写入不同的协议文件,例如:SchemaBookSearchAPI、SchemaUserAPI等等。

然后修改之前的run_pigeon.sh脚本。

代码语言:javascript
复制
#!/bin/sh
cd pigeon

for file in `ls`;do
  filename=${file%.*}
    flutter pub run pigeon --input pigeon/${file%.*}.dart \
      --dart_out lib/${file%.*}_api.dart \
      --java_out ../QDReaderGank.App/src/main/java/com/qidian/QDReader/flutter/${file%.*}Api.java \
      --java_package "com.qidian.QDReader.flutter"
done

脚本其实很简单,就是在Pigeon目录下,循环所有的文件来分别执行原有的CLI脚本。

这样就会生成多个协议的不同调用文件,分别对应不同协议的实现。

在这个方案下,每个业务场景会创建一个XXXFlutterActivity,并在XXXAPI下,由Native侧分别创建不同的协议实现。

但是这个方案有一个致命的缺陷,那就是原本是为了提高效率而引入的Flutter,在这个场景下,依然需要原生侧的人力来进行开发,虽然工作量不大,但是能否将这部分人力也去掉呢?

所以,我们需要对轻量化Flutter框架做进一步改造。

首先,依然是借用Pigeon的那一套东西,生成相应的Channel代码,之所以要使用Pigeon来生成代码的原因,主要还是Pigeon使用了BasicMessageChannel来进行Channel通信,效率相对于几种不同的Channel来说是最高的,其次,生成代码屏蔽了Channel的一些原始调用方法,使得调用更加方便了。

所以,我们现在只保留一套通用协议,该协议中只包含3个方法,Get请求、Post请求和ActionURL调用。

代码语言:javascript
复制
import 'package:pigeon/pigeon.dart';

@HostApi()
abstract class NativeNetApi {
  @async
  String getNativeNetBridge(String path, Map<String, Object> params);

  @async
  String postNativeNetBridge(String path, Map<String, Object> params);

  void doActionUrlCall(String actionUrl);
}

接下来,依然是通过Pigeon生成三方的协议代码,在Android中,我们创建一个通用的FlutterActivity,并实现协议中关于网络请求的方法,借助前面几节的内容,我们可以很方便的实现下面的代码。

代码语言:javascript
复制
class SingleFlutterActivity : FlutterActivity() {

    private val engine: FlutterEngine by lazy {
        val app = activity.applicationContext as QDApplication
        val dartEntrypoint =
            DartExecutor.DartEntrypoint(
                FlutterInjector.instance().flutterLoader().findAppBundlePath(),
                intent.getStringExtra("EntryName").toString()
            )
        app.engines.createAndRunEngine(activity, dartEntrypoint)
    }

    companion object {
        @JvmStatic
        fun start(context: Context, flutterEntryName: String) {
            context.startActivity(Intent(context, SingleFlutterActivity::class.java).also {
                it.putExtra("EntryName", flutterEntryName)
            })
        }
    }

    private class NetBridgeApiImp(val context: Context, val lifecycleScope: LifecycleCoroutineScope) : NetBridgeApi.NativeNetApi {
        override fun getNativeNetBridge(path: String?, params: MutableMap<String, Any>?, result: NetBridgeApi.Result<String>?) {
            path?.let {
                lifecycleScope.launch {
                    try {
                        val data = XXXRetrofitClient.getCommonApi().getNetBridge(path, params)
                        result?.success(data.toString())
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            }
        }

        override fun postNativeNetBridge(path: String?, params: MutableMap<String, Any>?, result: NetBridgeApi.Result<String>?) {
            path?.let {
                lifecycleScope.launch {
                    try {
                        val data = XXXRetrofitClient.getCommonApi().postNetBridge(path, params)
                        result?.success(data.toString())
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            }
        }

        override fun doActionUrlCall(actionUrl: String?) {
            if (context is BaseActivity) {
                context.openInternalUrl(actionUrl)
            }
        }
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        NetBridgeApi.NativeNetApi.setup(flutterEngine.dartExecutor, NetBridgeApiImp(this, lifecycleScope))
    }

    override fun provideFlutterEngine(context: Context): FlutterEngine {
        return engine
    }

    override fun onDestroy() {
        super.onDestroy()
        engine.destroy()
    }
}

这样在Start这个Activity的时候,传入对应Flutter的路由名即可路由到对应的Flutter页面。

代码语言:javascript
复制
SingleFlutterActivity.start(activity, "main");

而在Flutter界面中,可以通过协议非常方便的调用原生方法。

代码语言:javascript
复制
void _loadData() async {
  String result = await NativeNetApi().getNativeNetBridge(
    "/apipath/xxxxx",
    {"itemId": 1111, "pg": 1, "pz": 20},
  );
  setState(() {
    model = BookModel.fromJson(json.decode(result)).data?.items ?? [];
  });
}

这样一来,原生侧只需要搭建好一套类似JSSDK的环境即可满足混编开发的需求,不用再根据不同的接口来进行重复的开发,而Flutter一侧,只需要设置API path和参数即可。

最后,我们需要在原生侧增加通用接口的封装即可,首先,实现通用的Get和Post请求。

代码语言:javascript
复制
@GET("{path}")
suspend fun getNetBridge(
    @Path(value = "path", encoded = true) path: String,
    @QueryMap param: @JvmSuppressWildcards Map<String, Any>?,
): JsonObject

@FormUrlEncoded
@POST("{path}")
suspend fun postNetBridge(
    @Path(value = "path", encoded = true) path: String,
    @FieldMap mapParam: @JvmSuppressWildcards Map<String, Any>?,
): JsonObject

原生侧网络依然使用OKHttp进行封装,这里有一个需要注意的就是在Kotlin中使用Retrofit,如果参数类型是Any的话,需要使用@JvmSuppressWildcards注解来将Any标记为Object类型。

通过上面的操作,我们就打通了整个链路。

❝其它对应需要桥接原生的能力,只需要新增接口即可,例如埋点,新增曝光和点击接口,在Flutter中调用协议即可实现。 ❞

轻量化下的开发流程

在使用Flutter开发新的业务需求时,首先需要在Flutter中创建相应的路由名,然后在main中配置相应的业务页面,接下来即可进行正常的Flutter业务开发,在网络请求等需要桥接原生的地方,利用接口协议进行桥接,在接口还未上线时,可以通过Mock的方式进行调试,或者在Flutter中增加一层Mock配置,这样可以以不参与原生编译的方式单独进行开发,极大的利用了Flutter的开发效率高的特性。

在接口上线后,即可发布aar到原生项目,从而参与调试。

这样就完成了整个改造的闭环,使用轻量级Flutter框架进行业务开发,缩减了一半的原生人力成本,同时也提高了UI的统一程度,方便的视觉走查,另外,对相应的测试成本也有缩减,大部分功能只需要在一个平台上进行测试,其它一些兼容性测试,在分端设备上测试即可。

性能Benchmark

大数据量场景

使用Mock接口数据的方式测试,字符数120000,应该是常规开发中比较大的接口,经测试,可以正常传递数据。

  • 测试方法:Mock Native请求接口数据,替换为新的数据,获取数据后展示到界面上。
  • 测试结果:Channel耗时统计10次,Debug包下,均值在12ms左右,Release包下,均值在7ms左右,满足使用条件。

频繁请求场景

使用普通接口数据,连续请求10次,目前常规开发中的接口请求场景,大部分为1到3次,可以满足几乎目前所有的使用场景。

  • 测试方法:循环10次,连续调用Native API获取接口数据,并在界面展示返回数据。
  • 测试结果:测试通过,数据正常请求并展示。

通过上面两个测试场景,可以得出结论,该方案具有可行性。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-02-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 群英传 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 轻量化改造的意义
  • 轻量化改造实践
  • 轻量化下的开发流程
  • 性能Benchmark
    • 大数据量场景
      • 频繁请求场景
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档