深入浅出Android TextureView

769 阅读9分钟

我们来通俗易懂地解释一下 Android 中的 TextureView。你可以把它想象成一个更灵活、能更好融入普通视图体系的“画板” ,专门用来显示动态内容(比如视频、相机预览、OpenGL ES 渲染的 3D 场景等)。

一、它解决了什么问题?(为什么需要它?)

想象一下你要在 App 里播放一个视频:

  1. 最普通的 View (如 ImageView):  只能显示静态图片,无法流畅播放连续变化的视频帧。

  2. SurfaceView  这是 Android 早期提供的方案。它像一个挖在墙上的洞Window),后面有一个独立的绘图表面(Surface)。优点是性能好(直接在专用缓冲区绘制,不依赖 UI 线程),缺点是:

    • “洞”的特性:  它不能很好地与其他普通 View 叠加(动画、透明度、旋转等效果受限),因为它总是在最顶层(或者被其他 View 遮挡)。
    • 渲染时机不同步:  它的绘制发生在独立的线程/缓冲区,与普通 View 的绘制流程不同步,导致在其上做动画或与其他 View 组合时可能不流畅或出现撕裂感。

TextureView 就是为了解决 SurfaceView 的这些缺点而生的!

二、TextureView 是什么?

  • 本质:  它是一个继承了 View 的普通控件。这意味着它可以像 ButtonTextViewImageView 一样被自由地添加到布局中,应用各种动画(平移、缩放、旋转、透明度)、设置层级(Z轴)、与其他 View 叠加组合,完美融入 Android 的标准视图层级和绘制流程。
  • 核心能力:  它内部封装了一个 SurfaceTextureSurfaceTexture 是连接图像数据生产者(如 MediaPlayerCamera2OpenGL ES)和 OpenGL ES 纹理 的关键桥梁。TextureView 则负责将这个 OpenGL ES 纹理的内容最终绘制到屏幕上
  • 关键点:  TextureView 不直接绘制内容!它只是提供了一个表面(通过 SurfaceTexture 关联的 Surface)让生产者(如视频解码器、相机)把图像帧画上去,然后 TextureView 负责把这个图像帧当作一个纹理(可以想象成一张图片),用 OpenGL ES 把自己整个区域“贴”上这张纹理,最终显示出来。

三、如何使用 TextureView?(步骤详解)

使用 TextureView 的核心步骤围绕着监听它的可用性和获取它的绘图表面

  1. 在布局 XML 中添加 TextureView:

    <TextureView
        android:id="@+id/my_texture_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    
  2. 在 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 的四个回调方法
    }
    
  3. 实现 SurfaceTextureListener 接口 (核心!):
    这个接口定义了 TextureView 内部绘图表面生命周期变化的回调:

    • onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height)最重要的回调!

      • 调用时机:  当 TextureView 的绘图表面首次创建完成准备好接收内容时调用。

      • 你要做什么:

        1. 用传入的 surfaceTexture 创建一个 Surface 对象Surface surface = new Surface(surfaceTexture);
        2. 将这个 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 被移除)。

      • 你要做什么:

        1. 停止生产者!  停止视频播放、关闭相机预览等。非常重要!  如果在表面销毁后生产者还在往它写数据,会导致错误。
        2. 释放资源 (可选但推荐):  释放掉你之前用 surfaceTexture 创建的 Surface 对象 (surface.release())。注意:不要释放参数传入的这个 surfaceTextureTextureView 自己会管理它。
        3. 返回值:  通常返回 true,告诉 TextureView “你可以销毁 SurfaceTexture 了”。如果你需要自己控制销毁时机(很少见),可以返回 false 并在稍后手动调用 surfaceTexture.release()
    • onSurfaceTextureUpdated(SurfaceTexture surfaceTexture):

      • 调用时机:  每当 SurfaceTexture 关联的生产者更新了一帧新的图像内容时调用。

      • 你要做什么:  这个回调非常频繁(视频可能每秒几十次)。通常不需要在这里做太多事情,除非你需要:

        • 对每一帧图像做实时处理(例如人脸识别、滤镜)。
        • 精确知道新帧到达的时间点(例如做音视频同步)。
      • 性能注意:  避免在这里执行耗时操作,否则会阻塞 UI 线程导致卡顿。

  4. 启动内容生产者:
    在 onSurfaceTextureAvailable 中将 Surface 设置给生产者后,就可以启动生产者了(例如 myMediaPlayer.start()myCameraCaptureSession.setRepeatingRequest(...))。

  5. 释放资源 (重要!):
    在 Activity/Fragment 销毁时(例如 onDestroy),确保:

    • 已经停止了生产者(在 onSurfaceTextureDestroyed 中应该已经做了)。
    • 释放了你自己创建的 Surface 对象(在 onSurfaceTextureDestroyed 中做)。
    • TextureView 本身会处理其内部的 SurfaceTexture

四、原理(深入浅出)

想象一个流水线:

  1. 内容生产者 (Producer):  比如 MediaPlayer(视频解码)、Camera2 API(相机预览)、OpenGL ES 渲染线程。它们负责生成连续的图像帧(像素数据)。

  2. SurfaceTexture  这是 TextureView 的核心秘密武器。它像一个带有传送带的中转站

    • 它内部维护着一个 BufferQueue(缓冲区队列)。
    • 生产者通过 SurfaceTexture 提供的 Surface 将生成的图像帧放入这个队列
    • SurfaceTexture 会 attach 一个 OpenGL ES 纹理 (Texture) 到这个队列。当有新的图像帧到达队列时,SurfaceTexture 会自动更新 (updateTexImage()) 它所关联的这个 OpenGL ES 纹理的内容,使其包含最新的图像帧。
  3. TextureView (Consumer):

    • 它本身是一个普通的 View
    • 在它的 draw() 方法中,它会检查内部的 SurfaceTexture 是否有更新。
    • 如果有更新,它会使用 OpenGL ES 命令,将 SurfaceTexture 关联的那个包含了最新图像帧的纹理,绘制到 TextureView 自己的显示区域上。你可以想象成它把这张最新的“图片”当作壁纸,贴满整个 TextureView
    • 因为它使用的是 OpenGL ES 进行最终的绘制,并且纹理支持各种变换,所以 TextureView 可以轻松实现旋转、缩放、透明度等复杂的视觉效果。

关键优势:

  • 统一绘制流程:  TextureView 的最终绘制(把纹理贴到屏幕上)是作为普通 View 绘制流程的一部分进行的(发生在 UI 线程的 draw 过程中)。这使得它能完美支持 View 的动画、变换、叠加效果。
  • 硬件加速:  整个纹理更新和绘制过程充分利用了 GPU(OpenGL ES),效率很高。

五、源码调用流程(简化版)

  1. TextureView 初始化:  创建时内部会初始化一个 SurfaceTexture 和关联的 TextureLayer(负责 OpenGL ES 渲染)。

  2. 设置监听器 (setSurfaceTextureListener):  注册回调。

  3. onAttachedToWindow / 布局测量:  TextureView 被添加到窗口树,系统开始布局。

  4. 表面创建:  当 TextureView 被布局好且即将可见时,系统底层会为其分配绘图资源。触发 SurfaceTexture 初始化。

  5. 回调 onSurfaceTextureAvailable:  系统通过 SurfaceTextureListener 通知应用,SurfaceTexture 已就绪,应用拿到 SurfaceTexture 创建 Surface 并传递给生产者。

  6. 生产者绘制:  生产者(如 MediaPlayer)通过 Surface 将图像帧推入 SurfaceTexture 内部的 BufferQueue

  7. 帧更新通知:  SurfaceTexture 检测到新帧入队,标记自身状态为“有更新”,并可选的触发 onSurfaceTextureUpdated 回调(如果应用注册了)。

  8. TextureView 绘制 (draw()):

    • 检查内部 SurfaceTexture 的“更新”标记。
    • 如果标记为“有更新”,调用 SurfaceTexture.updateTexImage()关键步骤!  这个调用会从 BufferQueue 中取出最新的图像帧数据,并将其上传/更新到关联的 OpenGL ES 纹理对象中。同时计算该帧的变换矩阵 (getTransformMatrix())。
    • 使用 OpenGL ES 命令(通过 TextureLayer 或 HardwareLayer),应用上一步得到的变换矩阵,将更新后的纹理绘制到 TextureView 对应的屏幕区域。
    • 清除“更新”标记。
  9. 尺寸变化:  如果 TextureView 大小改变,触发 onSurfaceTextureSizeChanged,应用可能需要调整生产者。

  10. 表面销毁:  当 TextureView 被移除或窗口销毁,触发 onSurfaceTextureDestroyed,应用停止生产者并释放相关资源。TextureView 内部释放 SurfaceTexture 和 TextureLayer

六、总结与注意事项

  • 是什么?  一个可以显示动态内容(视频、相机、OpenGL)的普通 View,支持完美动画、变换、叠加。

  • 怎么用?

    1. 布局添加 TextureView
    2. 实现 SurfaceTextureListener
    3. 在 onSurfaceTextureAvailable 中用传入的 SurfaceTexture 创建 Surface 并传递给生产者
    4. 启动生产者。
    5. 在 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(如按钮、文字)无缝叠加组合时。
    • 对性能要求不是极其苛刻的场景(大部分视频播放、相机预览完全够用)。