03. Keynote. 모바일 개발자를 위한 I/O 2018: Android의 새로운 기능 소개
발표자 :
Sean McQuillan (Developer Advocate Android, Google) Hadi Hariri (VP of Developer Advocacy, Jetbrains) Benjamin Frenkel (Product Manager, Google)
발표자료 :
Andrioid에서 개발자들은 무엇을 더 할 수 있을까요? 개발자에게 더욱 많은 것들을 가능하게 하는 Android 플랫폼의 새롭고 흥미진진한 최신 기능을 총정리해드립니다.
Android App Bundle
App Size를 줄이는 것은 중요함. (100 MB 이상의 앱의 설치율은 30% 정도 떨어짐)
Android App Bundle을 사용하여 동적 기능 모듈(dynamic feature modules)을 앱에 추가함.
Android Studio에서 개발자가 임의의 기기에 대해 앱에 필요한 모든 것, 즉 모든 언어, 모든 기기 화면 크기, 모든 하드웨어 아키텍처가 들어 있는 App Bundle 빌드
사용자가 해당 앱을 다운로드하는 시점에 Dynamic Delivery로 사용자의 기기와 일치하는 코드와 리소스만 전달
Kotlin
Most importantly, 95% of developers tell us they are very happy with using Kotlin for their Android development. And, the more developers use it, the more that number rises. The number of Play Store apps using Kotlin grew 6x in the last year. 35% of pro developers use it, and that number is growing each month.
데이터 바인딩에 임의의 POJO(Plain Old Java Object)를 사용 가능하지만, POJO를 수정하더라도 UI가 업데이트 되지는 않음. 데이터가 변경될 때 이를 알려주는 기능을 데이터 객체에 부여해야 데이터 바인딩을 제대로 활용할 수 있음. Observable 객체, 필드, 컬렉션이라는 세 가지 다른 데이터 변경 알림 매커니즘이 있음.
이러한 Observable 데이터 객체 중 하나가 UI에 바인딩되어 있고 데이터 객체 속성이 변경되면 UI가 업데이트 됨.
Observable 객체
Observable 인터페이스를 구현하는 클래스를 사용하면 바인딩이 바인딩된 객체 단일 리스너를 연결.
Observable 인터페이스에는 리스너를 추가/제거하는 매커니즘이 있지만, 알림은 개발자의 선택에 따라 결정됨.
더 쉽게 개발하도록 BaseObservable을 만들어 리스너 등록 매커니즘 구현함. 속성이 변할 때 이를 알릴 책임은 데이터 클래스 구현자에게 있으며, getter에 Bindable 주석을 할당하고 setter에서 이를 알림
View 가 View.OnAttachStateChangeListener에 대한 set 메서드를 사용하는 대신 리스너에 대한 add와 remove를 사용하기 때문에 위 예시는 좀 더 복잡함.
ListenerUtil 클래스는 Binding Adapter에서 이전의 리스너를 제거할 수 있도록 이들을 추적하는 데 도움이 됨.
인터페이스 OnViewDetachedFromWindow와 OnViewAttachedToWindow에 @TargetApi(VERSION_CODES.HONEYCOMB_MR1) 주석을 추가하면 데이터 바인딩 코드 생성기가 addOnAttachStateChangeListener(View.OnAttachStateChangeListener) 에 의해 지원되는 같은 버전인 HoneyComb MR1과 새 기기에서 실행 시 리스너만 생성되어야 한다는 점을 인식하게 됨.
변환기
객체 변환
바인딩 식에서 Object가 반환되면 자동 setter, 이름이 바뀐 setter, 맞춤 setter 중에서 setter가 선택됨.
android:text='@{map["firstName"]}'
or
android:text="@{map[`firstName`]}"
android:text="@{map['firstName']}"
리소스
일반적인 구문을 사용하는 식의 일부로 리소스에 액세스할 수 있음.
<!-- 일부로 리소스에 액세스할 수 있음 -->
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
<!-- 매개변수를 제공하여 형식 문자열과 복수형을 평가할 수 있음 -->
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
<!-- 복수형이 여러 매개변수를 취할 때는 모든 매개변수가 전달되어야 함 -->
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
변수 형식은 컴파일 시에 검사되므로, 변수가 android.databinding.Observable을 구현하거나 변수가 Observable 컬렉션인 경우 그 점이 형식에 반영되어야 함. 변수가 기본 클래스거나 Observable* 인터페이스를 구현하지 않은 경우 변수가 식별되지 않음.
다양한 구성(예: 가로/세로 모드)에 대한 다양한 레이아웃 파일이 있을 경우 변수들이 결합하므로 충동하는 변수 정의가 없어야 함.
생성되는 바인딩 클래스는 설명되어 있는 변수마다 각각 setter와 getter가 있음. 변수는 setter가 호출될 때까지 기본 Java 값을 취함. 즉, 참조 형식에는 null, int에는 0, boolean 에는 false 값을 취함.
필요에 따라 바인딩 식에 사용하기 위해 context 로 명명된 별도의 변수가 생성됨. context는 루트 View의 getContext()에서 가져온 값임. context 변수는 그 이름을 가진 명시적 변수 선언으로 재정의 됨.
사용자 지정 바인딩 클래스 이름
기본적인 Binding 클래스 : 레이아웃 파일명을 바탕으로 생성되며 파스칼 표기법으로 바꾼 후 "Binding"이 접미사로 붙음. 이 클래스는 모듈 패키지 아래 데이터 바인딩 패키지에 배치됨.
예를 들면
레이아웃 파일 : contact_item.xml
바인딩 클래스 : ContactItemBinding
모듈 패키지 : com.example.my.app
배치 위치 : com.example.my.app.databinding
data 요소의 class 특성을 조정하여 바인딩 클래스명을 바꾸거나 다른 패키지에 배치할 수 있음.
<!-- 바인딩클래스명 변경 -->
<dataclass="ContactItem">
...
</data>
or
<!-- 모듈 패키지 내에서 다른 패키지에 배치할 경우 -->
<dataclass=".ContactItem">
...
</data>
or
<!-- 전체 패키지가 제공될 경우 어떤 패키지든 사용 가능 -->
<dataclass="com.example.ContactItem">
...
</data>
Include
특성에 앱 네임스페이스와 변수 이름을 사용하여 포함하는 레이아웃에서 포함되는 레이아웃의 바인딩으로 변수를 전달할 수 있음.
애플리케이션 구성 요소가 시작되고, 앱에 실행 중인 다른 구성 요소가 없으면 하나의 실행 스레드로 앱의 Linux 프로세스 시작함. 기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 스레드에서 실행됨. 하지만 여러가지 구성 요소가 각자 별도의 프로세스에서 실행되도록 할 수도 있고, 어느 프로세스에든 추가 스레드를 만들 수 있음.
프로세스
기본적으로 같은 앱의 모든 구성 요소는 같은 프로세스와 스레드에서 실행되고 이를 바꾸면 안됨. 그러나 어느 프로세스가 특정 구성 요소에 속하는지 확인해야 할 경우 매니페스트 파일에서 확인 가능.(android:process 속성)
지원하는 요소 : <application>, <activity>, <service>, <receiver>, <provider>
안드로이드 시스템에서 어느 프로세스를 삭제할지 결정할 때, 이들의 상대적 중요성을 가늠함.
프로세스 수명 주기
안드로이드 시스템은 프로세스에서 실행되는 구성 요소와 해당 구성 요소의 상태에 기초하여 각 프로세스에 "중요 계층"을 부여함. 중요도가 낮은 프로세스가 먼저 제거됨.
중요 계층 5가지
포그라운드 프로세스 : 사용자가 현재 진행하는 작업에 필요한 프로세스 (Activity나 Foreground Service)
사용자가 상호작용하는 Activity를 호스팅할 경우
사용자와 상호작용하는 액티비티에 바인드된 Service를 호스팅할 경우
포그라운드에서 실행되는 Service를 호스팅할 경우
수명 주기 콜백 중 하나를 실행하는 Service를 호스팅할 경우 (onCreate(), onStart() 또는 onDestroy() )
가시적 프로세스 : 포그라운드 구성 요소는 없지만 사용자가 화면에서 보는 것에 영향을 미칠 수 있는 프로세스
맨 앞에 있지는 않지만 사용자에게 보이는 Activity 를 호스팅할 경우
눈에 보이는 액티비티에 바인딩된 Service 호스팅할 경우
서비스 프로세스 : startService() 메서드로 시작되었지만, 위 경우에 들어가지 않는 서비스를 실행 중인 프로세스일 경우
백그라운드 프로세스 : 현재 사용자에게 보이지 않는 액티비티를 보유한 프로세스. 이러한 프로세스는 LRU(최저 사용 빈도) 목록에 보관하여 사용자가 가장 최근에 본 액티비티가 있는 프로세스가 가장 마지막에 중단되도록 함.
빈 프로스세스 : 활성 앱 구성 요소를 보유하지 않은 프로세스.
프로세스의 등급은 다른 프로세스가 이에 의존할 경우 상승 가능.(의존 대상의 중요도가 의존할 대상과 같거나 높음)
스레드
앱이 시작되면 앱에 대한 실행의 스레드를 생성하며, 메인 스레드라 함. 이 스레드는 드로어블 이벤트를 포함한 UI 위젯에 이벤트를 발송하는 역할을 맡기도 하여 UI 스레드라고도 함.
시스템은 구성 요소의 각 인스턴스에 대해 별도의 스레드를 생성하지 않음. 같은 프로세스에서 실행되는 모든 구성 요소는 UI 스레드에서 시작되고, 구성 요소를 호출하는 시스템이 해당 스레드에서 발송됨.
시스템 콜백에 응답하는 메서드(onKeyDown()이나 생명주기 메서드 등)는 항상 프로세스의 UI 스레드에서 실행됨.
UI 스레드에서 DB 쿼리 등의 긴 작업을 수행하면 사용자가 보기에 앱이 중단된 것처럼 보이며, 약 5초이상 진행되면 ANR 대화상자가 표시됨.
어떤 경우에는 구현하는 메서드가 하나 이상의 스레드에서 호출되는 일이 있어 안전하게 작성해야 함.
주로 원격으로 호출할 수 있는 메서드이 있음. 바인드된 서비스 등.
IBinder에서 구현된 메서드가 IBinder가 실행되는 프로세스에서 호출될 경우, 해당 메서드는 호출자의 스레드에서 실행됨. 호출이 다른 프로세스에서 발생하면, 해당 메서드는 시스템이 IBinder 와 같은 프로세스에 유지하는 스레드 풀에서 선택된 스레드에서 실행됨.(프로세스의 UI 스레드에서 실행되지 않음)
위와 같은 이유로 스레드로부터 안전한 구현이 필요함.
프로세스 간 통신
안드로이드는 원격 프로시저 호출(RPC)을 사용한 프로세스 간 통신(IPC) 매커니즘을 제공함.
여기서 메서드는 액티비티나 다른 앱 구성 요소에 호출되지만 원격으로 실행되고, 결과는 모두 호출자에게 되돌려 보냄.
메서드 호출과 데이터는 운영 체제가 이해할 수 있는 수준으로 분해되고, 로컬 프로세스와 주소 공간에서 원격 프로세스와 주소 공간으로 전송되어 다시 결합되어 호출에 응답하게 됨. 그런 다음 반환 값이 반대 방향으로 전송됨.
안드로이드가 이와 같은 IPC 트랜잭션을 수행하는데 필요한 코드를 제공하므로, 개발자는 RPC 프로그래밍 인터페이스를 정의하고 구현하는 데만 집중하면 됨.