Android 앱 프레임워크 UI는 뷰로 시작하는 객체의 계층 구조에 기반합니다. 모든 UI 요소는 직사각형 영역에 맞게 요소를 조정해주는 일련의 측정 및 레이아웃 프로세스를 거치게 됩니다. 그런 다음에는 눈에 보이는 모든 뷰 객체가 앱이 포그라운드에 표시되었을 때 WindowManager에 의해 설정되었던 노출 영역으로 렌더링됩니다. 앱의 UI 스레드는 프레임당 버퍼의 레이아웃 및 렌더링을 수행합니다.
SurfaceView
SurfaceView는 추가 합성 레이어를 뷰 계층 구조 내에 삽입할 때 사용할 수 있는 구성요소입니다. SurfaceView는 같은 레이아웃 매개변수를 다른 뷰로 취하기 때문에 다른 모든 뷰처럼 조작할 수 있지만 SurfaceView의 콘텐츠는 투명합니다.
GL 컨텍스트 또는 미디어 디코더와 같은 외부 버퍼 소스로 렌더링할 때는 버퍼 소스의 버퍼를 복사하여 화면에 버퍼를 표시해야 합니다. SurfaceView를 사용하면 이를 수행할 수 있습니다.
SurfaceView의 뷰 구성요소가 표시되기 직전에 프레임워크는 SurfaceFlinger의 새 노출 영역을 요청하도록 SurfaceControl에 요구합니다. 노출 영역이 생성되거나 삭제될 때 콜백을 수신하려면 SurfaceHolder 인터페이스를 사용합니다. 기본적으로 새로 생성된 노출 영역은 앱 UI 노출 영역 뒤에 배치됩니다. 기본 Z-순서를 재정의하여 새 노출 영역을 위에 배치할 수 있습니다.
SurfaceView를 이용한 렌더링은 별도의 노출 영역에 렌더링해야 하는 경우에 유용합니다(예: Camera API 또는 OpenGL ES 컨텍스트로 렌더링하는 경우). SurfaceView로 렌더링하면 SurfaceFlinger가 직접 버퍼를 화면에 작성합니다. SurfaceView가 없으면 버퍼를 오프스크린 노출 영역에 합성해야 합니다. 그러면 버퍼가 화면에 합성되므로 SurfaceView로 렌더링할 때 추가 작업이 필요하지 않습니다. SurfaceView로 렌더링한 후에는 UI 스레드를 사용하여 활동 수명 주기와 조율하고 필요한 경우에는 뷰 크기 또는 위치를 조정합니다. 그러면 하드웨어 컴포저가 앱 UI와 다른 레이어를 혼합합니다.
새 노출 영역은 BufferQueue의 생산자 측이며 소비자는 SurfaceFlinger 레이어입니다. 노출 영역은 BufferQueue에 피드 가능한 모든 메커니즘으로 업데이트할 수 있습니다(예: 노출 영역에서 제공되는 캔버스 함수, GLES로 노출 영역에 EGLSurface 및 그림을 추가 또는 미디어 디코더를 구성하여 노출 영역 작성).
SurfaceView 및 활동 수명 주기
SurfaceView를 사용할 때는 기본 UI 스레드 외의 스레드에서 노출 영역을 렌더링합니다.
SurfaceView를 포함하는 활동의 경우 별개이지만 상호 종속되는 두 가지 상태 시스템이 있습니다.
- 앱
onCreate
/onResume
/onPause
- 노출 영역 생성됨/변경됨/삭제됨
활동이 시작되면 이 순서대로 콜백을 수신합니다.
onCreate()
onResume()
surfaceCreated()
surfaceChanged()
뒤로를 클릭하면 다음이 표시됩니다.
onPause()
surfaceDestroyed()
(노출 영역이 사라지기 직전에 호출됨)
화면을 회전하면 활동이 해제되었다가 다시 생성되며, 전체 주기를 수신하게 됩니다. isFinishing()
을 확인하여 빠른 재시작인지 알 수 있습니다. 활동은 아주 빠르게 시작/중지할 수 있기 때문에 surfaceCreated()
가 onPause()
이후에 발생합니다.
전원 버튼을 탭하여 화면을 절전 모드로 전환하면 surfaceDestroyed()
없이 onPause()
만 수신됩니다. 노출 영역은 활성 상태를 유지하며 렌더링을 이어갈 수 있습니다. 계속해서 요청하면 Choreographer 이벤트를 계속 수신할 수 있습니다. 잠금 화면이 다른 방향을 강제하는 경우 기기 절전 모드가 해제될 때 활동이 재시작될 수 있습니다. 아니면 전과 같은 노출 영역으로 화면 절전 모드를 해제할 수 있습니다.
스레드 수명은 화면이 절전 모드로 전환될 때 어떤 동작을 원하는지에 따라 노출 영역이나 활동에 연결될 수 있습니다. 스레드는 활동 시작/중지 또는 노출 영역 생성/삭제 시에 시작/중지될 수 있습니다.
활동 시작/중지 시에 스레드가 시작/중지되도록 하는 방식은 앱 수명 주기에도 적합합니다. 렌더기 스레드는 onResume()
에서 시작하여 onStop()
에서 중지합니다.
간혹 스레드를 생성하고 구성할 때는 이미 노출 영역이 존재하거나 그렇지 않을 수도 있습니다. 예를 들면 전원 버튼으로 화면을 전환해도 계속해서 활성 상태가 유지될 수 있습니다. 스레드를 초기화하기 전에 노출 영역이 생성되기를 기다려야 합니다. 노출 영역이 다시 생성되지 않은 경우에는 스레드가 다시 실행되지 않으므로 surfaceCreate()
콜백에서는 초기화할 수 없습니다. 대신 노출 영역 상태를 쿼리하거나 캐시한 다음 렌더기 스레드에 전달합니다.
노출 영역 생성/삭제 시에 스레드가 시작/중지되도록 하면 효과적인 이유는 노출 영역 및 렌더기가 논리적으로 밀접한 관계를 지니고 있기 때문입니다. 노출 영역이 생성된 후에 스레드를 시작하면 스레드 간 통신 문제를 피할 수 있으며 단순히 노출 영역 생성/변경 메시지가 전달됩니다. 화면이 절전 모드로 전환될 때 렌더링을 중지했다가 절전 모드 해제 시에 재개되도록 하려면 프레임 그리기 콜백 호출을 멈추도록 Choreographer에 지시해야 합니다. onResume()
은 렌더기 스레드가 실행 중인 경우 콜백을 재개합니다. 하지만 프레임 간의 경과된 시간에 따라 애니메이션을 적용하면 다음 이벤트가 도착할 때까지 상당한 공백이 생길 수 있으며 명시적인 일시중지/재개 메시지를 사용하여 이 문제를 해결할 수 있습니다.
두 옵션 모두, 스레드의 수명이 활동 또는 노출 영역과 연결되는지에 상관없이 렌더기 스레드의 구성 방식과 실행 여부에 집중합니다. 관련된 문제는 활동이 onStop()
또는 onSaveInstanceState()
에서 중지되었을 때 스레드에서 상태를 추출하는 것입니다. 이러한 경우에는 스레드의 수명을 활동에 연결하는 것이 가장 효율적입니다. 렌더기 스레드가 조인된 후에는 동기화 프리미티브 없이도 렌더링된 스레드의 상태에 액세스할 수 있기 때문입니다.
GLSurfaceView
GLSurfaceView 클래스는 EGL 컨텍스트, 스레드 간 통신 및 활동 수명 주기와의 상호작용을 위한 도우미 클래스를 제공합니다. GLES 사용을 위해 GLSurfaceView를 사용할 필요는 없습니다.
예를 들어 GLSurfaceView는 렌더링을 위한 스레드를 생성하고 거기에 EGL 컨텍스트를 구성합니다. 상태는 활동이 일시중지되면 자동으로 정리됩니다. 대부분의 앱은 EGL에 관해 전혀 몰라도 GLSurfaceView와 함께 GLES를 사용할 수 있습니다.
대부분의 경우 GLSurfaceView의 사용으로 GLES 작업을 훨씬 쉽게 수행할 수 있습니다. 하지만 경우에 따라 작업에 방해가 될 수도 있습니다.