레이아웃 비동기로 인플레이트하기



안드로이드 서포트 라이브러리 리비전 24부터 레이아웃을 비동기로 인플레이트 할 수 있는 클래스가 추가되었다.

AsyncLayoutInflater

어싱크 레이아웃 인플레이터를 사용하면 무거운 인플레이션이 수행되는 동안 UI스레드에 대한 응답성을 보장할 수 있다. 기존의 레이아웃 인플레이터를 사용하는 경우 UI스레드에서 인플레이션 작업이 수행되어 무거운 레이아웃의 경우 화면이 끊기거나 심한 경우 ANR이 발생된다. 이러한 문제를 보완하기 위해 AsyncLayoutInflater를 제공한다. 별도의 스레드에서 인플레이션을 수행하고 생성된 뷰를 UI스레드로 콜백 받을 수 있다.

사용방법은 레이아웃 인플레이터와 동일하게 AsyncLayoutInflater로 클래스를 생성 후 inflate() 메서드를 호출하면 된다.

AsyncLayoutInflater(@NonNull Context context)

inflate() 메서드 호출 시 인플레이트 대상의 레이아웃 리소스와 인플레이트 된 뷰를 속하게 하고 싶은 뷰 그룹과 콜백 받기 위한 OnInflateFinishedListener의 파라미터가 필요하다.

inflate(@LayoutRes int resid, @Nullable ViewGroup parent, @NonNull OnInflateFinishedListener callback)

생성이 완료되면 OnInflateFinishedListener의 onInflateFinished()를 통해 뷰를 전달된다.

AsyncLayoutInflater은 UI스레드가 아닌 별도의 스레드를 통해서 레이아웃을 인플레이트 하기 때문에 무거운 레이아웃으로 인해 화면의 응답성이 떨어지는 경우 사용할만하다. 액티비티 시작 시 레이아웃이 너무 큰 경우라도 화면의 전환 속도가 느리다면 로딩화면을 심플하게 구현 후 다음 비동기 처리를 통해 뷰를 생성하는 것도 하나의 방법이 될 수 있을 것이다.

구현 예시



안드로이드 7.1 앱 바로가기 기능 구현



안드로이드 7.1에 앱 바로가기(Shortcut)기능이 추가되었다. 구글의 픽셀런처에만 지원하고 있지만 앞으로 안드로이드 7.1이 올라가면서 제조사의 런처에서도 지원될 가능성이 높다.

픽셀런처의 홈 화면에서 아이콘을 길게 누르면 앱 바로가기 목록이 펼쳐지며, 이동을 위해 움직이게 되는경우 비활성화 되는 방식이다. 애플 iPhone의 3D터치와 기능은 같으나 작동방법은 소프트웨어로 구현되었다는 점에서 다르다.

구현방법은 아주간단하다. 구글에서 정해둔 규약을 잘 따라 메니페스트에 메타데이터를 정의하면 된다. 에버노트의 경우 검색 바로가기, 노트바로 작성하기등 앱에 진입하지 않고 빠르게 특정한 작업을 할 만한 사항들의 기능을 넣었다.

정해진 이름으로 메타 데이터에 앱 바로가기를 위한 리소스를 추가후 바로가기 아이템을 xml에 하나씩 정의하면된다. 앱에 넣을 기능의 커스텀 스킴을 정의 해두었다면 intent의 action에 스킴을 호출하면 아주 간단하게 추가할 수 있다. 그렇지 않은 경우 실행할 Activity 클래스명을 적어주면된다.

동적 구현방법
xml을 통해 정적으로 구현할 수도 있으나 상황에 따라 앱 바로가기 아이템을 추가하거나 삭제 하고 싶은 경우 ShortcutManager를 통해서 추가하거나 삭제 또는 업데이트할 수 있다.

앱 바로가기 기능을 통해 앱에 진입 하지 않도 빠르게 검색을 한다거나 재생목록을 재생하는등 다양하게 활용하여 사용편의성을 높일 수 있다. 이미 이런 사용성을 생각한 개발자라면 런처 바로가기 기능을 통해 이미 구현을 했을 것이고 앱 바로가기 기능은 쉽게 구현 할 수 있을 것이라 생각된다.

참고: https://developer.android.com/preview/shortcuts.html


Stable Id를 이용한 RecyclerView 성능 향상법

RecyclerView는 ListView를 완전히 대체할 수 있을 만큼 기능과 성능이 크게 향상되었다. ListView에서도 어떻게 하면 끊김 없이 빠른 스크롤을 지원할까라는 고민을 해왔었고, RecyclerView를 사용하다 보면 똑같은 고민을 또 하게 될 것이다.

ViewHolder라는 패턴을 통해 ListView의 성능을 RecyclerView에서 크게 향상할 수 있었다. 재활용하는 뷰들의 클래스를 View 태그 또는 Array에 저장하고 필요할 때 바로 가져와서 사용하는 방법으로 성능을 크게 향상하였다. ListView에서 재활용되는 뷰를 해당 포지션에 맞게 가져오는 곳에서 성능을 향상할 수 있었다면, RecyclerView는 가져온 View에 데이터를 바인드 시 최적화할 수 있는 방법을 제공하고 있다.

HasStableIds사용을 통해 데이터 바인드 시 onBindViewHolder()를 최적화 되게 호출할 수 있다. 아래 2가지 중 하나만이라도 해당한다면 성능을 크게 향상할 수 있다.

  • 똑같은 데이터가 반복적으로 나타는 리스트이다.
  • notifyDataSetChanged를 자주 호출한다.

HasStableIds는 Adapter.setHasStableIds(boolean)을 통해 설정할 수 있으며, 사용하는 경우 어댑터의 getItemId(int)를 반드시 구현해야 작동한다.

getItemId(int)를 통해 해당 아이템은 고정된 상태로 설정된다. 예를 들어 아래와 값을 반환되게 구현했다면 어떤 성능적인 변화가 일어날까?

position
return
0
100
1
200
2
300
3
100
4
400
5
500

onBindViewHolder(view, int)는 포지션이 0, 1, 2, 4, 5 만 호출된다. 3번은 0번째 포지션에서 같은 고정된 ID를 반환했기 때문에 같은 데이터로 인식하여 onBindViewHolder(view, int)가 호출되지 않는다. 같은 데이터임을 알고 데이터 바인드를 할 필요가 없기 때문에 호출되지 않으며 그만큼 성능은 향상된다.

position
return
0
100
1
200
2
600
3
300
4
100
5
400
6
500

포지션 2번에 데이터를 추가하고 notifyDataSetChanged()를 호출하였다. 이때 onBindViewHolder(view, int)는 현재 보이고 있는 포지션이 모두 호출되지만 StableId를 사용하게 된다면 이미 호출된 고정된 ID를 제외한 위치가 호출된다. notifyDataSetChanged()를 하였음에도 변경되는 ID만을 골라 해당 포지션만 onBindViewHolder(view, int)를 호출하게 됨으로 그만큼 성능은 향상된다.


Android Support Annotations

안드로이드 개발 시 Annotation을 사용할 수 있도록 서포트 라이브러리를 지원한다. Annotation을 통해 코드의 설명을 간단명료하게 표시할 뿐 아니라 제약할 수도 있다. 즉, 해당 메서드나 멤버 변수를 다른 사람 또는 내가 재사용할 때 필요한 규약을 지정하여 예외적인 사항을 컴파일 에러를 통해 바로 확인이 가능하다.

왜 사용해야 하는가?

런타임시 발생되는 예외사항을 빌드 전 컴파일 에러를 통해 대부분 막을 수 있기때문에 앱의 개발 속도 향상은 물론 코드를 설명하기위한 긴 설명의 주석을 대체할 수 있다. 또한 다른 사람이 작성한 코드를 사용하거나 반대로 내가 작성한 코드를 다른사람이 사용하거나 분석할때 시간을 줄 일 수 있다.

안드로이드 Annotation 라이브러리 추가(build.gradle)

dependencies { 
    compile 'com.android.support:support-annotations:24.2.0' 
}

@NonNull / @Nullable

메서드의 파라미터에 null이 허용되는 경우도 있고, 반드시 값이 필요한 경우도 있다. 자신이 개발한 코드가 아니거나 또는 개발을 했음에도 코드를 살펴보고 null체크를 하는지 판단을 해야 하는 경우 사용하면 된다. 파라미터에 @NonNull을 사용하면 null값을 허용하지 않으며, 임으로 null을 넣는 경우 컴파일 에러가 난다. 반대로 메서드 내부적으로 null체크를 하여 해당 기능을 분기하는 경우 null이 허용 가능함으로 @Nullable를 사용하면 된다.
 
no-annotations
 
nonnull

@CheckResilt

메서드의 리턴 값을 필수적으로 받아야 할 때 사용하면 된다.
 
checkreturn.png

@StringRes / @DrawableRes / @ColorRes / @(Etc)Res

메서드에 전달 인자가 리소스 주소인 Integer인 경우 해당 리소스가 어떠한 형태인지를 미리 알려줄 수 있는 기능이다. 예를 들어 TextView의 setText(int) 메서드의 경우 String 리소스 주소를 넣어야 한다는 것을 명시할 수 있다. String리소스 주소가 아닌 Drawable리소스나 임의의 Integer값을 넣는 경우 컴파일 에러를 통해 알려준다.
 
stringres.png

@MainThread / @UiThread / @WorkerThread

메서드가 실행될 때 해당 수행되어야 할 스레드를 명시할 수 있다. AsyncTask의 스레드에서 UI를 변경하는 작업을 하는 경우 에러가 나는데, 이런 경우를 사전에 알려준다.
 

@Keep

빌드시 프로가드를 통해 난독화가 되어 사용 불가능 한 클래스의 경우 난독화를 예외처리해주어야 한다. 본인이 개발하지 않는 경우 이런 히스토리를 알기는 쉽지 않다. 이런 경우 @Keep를 통해 난독화 예외처리를 해야 하는 클래스인지 명시적으로 알려줄 수 있다.

이외에도 @CallSuper, @RequiresPermission, @Size 등 다양한 Annotation이 존재한다. 좀 더 알아보기 위해 아래 사이트를 참고하자.

참고:


구글 I/O Extended Seoul에서 발표한 Android N

구글 IO때 예상된 일정에는 차질 없다. 빠른 시일내에 N테스트를 해야한다.


스크린 줌 설정

런타임시 화면 밀도가 바뀔 수 있으니 테스트해봐야 한다. Bitmap을 캐쉬해두는 경우 문제가 될수 있다. 고사양 기기가 sw320dp를 가질 수도 있으니 염두해둔다.


멀티스크린

가로/세로 모드를 반드시 구현해라.
멀티스크린 모드로 인해 화면이 작아 질수 있으니 최소 220dp(w/h)가 지원되도록 개발해야한다.
사용자가 화면크기를 늘리거나 줄일떄 기본으로 Activity가 재 생성되며, 필요에 따라 onConfigurationCahaged() 이벤트로 처리 할 수 있다.
attr#resizedableActivity값은 기본적으로 TRUE이며 필요에 따라 속성을 변경 하면된다. 단, Root Activity의 FLAG의 속성에 따라 작동 유무가 판단된다.

targetSdk 24를 쓰면서 멀티스트린 지원하지 않게 하는 팁!
* targetSdk = 24
* resizedableActivity = false
* launchMode = singleInstance | singleTask

앱간의 Drag and Drop API도 있으니 필요시에 사용가능하다.


배터리 최적화 모드(Doze)

Doze on the Go (light Doze모드) 새기능 추가
움직임이 있더라도 사용자가 폰을 일정시간 사용하지 않으면 doze모드 진입
네트워크 작업 중단, 잡스케줄러 지연
이미 Doze모드에 최적화된 앱은 별도로 신경 쓸 필요는 없다.

테스트

#adb shell dumpsys deviceidle step light

Doze모드 whitelist에 추가 하는 API도 있는데 잘못 하용하면 앱이 내려갈 수 있다.

참고: 안드로이드 앱 배터리 최적화 무시방법


메모리 최적화 모드

Broadcast 이벤트를 처리시 많은 앱에서 동시에 처리 하는경우 문제가 된다. 동시에 수많은 앱에서 이벤트를 처리 하기때문에 성능적으로 문제가 되며, 특히 사용하고 있지 않는 앱도 이벤트를 받는등 불필요한 작업이 있을 수 있다.

런타임시에 등록한 경우는 작동되나 안드로이드 메니페스트에서 선언한 Broadcast의 경우 작동하지 않는다.
CONNECTIVITY_CHANGE, NEW_PICTURE, NEW_VIDEO는 메니페스트에서 선언한 Broadcast의 경우 N부터 작동하지 않는다.

테스트

#adb shell cmd appops set <package-name> RUN_IN_BACKGROUND igenore

빠르고 유연한 ContraintLayout

ContraintLayout?

ContraintLayout은 2016 구글 I/O를 통해 발표된 안드로이드의 새로운 레이아웃이다. 안드로이드 스튜디오(2.2 Preview2 부터)에 내장된 새로운 레이아웃 에디터(Blue Print)와 연동을 통해 이전의 레이아웃보다 쉽게 구성할 수 있다. 뷰 계층의 깊이와 복잡성을 해결하기 위해 ContraintLayout이 만들어졌으며, 앱의 UI렌더링 속도를 높일 수 있을 뿐만아니라 다양한 기기의 해상도에 최적화된 UI를 쉽게 개발 할 수도 있다. 안드로이드 서포트 라이브러리를 통해 사용가능하며 API 레벨 9부터 사용가능하다.

build.gradle에서 해당 라이브러리를 추가한다.

compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha2'

ContraintLayout 속성

  • layout_constraintTop_toTopOf
  • layout_constraintTop_toBottomOf
  • layout_constraintBottom_toTopOf
  • layout_constraintBottom_toBottomOf
  • layout_constraintLeft_toTopOf
  • layout_constraintLeft_toBottomOf
  • layout_constraintLeft_toLeftOf
  • layout_constraintLeft_toRightOf
  • layout_constraintRight_toTopOf
  • layout_constraintRight_toBottomOf
  • layout_constraintRight_toLeftOf
  • layout_constraintRight_toRightOf
  • layout_constraintCenterX_toCenterX
  • layout_constraintCenterY_toCenterY
  • layout_constraintBaseline_toBaselineOf

속성을 보면 알겠지만 많은 속성들로 인해 복잡하고 뷰들 간의 관계에 대한 속성들이 대부분이다. 자식뷰들관의 관계를 연결해주는 것을 보면 RelactiveLayout와 비슷해보이지만 ContraintLayout은 자식들과 부모관의 관계에 대한 정렬및 배치 방식과 좌표가 아닌 비율을 통해 위치가 지정되는등 다양한 기능을 가졌다.

 

레이아웃 에디터

개발자라면 그동안 레이아웃을 XML편집기를 이용하여 텍스트로 작성했을 것이다. 하지만 ContraintLayout은 무수히 많은 속성과 타겟 뷰의 ID를 값으로 주는 만큼 레이아웃구조를 텍스트로 작성하거나 또는 읽을때 예전보다는 확연히 힘들것이다. 이렇게 복잡한 레이아웃을 텍스트가 아닌 새롭게 선보인 디자인 툴(Blue Print)을 이용하면 훨씬 쉽고 간단하게 작성할 수 있다.

레이아웃 에디터는 UI설계를 하는 Design과 뷰들관의 관계를 보여주는 BluePrint 화면 2개가 나타난다. 이 2개 화면을 동시에 보거나 따로보는 방법은 레이아웃 에디터의 상단 아이콘을 이용하면 전환할 수 있다.

be44fb8e685150a5565f19c94e36e954
Design 표시 아이콘: 한번 누르면 디자인 화면으로 전환 되며 또한번 누르면 BluePrint 화면을 동시에 2개가 나타난다.

a72c79e2a3bf45f38413fec30f3dac36
BluePrint 표시 아이콘: 한번 누르면 BluePrint화면으로 전환되며 또한번 누르면 디자인 화면과 동시에 2개가 나타난다.

 

레이아웃 편집시 쓰이는 아이콘

b25f376c52508c900dbab5a8bf5d68bf
BluePrint화면의 뷰들간의 관계에 대한 정보를 숨기고 보일 수 있다.

f6ea28b2afd03030cfeed10d9b3f165a
새로운 뷰를 드래그 하는 경우 다른뷰들간의 관계를 자동으로 연결할 수 있다.

3fd3b60139267dc5bd0e2abce72f6f30
관계를 정보를 모두 삭제 한다.

4d928284744c25094798de88e6cce11d
자동으로 관계에 대한 정보를 연결한다.

7c2b5cfb7453a9614c2e5507998eff27
마진값의 단위를 선택한다. 0, 8, 16dp로 전환이 가능하다.

 

레이아웃 내에서 쓰이는 툴


하나의 뷰를 나타내며 가로/세로 크기와 다른뷰와의 관계에 대해 화살표로 설정할 수 있는데 자세히 알아보자.

99a8ebe4d5ada56e4abacb2dfedc9d50
뷰 크기 변경 컨트롤: 각모서리 가장자리에 있는 네모 모양을 통해 뷰의 크기를 늘리고 줄일 수 있다.

924dedda9eb89c0ab8f6a268c4558e13
관계 설정 컨트롤: 둥글게 생긴 부분으로 다른 뷰들관의 관계를 지정할 수 있다. 가로축의 컨트롤은 다른뷰의 가로축에만 연결되며, 세로축은 다른뷰의 세로축에만 연결된다. 이미 관계가 지정되어 있을때 클릭하면 해제된다.

a6b25ad94ce53ddfedbf2d689a506b78
베이스 라인 컨트롤: 뷰의 기본 라인을 맞춘다. 베이스 라인은 뷰내의 실제 컨텐트가 배치해있는 위치이다. 해당 컨트롤을 선택하기위해서는 커서를 몇초간 위치해있어야 한다.

 

ContraintLayout에서 뷰크기 지정

뷰의 크기는 우리가 알고 있듯이 고정된크기, 뷰의 컨텐츠에 맞게 지정되는 방식, 부모크기를 따라가는 방식이 있다. 이를 UI적으로 표현하여 좀거 쉽게 설정가능하다.

e8124ddde04b2aa76e42f79582123218

7300702340c7018202fbdcf57af5b525
고정된크기: 뷰의 사이즈가 고정되어 있다.

709b28b53490f5fb5f6dcdc5380a6fb3
부모 컨텐츠 크기: MATCH_PARENT방식으로 작동한다.

af2e72fd3b0d4188c61e06da211f9835
뷰 컨텐츠 크기: 뷰의 크기에 따라 크기가 설정된다.

edcfce9f89f19fb900de367212f55c3a
수평및 수직 정렬: 부모 뷰와 자식뷰가 연결되어 있는 경우 정렬을 퍼센테이지로 설정할 수 있다. LinearLayout의 weight와 비슷하다고 생각하면 쉽다.

팁!

처음 ContraintLayout을 접해보면 생각보다 힘든 작업이 될가능성이 높다. 이것저것 버튼을 눌러서 레이아웃을 맞추기에 생각보다 번거롭기 때문이다. 그래서 한가지 팁을 소개해보겠다. 먼저 뷰들간의 관계를 연결하지 말고 배치만으로 작업을 한다. 연결 되어 있다면 모두 끊은 상태에서 배치작업을 한뒤 자동 관계아이콘을 클릭하여 연결을 자동으로 구성 후 보정작업을 하거나 간단한 경우 직접 연결해준다.

 

ContraintLayout은 알파단계로 약간의 버그가존재하며 디자인툴이 생각보다 빨리 움직이지 않고 한번씩 다운되는등의 문제점이 발생된다. 하지만 기존의 너무 깊은 레이아웃을 구조로 앱의 성능에 문제가 생긴점을 해결해줄 속 시원한 레이아웃이 될것 같다. 그리고 BluePrint로 인해 디자이너도 툴을 쉽게 익히고 사용하는데 무리가 없을것 같다. 기존의 계층구조가 복잡한 레이아웃이 있다면 조금씩 바꿔나가는 것에 추천한다.

 


jCenter로 안드로이드 라이브러리 간단하게 배포하기

안드로이드 스튜디오로 오면서 Gradle로 인해 외부라이브러리 사용이 훨씬 편해졌다. 예전에는 JAR파일이나 라이브러리 프로젝트를 직접 다운받아 프로젝트에 Import해서 라이브러리를 사용했다. 안드로이드 스튜디오의 Gradle파일에서 Dependencies에서 라이브러리명만 작성하면 연결되어 있는 저장소에서 파일을 받아오게 된다. Maven Repository중 가장쉽고 간단한 jCenter가 있으며 최근 안드로이드 스튜디오에서 새로운 프로젝트를 생성하면 기본 저장소로 설정되어 있기도하다.

 

jCenter에 라이브러리 배포하기

1) jCenter를 운영하는 bintray.com 사이트에 가입한다.

2) 가입 후 다양한 Repository가 있는데 우리는 Maven을 사용할 것이다.

3) Maven에서 Add New Package 버튼을 눌러 새로운 패키지 만들어두어도 되나, 아래에서 업로드시 패키지가 없다면 새롭게 생성해주기 때문에 만들지 않아도 된다. 그리고 Import from Github버튼을 눌러 Github에서도 가져올 수 있다. 단, 정보만 가져온다.

 

4) 이제 사이트에서 설정하는 것은 모두 끝났으며, 안드로이스 스튜디오의 라이브러리 프로젝트에서 novoda:bintray-release를 이용하여 빌드후 bintray에 바로 배포하는 라이브러리를 사용한다.

 

5) 프로젝트의 build.gradle에 아래와 같이 novoda:bintray-release라이브러리를 추가하다. 이것을 활용하여 빌드된 파일들을 bintray로 업로드한다.

buildscript{
   repositories{
      jcenter()
   }

   dependencies{
      classpath 'com.android.tools.build:gradle:2.1.0'
      classpath 'com.novoda:bintray-release:0.3.4'
   }
}

 

6) 라이브러리의 모듈에 있는 build.gradle에 업로드할 사용자 정보를 작성한다. 참고로 artifactId는 소문자로 작성하고 단어사이에는 하이픈(-)을 넣어 컨벤션을 지키도록하자.

apply plugin: 'com.android.library'
apply plugin: 'com.novoda.bintray-release'

publish {
   userOrg = 'kmshack'
   groupId = ‘com.kmshack.library'
   artifactId = ‘android-exception-tracker'
   publishVersion = '1.0.2'
   desc = 'Android Exception Tracker'
   website = 'https://github.com/kmshack/ExceptionTracker'
   issueTracker = "https://github.com/kmshack/ExceptionTracker/issues"
   repository = "https://github.com/kmshack/ExceptionTracker.git"
}

 

7) 터미널에가서 프로젝트의 위치에서 아래명령으로 빌드를 수행해보자.

$ ./gradlew clean build bintrayUpload -PbintrayUser=BINTRAY_USERNAME -PbintrayKey=BINTRAY_KEY -PdryRun=false

*BINTRAY_USERNAME은 아이디이며, BINTRAY_KEY는 Bintray 프로필 페이지의 API Key메뉴에서 값을 확인 할 수 있다.

 

8) 빌드가 끝나면 Bintray 사이트로 가면 해당 패키지가 만들어 졌고, 관련된 라이브러리 파일들도 업로드된 것을 볼 수 있다.

 

9) 여기까지가 Bintray 저장소에 라이브러리를 올린 것이고, 기본 저장소인 jCenter에는 아직 등록 되지 않았다. jCenter에 등록은 아주 간단하다. 패키지 정보 화면에서 Add to jCenter버튼을 눌러 확인만 하고 기다리면 jCenter저장소와 연동된다.

 

10) 연동이 된다면 Add to jCenter버튼은 없어지고, jCenter아이콘이 나타나며, 이제 안드로이드 스튜디오에서 사용해보면 된다.
compile ‘<groupId>:<artifactId>:<publishVersion>’ 이런식의 주소가 붙여지니 테스트해보자.