안드로이드 하드웨어 가속

Android 3.0 (API 레벨 11)부터 Android 2D 렌더링 파이프 라인은 하드웨어 가속을 지원합니다. 즉, View의 캔버스 에서 수행되는 모든 그리기 작업이 GPU를 사용합니다. 하드웨어 가속을 활성화하는 데 필요한 리소스가 증가하기 때문에 앱에서 더 많은 RAM을 소비합니다.

타겟 API 레벨이 14 이상인 경우 기본적으로 하드웨어 가속이 활성화되지만 명시적으로 활성화 할 수도 있습니다. 애플리케이션에서 일반적인View와 Drawable만 사용하는 경우 전역으로 설정하면 별다른 부작용이 발생하지 않습니다. 그러나 모든 2D 작업에 하드웨어 가속이 지원되는 것은 아니기 때문에 이 기능을 켜면 일부 커스텀뷰 또는 그리기 메소드 호출에 영향을 줄 수 있습니다.  일반적으로 문제가 생기는 증상으로는 그림을 그린것이 나타나지 않거나, 예외 또는 잘못된 렌더링 된 픽셀로 나타납니다. 이를 해결하기 위해 Android는 다방면으로 하드웨어 가속을 활성화 또는 비활성화하는 옵션을 제공합니다. 

애플리케이션에서에서 커스텀 그리기를 수행하는 경우 하드웨어 가속이 설정된 실제 하드웨어 장치에서 응용 프로그램을 테스트하여 문제를 찾으십시오. 아래의 지원하지 않는 그리기 연산자 섹션은 하드웨어 가속와이를 해결하는 방법 알려진 문제에 대해 설명합니다.

하드웨어 가속 제어하기

다음과 같은 계층에서 하드웨어 가속을 제어 할 수 있습니다

  • Application
  • Activity
  • Window
  • View

Application에서 제어하기

안드로이드 메니페스트의 <application> 태그에 하드웨어 가속 여부를 지정할 수 있습니다.

<application android:hardwareAccelerated="true" ...>

Activity에서 제어하기

글로벌하게 하드웨어 가속 옵션을 설정하고 싶지 않다면 액티비티별로 옵션을 줄수도 있습니다. 하드웨어 가속옵션을 액티비티에서 지정하려면 액티비티의 android:hardwareAccelerated 애트리뷰트를 true 또는  false 로 지정하면 됩니다.

<application android:hardwareAccelerated="true">
    <activity ... />
    <activity android:hardwareAccelerated="false" />
</application>

Window에서 제어하기

더 세세하게 하드웨어 가속을 제어하고 싶다면 주어진 윈도우에 하드웨어 가속 옵션을 다음과 같이 적용할 수 있습니다.

getWindow().setFlags(
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

Note : 현재는 Window 계층에서 하드웨어 가속 옵션을 비활성화 하는것이 불가능합니다.

View에서 제어하기

다음과 같이 런타임에 독립적으로 뷰의 하드웨어 가속 옵션을 비활성화 하는것도 가능합니다.

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

Note : 현재는 Window 계층에서 하드웨어 가속 옵션을 비활성화 하는것이 불가능합니다. 뷰계층은 하드웨어 가속을 끄는것 말고 다른 기능을 가지고 있습니다. 자세한 내용은 아래의 뷰 계층 섹션을 살펴보세요.

View가 하드웨어 가속이 되었는지 확인하기

때로는 하드웨어 가속이 된 상태인지 확인할 필요가 있습니다. 특히 커스텀뷰에서 말이죠. 

하드웨어 가속이 되었는지 확인하는 두가지 방법입니다.

  • View.isHardwareAccelerated() 가 true를 반환한다면 View는 하드웨어 가속된 윈도우에 붙어있는것이다.
  • Canvas.isHardwareAccelerated() 가 true를 반환한다면 Canvas는 하드웨어 가속된 것이다.

만약 그리기 코드를 반드시 확인해야한다면, 가능하면 View.isHardwareAccelerated()를 사용하는것보다Canvas.isHardwareAccelerated()를 사용하세요. View가 하드웨어 가속화 된 윈도우에 붙었을 때 View는 여전히 하드웨어 가속화되지 않은 Canvas를 사용하여 그릴 수도 있습니다. 예를들어 캐싱을 위해 View를 비트맵으로 그릴 때 이런일이 발생합니다.

안드로이드 그리기 모델

하드웨어 가속이 활성화 되어있을때 안드로이드 프레임워크는 화면에 애플리케이션을 렌더링 하기 위해 디스플레이 목록(display lists)을 활용한 새로운 그리기 모델을 사용합니다. 디스플레이 목록을 완전히 이해하고 이것들이 어떻게 애플리케이션에 영향을 미치는지 이해하는것은 안드로이드가 하드웨어 가속없이 뷰를 그리는지에 대한 이해를 하는데에도 도움이 된다. 다음 섹션은 소프트웨어 적으로 그리고 하드웨어 가속을 사용하여 그림을 그리는 모델에 대한 설명을 하고 있다.

소프트웨어 기반의 그리기 모델

소프트웨어 그리기 모델에서는 뷰는 다음 두가지 스텝에 따라 그려진다

  1. 계층을 무효화한다 (invalidate the hierachy)
  2. 계층을 그린다 (draw the hierachy)

첫째, 애플리케이션이 UI의 일부분을 업데이트 할 때마다 invalidate()를 호출한다. 무효화 메시지(invalidation  messages)들은 모든 뷰계층에 전파되어 스크린에서 다시 그려질 필요가 있는 더티영역을 어떻게 그릴지 계산한다. 안드로이드 시스템은 그런뒤 더티 영역과 교차해 있는 계층에 있는 뷰들을 그립니다. 불행히도 이 그리기 모델에는 두가지 문제점이 있습니다.

  • 첫째, 이 모델은 매 그리는 순간마다 많은양의 코드 실행이 필요합니다. 예를 들면 애플리케이션의 버튼에서  invalidate()를 호출 하고  그 버튼이 다른 뷰의 상위에 있다면, 안드로이드 시스템은 뷰가 변경되지 않았더라도 다시 그리게 됩니다.
  • 둘째, 그리기 모델은 애플리케이션의 버그들을 숨길수도 있습니다. 더티영역을 교차할때 안드로이드 시스템이 뷰를 다시 그린 후로 invalidate()가 호출되지 않았는데도  개발자가 변경한 뷰의 콘텐츠영역이 다시 그려질 수 있습니다. 이 경우 올바른 동작을 위해 무효화 되어지는 다른뷰에 의존하게 됩니다.  이 동작은 애플리케이션을 수정할 때마다 변경 될 수 있습니다. 이 때문에 뷰의 코드에 영향을 주는 데이터 또는 상태를 수정할 때 마다 항상 커스텀뷰에서 invalidate()를 호출해야합니다.

Note : 안드로이드 View는 View의 배경색 또는 TextView의 텍스트와 같은 속성이 변경될 때 마다 자동으로 invalidate()를 호출합니다.

하드웨어 가속화 그리기 모델 

안드로이드 시스템은 여전히 invalidate() 및 draw()를 사용하여 화면 갱신을 요청하고 View를 렌더링 하지만 실제 그리기는 다르게 처리합니다. 안드로이드 시스템은 그리기 명령을 즉시 실행하는 대신에 View 계층의 그리기 코드 출력을 포함하는 디스플레이 목록에 이를 기록합니다. 또 다른 최적화는 안드로이드 시스템이 invalidate() 호출로 더티로 표시된 뷰의 표시 목록만 기록하고 업데이트 하면 된다는 것입니다. 무효화 되지 않은 뷰는 이전에 기록된 디스플레이 목록을 다시 발행하여 간단히 그릴 수 있습니다. 새로운 그리기 모델은 다음 3단계를 가집니다.

  1. 계층 무효화(Invalidate the hierachy)
  2. 디스플레이 목록 기록하고 갱신하기
  3. 디스플레이 목록 그리기

이 모델로 더티 영역을 교차하는 뷰를 사용하여 draw()메소드를 실행할 수 없습니다. 안드로이드 시스템이 View의 디스플레이 목록을 기록하도록 하려면 invalidate()를 호출해야합니다. invalidate()를 안하면 뷰가 변경된 이후에도 뷰가 동일 하게 보입니다.

디스플레이 목록을 사용하면 알파 또는 회전과 같은 특정 속성의 설정에 대해 대상 뷰를 무효화 할 필요가 없으므로 애니메이션 성능이 향상됩니다. 이 최적화는 디스플레이 목록이 있는 뷰에도 적용됩니다(애플리케이션이 하드웨어 가속화되었을때 모든 뷰). 예를 들어 Button위에 ListView를 포함하는 LinearLayout이 있다고 가정할때 디스플레이 목록은 다음과 같습니다.

  • DrawDisplayList(ListView)
  • DrawDisplayList(Button)

이제 ListView의 알파값을 변경한다고 가정하고 setAlpha(0.5f)를 호출하면 디스플레이 목록에 다음의 내용이 포함됩니다.

  • SaveLayerAlpha(0.5)
  • DrawDisplayList(ListView)
  • Restore
  • DrawDisplayList(Button)

ListView의 복잡한 그리기 코드가 실행되지 않았습니다. 대신 시스템은 훨씬 간단한 LinearLayout 디스플레이 목록만 갱신했습니다. 하드웨어 가속이 활성화 되지 않는 애플리케이션에서는 리스트와 상위의 그리기 코드가 다시 실행됩니다.

지원하지 않는 그리기 기능

하드웨어 가속시 2D 렌더링 파이프 라인은 가장 많이 사용되는 Canvas를 그리기 작업과 덜 사용되는 작업을 지원합니다. Android, 기본 위젯 및 레이아웃, 반사 및 타일 텍스처와 같은 일반적인 고급 시각 효과와 함께 제공되는 응용 프로그램을 렌더링하는 데 사용되는 모든 그리기 작업이 지원됩니다.

다음 표는 API 레벨에서 다양한 조작의 지원 레벨을 설명합니다.

First supported API level
Canvas
drawBitmapMesh() (colors array) 18
drawPicture() 23
drawPosText() 16
drawTextOnPath() 16
drawVertices()
setDrawFilter() 16
clipPath() 18
clipRegion() 18
clipRect(Region.Op.XOR) 18
clipRect(Region.Op.Difference) 18
clipRect(Region.Op.ReverseDifference) 18
clipRect() with rotation/perspective 18
Paint
setAntiAlias() (for text) 18
setAntiAlias() (for lines) 16
setFilterBitmap() 17
setLinearText()
setMaskFilter()
setPathEffect() (for lines) 28
setShadowLayer() (other than text) 28
setStrokeCap() (for lines) 18
setStrokeCap() (for points) 19
setSubpixelText() 28
Xfermode
PorterDuff.Mode.DARKEN (framebuffer) 28
PorterDuff.Mode.LIGHTEN (framebuffer) 28
PorterDuff.Mode.OVERLAY (framebuffer) 28
Shader
ComposeShader inside ComposeShader 28
Same type shaders inside ComposeShader 28
Local matrix on ComposeShader 18

캔버스 스케일링

하드웨어 가속 2D 렌더링 파이프 라인은 스케일링되지 않은 드로잉을 지원하기 위해 먼저 구축되었으며, 일부 드로잉 작업은 더 높은 스케일 값에서 품질이 크게 저하됩니다. 이러한 작업은 GPU에 의해 변형 된 스케일 1.0으로 그려진 텍스처로 구현됩니다. API 레벨 28부터 모든 드로잉 작업이 문제없이 확장 될 수 있습니다.

다음 표는 대규모를 올바르게 처리하기 위해 구현이 변경된시기를 보여줍니다.

Drawing operation to be scaled First supported API level
drawText() 18
drawPosText() 28
drawTextOnPath() 28
Simple Shapes* 17
Complex Shapes* 28
drawPath() 28
Shadow layer 28

애플리케이션이 이러한 누락된 기능이나 제한사항에 영향을 받는 경우 setLayerType(View.LAYER_TYPE_SOFTWARE, null) 을 호출하여 애플리케이션의 영향을 받는 부분에 대해서만 하드웨어 가속을 해제 할 수 있습니다. 이 방법을 사용하면 다른 모든곳에서는 여전히 하드웨어 가속의 장점을 챙길 수 있습니다. 

View 레이어

모든 안드로이드 버전에서 뷰는 뷰의 드로잉 캐시를 사용하거나 Canvas.saveLayer()를 사용하여 오프-스크린 버퍼로 렌더링 할 수 있었습니다. 오프-스크린 버퍼 또는 레이어들은 여러가지 용도가 있습니다. 복잡한 뷰에 애니메이션을 적용하거나 여러가지 효과를 적용 할 때 더 나은 성능을 얻을 수 있습니다. 예를 들어 Canvas.saveLayer()를 사용하여 페이드 효과를 구현할 수 있고, 뷰를 레이어로 임시 렌더링 한 다음 화면에서 불투명도를 다시 합성할 수 있습니다.

안드로이드 3.0 버전을 시작으로 View.setLayerType() 메소드로 레이어를 사용하는 방법과 시기를 보다 세밀하게 조절 할 수 있습니다. 이 API는 레이어 타입과 페인트 이 두가지 파라미터를 갖습니다. 페인트는 컬러필터, 블렌딩모드, 레이어의 투명도 등을 적용합니다. 레이어 타입은 아래와 같이 세가지 중에 하나를 고를 수 있습니다.

  • LAYER_TYPE_NONE : View가 정상적으로 그려지고, 오프-스크린 버퍼에 의해 지원되지는 않습니다. 이 타입이 기본값입니다.
  • LAYER_TYPE_HARDWARE : 애플리케이션이 하드웨어 가속화된 경우 뷰는 하드웨어텍스쳐로 렌더링 됩니다. 애플리케이션이 하드웨어 가속화 되지 않은 경우 이 레이어 타입은 LAYER_TYPE_SOFTWARE와 동일하게 동작합니다.
  • LAYER_TYPE_SOFTWARE : 비트맵에 소프트웨어 방식으로 뷰가 렌더링 됩니다.

사용하는 레이어 타입은 목적에 따라 다릅니다:

  • 퍼포먼스 : 하드웨어 레이어 타입을 사용하여 뷰를 하드웨어 텍스처로 렌더링 하세요. 뷰가 레이어로 렌더링 되면 뷰가 invalidate()를 호출 할 때까지 그리기 코드를 실행할 필요가 없습니다. 투명 애니메이션과 같은 일부 애니메이션은 레이어에 직접 적용 할 수 있어서 GPU에서 매우 매우 효율적입니다.
  • 비주얼 효과 : 하드웨어 또는 소프트웨어 타입을 사용하세요 그리고 뷰에게 특별한 비주얼 효과를 적용하기 위해 페인트를 사용하세요
  • 호환성 : 소프트웨어 레이어 타입을 사용하여 소프트웨어에서 뷰를 강제로 렌더링 하세요. 하드웨어 가속 뷰에 렌더링 문제가 있는 경우 하드웨어 렌더링 파이프 라인의 한계를 쉽게 해결할 수 있습니다.

뷰 레이어 그리고 애니메이션

하드웨어 레이어는 애플리케이션이 하드웨어 가속화 될 때 더 빠르고 부드러운 애니메이션을 제공할 수 있습니다. 많은 그리기 작업을 수행하는 복잡한 뷰를 애니메이션으로 만들 때 60fps로 애니메이션을 실행하는 것이 항상 가능한 것은 아닙니다. 하드웨어 레이어를 사용하여 뷰를 하드웨어 텍스처로 렌더링하면 이를 개선할 수 있습니다. 그런 다음 하드웨어 텍스처를 사용하여 뷰에 애니메이션을 적용하여 애니메이션을 적용할 때 뷰를 계속 다시 그릴 필요가 없습니다. invalidate()를 호출하는 뷰의 속성을 변경하거나 invalidate()를 수동으로 호출하지 않으면 뷰가 다시 그려지지 않습니다. 애플리케이션에서 애니메이션을 실행하고 원하는 부드러운 결과를 얻지 못하면 애니메이션 뷰에서 하드웨어 레이어를 활성화하는 것이 좋습니다.

뷰가 하드웨어 레이어에 의해 지원되는 경우 일부 속성은 화면에서 계층이 합성되는 방식으로 처리됩니다. 이러한 속성을 설정하면 뷰를 무효화하고 다시 그릴필요가 없어 효율적입니다. 다음 속성 목록은 레이어가 합성되는 방식에 영향을 줍니다. 이러한 속성 중 하나에 대해 setter를 호출하면 최적의 무효화가 발생하고 대상 뷰가 다시 그려지지 않습니다.

  • alpha : 레이어의 투명도를 변경한다
  • x, y, translationX, translationY : 레이어의 포지션을 변경한다.
  • scaleX, scaleY : 레이어의 사이즈를 변경한다
  • roatation, rotationX, rotationY : 3차원공간에서 레이어의 방향을 변경한다
  • pivotX, pivotY : 레이어의 Transformation 원점을 변경합니다.

이러한 속성들은 ObjectAnimator로 뷰를 애니메이션 할 때 사용되는 이름입니다. 이러한 속성에 접근하려면 적절한 setter 또는 getter를 호출하세요. 예를 들어, 알파값을 수정하려면 setAlpha()를 호출하세요. 다음 코드 스니핏은 Y축 기준에서 3D로 회전시키는 가장 효율적인 방법을 보여줍니다.

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();

하드웨어 계층은 비디오 메모리를 소비하므로 애니메이션이 지속되는 동안에만 사용하도록 설정하고 애니메이션이 끝난후에는 사용하지 않는 것이 좋습니다. 애니메이션 리스너를 사용하여 이 작업을 수행 할 수 있습니다.

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setLayerType(View.LAYER_TYPE_NONE, null);
//애니메이션이 끝날때 레이어 타입을 변경하여 메모리를 더이상 소비하지 않도록함
    }
});
animator.start();

Tips and Tricks

하드웨어 가속 2D그래픽으로 전환하면 성능이 즉시 향상 되지만 다음 권장 사항에 따라 GPU를 효과적으로 사용하도록 애플리케이션을 설계해야합니다.

  • 애플리케이션 내에서 뷰의 갯수를 줄이세요
    시스템이 그려하는 많은 뷰를 가질수록 더 느려지게 됩니다. 이것은 소프트웨어 렌더링 파이프라인 방식에서도 적용됩니다. 뷰를 줄이는것은 UI를 최적화 하는 가장 쉬운 방법 중에 하나입니다.
  • 오버드로우 방식을 피하세요 
    너무 많은 레이어들을 그리지 마세요. 다른 뷰에 의해 가려진 뷰를 제거하세요. 서로 혼합된 여러 레이어를 그려야하는 경우 단일 레이어로 병합하는것이 좋습니다. 현재 하드웨어를 사용하는 좋은 방법은 프레임 당 화면의 픽셀수의 2.5배를 넘지않는 것입니다.
  • 그리기 메소드에서 렌더 오브젝트를 생성하지 마세요
    일반적인 실수로 Paint나 Path객체를 매번 렌더링 매소드가 호출될 때마다 생성하는 것입니다. 이것은 강제로 GC를 자주 발생시키고 캐시 또는 최적화를 하드웨어 파이프 라인에서 생략하게 됩니다.
  • 도형을 너무 자주 수정하지 마세요
    복잡한 도형, 선 그리고 동그라미 들은 텍스쳐 마스크를 사용하여 렌더링 되게 됩니다. 매번 선을 생성 또는 수정할 때마다 하드웨어 파이프라인은 새로운 마스크를 생성하게 되게 이를 생성하는 비용은 굉장히 많이 듭니다.
  • Bitmap을 너무 자주 수정하지 마세요
    비트맵의 콘텐츠를 바꿀때마다 GPU텍스쳐로 다시 업로드를 해야하고 다음 프레임에 이를 그리게 됩니다.
  • 투명도는 조심해서 사용하세요
    뷰를 setAlpha(), AlphaAnimation 또는 ObjectAnimator를 사용하여 투명하게 만들 때, 오프-스크린 버퍼내에서 렌더링 되고 fill-rate를 두배로 필요하게 됩니다. 매우 큰 뷰에 투명도를 적용할 때는 View의 레이어 타입을 LAYER_TYPE_HARDWARE로 설정하는 것을 고려하시기 바랍니다.

 

Buy me a coffeeBuy me a coffee
카테고리: Android

0개의 댓글

답글 남기기

이메일은 공개되지 않습니다.