4. 黑屏问题根因分析:Surface创建失败、权限拒绝、Activity生命周期异常
黑屏问题,说白了就是用户看到了一块黑屏,但手机其实没死。系统还在跑,触摸可能还有反应,就是画面出不来。我遇到过不少这类问题,今天咱们就聊聊三个最常见的根因:Surface创建失败、权限拒绝、Activity生命周期异常。
4.1 Surface创建失败:画面没了“画布”
Surface是什么?你可以把它理解成一块画布。App要显示画面,得先拿到这块画布,然后在上面画东西。如果画布创建失败,那画面自然就是黑的。
4.1.1 为什么会创建失败?
我个人习惯,遇到黑屏先看logcat。搜索关键词 Surface、SurfaceControl、BufferQueue。常见的失败原因有这几个:
- BufferQueue耗尽:每个Surface背后都有一个BufferQueue。如果App创建了太多Surface,或者某个Surface一直不释放buffer,队列就满了。新的Surface就创建不出来。
- 内存不足:Surface需要分配图形缓冲区。系统内存或显存不够时,分配会失败。
- 硬件限制:某些设备对Surface的尺寸、格式有限制。比如最大支持4K,你非要搞个8K的Surface,那肯定失败。
关键log:看到 allocateBuffers failed 或 dequeueBuffer failed,基本就是BufferQueue或内存的问题。
4.1.2 实战排查思路
我记得有一次,一个视频App在播放4K视频时黑屏。查log发现 SurfaceControl: Failed to create surface。进一步看,是App在快速切换视频时,旧Surface没及时释放,新Surface又不断创建,把BufferQueue撑爆了。
排查步骤我建议这样:
- 抓取dumpsys:执行
adb shell dumpsys SurfaceFlinger,查看所有Surface的状态。重点关注BufferQueue的totalBuffers和activeBuffers。 - 检查内存:
adb shell dumpsys meminfo,看图形内存(Graphics)是否异常高。 - 复现问题:在问题复现时,立刻抓取logcat,过滤
Surface和BufferQueue。
小技巧:可以用 adb shell dumpsys window 查看当前窗口的Surface信息。如果某个窗口的 Surface 状态是 null,那基本就是创建失败了。
4.2 权限拒绝:系统不让你画
有时候Surface创建成功了,但画面还是黑的。为什么?因为系统不让你画。权限拒绝,说白了就是App没有拿到显示所需的权限。
4.2.1 常见的权限问题
| 权限类型 | 表现 | 典型log |
|---|---|---|
SYSTEM_ALERT_WINDOW |
悬浮窗黑屏 | Permission Denial: requires android.permission.SYSTEM_ALERT_WINDOW |
INTERACT_ACROSS_USERS |
多用户场景黑屏 | Permission Denial: requires android.permission.INTERACT_ACROSS_USERS |
BIND_ACCESSIBILITY_SERVICE |
无障碍服务黑屏 | Permission Denial: requires android.permission.BIND_ACCESSIBILITY_SERVICE |
嗯,这里要注意。权限拒绝导致的黑屏,往往不是App主动请求的权限,而是系统服务在创建Surface时检查的权限。比如,一个App想在其他App上层显示悬浮窗,就必须有 SYSTEM_ALERT_WINDOW 权限。如果没有,系统直接拒绝创建Surface。
4.2.2 我曾经踩过的坑
我曾经处理过一个case:一个Launcher在启动时偶尔黑屏。查了三天,最后发现是 WindowManager 在添加窗口时,检查了 INTERACT_ACROSS_USERS 权限。这个权限在普通App里是不应该有的。但那个Launcher被系统签名了,反而因为签名冲突导致权限检查失败。
你想想看,权限拒绝的log有时候藏得很深。它不会直接说“黑屏”,而是说“Permission Denial”。所以排查时,一定要把logcat的权限过滤打开,搜索 Permission 和 Denial。
避坑指南:我曾经见过一个App,在Android 12上黑屏,在Android 11上正常。原因是Android 12对 SYSTEM_ALERT_WINDOW 权限的授予方式变了。以前可以静态声明,现在必须动态申请。所以,权限问题一定要结合系统版本看。
4.3 Activity生命周期异常:画面“断片”了
Surface创建好了,权限也给了,但画面还是黑的。那问题很可能出在Activity的生命周期上。说白了,就是Activity在错误的时机做了错误的事。
4.3.1 生命周期与Surface的关系
Activity的生命周期和Surface的创建/销毁是紧密绑定的。我画个简化的流程:
onCreate→ Activity开始创建,但此时Surface还没准备好。onResume→ Activity可见,Surface已经创建好了。onPause→ Activity部分可见,Surface还在。onStop→ Activity不可见,Surface可能被销毁。
如果App在 onCreate 里就尝试获取Surface并绘制,那肯定拿不到。因为Surface还没创建。同样,如果在 onStop 之后还持有Surface引用,那Surface可能已经被系统回收了,绘制上去也没用。
4.3.2 典型的生命周期异常场景
场景一:SurfaceView在onCreate里初始化
很多新手会这么写:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SurfaceView sv = findViewById(R.id.surfaceView);
SurfaceHolder holder = sv.getHolder();
// 这里获取Surface,但可能为null
Surface surface = holder.getSurface();
// 直接绘制...
}
这样写,holder.getSurface() 很可能返回null。因为SurfaceView的Surface是在 onResume 之后才真正创建的。正确的做法是在 SurfaceHolder.Callback 的 surfaceCreated 回调里获取Surface。
场景二:Activity被销毁但Surface没释放
我记得有个游戏App,在快速切换Activity时黑屏。原因是Activity的 onDestroy 被调用了,但游戏引擎还在后台线程持有Surface引用,继续往上面绘制。系统发现Surface的宿主Activity已经死了,就直接把Surface回收了。结果就是黑屏。
关键点:Surface的生命周期必须和Activity的生命周期同步。Activity销毁时,一定要确保所有对Surface的引用都释放了。
4.3.3 排查方法
我建议用 adb shell dumpsys activity activities 查看Activity的状态。重点关注:
mState:当前生命周期状态(RESUMED、PAUSED、STOPPED等)mSurface:Surface是否有效mWindowAdded:窗口是否已添加
如果看到 mState=STOPPED 但 mSurface 还是非空,那就要小心了。说明Surface可能没有被正确销毁。
个人经验:我习惯在Activity的 onPause 和 onResume 里加log,打印Surface的hashCode。这样能清楚看到Surface是否被重建了。如果两次log的hashCode一样,说明Surface被复用了;如果不一样,说明被重建了。如果 onResume 里Surface是null,那问题就大了。
4.4 三者交织的复杂场景
实际项目中,黑屏问题往往不是单一原因导致的。可能是Surface创建失败 + 生命周期异常的组合。比如:
- App在
onCreate里尝试创建Surface,失败。 - App没有处理失败,继续往下走。
- 到了
onResume,系统尝试重新创建Surface,但因为BufferQueue还锁着,再次失败。 - App陷入死循环,一直黑屏。
这种问题,光看log很难定位。我建议用 systrace 抓取整个流程,看SurfaceFlinger、App进程、BufferQueue之间的交互。重点关注 SurfaceFlinger 的 onTransaction 和 onMessageInvalidate。
避坑指南:我曾经遇到一个case,App在低内存设备上频繁黑屏。查了两个月,最后发现是 SurfaceFlinger 在内存压力下,主动销毁了某些Surface。但App不知道,还在往已销毁的Surface上绘制。解决方案是在App里监听 ComponentCallbacks2 的 onTrimMemory,在内存紧张时暂停绘制。
4.5 总结一下
黑屏问题的根因,说白了就这三类:
- Surface创建失败:查BufferQueue、内存、硬件限制。
- 权限拒绝:查logcat里的Permission Denial,注意系统版本差异。
- Activity生命周期异常:确保Surface的创建和销毁与生命周期同步。
嗯,最后说一句。排查黑屏问题,耐心比技术更重要。有时候一个log就能定位,有时候要抓几十次systrace。我个人的习惯是,先看logcat,再看dumpsys,最后上systrace。一步步来,总能找到根因。