Flutter動態(tài)化探索之路:從AOT到解釋執(zhí)行的思考與實(shí)踐

前言

Flutter 在 Release 模式下使用 AOT 編譯,性能高,但動態(tài)化能力不足。通過 Dart 解釋器可在運(yùn)行時執(zhí)行 Dart 代碼,實(shí)現(xiàn)動態(tài)化。本文介紹基于 d4rt 和 flutter_d4rt 的動態(tài)化方案。

一、動態(tài)化合規(guī)性:可以放心使用嗎?

1.1 應(yīng)用商店對動態(tài)化的態(tài)度

許多開發(fā)者擔(dān)心動態(tài)化會被應(yīng)用商店拒絕。實(shí)際上,主流商店允許解釋器方案,但禁止下發(fā)可執(zhí)行代碼。

Google Play Store 的要求:

  • 允許:使用解釋器或虛擬機(jī)執(zhí)行動態(tài)代碼(如 JavaScript、Dart 解釋器)
  • 禁止:下發(fā)可執(zhí)行代碼(如 DEX、JAR、.so 文件)

Apple App Store 的要求(3.3.1b):

  • 允許:解釋型代碼可以被下載,前提是:
    • 不會改變應(yīng)用程序的主要用途
    • 不會創(chuàng)建應(yīng)用商店或代碼商店
    • 不會繞過系統(tǒng)的簽名、沙箱或其他安全機(jī)制

1.2 Shorebird 的啟示

Shorebird 由 Flutter 聯(lián)合創(chuàng)始人 Eric Seidel 創(chuàng)立,是 Flutter 動態(tài)化的主流服務(wù)商,并得到官方推薦。其合規(guī)說明適用于同類方案:

  • 使用 Dart 虛擬機(jī)執(zhí)行動態(tài)代碼,符合商店規(guī)范
  • 禁止濫用,不能具有欺騙性
  • 這是成熟的行業(yè)實(shí)踐(如 React Native 的 CodePush)

結(jié)論: d4rt、dart_eval 等基于解釋器的方案在合規(guī)性上沒有問題,可以放心使用。

二、d4rt:Dart 解釋器基礎(chǔ)

2.1 什么是 d4rt?

d4rt(發(fā)音為 "dart")是用 Dart 編寫的 Dart 解釋器和運(yùn)行時,支持:

  • 動態(tài)執(zhí)行 Dart 代碼
  • 橋接宿主類和方法
  • 泛型支持
  • Async/await
  • 類、枚舉、擴(kuò)展
  • 模式匹配
  • 安全沙箱

核心特點(diǎn):

  • 基于 AST 解釋執(zhí)行,首次執(zhí)行較快
  • 支持完整的 Dart 語法特性
  • 提供橋接系統(tǒng),可調(diào)用宿主代碼

2.2 基本使用

最簡單的例子:

import 'package:d4rt/d4rt.dart';

void main() {
  // 需要動態(tài)執(zhí)行的代碼
  final code = '''
int fib(int n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
}

main() {
  return fib(6);
}
''';

  // 創(chuàng)建解釋器實(shí)例
  final interpreter = D4rt();
  
  // 執(zhí)行代碼并返回結(jié)果
  final result = interpreter.execute(source: code);
  
  print('Result: $result'); // Result: 8
}

原理說明:

  • D4rt() 創(chuàng)建解釋器實(shí)例,內(nèi)部維護(hù)執(zhí)行環(huán)境
  • execute() 將字符串代碼解析為 AST,然后解釋執(zhí)行
  • 執(zhí)行結(jié)果直接返回,可以是任意類型(int、String、List 等)
  • 整個過程在運(yùn)行時完成,無需編譯

實(shí)際應(yīng)用場景:

  • 計(jì)算邏輯動態(tài)下發(fā)(如優(yōu)惠券規(guī)則、活動規(guī)則)
  • 配置腳本執(zhí)行(如 A/B 測試配置)
  • 插件系統(tǒng)(動態(tài)加載業(yè)務(wù)模塊)

2.3 更多示例

遞歸計(jì)算斐波那契數(shù)列:

final code = '''
int fib(int n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
}

main() {
  var result = [];
  for (int i = 0; i <= 10; i++) {
    result.add(fib(i));
  }
  return result.toString();
}
''';

final interpreter = D4rt();
final result = interpreter.execute(source: code);
// 輸出: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

計(jì)算階乘:

final code = '''
int factorial(int n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}

main() {
  return '5! = \${factorial(5)}';
}
''';

計(jì)算累加和:

final code = '''
main() {
  int sum = 0;
  for (int i = 1; i <= 100; i++) {
    sum += i;
  }
  return 'Sum from 1 to 100 = \$sum';
}
''';

三、d4rt 橋接:連接宿主與動態(tài)代碼

3.1 橋接的概念

默認(rèn)情況下,d4rt 只能執(zhí)行標(biāo)準(zhǔn)庫代碼。通過橋接,可以將宿主代碼(參與編譯的代碼)導(dǎo)入解釋器,讓動態(tài)代碼調(diào)用宿主能力。

核心概念:

  • 宿主代碼:參與編譯的代碼,一經(jīng)編譯無法更改
  • 橋接:將宿主代碼的能力手動導(dǎo)入解釋器
  • 橋接范圍:只有被橋接的類和方法才能在動態(tài)代碼中使用

為什么需要橋接?

  • 動態(tài)代碼需要調(diào)用業(yè)務(wù)邏輯(如網(wǎng)絡(luò)請求、數(shù)據(jù)庫操作)
  • 需要訪問宿主的狀態(tài)和服務(wù)(如用戶信息、配置)
  • 需要調(diào)用第三方庫的能力(如 WebView、地圖)

3.2 橋接枚舉

定義宿主枚舉:

enum Gender { male, female }

創(chuàng)建橋接定義:

final gendarEnumBridge = BridgedEnumDefinition<Gender>(
  name: 'Gender',
  values: Gender.values,
);

注冊到解釋器:

final interpreter = D4rt();
interpreter.registerBridgedEnum(
  gendarEnumBridge,
  'package:example/d4rx_bridge.dart',  // 包名,用于動態(tài)代碼中的 import
);

在動態(tài)代碼中使用:

final code = '''
import 'package:example/d4rx_bridge.dart';

main() {
  return Gender.male.name;  // 可以正常使用橋接的枚舉
}
''';

3.3 橋接類

定義宿主類:

class People {
  People(this.name, this.gender);
  
  final String name;
  final Gender gender;
  
  String sayHello() {
    String ret = 'Hello, my name is $name, I am a ${gender.name}.';
    print(ret);
    return ret;
  }
}

創(chuàng)建橋接定義:

final peopleClassBridge = BridgedClass(
  // 類名及宿主類型
  nativeType: People,
  name: 'People',
  
  // 構(gòu)造函數(shù)
  constructors: {
    '': (
      InterpreterVisitor visitor,
      List<Object?> positionalArgs,
      Map<String, Object?> namedArgs,
    ) {
      if (positionalArgs.length != 2) {
        throw ArgumentError('Expected 2 positional arguments');
      }
      final name = positionalArgs[0] as String;
      final gender = positionalArgs[1] as Gender;
      return People(name, gender);
    },
  },
  
  // 屬性訪問(getter)
  getters: {
    'name': (InterpreterVisitor? visitor, Object target) {
      if (target is! People) throw TypeError();
      return target.name;
    },
    'gender': (InterpreterVisitor? visitor, Object target) {
      if (target is! People) throw TypeError();
      return target.gender;
    },
  },
  
  // 實(shí)例方法調(diào)用
  methods: {
    'sayHello': (
      InterpreterVisitor? visitor,
      Object target,
      List<Object?> positionalArgs,
      Map<String, Object?> namedArgs,
    ) {
      if (target is! People) throw TypeError();
      return target.sayHello();
    },
  },
);

注冊并使用:

final interpreter = D4rt();
interpreter.registerBridgedClass(
  peopleClassBridge,
  'package:example/d4rx_bridge.dart',
);

final code = '''
import 'package:example/d4rx_bridge.dart';

main() {
  var p = People("Maeiee", Gender.male);
  return p.sayHello();
}
''';

final result = interpreter.execute(source: code);

原理說明:

  • BridgedClass 定義了如何將宿主類映射到解釋器
  • constructors 定義構(gòu)造函數(shù)如何創(chuàng)建實(shí)例
  • getters 定義如何訪問屬性
  • methods 定義如何調(diào)用方法
  • 這些都是模板代碼,按照固定模式編寫即可

四、flutter_d4rt:Flutter 動態(tài)化

4.1 什么是 flutter_d4rt?

flutter_d4rt 是 d4rt 的 Flutter 擴(kuò)展,完成對 Flutter 框架的橋接,支持動態(tài)執(zhí)行 Flutter Widget 代碼。

核心能力:

  • 動態(tài)執(zhí)行 Flutter Widget 代碼
  • 支持 StatefulWidget 和狀態(tài)管理
  • 支持動畫、異步操作
  • 支持橋接自定義組件

4.2 基本使用

最簡單的例子:

import 'package:flutter_d4rt/flutter_d4rt.dart';

class SimpleExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 動態(tài)化的 Flutter 代碼
    const dynamicCode = '''
import 'package:flutter/material.dart';

Widget createButton() {
  return ElevatedButton(
    onPressed: () => print('Dynamic button pressed!'),
    child: Text('Dynamic Button'),
  );
}
''';

    // 使用 InterpretedWidget 渲染動態(tài)組件
    return InterpretedWidget(
      code: dynamicCode,
      entryPoint: 'createButton',  // 指定入口函數(shù)或類
    );
  }
}

原理說明:

  • InterpretedWidget 接收字符串形式的 Flutter 代碼
  • 內(nèi)部使用 d4rt 解釋器解析和執(zhí)行代碼
  • 執(zhí)行結(jié)果是一個 Widget,直接渲染到界面
  • entryPoint 可以是函數(shù)名或類名

4.3 完整示例:Counter

動態(tài)代碼:

const dynamicCode = '''
import 'package:flutter/material.dart';

class CounterApp extends StatefulWidget {
  @override
  _CounterAppState createState() => _CounterAppState();
}

class _CounterAppState extends State<CounterApp> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Dynamic Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            Text(
              '\$_counter',
              style: Theme.of(context).textTheme.headlineLarge,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
''';

使用:

InterpretedWidget(
  code: dynamicCode,
  entryPoint: 'CounterApp',  // 直接指定類名
)

關(guān)鍵點(diǎn):

  • 支持完整的 Flutter 語法,包括 StatefulWidget、setState
  • 狀態(tài)管理在解釋器中正常工作
  • 字符串插值需要使用轉(zhuǎn)義符 \$ 而不是 $

4.4 更多場景示例

長列表:

const dynamicCode = '''
import 'package:flutter/material.dart';

Widget buildList() {
  return ListView.builder(
    itemCount: 100,
    itemBuilder: (context, index) {
      return ListTile(
        title: Text('Item \$index'),
        leading: Icon(Icons.star),
      );
    },
  );
}
''';

表單:

const dynamicCode = '''
import 'package:flutter/material.dart';

class FormExample extends StatefulWidget {
  @override
  _FormExampleState createState() => _FormExampleState();
}

class _FormExampleState extends State<FormExample> {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _nameController,
            decoration: InputDecoration(labelText: 'Name'),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter your name';
              }
              return null;
            },
          ),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState!.validate()) {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('Hello, \${_nameController.text}!')),
                );
              }
            },
            child: Text('Submit'),
          ),
        ],
      ),
    );
  }
}
''';

動畫:

const dynamicCode = '''
import 'package:flutter/material.dart';

class AnimationExample extends StatefulWidget {
  @override
  _AnimationExampleState createState() => _AnimationExampleState();
}

class _AnimationExampleState extends State<AnimationExample> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      duration: Duration(seconds: 1),
      width: _expanded ? 200 : 100,
      height: _expanded ? 200 : 100,
      color: Colors.blue,
      child: ElevatedButton(
        onPressed: () {
          setState(() {
            _expanded = !_expanded;
          });
        },
        child: Text(_expanded ? 'Collapse' : 'Expand'),
      ),
    );
  }
}
''';

異步操作:

const dynamicCode = '''
import 'package:flutter/material.dart';
import 'dart:async';

class AsyncExample extends StatefulWidget {
  @override
  _AsyncExampleState createState() => _AsyncExampleState();
}

class _AsyncExampleState extends State<AsyncExample> {
  String _status = 'Ready';

  Future<void> _simulateNetworkRequest() async {
    setState(() {
      _status = 'Loading...';
    });
    
    await Future.delayed(Duration(seconds: 2));
    
    setState(() {
      _status = 'Loaded!';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Status: \$_status'),
        ElevatedButton(
          onPressed: _simulateNetworkRequest,
          child: Text('Load Data'),
        ),
      ],
    );
  }
}
''';

五、flutter_d4rt 橋接自定義組件

5.1 為什么需要橋接組件?

雖然 flutter_d4rt 已經(jīng)橋接了 Material 組件庫,但在實(shí)際場景中,我們經(jīng)常需要:

  • 使用第三方組件(如 WebView、地圖)
  • 使用業(yè)務(wù)自定義組件
  • 調(diào)用宿主的能力和服務(wù)

通過橋接,可以將這些組件預(yù)埋到解釋器,在動態(tài)代碼中直接使用。

5.2 橋接簡單組件

定義宿主組件:

class ASimpleWidget extends StatelessWidget {
  const ASimpleWidget({super.key, this.wordsToShow = ''});
  
  final String wordsToShow;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)],
        ),
        borderRadius: BorderRadius.circular(16),
      ),
      child: Text(
        wordsToShow.isEmpty ? 'D4RT 動態(tài)化' : wordsToShow,
        style: TextStyle(
          fontSize: 24,
          fontWeight: FontWeight.bold,
          color: Colors.white,
        ),
      ),
    );
  }
}

創(chuàng)建橋接定義:

final aSimpleWidgetBridge = BridgedClass(
  nativeType: ASimpleWidget,
  name: 'ASimpleWidget',
  constructors: {
    '': (
      InterpreterVisitor visitor,
      List<Object?> positionalArgs,
      Map<String, Object?> namedArgs,
    ) {
      final wordsToShow = namedArgs['wordsToShow'] as String? ?? '';
      return ASimpleWidget(wordsToShow: wordsToShow);
    },
  },
);

注冊并使用:

class FlutterD4rtCustomPage extends StatefulWidget {
  @override
  State<FlutterD4rtCustomPage> createState() => _FlutterD4rtCustomPageState();
}

class _FlutterD4rtCustomPageState extends State<FlutterD4rtCustomPage> {
  final FlutterRunInterpreter _interpreter = FlutterRunInterpreter();
  Widget? _interpretedWidget;

  @override
  void initState() {
    super.initState();
    _initializeAndExecute();
  }

  Future<void> _initializeAndExecute() async {
    try {
      _interpreter.initialize();
      
      // 注冊橋接組件
      _interpreter.registerBridgedClass(
        aSimpleWidgetBridge,
        'package:example/flutter_d4rx_bridge.dart',
      );

      // 動態(tài)代碼
      const code = '''
import 'package:flutter/material.dart';
import 'package:example/flutter_d4rx_bridge.dart';

Widget build(BuildContext context) {
  return Column(
    children: [
      ASimpleWidget(wordsToShow: 'Hello from D4RT!'),
      SizedBox(height: 24),
      ASimpleWidget(wordsToShow: 'This is a bridged widget.'),
    ],
  );
}
''';

      // 執(zhí)行代碼,返回 Widget
      final result = await _interpreter.execute(code, 'build', args: [context]);
      
      setState(() {
        _interpretedWidget = result as Widget;
      });
    } catch (e) {
      print('Error: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Flutter D4RT Custom Widget')),
      body: _interpretedWidget ?? Center(child: CircularProgressIndicator()),
    );
  }
}

5.3 橋接復(fù)雜組件:WebView

橋接 WebViewController:

final webviewControllerBridge = BridgedClass(
  nativeType: WebViewController,
  name: 'WebViewController',
  constructors: {
    '': (
      InterpreterVisitor visitor,
      List<Object?> positionalArgs,
      Map<String, Object?> namedArgs,
    ) {
      final controller = WebViewController();
      controller.setJavaScriptMode(JavaScriptMode.unrestricted);
      return controller;
    },
  },
  methods: {
    'loadRequest': (
      InterpreterVisitor visitor,
      Object target,
      List<Object?> positionalArgs,
      Map<String, Object?> namedArgs,
    ) async {
      if (target is! WebViewController) throw TypeError();
      final uri = positionalArgs[0] as Uri;
      await target.loadRequest(uri);
      return null;
    },
  },
);

橋接 WebViewWidget:

final webViewWidgetBridge = BridgedClass(
  nativeType: WebViewWidget,
  name: 'WebViewWidget',
  constructors: {
    '': (
      InterpreterVisitor visitor,
      List<Object?> positionalArgs,
      Map<String, Object?> namedArgs,
    ) {
      WebViewController? controller = namedArgs['controller'] as WebViewController?;
      if (controller == null) {
        throw ArgumentError('controller is required');
      }
      return WebViewWidget(controller: controller);
    },
  },
);

在動態(tài)代碼中使用:

const code = '''
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class DynamicWebViewPage extends StatefulWidget {
  @override
  _DynamicWebViewPageState createState() => _DynamicWebViewPageState();
}

class _DynamicWebViewPageState extends State<DynamicWebViewPage> {
  late WebViewController _controller;

  @override
  void initState() {
    super.initState();
    _controller = WebViewController();
    _controller.loadRequest(Uri.parse('https://baidu.com'));
  }

  @override
  Widget build(BuildContext context) {
    return WebViewWidget(controller: _controller);
  }
}
''';

5.4 橋接的意義

為什么要預(yù)埋組件?

  1. 性能優(yōu)化:解釋器執(zhí)行效率低于 AOT。將性能敏感部分(如動畫、復(fù)雜計(jì)算)放在宿主,動態(tài)代碼只負(fù)責(zé)聲明,可減少性能損失。
  2. 橋接第三方庫:讓動態(tài)代碼使用第三方庫能力,同時保持 Bundle 體積可控。
  3. 宿主內(nèi)外打通:訪問宿主狀態(tài)和服務(wù)(如全局配置、用戶信息),實(shí)現(xiàn)業(yè)務(wù)邏輯。
  4. 營銷場景:Banner、活動彈窗等頻繁變更的場景,適合動態(tài)化。
  5. 業(yè)務(wù)頁面:頻繁改動的業(yè)務(wù)頁面,可通過動態(tài)化快速上線。

六、性能與最佳實(shí)踐

6.1 性能特點(diǎn)

解釋器執(zhí)行:

  • 首次執(zhí)行需要解析代碼,有一定開銷
  • 狀態(tài)更新在解釋器中執(zhí)行,性能略低于原生
  • 對于大多數(shù) UI 場景,性能表現(xiàn)良好

優(yōu)化策略:

  • 將熱點(diǎn)代碼(如動畫、復(fù)雜計(jì)算)橋接到宿主執(zhí)行
  • 使用 FlutterRunInterpreter 的代碼緩存能力
  • 避免在動態(tài)代碼中執(zhí)行大量計(jì)算

6.2 最佳實(shí)踐

代碼組織:

  • 將橋接定義統(tǒng)一管理,便于維護(hù)
  • 使用有意義的包名區(qū)分不同模塊的橋接類
  • 為橋接類編寫完整的文檔說明

錯誤處理:

  • 動態(tài)代碼執(zhí)行可能出錯,需要完善的錯誤處理
  • 提供友好的錯誤提示,便于調(diào)試

安全考慮:

  • d4rt 提供了沙箱權(quán)限系統(tǒng),可以限制危險(xiǎn)操作
  • 對于文件系統(tǒng)等敏感能力,需要控制權(quán)限

七、總結(jié)

基于 d4rt 和 flutter_d4rt 的動態(tài)化方案,為 Flutter 應(yīng)用提供了熱更新能力。通過解釋器執(zhí)行動態(tài)代碼,符合應(yīng)用商店規(guī)范,可以放心使用。

核心要點(diǎn):

  • 動態(tài)化方案符合應(yīng)用商店規(guī)范
  • d4rt 支持完整的 Dart 語法特性
  • 通過橋接可以調(diào)用宿主代碼和第三方庫
  • flutter_d4rt 實(shí)現(xiàn)了真正的 Flutter 動態(tài)化
  • 合理使用橋接可以優(yōu)化性能

適用場景:

  • 營銷活動頁面
  • 頻繁改動的業(yè)務(wù)頁面
  • 插件系統(tǒng)
  • A/B 測試
  • 配置驅(qū)動的功能

動態(tài)化是一個工具,合理使用可以提升開發(fā)效率和用戶體驗(yàn)。希望本文能幫助你在 Flutter 項(xiàng)目中落地動態(tài)化方案。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容