背景:
在 Android 开发中反射技术被广泛应用作用如下:
- 创建类、获取私有成员、方法
- 动态代理
- 运行时调用对象任意方法
- 热修复、插件化、
Retrofit、Gson各种库等
在 Flutter 中为了包大小,健壮性等的原因禁止了反射。
Flutter 如何动态调用方法、获取对象信息?
可以借助 Dart VM 来实现。
本文将实战以下内容:
使用 VM Service 调用 Dart 方法获取私有对象。
- _element 树信息
- _MyHomePageState 中的 _counter 值。
Dart VM Service
一 什么是 Dart VM
与 Java 中的 JVM 类似,Dart VM 是用于在本地执行 Dart 代码的组件集合。
一句话来说 Dart VM 是一个虚拟机
它包括以下内容:
- 运行时系统
- 对象模型
- 垃圾收集
- 快照
- 核心库原生方法
- 可通过服务协议访问的开发体验组件 调试分析 热重载
- 即时 (JIT) 和提前 (AOT) 编译管道
- 口译员
- ARM 模拟器
二、什么是 Dart VM Service
是 Dart 虚拟机内部提供的一套 Web 服务,通过 JSON-RPC 2.0 协议访问 Dart VM 服务协议的库。
通过JSON-RPC 2.0意味着可以从其他进程获取App中的数据
通过 yaml 文件我们可以使用 vm service 的功能了
实战:获取 _MyAppState 中的 _counter 对象
我们将在电脑(PC)端中的 Dart 进程来获取 App 中的数据。
创建一个 Flutter 新工程。
创建一个测试文件在test目录下。
void main() async {
}
添加 vm_service 依赖
dependencies:
vm_service: ^7.5.0
1.1 在 main.dart 顶层文件位置添加获取 WidgetsBinding.instance!.renderViewElement! 的方法
Element instance() => WidgetsBinding.instance.renderViewElement;
void main() {
runApp(const MyApp());
}
1.2 拿到 VM Service URI
如何拿到 URI?
方法一: 我们启动 Flutter App 的时候日志已经告诉我们了:
√ Built build\app\outputs\flutter-apk\app-debug.apk.
Installing build\app\outputs\flutter-apk\app.apk...
Debug service listening on ws://127.0.0.1:50122/HPw25EKJ0bs=/ws
方法二:
通过 Service.getInfo() 方法获取 URI 。该方法能让我们的 App 知道 VM Service URI
需要注意:
- 不同的进程会得到不同的结果
Future<String?> getObservatoryUri() async {
ServiceProtocolInfo serviceProtocolInfo = await Service.getInfo();
_observatoryUri = serviceProtocolInfo.serverUri;
Uri url = convertToWebSocketUrl(serviceProtocolUrl: _observatoryUri!);
return url.toString();
}
通过以上两种方法我们能得到 URI: ws://127.0.0.1:50122/HPw25EKJ0bs=/ws 。本次实战将使用方法一来实现
1.3 VM Service 获取 renderViewElement 实例。打印 Element 树信息并找到 MyHomePageState 中 _counter
获取流程如下:
-
- 通过
URI与App的VM Service建立连接
- 通过
-
- 获取
mainIsolate、library的 id。
- 获取
-
- 通过
invoke方法调用我们在 1.1中的main.dart中写好的函数, 得到Json格式的Response。
- 通过
-
Response转换为Instance引用对象
-
- 根据
引用对象的id转换为实例对象
- 根据
-
- 重复以上操作遍历
element树找到_MyHomePageState对象拿到_counter值
- 重复以上操作遍历
import 'package:vm_service/vm_service.dart';
import 'package:vm_service_demo/vm_service_utils.dart';```````
const libraryPath = 'package:vm_service_demo/main.dart';
final utils = VmServerUtils(uriStr: 'ws://127.0.0.1:63645/l_CQSE1CsE8=/ws');
late final VmService vms;
late final Isolate mainIsolate;
late final LibraryRef library;
late final Instance renderViewElement;
void main() async {
vms = await utils.getVmService();
mainIsolate = (await utils.findMainIsolate())!;
library = (await utils.findLibrary(libraryPath))!;
/// 私有方法,getter、setter 方法不能调用
Response? renderViewElementResponse =
await vms.invoke(mainIsolate.id!, library.id!, 'binding', []);
renderViewElement = Instance.parse(renderViewElementResponse.json)!;
/// 从 renderViewElement 深度优先遍历
await depthFirst(renderViewElement.id!);
}
Future<void> depthFirst(String keyId) async {
while (true) {
final obj = await _depthFirst(keyId);
if (obj == null) break;
/// List<Widget> 递归遍历
if (obj.elements != null && obj.elements!.isNotEmpty) {
for (final e in obj.elements!) {
await depthFirst(e.id!);
}
}
return;
}
}
Future<Instance?> _depthFirst(String objId) async {
Obj obj = await vms.getObject(mainIsolate.id!, objId);
var element = Instance.parse(obj.json)!;
while (true) {
Response widgetResponse = await vms.invoke(mainIsolate.id!, element.id!, 'toString', []);
final widgetRef = Instance.parse(widgetResponse.json);
print(' ${widgetRef!.valueAsString}');
if (widgetRef.valueAsString!.startsWith('MyHomePage')) {
final _counter = await findCounterField(element);
print('_counter = $_counter');
return null;
}
final child = element.getFieldValueInstance('_child');
if (child is InstanceRef) {
obj = await vms.getObject(mainIsolate.id!, child.id!);
element = Instance.parse(obj.json!)!;
} else if (child == null) {
final _children = element.getFieldValueInstance('_children');
if (_children == null) return null;
obj = await vms.getObject(mainIsolate.id!, _children.id!);
element = Instance.parse(obj.json!)!;
return element;
} else {
return null;
}
}
}
Future<String> findCounterField(Instance element) async {
final state = element.getFieldValueInstance('_state');
final stateObj = await vms.getObject(mainIsolate.id!, state.id!);
var _counterRef = Instance.parse(stateObj.json)!;
return _counterRef.valueAsString!;
}
我们成功拿到了 WidgetsFlutterBinding 对象,并打印了所有 element 对象信息并打印出了 counter = 10:
二、ObjRef , Obj 和 id 的作用
先介绍 vm_service 中的核心内容:ObjRef、Obj、id
vm_service 返回的数据主要分为两大类, ObjRef(引用类型)和 Obj(对象实例类型)。其中 Obj 完整的包含了 ObjRef 的数据,并在其基础上增加了额外信息(ObjRef 只包含了一些基本信息,例如:id,name 等)。
基本所有的 API 返回的数据都是 ObjRef,当 ObjRef 里面的信息满足不了你的时候,再调用 getObject(,,,)来获取 Obj。
关于 id: Obj 和 ObjRef 都含有 id,这个 id 是对象实例在 vm_service 里面的一个标识符, vm_service 几乎所有的 API 都需要通过 id 来操作,比如:getInstance(isolateId, classId, ...)、 getIsolate(isolateId)、 getObject(isolateId, objectId, ...)。
总结:
Dart VM Service的invoke()方法虽然跟反射很像,但是它不能执行构造函数、getter、setter方法。Flutter Driver就是封装了Dart VM Service完成了自动化测试的功能。Dart VM Service只有在Debug、ProFile模式下才能运行,release模式不能使用。- vm_service_demo github