设计者其实也想到了这个问题,因此额外提供了一个 SurfaceHolder 的 API 接口,通过该接口,开发者可以直接拿到独立绘图表面的 Canvas 对象,以及对这个画布进行绘制操作:
// /frameworks/base/core/java/android/view/SurfaceHolder.java
public interface SurfaceHolder {
// ...
public Canvas lockCanvas();
public void unlockCanvasAndPost(Canvas canvas);
//...
}
1
2
3
4
5
6
7
8
遗憾的是,即使拿到 Canvas,开发者仍然会受到限制:
// /frameworks/base/core/java/com/android/internal/view/BaseSurfaceHolder.java
public abstract class BaseSurfaceHolder implements SurfaceHolder {
private final Canvas internalLockCanvas(Rect dirty, boolean hardware) {
if (mType == SURFACE_TYPE_PUSH_BUFFERS) {
throw new BadSurfaceTypeException("Surface type is SURFACE_TYPE_PUSH_BUFFERS");
}
// ...
}
}
1
2
3
4
5
6
7
8
9
10
这里的代码,笔者引用 罗升阳 的 这篇文章 中的一段来解释:
注意,只有在一个 SurfaceView 的绘图表面的类型不是 SURFACE_TYPE_PUSH_BUFFERS 的时候,我们才可以自由地在上面绘制 UI。我们使用 SurfaceView 来显示摄像头预览或者播放视频时,一般就是会将它的绘图表面的类型设置为 SURFACE_TYPE_PUSH_BUFFERS 。在这种情况下,SurfaceView 的绘图表面所使用的图形缓冲区是完全由摄像头服务或者视频播放服务来提供的,因此,我们就不可以随意地去访问该图形缓冲区,而是要由摄像头服务或者视频播放服务来访问,因为该图形缓冲区有可能是在专门的硬件里面分配的。
由此可见,SurfaceView 黑屏问题的原因是综合且复杂的,无论是通过 setZOrderOnTop() 等方法设置为背景透明(但是会在页面层级的最上方),亦或者调整布局参数,都会有大大小小的一些问题。
小结
综合来看,SurfaceView 这些饱受争议的问题,从设计的角度来看,都是有其自身考量的。
而为了解决这些问题,官方后续提供了 TextureView 以替换 SurfaceView,TextureView 的原理是和 View 一样绘制到当前 Activity 的窗口上,因此不存在 SurfaceView 的这些问题。
换个角度来看,由于 TextureView 渲染依赖于主线程,因此也会导致了新的问题出现。除了性能比较 SurfaceView 会有明显下降外,还会有经常掉帧的问题,有机会笔者会另起一篇进行分享。
————————————————