TextureView 객체는 SurfaceTexture를 래핑하여 콜백에 응답하고 새 버퍼를 획득합니다. 새 버퍼를 획득한 TextureView는 뷰 무효화 요청을 실행하고 최신 버퍼의 콘텐츠를 데이터 소스로 사용하여 그리기를 실행한 후 뷰 상태에 표시된 내용에 따라 그대로 렌더링을 실행합니다.
OpenGL ES(GLES)는 SurfaceTexture를 EGL 생성 호출에 전달하여 TextureView에 대해 렌더링할 수 있지만 이 경우 문제가 발생합니다. GLES가 TextureView에 대해 렌더링하면 BufferQueue 생산자 및 소비자가 같은 스레드에 위치하며, 이로 인해 버퍼 전환 호출이 지연되거나 실패할 수 있습니다. 예를 들어 생산자가 UI 스레드에서 여러 개의 버퍼를 연달아 빠르게 제출하는 경우 EGL 버퍼 전환 호출은 버퍼를 BufferQueue에서 대기 해제해야 합니다. 하지만 소비자와 생산자가 같은 스레드에 있으므로 사용 가능한 버퍼가 없고 전환 호출은 중단되거나 실패합니다.
버퍼 전환이 지연되지 않도록 하려면 BufferQueue에 항상 대기 해제 가능한 버퍼가 있어야 합니다. 이를 구현하기 위해 BufferQueue는 새 버퍼가 큐에 대기할 때 이전에 획득한 버퍼의 콘텐츠를 폐기하고 최소 및 최대 버퍼 수에 제한을 적용하여 소비자가 모든 버퍼를 동시에 소비하지 못하게 합니다.
SurfaceView 또는 TextureView 선택
SurfaceView 및 TextureView는 비슷한 역할을 하며 둘 다 뷰 계층 구조의 시티즌입니다. 하지만 SurfaceView와 TextureView는 서로 구현이 다릅니다. SurfaceView는 다른 뷰와 동일한 매개변수를 취하지만 SurfaceView 콘텐츠는 렌더링 시 투명합니다.
TextureView는 SurfaceView보다 알파 및 회전 처리가 뛰어나지만 SurfaceView는 UI 요소로 계층화된 다른 동영상을 합성하는 경우 성능적 이점을 지닙니다. 클라이언트가 SurfaceView로 렌더링하면 SurfaceView는 클라이언트에 별도의 합성 레이어를 제공합니다. SurfaceFlinger는 기기에서 지원하는 경우 별도의 레이어를 하드웨어 오버레이로 작성합니다. 클라이언트가 TextureView로 렌더링하면 UI 툴킷은 TextureView의 콘텐츠를 GPU가 포함된 뷰 계층 구조로 합성합니다. 예를 들어 다른 뷰가 TextureView 위에 배치되면 콘텐츠 업데이트로 인해 다른 뷰 요소가 다시 그려질 수 있습니다. 뷰 렌더링이 완료되면 SurfaceFlinger는 앱 레이어 및 기타 모든 레이어를 합성합니다. 따라서 보이는 모든 픽셀이 두 번 합성됩니다.
우수사례: Grafika의 재생 동영상
Grafika의 재생 동영상에는 각각 TextureView 및 SurfaceView로 구현된 한 쌍의 동영상 플레이어가 포함됩니다. 활동의 동영상 디코딩 부분은 MediaCodec의 프레임을 TextureView 및 SurfaceView의 노출 영역으로 전송합니다. 두 구현 간의 가장 큰 차이점은 올바른 가로세로 비율을 표시하는 데 필요한 단계입니다.
SurfaceView를 확장하려면 FrameLayout의 맞춤 구현이 필요합니다.
WindowManager는 새 창 위치와 새 크기 값을 SurfaceFlinger에 전송해야 합니다. TextureView의 SurfaceTexture를 확장하려면 TextureView#setTransform()으로 변환 매트릭스를 구성해야 합니다.
올바른 가로세로 비율을 표시한 후에는 두 구현 모두 동일한 패턴을 따라야 합니다. SurfaceView/TextureView가 노출 영역을 생성하면 앱 코드는 재생을 사용 설정합니다. 사용자가 재생을 탭하면 동영상 디코딩 스레드가 시작되고 노출 영역이 출력 타겟이 됩니다. 이후에는 앱 코드가 아무것도 하지 않으며, 구성 및 표시가 SurfaceFlinger(SurfaceView의 경우) 또는 TextureView에 의해 처리됩니다.
우수사례: Grafika의 이중 디코드
Grafika의 이중 디코드는 TextureView 내의 SurfaceTexture에 대한 조작을 보여 줍니다.
Grafika의 이중 디코드는 TextureView 객체 쌍으로 두 개의 동영상을 나란히 표시하여 화상 회의 앱을 시뮬레이션합니다. 화면 방향이 변경되고 활동이 재시작되면 MediaCodec 디코더가 중지되지 않고 실시간 동영상 스트림의 재생을 시뮬레이션합니다. 효율성을 개선하려면 클라이언트가 노출 영역 연결을 유지해야 합니다. 노출 영역은 SurfaceTexture의 BufferQueue에 위치한 생산자 인터페이스의 핸들입니다. TextureView는 SurfaceTexture를 관리하므로 클라이언트가 SurfaceTexture의 연결을 유지하여 노출 영역의 연결을 유지해야 합니다.
SurfaceTexture의 연결 유지를 위해 Grafika의 이중 디코드는 TextureView 객체에서 SurfaceTextures 참조를 가져온 후 정적 필드에 이를 저장합니다.
그런 다음 Grafika의 이중 디코드는 TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed()에서 false를 반환하여 SurfaceTexture가 삭제되지 않도록 합니다. 그러면 TextureView는 활동 구성 변경사항 전체에 걸쳐 유지 가능한 onSurfaceTextureDestroyed()에 SurfaceTexture를 전달합니다. 클라이언트는 setSurfaceTexture()를 통해 이러한 변경사항을 새 TextureView에 전달합니다.
개별 스레드는 각 동영상 디코더를 구동합니다. Mediaserver는 디코딩된 출력을 포함하는 버퍼를 BufferQueue의 소비자인 SurfaceTextures에 전송합니다. TextureView 객체는 렌더링을 진행하고 UI 스레드에서 실행합니다.
SurfaceView로 Grafika의 이중 디코드를 구현하기는 TextureView로 구현하는 것보다 어렵습니다. 이는 SurfaceView 객체가 방향 변경 도중 노출 영역을 삭제하기 때문입니다. 또한 SurfaceView 객체는 2개의 레이어를 추가합니다. 이 경우 하드웨어에서 사용 가능한 오버레이 수가 제한되므로 이는 이상적인 방식이 아닙니다.
이 페이지에 나와 있는 콘텐츠와 코드 샘플에는 콘텐츠 라이선스에서 설명하는 라이선스가 적용됩니다. 자바 및 OpenJDK는 Oracle 및 Oracle 계열사의 상표 또는 등록 상표입니다.
최종 업데이트: 2025-07-27(UTC)
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["필요한 정보가 없음","missingTheInformationINeed","thumb-down"],["너무 복잡함/단계 수가 너무 많음","tooComplicatedTooManySteps","thumb-down"],["오래됨","outOfDate","thumb-down"],["번역 문제","translationIssue","thumb-down"],["샘플/코드 문제","samplesCodeIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2025-07-27(UTC)"],[],[],null,["# TextureView\n\nThe [TextureView](https://developer.android.com/reference/android/view/TextureView?hl=en) class is a view object that combines a view\nwith a SurfaceTexture.\n\nRendering with OpenGL ES\n------------------------\n\nA TextureView object wraps a SurfaceTexture, responding to callbacks and\nacquiring new buffers. When a TextureView acquires new buffers, a TextureView\nissues a view invalidate request and draws using the contents of the newest\nbuffer as its data source, rendering wherever and however the view state\nindicates it should.\n\n[OpenGL ES (GLES)](https://www.khronos.org/opengles/) *can*\nrender on a TextureView by passing the\nSurfaceTexture to the EGL creation call, but this creates a problem. When GLES\nrenders on a TextureView, BufferQueue producers and consumers are in the same\nthread, which can cause the buffer swap call to stall or fail. For example, if\na producer submits several buffers in quick succession from the UI thread, the\nEGL buffer swap call needs to dequeue a buffer from the BufferQueue. However,\nbecause the consumer and producer are on the same thread, there won't be any\nbuffers available and the swap call hangs or fails.\n\nTo ensure that the buffer swap doesn't stall, BufferQueue always needs a\nbuffer available to be dequeued. To implement this, BufferQueue discards the\ncontents of the previously acquired buffer when a new buffer is queued and\nplaces restrictions on minimum and maximum buffer counts to prevent a consumer\nfrom consuming all buffers at once.\n\nChoosing SurfaceView or TextureView\n-----------------------------------\n\n| **Note:** In API 24 and higher, it's recommended to implement SurfaceView instead of TextureView.\n\nSurfaceView and TextureView fill similar roles and are both citizens of the\nview hierarchy. However, SurfaceView and TextureView have different\nimplementations. A SurfaceView takes the same parameters as other views, but\nSurfaceView contents are transparent when rendered.\n\nA TextureView has better alpha and rotation handling than a SurfaceView, but\na SurfaceView has performance advantages when compositing UI elements layered\nover videos. When a client renders with a SurfaceView, the SurfaceView provides\nthe client with a separate composition layer. SurfaceFlinger composes the\nseparate layer as a hardware overlay if supported by the device. When a client\nrenders with a TextureView, the UI toolkit composites the TextureView's content\ninto the view hierarchy with the GPU. Updates to the content may cause other\nview elements to redraw, for example, if the other views are positioned on top\nof a TextureView. After view rendering completes, SurfaceFlinger composites the\napp UI layer and all other layers, so that every visible pixel is composited\ntwice.\n| **Note:** DRM-protected video can be presented only on an overlay plane. Video players that support protected content must be implemented with SurfaceView.\n\nCase Study: Grafika's Play Video\n--------------------------------\n\n[Grafika's Play Video](https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/PlayMovieActivity.java) includes a pair of video players, one\nimplemented with TextureView and one implemented with SurfaceView. The video\ndecoding portion of the activity sends frames from MediaCodec to a surface for\nboth TextureView and SurfaceView. The biggest difference between the\nimplementations are the steps required to present the correct aspect ratio.\n\nScaling SurfaceView requires a custom implementation of FrameLayout.\nWindowManager needs to send a new window position and new size values to\nSurfaceFlinger. Scaling a TextureView's SurfaceTexture requires configuring a\ntransformation matrix with `TextureView#setTransform()`.\n\nAfter presenting the correct aspect ratio, both implementations follow the\nsame pattern. When SurfaceView/TextureView creates the surface, the app code\nenables playback. When a user taps **play**, it starts a video\ndecoding thread, with the surface as the output target. After that, the app\ncode doesn't do anything---composition and display are handled by\nSurfaceFlinger (for the SurfaceView) or by the TextureView.\n\nCase Study: Grafika's Double Decode\n-----------------------------------\n\n[Grafika's Double Decode](https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/DoubleDecodeActivity.java) demonstrates manipulation of the\nSurfaceTexture inside a TextureView.\n\nGrafika's Double Decode uses a pair of TextureView objects to show two videos\nplaying side by side, simulating a video conferencing app. When the orientation\nof the screen changes and the activity restarts, the MediaCodec decoders don't\nstop, simulating playback of a real-time video stream. To improve efficiency,\nthe client should keep the surface alive. The surface is a handle to the producer\ninterface in the SurfaceTexture's BufferQueue. Because the TextureView manages\nthe SurfaceTexture, the client needs to keep the SurfaceTexture alive to keep\nthe surface alive.\n\nTo keep the SurfaceTexture alive, Grafika's Double Decode obtains references\nto SurfaceTextures from the TextureView objects and saves them in a static field.\nThen, Grafika's Double Decode returns `false` from\n`TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed()` to\nprevent the destruction of the SurfaceTexture. TextureView then passes a\nSurfaceTexture to `onSurfaceTextureDestroyed()` that can be\nmaintained across the activity configuration change, which the client passes to\nthe new TextureView through `setSurfaceTexture()`.\n\nSeparate threads drive each video decoder. Mediaserver sends buffers with\ndecoded output to the SurfaceTextures, the BufferQueue consumers. The\nTextureView objects perform rendering and execute on the UI thread.\n\nImplementing Grafika's Double Decode with SurfaceView is harder than\nimplementing with TextureView because SurfaceView objects destroy surfaces\nduring orientation changes. Additionally, using SurfaceView objects adds two\nlayers, which isn't ideal because of the limitations on the number of overlays\navailable on the hardware."]]