반응형

1. 구성요소

1) 기본 구성요소

1–1)LayoutManager

*리스트, 그리드, 스태그드 그리드 등의 배치를 담당하고
*동시에 스크롤도 담당한다.
*아이템의 focus담당 (리스트를 내릴때 새로운 아이템을 추가해야하는지도 알려준다)

1–2) Adapter

*view, viewHoler생성
*item과 viewHolder를 bind
*RecyclerView에게 changes를 notify
*아이템 클릭등의 interaction 핸들링
*viewTypes 분기
*onFailedToRecyclerView를 통해 Recycler복원

2) 내부 구성요소

2 . Recycle 방법

1) 레이아웃 매니저가 getViewForPosition으로 view를 요청

2) RecyclcerView는 캐시에 getViewForPosition으로 확인. 있으면 LayoutManager에게 반환

3) 캐시에 없으면 adapter에게 type이 뭔지 물어보고 Recycled Pool에 getViewHolderByType으로 요청

4) Pool에 있으면 반환, 없으면 adapter에게 createViewHolder로 아이템 생성.

5) 뷰를 찾으면 adapter에서 bindview를 하고 LayoutManager에게 리턴.

6) LayoutManager는 RecyclerView에게 addView를 수행하고 adapter의 onViewAttachedToWindow가 호출됨.

  • 캐시와 Recycled Pool은 RecyclerView안에 이미 선언되어있음.
  • setItemViewCacheSize함수로 캐시 사이즈 변경 가능.

3. childeren 제어 순서

1) recyclerView가 LayoutManager에게 onLayoutChildren을 하면 배치를 시켜줌.
2) recyclerView에서 지워지거나 추가된 childeren들을 조사해서 알맞는 작업을 진행.
3) recyclerView는 ItemAnimator에게 animateChildren을 호출하여 애니메이션 수행
4) ItemAnimator가 동작을 하고 onAnimationFinished를 호출함.
5) adapter에게 onViewDetachedFromWindow를 호출
5) 상황에 따라서 view를 캐시하거나 재활용을 하게 된다.

4. ItemDecoration

1) getItemOffsets함수를 통해 Item의 영역을 늘릴 수 있다.
2) onDraw함수를 통해서 아이템이 그려지기 전에 먼저 그릴 수 있고
3) onDrawOver함수를 통해서 아이템 위에 덮어서 그릴 수 있다. 
4) 주의사항:

  • adapter에 접근하려 하지 말것
  • 그릴때 필요한 정보는 ViewHolder에 넣어둘것
  • viewHolder가 필요할때 recyclerView.getChildViewHolder(view)함수를 통해서 가져올것.

5. 꼭 알아야 할 것

1) 모든 아이템을 notify하는것은 되도록 피해라( notifyItemRangeChanged(0, getItemCount());
 ->해결 방법. notifyItemChanged(3)

2) onBindViewHolder 메소드 안에 final int position을 사용하지 말라. (final인 position은 보장하지 않는다. 위치가 바뀌거나 지워질때 등)
-> 해결방법. holder.getAdapterPosition을 이용해라.

3) holder.getAdapterPosition()해서 가져온 position값이 RecyclerView.NO_POSITION인지 꼭 확인하도록 해라.
-> 방법. if(holder.getAdapterPosition()!=RecyclerView.NO_POSITION)인지 확인하고 필요한 로직 추가.

4) Item Payload를 활용하라.
-> 방법. adapter.notifyItemChanged(position, LIKE_UPDATE); (좋아요 눌렀을때 처리, 타입을 여러개로 나눠서 필요한 호출을 한다.) 
 onBindViewHolder(holder, position, List payloads)를 override해서 사용하고
 payloads.contains(LIKE_UPDATE) 함수로 좋아요를 눌렀는지 판단해서 로직 처리가 가능하다.

5) onCreateViewHolder에서는 새로운 인스턴스만 반환해라.
-> 전역변수로 holder를 keep했다가 캐시처럼 넘기는 오류는 범하지 말라. 항상 new Holder()를 반환하도록 해라.

6) pool을 메모리 캐시에 놓고 모든 recyclerview에 세팅하지 말라.
:context leak를 유발하게 될것.

7) recyclerView adpater는 single쓰레드 기반이다.
->영향을 주는 list들은 main Thread에서만 변경하도록 해야 정상 동작한다.

반응형
,
반응형

일반적으로 하나의 인스턴스만 존재해야 할 경우 Singleton 패턴을 사용하게 된다. 물론 Single Thread에서 사용되는 경우에는 문제가 되지 않지만 Multi Thread 환경에서 Singleton 객체에 접근 시 초기화 관련하여 문제가 있다.

보통 Singleton 객체를 얻는 static 메서드는 getInstance()로 작명하는 게 일반적이다.

그렇다면 어떻게 코드를 작성해야 Singleton 객체를 생성하는 로직을 thread-safe 하게 적용할 수 있을까? 정말 단순하게 별로 신경 쓰고 싶지 않다면 메서드에 synchronized 키워드만 추가해도 무방할 것이다.

그렇지만 좋은 개발자가 되기 위해선 효율적인 코드에 대해서 고민을 해 봐야 한다. 메서드에 Singleton 클래스의 getInstance() 메서드에 synchronized키워드를 추가하는 건 역할에 비해서 동기화 오버헤드가 심하다고 생각한다.

그래서 개발자들 사이에서 Singleton 초기화 관련하여 많은 이디엄들이 연구되었고 몇몇 이디엄들을 소개하려고 한다.


Double Checked Locking

일명 DCL이라고 불리는 이 기법은 현재 broken 이디엄이고 사용을 권고하지 않지만 이러한 기법이 있었다는 걸 설명하고 싶다. 코드는 다음과 같다.


메서드에 synchronized를 빼면서 동기화 오버헤드를 줄여보고자 하는 의도로 설계된 이디엄이다. instance null 인지 체크하고 null 일 경우 동기화 블럭에 진입하게 된다. 그래서 최초 객체가 생성된 이후로는 동기화 블럭에 진입하지 않기 때문에 효율적인 이디엄이라고 생각할 수 있다. 하지만 아주 안 좋은 케이스로 정상 동작하지 않을 수 있다. 그래서 broken 이디엄이라고 하는 것이다.

Thread A와 Thread B가 있다고 하자. Thread A가 instance의 생성을 완료하기 전에 메모리 공간에 할당이 가능하기 때문에 Thread B가 할당된 것을 보고 instance를 사용하려고 하나 생성과정이 모두 끝난 상태가 아니기 때문에 오동작할 수 있다는 것이다. 물론 이러할 확률은 적겠지만 혹시 모를 문제를 생각하여 쓰지 않는 것이 좋다.


Enum

Java에 지대한 공헌을 한 Joshua Bloch가 언급한 이디엄이다. 그냥 간단하게 class가 아닌 enum으로 정의하는 것이다.


Enum은 인스턴스가 여러 개 생기지 않도록 확실하게 보장해주고 복잡한 직렬화나 리플렉션 상황에서도 직렬화가 자동으로 지원된다는 이점이 있다. 하지만 Enum에도 한계는 있다. 보통 Android 개발을 하게 될 경우 보통 Singleton의 초기화 과정에 Context라는 의존성이 끼어들 가능성이 높다. Enum의 초기화는 컴파일 타임에 결정이 되므로 매번 메서드 등을 호출할 때 Context 정보를 넘겨야 하는 비효율적인 상황이 발생할 수 있다. 결론은 Enum은 효율적인 이디엄이지만 상황에 따라 사용이 어려울 수도 있다는 점이다.


LazyHolder

현재까지 가장 완벽하다고 평가받고 있는 이디엄이다. 이 이디엄은 synchronized도 필요 없고 Java 버전도 상관없고 성능도 뛰어나다. 일단 코드를 보면 아래와 같다.


이 이디엄에 대해서 설명을 하자면 객체가 필요할 때로 초기화를 미루는 것이다. Lazy Initialization이라고도 한다. Singleton 클래스에는 LazyHolder클래스의 변수가 없기 때문에 Singleton 클래스 로딩 시 LazyHolder 클래스를 초기화하지 않는다. LazyHolder 클래스는 Singleton 클래스의 getInstance() 메서드에서 LazyHolder.INSTANCE를 참조하는 순간Class가 로딩되며 초기화가 진행된다. Class를 로딩하고 초기화하는 시점은 thread-safe를 보장하기 때문에 volatile이나 synchronized 같은 키워드가 없어도 thread-safe 하면서 성능도 보장하는 아주 훌륭한 이디엄이라고 할 수 있다.

현재까지 LazyHolder에 대해서 문제점은 나타나지 않고 있다. 혹시나 Multi Thread 환경에서 Singleton을 고려하고 있다면 LazyHolder 기법을 이용하자.


반응형
,
반응형

Context의 정의

Interface to global information about an application environment. 
This is an abstract class whose implementation is provided by the Android system. 
It allows access to application-specific resources and classes, as well as up-calls for application-level operations 
such as launching activities, broadcasting and receiving intents, etc.
  • Application 환경에 대한 전역 정보를 접근하기 위한 인터페이스.
  • 추상 클래스이며 실제 구현은 Android 시스템에 의해 제공된다.
  • Context를 통해 어플리케이션에 특화된 리소스나 클래스에 접근할 수 있다.
  • Activity 실행, Intent 브로드캐스팅 그리고 Intent 수신 등과 같은 응용 프로그램 수준의 작업을 수행하기 위한 API를 호출 할 수 있다.

어플리케이션에 관하여 시스템이 관리하고 있는 정보에 접근

  • Context 인터페이스가 제공하는 API 중 getPackageName(), getResource() 등의 메서드

안드로이드 시스템에서 제공하는 API 를 호출할 수 있는 기능

  • startActivity(), bindService() 와 같음 메서드

Context 역할

Context는 어플리케이션과 관련된 정보에 접근하고자 하거나 어플리케이션과 연관된 시스템 레벨의 함수를 호출하고자 할 때 사용된다. 
그런데 안드로이드 시스템에서 어플리케이션 정보를 관리하고 있는 것은 시스템이 아닌, AcitivityManagerService 라는 일종의 또 다른 어플리케이션이다. 
따라서 다른 일반적인 플랫폼과는 달리, 안드로이드에서는 어플리케이션과 관련된 정보에 접근하고자 할 때는 AcitivityManagerService를 통해야만 한다. 
당연히 정보를 얻고자 하는 어플리케이션이 어떤 어플리케이션인지에 관한 키 값도 필요하다.

즉, 안드로이드 플랫폼 관점에서 Context는 다음과 같은 두 가지 역할을 수행하기 때문에 꼭 필요하다.

  • 자신이 어떤 어플리케이션을 나타내고 있는지 알려주는 ID 역할
  • ActivityManagerService에 접근할 수 있도록 하는 통로 역할

Context 생성

Acitivy와 Service가 생성될 때 만들어지는 Context와 BroadcastReceiver가 호출될 때 (onReceive()) 전해지는 Context는 모두 서로다른 인스턴스이다.

즉, Context는 어플리케이션이 시작될 때는 물론, 어플리케이션 컴포넌트들이 생성될 때 마다 생성된다.

새롭게 생성되는 Context들은 부모와 완전히 독립되어 있는 존재가 아니고 ‘거의’ 비슷한 내용을 담고 있다.

Context 종류

안드로이드 프레임워크에서 Context는 2가지 종류로 나뉜다

  • Application Context
  • Activity Context

안드로이드 프레임워크에서 Context는 Application Context와 Activity Context로 구분지을 수 있는데, Application Context는 application life-cycle에 접목되는 개념이고 Activity Context는 activity life-cycle에 접목되는 개념이다. 즉 전자는 하나의 애플리케이션이 실행되어 종료될 때까지 동일한 객체인 반면, 후자는 액티비티가 onDestroy() 된 경우 사라질 수 있는 객체이다. 이를 참고하여 목적에 맞도록 알맞은 종류의 context를 참조해야 한다.

Application Context

Application Context는 애플리케이션 자체의 생명 주기(라이프사이클)에 영향을 받는다. 
따라서 항상 애플리케이션의 생명 주기와 함께한다.

Activity Context

Activity Context는 Activity의 라이프사이클과 함께 작동해 onDestroy()와 함께 사라진다. 
즉, Activity에 대한 환경 정보들이 Context에 있고, 이 Context에 Intent를 통해 다른 액티비티를 띄우면, 액티비티 스택이 쌓이게 된다.

Context Type

Application

실행중인 어플리케이션 프로세스는 Singletone instances 이다. 
Activity이나 Service에서는 getApplication() 메소드를 통해 getApplicationContext()로 Context를 얻어 올 수 있다. 
프로세스내 동일한 instanees를 받게 된다.

Activity/Service

ContextWrapper를 상속받아 구현한 Context이며 메소드로 Context를 얻어 오면 기본 Context로 구성되어 있다. 
프레임워크는 Activity또는 Service가 실행 될때 기본 Context에다 필요한 정보를 warp한다. 
실행시 고유한 Context를 가지게 된다.

BroadcaseReceiver

위의 2가지와 다르게 자기자신이 Context자체는 아니다. 
하지만 onRecevie()시 Context를 가져올 수 있는데, 이때의 Context는 ReveiverRestrictedContext이며 두가지 기능 registerReceiver()와 bindService()를 사용 할 수 없다. 
리시버가 브로드캐스트를 처리 할때마다 새로운 Context가 생성 된다.

ContextProvider

브로드캐스트와 마찬가지로 자기자신이 Context를 상속 받은것은 아니다. 
하지만 액세스후 getContext()를 통해 Context를 가져 올 수 있다. 
ContextProvider가 동일한 응용프로그램에 대해 호출시, 동일한 응용프로그램의 Singletone instances를 반환하게 된다. 
하지만 별도의 프로세스에 있는 경우(서로다른 다른 앱), 프로바이더가 실행되는 응용프로그램의 instances가 반환된다.




Context 참조

LoginActivity.this

LoginActivity.this 는 Acitivity를 상속받은 자신의 클래스를 참조하지만 Activity 또한 Context class를 상속받으므로 activity context를 제공하는데 사용될 수 있다.

getApplication()

getApplication()은 Application 객체를 참조하지만 Application 클래스는 Context 클래스를 상속받으므로 application context를 제공하는데 사용될 수 있다.

getApplicationContext()

getApplicationContext()는 application context를 제공한다.

getBaseContext()

getBaseContext()는 activity context를 제공한다.



반응형
,
반응형

태스크란? (Task, Activity Stack)
어피니티란? (Android Affinity)
플래그란? (Android Flag)




안드로이드 태스크란? (Android Task, Activity Stack)

- Task는 어플리케이션에서 실행되는 액티비티를 보관하고 관리하며 Stack형태의 연속된 Activity로 이루어진다

- 선입후출(First In Last Out)형태로 나중에 적재된 액티비티일 수록 가장 먼저 사용된다 
만약 1페이지>2페이지>3페이지 순으로 액티비티를 이동했을때 실행순서대로 Task에 push해 놓았다가
back버튼을 누르면 3페이지>2페이지>1페이지순으로 Task에서 pop시켜 되돌아 간다고 생각하면 된다

- 서로 다른 어플리케이션간의 이동에도 Task를 이용해 사용자 경험(UX)를 유지시켜 준다

- 최초적재 액티비티는 Root Activity 라고 하며 어플리케이션 런처로부터 시작된다

- 마지막으로 적재되는 액티비티는 Top Activity 라고 하며 현재 화면에 활성화 되어있는 액티비티를 말한다

- Task내에는 서로 다른 어플리케이션의 액티비티들이 포함될 수 있어 어플리케이션에 경계없이 하나의 어플리케이션인것 처럼 보이게 해준다

- Task의 Stack내에 존재하는 액티비티들은 모두 묶여서 background와 foreground로 함께 이동한다. 홈버튼 클릭(task interrupt => background 이동), 홈버튼 롱클릭(recent task => foreground 이동)

- Flag를 사용하여 Task내 액티비티의 흐름을 제어할 수 있다


어피니티란? (Android Affinity)

- 어플리케이션 내의 액티비티들은 하나의 어피니티를(affinity:친화력) 가지고 있다

- AndroidManifest 에서 <activity> 요소의 taskAffinity 속성을 사용해 개별 affinity가 지정 가능하다

- FLAG_ACTIVITY_NEW_TASK 플래그를 가진 인텐트 객체로 부터 호출된 allowTaskReparenting 
속성을 True로 가지고 있는 액티비티에 한해 affinity 가 동작한다

- 위 조건이 만족한 상황에서 시작된 액티비티는 자신과 동일한 어피니티를 갖는 태스크가 있을경우 해당 태스크로 이동한다

- 즉, [b]어피니티를 가진 A액티비티가 호출되어 해당 태스크에 속해있을때 [b]어피니티를 가진 태스크가 호출되면 A액티비티는 [b]어피니티를 가진 태스크로 이동한다

- 어피니티에 의해 태스크가 이동된 후에 back버튼으로 반환시 원래 해당하던 태스크로 돌아간다

- 하나의 어플리케이션내에서 하나 이상의 기능을 갖는 어플리케이션이 존재할경우 각 액티비티별로 
다른 어피니티를 지정해 관리할 수 있다


플래그란? (Android Flag)

- AndroidManifest 에서 플래그를 사용할때에는 <activity> 요소의 launchMode 속성을 사용하며
launchMode에서 사용가능한 속성은 다음과 같이 4가지만 가능하다

standard: 
스택중 어느곳에나 위치 가능하며 여러개의 인스턴스가 생성가능하다

singleTop: 
스택중 어느곳에나 위치 가능하며 여러개의 인스턴스가 생성가능하고 호출한 activity와 현재
최상위 activity가(top activity) 동일한 경우 최상위 activity가 재사용 된다(기존 최상위 activity는 pop)

singleTask: 
루트 액티비티로만 존재하며 하나의 인스턴스만 생성가능하다(타 task에서 동일 activity 사용불가)
다른 액티비티 실행시 동일 Task내에서 실행이 가능하다

singleInstance: 루트 액티비티로만 존재하며 하나의 인스턴스만 생성가능하고 태스크내에 해당
액티비티 하나만 속할 수 있어
 다른 액티비티를 실행시키면 새로운 Task가 생성되어
(FLAG_ACTIVITY_NEW_TASK와 동일) 그 Task내에 포함된다


- 소스코드에서 플래그를 사용하고 싶을때에는 Intent에 addFlags() 또는 setFlags() 메소드를 사용한다

FLAG_ACTIVITY_NEW_TASK: 
동일 affinity의 task가 있으면 그곳에 실행되고 아니면 새로운 task를 실행

FLAG_ACTIVITY_SINGLE_TOP: 
상단 singleTop과 같으며, 실행시 재사용 액티비티의 실행은 onPause(), onNewIntent(), onResume()
순으로 호출된다
☞ [B]를 single top설정: [A][B] 상태에서 [B] 호출시 => [A][재사용된B]
☞ [B]를 single top설정: [B][A] 상태에서 [B] 호출시 => [B][A][B]

FLAG_ACTIVITY_NO_HISTORY:
해당 액티비티는 재활성화시(back키를 눌러 다시 활성화될때) pop 된다
☞ [B]를 no history설정: [A][B][A] 상태에서 back키 사용시 [A]가 pop 되고 [B] 역시 
no history에 의해 pop => [A]

FLAG_ACTIVITY_REORDER_TO_FRONT:
activity 호출시 이미 같은 activity가 task내에 있으면 같은 activity는 pop 시키고 해당 activity가 push 된다
☞ [A]를 reorder to front설정: [A][B] 상태에서 [A] 호출시 같은 activity인 [A]가 pop되고 => [B][A]

FLAG_ACTIVITY_CLEAR_TOP:
해당 task에 있는 모든 activity를 pop 시키고 해당 activity가 root activity로 task에 push된다
☞ [A]를 clear top설정: [A][B] 상태에서 [A] 호출시 모두 pop되고 => [A]
단, 해당 플래그는 액티비티를 모두 onDestroy() 시킨 후 새롭게 onCreate() 시키기 때문에 [A]를
유지하려면 FLAG_ACTIVITY_SINGLE_TOP 플래그와 함께 사용하면 된다

http://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_BROUGHT_TO_FRONT


Clear Task

- Task를 오랫동안 사용하지 않고 방치해 두면 시스템은 Root Activity를 제외한 모든 액티비티를 Clear 시킨다
- 이러한 동작은 Activity의 속성을 수정하여 제어할 수 있다

alwaysRetainTaskState:
Task의 Root Activity에 true로 설정되어 있다면 상단에 언급되었던 동작은 발생하지 않으며 Task는
오랜 시간 이후에도 Stack에 있는 모든 Activity를 유지한다

clearTaskOnLaunch:
이 속성이 true로 설정되어 있으면 alwaysRetainTaskState 와 정반대로 사용자가 Task를 떠났다가
다시 돌아올 때마다 항상 Stack은 Root Activity로 정리된다

finishOnTaskLaunch:
이 속성은 clearTaskOnLaunch와 유사하지만 전체 Task가 아닌 단일 Activity에서 동작한다
그리고 그것은 Root Activity를 포함한 어떤 Activity가 사라지는 원인이 될 수도 있다
true로 설정되어 있을 때, Activity는 현재 Session 동안 Task의 일부만 유지한다
만일 사용자가 해당 Task를 벗어났다가 다시 돌아오면 더이상 존재하지 않는다



반응형
,
반응형

안드로이드의 Process

타 운영체제와 마찬가지로, 실행가능한 프로그램 데이터가 메모리로 로드되어 실행된 것을 프로세스라고 한다. 안드로이드는 리눅스 기반의 OS이므로, 리눅스의 프로세스처럼 각각 ID가 부여되며, 다른 프로세스와 철저히 격리되어 실행된다.

Process의 구등

안드로이드는 앱의 컴포넌트(엑티비티, 서비스, 리시버, 프로바이더)를 실행하는 시점에서, 그 컴포넌트가 메모리로 로드되고, 컴포넌트가 속한 앱의 프로세스가 구동된다.
Note : 앱의 프로세스가 새로 구동될 때마다, 시작점은 Application의 onCreate()메서드이다.

Process 변경

컴포넌트는 기본적으로 앱의 프로세스에 속하지만, 소속 프로세스를 변경할 수도 있다.
메니페스트 파일에서 컴포넌트를 선언할 때, android:process 필드를 지정하면, 이 컴포넌트는 앱의 프로세스와는 별개로, 다른 독립된 프로세스에서 실행된다.
Note : 별도의 프로세스에서 동작하더라도, Application의 onCreate()메서드는 호출된다.(왜그럴까......??)

안드로이드의 Task

앱을 실행하게 되면, 그 앱은 필요한 컴포넌트들을 호출하게 된다. 각 앱마다 현재 사용하고 있는 컴포넌트들을 그룹화하여 관리하는 것이 Task이다. 여기서의 컴포넌트는 해당 앱에 속한 컴포넌트 일수도 있고, 다른 앱에 속한 컴포넌트 일수도 있다.

컴포넌트 교차 생성

안드로이드의 독특한 특징으로, 다른 앱에 속한 컴포넌트를 호출하여 사용할 수 있다. 이렇게 호출된 컴포넌트는 현재 실행되고 있는 앱의 Task에 속하게 된다. 하지만, 호출된 컴포넌트가 메모리에 로드되면, 그 컴포넌트의 원래 프로세스가 구동되며, 컴포넌트가 실행되면서 필요한 권한설정은 원래 앱이 획득한 권한설정을 따르게 된다.
  • ex) 메모앱에서 기본 카메라의 엑티비티를 호출하는 경우
    • 기본 카메라의 엑티비티는 메모앱의 Task로 묶이게 된다.
    • 메모앱이 기본 카메라 엑티비티를 호출하는 시점에, 기본 카메라 앱의 프로세스가 구동된다. 즉, 메모앱의 프로세스와 기본 카메라 앱의 프로세스가 동시에 실행중인 상황이 된다.
    • 기본 카메라 엑티비티가 사용하고자 하는 권한(카메라 접근, 외부저장소 저장 등)은 메모앱의 권한설정이 아닌, 기본 카메라 앱의 권한설정을 따르게 된다. 따라서, 메모앱은 사진관련 권한이 없어도, 기본 카메라 앱에 권한이 있으면 문제없이 이용할 수 있고 반대로, 메모앱이 사진관련 권한이 있어도, 기본 카메라 앱에 권한이 없으면 기능을 이용할 수 없다.

엑티비티 스택

Task는 엑티비티의 순서를 관리하기 위해 별도로 스택을 유지하여 엑티비티를 저장한다. 즉, Task가 지워지면, 엑티비티의 순서도 지워지게 된다.

Task 변경

다른 컴포넌트들에 비해, 엑티비티는 메니페스트에서 <activity>태그의 android:launchMode 필드를 주거나, 엑티비티를 실행할 인텐트에 플래그 값을 추가하여 Task를 변경할 수 있다.(변경이라고는 해도 Task간 이동은 불가능하다.)


반응형
,
반응형

: 서비스를 사용하지 않고 별도의 스레드를 생성해 백그라운드 작업을 하면 어떤 문제가 발생할까? 스레드로 백그라운드 작업을 하다가 앱에서 back 키를 눌러 액티비티를 모두 종료할 시 앱 프로세스의 우선순위가 낮아져 LMK(low memory killer)가 프로세스를 강제로 종료할 수 있다. 이 때 스레드 또한 종료되게 된다. 그렇다면 서비스는 그냥 스레드와 다를까? 먼저, 프로세스 우선순위에 대해 알아보자. 


- 프로세스 우선순위

1. 포그라운드 프로세스 : 사용자와 상호작용하는 액티비티나 그런 액티비티에 바인딩된 서비스, onCreate/ onStart/ onStartCommand/ onDestory 를 실행 중인 서비스, onReceive를 실행하는 브로드캐스트 리시버에 해당한다. 


2. 가시(visible) 프로세스 : 포그라운드 컴포넌트를 가지지 않지만 사용자가 보는 화면에 아직 영향이 있는 프로세스이다. 액티비티의 경우 onPause()까지 실행됬지만 가시 상태인 것이다. 이 액티비티에 바인딩된 서비스도 가시 프로세스이다.


3. 서비스 프로세스 : startService로 실행했지만 1,2 번 상태가 아닌 서비스가 실행 중인 프로세스이다.


4. 백그라운드 프로세스 : 액티비티가 종료되지 않았지만 사용자에게 보이지 않고 활성화된 컴포넌트가 없는 프로세스(홈 키를 눌러 onStop()까지 불린 태스크)


5. 빈 프로세스 : 사용자가 백 키로 액티비티를 모두 종료하고 활성화된 컴포넌트가 없는 프로세스이다. 우선순위가 낮아서 리소스가 부족하면 가장 먼저 종료된다.


=> 맨 위에서 말한 것을 보면 액티비티가 모두 종료되었으니 빈 프로세스가 된다. 하지만 서비스를 사용한다면 onStartCommand가 리턴된 후 서비스는 서비스 프로세스가 된다. 따라서 백그라운드 작업이 종료될 위험에서 안전하다.


: 서비스를 사용할 때 주의할 점은 서비스의 생명주기 메서드는 메인 스레드에서 실행된다는 점이다. 생명주기 메서드 내에서 오랜 시간동안 작업하면 안 된다. 또한 서비스는 앱에서 1개의 인스턴스밖에 생기지 않는다. 별도로 싱글톤으로 구현하지 않아도 되고 서비스 안에서 사용되는 변수도 별도의 싱글톤 패턴으로 만들 필요 없다. 다음은 서비스의 생명주기이다.

서비스는 스타티드 서비스와 바운드 서비스가 있다. 서로 결합해서 사용할 수도 있다. 스타티드&바운드 서비스를 사용해야 하는 예는 다음과 같다. 음악 재생 액티비티에서 액티비티를 종료해도 음악이 나와야 한다. 이는 스타티드 서비스이다. 홈 화면에서 음악 재생 액티비티로 가면 현재 듣고 있는 음악에 대한 정보가 나와야 한다. 이는 바운드 서비스이다. 또 다른 경우 네트워크로 파일을 다운로드 받고 있다면 액티비티를 종료해도 다운은 계속 되야 한다. 이는 스타티드 서비스이다. 다시 액티비티로 돌아왔을 때 다운 진행 상황을 보여줘야 한다면 이는 바운드 서비스이다. 이렇게 바운드 서비스와 스타티드 서비스를 결합해야 되기도 하다.


- 스타티드 서비스

: 스타티드 서비스는 startService로 시작한다. 단, startService를 호출한다고 바로 시작하지 않고 메인 루퍼의 메시지 큐에서 차례대로 작업이 처리된다. startService를 처음 호출했을 때는 onCreate()->onStartCommand() 가 콜백되고 그 이후에 startService를 호출하면 onStartCommand만 호출된다. 서비스에서 작업 진행 상황을 액티비티에 전달해야 할 때가 있다. 이 때 ResultReceiver 브로드 캐스트를 사용한다. 

@Override
protected void onCreate(Bundle savedInstanceState){
	
	....
	
	Intent intent = new Intent(this, MyService.class);
	intent.putExtra(Constant.EXTRA_RECEIVER, resultReceiver);
	startService(intent);
	
}

private Handler handler = new Handler();

private ResultReceiver resultReceiver = new ResultReceiver(handler){
	@Override
	protected void onReceiveResult(int resultCode, Bundle resultData){
		if(resultCode == Constant.SYNC_COMPLETED){
			.....
		}
	}
}

class MyService extends Service{
	@Override
	public int onStartCommand(Intent intent, int flags, int startId){
		
		....
		
		final ResultReceiver receiver = intent.getParcelableExtra(Constant.EXTRA_RECEIVER);
		recevier.send(Constant.SYNC_COMPLETED, null);
		stopSelf();
	}
}

만약 디바이스의 메모리가 부족할 경우 안드로이드 시스템은 서비스를 강제 종료시킬 수 있다. 이 때 서비스는 재시작할 수 있는 데 이는 onStartCommand의 리턴 값에 따라 동작 방식이 달라진다. 리턴 값은 다음과 같다.


- START_NOT_STICKY : 강제 종료되면 재시작하지 않는다. 예로 화면에 최신 뉴스를 보여줄 때다.


- START_STICKY : 강제 종료되면 다시 onStartCommand를 호출하되 Intent가 null로 전달된다. onStartCommand 메서드의 기본 리턴 값이다. intent가 null로 전달되기 때문에 내부 변수를 사용하는 서비스에 적합하다. 예로 SNS 앱에서 새로운 메시지가 몇 개 왔는 지 정기적으로 확인한다면 적절하다. 


- START_REDELIVER_INTENT : 강제 종료되면 재시작하되 Intent가 전달된다. 예로 특정 상점의 상품 목록을 가져온 후 DB에 저장하는 경우가 있다.


재시작 동작 방식은 중요하지만 그에 못지 않게 서비스 종료도 중요하다. 서비스 종료를 제대로 하지 않으면 작업이 종료되었는 데 재시작 할 수도 있고 메모리를 잡아먹게 된다. 서비스 종료는 stopService() 보다는 stopSelf() 메서드로 하는 걸 권장한다. 두 메서드의 동작 방식은 동일하지만 stopSelf()는 Service 내에서 호출하는 것이다. 서비스의 작업이 끝나면 바로 stopSelf를 호출해 종료하는 게 좋다. 이 때 서비스는 onDestroy()까지 실행된다. 서비스에서 여러 개의 스레드가 실행될 때 주의할 게 멤버 변수를 최소한으로 사용해야 한다. 여러 스레드가 잘못 공유할 수 있기 때문이다. 또한 여러 군데에서 startService를 실행한다면 모든 작업이 끝났을 때 서비스를 종료해야 한다. 이를 위해서 종료할 때 매 작업마다 stopSelf()를 호출하지 말고 stopSelfResult(int startId)를 호출하는게 좋다. stopSelfResult는 startId가 가장 최근에 시작된 것이라면 그 때 서비스를 종료한다. startId는 onStartCommand() 메서드의 인자이다.


서비스의 멀티스레딩이 필요없을 경우 순차적으로 실행하는 IntentService를 사용하자. IntentService는 onStartCommand 메서드의 기본 리턴 값이 START_NOT_STICKY이다. 리턴 값을 변경하려면 setIntentRedelivery(true)를 호출하면 된다. IntentService에서 Toast를 사용할 때 주의해야 한다. Toast는 내부적으로 Handler을 생성해 루퍼가 필요하다. 일반 Service에서는 Toast를 사용하려면 Looper.prepare을 해야 한다. IntentService는 내부적으로 Looper가 있기 때문에 바로 Toast 사용이 가능하다. 하지만 Toast는 화면에 Toast를 보여주는 작업과 일정 시간 후 화면에서 Toast를 제거하는 작업을 해서 루퍼의 메시지 큐에 2개의 메시지가 들어간다. 만약 IntentService가 Toast를 보여주는 작업만 메시지 큐에서 처리하고 Destroy 되 Looper가 제거된다면 Toast 제거 작업은 실행이 안 되 Toast가 화면에 계속 띄어지게 된다. 따라서 Looper.getMainLooper를 사용해 핸들러를 생성 후 Toast를 사용하는 게 좋다. 


서비스가 중복되서 실행되는 걸 원치 않을 수 있다. 예로 메모 앱에서 서버와 지속적으로 동기화할 때 비슷한 시간에 여러 번 동기화 되는 걸 원치 않을 수 있다. 즉, startService를 호출할 때마다 매번 스레드가 생성되지 않고 이미 시작되었다면 나머지는 스킵해야 할 수 있다. 이를 구현하기 위해 onCreate()에서 스레드를 생성하여 구현(onCreate는 한번만 호출된다)할 수 있지만 서비스의 일반적인 형태가 아니다. onStartCommand()에서 스레드를 생성해서 이를 구현하는 방법은 다음과 같다. 

private ExecutorService exec = Executors.newSingleThreadExecutor();
private boolean isRunning = false;

@Override
public int onStartCommand(Intent intent, int flags, int startId){
	if(isRunning)
		return START_NOT_STICKY;
	isRunning = true;
	exec.submit(new Runnable(){
		......
               stopSelf();
	});
	return START_STICKY;
}

@Override
public void onDestroy(){
	isRunning = false;
}

isRunning 변수를 통해 스킵 시에는 START_NOT_STICKY를 리턴해 재시작되지 않도록 했다. Executors 팩토리 메서드를 사용했는데 ThreadPoolExecutor을 직접 사용하면 exec 변수를 다음과 같이 할 수 있다.

private exec = new ThreadPoolExecutor(1,1, 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadPoolExecutor.DiscardPolicy();

DiscardPolicy를 통해 요청이 추가로 들어온다면 해당 요청을 버린다. 따라서 isRunning 변수를 사용할 필요 없다.



- 바운드 서비스

: 바운드 서비스는 서비스에 정의된 메서드를 다른 컴포넌트에서 호출할 수 있도록 한다.

public abstract boolean bindService(Intent service, ServiceConnection conn, int falgs)

service 매개변수는 대상 서비스를 가리킨다. conn은 서비스와 연결되거나 끊길 때의 콜백이다. flags는 보통 Context.BIND_AUTO_CREATE가 들어간다. startService를 실행해서 서비스를 실행하지 않았다면 BIND_AUTO_CREATE 옵션을 넣어야 한다. 스타티드 & 바운드 서비스가 아닌 이상 startService를 호출할 일이 없으므로 해당 옵션은 필수이다. 해당 옵션을 주고 stopService를 호출하면 서비스는 종료되지 않는다. 바운딩된 곳마다 unbindService를 실행해야 서비스가 종료된다. 해당 옵션이 없으면 stopService로 바로 종료된다. 또한 메모리 문제로 강제 종료됬을 시 해당 옵션이 있다면 재연결된다. 바운드 서비스는 로컬 바인딩과 리모트 바인딩으로 나뉜다. 리모트 바인딩은 다른 프로세스에서 접근할 때 사용하고 로컬 바인딩은 로컬 프로세스에서만 접근 가능하다.


여러 클라이언트들이 bindService()를 호출했다면 모든 클라이언트가 unbindService()를 호출해야 서비스가 정리된다. bindService와 unbindService는 액티비티의 onStart와 onStop에서 호출하는 걸 권장한다. onResume/onPause에서 이들 메서드들을 호출하면 해당 생명주기들이 자주 콜백되기 때문에 서비스를 종료하고 생성하는 작업을 많이 해 비용이 많이 든다. 


바인드 서비스에서 백그라운드 작업 시 결과를 돌려주는 방법으로 다음과 같이 4가지가 있다.

1. 백그라운드 작업이 끝났을 때 sendBroadcast()를 사용한다.

2. ResultReceiver을 사용한다.

3. Messenger를 사용해 양방향 통신을 한다.

4. 바인더 콜백을 이용한다. 이 방법은 안드로이드 프레임워크에서 많이 사용되는 방법이다. 아래는 바인더 콜백을 이용한 예이다.

public class MainService extends Service {
//서비스에서 선언.
 
//서비스 바인더 내부 클래스 선언
 public class MainServiceBinder extends Binder {
        MainService getService() {
            return MainService.this; //현재 서비스를 반환.
        }
    }
 
private final IBinder mBinder = new MainServiceBinder();
 
 @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        //throw new UnsupportedOperationException("Not yet implemented");
        return mBinder;
    }
 
//콜백 인터페이스 선언
public interface ICallback {
        public void recvData(); //액티비티에서 선언한 콜백 함수.
    }
 
private ICallback mCallback;
 
//액티비티에서 콜백 함수를 등록하기 위함.
public void registerCallback(ICallback cb) {
        mCallback = cb;
    }
 
//액티비티에서 서비스 함수를 호출하기 위한 함수 생성
public void myServiceFunc(){
    //서비스에서 처리할 내용
    }
 
 
//서비스에서 액티비티 함수 호출은..
     
    mCallback.recvData();
}
public class MainActivity extends Activity {
 
//액티비티에서 선언.
private MainService mService; //서비스 클래스
 
//서비스 커넥션 선언.
private ServiceConnection mConnection = new ServiceConnection() {
        // Called when the connection with the service is established
        public void onServiceConnected(ComponentName className, IBinder service) {
            MainService.MainServiceBinder binder = (MainService.MainServiceBinder) service;
            mService = binder.getService(); //서비스 받아옴
            mService.registerCallback(mCallback); //콜백 등록
        }
 
        // Called when the connection with the service disconnects unexpectedly
        public void onServiceDisconnected(ComponentName className) {
            mService = null;
        }
    };
 
//서비스에서 아래의 콜백 함수를 호출하며, 콜백 함수에서는 액티비티에서 처리할 내용 입력
private MainService.ICallback mCallback = new MainService.ICallback() {
        public void recvData() {
 
    //처리할 일들..
 
        }
    };
 
//서비스 시작.
public void startServiceMethod(View v){
        Intent Service = new Intent(this, MainService.class);
        bindService(Service, mConnection, Context.BIND_AUTO_CREATE);
    }
 
//액티비티에서 서비스 함수 호출
 
    mService.myServiceFunc();
}


반응형
,
반응형

안드로이드 어플리케이션을 구성하는 네 가지 기본 요소에는 ActivityServiceBroadcast ReceiverContent Provider가 있다.  인텐트(Intent)란 이러한 어플리케이션 구성요소(컴포넌트) 간에 작업 수행을 위한 정보를 전달하는 역할을 한다.  인텐트를 가장 손쉽게 사용한 예로는 액티비티간의 화면전환을 들 수 있다.  즉 인텐트는 컴포넌트A가 컴포넌트B를 호출할 때 필요한 정보를 담고 있으며, 이 정보에는 호출되는 컴포넌트B의 이름이 명시적으로 표시되기도 하고, 속성들이 암시적으로 표시되기도 한다.  또한 호출된 컴포넌트B가 호출한 컴포넌트A로 어떠한 결과를 전달할 때도 인텐트가 사용된다. 어떠한 컴포넌트를 호출하냐에 따라 사용되는 대표적인 메소드로는 다음과 같은 것들이 있다.

  • startActivity() -새로운 액티비티를 화면에 띄울 때
  • startService(), bindService() – 서비스와 관련
  • broadcastIntent() – 브로드캐스팅을 수행할 때

인텐트는 명시적 인텐트(Explicit Intent)와 암시적 인텐트(Implicit Intent)로 구분할 수 있는데 인텐트에 클래스 객체나 컴포넌트 이름을 지정하여 호출될 대상을 확실히 알 수 있는 경우에는 명시적 인텐트, 호출될 대상의 속성들을 지정했지만 호출될 대상이 달라질 수 있는 경우에는 암시적 인텐트라고 한다.

  1. 명시적 인텐트

다음은 MainActivity에서 button을 눌렀을 때 SecondActivity로 화면전환하는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*MainActivity*/
 
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
 
 
public class MainActivity extends Activity implements View.OnClickListener{
Button button;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 button = (Button)findViewById(R.id.button);
 button.setOnClickListener(this);
 }
 
 @Override
 public void onClick(View v) {
 Intent intent = new Intent(getApplicationContext(),SecondActivity.class);
 startActivity(intent);
 }
}

위 코드에서 살펴 볼 코드는 다음과 같다.

1
2
Intent intent = new Intent(getApplicationContext(),SecondActivity.class);
startActivity(intent);

새로운 인텐트 객체를 생성하면서 getApplicationContext()와 SecondActivity.class를 파라마터로 넘긴다. getApplicationContext()는 현재 액티비티 정보가 담겨있는 것이라고 생각하면 될 듯 하다. SecondActivity는 호출할 컴포넌트이다. 그리고 startActivity()에 인텐트 객체인 intent를 파라미터로 넘겨주면 새로운 화면인 SecondActivity가 실행된다. startActivity()는 새로 띄우는 액티비티로부터 받는 응답을 처리할 필요가 없을 때 간단하게 사용된다. 이에 반해 startActivityForResult()의 경우 새로 띄운 액티비티로 받는 응답을 처리할 경우에 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*MainActivity*/
Intent intent = new Intent(getApplicationContext(), SecondActivity.class);
startActivityForResult(intent,REQUEST_CODE);
 
...
 
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode==REQUEST_CODE){
        if(resultCode==RESULT_OK){
            String key = intent.getExtras().getString("key");
            Toast.makeText(getApplicationContext(),key,Toast.LENGTH_LONG).show();
        }
    }
}

startActivityForResult()는 인텐트 객체인 intent뿐만 아니라 정수형태의 코드 값을 파라미터로 전달하는데, 새로 띄웠던 여러 액티비티 중에 어떤 것으로부터 온 응답인지 구분하기위해 사용된다. 이 코드 값은 임의로 선언할 수 있지만 어플리케이션에 들어갈 액티비티가 여러 개 있을 수 있으므로 서로 중복되지 않는 값으로 설정해야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*SecondActivity*/
...
public class SecondActivity extends View.OnClickListener{
Button button;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    button = (Button)findViewById(R.id.button);
    button.setOnClickListener(this);
  }
@Override
public void onClick(View v){
    Intent result = new Intent();
    result.putExtra("key",value);
    setResult(RESULT_OK,result);
    finish();
  }
}

새로 띄워진 SecondActivity에서 result라는 인텐트 객체를 생성하고, putExtra()를 통해 부가 데이터를 넣고 있다. putExtra() 메소드를 이용할 때는 key와 value 값을 함께 넣어줘야 나중에 value값을 확인하고자 할 경우 key값을 가지고 데이터를 가지고 올 수 있다. 그리고 setResult()메소드를 통해 MainActivity로 응답을 보내고 있다. 그러면 MainActivity에서는 onActivityResult()함수로 그 응답을 처리하는 역할을 한다.

2. 암시적 인텐트

1
2
3
4
5
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://m.naver.com"));
startActivity(intent);
....
Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("tel:010-0000-0000"));
startActivity(intent);

위와 같이 암시적 인텐트는 보통 액션(Action)과 데이터(data)라는 속성으로 구성되어있다. 이 두가지 속성말고도 Category, Type, Component, Extras 라는 속성을 가진다. Component라는 속성을 지정할 경우 컴포넌트 클래스 이름을 명시적으로 지정하게 되는데 이 경우가 명시적 인텐트에 속하게 된다. 결국 암시적 인텐트는 Component 속성을 제외한 나머지 속성들로 구성되며, 이러한 속성들에 부합하는 컴포넌트가 실행된다.(호출할 대상들이 달라질 수 있음)

1
2
3
4
5
...
public void onClick(View v){
  Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://m.naver.com"));
  startActivity(intent);
}

위 코드를 실행했을 때의 화면이다.
intent
이와 같이 암시적 인텐트는 그 속성에 부합하는 컴포넌트가 여러개 있을 때 선택할 수 있도록 해준다.

3. 인텐트 필터

안드로이드 어플리케이션을 만들 때 메니페스트라는 파일이 있고, 파일 내용을 보면 Intent-filter라는 것이 있다.  예를 들어 컴포넌트 A가 컴포넌트 B로 인텐트를 보냈다고 하자. 명시적 인텐트라면 컴포넌트B는 바로 인텐트를 받았겠지만 암시적 인텐트라면 컴포넌트B는 이 인텐트가 자신의 속성과 부합하는지 확인을 해야할 것이다. 이러한 확인작업을 위해 Intent-filter라는 것이 존재한다. 인텐트 필터도 암시적 인텐트의 속성처럼 Action, data, category 필터라는 것을 가지고 있다. 이 필터들을 가지고 암시적 인텐트의 속성과 비교를 하게된다.

반응형
,
반응형

1. Activity 생명주기


먼저, Activity에 대해서 정리해볼텐데요,

아래 이미지를 보시면서 전체 흐름에 대해서 보고, 

하나씩 정리해보도록 하겠습니다.



1-1. OnCreate ()


액티비티가 Launch되고 나서, 최초에 실행될 때, 호출 됩니다.

액티비티에 필요한 리소스들을 여기서 초기화 해주는데요.

Button이라든가 EditText를 예로 들 수 있겠습니다.



1-2. OnStart ()


유저에게 액티비티가 보이도록 해주는데요.

이 단계는 매우 빠르게 끝나고, onResume() 상태로 들어갑니다.


보통 BroadcastReceiver는 이 단계에서 등록을 하는것이 좋습니다.



1-3. OnResume () 와 OnPause()


제 생각에, 안드로이드 개발에 있어서 꼭 알아야 하는, 매우 중요한 단계 두가지입니다.

OnResume 단계에서, Activity는 foreground로 나와서, 유저와 인터랙션을 하게 되는데요.


이 때, 유저에게 전화가 오거나, 다른 액티비티로 이동하면, OnResume()에 있던 activity는,

OnPause()를 호출하게 됩니다.

그러다가 유저가 다시 해당 activity로 돌아오면, 이번에는 다시

OnResume()을 호출합니다.


즉, Foreground로 나오면, OnResume을 호출하고, 다른 액티비티가 Background로 나오게 되면, OnPause를 호출하는 것인데요.

OnResume에서는, Camera리소스 같이 화면이 포커스를 가지고 있는 경우에만 사용할 리소스들을 초기화 해주고,

OnPause에서, 해제해 주어야 합니다.


생각보다 OnResume과 OnPause에서 리소스를 초기화하고, 해제해주어야 하는 경우가 많습니다.

BroadcastReceiver를 동적으로 생성했거나, SensorManager로 GPS를 사용하거나 하는 경우에는,

OnPause시에 리소스를 해제해 주면, 배터리소모를 막을 수 있기도 합니다.


한가지 주의할 점은, 

OnPause에서  데이터를 저장하거나, 네트워크를 호출하고, 데이터베이스를 실행하면 않된다는 것입니다.

안드로이드 가이드에서는, onPause가 위의 과업들을 수행할 만큼의 충분한 시간을 가지지 못한다고 합니다.

이런 무거운 과업들은 onStop에서 행하라고 하는 군요.



1-4. OnStop()


OnPause에서의 시간은 매우 짧습니다. 유저에게 다른 Activity가 보이고, 더이상 해당 Activity가 화면에서 보이지 않게되면,

OnStop이 호출됩니다. 

이 단계에서는, Activity Object가 메모리에만 남아있고, window manager에서는 attach되어있지 않아서, 

데이터가 있다면 다시 복원할 수 있는 상태입니다.


OnStop에서 유저가 다시 해당 액티비티로 돌아오게 되면,

OnRestart()를 거쳐서, OnStart()가 호출되게 됩니다.

하지만, 유저가 Activity를 finish()하거나(안드로이드 하단의 세모 버튼), 시스템이 메모리 부족으로 해당 액티비티를 finish()시킨다면,

onDestroy()를 호출하고 사라집니다.


참고로, 시스템은 OnStop()상태에 들어서면, onSaveInstanceState()이라는 메소드를 호출하는데요.

이 단계에서 key-value형태로 값을 저장할 수 있고, 이 값은 OnCreate()에서 복원할 수 있습니다.

이 부분에 관해서는 다른 글로 정리해보겠습니다~.



1-5. OnDestroy() 


OnStop()단계에서, 시스템이 메모리가 부족하여 시스템이 액티비티를 finish 하거나,

유저가 액티비티를 finish하는 경우, 혹은

프로그램적으로 activity를 finish()시키는 경우 OnDestroy가 호출됩니다.


한가지 알아둬야 할 점은, 안드로이드에서 세로화면에서 가로화면으로 전환시키는 것은,

내부적으로는 현재의 Activity를 OnDestroy()를 호출하고, 다시  OnCreate()를 호출한다는 것입니다.



2. Fragment 생명주기


아래 표에 Fragment의 생명주기를 나열해놓았는데요.

뭔가 많아 보이지요?

Fragment자체가 Activity안에서 존재하게 되므로, Activity의 존재와도 연관된 생명주기들을 추가적으로 가지고 있기 때문인데요.

하나하나 정리해 보겠습니다.


2-1. OnAttach ()


Fragment가 Activity에 attach될 때 호출됩니다.

코드적으로 재미있는 것은, 호출되면서 인자로 context가 주어진다는 점입니다.

이 context를 가지고, 부모 액티비티에 lisnter interface를 implement했다면,

형변환을 해서, 아래와 같이 가져올 수 있습니다.




2-2. OnCreate()


액티비티와 마찬가지로 초기화해야하는 리소스들을 여기서 초기화 해줍니다.

프래그먼트를 생성하면서 넘겨준 값들이 있다면, 여기서 변수에 넣어주면 됩니다.


하지만, 주의해야 할 점은, 여기서 UI 초기화는 할 수 없다는 점입니다.

UI초기화는 다음단계에서 해줍니다.




2-3. OnCreateView()


Layout을 inflate하는 곳인입니다.  view객체를 얻을 수 있으므로,

여기서 View와 관련되서, 버튼이나 EditText, TextView등을 초기화 할 수 있습니다.



2-4. OnActivityCreated()


Fragement에서의 OnCreateView를 마치고,

Activity에서 onCreate()가 호출되고 나서 호출되는 메소드입니다.

Activity와 Fragment의 뷰가 모두 생성된 상태로,

View를 변경하는 작업이 가능한 단계입니다.



2-5. OnStart()


Activity에서의 역할과 비슷합니다.

유저에게 Fragment가 보이도록 해줍니다.

Activity는 이단계에서는 started상태입니다.


2-6. OnResume()


이부분도 Activity와 비슷한데요.

유저에게 Fragment가 보여지고, 유저와 상호작용이 가능하게 되는 부분입니다.



2-6. OnPause(), OnStop()


OnPause에서 OnStop으로 가는 단계도, 액티비티의 생명주기와 유사한데요.

프래그먼트의 부모 액티비티가 아닌, 다른 액티비티가 foreground로 나오게 되면,

OnPause()를 콜하구요 Backstack으로 들어가게 됩니다.


다른 액티비티가 화면을 완전히 가리게 되면, 

onStop()을 호출하게 됩니다.

onStop에서 프래그먼트는 액티비티의 다른 데이터들과 마찬가지로,

유저가 다시 해당 액티비티를 호출하면 다시 복원될수 있는 상태입니다.



2-6. OnDestroyView()


메소드의 이름에서도 알수있겟지만, 

프래그먼트와 관련된 View가 제거되는 단계입니다.


2-6. OnDestroy()


프래그먼트는 생성될 때, onCreate-> onCreateView로,

초기화할 리소스들과, 초기화할 뷰를 순서대로 호출했는데요.

Destroy할 때는, 반대로 OnDestroyView에서 View를 제거하고,

OnDestroy()를 호출합니다.



2-6. OnDetach()


프래그먼트가 액티비티로부터 해제되어질때 호출됩니다.



반응형
,
반응형

프래그먼트(Fragment)라는 녀석도 있지만, 어플리케이션이 기본적으로 동작하기 위해선 무조건 하나이상의 액티비티가 있어야한다.


보통 메인 액티비티가 그 역할을 한다. 


또 프래그먼트라는 녀석은 결국 액티비티위에서 자신의 생명이 좌지우지 되기때문에 


프래그먼트의 생명주기가 어플리케이션의 생명주기라고 볼 수 없고, 액티비티의 생명주기가 더 맞다고 볼 수 있다. 


따라서 우리는 하나의 액티비티가 어떻게 살다가 죽는지 알아보자 


또. 다른 액티비티와의 관계에 있어서 서로 어떤 프로세스를 거치는지 알아보자.



액티비티 생명주기



1. onCreate()

Activity가 처음 만들어질 때 호출되는 함수이면서, 어플리케이션이 처음 시작할 때 최초로 한번 실행되는 함수이다. 주로 view를 만들거나 view resource bind , data to list 등을 onCreate()에서 담당하며, 이전 상태의 정보를 담고있는 Bundle을 제공한다.


2. onStart()

Activity가 다시 시작되기 전에 호출, Actvitiy가 멈춘 후 호출되는 함수, Activity가 사용자에게 보여지기 직전에 호출되는 함수


3. onResume()

Activity가 비로소 화면에 보여지는 단계, 사용자에게 Focus를 잡은 상태


3-1 onRestart()

Activity가 멈춰있다가 다시 호출될 때 불리는 함수, 즉 Stopped상태였을 때 다시 호출되어 시작될 때 불린다.


---------- 다른 Activity가 호출 되는 경우 ---------- 


4. onPause()

 Activity위에 다른 Activity가 올라와서 focus를 잃었을 때 호출되는 함수. 

 완전 Activity가 가려지지 않은 상태에서 호출되는 함수.

 즉 일부분이 보이거나 투명상태일 경우 호출된다.

 다른 Activity가 호출되기 전에 실행되기 때문에 onPause()함수에서 시간이 많이 소요되는 작업이나, 

 많은 일을 처리하면, 다른 Activity가 호출되는 시간이 지연되기 때문에 많은 일을 처리하지 않도록 주의하자.

 영구적인 data는 여기서 저장한다.


5. onStop()

Activity위에 다른 Activity가 완전히 올라와 화면에서 100% 가려질 때 호출되는 함수. 홈키를 누르는 경우. 

또는 다른 액티비티페이지 이동이 있는 경우. 만약 이상태에서 Activity가 다시 불려지면, onRestart()함수가 호출된다.


6. onDestroy()

Activity가 완전히 스택에서 없어질 때 호출되는 함수, 즉 제거되는 경우. 

finish() 메소드가 호출되거나, 시스템 메모리 확보를 위해 호출된다.


*onStop(), onDestroy() 함수는 호출되지 않을 수 있음.

예) 메모리 부족으로 인해 onStop()을 안 탈 수 있다.



반응형
,
반응형

안드로이드의 4대 컴포넌트는 액티비티, 서비스, 콘텐트제공자, 방송수신자 이렇게 4가지를 말합니다. 어플리케이션을 만들때 주요 구성요소이자 안드로이드의 사실상의 모든 구성요소입니다. 각각의 구성요소는 인텐트를 통해서 상호 통신을 합니다. 즉 액티비티에서 다른 구성요소를 호출할때는 인텐트를 거쳐야 한다는 것입니다.


구성요소



Activity(액티비티)

액티비티는 UI 화면을 담당하는 컴포넌트입니다. 액티비티 역할을 하기 위해서는 자바소스에서 Activity클래스를 상속해야 하며 액티비티가 기본적으로 가지고 있는 생명주기 메소드를 재정의하여 원하는 기능을 구현하는 방식으로 제작합니다. 가장 많이 쓰이는 컴포넌트 이기때문에 굉장히 중요하다고 볼 수 있습니다.


Activity(액티비티)의 특징

1. 안드로이드 어플리케이션은 반드시 하나이상의 Activity를 가지고 있어야 합니다.

2. 두개의 액티비티를 동시에 Display할 수 없습니다.

3. 다른 어플리케이션의 액티비티도 불러낼 수 있습니다.

4. 액티비티 내에는 프래그먼트(Fragment)를 추가하여 화면을 분할시킬 수 있습니다.


public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

굉장히 익숙하신 코드죠? 이게 바로 액티비티입니다.



2. Service(서비스)

서비스는 백그라운드에서 실행되는 프로세스를 의미합니다. 서비스는 화면이 존재하지 않습니다. 하지만 서비스도 애플리케이션의 구성요소이므로 새로 만든 후에는 항상 매니페스트에 등록을 해주어야 합니다. 메인 액티비티에서 서비스를 시작하고 싶은 경우에는 startService()라는 메서드를 이용해 서비스를 실행시킬 수 있습니다.


서비스

Service(서비스)의 특징

1. 화면이 없습니다. 그저 백그라운드에서 돌아가는 컴포넌트입니다.

2. 한번 시작된 서비스는 어플리케이션이 종료되도 계속해서 백그라운드에서 돌아갑니다.

3. 모든 서비스는 Service클래스를 상속받아서 사용합니다.

4. 네트워크를 통해서 데이터를 가져올 수 있습니다.



3. Content Provider(콘텐트 제공자)

콘텐트 제공자는 데이터를 관리하고 다른 어플리케이션 데이터를 제공해주는 컴포넌트입니다. 데이터베이스의 데이터를 전달할때 많이 사용합니다. 콘텐트 제공자는 생명주기를 가지고 있지 않습니다.


Content Provider(콘텐트 제공자)의 특징

1. 파일입출력, SQLiteDB, Web등을 통해서 데이터를 관리합니다.

2. 콘텐트 제공자를 통하여 다른 어플리케이션의 데이터도 변경할 수 있습니다.



4. Broadcast Recevier(방송 수신자)

방송 수신자란 안드로이드에서 다양한 이벤트와 정보를 받아 반응하는 컴포넌트입니다. 브로드캐스팅은 메시지를 여러 객체에게 전달하는 방법을 의미하는데 이렇게 전달되는 브로드캐스팅 메시지를 방송수신자라는 어플리케이션의 구성요소를 이용해 받을 수 있습니다.


방송수신자


Broadcast Recevier(방송수신자)의 특징

1. 디바이스에서 발생하는 일 중에서 어플리케이션이 알아야 하는 상황이 발생하면 알려줍니다.

2. 수신기를 통해 디바이스의 상황을 감지하고 적절한 작업을 수행합니다.

3. 대부분 UI가 존재하지 않습니다.


// 추가

1. Activity - 화면 UI를 구성하는 View를 담을 수 있는 그릇이라고 생각하시면 됩니다. 모든 화면이 있는 

                   어플리케이션이라면 Activity가 존재하며 Activity가 존재해야만 화면 구성을 할 수가 있습니다. 

                   Activity를 사용하기 위해서는 manifest.xml 파일에 등록을 해야하고 Activity 생명주기를 

                   잘 이해하셔야 합니다. 다음 포스팅에서 Activity 생명주기에 대해 설명 드리겠습니다.


2. Service - Service는 백그라운드에서 실행되는 컴포넌트로 눈에 보이지는 않지만 오랜동안 실행되는 작업이나 

                   원격 프로세스를 위한 작업을 할 때 사용합니다. 앞서 보이지 않다고 말씀드렸듯이 사용자 인터페이스를 

                   제공하지 않습니다. 또한 사용자가 다른 어플리케이션으로 전환하더라도 백그라운드에서 계속해서 실행합니다. 

                   예를 들면 스마트폰으로 음악을 들을 때 보통 앱을 켜고 음악을 선택해 플레이 시킨 다음 다른 앱을 켜도 

                   노래가 계속 흘러나오는 것이 바로 Service를 활용하여 음악 플레이 하는 부분을 구현 한 것입니다. 

                   Activity와 마찬가지로 사용하기 위해서는 manifest.xml 파일에 등록을 해야하고 

                   Activity와 연동 된 수명주기를 잘 이해하셔야 합니다.


3. Broadcast Receive - Broadcast Receive는 안드로이드 단말기에서 발생하는 다양한 이벤트나 정보를 받고 

                                      정보에 따라 반응하는 컴포넌트 입니다. 단말기에서 발생하는 일 중에서 어플리케이션이 알아야 하는 

                                      상황을 계속해서 알려주는 역할을 합니다. 예를 들면 배터리가 없거나 문자나 전화가 왔을 때 

                                      우리가 알고 싶지 않아도 알려주도록 구현되어 있는 것과 같이 Broadcast Receive를 이용해서 

                                      여러 상황을 감지하고 적절한 작업을 수행하는 역할을 합니다.


4. Content Provider - Content Provider는 어플리케이션 간 데이터를 공유하게 해주는 인터페이스입니다. 

                                    안드로이드 시스템의 각종 설정 값 이라던지 SD카드 내의 미디어 등에 접근하는 것이 가능하고, 

                                    다른 어플리케이션에 접근하는 것도 가능합니다. 물론 접근하려는 어플리케이션에서 

                                    정의한 수준의 접근만 가능하게 됩니다. 가장 쉬운 예시가 전화번호부 어플리케이션 입니다. 

                                    이 어플리케이션에 저장되어 있는 DB를 이용하여 많은 어플리케이션들이 사용하고 있습니다. 

                                    페이스북이나 카카오톡 등등 전화번호부를 확인하여 자동으로 친구추가가 되는 기능을 생각하시면 

                                    이해가 쉬울 것 같습니다. 전화번호부 어플리케이션에서 Content Provider를 제공하고 

                                    카카오톡이나 페이스북에서 Content Resolver를 가지고 제공하는 데이터를 읽어 오는 것입니다. 

                                    이와같이 Content Provider는 자신의 데이터를 제공해주는 어플리케이션에서 구현해주는 

                                    컴포넌트라고 생각하시면 됩니다.


반응형
,