AW205: 소프트 키보드, tools 네임스페이스, 렌더쓰레드

Android Weekly는 매주 발행되는 안드로이드 뉴스레터입니다. 영어 기사를 정독할 시간이 없는 분을 위해 핵심 꼭지를 요약했습니다.

주간 안드로이드 뉴스를 요약해 드립니다. Android Weekly 205 원문도 읽어보세요.


렌더 쓰레드란?

롤리팝 전까지는 어떤 무거운 작업을 하면서 부드러운 애니메이션 효과를 기대하기 어려웠지만 롤리팝 이후로는 어떤 일이 벌어지는지와 상과없이 물결 효과 등 애니메이션 효과를 더 효과적으로 사용할 수 있게 되었습니다. 이게 가능할 수 있던 것은 바로 RenderThread덕분이죠~

“시스템이 관리하는 프로세싱 쓰레드로써 UI쓰레드가 지연되어도 애니메이션을 부드럽게 동작하도록 한다”

RenderThread는 롤리팝에서 새로 소개된 컴포넌트 입니다. 아직까지는 관련 문서가 거의 없고, 위와 같이 그 정의 또한 모호한; RenderThread에 대해 알아보겠습니다.

안드로이드는 각각의 프레임을 CPU에서 직접 그리는 요청을 처리하는 것 대신 ‘display list’를 사용합니다. 그리기 조작과 관련된 일련의 작업들을 기록해두며 RenderNode라는 클래스로 표현됩니다.

display list를 사용하는 간접적인 방식의 이점

  • 추후 비지니스 로직와 더 상호작용하지 않고서도 필요할 때마다 다시 그릴 수 있음
  • tanslation이나 scale과 같은 조작들을 다시 re-issue하지 않고서 전체 리스트 항목에 대해 적용할 수 있음
  • 모든 그리기 조작이 알려지면 최적화 됨(예: 가능하면 모든 텍스트는 한번에 그려짐)
  • display list를 다루는 것은 다른 쓰레드에 전달될 수 있음

위 장점 중 마지막 내용이 바로 RenderThread가 하는 역할과 관련되는 내용으로, UI쓰레드로부터 GPU의 최적화 및 전달(dispatching)을 다룹니다.

실제로 렌더링을 실행하는 것을 GPU인데 GPU자체로는 애니메이션에 대해서는 알지 못합니다. 따라서 어떤 것에 애니메이션 효과를 주는 것은 각 프레임별로 다른 그리기 명령을 실행하도록 해야 합니다. UI쓰레드에서 관련 작업이 수행되면 어떤 무거운 작업이 있을 때 새로운 그리기 명령이 제시간에 수행되지 못하게 막혀서 지연이 발생하게 됩니다. RenderThread는 display list 파이프라인을 조작할 수 있고, 어떤 display list의 생성 또는 수정은 UI쓰레드에서 일어나고 있습니다.

이런 개발 뉴스를 더 만나보세요

그러면 어떻게 다른 쓰레드에서 에니메이션을 업데이트 할 수 있을까요?

하드에어 acceleration이 그릴때, Canvas구현은 DisplayListCanvas클래스가 담당하는데 이 클래스는 몇가지 그리기 메소드를 오버로드하고 있으며, 직접적인 값을 받는 것 대신 CanvasProperty를 참조해 관련 값의 랩핑을 제공합니다. 이 방식으로 디스플레이 리스트는 여전히 UI쓰레드에서 생성될 수 있는 것입니다(스태틱 명령들이지만 CanvasProperty 맵핑을 이용해 파라미터는 동적으로 변경될 수 있음). CanvasProperty의 값들은 RenderNodeAnimator를 통해 구현됩니다.

애니메이션 결과에 대한 몇가지 흥미로운 점

  • 타겟 DisplayListCanvas는 수동으로 설정되어야 하며 변경 불가
  • 한번 실행되면 멈춤/재개할 수 없으며 오직 취소만 가능함 또한 현재값 을 알 수 없음
  • 커스텀 Interpolator가 제공되며 RenderThread에서 부를 수 있음
  • 시작 대기는 일반적으로 RenderThread에서 기다림

렌더쓰레드로 할 수 있는 것들

  • 뷰 프로퍼티(View.animate)
    • Translation(X, Y, Z)
    • Scale(X,Y)
    • Ratation(X,Y)
    • Alpha
    • Circular reveal 애니메이션
  • 캔버스 메소드
    • drawCircle(centerX, centerY, radius, paint)
    • drawRoundRect(left, top, right, bottom, cornerRadiusX, cornerRadiusY, paint)
  • 페인트 프로퍼티
    • Alpha
    • Stroke width

구글이 현재까지는 머테리얼디자인을 위해 꼭 필요한 조작에 대해서만 랩핑을 제공하는 것처럼 보이지만 아마도 Android N에서는 더 많은 기능을 포함할 것으로 기대합니다. 렌더쓰레드 이해하기 (Understanding the RenderThrea)에서 실제 코드도 함께 참고해보세요~

안드로이드 소프트 키보드 개발 이야기

앱에서 텍스트를 입력하려는데 소프트 키보드가 나타나면서 화면이 엉망;이 되어버린 경험 있으신가요? 안드로이드 키보드 핸들링하기 (Keyboard Handling on Android)에서 “PSPDFKit”에서 텍스트 에디팅기능을 구현하는 과정에서 키보드와 관련해 많은 고민을 했던 것 같습니다. 이 경험을 바탕으로 어떻게 문제를 해결했는지 소프트 키보드 개발 꿀팁 나갑니다~

  • 키보드 제시에 따른 레이아웃 조정

안드로이드의 기본 소프트 키보드 동작방식은 실행중인 어플리케이션 UI위로 키보드를 제시하는 것입니다. 대부분의 경우 이 기본 방식을 사용하는데 문제가 없겠지만 아래와 같이 UI의 중요한 부분을 가리는 상황도 발생합니다.


이럴 경우는 메니페스트의 <activity>요소에 android:windowSoftInputMode="adjustResize"속성을 주어 레이아웃을 조정하거나 아래와 같이 동적으로 해결할 수 있습니다.

activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

이렇게 레이아웃 크기를 재조정할 경우 변경된 뷰 사이즈는 onSizeChanged(int, int, int, int) 메소드를 통해 확인할 수 있습니다.

  • 전체화면 모드

위와 같이 adjustResize를 사용할 수도 있지만, 공식문서에 제시된바에 따르면 윈도우 레이아웃 파라미터 플래그에 FLAG_FULLSCREEN를 포함할 경우 위 속성값은 적용되지 않고, 전체화면으로 유지 된다는 문제가 있습니다.

주목할 점은 이 상황에서 각 뷰들은 여전히 키보드가 보이는지, 감춰져있는지 fitSystemWindows(Rect) 메소드를 통해 정보를 받고 있다는 것입니다. 따라서 크기가 조정되어야 할 뷰에서 setFitsSystemWindows(true)를 호출해 ` fitSystemWindows`메소드를 구현할 수 있습니다. 이렇게 하면 시스템바, 액션바, 네비게이션 바와 함께 소프트 키보드를 변경할 수 있습니다.

NOTE: API 20에서 fitSystemWindows가 deprecated되면서 onApplyWindowInsets(WindowInsets)로 대체되었습니다.

  • 키보드 상태 확인하기

안타깝게도 안드로이드에서 소프트 키보드 상태(visibility)를 확인하는 API가 제공되지 않습니다.

그렇지만 우리는 getWindowVisibleDisplayFrame(Rect)를 사용하면 보이는 뷰의 사이즈를 구할 수 있습니다. 이를 통해 아래와 같이 키보드 전체화면 모드에서도 동작하는 visibility 리스너를 구현할 수 있습니다.

// 최소 키보드 높이 기준
final int MIN_KEYBOARD_HEIGHT_PX = 150;

// 탑 레벨 윈도우 데코 뷰
final View decorView = activity.getWindow().getDecorView();

// 글로벌 레이아웃 리스너 등록
decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    private final Rect windowVisibleDisplayFrame = new Rect();
    private int lastVisibleDecorViewHeight;

    @Override
    public void onGlobalLayout() {
        // 윈도우 내 보이는 rectangle 호출
        decorView.getWindowVisibleDisplayFrame(windowVisibleDisplayFrame);
        final int visibleDecorViewHeight = windowVisibleDisplayFrame.height();

        // 변경된 데코 뷰 높이로부터 키보드 상태 확인
        if (lastVisibleDecorViewHeight != 0) {
            if (lastVisibleDecorViewHeight > visibleDecorViewHeight + MIN_KEYBOARD_HEIGHT_PX) {
                // 키보드 높이 계산(전체화면 모드일 때 네비게이션 바 높이 계산하는 것도 포함)
                int currentKeyboardHeight = decorView.getHeight() - windowVisibleDisplayFrame.bottom;
                // 리스너에 키보드 보임 알림
                listener.onKeyboardShown(currentKeyboardHeight);
            } else if (lastVisibleDecorViewHeight + MIN_KEYBOARD_HEIGHT_PX < visibleDecorViewHeight) {
                // 리스너에 키보드 숨김 알림
                listener.onKeyboardHidden();
            }
        }
        // 다음번 불릴 경우를 고려해 현재 높이 값 저장
        lastVisibleDecorViewHeight = visibleDecorViewHeight;
    }
});

외와 같은 방식을 사용해 사용자가 타이핑을 시작할 때 그 안의 내용이 스크롤되면서 더 나은 사용자 경험을 제공하는 모습으로 변했습니다!!

효과적인 디자인 타임 개발 팁

구글이 제공하는 XML 레이아웃 예시들을 보면 tools namespace가 나오곤 합니다. 툴즈 네임스페이스는 안드로이드 스튜디오 디자인 뷰의 레이아웃을 제시할 때 도움되는 속성들을 모아둔 것이라고 생각할 수 있고 따라서 UI관련 변화에 대한 빌드 시간을 줄여줄 수 있습니다. 이 네임스페이스에 대해 몇 가지 특징을 이해햔다면 개발과정에서 꽤 쓸만한 기능으로 활용할 수 있습니다.

  • 네입스페이스 삽입
    • toolNS를 친 후 탭 키를 눌러 완성하거나
    • android: 네임스페이스 속성을 사용하는 다른 라인을 CMD+D로 복제해서 tools로 prefix를 수정해서 사용하세요.


  • android:text="" vs. tools:text="@string" android:text=""를 사용하면 사용자에게는 보이지 않지만 .apk에는 쓸모없는 것들이 포함됩니다. 이때 tools:text="@string"를 사용한다면 preview화면에서는 마치 android:text="" 속성을 사용한 것처럼 확인할 수 있게 됩니다. 즉, 툴 네임스페이스는

“디자인 타임에서만 유효하고, 런타임에는 존재하지 않습니다.

  • android와, tools 네임스페이스 동시에 사용하기

리스트뷰를 예로 들면 런타임에서 확인하고 싶은 피처를 디자인 프리뷰에서는 막아놓고 싶은 경우가 있습니다. 이때 아래와 같이 사용하실 수 있습니다.

<ListView
    android:id="@+id/listView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:fastScrollAlwaysVisible="true"
    tools:fastScrollAlwaysVisible=""/>

추가로 리스트뷰와 관련된 속성으로 tools:listheader, tools:listfooter도 사용해보세요.

  • @TargetApi vs. tools:targetApi="M" 숫자나 코드네임으로 @TargetApi자바 어노테이션과 같은 방식으로 API레벨을 맞출 수 있습니다.

  • tools:layout="@layout/your_layout" 사용하기 프래그먼트나 커스텀뷰를 디바이스나 에뮬레이터에 직접 실행시키지 않고 프리뷰에서도 확인할 수 있는 편리한 방법입니다.

  • tools:context=".MainActivity" 안드로이드 스튜디오에서 XML파일 생성시 자동으로 만들어주는 속성이라 많이 익숙하실텐데요, 하나의 xml은 여러 액티비티나 프래그먼트로 사용될 수 있기때문에 이 속성을 이용해 어떤 액티비티 클래스인지 연결지을 수 있습니다.

  • 린트 워닝 무시하기 물론 워닝과 에러는 고쳐야하겠지만~~ 가끔 무시해도 될만한;; 워닝은 tools:ignore=""를 사용해 편의상 무시할 수 있는 방법이 있습니다. 참고하세요~

그 외에도 메뉴와 관련해 tools:menu="comma separated menu IDs, 툴바 네비게이션 모드 설정 tools:actionBarNavMode="standard|list|tabs" 등 개발 중 활용할 수 있는 툴즈 네임스페이스 활용방법을 툴 네임스페이스 제대로 알기(Mastering tools namespace on Android)에서 확인해보세요.

오픈소스 라이브러리

  • RxAssertions 더 나은 RxJava assertions를 위한 라이브러리로, 기존의 Ribot의 repo가 deprecated 됨에 따라 이 아이디어에 근거해 더욱 향상된 기능을 포함하고 있습니다.

  • Android-Image-Cropper 카메라와 갤러리에 최적화된 이미지 크롭 라이브러리입니다.
    • POWERFUL: 줌, 회전, 멀티소스
    • CUSTOMIZABLE: 모양, 스타일
    • OPTIMIZED: 비동기, 샘플링, 매트릭스
  • Swipe-action-layout 머테리얼 디자인으로 새로운 액션을 제공하는 레이아웃 스와이프 라이브러리입니다.

더 읽을 거리

5월 셋째 주의 기사를 Android Weekly 205 영어 원문에서 볼 수 있습니다.

지난 뉴스가 궁금하다면 아래 링크를 참고해 주세요.

컨텐츠에 대하여

이 컨텐츠는 저자의 허가 하에 이곳에서 공유합니다.


Realm Korea

Realm Korea Team

4 design patterns for a RESTless mobile integration »

close