背景:
在 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