2分钟教你Flutter怎么避免引用内存泄漏
- 内存泄漏原因
- 1. 在当前类,或者方法等移除改引用,让其他自动释放,等下一轮GC扫描释放。如
- 2. 使用弱引用-----**WeakReference**,当前使用完,生命周期结束后,自动释放。
- 使用vm_service插件触发GC
内存泄漏原因
所有语言的内存泄漏无非就是GC Root(线程,全局或静态数组或引用等)的引用链持有了需要释放的引用,导致无法释放。
解决释放的方法,无非就是2种,如下:
1. 在当前类,或者方法等移除改引用,让其他自动释放,等下一轮GC扫描释放。如
class B{void doSomething(){}
}class A{B? b;
}
A a = A();
void test(){a.b = B();a.b?.doSomething();
}void dispose(){a.b = null;
}void main() {test();dispose();
}
A 这时候时全局的,如果使用完B后,想要B自动释放,只需要调用dispose() 将B置null就好。
然后,现实就是有可能,忘记调用dispose().导致B无法释放。怎么办呢?对,你猜到了,就是弱引用,Dart也有!
来了来了就是那个闪亮的仔, WeakReference(和Java的弱引用名字一样哦)
2. 使用弱引用-----WeakReference,当前使用完,生命周期结束后,自动释放。
在上面代码,B的生命周期,仅仅是在test这个方法里面,所以执行test后B就不需要用了,那么就让他自动等GC移除.
让我们看看他的源码
/// A weak reference to a Dart object.
///
/// A _weak_ reference to the [target] object which may be cleared
/// (set to reference `null` instead) at any time
/// when there is no other way for the program to access the target object.
///
/// _Being the target of a weak reference does not keep an object
/// from being garbage collected._
///
/// There are no guarantees that a weak reference will ever be cleared
/// even if all references to its target are weak references.
///
/// Not all objects are supported as targets for weak references.
/// The [WeakReference] constructor will reject any object that is not
/// supported as an [Expando] key.
///
/// Use-cases like caching can benefit from using weak references. Example:
///
/// ```dart
/// /// [CachedComputation] caches the computation result, weakly holding
/// /// on to the cache.
/// ///
/// /// If nothing else in the program is holding on the result, and the
/// /// garbage collector runs, the cache is purged, freeing the memory.
/// ///
/// /// Until the cache is purged, the computation will not run again on
/// /// a subsequent request.
/// ///
/// /// Example use:
/// /// ```
/// /// final cached = CachedComputation(
/// /// () => jsonDecode(someJsonSource) as Object);
/// /// print(cached.result); // Executes computation.
/// /// print(cached.result); // Most likely uses cache.
/// /// ```
/// class CachedComputation<R extends Object> {
/// final R Function() computation;
///
/// WeakReference<R>? _cache;
///
/// CachedComputation(this.computation);
///
/// R get result {
/// final cachedResult = _cache?.target;
/// if (cachedResult != null) {
/// return cachedResult;
/// }
///
/// final result = computation();
///
/// // WeakReferences do not support nulls, bools, numbers, and strings.
/// if (result is! bool && result is! num && result is! String) {
/// _cache = WeakReference(result);
/// }
///
/// return result;
/// }
/// }
/// ```
("2.17")
abstract class WeakReference<T extends Object> {/// Creates a [WeakReference] pointing to the given [target].////// The [target] must be an object supported as an [Expando] key,/// which means [target] cannot be a number, a string, a boolean, a record,/// the `null` value, or certain other types of special objects.external factory WeakReference(T target);/// The current object weakly referenced by [this], if any.////// The value is either the object supplied in the constructor,/// or `null` if the weak reference has been cleared.T? get target;
}
好家伙,上面就用例子叫我们怎么使用,属性target就是获取当前需要的引用。让我们改改上面
class B{void doSomething(){}
}class A{WeakReference<B>? b;
}
A a = A();
void test(){a.b = WeakReference(B());a.b?.target?.doSomething();
}void dispose(){a.b = null;
}void main() {test();print(a.b?.target);
}
让我们执行下,看看结果
控制台:
Instance of ‘B’
??? ,有同学就问,怎么还持有,怎么还不释放。
因为当前没触发GC垃圾回收器,所以没必要进行回收。看过我这篇文章Android内存抖动
的同学知道,因为频繁回收会出现内存抖动现象,从而导致App掉帧甚至卡顿现象,GC垃圾回收器在这做了优化。那如果真的想测试验证这个结果,怎么办?
可以2种方案,第一种,老套路啦,强制制造大量无效垃圾引用,让GC触发回收。
如:制造大量的引用,并且需要释放的引用
void main() {test();List.generate(10000, (index) {ByteData byteData = ByteData(10000);});print(a.b?.target);
}
让我们执行下,看看结果
控制台:
null
当当当~,回收了吧
使用vm_service插件触发GC
还有一种办法,就是调用vm_service插件提供的VmService进行回收。注意一点是,这个GC做了优化,回收是单独对线程进行回收的,所以需要筛选对应的线程进行回收,如我们的线程基本是在UI线程也就是main线程中进行的,那么我们就筛选main
class VmServiceHelper {bool debug = false;VmServiceHelper() {assert(() {debug = true;return true;}());}VmService? _vmService;VM? _vm;Uri? _observatoryUri;Future<VmService?> getVmService() async {if (_vmService == null && debug) {ServiceProtocolInfo serviceProtocolInfo = await Service.getInfo();_observatoryUri = serviceProtocolInfo.serverUri;if (_observatoryUri != null) {Uri url = convertToWebSocketUrl(serviceProtocolUrl: _observatoryUri!);try {_vmService = await vmServiceConnectUri(url.toString());} catch (error, stack) {print(stack);}}}return _vmService;}Future try2GC() async {if (!debug) {return;}final vms = await getVmService();if (vms == null) return null;final isolate = await getMainIsolate();if (isolate?.id != null) {await vms.getAllocationProfile(isolate!.id!, gc: true);}}Future<Isolate?> getMainIsolate() async {if (!debug) {return null;}IsolateRef? ref;final vm = await getVM();if (vm == null) return null;var index = vm.isolates?.indexWhere((element) => element.name == 'main');if (index != -1) {ref = vm.isolates![index!];}final vms = await getVmService();if (ref?.id != null) {return vms?.getIsolate(ref!.id!);}return null;}Future<VM?> getVM() async {if (!debug) {return null;}return _vm ??= await (await getVmService())?.getVM();}
}
在使用之前,我们需要在运行配置上面加入
VsCode
{..."args": ["--disable-dds"],"type": "dart"},
Android Studio
然后我们使用VmServiceHelper().try2GC(),代替上面的语句如下。但是需要运行app来测试。
test();
await VmServiceHelper().try2GC();
print(a.b?.target);
好了,如果本次文章觉得对你有用,请点赞,收藏,关注3连谢谢。
需要VmServiceHelper这个的朋友,可以到 flutter_leak_canary: ^1.0.1这个上面下载哦,如果顺手,给我点个Like哟