新增网络可视化请求工具

This commit is contained in:
2026-07-22 09:24:48 +08:00
parent 4ff93edaab
commit d6e80df5bf
230 changed files with 92710 additions and 51 deletions
@@ -0,0 +1,334 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:ui' as ui;
import 'dart:math' as math;
import 'package:flutter_ume/util/constants.dart';
class InspectorOverlay extends LeafRenderObjectWidget {
const InspectorOverlay(
{Key? key,
required this.selection,
this.needEdges = true,
this.needDescription = true})
: super(key: key);
final InspectorSelection selection;
final bool needDescription;
final bool needEdges;
@override
_RenderInspectorOverlay createRenderObject(BuildContext context) {
return _RenderInspectorOverlay(
selection: selection,
needDescription: needDescription,
needEdges: needEdges);
}
@override
void updateRenderObject(
BuildContext context, _RenderInspectorOverlay renderObject) {
renderObject.selection = selection;
}
}
class _RenderInspectorOverlay extends RenderBox {
_RenderInspectorOverlay({
required InspectorSelection selection,
required this.needDescription,
required this.needEdges,
}) : _selection = selection;
final bool needDescription;
final bool needEdges;
InspectorSelection get selection => _selection;
InspectorSelection _selection;
set selection(InspectorSelection value) {
if (value != _selection) {
_selection = value;
}
markNeedsPaint();
}
@override
bool get sizedByParent => true;
@override
bool get alwaysNeedsCompositing => true;
@override
void performResize() {
size = constraints.constrain(const Size(double.infinity, double.infinity));
}
@override
void paint(PaintingContext context, Offset offset) {
assert(needsCompositing);
context.addLayer(_InspectorOverlayLayer(
needEdges: needEdges,
needDescription: needDescription,
overlayRect: Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height),
selection: selection,
));
}
}
class _InspectorOverlayLayer extends Layer {
_InspectorOverlayLayer({
required this.overlayRect,
required this.selection,
required this.needDescription,
required this.needEdges,
});
InspectorSelection selection;
final bool needDescription;
final bool needEdges;
final Rect overlayRect;
_InspectorOverlayRenderState? _lastState;
late ui.Picture _picture;
TextPainter? _textPainter;
double? _textPainterMaxWidth;
@override
void addToScene(ui.SceneBuilder builder, [Offset layerOffset = Offset.zero]) {
if (!selection.active) return;
final _SelectionInfo info = _SelectionInfo(selection);
final RenderObject? selected = info.renderObject;
final List<_TransformedRect> candidates = <_TransformedRect>[];
for (RenderObject candidate in selection.candidates) {
if (candidate == selected || !candidate.attached) continue;
candidates.add(_TransformedRect(candidate));
}
final _InspectorOverlayRenderState state = _InspectorOverlayRenderState(
selectionInfo: info,
overlayRect: overlayRect,
selected: _TransformedRect(selected!),
textDirection: TextDirection.ltr,
candidates: candidates,
);
if (state != _lastState) {
_lastState = state;
_picture = _buildPicture(state);
}
builder.addPicture(layerOffset, _picture);
}
ui.Picture _buildPicture(_InspectorOverlayRenderState state) {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final Canvas canvas = Canvas(recorder, state.overlayRect);
final Size size = state.overlayRect.size;
final Paint fillPaint = Paint()
..style = PaintingStyle.fill
..color = kHighlightedRenderObjectFillColor;
final Paint borderPaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1.0
..color = kHighlightedRenderObjectBorderColor;
final Rect selectedPaintRect = state.selected.rect.deflate(0.5);
canvas
..save()
..transform(state.selected.transform.storage)
..drawRect(selectedPaintRect, fillPaint)
..drawRect(selectedPaintRect, borderPaint)
..restore();
if (needEdges) {
for (_TransformedRect transformedRect in state.candidates) {
canvas
..save()
..transform(transformedRect.transform.storage)
..drawRect(transformedRect.rect.deflate(0.5), borderPaint)
..restore();
}
}
final Rect targetRect = MatrixUtils.transformRect(
state.selected.transform, state.selected.rect);
final Offset target = Offset(targetRect.left, targetRect.center.dy);
const double offsetFromWidget = 9.0;
final double verticalOffset = (targetRect.height) / 2 + offsetFromWidget;
if (needDescription) {
_paintDescription(canvas, state.selectionInfo.message,
state.textDirection, target, verticalOffset, size, targetRect);
}
return recorder.endRecording();
}
void _paintDescription(
Canvas canvas,
String message,
TextDirection textDirection,
Offset target,
double verticalOffset,
Size size,
Rect targetRect,
) {
canvas.save();
final double maxWidth =
size.width - 2 * (kScreenEdgeMargin + kTooltipPadding);
final TextSpan? textSpan = _textPainter?.text as TextSpan?;
if (_textPainter == null ||
textSpan!.text != message ||
_textPainterMaxWidth != maxWidth) {
_textPainterMaxWidth = maxWidth;
_textPainter = TextPainter()
..maxLines = kMaxTooltipLines
..ellipsis = '...'
..text = TextSpan(
style: TextStyle(color: kTipTextColor, fontSize: 12.0, height: 1.2),
text: message)
..textDirection = textDirection
..layout(maxWidth: maxWidth);
}
final Size tooltipSize = _textPainter!.size +
const Offset(kTooltipPadding * 2, kTooltipPadding * 2);
final Offset tipOffset = positionDependentBox(
size: size,
childSize: tooltipSize,
target: target,
verticalOffset: verticalOffset,
preferBelow: false,
);
final Paint tooltipBackground = Paint()
..style = PaintingStyle.fill
..color = kTooltipBackgroundColor;
canvas.drawRect(
Rect.fromPoints(
tipOffset,
tipOffset.translate(tooltipSize.width, tooltipSize.height),
),
tooltipBackground,
);
double wedgeY = tipOffset.dy;
final bool tooltipBelow = tipOffset.dy > target.dy;
if (!tooltipBelow) wedgeY += tooltipSize.height;
const double wedgeSize = kTooltipPadding * 2;
double wedgeX = math.max(tipOffset.dx, target.dx) + wedgeSize * 2;
wedgeX = math.min(wedgeX, tipOffset.dx + tooltipSize.width - wedgeSize * 2);
final List<Offset> wedge = <Offset>[
Offset(wedgeX - wedgeSize, wedgeY),
Offset(wedgeX + wedgeSize, wedgeY),
Offset(wedgeX, wedgeY + (tooltipBelow ? -wedgeSize : wedgeSize)),
];
canvas.drawPath(Path()..addPolygon(wedge, true), tooltipBackground);
_textPainter!.paint(
canvas, tipOffset + const Offset(kTooltipPadding, kTooltipPadding));
canvas.restore();
}
@override
@protected
bool findAnnotations<S extends Object>(
AnnotationResult<S> result, Offset localPosition,
{required bool onlyFirst}) {
return false;
}
}
class _SelectionInfo {
const _SelectionInfo(this.selection);
final InspectorSelection selection;
RenderObject? get renderObject => selection.current;
Element? get element => selection.currentElement;
Map? get jsonInfo {
if (renderObject == null) return null;
final widgetId = WidgetInspectorService.instance
// ignore: invalid_use_of_protected_member
.toId(renderObject!.toDiagnosticsNode(), '');
if (widgetId == null) return null;
String infoStr =
WidgetInspectorService.instance.getSelectedSummaryWidget(widgetId, '');
return json.decode(infoStr);
}
String? get filePath {
final f = (jsonInfo != null && jsonInfo!.containsKey('creationLocation'))
? jsonInfo!['creationLocation']['file']
: '';
return f;
}
int? get line {
final l = (jsonInfo != null && jsonInfo!.containsKey('creationLocation'))
? jsonInfo!['creationLocation']['line']
: 0;
return l;
}
String get message {
return '''${element!.toStringShort()}\nsize: ${renderObject!.paintBounds.size}\nfilePath: $filePath\nline: $line''';
}
}
class _InspectorOverlayRenderState {
_InspectorOverlayRenderState({
required this.overlayRect,
required this.selected,
required this.candidates,
required this.textDirection,
required this.selectionInfo,
});
final Rect overlayRect;
final _TransformedRect selected;
final List<_TransformedRect> candidates;
final TextDirection textDirection;
final _SelectionInfo selectionInfo;
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType) return false;
final _InspectorOverlayRenderState typedOther = other;
return overlayRect == typedOther.overlayRect &&
selected == typedOther.selected &&
listEquals<_TransformedRect>(candidates, typedOther.candidates);
}
@override
int get hashCode =>
Object.hash(overlayRect, selected, Object.hashAll(candidates));
}
class _TransformedRect {
_TransformedRect(RenderObject object)
: rect = object.semanticBounds,
transform = object.getTransformTo(null);
final Rect rect;
final Matrix4 transform;
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType) return false;
final _TransformedRect typedOther = other;
return rect == typedOther.rect && transform == typedOther.transform;
}
@override
int get hashCode => Object.hash(rect, transform);
}
@@ -0,0 +1,5 @@
import 'package:flutter_ume/service/vm_service/service_wrapper.dart';
mixin VMServiceWrapper {
final ServiceWrapper serviceWrapper = ServiceWrapper();
}
@@ -0,0 +1,107 @@
import 'dart:developer';
import 'dart:isolate';
import 'package:vm_service/utils.dart';
import 'package:vm_service/vm_service.dart' as vm;
import 'package:vm_service/vm_service_io.dart';
class ServiceWrapper {
vm.VmService? _service;
String? _isolateId;
String? get isolateId {
if (_isolateId != null) {
return _isolateId;
}
_isolateId = Service.getIsolateID(Isolate.current);
return _isolateId;
}
Future<vm.VmService> getVMService() async {
if (_service != null) {
return _service!;
}
ServiceProtocolInfo info = await Service.getInfo();
String url = info.serverUri.toString();
Uri uri = Uri.parse(url);
Uri socketUri = convertToWebSocketUrl(serviceProtocolUrl: uri);
_service = await vmServiceConnectUri(socketUri.toString());
return _service!;
}
Future<vm.VM> getVM() async {
vm.VmService virtualMachine = await getVMService();
return virtualMachine.getVM();
}
Future<vm.MemoryUsage> getMemoryUsage() async {
vm.VmService virtualMachine = await getVMService();
return virtualMachine.getMemoryUsage(isolateId!);
}
Future<vm.ClassList> getClassList() async {
vm.VmService virtualMachine = await getVMService();
return virtualMachine.getClassList(isolateId!);
}
Future<vm.AllocationProfile> getAllocationProfile() async {
vm.VmService virtualMachine = await getVMService();
return virtualMachine.getAllocationProfile(isolateId!, reset: true);
}
Future<vm.Isolate> getIsolate() async {
vm.VmService virtualMachine = await getVMService();
return virtualMachine.getIsolate(isolateId!);
}
Future<List<vm.LibraryRef>?> getLibraries() async {
vm.Isolate isolate = await getIsolate();
return isolate.libraries;
}
Future<vm.HeapSnapshotGraph> getSnapshot() async {
vm.VmService virtualMachine = await getVMService();
vm.Isolate isolate = await getIsolate();
return vm.HeapSnapshotGraph.getSnapshot(virtualMachine, isolate);
}
Future<vm.InstanceSet> getInstances(String objectId, int limit) async {
vm.VmService virtualMachine = await getVMService();
return virtualMachine.getInstances(isolateId!, objectId, limit);
}
Future<vm.Stack> getStack() async {
vm.VmService virtualMachine = await getVMService();
return virtualMachine.getStack(isolateId!);
}
Future<vm.Obj> getObject(String objectId, {int? offset, int? count}) async {
vm.VmService virtualMachine = await getVMService();
return virtualMachine.getObject(isolateId!, objectId,
offset: offset, count: count);
}
Future<vm.InboundReferences> getInboundReferences(String objectId) async {
vm.VmService virtualMachine = await getVMService();
return virtualMachine.getInboundReferences(isolateId!, objectId, 100);
}
Future<List<vm.ClassHeapStats>> getClassHeapStats() async {
vm.AllocationProfile profile = await getAllocationProfile();
List<vm.ClassHeapStats> list = profile.members!
.where((element) =>
element.bytesCurrent! > 0 || element.instancesCurrent! > 0)
.toList();
return list;
}
Future<vm.ScriptList> getScripts() async {
vm.VmService virtualMachine = await getVMService();
return virtualMachine.getScripts(isolateId!);
}
Future<vm.Response> evaluate(String targetId, String expression) async {
vm.VmService virtualMachine = await getVMService();
return virtualMachine.evaluate(isolateId!, targetId, expression);
}
}