深入解析Android Surface同步机制及其在窗口绘制中的关键作用

张开发
2026/4/17 21:37:12 15 分钟阅读

分享文章

深入解析Android Surface同步机制及其在窗口绘制中的关键作用
1. Android Surface同步机制概述在Android系统中Surface是图形系统中一个非常重要的概念。简单来说Surface就是一块内存区域专门用来存储图形数据。想象一下Surface就像是一块画布应用程序可以在上面绘制各种内容然后系统会把这些内容显示到屏幕上。Surface同步机制是Android 13引入的新特性它的主要作用是协调不同Surface之间的绘制过程。在实际开发中我们经常会遇到这样的场景一个窗口可能包含多个Surface比如主Surface和SurfaceView的Surface如果这些Surface的绘制进度不一致就可能导致显示问题。最常见的就是黑屏问题——主Surface已经绘制完成并显示出来了但SurfaceView的绘制还没完成结果用户看到的就是部分黑屏的界面。这个机制的核心代码最初位于SurfaceSyncer.java中但在Android 14中相关代码被重构并移动到了SurfaceSyncGroup.java。这种变化反映了Google对同步机制的持续优化和改进。2. Surface同步机制的工作原理2.1 同步机制的触发时机同步过程始于ViewRootImpl的performTraversals()方法。这个方法可以看作是Android视图系统的心脏负责协调整个视图树的测量、布局和绘制流程。当需要进行绘制时ViewRootImpl会先检查是否需要创建同步private void createSyncIfNeeded() { if (isInLocalSync() || !mReportNextDraw) { return; } final int seqId mSyncSeqId; mSyncId mSurfaceSyncer.setupSync(transaction - { mHandler.postAtFrontOfQueue(() - { mSurfaceChangedTransaction.merge(transaction); reportDrawFinished(seqId); }); }); mSurfaceSyncer.addToSync(mSyncId, mSyncTarget); notifySurfaceSyncStarted(); }这段代码做了几件重要的事情检查是否已经在同步过程中设置同步完成后的回调将当前ViewRootImpl添加为同步目标通知相关的SurfaceView开始同步2.2 SurfaceView的同步过程SurfaceView作为特殊的视图它有自己的Surface和独立的绘制流程。当ViewRootImpl调用dispatchOnPreDraw()时会触发SurfaceView的onPreDraw回调private final ViewTreeObserver.OnPreDrawListener mDrawListener () - { mHaveFrame getWidth() 0 getHeight() 0; updateSurface(); return true; };在updateSurface()方法中SurfaceView会根据情况选择不同的同步策略。当不需要缓冲同步时会调用handleSyncNoBuffer()方法private void handleSyncNoBuffer(SurfaceHolder.Callback[] callbacks) { final int syncId mSurfaceSyncer.setupSync(this::onDrawFinished); mSurfaceSyncer.addToSync(syncId, syncBufferCallback - redrawNeededAsync(callbacks, () - { syncBufferCallback.onBufferReady(null); synchronized (mSyncIds) { mSyncIds.remove(syncId); } })); mSurfaceSyncer.markSyncReady(syncId); synchronized (mSyncIds) { mSyncIds.add(syncId); } }这个过程建立了SurfaceView自己的同步组并设置了绘制完成的回调。值得注意的是SurfaceView会立即标记同步就绪(markSyncReady)因为它不需要等待缓冲区。3. 同步合并的关键流程3.1 同步组的合并当ViewRootImpl和SurfaceView都建立了自己的同步组后接下来就是关键的合并过程。这个过程由ViewRootImpl通过notifySurfaceSyncStarted()发起private void notifySurfaceSyncStarted() { for (int i 0; i mSurfaceChangedCallbacks.size(); i) { mSurfaceChangedCallbacks.get(i).surfaceSyncStarted(); } }SurfaceView收到通知后会将自己的同步组与ViewRootImpl的同步组合并public void surfaceSyncStarted() { ViewRootImpl viewRoot getViewRootImpl(); if (viewRoot ! null) { synchronized (mSyncIds) { for (int syncId : mSyncIds) { viewRoot.mergeSync(syncId, mSurfaceSyncer); } } } }最终的合并操作发生在SurfaceSyncer的merge()方法中public void merge(int syncId, int otherSyncId, SurfaceSyncer otherSurfaceSyncer) { SyncSet syncSet mSyncSets.get(syncId); SyncSet otherSyncSet otherSurfaceSyncer.getAndValidateSyncSet(otherSyncId); if (otherSyncSet null) { return; } syncSet.merge(otherSyncSet); }合并的本质是将SurfaceView的SyncSet添加到ViewRootImpl的SyncSet中这样ViewRootImpl就能感知到SurfaceView的同步状态。3.2 同步完成的条件检查同步是否完成的检查逻辑集中在checkIfSyncIsComplete()方法中private void checkIfSyncIsComplete() { if (!mSyncReady || !mPendingSyncs.isEmpty() || !mMergedSyncSets.isEmpty()) { if (DEBUG) { Log.d(TAG, Syncable is not complete. mSyncReady mSyncReady mPendingSyncs mPendingSyncs.size() mergedSyncs mMergedSyncSets.size()); } return; } for (SyncTarget syncTarget : mSyncTargets) { syncTarget.onSyncComplete(); } mSyncTargets.clear(); mSyncRequestCompleteCallback.accept(mTransaction); mFinished true; }这个方法检查三个关键条件mSyncReady标记是否已经准备好同步mPendingSyncs待处理的同步目标列表mMergedSyncSets合并的其他同步组只有当这三个条件都满足时同步才算真正完成。4. 同步机制的实际应用与优化4.1 解决黑屏问题的原理Surface同步机制最直接的价值就是解决了窗口切换时的黑屏问题。在没有同步机制之前窗口切换的流程是这样的新窗口开始绘制主Surface绘制完成通知WMSWMS移除启动窗口(startingWindow)SurfaceView还在绘制中结果用户看到黑屏引入同步机制后流程变成了新窗口开始绘制主Surface和SurfaceView都加入同步组只有当两者都绘制完成才会通知WMSWMS移除启动窗口结果用户看到完整的窗口内容这种改变虽然增加了些许复杂性但显著提升了用户体验。4.2 性能考量与优化建议在实际使用Surface同步机制时有几个性能优化的关键点合理设置同步范围不是所有SurfaceView都需要参与同步。对于简单的、绘制快速的SurfaceView可以跳过同步以减少开销。注意同步超时虽然同步机制能避免黑屏但如果某个SurfaceView绘制时间过长可能会导致ANR。应该监控同步耗时必要时添加超时机制。减少同步层级避免嵌套过多的同步组这会增加检查的复杂度。尽量保持同步关系扁平化。合理使用markSyncReady对于立即就绪的Surface应该尽早调用markSyncReady而不是等到实际绘制完成。事务合并优化同步过程中会产生多个Transaction应该合理合并它们以减少IPC次数。// 优化后的同步设置示例 mSurfaceSyncer.setupSync(transaction - { // 合并所有pending的transaction mergePendingTransactions(transaction); // 确保在主线程执行最终操作 mHandler.post(() - { applyMergedTransaction(transaction); reportDrawFinished(); }); });4.3 调试技巧与常见问题在调试Surface同步问题时以下几个技巧可能会帮到你启用调试日志设置DEBUG标志可以输出详细的同步流程日志帮助理解同步状态变化。检查同步三要素当同步卡住时分别检查mSyncReady、mPendingSyncs和mMergedSyncSets的状态找出是哪个条件不满足。注意线程问题同步回调可能在不同线程执行确保线程安全的操作特别是对共享状态的修改。监控绘制性能使用Systrace或Perfetto工具监控绘制耗时找出性能瓶颈。处理异常情况考虑同步过程中可能发生的异常如Surface被销毁等添加适当的错误处理。一个常见的坑是忘记调用markSyncReady()这会导致同步永远无法完成。我曾经遇到过这样的问题SurfaceView的内容已经显示出来了但窗口状态没有更新最后发现是因为漏掉了markSyncReady调用。

更多文章