背景
影响
西瓜之前存在过一类RenderThread闪退,从堆栈上看,全部都是系统so调用,给人的第一印象像是一个系统bug,无从下手。闪退集中在Android 5~6上,表现为打开直播间立即闪退。该问题在2022年占据Native Crash Top5,2023年更是上升到到Top1。因此有必要投入时间和精力再重新审视一下这个问题。在历经多周的源码分析和排查后,逐步明确了问题根因并修复,最终取得了显著的稳定性收益和业务收益。
接下来,我们将抽丝剥茧,一步步深入分析这个历史遗留问题,揭开它背后真正的原因。
基本信息
具体堆栈如下:
堆栈都是系统的so调用,不能明确具体闪退业务场景,只能看出是RenderThread线程主动abort了。
根据abort message找到对应的abort代码,在CanvasContext::requireSurface时闪退了,代码如下:
问题特征:
问题集中在Android 5.0~6.0,线程集中在RenderThread,无明显机型、厂商特征。
RenderThread简介
为了便于理解下面的分析过程,先对RenderThread简单的介绍。顺便看一下是怎么调用到CanvasContext::requireSurface的。
相关类图如下:
相关源码:frameworks/base/libs/hwui/renderthread
RenderThread::threadLoop
RenderThread继承自Thread和Singleton,是一个单例模式的线程,通过RenderThread.getInstance()获取。和主线程很像,内部是一个通过for实现的无限循环,不断从TaskQueue里通过nextTask函数获取RenderTask并执行,RenderTask执行完后会按需调用requestVsync。核心代码在threadLoop函数中:
ThreadedRender
Java层通过ThreadedRender与RenderThread进行通信。当Window启用硬件加速时,ViewRootImpl会通过HardwareRenderer.create()创建一个ThreadedRender实例。ThreadedRender在创建时,会调用nCreateProxy在native层创建一个RenderProxy。ThreadedRender通过RenderProxy向RenderThread提交任务。
RenderProxy
RenderProxy在创建时,会同步创建一个CanvasContext,再通过RenderThread.getInstance()拿到RenderThread实例。RenderProxy通过CREATE_BRIDGE定义了许多Bridge函数,再通过SETUP_TASK把这些Bridge函数包装成RenderTask,再通过postAndWait提交给RenderThread调用。postAndWait之后,当前线程进入等待状态,当对应的task执行完毕之后唤醒当前线程。以RenderProxy::createTextureLayer为例:
CanvasContext
RenderProxy把任务提交给RenderThread之后,执行的实际上是CanvasContext::createTextureLayer,就是在这里调用了requireSurface。
初步猜想
其他 App 相似问题修复
其他端App也曾有过'requireSurface() called but no surface set! '相关闪退。原因是:在Activity进行侧滑退出时,侧滑框架需要强制对下层Activity进行绘制生成Bitmap,再用这个Bitmap来实现Activity的切换效果。但由于下层Activity此前已处于不可见状态,可能有业务层主动释放了下层Activity中的TextureView,导致了no surface set的闪退。经过对西瓜的侧滑框架的源码分析,发现不会产生此类问题,因此西瓜的问题应该另有其因。
正面分析西瓜问题
问题的条件是mEglSurface == EGL_NO_SURFACE,看下mEglSurface赋值为EGL_NO_SURFACE的时机。
总共有两处:
第一处:CanvasContext::setSurface
这里总共两处mEglSurface赋值操作。一处直接赋值为EGL_NO_SURFACE,另一处为mEglManager.createSurface的返回值。而mEglManager.createSurface在返回前判断如果是EGL_NO_SURFACE会主动abort,显然createSurface的返回值一定不是EGL_NO_SURFACE。
void CanvasContext::setSurface(ANativeWindow* window) {
if (mEglSurface != EGL_NO_SURFACE) {
mEglSurface = EGL_NO_SURFACE;
}
if (window) {//不可能返回EGL_NO_SURFACE
mEglSurface = mEglManager.createSurface(window);
}
}
EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, nullptr);
LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,"Failed to create EGLSurface for window %p, eglErr = %s",(void*) window, egl_error_str());
return surface;
}
那么这里根据window是否为nullptr又可以分为两种情况:
setSurface(nullptr)之后,mEglSurface 最终赋值为 EGL_NO_SURFACE,之后调用requireSurface发生abort。
setSurface(window),第5行会先设置为EGL_NO_SURFACE,在第10行createSurface返回之前,此时在另外一个线程调用requireSurface也会发生abort。
第二处:初始值
初始值为EGL_NO_SURFACE。只有调用CanvasContext::setSurface时,mEglSurface 才会被赋值,在此之前,调用了requireSurface也会引发闪退。
class CanvasContext : public IFrameCallback {
private:
EGLSurface mEglSurface = EGL_NO_SURFACE;
}
总结下来,有三个时机调用requireSurface会导致闪退:
多线程并发,mEglSurface短暂为EGL_NO_SURFACE
CanvasContext::setSurface(nullptr)之后,即mEglSurface被销毁
CanvasContext::setSurface之前,即mEglSurface未初始化
深入分析
7.0+系统是如何避免这个问题的?
从多维信息看出,问题在6.0及以下版本发生。那么7.0上系统做了哪些优化,是如何规避我们上面三种可能的情况的?这些优化思路对我们解决问题能否提供帮助?
对比6.0和7.0代码之后,发现谷歌直接把requireSurface这个方法移除了!
逐个翻看6.0~7.0上RenderThread相关的commit,最终找到了这个commit(8afcc769)。这里确实是把requireSurface删除了,并在createTextureLayer中调用了一下 mEglManager.initialize()。而EglManager::initialize里的实现,是执行下EglManager的初始化,这里跟6.0基本一致。

最低0.47元/天 解锁文章
2万+

被折叠的 条评论
为什么被折叠?



