Skip to content

Commit e14fe75

Browse files
committed
add Extension and mixin support,see the demo
1 parent 520db63 commit e14fe75

9 files changed

Lines changed: 466 additions & 206 deletions

File tree

README.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44

55

66
# 懒人做法,一步到位
7-
87
下载frontend_server.dart.snapshot并覆盖 flutter_macos_stable/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot
8+
9+
flutter_macos_stable/bin/cache/dart-sdk/bin/snapshots/frontend_server.dart.snapshot
10+
911

1012
# 编译之前
11-
1. 下载最新的dart-sdk,下载地址:https://github.com/dart-lang/sdk
12-
2. 将dart-sdk切换到 2.14.4: git checkout 2.14.4
13-
3. 修改rebased_package_config.json,rootUri指向dart-sdk
14-
4. 有些third_party目录下的包可能没有,这需要修改成host对应的地址在flutter_macos_stable/.pub-cache/hosted
13+
1. 下载最新的dart-sdk,具体下载方法可以查看https://github.com/dart-lang/sdk/wiki/Building,这里建议用depot_tools的方式下载,因为直接下载github上的sdk,third_party目录下面不会有你需要的包
14+
2. 将dart-sdk切换到flutter对应的版本,一般情况下flutter对应的dart版本是可以通用的,但是如果flutter有大版本升级,需要切换到对应的版本.
15+
3. 修改rebased_package_config.json中依赖库rootUri指向你现在的dart-sdk绝对路径
1516

1617
# 关于ide代码报错
1718
1. 并不影响程序的编译,因为包的依赖关系是在rebased_package_config.json的,
@@ -25,13 +26,13 @@ dart --deterministic --no-sound-null-safety --packages=rebased_package_config.js
2526

2627
2. 执行下面命令测试(注意目录替换)):
2728

28-
编译aot dill
29-
dart run frontend_server.dart.snapshot --sdk-root /Users/lixin/Documents/flutter_macos_stable/bin/cache/artifacts/engine/common/flutter_patched_sdk/ --target=flutter --aot --tfa --no-print-incremental-dependencies -Dflutter.inspector.structuredErrors=true -DFLUTTER_WEB_AUTO_DETECT=true -Ddart.vm.profile=false -Ddart.vm.product=false --enable-asserts --track-widget-creation --packages /Users/lixin/Documents/FlutterWorkspace/aspect_frontend_server/example/.dart_tool/package_config.json --output-dill app.dill --depfile /Users/lixin/Documents/FlutterWorkspace/aspect_frontend_server/example/.dart_tool/flutter_build/b0fee5c86b6ccb9c75440c36f0f7cea4/kernel_snapshot.d package:example/main.dart
29+
## 编译aot dill
30+
dart run frontend_server.dart.snapshot --sdk-root /Users/lixin/Documents/flutter_macos_stable/bin/cache/artifacts/engine/common/flutter_patched_sdk/ --target=flutter --aot --tfa --no-print-incremental-dependencies -Dflutter.inspector.structuredErrors=true -DFLUTTER_WEB_AUTO_DETECT=true -Ddart.vm.profile=false -Ddart.vm.product=false --enable-asserts --track-widget-creation --packages /Users/lixin/Documents/FlutterWorkspace/aspect_frontend_server/example/.dart_tool/package_config.json --output-dill app.dill --depfile /Users/lixin/Documents/FlutterWorkspace/aspect_frontend_server/example/.dart_tool/flutter_build/ac9605ecc9f9bc8bf03f7416deb04995/kernel_snapshot.d package:example/main.dart
3031

31-
编译运行时dill
32-
dart run frontend_server.dart.snapshot --sdk-root /Users/lixin/Documents/flutter_macos_stable/bin/cache/artifacts/engine/common/flutter_patched_sdk/ --target=flutter --verbose --no-print-incremental-dependencies -Dflutter.inspector.structuredErrors=true -DFLUTTER_WEB_AUTO_DETECT=true -Ddart.vm.profile=false -Ddart.vm.product=false --enable-asserts --track-widget-creation --packages /Users/lixin/Documents/FlutterWorkspace/aspect_frontend_server/example/.dart_tool/package_config.json --output-dill app.dill --depfile /Users/lixin/Documents/FlutterWorkspace/aspect_frontend_server/example/.dart_tool/flutter_build/b0fee5c86b6ccb9c75440c36f0f7cea4/kernel_snapshot.d package:example/main.dart
32+
## 编译运行时dill
33+
dart run frontend_server.dart.snapshot --sdk-root /Users/lixin/Documents/flutter_macos_stable/bin/cache/artifacts/engine/common/flutter_patched_sdk/ --target=flutter --verbose --no-print-incremental-dependencies -Dflutter.inspector.structuredErrors=true -DFLUTTER_WEB_AUTO_DETECT=true -Ddart.vm.profile=false -Ddart.vm.product=false --enable-asserts --track-widget-creation --packages /Users/lixin/Documents/FlutterWorkspace/aspect_frontend_server/example/.dart_tool/package_config.json --output-dill app.dill --depfile /Users/lixin/Documents/FlutterWorkspace/aspect_frontend_server/example/.dart_tool/flutter_build/ac9605ecc9f9bc8bf03f7416deb04995/kernel_snapshot.d package:example/main.dart
3334

34-
测试aot编译成二进制
35+
## 测试aot编译成二进制
3536
/Users/lixin/Documents/flutter_macos_stable/bin/cache/artifacts/engine/android-arm64-release/darwin-x64/gen_snapshot --deterministic --snapshot_kind=app-aot-elf --elf=app.so --strip app.dill
3637

3738

@@ -43,14 +44,13 @@ cd example
4344
flutter run
4445

4546
### 该方法和aspectd的区别
46-
1. aspectd不支持flutter 2.5.4,本项目是基于flutter 2.5.4测试
47-
2. aspectd的编译需要对dart sdk中的vm进行修改,本项目不需要
48-
3. aspectd使用前需要对flutter tools的代码进行修改,本项目只需要替换flutter sdk对应的frontend_server.dart.snapshot即可
49-
4. aspectd的实现原理过于复杂,本项目去掉了Call,Inject等用法保留了Execute用法的同时对注入逻辑进行了简化
50-
5. aspectd还需要aspect_impl等,本项目可以直接在主程序代码中添加注入代码,也可以用plugin的方式添加
51-
6. 本项目不需要引入任何第三方包,用pragma注解完成对应插桩
52-
7. 可以有限制支持hot reload,完全支持hot restart,免去了冷重启的烦恼
53-
8. 为了性能优化inject方法限制必须是static的
47+
1. aspectd不支持flutter 2.5.4以上,本项目最高支持到2.10.1,再高没有测过
48+
2. aspectd使用前需要对flutter tools的代码进行修改,本项目只需要替换flutter sdk对应的frontend_server.dart.snapshot即可
49+
3. aspectd的实现原理过于复杂,本项目去掉了Call,Inject等用法保留了Execute用法的同时对注入逻辑进行了简化
50+
4. aspectd还需要aspect_impl等,本项目可以直接在主程序代码中添加注入代码,也可以用plugin的方式添加
51+
5. 本项目不需要引入任何第三方包,用pragma注解完成对应插桩
52+
6. 可以有限制支持hot reload,完全支持hot restart,免去了冷重启的烦恼
53+
7. 为了性能优化inject方法限制必须是static的
5454

5555
### 为什么用pragma注解,而不是自定义注解?
5656
本项目是在aot优化后再对字节码进行修改,aot优化后只有白名单中的注解才能被识别到,pragma是在白名单中的注解.

example/lib/inject.dart

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'package:flutter/foundation.dart';
2+
13
@pragma("aopd:aspect")
24
@pragma('vm:entry-point')
35
class Inject {
@@ -8,14 +10,14 @@ class Inject {
810
"methodName": "-_test1",
911
"isRegex": false
1012
})
11-
//必须是static,必然不起作用
13+
//必须是static,不然不起作用
1214
static void _test1(
1315
Object target,
1416
String functionName,
1517
List<dynamic> positionalParams,
1618
Map<String, dynamic> namedParams,
1719
Function proceed) {
18-
print(
20+
debugPrint(
1921
"[Inject] $functionName start ${positionalParams[0]} ${positionalParams[1]} ${namedParams["key3"]}");
2022
Function.apply(proceed, positionalParams, _transToNamedParams(namedParams));
2123
}
@@ -27,14 +29,14 @@ class Inject {
2729
"methodName": "-_test2",
2830
"isRegex": false
2931
})
30-
//必须是static,必然不起作用
32+
//必须是static,不然不起作用
3133
static Future<bool> _test2(
3234
Object target,
3335
String functionName,
3436
List<dynamic> positionalParams,
3537
Map<String, dynamic> namedParams,
3638
Function proceed) {
37-
print(
39+
debugPrint(
3840
"[Inject] $functionName start ${positionalParams[0]} ${positionalParams[1]} ${namedParams["key3"]}");
3941
return Function.apply(
4042
proceed, positionalParams, _transToNamedParams(namedParams));
@@ -47,14 +49,14 @@ class Inject {
4749
"methodName": "+_test3",
4850
"isRegex": false
4951
})
50-
//必须是static,必然不起作用
52+
//必须是static,不然不起作用
5153
static Future<bool> _test3(
5254
Object target,
5355
String functionName,
5456
List<dynamic> positionalParams,
5557
Map<String, dynamic> namedParams,
5658
Function proceed) {
57-
print(
59+
debugPrint(
5860
"[Inject] $functionName start ${positionalParams[0]} ${positionalParams[1]} ${namedParams["key3"]}");
5961
return Function.apply(
6062
proceed, positionalParams, _transToNamedParams(namedParams));
@@ -67,21 +69,64 @@ class Inject {
6769
"methodName": "+_test4",
6870
"isRegex": false
6971
})
70-
//必须是static,必然不起作用
72+
//必须是static,不然不起作用
7173
static Future<bool> _test4(
7274
Object target,
7375
String functionName,
7476
List<dynamic> positionalParams,
7577
Map<String, dynamic> namedParams,
7678
Function proceed) async {
77-
print(
79+
debugPrint(
7880
"[Inject] $functionName start ${positionalParams[0]} ${positionalParams[1]} ${namedParams["key3"]}");
7981
bool success = await Function.apply(
8082
proceed, positionalParams, _transToNamedParams(namedParams));
81-
print("[Inject] $functionName result ${success}");
83+
debugPrint("[Inject] $functionName result $success");
8284
return success;
8385
}
8486

87+
@pragma('vm:entry-point')
88+
@pragma("aopd:inject", {
89+
"importUri": "package:example/main.dart",
90+
"clsName": "ExtensionHomePageState",
91+
"methodName": "-ExtensionHomePageState|_test5",
92+
"isRegex": false
93+
})
94+
//必须是static,不然不起作用
95+
//这里需要注意Extension的注入和普通方法不同,methodName的写法也与普遍的不同
96+
//Extension中的方法第一个positionalParams[0]所代表的参数是它扩展的实例本身
97+
static Future<bool> _test5(
98+
Object target,
99+
String functionName,
100+
List<dynamic> positionalParams,
101+
Map<String, dynamic> namedParams,
102+
Function proceed) async {
103+
debugPrint(
104+
"[Inject] $functionName start ${positionalParams[0].runtimeType.toString()} ${positionalParams[1]} ${positionalParams[2]} ${namedParams["key3"]}");
105+
bool success = await Function.apply(
106+
proceed, positionalParams, _transToNamedParams(namedParams));
107+
debugPrint("[Inject] $functionName result $success");
108+
return success;
109+
}
110+
111+
@pragma('vm:entry-point')
112+
@pragma("aopd:inject", {
113+
"importUri": "package:example/main.dart",
114+
"clsName": r"__.+MixinHomePageState",
115+
"methodName": "-_test6",
116+
"isRegex": true
117+
})
118+
//必须是static,不然不起作用
119+
static void _test6(
120+
Object target,
121+
String functionName,
122+
List<dynamic> positionalParams,
123+
Map<String, dynamic> namedParams,
124+
Function proceed) async {
125+
debugPrint(
126+
"[Inject] $functionName start ${positionalParams[0]} ${positionalParams[1]} ${namedParams["key3"]}");
127+
Function.apply(proceed, positionalParams, _transToNamedParams(namedParams));
128+
}
129+
85130
@pragma('vm:entry-point')
86131
static Map<Symbol, dynamic> _transToNamedParams(
87132
Map<String, dynamic> namedParams) {

example/lib/main.dart

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,29 +54,45 @@ class MyHomePage extends StatefulWidget {
5454

5555
Future<bool> _test4(int key1, String key2, {String key3 = 'namedkey4'}) async {
5656
total++;
57-
print("[Inject] $total _test4 $key1 $key2 $key3");
57+
debugPrint("$total _test4 $key1 $key2 $key3");
5858
return true;
5959
}
6060

61-
class _MyHomePageState extends State<MyHomePage> {
61+
extension ExtensionHomePageState on _MyHomePageState {
62+
Future<bool> _test5(int key1, String key2,
63+
{String key3 = 'namedkey5'}) async {
64+
total++;
65+
debugPrint("$total _test5 $key1 $key2 $key3");
66+
return false;
67+
}
68+
}
69+
70+
mixin MixinHomePageState {
71+
void _test6(int key1, String key2, {String key3 = 'namedkey6'}) {
72+
total++;
73+
debugPrint("$total _test6 $key1 $key2 $key3");
74+
}
75+
}
76+
77+
class _MyHomePageState extends State<MyHomePage> with MixinHomePageState {
6278
int _counter = 0;
6379

6480
void _test1(int key1, String key2, {String key3 = 'namedkey1'}) {
6581
total++;
66-
print("[Inject] $total _test1 $key1 $key2 $key3");
82+
debugPrint("$total _test1 $key1 $key2 $key3");
6783
}
6884

6985
Future<bool> _test2(int key1, String key2,
7086
{String key3 = 'namedkey2'}) async {
7187
total++;
72-
print("[Inject] $total _test2 $key1 $key2 $key3");
88+
debugPrint("$total _test2 $key1 $key2 $key3");
7389
return true;
7490
}
7591

7692
static Future<bool> _test3(int key1, String key2,
7793
{String key3 = 'namedkey3'}) async {
7894
total++;
79-
print("[Inject] $total _test3 $key1 $key2 $key3");
95+
debugPrint("$total _test3 $key1 $key2 $key3");
8096
return true;
8197
}
8298

@@ -86,6 +102,8 @@ class _MyHomePageState extends State<MyHomePage> {
86102
_test2(_counter, "positional2");
87103
_test3(_counter, "positional3");
88104
_test4(_counter, "positional4");
105+
_test5(_counter, "positional5");
106+
_test6(_counter, "positional6");
89107
setState(() {
90108
// This call to setState tells the Flutter framework that something has
91109
// changed in this State, which causes it to rerun the build method below

frontend_server.dart.snapshot

62.2 KB
Binary file not shown.

lib/starter.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
library aspect_frontend_server;
2+
// ignore_for_file: import_of_legacy_library_into_null_safe
3+
14
import 'dart:io';
25
import 'package:args/args.dart';
36

lib/transformer.dart

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// ignore_for_file: import_of_legacy_library_into_null_safe, unused_import
2+
13
import 'package:frontend_server/frontend_server.dart' as frontend;
24
import 'package:vm/target/flutter.dart';
35

@@ -99,13 +101,14 @@ class _AopExecuteVisitor extends RecursiveVisitor<void> {
99101
}
100102

101103
@override
102-
void visitClass(Class cls) {
103-
String clsName = cls.name;
104-
Library originalLibrary = cls.parent as Library;
104+
void visitClass(Class node) {
105+
String clsName = node.name;
106+
Library originalLibrary = node.parent as Library;
105107
bool matches = false;
106108
int aopItemInfoListLen = _aopItemList.length;
107109
for (int i = 0; i < aopItemInfoListLen && !matches; i++) {
108110
AopItem aopItem = _aopItemList[i];
111+
109112
if ((aopItem.isRegex && RegExp(aopItem.clsName).hasMatch(clsName)) ||
110113
(!aopItem.isRegex && clsName == aopItem.clsName) &&
111114
originalLibrary.importUri.toString() == aopItem.importUri) {
@@ -114,10 +117,46 @@ class _AopExecuteVisitor extends RecursiveVisitor<void> {
114117
}
115118
}
116119
if (matches) {
117-
cls.visitChildren(this);
120+
print(
121+
"[AspectAopTransformer] visitClass match ${node.parent.runtimeType.toString()} ${node.name}");
122+
node.visitChildren(this);
118123
}
119124
}
120125

126+
@override
127+
void visitExtension(Extension node) {
128+
String clsName = node.name;
129+
Library originalLibrary = node.parent as Library;
130+
bool matches = false;
131+
int aopItemInfoListLen = _aopItemList.length;
132+
for (int i = 0; i < aopItemInfoListLen && !matches; i++) {
133+
AopItem aopItem = _aopItemList[i];
134+
if ((aopItem.isRegex && RegExp(aopItem.clsName).hasMatch(clsName)) ||
135+
(!aopItem.isRegex && clsName == aopItem.clsName) &&
136+
originalLibrary.importUri.toString() == aopItem.importUri) {
137+
matches = true;
138+
break;
139+
}
140+
}
141+
if (matches) {
142+
print(
143+
"[AspectAopTransformer] visitExtension extension match ${node.parent.runtimeType.toString()} ${node.name}");
144+
node.visitChildren(this);
145+
}
146+
}
147+
148+
@override
149+
void visitClassReference(Class node) {
150+
//print(
151+
// "[AspectAopTransformer] visitClassReference match ${node.parent.runtimeType.toString()} ${node.name}");
152+
}
153+
154+
@override
155+
void visitSuperMethodInvocation(SuperMethodInvocation node) {
156+
//print(
157+
// "[AspectAopTransformer] visitSuperMethodInvocation match ${node.parent.runtimeType.toString()} ${node.name}");
158+
}
159+
121160
@override
122161
void visitProcedure(Procedure node) {
123162
String procedureName = node.name.text;
@@ -129,6 +168,7 @@ class _AopExecuteVisitor extends RecursiveVisitor<void> {
129168
originalClass = node.parent as Class;
130169
originalLibrary = originalClass.parent as Library;
131170
}
171+
132172
String? clsName = null;
133173
String? importUri = null;
134174
if (needCompareClass) {
@@ -144,7 +184,11 @@ class _AopExecuteVisitor extends RecursiveVisitor<void> {
144184
RegExp(aopItem.methodName).hasMatch(procedureName)) ||
145185
(!aopItem.isRegex && procedureName == aopItem.methodName)) {
146186
if (needCompareClass) {
147-
if (aopItem.clsName == clsName && aopItem.importUri == importUri) {
187+
if (((aopItem.isRegex &&
188+
clsName != null &&
189+
RegExp(aopItem.clsName).hasMatch(clsName)) ||
190+
(!aopItem.isRegex && aopItem.clsName == clsName)) &&
191+
aopItem.importUri == importUri) {
148192
matchedAopItem = aopItem;
149193
break;
150194
}
@@ -350,6 +394,6 @@ class _AopExecuteVisitor extends RecursiveVisitor<void> {
350394
//将原本的处理流程替换成注入后的流程
351395
functionNode.body = block;
352396
print(
353-
"[AspectAopTransformer] inject ${originalProcedure.name.toString()} success");
397+
"[AspectAopTransformer] inject ${originalProcedure.name.toString()} success ");
354398
}
355399
}

0 commit comments

Comments
 (0)