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