轻量开源Flutter 热更新库 MicroDart使用指南

2,181 阅读5分钟

下载地址

github.com/lancexin/mi…

如何跑通代码?

1.下载代码之前你需要下载dart-sdk,具体下载方法可以查看 github.com/dart-lang/s… 这里建议用depot_tools的方式下载,因为直接下载github上的sdk,third_party目录下面不会有你需要的包 我用dart-sdk 版本是 3.0.5 ,切换后 通过 gclient sync 下载依赖包。

2.修改 micro_dart_compiler/pubspec.yaml 里面的路径指向你下载的 dart sdk

如何生成动态化产物?

如果只是想尝试而不想编译,micro_dart_compiler已经有命令行工具可以尝试执行以下代码:

dart run micro_dart.dart.snapshot --verbose examples/flutter_plugin_2/lib/plugin_2.dart --op-out plugin_2_ops.txt --json-ast-out plugin_2_json.txt --ast-out plugin_2_ast.txt --external-methods-out external-methods-out.txt

在micro_dart_compiler 里的 test 目录下面有相关例子也可以看一下 其中例子里生成动态化的代码:

micro_dart_compiler/test/dart_example_1_generate.dart

micro_dart_compiler/test/flutter_plugin_1_generate.dart

micro_dart_compiler/test/flutter_plugin_2_generate.dart

micro_dart_compiler/test/flutter_plugin_animations_generate.dart

micro_dart_compiler/test/flutter_plugin_gallery_generate.dart

具体生成热更新产物的代码:

//热更新 plugin 的目录
const String flutterPlugin1Path = "../examples/flutter_plugin_2/";
//原程序的目录
const String flutterExamplePath = "../examples/flutter_example_2/";
void main() async {

  //plugin 编译入口
  Uri mainSource =
      ensureFolderPath(flutterPlugin1Path).resolve('lib/plugin_2.dart');
  //调用compilePlugin进行plugin 编译
  var program = await compilePlugin(
      mainSource, [], RegExp(r"package:flutter_plugin_2/+"), options);
  if (astToJsonFlag) {
    //生成 json 格式的ast 上下文,方便调试,实际使用可以不用
    astToJson("${testCasePath}flutter_example",
        RegExp(r"package:flutter_plugin_2/+"), program.component);
    //生成 伪代码 格式的ast 上下文,方便调试,实际使用可以不用     
    writeComponentToText(program.component!,
        path: "${testCasePath}plugin_2.txt");
  }
  //生成热更新代码的字节码
  var bytes = program.write().buffer.asByteData();
  //将字节码保存到文件
  File("${flutterExamplePath}assets/micro_dart2.data")
      .writeAsBytesSync(bytes.buffer.asUint8List());
  //生成最小外部引用清单    
  File("${flutterExamplePath}micro_dart_external_methods.json")
      .writeAsStringSync(program.getExternalCallMethods());
  //测试生成一个 MicroDartEngine解释器环境,主要为了调试
  var engine = MicroDartEngine.fromData(program.write().buffer.asByteData());
  //载入外部桥接引用
  engine.setExternalFunctions(libraryMirrors);
  if (printOp) {
    engine.debug = true;
    //打印出虚拟指令集,主要为了调试
    engine.printOpcodes();
  }
}

例子中代码都是在 main 函数里调用,是否只能全局动态化?

是可以部分动态化的,可以是一个 Widget 也可以是一个逻辑方法。callStaticFunction 就是调用动态化代码的入口方法。并不一定需要在 main 函数里调用。 比如你只想某个 widget 执行热更新,你可以调用callStaticFunction返回你想要的 widget,这个 widget 可以放在某个 widget 节点下面。比如 :

class BankListInfoPage extends StatelessWidget {
    const BankListInfoPage({super.key});
    @OverRide
    Widget build(BuildContext context) {
        return Scaffold(
        appBar: AppBar(
        title: const Text("我的银行卡"),
        ),
        body: engine.callStaticFunction(packegeUri, "getMyBankWidget", [], {});
    }
}

如何自动生成桥接代码

micro_dart_generator 就是代码生成器,micro_dart_generator 是一个 build_runner库,在原程序中集成它:

dev_dependencies:
  micro_dart_generator: 
    path: ../../micro_dart_generator

然后在命令行中 dart run build_runner 会自动生成桥接代码。

如果你不想用 build_runner,或只是想生成dart core 或者 flutter sdk 的桥接代码,micro_dart_generator/bin 下面有一些代码可以借鉴。

如果你不想每次都修改,可以对overwrite_strategy.json 进行修改。这个文件可对一些错误的生成代码进行覆盖。或者你希望忽略一些全局 package 等都可以在这个文件里配置

如何生成最小桥接代码?

目前micro_dart_generator可以通过配置进行生成,配置的格式可以参考:

examples/flutter_example_gallery_2/micro_dart_external_methods.json

examples/flutter_animations_2/micro_dart_external_methods.json

examples/flutter_example_2/micro_dart_external_methods.json

这个文件micro_dart_compiler可以在编译动态化代码的时候自己生成一个最小量的,这表示当前 plugin 跑起来调用的所有外部桥接汇总。具体生成方法可以参考:

micro_dart_compiler/test/flutter_plugin_2_generate.dart:

File("${flutterExamplePath}micro_dart_external_methods.json")
    .writeAsStringSync(program.getExternalCallMethods());

动态化代码和原程序的关系

原程序与动态化代码是分开的。动态化代码是 plugin 的形式,它依赖于原程序。

比如examples里面的flutter_gallery 的例子:

flutter_plugin_gallery 是我们需要动态化的代码库

flutter_example_gallery 是原程序,是非动态化的代码库

flutter_plugin_gallery的 pubspec.yaml里需要配置:

dependencies:
  flutter:
    sdk: flutter
  flutter_example_gallery:
    path: ../flutter_example_gallery

且这样不能再有任何其他的第三方插件,如果需要则在原程序里引入,并生成对应的桥接代码

flutter_example_gallery不需要依赖flutter_plugin_gallery,但是需要引入micro_dart_runtime:

dependencies:
  flutter:
    sdk: flutter
  micro_dart_flutter: 
    path: ../../micro_dart_flutter
  micro_dart_runtime: 
    path: ../../micro_dart_runtime

这里的micro_dart_flutter是桥接代码,如果你也是 flutter 3.10.5 可以直接用。但如果是其他的flutter版本则需要自己通过micro_dart_generator生成自己的桥接代码,不同 flutter版本sdk 都是有细微差别的。 我也不建议引入全局的 flutter桥接,因为还是比较消耗内存的,而且编译会很慢。

动态化代码为何要于原程序分开?

设计上,动态化的代码是以 package 区分的,编译器可以很容易的根据 packageName 去判断是否需要生成动态化代码。而且非 packageName 的调用都可以视为是外部调用,这做方便而简单。

这么做的另外一个好处是,如果以后不需要动态化了,可以在原程序直接引入这个 plugin,不需要进行任何代码形式修改。

能否引入多个动态化的 plugin?

可以的,一个动态化 plugin 对应一个packageName,对应一个MicroDartEngine对象,就是一个解释器环境。源程序中允许有多个解释器MicroDartEngine对象,但是他们的上下文,全局变量等都是不共享的。

一个动态化 plugin 对应一个页面吗?

不一定,可以将多个页面都放在一个动态化plugin里面,这取决你自己。但是MicroDartEngine的初始化是消耗资源的,这取决于动态化代码的大小,会一次性导入内存。 多个MicroDartEngine的场景我理解是对内存比较敏感的时候,MicroDartEngine可以用到时初始化,用完就销毁。

没有动态化包下载模块或者类似于 MicroDartWidget的flutter封装组件吗?

目前 micro_dart 定位只是动态化代码解释器,就像一个房子需要地基,micro_dart 并不会做的面面俱到。起的是地基作用。