我们来通俗易懂地解释一下 Android 中的 TextureView。你可以把它想象成一个更灵活、能更好融入普通视图体系的“画板” ,专门用来显示动态内容(比如视频、相机预览、OpenGL ES 渲染的 3D 场景等)。
一、它解决了什么问题?(为什么需要它?)
想象一下你要在 App 里播放一个视频:
-
最普通的 View (如
ImageView): 只能显示静态图片,无法流畅播放连续变化的视频帧。 -
SurfaceView: 这是 Android 早期提供的方案。它像一个挖在墙上的洞(Window),后面有一个独立的绘图表面(Surface)。优点是性能好(直接在专用缓冲区绘制,不依赖 UI 线程),缺点是:- “洞”的特性: 它不能很好地与其他普通 View 叠加(动画、透明度、旋转等效果受限),因为它总是在最顶层(或者被其他 View 遮挡)。
- 渲染时机不同步: 它的绘制发生在独立的线程/缓冲区,与普通 View 的绘制流程不同步,导致在其上做动画或与其他 View 组合时可能不流畅或出现撕裂感。
TextureView 就是为了解决 SurfaceView 的这些缺点而生的!
二、TextureView 是什么?
- 本质: 它是一个继承了
View的普通控件。这意味着它可以像Button、TextView、ImageView一样被自由地添加到布局中,应用各种动画(平移、缩放、旋转、透明度)、设置层级(Z轴)、与其他 View 叠加组合,完美融入 Android 的标准视图层级和绘制流程。 - 核心能力: 它内部封装了一个
SurfaceTexture。SurfaceTexture是连接图像数据生产者(如MediaPlayer,Camera2,OpenGL ES)和 OpenGL ES 纹理 的关键桥梁。TextureView则负责将这个 OpenGL ES 纹理的内容最终绘制到屏幕上。 - 关键点:
TextureView不直接绘制内容!它只是提供了一个表面(通过SurfaceTexture关联的Surface)让生产者(如视频解码器、相机)把图像帧画上去,然后TextureView负责把这个图像帧当作一个纹理(可以想象成一张图片),用 OpenGL ES 把自己整个区域“贴”上这张纹理,最终显示出来。
三、如何使用 TextureView?(步骤详解)
使用 TextureView 的核心步骤围绕着监听它的可用性和获取它的绘图表面:
-
在布局 XML 中添加
TextureView:<TextureView android:id="@+id/my_texture_view" android:layout_width="match_parent" android:layout_height="match_parent" /> -
在 Activity/Fragment 中获取引用并设置监听器:
public class MyActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener { private TextureView mTextureView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextureView = findViewById(R.id.my_texture_view); mTextureView.setSurfaceTextureListener(this); // 设置监听器,通常是 Activity 自身实现 } // ... 下面实现 SurfaceTextureListener 的四个回调方法 } -
实现
SurfaceTextureListener接口 (核心!):
这个接口定义了TextureView内部绘图表面生命周期变化的回调:-
onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height): 最重要的回调!-
调用时机: 当
TextureView的绘图表面首次创建完成并准备好接收内容时调用。 -
你要做什么:
- 用传入的
surfaceTexture创建一个Surface对象:Surface surface = new Surface(surfaceTexture); - 将这个
Surface传递给内容生产者(这是关键一步!)。
@Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { // 1. 创建 Surface Surface surface = new Surface(surfaceTexture); // 2. 将 Surface 交给生产者 (例如 MediaPlayer) myMediaPlayer.setSurface(surface); // 告诉 MediaPlayer 把视频帧画到这个 Surface 上 // 或者: myCameraDevice.createCaptureSession(..., surface, ...); // 告诉相机把预览画面画到这个 Surface 上 } - 用传入的
-
此时
TextureView已经准备好显示内容了。生产者(如MediaPlayer)会开始向这个Surface推送图像帧。
-
-
onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height):- 调用时机: 当
TextureView的尺寸发生变化时(例如旋转屏幕、调整大小)调用。width和height是新的尺寸。 - 你要做什么: 通常需要通知生产者尺寸变化,以便它调整输出图像的分辨率或比例(例如
MediaPlayer可能需要重新设置视频尺寸,相机可能需要重新配置预览流)。你可以在这里重新创建Surface并设置给生产者(有时是必要的),或者调整生产者的输出设置。
- 调用时机: 当
-
onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture):-
调用时机: 当
TextureView的绘图表面即将被销毁时调用(例如 Activity 进入后台、TextureView被移除)。 -
你要做什么:
- 停止生产者! 停止视频播放、关闭相机预览等。非常重要! 如果在表面销毁后生产者还在往它写数据,会导致错误。
- 释放资源 (可选但推荐): 释放掉你之前用
surfaceTexture创建的Surface对象 (surface.release())。注意:不要释放参数传入的这个surfaceTexture,TextureView自己会管理它。 - 返回值: 通常返回
true,告诉TextureView“你可以销毁SurfaceTexture了”。如果你需要自己控制销毁时机(很少见),可以返回false并在稍后手动调用surfaceTexture.release()。
-
-
onSurfaceTextureUpdated(SurfaceTexture surfaceTexture):-
调用时机: 每当
SurfaceTexture关联的生产者更新了一帧新的图像内容时调用。 -
你要做什么: 这个回调非常频繁(视频可能每秒几十次)。通常不需要在这里做太多事情,除非你需要:
- 对每一帧图像做实时处理(例如人脸识别、滤镜)。
- 精确知道新帧到达的时间点(例如做音视频同步)。
-
性能注意: 避免在这里执行耗时操作,否则会阻塞 UI 线程导致卡顿。
-
-
-
启动内容生产者:
在onSurfaceTextureAvailable中将Surface设置给生产者后,就可以启动生产者了(例如myMediaPlayer.start(),myCameraCaptureSession.setRepeatingRequest(...))。 -
释放资源 (重要!):
在 Activity/Fragment 销毁时(例如onDestroy),确保:- 已经停止了生产者(在
onSurfaceTextureDestroyed中应该已经做了)。 - 释放了你自己创建的
Surface对象(在onSurfaceTextureDestroyed中做)。 TextureView本身会处理其内部的SurfaceTexture。
- 已经停止了生产者(在
四、原理(深入浅出)
想象一个流水线:
-
内容生产者 (Producer): 比如
MediaPlayer(视频解码)、Camera2 API(相机预览)、OpenGL ES渲染线程。它们负责生成连续的图像帧(像素数据)。 -
SurfaceTexture: 这是TextureView的核心秘密武器。它像一个带有传送带的中转站:- 它内部维护着一个
BufferQueue(缓冲区队列)。 - 生产者通过
SurfaceTexture提供的Surface将生成的图像帧放入这个队列。 SurfaceTexture会attach一个 OpenGL ES 纹理 (Texture) 到这个队列。当有新的图像帧到达队列时,SurfaceTexture会自动更新 (updateTexImage()) 它所关联的这个 OpenGL ES 纹理的内容,使其包含最新的图像帧。
- 它内部维护着一个
-
TextureView(Consumer):- 它本身是一个普通的
View。 - 在它的
draw()方法中,它会检查内部的SurfaceTexture是否有更新。 - 如果有更新,它会使用 OpenGL ES 命令,将
SurfaceTexture关联的那个包含了最新图像帧的纹理,绘制到TextureView自己的显示区域上。你可以想象成它把这张最新的“图片”当作壁纸,贴满整个TextureView。 - 因为它使用的是 OpenGL ES 进行最终的绘制,并且纹理支持各种变换,所以
TextureView可以轻松实现旋转、缩放、透明度等复杂的视觉效果。
- 它本身是一个普通的
关键优势:
- 统一绘制流程:
TextureView的最终绘制(把纹理贴到屏幕上)是作为普通 View 绘制流程的一部分进行的(发生在 UI 线程的draw过程中)。这使得它能完美支持 View 的动画、变换、叠加效果。 - 硬件加速: 整个纹理更新和绘制过程充分利用了 GPU(OpenGL ES),效率很高。
五、源码调用流程(简化版)
-
TextureView初始化: 创建时内部会初始化一个SurfaceTexture和关联的TextureLayer(负责 OpenGL ES 渲染)。 -
设置监听器 (
setSurfaceTextureListener): 注册回调。 -
onAttachedToWindow/ 布局测量:TextureView被添加到窗口树,系统开始布局。 -
表面创建: 当
TextureView被布局好且即将可见时,系统底层会为其分配绘图资源。触发SurfaceTexture初始化。 -
回调
onSurfaceTextureAvailable: 系统通过SurfaceTextureListener通知应用,SurfaceTexture已就绪,应用拿到SurfaceTexture创建Surface并传递给生产者。 -
生产者绘制: 生产者(如
MediaPlayer)通过Surface将图像帧推入SurfaceTexture内部的BufferQueue。 -
帧更新通知:
SurfaceTexture检测到新帧入队,标记自身状态为“有更新”,并可选的触发onSurfaceTextureUpdated回调(如果应用注册了)。 -
TextureView绘制 (draw()):- 检查内部
SurfaceTexture的“更新”标记。 - 如果标记为“有更新”,调用
SurfaceTexture.updateTexImage():关键步骤! 这个调用会从BufferQueue中取出最新的图像帧数据,并将其上传/更新到关联的 OpenGL ES 纹理对象中。同时计算该帧的变换矩阵 (getTransformMatrix())。 - 使用 OpenGL ES 命令(通过
TextureLayer或HardwareLayer),应用上一步得到的变换矩阵,将更新后的纹理绘制到TextureView对应的屏幕区域。 - 清除“更新”标记。
- 检查内部
-
尺寸变化: 如果
TextureView大小改变,触发onSurfaceTextureSizeChanged,应用可能需要调整生产者。 -
表面销毁: 当
TextureView被移除或窗口销毁,触发onSurfaceTextureDestroyed,应用停止生产者并释放相关资源。TextureView内部释放SurfaceTexture和TextureLayer。
六、总结与注意事项
-
是什么? 一个可以显示动态内容(视频、相机、OpenGL)的普通 View,支持完美动画、变换、叠加。
-
怎么用?
- 布局添加
TextureView。 - 实现
SurfaceTextureListener。 - 在
onSurfaceTextureAvailable中用传入的SurfaceTexture创建Surface并传递给生产者。 - 启动生产者。
- 在
onSurfaceTextureDestroyed中停止生产者并释放Surface。
- 布局添加
-
原理核心: 通过内部的
SurfaceTexture接收生产者图像帧 -> 更新 OpenGL ES 纹理 ->TextureView在draw()时用 OpenGL ES 把这个纹理绘制出来。 -
优点: 视图体系兼容性好,动画效果完美。
-
缺点:
- 性能开销略高于
SurfaceView: 因为最终绘制要走 View 系统的draw流程,并且涉及一次额外的纹理拷贝(生产者 ->SurfaceTexture纹理 ->TextureView最终绘制)。对于极高帧率或极低功耗要求场景,SurfaceView仍是首选。 - 内存占用: 多了一层纹理和缓冲区。
- API 要求: 需要 API Level 14 (Android 4.0) 及以上。在 5.0 (Lollipop) 之前,
TextureView在旋转时可能会短暂黑屏(需要应用额外处理)。
- 性能开销略高于
-
何时选择?
- 需要对动态内容应用动画(旋转、缩放、移动)、透明度、阴影等复杂视觉效果时。
- 需要将动态内容与其他普通 View(如按钮、文字)无缝叠加组合时。
- 对性能要求不是极其苛刻的场景(大部分视频播放、相机预览完全够用)。