안드로이드 기술면접 대비

sumi Yoo·2022년 11월 20일
1

[안드로이드 4대 컴포넌트]

  • 액티비티: 사용자와 상호작용하는 화면으로, UI를 담당하는 컴포넌트. 반드시 1개 이상의 액티비티를 가지고, 두개 이상의 액티비티를 동시에 보여줄 수 없다.
  • 서비스: 화면 없이 백그라운드에서 돌아가는 컴포넌트, 서비스 클래스를 상속받아 사용
  • 브로드캐스트 리시버: 안드로이드의 다양한 이벤트와 정보에 반응하는 컴포넌트, 디바이스에서 일어날 수 있는 일 중 애플리케이션이 알아야 하는 상황이 발생하면 이를 알려줌.
  • 컨텐트 프로바이더: 데이터를 관리하고, 다른 앱의 데이터를 제공해주는 컴포넌트.

[Task]

액티비티를 관리하는 스택, 가장 나중에 적재되는 액티비티가 가장 먼저 사용.
최초로 적재된 액티비티를 root activity, 마지막에 적재된 액티비티를 top activity.
Flag로 액티비티들의 흐름을 제어

[TDD]

테스트 주도 개발 방법론
테스트 코드를 먼저 작성 후, 짧은 개발 사이클을 반복하는 방법
목표: 잘 작동하는 깔끔한 코드를 작성하는 것

[SDK]

software development kit 소프트웨어 개발 도구 모음
개발에 도움이 될 개발 도구 프로그램, 디버깅 프로그램, 문서, API 등이 있다.

[API]

application programming interface
응용프로그램들 사이에 데이터를 주고 받는 방법.
어떤 특정 사이트에서 특정 데이터를 제공할 때 이 정보들을 어떠한 방식으로 요청해야 하는지, 어떤 정보를 제공하는지에 대한 규격

내부 동작을 알 필요 없이 간편하게 개발을 할 수 있음.
예) 네이버 지도 API, 기상청 API

[Listview 와 Recyclerview]

둘 다 리스트를 보여주는 화면이라는 점에서 동일. 데이터가 저장되어 있는 아이템들을 리스트 형태로 화면에 출력하며, 스크롤 기능을 통해 모든 데이터들을 보여줍니다.
리스트뷰는 스크롤을 내려 새로운 아이템이 화면에 보여져야 될 때마다 새로운 객체를 생성하기 때문에 이로 인해 과부하가 온다는 단점이 있어 이를 보완하고자 만들어진 게 리사이클러뷰. 리사이클러뷰는 새로운 아이템이 화면에 보여져야 할 때 화면에 보여지지 않는 이전에 사용했던 객체를 재사용하는 방식

[Activity 와 Fragment]

액티비티는 독립적으로 사용되고 전체 화면을 차지
프래그먼트는 액티비티에 종속되고 전체화면을 차지하지 않아도 되기 때문에 화면 디자인에 유연성을 가짐
하나의 액티비티에서 여러가지의 화면을 보여주고 싶을 때 사용되는게 프래그먼트
프래그먼트가 차지하는 공간은 재사용될 수 있는 장점

[ANR]

application not responding
메인 스레드가 일정시간 블로킹 되면 anr 오류를 띄웁니다.
메인 스레드를 어디선가 이미 점유하고 있다면 입력 이벤트를 전달하지 못하는데, 이 시간이 5초 이상 지연되면 anr을 발생
브로드캐스트 리시버 동작이 10초 이내에 완료하지 못했을 때

[Dialog, Toast, SnackBar]

다이얼로그는 사용자에게 추가 정보를 입력 또는 결정을 내리도록 할 때 표시하는 작은 화면입니다.

토스트 메시지는 작은 팝업으로 메시지에 필요한 공간만 차지하고 사용자와의 상호작용이 유지됩니다.
일정시간이 지나면 자동으로 사라집니다.

스낵바는 토스트 메시지와 유사하지만 사용자가 메시지에 응답할 수 있습니다.

[Intent]

메시지 객체로 컴포넌트들 사이에서 정보를 주고받을 수 있습니다.

Intent types

  • 명시적 인텐트
    인텐트에 클래스 객체나 컴포넌트 이름을 지정해서 사용하기 때문에 호출할 대상을 확실히 알 수 있는 경우에 사용합니다. (주로 애플리케이션 내부에서 사용)
  • 암시적 인텐트
    호풀할 대상이 달라질 수 있을때 사용
    시스템이 인텐트로 요청한 정보를 처리할 수 있는 적절한 컴포넌트를 찾아 사용자에게 그 대상과 처리 결과를 보여줍니다.

IntentFilter
메니페스트에 선언해서 사용하고, 암시적 인텐트를 통해 사용자가 어떤 종류의 앱을 사용할지 선택하도록 할 때 필요합니다.
카메라 앱을 실행하려 했을때 디바이스에 설치된 모든 카메라 앱 중 하나를 선택할 수 있는 대화 상자가 표시되고, 사용자는 그 중 하나를 선택해서 앱을 실행할 수 있는 상황이 이 인텐트 필터의 예시입니다.

PendingIntent
인텐트를 가지고 있는 클래스로, 당장 수행하지 않고 특정한 시점에 수행
특정한 시점이란 보통 앱이 실행되고 있지 않는 상태를 의미
푸시알림을 탭하면 해당 앱으로 다시 복귀하는 동작이 pendingintent의 예시
다른 프로세스에게 권한을 부여해서 인텐트를 마친 자신의 앱에서 실행되는 것처럼 해줌.

[MainThread]

멀티 스레드로 UI를 업데이트하면 동일한 UI 자원을 사용할 때 교착상태, 경쟁상태 등 문제가 발생할 수 있다.
메인 스레드에서만 UI 업데이트 허용
UI 스레드로 부르기도 함

앱이 시작되면 메인 스레드가 생성
Activity Thread가 메인 클래스, main() 앱의 시작지점
main() 함수에서 메인 루퍼가 생성되고 루프가 시작
루퍼의 loop() 무한 반복문을 가지고 있기 때문에 프로세스가 종료될 때까지 mian()는 종료되지 않습니다.

[Lopper]

하나의 스레드에는 하나의 루퍼만 가지고, 루퍼도 하나의 스레드만 관리합니다.
루퍼의 내부에는 메시지 큐가 존재하는데, 해당 스레드가 처리해야 할 동작들이 메시지라는 형태로 큐에 FIFO 방식으로 쌓이게 됩니다. 무한 루프를 돌면서 큐에 쌓인 메시지들을 하나씩 꺼내서 핸들러에 전달하는 역할

[Handler]

메시지를 루퍼의 메시지 큐에 넣는 기능과 처리하는 기능을 제공
Handler 생성하면 자동으로 해당 스레드의 루퍼와 연결되고, 메시지를 전달하고 처리할 수 있게 됩니다.

[Context]

애플리케이션 환경에 대한 전역 정보, 여러 컴포넌트들의 상위 클래스.

  • 애플리케이션의 현재상태를 나타낸다.
  • 액티비티와 어플리케이션의 정보를 얻기 위해 사용할 수 있다.
  • 리소스, 데이터베이스, shared preference, 시스템 서비스 등에 접근하기 위해 사용할 수 있다.

[Application Context]

싱글턴 인스턴스, 애플리케이션 라이프사이클에 묶여있다.

[Activity Context]

액티비티 라이프사이클과 연결되어 있습니다.
액티비티 컨택스트는 액티비티와 함께 소멸해야 하는 경우에 사용합니다.

[LayoutInflater]

xml에 정의된 리소스를 view 객체로 반환
onCreate() setContentView() 또한 inflater 역할
이 함수 내부에서 LayoutInflater가 실행되어 view들을 객체화
inflate() 3가지 인자 (객체화 하고자 하는 xml 파일, 객체화 된 뷰를 붙일 부모의 레이아웃이나 컨테이너, boolean)
true -> 부모 view에 바로 붙인
flase -> 나중에 붙인(parent.addView() 처럼 부모 뷰에 현재 뷰를 붙이는 모든 코드)

프래그먼트 attachToParent를 true Exception 발생
보통의 구현에서는 프래그먼트 매니저가 프래그먼트를 자동으로 add() 해주기 때문에 이미 붙은 프래그먼트를 중복해서 붙이려고 하는 상황으로 Exception 발생
직접 뷰를 붙여야 한다는 확신이 없다면 true 해서는 안된다.

[onStop에서 DB 업데이트를 모두 저장할 수 있는지?]

아니다.
디바이스의 메모리가 부족해 우선순위가 낮은 프로세스를 종료해야 한다면 onPause에서도 종료가 될 수 있기 때문에 무조건 onStop(), onDestroy()가 호출된다는 보장이 없다.

[View, ViewGroup]

뷰는 화면의 구성요소, 화면에 보여지는 모든 것이 뷰
뷰에는 화면 어디에 배치 되어야 하는지에 대한 정보가 없기 때문에, 뷰만으로는 화면에 나타날 수 없음.
이때 뷰그룹이 필요함.

뷰그룹은 뷰를 상속한 클래스, 뷰들을 가지고 있고, 레이아웃이라고도 함.
뷰 그룹은 뷰를 배치하고, 뷰 그룹 또한 뷰이기 때문에 자식 뷰 그룹을 배치할 수 있음.

[SharedPreferences]

안드로이드에서 사용할 수 있는 데이터 저장소
데이터를 xml 파일로 저장, 파일이 앱 폴더 내에 저장되므로 앱이 삭제되면 같이 삭제됨.
key-value 형식으로 primitive 타입만 저장할 수 있음.

데이터의 양이 많거나 중요한 정보라면 서버나 DB를 이용해 저장하는 것이 좋겠지만, 간단한 설정 값이나 문자열 같은 데이터는 SharedPreferences로 저장하는게 더 효율적.
예) 로그인 여부 확인

[LiveData]

Jetpack에서 지원하는 라이브러리로, LifeCycle을 통해 생명 주기를 인식하는 데이터 홀더 클래스.
LifeCycleObserver를 통해 LiveData는 UI Controller의 LifeCycle를 옵저브하고, UI Controller는 LiveData 값의 변경을 옵저브함.
data 값의 변경 알림은 LifeCycleOwner가 STARTED, RESUMED인 활성 상태에만 받음.
observeForever()는 항상 활성 상태로 간주하여 항상 알림을 받을 수 있도록 함.
removeObserver()로 옵저버를 제거함.

장점

  • UI와의 데이터 일치성 보장
    LivaData는 옵저버 패턴.
    data값이 변경되면 옵저버 객체에게 이를 알림.
    데이터가 변경될 때마다 옵저버가 대신 UI를 업데이트 하므로 개발자가 따로 업데이트를 해줄 필요가 없음.
  • 메모리 누수
    LifeCycleOwner의 상태가 DESTROYED가 되면 옵저버가 즉시 구독을 취소하기 때문에, 메모리 누수에 대해 걱정할 필요가 없음.
  • 구성 변경
    ViewModel과 함께 사용하여 기기 회전같은 구성 변경이 일어나도 데이터 값을 유지할 수 있음.

[MutableLiveData]

변경 가능한 LiveData로 값을 setting하는 setValue(), postValue() 함수가 있음.

[setValue() 와 postValue()]

setValue()는 메인 스레드에서 동기적으로 데이터 값을 변경합니다.
순서를 보장하고, 변경 값이 즉시 반영되어야 한다면 setValue()를 사용.

postValue()는 백그라운드 스레드에서 비동기적으로 데이터 값을 변경.
순서를 보장할 필요가 없거나, 메인 스레드를 블로킹하지 않아야할 때 사용.
따라서 메인 스레드에 적용되기 전에 값에 접근을 한다면 반영이 되지 않을 수도 있음.

[LivaData 캡슐화]

MutableLiveData는 read, write이 가능하고, LivaData는 read만 가능.
수정이 가능한 MutableLiveData는 private로 선언해 외부에서 변경할 수 없게 막고,
외부에서 값을 가져올때는 읽기만 가능한 LivaData에 접근해 사용.
액티비티나 프래그먼트에서 UI 데이터 값을 변경하는 것은 MVVM 아키텍쳐에 위배되는 행동

[ObservableField 와 LiveData]

LiveData는 생명주기를 알고 있기 때문에, observer를 자동으로 관리.
ObservableField는 생명주기를 알지 못하기 때문에, 수동으로 관리해줘야 함.
따라서 메모리 누수면에서 LiveData가 더 좋다.

[Application Class]

전역 앱 상태를 유지하기 위한 기본 클래스로, 애플리케이션 안에서 공동으로 멤버 변수나 메서드를 사용할 수 있도록 해주는 공유 클래스.
Application Class 또는 Application Class의 하위 클래스들은 앱이 실행될 때 가장 먼저 인스턴스화 됨.
Context를 이용해서 접근 가능

[OkHttp Interceptor]

서버와 통신을 하기 직전 직후에 요청/응답을 가로채서 추가적인 작업을 한 후에 원래의 흐름으로 다시 되돌려주는 기능.
모니터링을 한다거나 원하는 값을 끼우는 등의 작업을 할 수 있음.

Application Interceptor는 Application 과 OkHttp Core 사이에서 요청/응답을 가로채는 역할
Network Interceptor는 서버와 OkHttp Core 사이에서 요청/응답을 가로채는 역할

[Intent 와 Bundle의 용도]

Intent는 저장이 아닌 전달하는 수단의 객체, Bundle은 상태/값 등을 저장하기 위한 객체

[What is the Android Manifest?]

프로그램에서 필수적.
코드를 실행하기 전에 시스템이 반드시 알아야 하는 애플리케이션의 정보를 포함하는 곳.

[View.GONE 과 View.INVISIBLE]

View.GONE: 어댑터의 getView()를 호출하지 않아 뷰를 그리지 않아, 레이아웃에 공간을 차지하지 않음.
View.INVISIBLE: 뷰를 그려놓고 보이지는 않지만 레이아웃에 공간을 차지함.

[Service와 Thread]

앱 실행 중 프로세스가 강제 종료 되었다고 했을 때, Service는 onStartCommand의 반환값에 따라서 강제 종료된 Service를 시스템이 다시 자동으로 시작하게 할 수 있음.
Thread는 시스템이 다시 복구시켜 주지 않음.

Thread는 하나의 프로세스 내에서 Thread간 메모리를 공유하지만 다른 프로세스에 접근할 수 없음.
Service는 Bind 방식으로 구현을 하면 다른 프로세스와의 통신도 가능.

Thread는 사용자와 상호작용하는 과정에서 메인 스레드를 블록 하지 않기 위한 작업을 하는 포그라운드 작업에 적합하고, Service는 사용자와 상호작용하지 않아도 계속 수행되어야 하는 백그라운드 작업에 적합.

[Service]

화면 없이 백그라운드에서 동작하는 컴포넌트.
기본적으로 메인 스레드에서 동작하기 때문에 별도로 스레드를 만들어서 작업을 수행해야 함.

종류

  • 포그라운드 서비스
    알림창을 통해 서비스가 실행중인 것을 나타내주고, 시스템에 의해서 강제종료 당하지 않음.
    사용자가 포그라운드 서비스가 동작하고 있음을 인지할 수 있도록 Notification을 함께 제공해야함.
    startFoground()를 서비스 내부에서 별도로 실행해서 포그라운드로 변경해줘야 함.
  • 백그라운드 서비스
    사용자에게 보이지 않고 작업을 수행.
    시스템의 리소스가 부족할 경우 강제종료 당할 수 있음.
  • 바운드 서비스
    클라이언트-서버 인터페이스를 제공하여 컴포넌트가 서비스와 상호작용하게 하고, 결과를 받을 수도 있음.
    여러 컴포넌트가 한 서비스에 한꺼번에 바인딩될 수 있고, 모든 컴포넌트가 바인딩이 해제되면 자동으로 서비스가 소멸.

서비스 호출

startService()
서비스가 한번 시작되면 무기한으로 백그라운드에서 동작.
생명주기는 onCreate() -> onStartCommand() 순으로, Service가 실행되는 도중에 다시 한 번 startService()가 호출되면 onStartCommand()부터 실행됨.

onStartCommand의 리턴타입 3가지
startService()를 통해 intent를 넘겨주는데, onStartCommand에서 이 intent를 받습니다.

  • START_STICKY
    서비스 강제 종료 시, 기존에 intent에 값이 있더라도 시스템에서 서비스 재시작 시 intent 값을 null로 초기화 시켜주면서 재시작
  • START_NOT_STICKY
    강제 종료 된 서비스가 재시작 되지 않음.
  • START_REDELIVER_INTENT
    기존 intent 값을 유지해서 서비스 재시작

bindService()
바인드 서비스를 실행할 때 호출하는 메서드
생명주기는 onCreate() -> onBind() -> onUnBind() -> onDestroy()
서비스 바인딩 객체를 생성하려면 onBind()를 구현해야 함.
onBind()는 IBinder를 반환하는데, 이 객체가 서비스와 클라이언트 사이의 인터페이스 역할.
클라이언트가 bindService() 호출하면 클라이언트가 서비스에 연결되면서 IBinder가 반환되고, 클라이언트가 IBinder를 통해 정보를 주고 받을 수 있음.

[Intent Service]

서비스의 서브 클래스로, Worker Thread를 사용하여 모든 시작 요청을 처리하되, 한 번에 하나씩 처리함. 요청한 작업이 완료되면 자동적으로 서비스를 중단.

[String StringBuffer StringBuilder]

모두 문자열을 저장하고 관리하는 클래스

String

String 객체는 한번 생성되면 할당된 공간이 변하지 않지만, StringBuffer와 StringBuilder의 경우 객체의 공간이 부족해지면 버퍼의 크기를 유연하게 늘려줌.

"hello" 값이 들어가있던 String 클래스의 참조변수 str이 "hello world"라는 값을 가지고 있는 새로운 메모리 영역을 가리키게 되고 hello 값이 할당되어 있던 메모리 영역을 가비지로 남아있다가 GC(가비지 콜렉션)에 의해 사라지게 된다. String 클래스는 불변하기 때문에, 문자열을 수정하는 시점에 새로운 String 인스턴스가 생성.
문자열 연산이 빈번하게 발생하는 경우 String 클래스를 사용하면 힙 메모리에 많은 가비지가 생성되어 메모리 부족으로 애플리케이션의 성능에 영향을 미칠 수 있음.
https://velog.velcdn.com/images/hugbee/post/1bf7d2f4-0948-4fb2-891b-7df78e7978cf/image.png

StringBuffer와 StringBuilder는 동일 객체내에서 문자열을 변경.
따라서 문자열 연산이 많은 경우라면 String 클래스가 아닌 StringBuffer/StringBuilder를 사용하는 것이 좋음.

StringBuffer StringBuilder

동기화 지원의 유무 차이.
StringBuffer는 메소드에 synchronized 키워드가 존재하여 멀티 스레드 상태에서 동기화를 지원하고, StringBuilder는 동기화를 지원하지 않음.

String: 문자열 연산이 적고, 멀티 스레드 환경일 경우(항상 새로운 객체가 생성되기 때문에 동기화를 고려하지 않아도 됨.)
StringBuffer: 문자열 연산이 많고 멀티 스레드 환경이 경우
StringBuilder: 문자열 연산이 많고 단일 스레드거나 동기화를 고려하지 않아도 되는 경우
StringBuilder
https://velog.velcdn.com/images/hugbee/post/4f93fd65-24a3-4004-98db-8b68ca836e04/image.png

[FragmentManager]

FragmentManager란 앱의 프래그먼트를 더하고, 삭제하고, 교체하는 역할을 하는 클래스.

액티비티에서 접근하기

모든 FragmentActivity와 이것의 서브 클래스(액티비티)는 getSupportFragmentManager로 FragmentManager에 접근할 수 있음.

프래그먼트에서 접근하기

프래그먼트 내에서 자식 프래그먼트를 관리하는 FragmentManager에 접근하기 위해서 getChildFragmentManager()를 사용. 자식 프래그먼트에서 부모의 FragmentManager에 접근하기 위해서 getParentFragmentManager()를 사용.

프래그먼트는 FragmentActivity를 상속받지 않기 때문에 getSupportFragmentManager를 사용할 수 없음.
Fragment를 상속받아 getChildFragmentManager()와 getParentFragmentManager()만 있다.

FragmentActivity와 Activity 의 차이

FragmentActivity는 Activity의 서브 클래스로, 안드로이드의 오래된 버전과의 호환성을 보장하기 위해 추가적인 메서드를 제공

[View Lifecycle]

생성자

  • View(Context context)
    코드상에서 뷰 객체를 생성할 때 사용하는 생성자입니다. 여기서 context 매개변수는 뷰가 실행될 때 현재 테마와 리소스 등을 구성하는데 사용됩니다.
  • View(Context context, @Nullable AttributeSet attrs)
    xml을 통해 뷰를 전개할 때 사용하는 생성자입니다. xml 파일에 정의된 속성으로 뷰를 생성합니다. 이 생성자는 기본 스타일인 0을 사용하므로 Context의 테마와 지정된 AttributeSet의 속성 값만 적용됩니다.
  • View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
    xml을 통해 뷰를 전개하고 테마 속성에서 클래스별 기본 스타일을 적용합니다. defStyleAttr 매개변수는 현재 테마의 속성입니다. 기본값을 찾지 않으려면 0을 지정할 수 있습니다.
  • View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)
    xml을 통해 뷰를 전개하고 테마 속성 또는 Style 리소스에서 클래스 별 기본 스타일을 적용합니다. 매개변수 defStyleRes는 뷰의 defStyleAttr가 0이거나 테마에서 찾을 수 없는 경우에만 기본값을 제공하는 Style 리소스 ID 입니다. 기본값을 찾지 않으려면 0으로 지정합니다.

[onAttachedWindow()]

뷰가 화면에 붙여질 때 호출되는 함수.
드로잉 할 표면이 있는 상태, 리소스를 할당하거나 리스너를 설정할 수 있음.

[onDetachedWindow()]

뷰가 화면에 떨어질 때 호출되는 함수.
드로잉 할 표면이 없는 상태, 리소스를 해제하고 모든 작업을 중단해야함.

[onFinisheInflate()]

뷰의 전개가 모두 끝났을 때 호출됨.
모든 자식 뷰가 화면에 추가 된 후 호출된다.

순회

뷰의 계층 구조는 자식 뷰와 뷰그룹으로 이루어진 트리 구조이기 때문에, 각 메서드는 부모에서 시작해서 마지막 노드까지 전위순회하면서 제약조건을 정의합니다.

[onMeasure()]

뷰의 크기를 측정하는 단계
뷰 그룹의 경우 계속해서 각 자식 뷰에 대한 측정을 하고 그에 대한 결과로 자신의 사이즈를 측정한다.
MeasureSpec를 통해 크기를 측정하는데, 크기에 대한 정보와 mode에 대한 정보를 담고있다.
부모뷰에서 자식뷰로 MeasureSpec를 넘겨준다.

MeasureSpec.EXACTLY: 정확한 사이즈가 정해진 상태, 정해진 사이즈 안에서 원하는 크기를 가질 수 있음.
MeasureSpec.UNSPECIFIED: mode가 설정되지 않은 경우며, 원하는 크기를 가질 수 있음.
MeasureSpec.AT_MOST: 주어진 사이즈에서 원하는 크기를 가질 수 있음. wrap_contentd

[onLayout()]

뷰의 위치와 크기를 할당합니다.

[onDraw()]

앞에서 정해진 크기와 위치를 이용해서 뷰를 실제로 그리는 단계.
Canvas와 Paint 객체를 이용해서 필요한 내용을 그림.
이 메서든 빈헌하게 호출되고, 호출 시 많은 시간이 걸리기 때문에, 이 곳에서 객체를 생성하는 일은 피하고 한 번 할당한 객체를 재사용하는 것이 좋음.

[invalidate()]

뷰의 크기나 위치 변경 없이, 뷰의 그래픽적인 요소만 바뀌었을 때 사용.
onDraw()를 재호출하여 뷰를 업데이트함.

[requestLayout()]

뷰의 크기, 위치가 변경되었을때 호출.
onMeasure()부터 시작해서 뷰를 다시 그림.

https://rkdxowhd98.tistory.com/164
https://beomseok95.tistory.com/249
https://gyubgyub.tistory.com/87
https://velog.io/@changhee09/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%BB%A4%EC%8A%A4%ED%85%80-%EB%B7%B0-%EC%9E%91%EC%84%B1

[다이얼로그를 띄울 시 onPause가 호출되는가?]

아니요
다이얼로그는 Activity가 아니기 때문에 Activity Stack에 쌓이지 않습니다. 그렇기 때문에 다이얼로그가 띄워진다고 현재 Activity를 정지시키지 않습니다.

[권한 요청 다이얼로그 onPause가 호출되는가?]

2022-11-28 01:07:57.217 29613-29613/com.example.activitylifecycle D/MainActivity: onCreate
2022-11-28 01:07:57.443 29613-29613/com.example.activitylifecycle D/MainActivity: onStart
2022-11-28 01:07:57.448 29613-29613/com.example.activitylifecycle D/MainActivity: onResume
2022-11-28 01:07:59.416 29613-29613/com.example.activitylifecycle D/MainActivity: onPause
2022-11-28 01:08:08.059 29613-29613/com.example.activitylifecycle D/MainActivity: onResume

네.
권한을 요청 시 requestPermissions() 메소드를 호출해야 합니다.
권한을 허용하고 있지 않은 경우 권한 요청을 하는 UI를 띄운다고 되어있습니다.
이 메소드(requestPermission)는 사용자에게 어떠한 권한을 허용하고 거절할지에 대한 Activity를 띄울 수 있다고 공식문서에 명시되어 있음.
Activity로 실행이 되어야 onRequestPermissionResult 오버라이드 함수가 호출되어 권한이 허용되었는지 거절되었는지에 대한 결과 값을 받아 올 수 있습니다.
https://programmar.tistory.com/46

참고로 Head up notification 또한 onPause를 호출시키지 않습니다. 그러나 Dialog Theme으로 구현된 Activity는 액티비티를 상속받으므로 현재 액티비티 스택에 쌓이므로 현재 Activity의 onPause를 호출시키게 됩니다.

[JetPack]

개발자가 더욱 더 편리하고, 쉽고, 빠르게 높은 퀄리티의 앱을 개발하도록 돕는 모음 도구.

https://velog.io/@eoqkrskfk94/android-Jetpack

[AAC - Android Architecture Components]

jetpack에서 아키텍쳐에 해당하는 부분으로 테스트와 유지보수가 쉬운 안드로이드 앱을 디자인할 수 있도록 돕는 라이브러리 모음.
AAC를 활용하면 MVVM 구조로 앱 설계가 가능해짐.
https://velog.io/@twaun95/Android-JetPack-AAC

[MVVM]

MVVM 패턴은 View, ViewModel, Model(Repository)을 분리해 뷰와 모델간의 의존성을 줄여준다.

일단 MVC의 경우에는 안드로이드에서 적용할 때 View와 Controller가 Activity에서 모두 처리되어야하기 때문에 Activity가 커지는 문제가 있어서 관심사의 분리가 비교적 원활하지 않다고 여겨졌다.

MVP는 Presenter가 뷰와 1대1로 동작하기 때문에 뷰와 프레젠터의 의존성이 강해지는 문제가 발생하고 이에 따라 종종 프레젠터의 로직이 비대해지는 문제가 발생하기도 했다.

뷰와 모델의 관심사를 충분히 분리할 수 있고, 화면회전 등의 동작으로 뷰가 다시 생성되어도 뷰모델을 통해 데이터를 유지할 수 있는 MVVM 방식을 채택하기로 결정했다.

Activity / Fragment

View에 해당되며, 대부분 액티비티나 프래그먼트로 이루어짐.
UI 와 관련된 프레젠테이션 로직을 작성함.

ViewModel

viewModel에 해당되며, View가 요청한 데이터를 요청하고 받아오는 역할을 하고, 이와 관련된 비즈니스 로직을 작성한다.

LiveData

ViewModel에서 View에게 일관성 있는 데이터를 제공하기 위해 사용.
액티비티나 프래그먼트에서 이를 옵저빙해 변화를 감지하여 동작을 수행할 수 있다.

DataBinding

xml에서 ViewModel의 LiveData를 바인딩하여 관찰하고, 데이터 변화에 따른 UI 동작을 가능하게 한다.
데이터와 뷰를 연결하는 작업을 레이아웃에서 처리 할 수 있게 해주는 라이브러리.

Repository

ViewModel에서 요청한 데이터를 내부 데이터베이스에서 불러오거나, 외부 서버와 통신을 통해 데이터를 불러오고 불러온 데이터를 저장하고 가공해서 ViewModel에게 전달함.

이로써 ViewModel에서는 데이터를 저장하고 관리할 필요가 없어지게 된다.

[Layout]

  • LinearLayout: 가로 혹은 세로로 순차적으로 쌓아나가는 Layout
  • RelativeLayout: 상대적으로 위치를 지정해주는 Layout, 부모뷰나 또 다른 자식 view의 위치를 기준으로 위치를 지정해 줄 수 있음.
  • ConstraintLayout: RelativeLayout에 가중치를 추가하여 조금 더 발전된 Layout, 부모뷰나 또 다른 자식 view의 위치를 기준으로 위치를 지정해 줄 수 있고, 각 View의 크기를 유연하게 지정할 수 있는 Layout입니다.
  • TableLayout: View들을 표처럼 배치할 수 있는 레이아웃, <TableRow>는 TableLayout의 행(Row)가 돼고 TableRow안에 들어가는 뷰들의 수만큼의 열(Column)이 생기게 됩니다.
  • GridLayout: TableLayout의 좀 더 발전된 형태로 TableLayout과 LinearLayout이 합쳐진 Layout입니다.
    LinearLayout과 마찬가지로 android:orientation을 설정해 세로 배치 혹은 가로 배치를 설정해 줄 수 있습니다.
    rowCount, columnCount를 지정하면 정해진 수 만큼 뷰가 채워진 뒤 다음 행/열로 넘어간 뒤 뷰가 채워지게 됩니다.
  • FrameLayout은 한가지의 뷰를 보여줄 때 사용됩니다.
    여러개의 뷰를 중첩시킬 수 있기 때문에 여러개의 뷰를 중첩한 후 android:visivility를 설정하여 한가지의 뷰만 visible처리를 하여 보여주는 방식으로 사용합니다.

[코루틴]

스레드 안에서 실행되는 일시 중단 가능한 작업의 단위로, 경량 스레드

[코루틴과 스레드의 차이점]

코루틴은 컨텍스트 스위치가 발생하지 않아 스레드보다 비용이 덜 든다.

[CoroutineScope]

코루틴의 범위로 코루틴의 블록을 묶음으로 제어할 수 있는 단위

[GlobalScope]

CoroutineScope의 한 종류로 Application 생명주기에 종속적.
Activity가 onDestroy되어도 동작하는 코루틴 Context

[Dispatcher]

CoroutineContext의 주요 요소로 CoroutineContext를 상속받아 어떤 스레드에서 어떻게 동작할 것인가에 대한 정의

  • Dispatchers.Main: 메인 스레드에서 동작하는 방식
  • Dispatchers.IO: 네트워크/파일 작업에 사용하는 방식
  • Dispatchers.Default: CPU 사용량이 많은 작업에 사용

=> Main을 제외한 IO/Default는 백그라운드 작업

[launch() & async()]

scope의 확장함수로서, 코루틴을 만들고 실행하는 코루틴 빌더
launch: job 반환, 반환값이 없음
async: Deffered<T> 반환(T는 반환값의 자료형), 반환값 존재

[withContext() - T 반환]

async처럼 결과값을 반환하는 빌더로 async와 유사하지만, withContext()는 Deferred<T>객체로 반환하지 않고, 결과(T)를 그 자리에서 반납한다

[코루틴 제어]

job 객체

대기 - join(), joinAll()
중단 - cancle(): 부모(job)을 포함해 모두 종료하는 함수로 한번 cancle하면 재사용이 불가은
cancleChildern(): 부모를 제외한 하위 루틴들을 모두 종료

Deffered<T> 객체

대기 - await() 로 대기하고 결과값을 전해 받음, awaitAll()

[코루틴 지연실행]

코루틴 빌더(launch/async)의 start 인자에 CoroutineStart.Lazy값을 할당하면 해당 코루틴을 호출하는 시점에 실행

[runBlocking - T 반환]

코드 블럭이 작업이 완료될 때 까지 스레드를 점유하고 대기

[예외처리 Exception]

// 예외 처리를 할 수 없어, 앱이 죽어버리는 방법
GlobalScope.launch(Dispatchers.IO) {	// IO, 백그라운드 환경
    launch {
        throw Exception()	// 임의로 Exception 발생
    }
}

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

// 코루틴 선언 시 handler를 추가하여 예외 발생 시 콜백으로 처리하는 방법
GlobalScope.launch(Dispatchers.IO + handler) {    // 1
    launch {
        throw Exception()   // 2
    }
}

val handler = CoroutineExceptionHandler { coroutineScope, exception ->   // 3
    Log.d(TAG, "$exception handled!")
}

// 예외 발생 시 handler에 콜백으로 처리하여 앱이 죽지 않는 방법

CoroutineExceptionHandler: 예외 발생 시 등록한 handler에 위임하여 콜백으로 예외 이벤트를 처리하는 방식

but, async() / withContext()는 핸들러를 등록하여 throw로 예외처리 방법이 불가능

GlobalScope.launch(Dispatchers.IO) {
    try {
    	// withContext의 외부에 try~catch로 예외 처리 가능한 방법
        val name = withContext(Dispatchers.Main) {	
            throw Exception()
        }
    } catch (e: java.lang.Exception) {
        Log.d(TAG, "$e handled!")
    }
}

-> withContext 코루틴 블럭의 외부에 try-catch를 선언하여 예외 발생 시 처리할 수 있는 방법, async도 동일

[delay()]

스레드의 sleep() 메서드는 스레드를 점유하고 대기하는 Blocking 함수
코루틴의 delay() 메서드는 스레드를 점유하지 않는 non-blocking 함수

[CoroutineContext]

코루틴을 어떻게 처리할 것인지에 대한 여러가지 정보들의 모음.

[취소 (Cancellation)]

코루틴은 내부적으로 취소 동작을 위해 CancellationException을 사용
CancellationException 예외는 모든 핸들러들이 무시하므로 handler를 등록해도 아무런 효과가 없습니다.
코루틴 동작 중 취소 상황 시 처리를 해야할 일이 있다면 try-catch블록을 이용해서 예외처리용으로 사용이 가능

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = launch {
		// child 코루틴 생성
        val child = launch {
            try {
                delay(Long.MAX_VALUE)	// Long자료형 최대값만큼 대기
            } catch (e :CancellationException) {	// CancellationException 예외 발생 처리
                println("Child is cancelled")
            }
        }
        yield()		// Thread순서 양보, 생략 시 child코루틴이 시작도 전에 cancel실행 가능
        println("Cancelling child")
        child.cancelAndJoin()	// child 코루틴 취소/대기
        yield()
        println("Parent is not cancelled")
    }
    job.join()
}

[예외 (Exception)]

일반적인 코루틴은 취소예외 이외의 예외는 양방향 전파라는 점

  • 양방향 전파 : 아래 방향(부모 -> 자식) + 위 방향 (자식 -> 부모) 모두 전파
    부모 예외로 인한 취소 발생 시 모든 자식 종료
    자식 예외로 인한 취소 발생 시 부모 / 모든 자식 종료

[SupervisorJob (감독자 작업)]

SupervisorJob은 일반적인 Job과 다르게 예외를 단방향전파
(예외로 인한 코루틴의 취소가 단방향/아래 방향 (부모 -> 자식)으로만 전파된다는 점)

  • 단방향 전파 : 아래 방향(부모 -> 자식)만 전파되는 것
    부모 예외로 인한 취소 발생 시 모든 자식 종료
    자식 예외로 인한 취소 발생 시 해당 Job만 종료

[supervisorScope]

블록 내부의 모든 코루틴에 SupervisorJob을 설정하고 싶을 때 사용.

[Activity 생명주기]

onCreate()

액티비티가 생성될 때 호출되며, 사용자 인터페이스 초기화에 사용됨.

onRestart()

액티비티가 멈췄다가 다시 시작되기 전에 호출됨.

onStart()

액티비티가 사용자에게 보여지기 바로 직전에 호출됨.

onResume()

액티비티가 사용자와 상호작용하기 바로 전에 호출됨.

onPause()

다른 액티비티가 보여질 때 호출됨. 데이터 저장 스레드 중지 등의 처리를 하기에 적당한 메서드

onStop()

액티비티가 더이상 사용자에게 보여지지 않을 때 호출됨. 메모리가 부족할 경우에는 onStop() 메서드가 호출되지 않을 수도 있음.

onDestroy()

액티비티가 소멸될 때 호출됨. finish() 메서드가 호출되거나 시스템이 메모리 확보를 위해 액티비티를 제거할 때 호출됨.

onDestroy()만 호출되는 경우

(최근 앱 목록 표시 중) – 액티비티 제거하기(밀어내기나 ‘X’ 버튼)
불릴때가 있고 안불릴때도 있음.

onPause() -> onStop() -> onDestroy()

(액티비티 foreground 상태) – 뒤로가기 버튼 눌러 액티비티 나가기

onPause() -> onStop()

(액티비티 foreground 상태) – 전원 버튼 누르기 – 화면 잠금
(액티비티 foreground 상태) – 홈 버튼 누르기 – 런쳐 홈으로 이동

onPause()만 호출되는 경우

  • (액티비티 foreground 상태) – 다른 액티비티 실행
2022-11-28 00:59:34.818 28307-28307/com.example.activitylifecycle D/MainActivity: onPause
2022-11-28 00:59:35.221 28307-28307/com.example.activitylifecycle D/MainActivity: onCreate2
2022-11-28 00:59:35.240 28307-28307/com.example.activitylifecycle D/MainActivity: onStart2
2022-11-28 00:59:35.241 28307-28307/com.example.activitylifecycle D/MainActivity: onResume2
2022-11-28 00:59:36.001 28307-28307/com.example.activitylifecycle D/MainActivity: onStop

[Flow]

순차적으로 값을 배출하는 비동기 데이터 스트림.
suspending 함수는 비동기적으로 하나의 값만 반환하고, flow는 여러개를 반환합니다.
콜드 스트림으로, 요청 측에서 collect를 호출해야 값을 발생하기 시작.
수집(Collect)되기 전까지 실행되지 않는다는 것

콜드 스트림 - 요청이 있는 경우에 보통 1:1로 값을 전달하기 시작.
핫 스트림 - 0개 이상의 상대를 향해 지속적으로 값을 전달.

코루틴에서 데이터 스트림을 구현하기 위해 필요한 구성요소는 다음과 같다.

Producer(생산자)
Intermediary(중간 연산자) - optional
Consumer(소비자)

[Flow 빌더]

flow {} 가장 기본적인 플로우 빌더.
emit() 이외에 asFlow()를 통해 Collection을 flow로 변환할 수 있음.

(1..10).asFlow().collect { value ->
    println(value)
}

flowOf { } - 고정된 값들을 방출하는 플로우를 정의
.asFlow () - 다른 Collection/Sequence들을 -> Flow로 변환
ex) List / Map / Array -> Flow로 변환

[Producer]

flow {} 코루틴 빌더(블록)을 생성한 후 내부에서 emit()을 통해 데이터를 생성.
플로우 블록은 suspend 함수이기 때문에 delay를 호출할 수 있음.

[Intermediary]

생성된 데이터를 수정함.
map(데이터를 원하는 형태로 변환), filter(데이터 필터링), onEach(데이터를 변경 후 수행한 결과를 반환) 등이 있음.

[Consumer]

collect()를 이용하여 전달된 데이터를 소비함.

[플로우 종단 연산자]

플로우 수집을 시작하는 종단 함수

  • toList 또는 toSet : Flow를 Mutable Collection으로 변환
  • first : 첫 번째 원소를 반환하고 나머지는 Cancel, 첫 번째 요소만 처리할 때
  • reduce : 첫 번째 원소부터 주어진 operation을 이용하여 누적시키면서 최종값을 반환
  • fold : 초기 값을 입력받아 주어진 operation을 이용하여 누적시키면서 최종값을 반환
  • collectIndexed : collect와 같은 동작이지만 index 요소가 추가되어 원하는 index에 맞는 처리가 가능

[다중 플로우 합성]

zip

두 개의 플로우들의 값(요소)들을 병합하는 연산자

val nums = (1..3).asFlow() // numbers 1..3
val strs = flowOf("one", "two", "three") // strings
nums.zip(strs) { a, b -> "$a -> $b" } // compose a single string
    .collect { println(it) } // collect and print
    
1 -> one
2 -> two
3 -> three

만약 두 개의 플로우 요소의 크기가 다르다면? : 두 개의 플로우 중 크기가 작은 플로우에 맞춰서 출력

0개의 댓글