熟悉Android自定义View的人大概都知道:
不要在
onDraw
里创建对象
大家在讲到这里,理由大概都是:
onDraw()被调用快速高频 -->
快速频繁申请内存 --->
频繁GC-->
线程挂起 -->
UI卡顿
当然,上面的理由重要且正确,没有什么可说的。因为它明显地写在了onDraw()
里,你一看就能联想到这些内容,所见即所得。
今天聊一个容易被忽略(或完全不知道)的隐形成本:
我们重写onDraw()
方法是为了自定义View,这时创建的对象很可能是与View绘制相关的Paint
, Path
, Typeface
等;在onDraw()
中调用Canvas API来绘制想要的图像,真实的绘制操作其实是调用了下层的C++代码(Skia
/OpenGL ES
),我们上面的提到了Paint
等对象都是封装了C++层的native对象,那么问题来了。。。
“啥?要讲C++了?”
。。。逻辑很简单
C++之所以称之为native,是因为它不会自动地管理内存(没有垃圾回收器,手动管理内存),程序需要显示地调用析构函数来销毁对象以回收内存。这个时候只能借助Java的finalize()
方法来finalizer(nativeObject)
, 而Java中的finalize()
方法被设计为被垃圾收集前调用,这意味着前面大量创建的Java对象被回收时要先执行完finalize()
方法中的代码,这导致了主线程进一步卡顿。
另外,析构函数的调用会锁native堆,这意味着什么?真正的绘制流程也会被高频阻塞,这个影响在Android 5.0之前体现在主线程,在Android5.0之后体现在RenderThread
, 进一步加重了卡顿。
看到这里,可能有的朋友要问了:
你说的挺玄乎,可我知道这个有什么用呢?
我肯定不会在onDraw()
里创建对象啊,也不会遇到你说的这个问题!
其实今天的内容是想引申一下,对于依赖的底层要有一些了解,它常常不会按照我们预期的方式来实现,之所以想写今天的内容,是因为联想到了自己在插件化实践中遇到的native缓存问题,解决起来也是煞费苦心。
reference
https://www.youtube.com/watch?v=HAK5acHQ53E&list=PLq_uWB0WqY0tVIrFZ41Wr5BZc9B_wf-XW&index=26