devlog of ShinJe Kim

[TIL] 2019-10-21

|

Today I Learned

서비스 라이프사이클

서비스 수명주기는 아래 두 경로중 하나로 실행된다.

  1. Started Service
    • 다른 컴포넌트에서 startService()를 호출할 때 서비스가 create된다.
    • stopSelf()를 스스로 호출하여 서비스를 stop하지 않는 이상 계속 실행된다.
    • 다른 컴포넌트에서 서비스를 중지(stop)하고 싶을 시 stopService()를 호출하면 된다.
    • 서비스가 stop되면 시스템은 서비스를 destroy한다.
    • caller(클라이언트)에게 결과를 return하지 않는다.
  2. Bound Service(연결 타입의 서비스)
    • 다른 컴포넌트에서 bindService()를 호출할 때 서비스가 create된다.
    • 클라이언트는 IBinder 인터페이스를 통해 서비스와 통신한다.
    • 클라이언트는 unbindService()를 호출함으로써 연결을 close할 수 있다.
    • 다수의 클라이언트가 동일한 하나의 서비스에 bind하는 것이 가능하며,
    • 모든 클라이언트가 unbind될 때 시스템은 서비스를 destroy한다.
    • 이 서비스는 스스로 중단하지 않는다.
    • caller(클라이언트)에게 결과를 return한다.

위의 두 가지 경로는 완전히 분리된 것이 아니다. startService()로 시작된 서비스를 bind할 수도 있다.

예를 들어, 음악을 재생하게 하는 Intent로 startService()를 호출함으로써 백그라운드 음악 재생을 시작할 수 있다. 이후에 사용자가 플레이어를 제어하고 싶다거나, 현재 재생중인 음악에 대한 정보를 알고 싶다면, 액티비티는 bindService()를 호출하여 서비스를 bind할 수 있다. 이러한 경우에는, 모든 클라이언트가 unbind되기 전까지는 stopService()혹은 stopSelf()를 호출하더라도 서비스를 멈출 수 없다.

두 가지 서비스의 차이점(각각 언제 사용해야 할까?)

  • bound service: association/client/activity가 연결되어 있을때에만 실행됨. 예) 미디어 플레이어의 UI를 destroy 한다면 플레이어 service도 close됨.
  • started service: stopSelf()를 스스로 호출해야 중단됨. 예) 프린트를 할 때 우리가 프린터 앱을 닫고 나가더라도 프린트는 계속 실행됨.

출처: stackOverflo - when-is-smart-to-use-bindservice-and-when-startservice

[TIL] 2019-10-17

|

Today I Learned

  • 코틀린에서 !의 의미는? (출처: stackoverflow - single exclamation mark in kotlin)
    • 코틀린에서 !(느낌표 하나)로 끝나는 타입이나 클래스는 플랫폼 타입(Platform Typed) 이라고 한다. 이는 널 정보를 포함하고 있지 않은 이전의 자바 코드와 호환되는 것을 사용할 때 볼 수 있다.

    • 플랫폼 타입을 어떻게 다룰 수 있을까? 플랫폼 타입을 nullable하게도 할 수 있고 non-null로도 할 수 있다. 컴파일러는 이러한 타입의 모든 메소드를 허용할 것이며, 이를 어떻게 사용하는지는 온전히 개발자의 몫이다. 만약 해당 값이 null일 것이 예상되면, 메소드를 호출하기 전에 null 검증을 해야한다. 만약 null이 아닐 것이라는 것이 확실하면 바로 쓰면 되지만, 이 가정이 잘못되었을 경우 자바에서는 예외가 발생할것이다.

  • 프로세스끼리는 서로 독립되어 있어서 둘이 데이터를 교환하기 위해서는 IPC라는 것이 필요함
  • 프로세스 안에 스레드가 있는 것처럼 프래그먼트는 액티비티의 context안에 존재함
  • fragment ktx 라이브러리를 사용해서 fragement replace하는 보일러플레이트를 없앨것
  • 객체 생성의 책임이 어디에 있느냐에 따라 사용할 수 있는 방법이 달라짐
    • 프레임워크가 생성하도록 할 때에는 액티비티 넘길 때 객체가 아닌 클래스(객체의 틀)를 넘기는것처럼 넘김. 이러면 데이터를 전달 할 때 번들 같은 것을 써서 넘겨야 함.
    • 내가 직접 생성하면 해당 객체 안에 직접 데이터를 넣을 수 있음.

TODO

  • FragmentTransaction 속성 다 공부해서 정리하기
  • 프래그먼트 속성 정리 문서만 정리하지 말고 샘플 만들어보기.
  • context, layoutmanager, viewtype 정리!!

[TIL] 2019-10-16

|

Today I Learned

  • kotlin에서 lateinit varby lazy {...}의 차이점은? (출처: stackoverflow - kotlin-property-initialization-using-by-lazy-vs-lateinit)
    • lazy { ... } 위임자(delegate)는 val 프로퍼티에만 쓰일 수 있는 반면 lateinitfinal 필드에 컴파일될 수 없으며 불변성을 보장할 수 없으므로 var에만 쓰일 수 있습니다.
    • lateinit var는 값을 저장하는 backing fields를 가지고 있습니다. by lazy { ... }는 값이 계산되어 저장되는 곳에 위임 객체(delegate object)를 생성하며 클래스 객체 내부의 위임자 인스턴스(delegate instance)에 대한 참조를 저장하고, 위임자 인스턴스와 함께 수행되는 프로퍼티의 getter를 생성합니다. 따라서 클래스에 backing field가 필요하면 lateinit를 쓰세요.
    • val과 더불어 lateinit은 널을 참조할 수 있는 프로퍼티나 자바의 기본 타입에 쓰일 수 없습니다. null은 초기화되지 않은 값에 쓰이기 때문입니다.
    • lateinit var은 객체가 보이는 곳 어디에서나 초기화할 수 있습니다(예: 프레임워크의 코드 내부). 또한 단일 클래스의 서로 다른 객체에 대한 다중 초기화(multiple initialization)가 가능합니다. 반대로 by lazy { ... }는 서브클래스의 프로퍼티를 오버라이딩 함으로써만 바뀔 수 있는 프로퍼티의 유일한 초기화를 제공합니다. 만약 미리 알 수 없는 프로퍼티를 외부에서 초기화하고 싶다면 lateinit을 사용하세요.
    • by lazy{ ... }를 사용한 초기화는 기본적으로 스레드에 안전하고 initializer가 최대 한 번만 호출되도록 보장해줍니다(하지만 이는 다른 lazy를 오버로딩)을 함으로써 바꿀 수 있습니다). lateinit var의 경우, 멀티 스레드 환경에서 프로퍼티를 올바르게 초기화하는지의 여부는 개발자의 코드에 달려있습니다.
    • Lazy 인스턴스는 저장되고 전달될 수 있으며 다수의 프로퍼티에 사용될 수도 있습니다. 이와 반대로, lateinit var는 그 어떤 런타임 상태도 저장하지 않습니다(초기화되지 않은 값의 필드에만 null을 할당합니다).
    • Lazy 인스턴스에 대한 참조를 가지고 있으면, isInitialized()는 해당 인스턴스가 이미 초기화되었는지 아닌지를 체크할 수 있도록 합니다(그리고 당신은 위임된 프로퍼티에서 반영하여 이러한 인스턴스를 얻을 수 있습니다). lateinit 프로퍼티가 초기화되었는지 아닌지 확인하려면 코틀린 1.2부터 지원되는 property::isInitialized를 사용하면 됩니다.
    • by lazy{ ... }를 통해 전달되는 람다는 클로저에 의해 사용되는 context에서 참조를 가져올 수 있습니다. 그런다음 해당 참조를 저장하고, 프로퍼티가 초기화 되는 경우에만 release합니다. 이로 인해 안드로이드 액티비티와 같은 객체의 계층구조가 너무 오랫동안 release되지 않거나 혹은 아예 되지 않는 상황이 발생할 수 있습니다. 따라서 initializer 람다 내부에서 사용하는 것을 주의해야합니다.
  • 이펙티브 자바 아이템 83. “지연 초기화는 신중히 사용하라”
    • 지연 초기화는 양날의 검이다. 클래스 혹은 인스턴스 생성 시의 초기화 비용은 줄지만 그 대신 지연 초기화하는 필드에 접근하는 비용은 커진다. 지연 초기화하려는 필드들 중 결국 초기화가 이뤄지는 비율에 따라, 실제 초기화에 드는 비용에 따라, 초기화된 각 필드를 얼마나 빈번히 호출하느냐에 따라 지연 초기화가(다른 수많은 최적화와 마찬가지로) 실제로는 성능을 느려지게 할 수도 있다.

    • 멀티스레드 환경에서는 지연 초기화를 하기가 까다롭다.

    • 그럼에도 지연 초기화가 필요할 때는?

      해당 클래스의 인스턴스 중 그 필드를 사용하는 인스턴스의 비율이 낮은 반면, 그 필드를 초기화하는 비용이 클 때. 이 떄에는 지연 초기화가 제 역할을 해 줄 것이다. (하지만 안타깝게도 정말 그런지를 알 수 있는 유일한 방법은 지연 초기화 적용 전후의 성능을 측정해보는 것이다.)

    • 대부분의 상황에서 일반적인 초기화가 지연 초기화보다 낫다.
    • 성능 때문에 혹은 위험한 초기화 순환을 막기 위해 꼭 지연 초기화를 써야 한다면 올바른 지연 초기화 기법을 사용하자. 인스턴스 필드에는 이중검사 관용구를, 정적 필드에는 지연 초기화 홀더 클래스 관용구를 사용하자. 반복해 조기화해도 괜찮은 인스턴스 필드에는 단일검사 관용구도 고려 대상이다.

[TIL] 2019-10-15

|

Today I Learned

  • 안드로이드의 PendingIntent란?

    다른 응용 프로그램에 PendingIntent를 제공하면, 다른 응용 프로그램이 해당 프로그램과 동일한 권한과 ID를 가진 것처럼 지정한 작업을 수행할 수 있는 권한이 부여된다.

[Android] Media 앱 아키텍처

|

이 글은 안드로이드 공식문서를 공부하며 정리한 글입니다.

Media 앱 아키텍처 개요(Media app architecture overview)

이 섹션에서는 UI를 위한 미디어 컨트롤러와, 실제 플레이어를 위한 미디어 세션을 어떻게 구분하는지에 대해 설명합니다.

여기에서는 두 가지의 미디어 애플리케이션 아키텍쳐를 다룹니다.

  • 클라이언트/서버 구조: 오디오 앱에 적합합니다.
  • 단일 액티비티 구조: 비디오 플레이어에 적합합니다.

또한, 어떻게 미디어 애플리케이션이 하드웨어에 응답하고, 오디오 출력 스트림을 사용하여 다른 애플리케이션에 협력하는지에 대해 설명합니다.

플레이어와 UI(Player and UI)

오디오 혹은 비디오를 재생하는 멀티미디어 애플리케이션에는 두 가지 부분이 있습니다.

  • 디지털 미디어를 받아 비디오/오디오로 렌더링하는 플레이어
  • 플레이어를 실행하고 선택적으로 플레이어의 상태를 표시하는 transport control이 있는 UI

ui-and-player.png

안드로이드에서는 아예 처음부터 자신만의 플레이어를 만들 수도 있고, 아래의 옵션 중 하나를 선택할 수도 있습니다.

  • MediaPlayer클래스가 일반적으로 가장 많이 쓰이는 오디오/비디오 형식과 데이터 소스를 지원하는 bare-bones 플레이어에 대한 기본 기능을 제공해줍니다.
  • ExoPlayer는 하위 레벨의 안드로이드 오디오 API를 공개하는 오픈 소스 라이브러리입니다. 엑소플레이어는 MediaPlayer에서 지원하지 않는 DASH나 HLS 스트리밍과 같은 높은 수준의 퍼포먼스를 지원합니다. 쉽게 새로운 컴포넌트를 추가할 수 있도록 엑소플레이어의 코드를 커스터마이즈할 수도 있습니다. 엑소플레이어는 안드로이드 버전 4.1(API 레벨 16) 이상부터 사용 가능합니다.

미디어 세션과 미디어 컨트롤러(Media Session and media controller)

UI와 플레이어에 대한 API는 재량껏 사용할 수 있지만, 두 부분 간의 상호 작용의 특성은 기본적으로 모든 미디어 플레이어 앱에서 동일합니다. 안드로이드 프레임워크는 미디어 세션(media session)미디어 컨트롤러(media controller)라는 두 가지 클래스를 정의합니다. 이 두 가지 클래스는 미디어 플레이어 앱을 구축하기 위한 잘 정의된 구조를 제공합니다.

미디어 세션과 미디어 컨트롤러는 미리 정의된 콜백을 사용하여 서로 통신합니다. 이 콜백은 표준 플레이어 동작(재생, 일시정지, 중단 등)과 앱에 고유한 특수한 동작을 정의하는데 사용하는 확장 가능한 사용자 호출(extensible custom call)을 사용하여 서로 통신합니다.

controller-and-session.png

미디어 세션(Media session)

미디어 세션은 플레이어와의 모든 통신을 담당하며 플레이어의 API를 앱의 나머지 부분에서 숨깁니다. 플레이어는 이를 제어하는 미디어 세션에서만 호출됩니다.

세션은 플레이어의 상태(재생/일시정지)와 재생중인 정보에 대한 정보를 유지합니다. 세션은 하나 이상의 미디어 컨트롤러로부터 콜백을 수신할 수 있습니다. 이로 인해 앱의 UI 뿐만 아니라 Wear OS나 Android Auto와 같은 주변 기기를 통해 플레이어를 제어할 수 있습니다. 콜백에 응답하는 로직은 일관적이어야합니다. MediaSession 콜백에 대한 응답은 어떤 클라이언트 앱이 콜백을 시작했는지와 상관 없이 동일해야만 합니다.

미디어 컨트롤러(Media controller)

미디어 컨트롤러는 UI를 분리시킵니다. UI 코드는 플레이어 그 자체가 아니라 미디어 컨트롤러와 통신합니다. 미디어 컨트롤러는 transport control action을 미디어 세션에 대한 콜백으로 변환합니다. 또한 세션의 상태가 변경될때마다 미디어 세션으로부터 콜백을 받습니다. 이는 연결된 UI를 자동으로 업데이트하는 메커니즘을 제공합니다. 미디어 컨트롤러는 한 번에 하나의 미디어 세션에만 연결할 수 있습니다.

미디어 컨트롤러 및 미디어 세션을 사용하면, 다른 인터페이스/플레이어를 런타임에 배포할 수 있습니다. 앱시 실행되는 디바이스의 기능에 따라 앱의 모양(appearance)/성능(performance)을 독립적으로 변경할 수 있습니다.

비디오 앱 vs 오디오 앱(Video apps versus audio apps)

비디오를 재생할때에는 우리의 눈과 귀 모두 관여합니다. 하지만 오디오를 재생할때에는 귀로는 소리를 들으며 다른 앱을 동시에 사용할 수 있습니다. 각 사용법에 따라 다른 설계를 사용합니다.

비디오 앱(Video app)

비디오 앱은 컨텐츠를 보여줄 창(window)이 필요합니다. 이로 인해 비디오앱은 대부분 단일 안드로이드 액티비티(single Android activity)로 구현됩니다. 이 때 비디오가 보여지는 스크린은 액티비티의 일부입니다.

video-player-activity.png

오디오 앱(Audio app)

오디오 플레이어는 UI가 보여질 필요가 없습니다. 한 번 재생을 시작하면 플레이어는 백그라운드 작업으로 계속 재생을 실행합니다. 따라서 오디오를 계속 들으며 다른 앱을 사용할 수 있습니다.

안드로이드에서 이러한 설게를 구현하기 위해서는 두 가지 컴포넌트를 사용하여 오디오 앱을 만들 수 있습니다: 하나는 UI를 위한 액티비티이고, 다른 하나는 플레이어를 위한 서비스입니다. 만약 유저가 다른 앱을 사용하게된다면, 플레이어 서비스는 백그라운드에서 실행됩니다. 오디오 앱의 두 부분을 별도의 구성 요소로 나눈다면, 각각의 구성 요소가 더욱 효율적으로 동작할 수 있습니다. UI 없이도 꽤 오랜 시간 실행되는 플레이어에 비해 일반적으로 UI는 짧은 수명을 가집니다.

audio-activity-and-service.png

오디오 앱에 선호되는 아키텍처는 클라이언트/서버 구조입니다. 플레이어와 플레이어의 미디어는 MediaBrowserService 내부에 구현되고, UI와 미디어 컨트롤러는 MediaBrowser와 함께 안드로이드 액티비티 내부에서 작동합니다.

클라이언트/서버 접근 방법을 구현하기 위해 지원되는 라이브러리는 두 가지 클래스를 제공합니다: 하나는 MediaBrowserService이고, 다른 하나는 MediaBrowser입니다. 서비스 컴포넌트는 미디어 세션과 플레이어를 포함한 MediaBrowserService의 서브클래스로 구현되어 있습니다. UI와 미디어 컨트롤러가 있는 액티비티는 MediaBrowserService와 통신하는 MediaBrowser를 포함해야만 합니다.

MediaBrowserService를 사용하면 직접적으로 앱의 UI 액티비티에 접근하지 않고도, Android Auto나 Android Wear와 같은 주변 기기가 앱을 쉽게 찾고, 컨텐츠를 열고, 재생을 제어할 수 있습니다. 각각의 앱은 자신만의 MediaController를 가지고 있기 때문에 하나의 동일한 MediaBrowserService에 여러 개의 앱을 연결할 수 있습니다. 따라서 MediaBrowserService를 제공하는 앱은 다중 동시 연결(multiple simultaneous connections)을 제어할 수 있어야만합니다.

MediaBrowserService는 두 가지 주요 기능을 제공합니다:

  • MediaBrowserService를 사용하면, MediaBrowserService가 있는 다른 컴포넌트와 애플리케이션에서 서비스를 검색하고, 자체 미디어 컨트롤러를 생성하고, 미디어 세션에 연결하고 플레이어를 제어할 수 있습니다. 이것이 바로 Wear OS와 Android Auto Application이 당신의 미디어 애플리케이션에 접근하는 방법입니다.
  • 선택적 브라우징 API(browsing API)를 제공합니다. 애플리케이션은 이 기능을 사용할 필요가 없습니다. 브라우징 API를 사용하면 클라이언트가 서비스를 조회하고 해당 컨텐츠 계층의 representation을 build out할 수 있습니다. 이 컨텐츠 계층은 재생 목록, 미디어 라이브러리 혹은 다른 종류의 컬렉션을 나타낼 수 있습니다.

★Note

미디어 세션과 미디어 컨트롤러의 경우와 마찬가지로, 권장되는 미디어 브라우저 서비스 및 미디어 브라우저 구현 방법은 media-compat support library에 정의된 MediaBrowserServiceMediaBrowserCompat 클래스를 이용하는 것입니다. 두 클래스는 API 21부터 도입된 이전 버전의 MediaBrowserServiceMediaBrowser 클래스를 대체합니다. “MediaBrowserService”와 “MediaBrowser”라는 용어는 MediaBrowserServiceCompatMediaBrowserCompat의 인스턴스를 간결하게 나타냅니다.

미디어 앱과 안드로이드 오디오 인프라(Media apps and the Android audio infrastructure)

잘 설계된 미디어 앱은 다른 오디오 재생 앱과 함께 잘 재생되어야(play well together)합니다. 휴대전화 기기를 공유하고 오디오를 사용하는 디바이스의 다른 앱과 협력할 수 있도록 준비(prpepare)되어야 합ㄴ디ㅏ. 또한, 디바이스의 하드웨어 컨트롤에도 응답해야합니다.

plays-with-others.png

이 모든 동작들은 Controlling Audio Output에 나와있습니다.

참고 문헌