스레드 안전 Intro

여러 Thread에서 객체에 접근할때, 객체가 항상 정확한 상태를 유지해야 스레드안전이 보장됩니다. 동기화는 하나의 Thread에 의해 변경되는 도중에 다른 스레드의 접근이 가능한 모든 변수를 읽거나 쓰는 코드에 적용되어야 합니다. 이러한 코드 영역을 임계여역이라고 하며, 임계영역은 원자적으로 실행되어야합니다. 결론적으로 말하자면, 한번에 하나의 스레드만 접근을 허용하도록 실행되어야 합니다.



암시적 잠금과 JAVA Monitor

 synchronized 키워드는 모든 자바 객체에서 사용하는 암시적 잠금으로 동작하게 됩니다.  임계영역에서 스레드의 실행이 한스레드에 독점적임을 의미하게됩니다. 

 하나의 스레드가 임계영역을 점유하는 동안 다른 스레드의 접근은 차단되고, 잠금이 해제될때까지 실행할수 없게됩니다. 


Java monitor에는 3가지 상태가 있습니다.


차단된 스레드, 실행중인 스레드, 대기스레드가 있습니다.


JAVA Monitor 3가지 모델링


1. 차단된 스레드

다른스레드에 의해 해제될 모니터를 기다리는 동안, 일시 중단된 스레드입니다.


2. 실행 중 스레드

모니터를 소유하고 현재 임계영역에서 코드를 실행중인 스레드입니다.


3. 대기 스레드

임계영역의 끝에 도달하기 전에 자발적으로 모니터의 소유권을 포기한 스레드입니다. 이 스레드는 사기 소유권을 얻을 때까지 스레드의 신호를 기다립니다.


스레드에 접근(1) -> 차단된스레드(2) -> 실행중인스레드(3) -> 대기스레드(4)   -> 다시 실행중인 스레드(5), (상황에 따라서)

위의 그림에있는 상태설명.


1. 모니터에 진입

스레드가 암시적 잠금에 의해 보호된 영역에 접근을 시도합니다. 이 스레드는 모니터에 들어가게됩니다. 만약 다른 스레드가, 이미 잠금을 차지하고 있으면 스레드의 잠금 획득이 연기되게 됩니다.


2. 잠금 획득

모니터를 소유하고 있는 다른 스레드가 없는경우, 차단된 스레드는 소유권을 획득하고 임계영역에서 실행되게 됩니다. 


3. 잠금 해제 및 대기

스레드는, 계속 실행하기 전에 충족해야할 조건을 기다려야 하는 경우도있습니다. (생산자 소비자문제처럼, 다른 스레들간의 순서를 조율하거나, 조건등에따라 스레드 실행순서를 제어하고자할때).

그럴때는 Object.wait()를 통해서 스레드 자신의 실행을 일시 중단합니다.


Object.wait()을 하게되면 Object (주로, synchronized에 쓰이는 열쇠? 동기화 구분의 변수명가 되겠습니다.) 를 synchronized하며, 실행중인 스레드영역에있던 스레드는, 열쇠를 내어놓고, 대기 스레드에 들어가서 notify가 다른 스레드에서 호출되기전까지 대기하게 됩니다. 

다른 실행중인 스레드에서 notify가 호출되면 대기스레드에있던 스레드는 다시 열쇠를획득하고 실행중인 스레드(4.과정)으로 들어가게됩니다.



4. 신호 후 잠금 획득

대기 스레드가 Object.notify(), Object.notifyAll()을 통해, 다른스레드로부터(실행중이었던 스레드) 신호를 받고, 스케줄러에 의해 선택되면, 다시 모니터의 소유권을 가지게됩니다. 하지만, 대기스레드가, 모니터를 소유할 가능성은, 잠재적으로 차단된 스레드보다는 앞설수 없다고합니다.



5. 잠금 해제 및 모니터 종료

임계영역의 끝에서 스레드는 모니터를 종료하고, 다른 스레드가 모니터를 소유할 수 있도록 자리를 떠나게 됩니다.



위의 다섯가지 순서는 아래의 코드와도 같습니다.

synchronized(this) { //(1) 

   // 코드 실행 (2)

   wait(); (3)

   // 코드 실행 (4)

}(5)




다음 포스팅에서는, 암시적 잠금사용의 예제와 소비자와 생산자 코드를 살펴보도록 하겠습니다.


다음 포스팅으로 가기 <= 클릭





들어가기 앞서

안드로이드 앱은, 자바의 멀티스레드 프로그래밍 모델을 준수해야합니다. 자바가 편하긴하지만 동시프로그램 처리, 데이터 일관성 유지, Task 시퀀스 설정등 복잡성이 증가하는 단점이 있습니다. 이는 개발자가 고려하여 최대한 복잡성은 줄이고 성능을 높일 수 있도록 구현해야할 것입니다.



스레드 기본사항

보통 안드로이드에서는, 하나의 스레드에서 실행하는 작업의 단위를 TASK라고 지칭합니다. 

스레드는 순차적으로 하나 또는 다수의 태스크를 실행할 수 있습니다.



실행

자바에서는 run method 안에서 태스크를 정의함으로 구현할 수 있다.


private class MyThreadTask implements Runnable{

   public void run(){

int j = 0; // 스레드에서의 지역스택에 저장됨.

   }

}


run() 메서드 안에서 호출되는 지역변수는 스레드의 지역 메모리 스택에 저장됩니다. 태스크의 실행은 Thread 객체생성과 start() 메서드 호출로 

실핼할 수 있습니다.


Thread myThread = new Thread(new MyThreadTask ());

myThread.start();


스레드의 지역데이터를 저장하는 전용메모리가 있습니다. 


CPU는 한번에 하나의 스레드 명령어를 처리할 수 있습니다.


스레드의 변경을 문맥교환, Context Switch라고 합니다.

하나의 프로세서에 동시에 실행되는 두개의 스레드를 코드와 그림으로 보시겠습니다.

Thread T1 = new Thread(new MythreadTask());

T1.start();

Thread T2 = new Thread(new MythreadTask());

T2.start();



스레드 T1 ------                   ------------

                        ||||(문맥교환시간)

스레드 T2                  ------                  -----


      시간


T1과 T2사이에는 CPU가 사용하는 문맥교환시간이 존재해서 T1이 끝나고 바로 T2가 실행되는게 아니라 약간의 문맥교환 오버헤드가 드는것을 확인하실 수 있습니다.(위에 |||| ) 




싱글 스레드 애플리케이션

각 앱은, 하나이상의 스레드를 가집니다. 또한, 사용자의 반응성을 고려해서 멀티스레드 환경으로 구현해야하는 경우도 존재합니다.



멀티스레드 애플리케이션

동작이 동시에 실행되는것으로 인식되도록 앱 코드를 여러코드 경로로 분할하는 애플리케이션입니다. 실행 스레드 수가 프로세서의 수를 초과하면 완벽한 동시성이 될수는 없습니다. 

멀티스레드는 필수적이긴하나 그에따른 복잡성 증가는 개발자가 풀어야 할 과제입니다.



자원소비증가

각 스레드는 지역변수등을 저장하는 전용 메모리영역을 할당받습니다. 프로세서 측면에서는 스레드 설정 및 해제, 문맥 교환에서 스레드를 저장하고 복원시, 오버해드가 생기게됩니다. 스레드가 많아질수록, 더많은 문맥교환이 일어나고 성능이 저하되게 됩니다.



복잡성 증가

코드를 분석하기 어려워집니다. 또한 실행에 불확실성을 가져다 줍니다.



데이터 불일치

두개의 스레드가 공유자원을 사용할 경우, 어떤 순서로 데이터를 접근하는지 알수없게됩니다. 하나의 공유변수를 두개의 스레드가 접근할때 경쟁조건에 노출되게 됩니다. 

그래서 공유변수에접근할때 다른스레드가 접근할수없도록, 원자영역을 만드는것이 필수적입니다. 자바에서 가장 동기화 메커니즘은 synchronized 키워드를 사용하면 되겠습니다.


ex_)

synchronized(this){

  sharedVal++;

}


성능을 위해 구조화된 Application

 안드로이드 디바이스는 동시 여러 작업을 처리할 수 있는 멀티프로세서 시스템입니다. 하지만 응용애플리케이션의 성능을 최적화하기 위해 작업을 동시에 실행하도록 보장하는것은 각각의 애플리케이션에 달려있습니다.


만약 앱이 동시적으로 분할된 동작을 하지않고, 길게 하나의 작업을 실행하는 것을 선호한다면, 하나의 CPU만을 이용할 수 있고 이는 차선의 성능으로 이어지게 됩니다. 분할되지 않은 작업은 동기적으로 실행해야하지만, 분할된 작업은 비동기적으로 실행할 수 있습니다.


*동기적(동시에), 비동기적(순서대로)


 여러개의 독립적인 Task가 가능한 앱은 비동기 실행을 사용하도록 구성해야합니다.


 한가지 방법은, 응용프로그램 실행을 여러 프로세스에 분리시키는 방법입니다. 

여러개의 독립된 작업이 동시에 실행될 수 있기 때문입니다. 그러나 모든 프로세스는 자신의 기본적 자원을 위해 메모리를 할당하므로 여러프로세스에서 응용프로그램을 실행하는 것은 하나의 프로세스에서 응용프로그램을 실행하는 것보다 많은 메모리를 사용하게 됩니다.


 또한, 프로세스 간의 시작과 통신이 느리며, 비동기 실행을 하는 효과적인 방법이 아닙니다. 물론 여러 프로세스가 유효한 디잔일 수 있지만, 이 결정은 성능과는 독립적이어야 합니다. 

 

 높은 처리량과 더 나은 성능을 만들기 위해 어플리케이션은 각각의 프로세스내에서 여러 스레드를 사용해야 합니다.



스레드를 통한, 반응성 있는 앱 구현

 앱은 여러개의 CPU에서 높은 처리량을 가진 비동기 실행이 가능하지만, 반응성 있는 앱을 보증하지는 않습니다. 


*반응성: 상호작용하는 동안, 사용자가 앱을 인식하는 방식을 말합니다.


 UI가 버튼 클렉이 신속하게 반응 혹은, 부드러운 애니메이션등, 신속하게 반응하는것 등에 주안점을 둡니다. 기본적으로 사용자 경험의 관점에서 성능은 앱이 얼마나 빠르게 UI구성요소를 업데이트하는지로 결정됩니다.


 UI 컴포넌트를 update 하는 역할은 시스템이 UI구성요소 업데이트를 유일하게 허용하는 UI Thread가 존재합니다.

 

 반응성있는 앱을 위해서라면, 오래걸리는 태스크를 UI스레드에서 실행하면 안됩니다. 만약 UI스레드에서 그런 테스크를 실행하면, 해당 스레드의 다른 모든 실행이 지연되게 됩니다. 일반적으로 UI스레드에서 오래 걸리는 테스크를 실행하면, 생기는 첫번째 증상은 UI가 응답하지않고, ANR을 내게 되는것입니다.


 런타임은 네트워크 내려받기와 같이 시간이 오래 걸리는 특정작업을 UI스에드에서 실행하는 것을 금지합니다.


그래서 아래와 같이 긴 작업은 백그라운드 스레드에서 처리되어야 합니다. 오래 걸리는 태스크는 보통 다음아래와 같습니다.

- DB 생성/삭제/수정

- 이미지 처리

- File 읽기/쓰기

- Text Parsing

- 네트워크 통신

- SharedPreferences 읽기/쓰기



안드로이드 앱에서, 스레드는 구성요소중 어느 것만큼이나 핵심적입니다. 안드로이드 컴포넌트(구성요소)와 시스템 콜백은 다른 언급이 없는한, UI스레드에서 실행되며, 더 오래 걸리는 태스크는 Background의 Thread를 사용해야 합니다.



오래걸리는 Task?

사실 명확한 오래걸리는 테스크에대한 기준은 없습니다. 사용자가 UI를 느리다고 인식하다면, 테스크를 UI스레드에서 실행하기에는 너무 길다는 신호입니다. 인간이 부자연스럽다고 생각하는 부분은 백그라운드 테스크에서 처리하는게 바람직합니다.




마무리

 안드로이드 앱은, 리눅스 프로세스에 포함된 달빅 런타임 내의 리눅스 OS위에서 실행됩니다. 

안드로이드는 가장 낮은 우선순위의 앱을 종료시키시 위해, 실행중인 각 응용프로그램의 중요성을 따져 프로세스 순위 시스템을 적용합니다.


성능향상을 위해 앱은 코드를 동시실행 하기 위해 여러 Thread 작업을 나눠야 합니다. 

모든 리눅스 프로세스는 UI Update를 책임지는 특정한 스레드인 UI스레드를 포함합니다.


오래 걸리는 작업은 모두 UI Thread에서 분리하여 다른 스레드에서 실행해야 합니다.





안드로이드 어플리케이션(응용프로그램) 실행

 안드로이드는 동시에 여러 안드로이드 응용프로그램을 실행할 수 있고, 큰 지연없이 응용프로그램(애플리케이션) 사이에서 사용자 스위치를 할 수 있는 Multi user, multi tasking 시스템입니다. 리눅스 커널은 멀티태스킹을 처리하고, 응용프로그램 실행은 리눅스 프로세스를 기반으로 진행되어집니다.



리눅스 프로세스

리눅스는 모든 사용자에게 OS로 추적할수있는 번호인 고유한 사용자 ID를 할당하게 됩니다. 

일반적인 리눅스처럼 사용자 권한으로 보호되는 개인 리소스에는 접근할 수있으나 다른 사용자의 개인리소스에는 절대 접근할수없습니다. 

따라서 사용자를 격리하기위해서 SandBox 라는게 만들어집니다. 


 안드로이드에서 모든 응용프로그램의 패키지는 고유한 사용자 ID값을 가지게 됩니다. 구체적으로 예를든다면, 안르도이드 앱은 리눅스에서 고유한 사용자에게 해당됩니다. 그렇기때문에 다른 앱의 리소스에 엑세스할수없게됩니다.


응용프로그램은 서로 다른 process와 VM에서 실핼되게 됩니다. 

기본적으로 응용프로그램과 process는 1:1관계이지만, 필요시, 하나의 응용프로그램을 여러개의 프로세스에서 실행하거나, 여러 앱을 같은 프로세스에서 실행할 수도 있습니다.



Life Cycle 생명주기 

앱의 생명주기는 자바 android.app.Application 클래스로 대응되는 리눅스 프로세스 안에서 캡슐화가 됩니다. runtime이 onCreate() 함수를 호출할 때 각 응용프로그램에 대한 Application 객체가 시작됩니다. 원칙적으로 응용프로그램은 onTerminate()로 런타임 호출을 종료하지만, 응용 프로그램에 의존하지 않을수도있습니다. 런타임이 마지막 onTerminate()를 호출할 기회가 있기전에 하부 리눅스 프로세스가 종료되기도 합니다. Application 객체는 프로세스에 첫번째로 생성되는 구성요소이자 마지막으로 소멸되는 구성요소입니다.



응용프로그램 시작

 안드로이드 구성요소중 하나가 실행을 위해 초기화되면, 응용프로그램은 시작됩니다. 어떤 구성요소든 어플리케이션(응용프로그램)에 대한 진입점이 될 수 있습니다. 일단 첫번째 구성요소(컴포넌트라고도 합니다.)가 시작되엇고, 프로세스가 실행되기 전이라면 리눅스 프로세스는 다음과 같은 시동순서를 거치게됩니다.


1. 리눅스 프로세스를 시작합니다.

2. 런타임을 생성합니다.

3. Application 클래스의 인스턴스를 생성합니다.

4. 응용프로그램을 위한 진입점 구성요소를 생성하게 됩니다.


새로운 리눅스 프로세스와 runtime을 설정하는 작업은 결코 짧은 시간안에 이루어지지 않습니다. 이작업은 성능을 저하시키고 사용자 경험에 두드러진 악영향을 미칠 수 있습니다. 따라서 시스템은 시스템 부트에 Zygote라는 특별한 프로세스를 시작하여, 안드로이드 응용프로그램의 시작 시간을 단축하려고 합니다. 


 Zygote는 미리 로드된 핵심 라이브러리의 전체 세트를 가지고 있습니다. 새로운 응용프로그램 프로세스는 모든 응용프로그램에서 공유되는 핵심 라이브러리를 복사하지않고, Zygote프로세스에서 fork합니다.



응용프로그램 종료

프로세스는 응용프로그램이 시작될때 생성되며, 시스템 자원이 해제될때 종료됩니다. 사용자가 나중에 응용프로그램(application)을 재 실행할 수 있는 경우가 있기때문에 Runtime은 시스템 전체 자원이 부족해지기 전까지는 현재 살아있는 응용프로개름의 모든 자원을 소멸시키지는 않습니다. 따라서 모든 구성요소가 소멸해도 응용프로그램이 자동적으로 종료되지않습니다.


 시스템 자원이 부족할 때 어떤 프로세스를 죽여야할지는 Runtime에 달려있습니다. 이러한 결정을 하기 위해 시스템은 애플리케이션의 가시성 및 현재 실행중인 구성요소(컴포넌트)에 따라 각각의 프로세스에 순위를 부과합니다. 낮은 순위의 프로세스는 높은 순위의 프로세스보다 먼저 강제로 종료됩니다. 프로세스의 순위를 높은 순서대로 보면 다음 아래와 같습니다.


포그라운드(가장 앞단, UI컴포넌트 쪽)

 전면에 보이는 컴포넌트를 가진 애플리케이션, 원격 프로세스에서 전면의 액티비티에 바인딩된 서비스, 실행중인 브로드캐스트 리시버가 해당됩니다.


화면에 보이는 프로세스

 화면에 보이나 부분적으로 숨겨진 컴포넌트(구성요소)를 가진 애플리케이션입니다.


Service 서비스

 백그라운드에서 실행중이며, 화면에 보이는 컴포넌트와 연결되어있지않은 서비스가 해당합니다.


Background 백그라운드

 화면에 보이지않는 액티비티, 대부분의 애플리케이션이 가지는 프로세스 레벨입니다.


빈프로세스

 활성화된 구성요소가 없는 프로세스, 빈 프로세스는 응용프로그램의 시작시간을 개선하기 위해 유지되는편이지만, 시스템이 자원을 회수할때 가장 먼저 종료됩니다.




안드로이드 응용프로그램은 Application 객체와 안드로이드4대구성요소인 액티비티, 서비스, 브로드캐스트리시버, 콘텐트 프로바이더입니다.


Application 객체

 자바프로그램에서 실행중인 응용프로그램의 표현은 android.app.Application 객체입니다. 이 객체는 응용프로그램이 시작할 때 instance 화됩니다. 또한 중지할때는 소멸됩니다. 참고로 Application 클래스의 인스턴스는 응용프로그램의 리눅스 프로세스 수명동안 지속됩니다. 프로세스가 종료되고 재시작할때 새로운 Application 의 인스턴스가 생성됩니다.


구성요소

 정말 중요합니다. 안드로이드응용프로그램의 기본조각은 RunTime에 의해 관리되는 구성요소(액티비티, 서비스, 브로드캐스트리시버, 콘텐트 프로바이더)가 있습니다. 이들 구성요소는 서로 다른 책임과 LifeCycle(생명주기)를 갖습니다. 

 모두 응용프로그램을 시작할 수 있는 진입점이됩니다. 구성요소가 시작되면 응용프로그램의 LifeCycle를 통해 다른 구성요소들을 시작시킬 수도 있습니다. 구성요소는 응용프로그램 내에서 또는 응용프로그램 간에 intent를 사용하여 다른 구성요소를 시작시킬 수 있습니다. 

 인텐트는 동작하는 수신자의 동작을 지정하기도하며(사진찍기와 이메일전송, 문자전송도 가능합니다.), 송신자에게 수신자로 데이터를 제공할 수 있기도합니다. 인텐트는 명시적 인텐트와 임시적 인텐트가있습니다.


명시적 인텐트

컴파일 시 응용프로그램에서 알 수 있는 4대구성요소의 이름을 분명하게 나타내는 인텐트입니다.


암시적인텐트

 IntentFilter 블록안에 여러개의 특성으로 정의한 구성요소에 런타임(실시간) Binding 합니다. 만약 해당 Intent가 어느 구성요소의 IntentFilter 특성과 일치하게된다면 그 구성요소가 시작되게 됩니다.


안드로이드 응용프로그램은 SubClass를 이용하여 구성요소를 구성하고, 응용프로그램의 모든 구성요소는 AndroidManifest.xml 파일에 등록되어야 합니다.



Activity 액티비티

 언제가 디바이스의 스크린을 차지하여 사용자에게 보인을 화면을 말합니다. 정보를 표시하거나 사용자의 입력받는부분을 담당합니다. UI구성요소를 포함하며, 모든 뷰계층구조에 대한 객체 참조를 담고있습니다. 사용자가 화면 사이를 옮겨 다닐 때 액티비티 인스턴스는 스택을 형성하게 됩니다.



Service 서비스

서비스는 사용자의 직접 상호작용은 없고, BackGround에서 눈에 띄지않게 뒷단에서 실행할 수 있습니다. 일반적으로 동작이 자신의 수명보다 오래 살 수 있을때 다른 구성요소에 실행을 떠넘기기 위해 사용되는 편입니다. 서비스는 started 또는 bound 모드로 실행될 수 있습니다. 2가지 실행방법이 있습니다.


시작서비스(started service)

 명시적 또는 암묵적 인텐트로, Context.startService(intent)가 호출될 때 시작됩니다. 그리고 Context.stopService(Intent)가 호출될때 종료됩니다.


바운드 서비스(bound service)

 명시적 혹은, 암묵적 인텐트 매개변수와 함께 Context.bindService(Intent, ServiceConnection, int)를 호출하여 여러 구성요소를 서비스에 바인딩할 수 있게됩니다. 바인딩 후, 구성요소는 ServiceConntection 인터페이스를 통하여, 그 Service와 Interaction 할 수 있고, Context.unbindService(ServiceConnection)을 통해 그 서비스로부터 바인딩을 해제할 수 있게됩니다. 마지막 구성요소가 서비스로부터 바인딩 해제되면 서비스는 소멸되게 됩니다.



Content provider 콘텐트 프로바이더

 안드로이드 응용프로그램 내부 혹은 응용프로그램 사이에서 상당한 양의 Data를 공유하려는 응용프로그램은 Content 프로바이더를 활용할 수 있습니다.


 콘텐트 프로바이더는 어떤 데이터 소스에도 접근을 제공하지만, 응용프로그램 안에서 사용하는 SQLite DB를 다른 응용프로그램과 공유할때 주로 사용됩니다. 이런 콘텐트 프로바이더의 도움으로 안드로이드 응용프로그램은 원격 프로세스에서 실행되는 응용프로그램에 Data를 공개할 수 있게 됩니다.



BroadCast receiver 브로드 캐스트 리시버

 BroadCast receiver는 응용프로그램 내, 원격 응용프로그램, 또는 Platform으로부터 보내진 Intent를 듣는 매우 제한된 기능을 가지고 있습니다. 

어떤 Intent가 BraodCastReceiver로 보내진 것인지 가리기 위해 수신되는 intent Filteriing을 합니다. 인텐트 수신을 시작하려면, 브로드캐스트 리시버를 동적으로 동록해야합니다. 

수신을 종료하려면 등록을 해제해야 합니다. 만약 AndroidManifest.xml에 정적으로 등록되어있다면, 응용프로그램이 설치되어있는 동안 계속해서 Intent를 수신할 수 있게 됩니다. 따라서 어떤 Intent가 필터에 일치할 때 브로드캐스트 리시버는 연결된 응용프로그램을 시작할 수 있게 됩니다.


포스팅에 앞서

안드로이드 스레드를 알기에 앞서 플랫폼, Applicatioin 아키텍처, 응용프로그램의 실행먼저 알아보겠습니다.

 

안드로이드 SoftWare Stack

 안드로이드의 응용프로그램은 리눅스 커널, native C++ 라이브러리, 응용프로그램 코드를 실행하는 run time을 기반으로 소프트웨어 스택 위에서 동작하게 됩니다.

 

4층 응용프로그램 계층이 있습니다.

3층 애플리케이션프레임워크, 코어자바가 있습니다.

2층 네이티브 라이브러리, 런타임이 있습니다.

1층 리눅스 커널이있습니다.

 

응용프로그램 == 어플리케이션 = 앱 = Application

자바언어로 구현된 안드로이드 응용프로그램을 지칭합니다. 안드로이드 응용프로그램은 자바와 Android Framework 라이브러리를 사용합니다. 우리가 흔히아는 안드로이드 앱을 일컷는 용어라 할 수 있습니다.

 

Core JAVA

 안드로이드 응용프로그램과 안드로이드 프레임워크에서 사용된 Core java 입니다. 코어자바는 완벽한 JAVA SE나 ME구현을 따르지 않고, JAVA 5기반의 중단되버린 Apache의 부분집합 혹은 집합입니다. 코어자바는 기초 자바 스레딩 메커니즘인 java.lang.Thread클래스와 java.util.concurrnet Package를 제공중입니다.

 

Application Framework

Window system, UI ToolKit, 리소스(자원) 등 기본적으로 자바에서 안드로이드 애플리케이션(응용프로그램)을 작성하기 위해 요구되는 모든것을 다루는 Android Class의 집합이라 생각하시면 되겠습니다. 프레임워크는 안드로이드 구성요소의 생명주기, LifeCycle간의 상호통신을 정의하고 관장, 관리하며 애플리케이션이 스레드 관리를 단순화하기 위해 활용되는 안드로이드 비동기 메커니즘 스레드를 제공합니다. HandlerThread, AsyncTask, IntentService, AsyncQueryHandler, Loaders가 있습니다.

 

 

Native Library

그래픽, 미디어, 데이터베이스, 폰트, OpenGL등 빠른 속도가 요구되므로, C/C++로 구현된 라이브러리입니다. 안드로이드 프레임워크 Native Code를 위한 자바래퍼를 제공하기 때문에 자바응용프로그램은 기본적으로 네티이브 라이브리와 직접 상호작용을 하지 않습니다. 보통 프레임워크를 거쳐서 사용하게 되는 구조입니다.

 

Run time

 내부바이트 코드표현으로, 가상 머신환경에서 컴파일된 안드로이드 애플리케이션(응용프로그램) 코드를 실행하는 Sand Box 화된 런타임 환경입니다. 모든 응용프로그램은 자신만의 달빅또는 아트 런타임에서 동작합니다. 아트는 킷캣, API 19단계 이상 부터 사용자가 활성화할수있는 선택적 런타임이며, 지금은 달빅이 기본 런타임입니다.

 

Linux Kernal

 많은분들이 아는 리눅스의 커널부분을 지칭합니다. 리눅스 디바이스드라이버가 관장하는 하드웨어기능을 사용하기 위한 하부 운영체제입니다. 덕분에 응용프로그램에서 소리, 네트워크, 카메라 등 기기의 HW를 사용할 수 있게됩니다. 모든 응용프로그램은 자신고유의 프로세스를 시작하고, 모든 프로세스는 실행되는 응용프로그램과 함께 RUN Time을 가집니다. 프로세스 내의 여러스레드가 응용프로그램 코드를 실행할수 있게 됩니다. 커널은 스케줄링을 통해 프로세스와 스레드가 사용하는 CPU실행시간을 나눕니다. 리눅스를 배워보셨다면 무슨말인지 이해하기가 더 수월하실 겁니다.

 

 

<안드로이드 액션바 + 탭 구현>


 안드로이드에서 제공되는 Action Bar에서 Navigation Tab Mode로 변경하여 제작하는것이 금일 포스팅의 목표입니다. 안드로이드가 요즘 목표가 액션바에 네비게이션 탭을 둬서 유저에게 제공하는 UI를 제공하는거죠. 또한, 프래그먼트 사용을 적극 권장하고 있는 추세인것 같습니다. 다중의 액티비티를 만들어내는 것보다 프래그먼트를 도입하여 하나의 액티비티에서 여러기능들을 수행할 수 있도록 유도하고 있는것 같습니다.


 실제로 이러한 움직임은 불과 몇 년전의 web 에서와 비슷하다고 할 수 있는데요. web page 시장도 예전에는 frame이라는 태그가 있었고, 여러 html 파일들로 나뉘어 져있었어요. 그런데 w3c에서 frame 태그를 없애더니 하나의 페이지에서 여러 정보를 전개할 수 있는 ajax등을 권장하고 있는 추세이지요. 


아무쪼록 액션바에 네비게이션 탭모드로 설정하고 프래그먼트와 연동하는 안드로이드 실습 포스팅 진행하도록 하겠습니다.

금일 실습해볼 과제의 최종결과물 먼저 보도록 하겠습니다.

 

 

 

두번째 그림은, 첫번쨰 프래그먼트 상태인데 중복으로 눌렀을 경우, 다시한번 선택되었다는 토스트가 뜨도록 구현한 사항입니다. 구현사항이 궁금하시면, MainActivity.java 의 onTabReselected 오버라이드 메서드를 참고하시면 되겠습니다. 



먼저 탭이동에 따른 프래그먼트 먼저 생성하겠습니다.

Fragment1.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">


<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="First Fragment"
android:textColor="#ff000000"
android:gravity="center" />
</LinearLayout>


첫번째 프래그먼트가 생성되었을때, inflate를 전개하는 코드입니다. inflater를 통해서, 전개하는 과정의 코드가 있습니다.

FragmentOne.java

package com.android.jinss.dando;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
* Created by PARK on 2017-07-08.
*/
public class FragmentTwo extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1, container, false);
}
}


2번째 프래그먼트 xml파일입니다.

Fragment2.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
>

<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Second Fragment"
android:textColor="#ff000000"
android:gravity="center" />


</LinearLayout>


두번째 프래그먼트가 생성되었을때, inflate를 전개하는 코드입니다.

FragmentTwo.java

package com.android.jinss.dando;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
* Created by PARK on 2017-07-08.
*/
public class FragmentTwo extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment2, container, false);
}
}


그리고 fragment가 펼쳐질 activty_main.xml을 살펴보시겠습니다.두 fragment가 들어갈 부분을 LinearLayout으로 지정해놓고, id를 fragment로 추가하였습니다.

fragemetOne, Two 프래그먼트가 해당 Id가 fragment인 LinearLayout 부분에 들어가서 전개되는것을 확인하실 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.android.jinss.dando.MainActivity"
>

<LinearLayout
android:id="@+id/fragment"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</LinearLayout> 


마지막으로 Actionbar.TabListner를 implements하는 MainActivty를 살펴보시겠습니다. Fragment전개 역시 Main에서 합니다.


MainActivtiy.java

 package com.android.jinss.dando;


import android.app.ActionBar;
import android.app.Activity;

import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity implements ActionBar.TabListener {
Fragment frag1, frag2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ActionBar ab = getActionBar();
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

//타이틀바는빼고 탭만 보여주기위해 작성.
ab.setDisplayShowTitleEnabled(false);
ab.setDisplayShowHomeEnabled(false);


frag1 = new FragmentOne();
frag2 = new FragmentTwo();
ab.addTab(ab.newTab().setText("Text Fragment")
.setTabListener(this));
ab.addTab(ab.newTab().setText("Image Fragment")
.setTabListener(this));
}

@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
ft.replace(R.id.fragment, tab.getPosition() == 0 ? frag1 : frag2);
}

@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
ft.remove(tab.getPosition() == 0 ? frag1 : frag2);
}

@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
Toast.makeText(this, "Reselected!", Toast.LENGTH_LONG).show();
}
}



액션바에 네비게이션 탭을 add하여 전개하는 코드들을 살펴보았습니다. 근데 하면서, 탭의 height을 늘리고싶었는데 도무지 방법을 모르겠더라구요.

그럴때는 개발자가 직접 custom theme style.xml을 직접 작성하여서 해야하는 것같던데 자세한 방법을 찾아보다가 지쳐서 일단 pending 상태입니다.


혹시 아시는 아이디어가 있으시다면 댓글로 달아주시면 고맙겠습니다. 이상으로 안드로이드 액션바에 네비게이션 tab mode와 fragment 동시 구현에 대한 포스팅을 마치도록 하겠습니다. 감사합니다.

+ Recent posts