Advanced Kotlin – Inline Functions (2) – Local Return

이 글은 Advanced Kotlin – Inline Functions (1) – Basic 에서 이어집니다.

Inline Functions는 단순히 성능 향상(정확히는 성능 임팩트 최소화) 뿐만이 아니라 Local Return 문에도 사용하다.

설명

간단히 아래 코드를 보자.

fun init() {
        val numbers = 1..100
        numbers.myLambda({
            if (it % 5 == 0) {
                [email protected]
            }
        })
        println("Hello!")
}

fun <T> Iterable<T>.myLambda(action: (T) -> Unit) {
        for (elements in this) action(elements)
}

myLambda 란 메소드는 Iterable<T> 의 확장 메소드(Extension methods) 로 리스트의 각 요소마다 action 을 실행하는 구조를 가지고 있다. 이 코드를 돌려보면 어떤 결과가 나올까. myLambda 의 원소가 5일 때, 해당 myLambda의 작업은 중지되고 ‘Hello!’ 이 터미널에 찍힐 것이다.

그렇다면, myLambda의 원소가 5일 때 해당 메소드 자체의 작업을 중지시키려면 어떻게 해야할까? 간단히 [email protected]를 return으로 바꾸면 될까?

‘return’은 허용되지 않습니다 란 오류를 호출하며 컴파일이 되지 않는다.

이런 경우에, 해당 myLambda에 inline 키워드를 붙여주면 정상적으로 작동하는데, 바이트 코드를 살펴보면

public final void init() {
      IntRange numbers = new IntRange(1, 100);
      Iterable $receiver$iv = (Iterable)numbers;
      Iterator var4 = $receiver$iv.iterator();

      int elements$iv;
      do {
         if(!var4.hasNext()) {
            String var2 = "Hello!";
            System.out.println(var2);
            return;
         }

         elements$iv = ((IntIterator)var4).nextInt();
      } while(elements$iv % 5 != 0);

}

이런 형태가 되어 최종적으로 Hello! 가 노출되지 않는다.

물론, Anonymous function를 사용하면 위와 같은 문제는 일어나지 않는 것 같다.

 numbers.myLambda(fun(elements) {
            if (elements % 5 == 0) {
                return
            }
 })

 

Android Oreo(8.0) – Background Location Limit

Android O 부터 앱의 대상 SDK 버전 상관 없이 후면 서비스에서 위치를 받는 것이 제한을 받게 되었다.

여기서 애매한 것이 전경(포그라운드, Foreground)과 후면(백그라운드, Background)의 구분 방법이다.
안드로이드 개발자 사이트 의 해당 항목을 보면, 아래 조건을 하나라도 만족시킬 경우에는 전경 서비스로 분류된다.

  • 액티비티가 시작했든, 일시 중지되었든 보여지는 액티비티가 존재할 경우
  • startForeground(int, Notification) 을 사용하여 위치를 받고 있다는 것을 알 수 있을 경우
  • 다른 전경 앱이 해당 앱과 연결되었을 경우 (서비스를 통한 바인딩, 콘텐츠 프로바이더 사용 등)

나머지는 전부 후면 서비스로 분리된다.

전경 서비스는 7.1.1 (API Level 25) 이하와 같이 작동한다. (물론, 실시간 위치 업데이트는 배터리 소모를 강력하게 야기시킨다.)
하지만 후면 서비스가 문제다.

제한을 받지 않는 방법?

제한을 받지 않으려면, 아래와 같은 방법을 하라고 추천하고 있다.

  • 앱을 전경으로 가져온다
  • 전경 서비스를 사용한다. 서비스가 살아있을 때는 반드시 OnGoing Notification 을 표시 해야 한다.
  • 전력 사용 최적화된 GeofencingApi 사용
  • 수동 브로드캐스트로 위치를 가져온다. (PendingIntent 사용)

영향을 받는 API

Fused Location Provider (FLP)

  • 전경 상태에 있을 경우엔 문제가 없음
  • 후면 상태일 경우, 시간당 몇번만 받을 수 있음

GeofencingApi

  • 후면 상태의 앱이라고 하더라도 Fused Location Provider 보다 더 많은 위치 업데이트를 받을 수 있다.
  • Geofencing 이벤트의 응답성은 대략 몇 분 정도.

GNSS Measurements and GNSS Navigation Messages

LocationManager

  • 후면 상태일 경우, 시간당 몇번만 받을 수 있음
  • 사용자의 기기에 Google Play Services가 설치되었다고 보장이 가능할 때, FLP의 사용을 강력히 추천함

Wifi-Manager

  • startScan 메소드는 시간당 몇 번만 후면 앱에 대해 전체 스캔을 수행함. 백그라운드 앱이 다시 호출하면 알아서 캐시된 결과를 가져옴.

결론

어쨌거나 저쨌거나, startForeground(int, Notification) 을 호출하는 것이 제일 간편한 방법이 되었다.
문제는, 고객사를 어떻게 설득해야 할지…

Android RelativeLayout 는 많은 비용을 소비하는가?

본 글은 본인의 Medium 에서 가져왔음을 밝힙니다.

이 글은 Android RelativeLayout에 대한 작은 고찰 에서 이어집니다.

해당 글을 올린 이후 약 4개월 뒤인 10월 말, Palette 1.0과 2.0의 레이아웃 측정 속도를 직접 비교할 수 있는 기회가 생겼습니다.

테스트 조건

  • Galaxy S6 실제 사용 기기
  • 사용된 계정: @WindSekirun
  • 조건: 주황색 + 어두운 색 배경, systrace 를 수집하는 10초 동안 계속 아래로 스크롤
  • 분석에 사용된 툴은 Systrace 라는 툴로, 안드로이드 SDK에서 기본 제공되고 있습니다.

주요 비교 이미지, 오른쪽이 1.0, 왼쪽이 2.0 입니다.

설명

Systrace 에서는 UI 스레드에서 해당 작업이 걸린 시간등을 분석하여 보여주는 역할을 합니다.

  • Scheduling delay: 다른 스레드에서 작업하는 동안 대기 시간
  • Path texture charn: 그래픽 텍스쳐를 기다리는 시간
  • Long View#draw(): 실제 뷰를 그리는 시간
  • Expensive measure/layout pass: RelativeLayout 가 레이아웃을 구성하는 데 걸리는 시간

이 중, 주로 UI 의 속도를 측정할 수 있는 기준은 ‘Expensive measure / layout pass’ 입니다.

  • Timespent — RelativeLayout 에 있는 childView 들이 표시되기 위해 소요되는 시간
  • measure — 변경된 뷰 크기 계산 시간
  • Layout — 변경된 뷰의 크기나 위치에 따라 실제로 레이아웃을 구성하는 시간

즉, 위 사진을 정리하면 아래와 같은 결과를 도출하였습니다.

 

안드로이드 앱 UI 을 만들 때, 가장 자유롭게 만들 수 있게 하는 레이아웃은 RelativeLayout 입니다. 해당 위젯의 이름대로 Relative, 상대적으로 설정할 수 있어 디자이너가 ‘이거 5dip만’ ‘이거 1dip만’ 옮겨달라고 할 때 쉽게 대응이 가능합니다.

하지만, RelativeLayout는 속한 레이아웃(Child)가 많으면 많을 수록 차일드 뷰의 위치나 크기를 계산하는 데에 많은 시간을 소요하게 됩니다. (당장 Palette 2.0만 해도 약 15개의 Child View를 가지고 있습니다.)

그래서 최근 글의 제목처럼 ‘과연 복잡한 레이아웃을 사용하려고 할 때, RelativeLayout는 많은 비용을 소비하는가?’ 라는 논란이 일어나고 있고, 이에 대응하기 위하여 다른 레이아웃을 이용하기도 합니다. (물론, 디자이너가 개발자한테 ‘디자이너님 이거 구성하려면 할 수는 있는데 이런 문제들이 있어요’ 라고 들었을 때 순순히 요청을 들어줄지는… (._.

결국 이런 문제는 UI를 디자인하는 디자이너와 기획자, 그리고 개발자간 협의가 필요합니다. 간단한 앱이라면 상대적으로 상관이 없지만, 무거운 앱이거나 실시간으로 올라오는 내용이 많은 앱의 경우 이러한 곳에서 발생하는 딜레이 하나하나가 최종적으론 앱 품질에 영향을 끼치기 때문입니다.

결론

  • 간단한 앱이거나, ListView를 사용하지 않거나 ListView를 표시하더라도 보여주는 뷰 갯수가 적다 -> 영향을 거의 미치지 않습니다.
  • 소셜 네트워크 앱 등 실시간으로 계속 갱신되야 하거나, 사진이 많이 올라온다 -> 영향을 매우 많이 미칩니다. 이 경우 개발자가 어느정도 신경을 써야 합니다.

물론, RecyclerView Prefetch 나 ViewHolder Pattern, Image Loader Library (Glide, Picasso, Fresco) 등을 이용하면 대부분의 렉을 최소화할 수 있지만 만약 개발하는 앱이 이런 것 들을 전부 적용했는데도 렉이 걸린다면 뷰 쪽을 살펴보는 것도 좋을 것 같습니다.