着重说下 Element Tree:
-
三棵树的构建是通过 Element 的 mount / unmount 方法构建
-
父子 Element 相互强引用, 所以 Element 泄漏会导致整棵 Element Tree 泄漏,连同强引用住对应的 Widget Tree, RenderObject Tree 一起泄漏,相当可观
-
Element 中强引用到 Widget, RenderObject 的 field 不会主动置为 null,所以三棵树的释放依赖 Element 被 GC 回收
Widget Tree 表示被引用的 Widget,例如引用 Image 的 RawImage Widget。
RenderObject Tree 会生成 Layer Tree,并且会强引用 ui.EngineLayer(c++ 分配内存),所以 Layer 相关的渲染内存会被这棵树持有。
综合上述,BuildContext 引用住了 Flutter 中的 3 棵树。因此:
-
BuildContext 引用的内存占用大,满足条件 1
-
BuildContext 在业务代码中使用频繁,作为参数传递等,泄漏风险高,满足条件 3
怎么监控 BuildContext
BuildContext 的泄漏是否可以完备定义?
从 Element 的生命周期看:
重点需要确定什么时候 Element 会被 Element Tree 丢弃,并且不会再使用,会被随后来的 GC 回收掉。
finalizeTree 处理代码如下:
// flutter_sdk/packages/flutter/lib/src/rendering/binding.dart
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void drawFrame() {
…
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
// 每一帧最后回收从 Element 树中移除的 Element
buildOwner.finalizeTree();
} finally {
}
}
}
// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart
class BuildOwner {
…
void finalizeTree() {
try {
// _inactiveElements 中记录不再使用的 Element
lockState(() {
_inactiveElements._unmountAll(); // this unregisters the GlobalKeys
});
} catch() {
}
}
…
}
// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart
class _InactiveElements {
…
void _unmountAll() {
_locked = true;
// 将 Element 拷贝到临时变量 elements 中
final List elements = _elements.toList()…sort(Element._sort);
// 清空 _elements,当前方法执行完,elements 也会被回收,则全部 Element 正常情况下都会被 GC 回收。
_elements.clear();
try {
elements.reversed.forEach(_unmount);
} finally {
assert(_elements.isEmpty);
_locked = false;
}
}
…
}
复制代码
finalize 阶段 _inactiveElements 中保存了被 Element Tree 丢弃,并且不会再使用的 Element;在执行完 unmount 方法后,即等待被 GC 回收。
因此 Element 泄漏可定义为:执行完 umount,并且 GC 后,仍存在这些 Element 的引用,则说明 Element 发生内存泄漏。满足条件 2。
内存泄漏检测工具
工具描述
我们对内存泄漏工具有 2 点要求:
-
准确。包括核心对象泄漏检测:image, layer,state,能够解决 Flutter 90% 以上对内存泄漏问题
-
高效。业务无感,自动化检测,优化引用链,快速定位到泄漏源
准确
从上文描述,BuildContext 毫无疑问是最有可能导致大内存泄漏的对象,是作为监控对象的最佳对象。为了提高准确度,我们也把最常用的 State 对象监控起来。
为什么要添加 State 对象的监控呢?
因为业务逻辑控制实现在 State 中,业务中实现的“闭包或者方法”传递很容易导致 State 泄漏。例子如下。
class MainApp extends StatefulWidget {
@override
State createState() {
return _MainAppState();
}
}
class _MainAppState extends State {
@override
void initState() {
super.initState();
// 注册这个回调,这个回调如果没有被反注册或者被其他上下文持有,都会导致 _MainAppState 泄漏。
xxxxManager.addListerner(handleAction);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
);
}
// 1个回调
void handleAction() {
…
}
}
复制代码
State 关联哪些内存会被泄漏?
结合以下代码看,泄漏肯定会导致关联的 Widget 泄漏,而 Widget 关联的内存如果是一张的 Image 或者 gif 的话,泄漏的内存也会很大。同时,State 中可能还以关联其他的一些强引用住的内存。
// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart
abstract class State with Diagnosticable {
// 强引用对应的 Widget 泄漏
T _widget;
// unmount 时候,_element = null, 不会导致泄漏
StatefulElement _element;
…
}
// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart
class StatefulElement extends ComponentElement {
…
@override
void unmount() {
…
_state.dispose();
_state._element = null;
// 其他地方持有,则导致泄漏。unmount 后 State 仍被持有,可作为一个泄漏定义。
_state = null;
}
…
}
复制代码
所以,我们方案将关联大内存的 BuildContext,业务常操作的 State 一并监控起来,提高整套方案的准确度。
高效
怎么实现自动化高效的内存泄漏检测?
首先我们要怎么明确一个对象是否发生泄漏?以 BuildContext 为例,我们采取类似“Java 对象弱引用”判定对象泄漏的方式:
-
将 finalizeTree 阶段的 inactiveElements 放到 weak Reference map 中
-
Full GC 后检测 weak Reference map ,如果其中仍持有未释放的 Element,则判定为发生泄漏
-
将泄漏的 Element 关联的 size,对应的 Widget,泄漏引用链信息输出
虽然 Dart 没有直接提供“弱引用”检测能力,但我们 Hummer 引擎从底层将“弱引用泄漏检测”功能完整实现了,这里简单介绍它判定泄漏的接口:
// 添加需要检测泄漏的对象,类似将对象放到若引用map中
external void leakAdd(Object suspect, {
String tag: ‘’,
});
// 检测之前放入的对象是否发生了泄漏,会进行 FullGc
external void leakCheck({
Object? callback,
String tag: ‘’,
bool clear: true,
});
external void leakClear({
String tag: ‘’,
});
external String leakCount();
external List leakTags();
复制代码
因此,要实现自动化检测,我们只需要明确 leakAdd(),leakCheck() 调用的时机即可。
leakAdd 时机
BuildContext 的时机在 finalizeTree 的 unmount 流程中:
// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart
class _InactiveElements {
…
void _unmount(Element element) {
element.visitChildren((Element child) {
assert(child._parent == element);
_unmount(child);
});
// BuildContext 泄漏 leakAdd() 时机
if (!kReleaseMode && debugMemoryLeakCheckEnabled && null != debugLeakAddCallback) {
debugLeakAddCallback(_state);
}
element.unmount();
…
}
…
}
复制代码
State 的时机在对应的 StatefulElement 的 unmount 流程中:
// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart
class StatefulElement extends ComponentElement {
@override
void unmount() {
_state.dispose();
_state._element = null;
// State 泄漏 leakAdd() 时机
if (!kReleaseMode && debugMemoryLeakCheckEnabled && null != debugLeakAddCallback) {
debugLeakAddCallback(_state);
}
_state = null;
}
}
复制代码
leakCheck 时机
leakCheck 本质上是一个检测是否存在泄漏的时机点,我们认为 Page 退出是个合适的时机,以业务 Page 为单位进行内存泄漏检测。示例代码如下:
// flutter_sdk/packages/flutter/lib/src/widgets/navigator.dart
abstract class Route {
_navigator = null;
// BuilContext, State leakCheck时机
if (!kReleaseMode && debugMemoryLeakCheckEnabled && null != debugLeakCheckCallback) {
debugLeakCheckCallback();
}
}
复制代码
工具实现
以 Page 为单位的自动化内存泄漏,根据使用场景,提供三种内存泄漏检测工具。
-
Hummer 引擎深度定制的 DevTools 资源面板展示,可以自动/手动触发内存泄漏检测
-
独立 APP 端内存泄漏展示,在 Page 发生泄漏时候,弹出泄漏对象详情
-
Hummer 引擎海鸥实验室自动化检测,自动化将内存泄漏详情以报告给出
工具 1、2 提供开发过程的内存泄漏检测能力,工具 3 可作为 APP 常规健康测试,自动化测试并输出检测报告结果。
异常检测实例
在 Demo 中模拟 StatelessWidget, StatefulWidget 被 BuildContext 持有导致的泄漏。泄漏的原因是被静态持有,Timer 异常持有。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。
最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
【算法合集】
【延伸Android必备知识点】
【Android部分高级架构视频学习资源】
**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
[外链图片转存中…(img-2iBfUy75-1712543160378)]
【算法合集】
[外链图片转存中…(img-hRM3RXhr-1712543160378)]
【延伸Android必备知识点】
[外链图片转存中…(img-u4pktrik-1712543160379)]
【Android部分高级架构视频学习资源】
**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!