Growingio Dart Frontend AOP Project
该AOP方式基于修改 Flutter 源代码的方式进行,需要拉取GitHub 上的 Flutter SDK。 具体安装可以参考官方引导:install flutter
请根据自己项目的flutter版本下载对应tag的 frontend_server.dart.snapshot
.
比如说 flutter 3.3.9版本,需要下载 tag 3.3.9 下的 frontend_server.dart.snapshot
文件。
flutter 3.19.0起,还需下载对应您当前的平台架构的frontend_server_aot.dart.snapshot
文件。
需要在 flutter sdk下进行替换,位置分别为:
- flutter/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot (macos)
- flutter/bin/cache/dart-sdk/bin/snapshots/frontend_server.dart.snapshot
- flutter/bin/cache/dart-sdk/bin/snapshots/frontend_server_aot.dart.snapshot (flutter version >=3.19.0)
覆盖 frontend_server.dart.snapshot
后需要清理缓存
flutter clean
一般在 growingio 插件中会引用 growingio_inject_impl.dart
,之后就能通过注解实现无埋点技术了。
可以通过反编译查看是否Hook成功。
使用项目下的 dump_dill.dart
:
dart dump_dill.dart
需要将 app.dill放在项目下,会生成out.dill.txt文件,可以查看文件中是否有注入成功。
app.dill 是flutter编译后的产物,一般位于
/.dart_tool/flutter_build/<一串长参数>/app.dill
可以将项目中的inject
目录下的文件加入你的项目中,这两个文件是 Dart 编译过程中实现AOP的关键文件。
固定文件名为 growingio_inject_annotation.dart
.
注解为 @Inject,注入到对象方法中,参数为importUri,clsName,methodName;可选为isRegex,isStatic,isAfter,injectType。
固定文件名为 growingio_inject_impl.dart
.
注入有两种方式,注入方式和参数如下所示:
@Inject 各个参数解释如下表:
参数 | 默认值 | 解释 |
---|---|---|
importUri | - | 注入指定的 library 路径 |
clsName | - | 指定的类名 |
methodName | - | 指定的方法名 |
isRegex | false | 是否需要对路径进行正则匹配 |
isStatic | false | 指定的方法是否是静态方法 |
isAfter | false | 是否注入方法的最后一行 |
injectType | 0 | 注入方式 0:普通注入,1:SuperInject |
@SuperInject 通过指定父类的路径和类名,所有继承该父类的子类中若包含指定的方法都会被Hook注入。
importUri变为指定父类的路径 clsName变为指定指定父类的类名
关于参数说明,第一个参数统一规定为 PointCut
,它包含 target=>this 和 result=>方法返回值 两个属性。其中只有isAfter为true且hook注入的方法有返回值时result才会有值。
其他剩余参数一一对应原方法的参数。注:可以少于原方法的参数个数。
如下方的方法
/// 原方法为 "package:flutter/src/widgets/navigator.dart" 文件下的类 "_RouteEntry"的"handlePop"方法。
@Inject(
"package:flutter/src/widgets/navigator.dart", "_RouteEntry", "handlePop",
isAfter: true)
@pragma("vm:entry-point")
/// 第一个参数统一为 PointCut,剩余的参数为原方法的入参,可以原封不动的挪过来。
dynamic _routeHandlePop(PointCut pointCut,
{required NavigatorState navigator,
required Route<dynamic>? previousPresent}) {
/// PointCut的target参数代表原方法中this
dynamic target = pointCut.target;
GrowingPageProvider.getInstance().handlePop(target.route, previousPresent);
/// PointCut的result方法代表原方法的返回值。
return pointCut.result;
}
修改 dart sdk pkg下的vm代码,路径为 /pkg/vm/lib/target/flutter.dart 能使外部的transformer在dart代码优化前参与到Flutter代码的编译中。 参考如下:https://github.com/XianyuTech/sdk/commit/0b75bb256761342d321b92540a477d6e59301a48
abstract class FlutterProgramTransformer {
void transform(Component component);
}
static List<FlutterProgramTransformer> _flutterProgramTransformers = [];
static List<FlutterProgramTransformer> get flutterProgramTransformers => _flutterProgramTransformers;
if (_flutterProgramTransformers.length > 0) {
for (int i = 0; i < _flutterProgramTransformers.length; i++) {
_flutterProgramTransformers[i].transform(component);
}
}
使用自己的 flutter_frontend_server 生成 frontend_server.dart.snapshot 来替换 dart 默认的snapshot,使其具有 AOP 的能力。
生成命令:首先进入 flutter_frontend_server 目录
dart --deterministic --no-sound-null-safety --snapshot=frontend_server.dart.snapshot frontend_server_starter.dart
dart --deterministic --snapshot=frontend_server.dart.snapshot frontend_server_starter.dart
在 dart 2.18.5 tag的源码中,frontend_server 还停留在 dart=2.9 (<2.12),所以只能忽略空安全编译
dart compile aot-snapshot --output=frontend_server_aot.dart.snapshot frontend_server_starter.dart
注意:在当前架构 (darwin_arm64/darwin_x64/windows_x64) 运行该命令生成的产物只能在同一架构使用,因此需要对不同的架构分别进行打包,推荐使用 Github Actions 进行多环境打包而不是本地环境打包
修改组件的路径,参考 widget inspect transform。 源码位置:https://github.com/dart-lang/sdk/blob/main/pkg/kernel/lib/transformations/track_widget_constructor_locations.dart 修改的几个重要关键词:
const String _locationFieldName = r'_gio_location';
if (importUri.path.contains('growingio_local_element.dart')) {
for (Class class_ in library.classes) {
if (class_.name == '_CustomHasCreationLocation') {
_hasCreationLocationClass = class_;
foundHasCreationLocationClass = true;
} else if (class_.name == '_CustomLocation') {
_locationClass = class_;
foundLocationClass = true;
}
}
}
Inspector_service 位置:https://github.com/flutter/devtools/blob/d2d6b5b7f4b92972aff3cab320c206d00bcdd6a9/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart
见如何调试