【Android渲染】在onDraw()中创建对象的隐形成本

829 阅读2分钟

熟悉Android自定义View的人大概都知道:

不要在onDraw里创建对象

大家在讲到这里,理由大概都是:

onDraw()被调用快速高频 --> 快速频繁申请内存 ---> 频繁GC--> 线程挂起 --> UI卡顿

当然,上面的理由重要且正确,没有什么可说的。因为它明显地写在了onDraw()里,你一看就能联想到这些内容,所见即所得。

今天聊一个容易被忽略(或完全不知道)的隐形成本:

All that is bad enough, but it's getting worse.

我们重写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

www.youtube.com/watch?v=HAK…