提交 148925c9 编写于 作者: [ [linjizong]

vmhelper重构;完善内存泄漏demo

上级 25967ead
......@@ -8,8 +8,6 @@ PODS:
- Flutter
- path_provider_windows (0.0.1):
- Flutter
- sensors (0.0.1):
- Flutter
- shared_preferences (0.0.1):
- Flutter
- shared_preferences_linux (0.0.1):
......@@ -27,7 +25,6 @@ DEPENDENCIES:
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`)
- path_provider_windows (from `.symlinks/plugins/path_provider_windows/ios`)
- sensors (from `.symlinks/plugins/sensors/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- shared_preferences_linux (from `.symlinks/plugins/shared_preferences_linux/ios`)
- shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`)
......@@ -45,8 +42,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_linux/ios"
path_provider_windows:
:path: ".symlinks/plugins/path_provider_windows/ios"
sensors:
:path: ".symlinks/plugins/sensors/ios"
shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios"
shared_preferences_linux:
......@@ -64,7 +59,6 @@ SPEC CHECKSUMS:
path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d
path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4
path_provider_windows: a2b81600c677ac1959367280991971cb9a1edb3b
sensors: 84eb7a30e47a649e4172b71d6e81be614c280336
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
shared_preferences_linux: afefbfe8d921e207f01ede8b60373d9e3b566b78
shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087
......
......@@ -8,13 +8,15 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:dokit/kit/apm/leak/leak_observer.dart';
import 'package:flutter/material.dart';
import 'package:dokit/dokit.dart';
import 'package:flutter/services.dart';
import 'package:memory_checker/leak_observer.dart';
import 'package:path_provider/path_provider.dart';
import 'package:dokit/kit/apm/vm/vm_helper.dart';
void main() {
List<String> blackList = [
......@@ -58,6 +60,7 @@ class MyApp extends StatelessWidget {
// closer together (more dense) than on mobile platforms.
visualDensity: VisualDensity.adaptivePlatformDensity,
),
navigatorObservers: [LeakObserver(), DoKitLeakObserver()],
home: DoKitTestPage(),
);
}
......@@ -179,6 +182,22 @@ class _DoKitTestPageState extends State<DoKitTestPage> {
},
),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4)),
color: Color(0xffcccccc)),
margin: EdgeInsets.only(bottom: 30),
child: FlatButton(
child: Text('Test Isolate',
style: TextStyle(
color: Color(0xff000000),
fontSize: 18,
)),
onPressed: () {
VmHelper.instance.testIsolate();
},
),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4)),
......@@ -347,7 +366,53 @@ class TestPageState extends State<TestPage> {
}
}
class A {
dynamic a;
A(this.a);
aa() {}
}
class B {
dynamic b;
B(this.b);
bb() {}
}
class C {
dynamic c;
C(this.c);
cc() {}
}
class D {
dynamic d;
D(this.d);
dd() {}
}
class TestPage2 extends StatefulWidget {
@override
StatefulElement createElement() {
// TODO: implement createElement
StatefulElement element = super.createElement();
// DoKitLeakObserver.element = element;
// DoKitLeakObserver.test = A(B(C(D(element))));
// Future.delayed(Duration(hours: 1), () {
// print(element);
// });
return element;
// return super.createElement();
}
@override
State<StatefulWidget> createState() {
return TestPageState2();
......
......@@ -13,7 +13,7 @@ dependencies:
path: ..
dio: 3.0.10
path_provider: 1.6.0
memory_checker: ^0.0.4
dev_dependencies:
flutter_test:
......
import 'package:dokit/kit/apm/leak/leak_obj.dart';
import 'package:dokit/kit/apm/vm/iso_pool.dart';
import 'package:dokit/kit/apm/vm/vm_helper.dart';
import 'package:dokit/kit/apm/vm/vm_service_wrapper.dart';
import 'package:vm_service/vm_service.dart';
import '../leak_kit.dart';
Expando<LeakInfo> expando;
Future<void> leakCheck(dynamic obj) async {
// print(obj);
if (!VMServiceWrapper.instance.connected || obj == null) {
return;
}
expando = new Expando();
expando[obj] = new LeakInfo();
VMServiceWrapper.instance.service
.getAllocationProfile(VMServiceWrapper.instance.main.id, gc: true);
Future.delayed(Duration(seconds: 1), () {
obj2Id(expando).then((id) => getRetainingPath(obj, id));
});
}
void getRetainingPath(dynamic obj, String expandoId) async {
String leakObjId = await IsoPool().start(_leakCheck, expandoId);
expando[obj] = null;
if (leakObjId != null) {
// String RetainingPath = await IsoPool().start(_getRetainingPath, leakObjId);
_getRetainingPath(leakObjId);
// print(RetainingPath);
} else {
print('no leak:$obj');
}
}
Future<String> _getRetainingPath(String leakObjId) async {
if (!VMServiceWrapper.instance.connected) {
await VMServiceWrapper.instance.connect();
}
if (VMServiceWrapper.instance.connected) {
final mainId = VMServiceWrapper.instance.main.id;
final service = VMServiceWrapper.instance.service;
RetainingPath path =
await service.getRetainingPath(mainId, leakObjId, 10000);
print(path);
path.elements.forEach((element) {
if (element.value.type == '@Instance') {
InstanceRef ref = element.value;
// 过滤隐藏类,一般是系统类
if (ref.classRef != null) {
service.invoke(mainId, element.value.id, 'toString', []).then(
(value) => printElement(ref.classRef.name, value as InstanceRef));
}
}
});
return path.toString();
}
}
printElement(String className, InstanceRef value) {
print('className:$className detail:${value.valueAsString}');
}
Future<String> _leakCheck(String objectId) async {
if (!VMServiceWrapper.instance.connected) {
await VMServiceWrapper.instance.connect();
}
if (VMServiceWrapper.instance.connected) {
final mainId = VMServiceWrapper.instance.main.id;
final service = VMServiceWrapper.instance.service;
Instance instance = await service.getObject(mainId, objectId);
BoundField field =
instance.fields.firstWhere((element) => element.decl.name == '_data');
instance = await service.getObject(mainId, field.value.id);
InstanceRef ref = instance.elements.firstWhere((element) =>
element != null && (element as InstanceRef).kind == 'WeakProperty');
instance = await service.getObject(mainId, ref.id);
return instance.propertyKey.id;
}
}
import 'package:dokit/kit/apm/vm_helper.dart';
import 'package:dokit/kit/apm/vm/vm_service_wrapper.dart';
import 'package:vm_service/vm_service.dart';
int _key = 0;
......@@ -17,12 +17,12 @@ dynamic keyToObj(String key) {
/// 对象转 id
Future<String> obj2Id(dynamic obj) async {
VmService service = VmHelper.instance.serviceClient;
String isolateId = VmHelper.instance.main.id;
VmService service = VMServiceWrapper.instance.service;
String isolateId = VMServiceWrapper.instance.main.id;
Isolate isolate = await service.getIsolate(isolateId);
String libraryId = isolate.libraries
.firstWhere(
(element) => element.uri.contains('dokit/engine/leak_obj.dart'))
(element) => element.uri.contains('dokit/kit/apm/leak/leak_obj.dart'))
.id;
// 用 vm service 执行 generateNewKey 函数
......
import 'package:dokit/kit/apm/leak/leak_checker.dart';
import 'package:flutter/material.dart';
import '../fps_kit.dart';
class DoKitLeakObserver extends NavigatorObserver {
static dynamic element;
static dynamic test;
@override
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) =>
_doCheck(route, previousRoute);
@override
void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) =>
_doCheck(newRoute, oldRoute);
@override
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) =>
_doCheck(route, previousRoute);
Future<bool> get enableCheck => Future.value(true);
void _doCheck(Route<dynamic> route, Route<dynamic> previousRoute) async {
final enableCheck = await this.enableCheck;
if (!enableCheck) return;
dynamic obj = element;
element = null;
await leakCheck(route);
}
}
import 'package:dokit/dokit.dart';
import 'package:dokit/engine/leak_obj.dart';
import 'package:dokit/kit/apm/leak/leak_obj.dart';
import 'package:dokit/kit/apm/apm.dart';
import 'package:dokit/kit/apm/fps_kit.dart';
import 'package:dokit/kit/apm/vm_helper.dart';
import 'package:dokit/kit/apm/vm/vm_helper.dart';
import 'package:dokit/kit/apm/vm/vm_service_wrapper.dart';
import 'package:dokit/kit/kit.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:vm_service/vm_service.dart';
import "package:vm_service/vm_service.dart";
class LeakKit extends ApmKit {
Expando<LeakInfo> _expando = new Expando();
......@@ -28,49 +29,27 @@ class LeakKit extends ApmKit {
@override
String getIcon() {
Uri uri=Uri.parse('http://www.baidu.com');
Uri uri = Uri.parse('http://www.baidu.com');
return 'images/dk_log_info.png';
}
static Element e;
static FpsInfo fpsInfo=new FpsInfo();
static FpsInfo fpsInfo = new FpsInfo();
@override
void start() {
Future.delayed(Duration(seconds: 1), () {
e = DoKitApp.rootKey.currentContext;
_expando[e] = new LeakInfo();
_expando[fpsInfo]=new LeakInfo();
VmHelper.instance.serviceClient
.getAllocationProfile(VmHelper.instance.main.id, gc: true);
_expando[fpsInfo] = new LeakInfo();
VMServiceWrapper.instance.gc();
Future.delayed(Duration(seconds: 1), () {
obj2Id(_expando).then((id) => printExpando(id));
});
});
}
void printExpando(String objectId) {
VmHelper.instance.serviceClient
.getObject(VmHelper.instance.main.id, objectId)
.then((value) => (value as Instance).fields.forEach((element) {
if (element.decl.name == '_data') {
VmHelper.instance.serviceClient
.getObject(VmHelper.instance.main.id, element.value.id)
.then((value) =>
(value as Instance).elements.forEach((element) {
if (element != null) {
InstanceRef ref = element as InstanceRef;
if (ref.kind == 'WeakProperty') {
VmHelper.instance.serviceClient
.getObject(VmHelper.instance.main.id, ref.id)
.then((value) =>
print((value as Instance).propertyKey));
}
}
}));
}
}));
}
void printExpando(String objectId) {}
@override
void stop() {}
......
import 'dart:ui';
import 'package:dokit/kit/apm/vm/vm_service_wrapper.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:dokit/util/util.dart';
......@@ -9,7 +10,7 @@ import 'package:vm_service/vm_service.dart';
import '../../dokit.dart';
import '../kit.dart';
import 'apm.dart';
import 'vm_helper.dart';
import 'vm/vm_helper.dart';
class MemoryInfo implements IInfo {
int fps;
......@@ -36,14 +37,16 @@ class MemoryKit extends ApmKit {
@override
void start() async {
VMServiceWrapper.instance.connect();
VmHelper vmHelper = VmHelper.instance;
await vmHelper.startConnect();
vmHelper.updateMemoryUsage();
VMServiceWrapper.instance
.connect()
.then((value) => vmHelper.resolveVMInfo());
}
void update() {
VmHelper.instance.dumpAllocationProfile();
VmHelper.instance.resolveFlutterVersion();
VmHelper.instance.updateAllocationProfile();
VmHelper.instance.updateFlutterVersion();
VmHelper.instance.updateMemoryUsage();
}
......@@ -53,7 +56,7 @@ class MemoryKit extends ApmKit {
@override
void stop() {
VmHelper.instance.disConnect();
VMServiceWrapper.instance.disConnect();
}
@override
......@@ -118,7 +121,7 @@ class MemoryPageState extends State<MemoryPage> {
fontSize: 16)),
StreamBuilder(
stream: Stream.periodic(Duration(seconds: 2), (value) {
VmHelper.instance.dumpAllocationProfile();
VmHelper.instance.updateAllocationProfile();
VmHelper.instance.updateMemoryUsage();
}),
builder: (context, snapshot) {
......
import 'dart:async';
import 'package:isolate/isolate.dart';
class IsoPool {
static final IsoPool _instance = IsoPool._internal();
factory IsoPool() {
return _instance;
}
IsoPool._internal();
LoadBalancer _loadBalance;
bool _isBalanceRunning = false;
Future<R> start<R, P>(FutureOr<R> Function(P argument) function, argument) {
return _isBalanceRunning
? _calculateMore(function, argument)
: _startBalance(function, argument);
}
Future<R> _startBalance<R, P>(
FutureOr<R> Function(P argument) function, argument) async {
if (_isBalanceRunning)
throw Exception('ISO pool is running, do not start again');
_loadBalance = await LoadBalancer.create(1, IsolateRunner.spawn);
final res = await _loadBalance.run<R, P>(function, argument);
_isBalanceRunning = true;
return res;
}
Future<R> _calculateMore<R, P>(
FutureOr<R> Function(P argument) function, argument) async {
if (!_isBalanceRunning) throw Exception('ISO pool is not running');
return await _loadBalance.run<R, P>(function, argument);
}
}
import 'dart:async';
import 'package:dokit/kit/apm/vm/iso_pool.dart';
import 'package:dokit/kit/apm/vm/version.dart';
import 'package:dokit/kit/apm/vm/vm_service_wrapper.dart';
import 'package:package_info/package_info.dart';
import 'package:vm_service/vm_service.dart';
class VmHelper {
VmHelper._privateConstructor();
static final VmHelper _instance = VmHelper._privateConstructor();
static VmHelper get instance => _instance;
// 各Isolate内存使用情况
Map<IsolateRef, MemoryUsage> memoryInfo = new Map();
AllocationProfile allocationProfile;
PackageInfo packageInfo;
// flutter版本
String _flutterVersion = '';
VM get vm => VMServiceWrapper.instance.vm;
Future<void> resolveVMInfo() async {
if (!VMServiceWrapper.instance.connected) {
return;
}
PackageInfo.fromPlatform().then((value) => packageInfo = value);
updateMemoryUsage();
updateFlutterVersion();
updateAllocationProfile();
}
String get flutterVersion {
if (!VMServiceWrapper.instance.connected) {
return 'Flutter Attach后可获取版本号';
}
if (_flutterVersion != '') {
return _flutterVersion;
} else {
return 'Flutter Attach后可获取版本号';
}
}
updateMemoryUsage() {
if (!VMServiceWrapper.instance.connected) {
return;
}
VMServiceWrapper.instance.service
.getMemoryUsage(VMServiceWrapper.instance.main.id)
.then((value) => memoryInfo[VMServiceWrapper.instance.main] = value);
}
updateFlutterVersion() {
if (!VMServiceWrapper.instance.connected) {
return;
}
VMServiceWrapper.instance.callExtensionService('flutterVersion')?.then(
(value) => _flutterVersion = FlutterVersion.parse(value.json).version);
}
updateAllocationProfile() {
if (!VMServiceWrapper.instance.connected) {
return;
}
VMServiceWrapper.instance.service
.getAllocationProfile(VMServiceWrapper.instance.main.id)
.then((value) => allocationProfile = value);
}
testIsolate() async {
Script script = await IsoPool().start(getScriptList, 'main.dart');
// Script script = await getScriptList('main.dart');
print(script?.source);
}
}
Future<Script> getScriptList(String fileName) async {
print('has connected:${VMServiceWrapper.instance.connected}');
if (!VMServiceWrapper.instance.connected) {
await VMServiceWrapper.instance.connect();
}
if (VMServiceWrapper.instance.service != null &&
VMServiceWrapper.instance.connected) {
ScriptList scriptList = await VMServiceWrapper.instance.service
.getScripts(VMServiceWrapper.instance.main.id);
ScriptRef scriptRef = scriptList?.scripts
?.firstWhere((element) => element.id.contains(fileName));
if (scriptRef != null) {
return (await VMServiceWrapper.instance.service.getObject(
VMServiceWrapper.instance.main.id, scriptRef.id)) as Script;
}
}
return null;
}
library service_tester;
import 'dart:async';
import 'dart:developer';
import 'package:dokit/kit/apm/version.dart';
import 'package:flutter/material.dart';
import 'package:package_info/package_info.dart';
import 'package:dokit/kit/apm/vm/version.dart';
import 'package:flutter/foundation.dart';
import 'package:vm_service/utils.dart';
import 'package:vm_service/vm_service.dart';
import 'package:vm_service/vm_service_io.dart';
import 'package:vm_service/utils.dart';
class VmHelper {
VmHelper._privateConstructor() {}
class VMServiceWrapper {
VMServiceWrapper._privateConstructor();
static final VmHelper _instance = VmHelper._privateConstructor();
static final VMServiceWrapper _instance =
VMServiceWrapper._privateConstructor();
static VmHelper get instance => _instance;
static VMServiceWrapper get instance => _instance;
VmService service;
VmService serviceClient;
Version _protocolVersion;
Version _dartIoVersion;
IsolateRef main;
VM vm;
// flutter版本
String _flutterVersion = '';
VM vm;
// 各Isolate内存使用情况
Map<IsolateRef, MemoryUsage> memoryInfo = new Map();
bool connected;
AllocationProfile allocationProfile;
PackageInfo packageInfo;
ExtensionService _extensionService;
bool connected = false;
Map<String, List<String>> get registeredMethodsForService =>
_registeredMethodsForService;
final Map<String, List<String>> _registeredMethodsForService = {};
Future<VmService> getService(info) async {
Uri uri = convertToWebSocketUrl(serviceProtocolUrl: info.serverUri);
return await vmServiceConnectUri(uri.toString(), log: StdoutLog());
}
startConnect() async {
Future<void> connect() async {
ServiceProtocolInfo info = await Service.getInfo();
if (info == null || info.serverUri == null) {
print("service protocol url is null,start vm service fail");
return;
}
Uri uri = convertToWebSocketUrl(serviceProtocolUrl: info.serverUri);
serviceClient = await vmServiceConnectUri(uri.toString(), log: StdoutLog());
service = await getService(info);
print('socket connected in service $info');
connected = true;
vm = await serviceClient.getVM();
vm = await service.getVM();
List<IsolateRef> isolates = vm.isolates;
main = isolates.firstWhere((ref) => ref.name.contains('main'));
main ??= isolates.first;
serviceClient
.getMemoryUsage(main.id)
.then((value) => memoryInfo[main] = value);
loadExtensionService();
PackageInfo.fromPlatform().then((value) => packageInfo = value);
getScriptList();
connected = true;
}
Future<Response> callExtensionService(String method) async {
if (_extensionService == null) {
_extensionService = new ExtensionService(service, main);
await _extensionService.loadExtensionService();
}
return _extensionService.callMethod(method);
}
gc() {
if (connected) {
service.getAllocationProfile(main.id, gc: true);
}
}
disConnect() async {
if (service != null) {
print('waiting for client to shut down...');
service.dispose();
await service.onDone;
connected = false;
service = null;
print('service client shut down');
}
}
}
class ExtensionService {
final VmService serviceClient;
final IsolateRef main;
Version _protocolVersion;
Version _dartIoVersion;
Map<String, List<String>> get registeredMethodsForService =>
_registeredMethodsForService;
final Map<String, List<String>> _registeredMethodsForService = {};
ExtensionService(this.serviceClient, this.main);
// 获取flutter版本,目前比较鸡肋,需要借助devtools向vmservice注册的服务来获取,flutter 未 attach的情况下无法使用。
void loadExtensionService() async {
Future<void> loadExtensionService() async {
final serviceStreamName = await this.serviceStreamName;
serviceClient.onEvent(serviceStreamName).listen(handleServiceEvent);
final streamIds = [
......@@ -83,15 +106,6 @@ class VmHelper {
print(e);
}
}));
resolveFlutterVersion();
}
String get flutterVersion {
if (_flutterVersion != '') {
return _flutterVersion;
} else {
return 'Flutter Attach后可获取版本号';
}
}
Future<String> get serviceStreamName async =>
......@@ -146,9 +160,6 @@ class VmHelper {
_registeredMethodsForService
.putIfAbsent(serviceName, () => [])
.add(e.method);
if (_flutterVersion == '' && serviceName == 'flutterVersion') {
resolveFlutterVersion();
}
}
if (e.kind == EventKind.kServiceUnregistered) {
......@@ -157,25 +168,6 @@ class VmHelper {
}
}
void resolveFlutterVersion() {
callMethod('flutterVersion')?.then(
(value) => _flutterVersion = FlutterVersion.parse(value.json).version);
}
void getScriptList() {
if (serviceClient != null && connected) {
serviceClient
.getScripts(main.id)
.then((value) => value.scripts?.forEach((value) {
if (value.id.contains('main.dart')) {
serviceClient
.getObject(main.id, value.id)
.then((obj) => {print((obj as Script).source)});
}
}));
}
}
Future<Response> callMethod(String method) {
if (registeredMethodsForService.containsKey(method)) {
return (serviceClient.callMethod(registeredMethodsForService[method].last,
......@@ -183,36 +175,6 @@ class VmHelper {
}
return null;
}
updateMemoryUsage() {
if (serviceClient != null && connected) {
serviceClient
.getMemoryUsage(main.id)
.then((value) => memoryInfo[main] = value);
}
}
dumpAllocationProfile() async {
if (serviceClient != null && connected) {
serviceClient
.getAllocationProfile(main.id)
.then((value) => allocationProfile = value);
}
}
disConnect() async {
if (serviceClient != null) {
print('waiting for client to shut down...');
serviceClient.dispose();
await serviceClient.onDone;
connected = false;
serviceClient = null;
print('service client shut down');
}
}
}
class StdoutLog extends Log {
......
import 'package:dokit/kit/apm/vm_helper.dart';
import 'package:dokit/kit/apm/vm/vm_helper.dart';
import 'package:dokit/kit/apm/vm/vm_service_wrapper.dart';
import 'package:dokit/kit/common/common.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:vm_service/vm_service.dart';
class BasicInfoKit extends CommonKit {
@override
......@@ -46,19 +48,7 @@ class BasicInfoPage extends StatelessWidget {
list.add(Divider(height: 0.5, color: Color(0xffeeeeee)));
list.add(InfoItem('Flutter版本', VmHelper.instance.flutterVersion));
list.add(Divider(height: 0.5, color: Color(0xffeeeeee)));
String isolate;
int index = 1;
VmHelper.instance.vm?.isolates?.forEach((element) {
if (isolate == null) {
isolate = '[isolate$index]: ${element.name} ${element.type}\n';
} else {
isolate += '[isolate$index]: ${element.name} ${element.type}\n';
}
});
if (isolate != null && isolate.length > 1) {
isolate = isolate.substring(0, isolate.length - 1);
}
list.add(InfoItem('Isolates', isolate));
list.add(IsolateItem());
list.add(Divider(height: 0.5, color: Color(0xffeeeeee)));
list.add(Container(
......@@ -78,6 +68,45 @@ class BasicInfoPage extends StatelessWidget {
}
}
class IsolateItem extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _IsolateItemState();
}
}
class _IsolateItemState extends State<IsolateItem> {
VM vm;
@override
void initState() {
super.initState();
VMServiceWrapper.instance.service.getVM().then((value) => setState(() {
vm = value;
}));
}
@override
Widget build(BuildContext context) {
String isolate;
int index = 1;
vm?.isolates?.forEach((element) {
if (isolate == null) {
isolate = '[isolate$index]: ${element.name} ${element.type}\n';
} else {
isolate += '[isolate$index]: ${element.name} ${element.type}\n';
}
index++;
});
if (isolate != null && isolate.length > 1) {
isolate = isolate.substring(0, isolate.length - 1);
}
isolate ??= '-';
return InfoItem('Isolates', isolate);
}
}
class InfoItem extends StatelessWidget {
final String label;
final String text;
......
......@@ -93,6 +93,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.16.1"
isolate:
dependency: "direct main"
description:
name: isolate
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.3"
matcher:
dependency: transitive
description:
......
......@@ -15,6 +15,7 @@ dependencies:
vm_service: ^5.5.0
package_info: ^0.4.3+2
shared_preferences: ^0.5.12+4
isolate: ^2.0.3
dev_dependencies:
flutter_test:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册