본문 바로가기

6. With IT/6.1 Android

안드로이드 어플리케이션 기초적인 내용들

출처 : http://wikiware-textcube.blogspot.kr/2009/12/4-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B8%B0%EC%B4%88.html


4. 안드로이드 어플리케이션 기초

4.1 안드로이드 아키텍처

 

(1) 안드로이드 어플리케이션의 특징

 

안드로이드 어플리케이션은 다음과 같은 특징을 가지고 있습니다.

 

  • 경계가 없는 어플리케이션 : 어플리케이션은 표준 API(Application Programming Interface)를 통해 핵심(core) 모바일 기능에 접근 가능합니다. 원하는 경우 어플리케이션은 자신의 기능을 다른 어플리케이션에게 공개할 수 있습니다.
  • 평등한 어플리케이션 : 모바일 기기에 있는 어플리케이션은 다이얼러(dialer)나 홈(home) 같은 핵심 구성 요소인 경우에도 대체 또는 확장이 가능합니다.
  • 웹 제어가 쉬운 어플리케이션 : 어플리케이션은 HTML, 자바스크립트, 스타일시트 등을 쉽게 추가할 수 있습니다. 또한 WebView를 통해 웹 컨텐츠를 쉽게 보여줄 수 있습니다.
  • 동시에 실행될 수 있는 어플리케이션 : 어플리케이션을 동시에 여러 개 실행할 수 있는 완벽한 멀티태스킹 환경을 제공합니다. 어플리케이션은 백그라운드에서 실행되면서 필요한 경우 알림 정보를 보낼 수 있습니다.

 

(2) 안드로이드 아키텍처

 

안드로이드는 아래 그림과 같은 아키텍처로 구성되어 있습니다. 아래 그림을 보면 4 개의 계층이 있습니다. 위에서부터 어플리케이션, 어플리케이션 프레임웍, 라이브러리/안드로이드 런타임이 있고, 제일 아래애 리눅스 커널이 있습니다.

 

 

어플리케이션 계층

 

제일 위의 어플리케이션 계층에는 Home, Contacts, Phone, Browser 같은 내장 어플리케이션 뿐만 아니고, 다른 어플리케이션들이 존재합니다. 내장 어플리케이션은 다음과 같은 것들이 있습니다.

 

  • Alarm Clock
  • Browser
  • Calculator
  • Camcorder
  • Camera
  • Contacts
  • Custom Locale (developer app)
  • Dev Tools (developer app)
  • Dialer
  • Email
  • Gallery
  • Gestures Builder
  • IME for Japanese text input
  • Messaging
  • Music
  • Settings
  • Spare Parts (developer app)

 

어플리케이션 프레임웍 계층

 

그 아래 계층이 어플리케이션 프레임웍 계층입니다. 이 계층에서는 뷰 시스템, 컨텐츠 제공자, 리소스 관리자, 알림 관리자, 액티비티 관리자 등의 프레임웍 API를 제공합니다. 이 API들은 내장 어플리케이션들이 사용하는 것과 동일합니다. 어플리케이션 아키텍처는 컴포넌트 재사용이 쉽게 설계되어 있습니다. 어플리케이션이 자신의 기능을 다른 어플리케이션에 노출(export)할 수 있으며, 이런 기능들은 다른 어플리케이션에서 사용이 가능합니다.

 

  • 뷰 시스템
    윈도 시스템의 컨트롤과 같은 것으로 리스트, 그리드(grid), 텍스트 박스, 버튼, 내장 브라우저 등 풍부하고 확장 가능한 사용자 인터페이스를 제공합니다.
  • 컨텐츠 제공자
    다른 어플리케이션이 데이터에 접근할 수 있도록 지원합니다.
  • 리소스 관리자
    현지화된 문자열, 그래픽, 레이아웃 파일 등과 같은 非코드 리소스에 접근할 수 있도록 합니다.
  • 알림 관리자
    상태 바에 각 어플리케이션의 용도에 따른 알림 정보를 표시할 수 있도록 합니다.
  • 액티비티 관리자
    어플리케이션의 수명을 관리합니다.

 

라이브러리와 안드로이드 런타임 계층

 

그 아래 계층에는 라이브러리와 안드로이드 런타임이 있습니다. 라이브라리 계층에는 표준 C 시스템 라이브러리인 libc와 PacketVideo 사의 OpenCORE에 기반을 둔 미디어(이미지나 음악 및 동영상) 라이브러리, 디스플레이 하부 시스템에 대한 접근을 제어하는 표면(surface) 관리자, 웹 브라우저 엔진인 LibWebCore, 기본 2D 그래픽 엔진인 SGL, OpenGL ES 1.0 API를 구현한 3D 라이브러리, 비트맵이나 벡터 폰트를 그려주는 FreeType, 가볍고 강력한 관계형 DBMS인 SQLite가 있습니다.

 

안드로이드 런타임은 자바 언어로 코어 라이브러리 기능을 사용할 수 있도록 하는 것입니다. 여기서 핵심은 Dalvik VM입니다. 이것은 모바일용으로 최적화된 가상 머신으로 전통적으로 사용되던 Java ME(Mobile Edition)와 같은 것을 사용하지 않고, 모든 어플리케이션이 자신의 프로세스 안에서 동작하고, 자신의 VM 인스턴스를 가지도록 만들어져 있습니다. 따라서 여러 개의 VM이 하나의 장치 안에서 동작이 가능하게 됩니다. 이 계층이 있으므로 해서 개발자는 특정 하드웨어 구현에 신경쓰지 않고 어플리케이션을 개발할 수 있게 된 것입니다.

 

리눅스 커널 계층

 

제일 아래 계층에는 리눅스 커널이 있습니다. 안드로이드 커널은 리눅스 2.6에 기반을 두고 만들어졌습니다. 이 계층에서는 보안, 스레딩, 프로세스, 메모리 관리 등의 하위 수준의 기능을 처리합니다.

 

4.2 어플리케이션 컴포넌트

 

(1) 안드로이드 패키지

 

컴파일된 자바 코드와 어플리케이션에 필요한 각종 데이터 및 리소스는 안드로이드 패키지(Android Package)에 번들이 되어 모바일 장치에 배포나 설치가 됩니다. 안드로이드 패키지는 확장자가 .apk로 아카이브된(archive)된 파일입니다. 하나의 .apk 파일은 하나의 어플리케이션으로 간주됩니다.

 

안드로이드 어플리케이션이 동작하는 양상은 3 가지 모습으로 표현할 수 있습니다. 첫 번째 모습은 모든 어플리케이션은 자신의 리눅스 프로세스 상에서 동작합니다. 어플리케이션을 실행시키면 안드로이드가 새로운 프로세스를 생성합니다. 그리고 어플리케이션이 더 이상 필요하지 않을 경우 프로세스를 종료합니다. 그 외에 다른 어플리케이션이 시스템 리소스를 요구할 때 다른 프로세스를 종료해야만 가능한 경우라면 안드로이드를 다른 프로세스를 강제로 종료할 수 있습니다.

 

안드로이드 어플리케이션 동작의 또 다른 모습은 각 프로세스는 자신의 자바 VM을 가진다는 것입니다. 이렇게 동작을 하기 때문에 각 어플리케이션은 다른 어플리케이션과 격리되어 동작합니다.

 

마지막 모습은 각 어플리케이션은 유일한 리눅스 사용자 ID를 할당 받는다는 것입니다. 어떤 어플리케이션의 파일들은 단지 그 어플리케이션의 사용자에게만 보이도록 권한(permission)이 설정됩니다. 두 개 이상의 어플리케이션이 같은 사용자 ID를 가지게 하는 예외적인 경우가 있는데, 이런 경우는 서로 다른 어플리케이션의 파일들을 볼 수 있게 됩니다.

 

(2) 어플리케이션 컴포넌트

 

안드로이드의 핵심 특징 중 하나는 다른 어플리케이션의 컴포넌트를 재사용할 수 있다는 것입니다. 만약 내 어플리케이션이 다른 어플리케이션의 기능을 이용하고 싶다면 다른 어플리케이션의 소스 코드도 필요 없고, 링크도 필요 없이 사용이 가능합니다. 물론 다른 어플리케이션의 자신의 기능을 노출해 줘야 합니다. 이렇게 되면 다른 어플리케이션의 기능 중 일부를 단순 호출하기만 하면 됩니다. 이런 특징 때문에 안드로이드 어플리케이션은main() 함수와 같은 하나의 진입점(entry point)만 가지지 않습니다. main()함수 자체가 없습니다.

 

이런 재사용을 가능하게 하는 구조가 바로 안드로이드 컴포넌트입니다. 안드로이드 컴포넌트는 다음과 같은 4 가지 종류가 있습니다.

 

  • 액티비티(activity)
    가시적인(visible) 사용자 인터페이스를 가지는 것들을 액티비티라고 합니다. 각 액티비티는 Activity 기본 클래스(base class)를 상속받아서 구현합니다. 하나의 어플리케이션은 여러 개의 액티비티로 구성이 될 수 있습니다. 각 액티비티는 전체 화면 또는 화면의 일부인 윈도(window)로 구성됩니다. 윈도는 뷰(view)를 가지고, 이 뷰는 컨트롤(control)과 같은 것으로 버튼, 텍스트, 스크롤 바, 메뉴 등을 가집니다.
  • 서비스(service)
    가시적인 사용자 인터페이스를 가지지 않고 백그라운드(background)에서 동작하는 것들을 서비스라고 합니다. 각 서비스는 Service 기본 클래스를 상속받아서 구현합니다. 서비스의 예로는 음악을 연주하는 서비스가 있습니다. 음악 연주 어플리케이션은 사용자 인터페이스가 필요하겠지만, 사용자가 음악 연주를 시작한 후에는 중지나 종료를 하기 전까지는 특별히 사용자 인터페이스가 필요하지 않습니다. 이런 부분은 서비스로 구현할 수 있습니다.
  • 브로드캐스트 수신자(broadcast receiver)
    널리 공지사항을 알리고자 하는 경우 특정 알림에 반응하는 것들을 브로드캐스트 수신자라고 합니다. 각 브로드캐스트 수신자는 BroadcastReceiver 기본 클래스를 상속받아 구현합니다. 반대로 사용자에게 뭔가를 알리고자 할 때는 NotificationManager를 이용합니다. 예를 들어, 타임존(timezone)이 바뀌었거나, 배터리가 부족하거나, 어떤 그림이 선택이 되었거나, 언어가 바뀌었거나 하는 다양한 상황이 있겠지요. 이럴 때 정보를 알리고자 하면NotificationManager를 사용하고, 알림을 받고자 하면BroadcastReceiver 기본 클래스를 상속받아 구현하면 됩니다.
  • 컨텐츠 공급자(content provider)
    어떤 어플리케이션의 데이터를 다른 어플리케이션에 제공하고자 할 때 사용하는 것들을 컨텐트 공급자라고 합니다. 각 컨텐츠 공급자는ContentProvider 기본 클래스를 상속받아 구현합니다.

 

(3) 인텐트(intent)

 

앞에서 설명한 것과 같이 안드로이드 컴포넌트는 4 가지 종류가 있습니다. 안드로이드에서 각 컴포넌트를 활성화시키는 방법은 인턴트(intent)라는 비동기 메시지를 이용합니다. 인턴트는 메시지를 담고 있는 Intent 객체입니다.

 

인텐트 객체로 활성화될 수 있는 컴포넌트는 액티비티, 서비스, 브로드캐스트 수신자 3 가지입니다. 컨턴츠 공급자는 인턴트로 활성화되지 않고, ContentResolver로부터 요청을 받았을 때 활성화됩니다. 인턴트를 인자로 전달하여 이들 컴포넌트를 활성화하는 방법은 다음과 같습니다.

 

  • 액티비티 활성화
    액티비티를 호출하는 쪽에서 Context.startActivity()Activity.startActivityForResult()를 부르면 됩니다. 그러면 안드로이드가 받아서 호출되는 쪽의 onNewIntent() 메소드를 불러 줍니다. 물론 인자로 인텐트가 전달됩니다. 전달된 인텐트 인자를 보고 의도를 파악하면 됩니다. 이와 별도로 호출된 액티비티는 getIntent() 메소드를 호출하여 의도를 파악할 수도 있습니다.
  • 서비스 활성화
    서비스를 호출하는 쪽에서 Context.startService() 메소드를 부르면 됩니다. 그러면 안드로이드가 받아서 호출되는 쪽의 onStart() 메소드를 불러 줍니다. 물론 인자로 인텐트가 전달됩니다.
  • 브로드캐스트 수신자 활성화
    브로드캐스트 수신자를 호출하는 쪽에서 Context.sendBroadcast()Context.sendOrderedBroadcast() 또는Context.sendStickyBroadcast()를 부르면 됩니다.

 

컴포넌트가 활성화된 후에는 자신이 스스로 종료(shut down)할 수도 있고, 다른 컴포넌트가 종료시킬 수도 있습니다. 각 컴포넌트를 종료하는 방법은 다음과 같습니다.

 

  • 액티비티 종료
    액티비티가 스스로 종료하고자 하는 경우는 finish() 메소드를 호출하면 됩니다. 반면에 다른 액티비티를 startActivityForResult() 메소드를 호출하여 활성화시킨 후에 종료하고자 할 때 finishActivity() 메소드를 호출하면 됩니다.
  • 서비스 종료
    서비스가 스스로 종료하고자 하는 경우는 stopSelf() 메소드를 호출하면 됩니다. 반면에 다른 서비스를 Centext.bindService()나 onBind(), bindService() 등의 함수로 호출하여 시작시킨 후에 종료하고자 하면Context.stopService() 메소드를 호출하면 됩니다.
  • 컨텐츠 공급자 종료
    별도의 종료를 위한 메소드가 제공되지 않습니다. 컨턴츠 공급자는ContentResolver에 반응하여 활성화되고 일을 처리한 후 종료됩니다.
  • 브로드캐스트 수신자 종료
    컨텐츠 공급자와 유사하게 별도의 종료를 위한 메소드가 제공되지 않습니다. 브로드캐스트 메시지에 반응하여 활성화되고 일을 처리한 후 종료됩니다.

 

(4) 매니패스트(manifest) 파일

 

안드로이드가 어플리케이션의 특정 컴포넌트를 시작하려면 안드로이드는 어플리케이션에 어떤 컴포넌트들이 있는지 알고 있어야 합니다. 이를 위해서 어플리케이션은 자신의 컴포넌트들을 매니패스트 파일에 선언을 해야 하며, 이 매니패스트 파일도 안드로이드 패키지인 .apk 파일 안에 번들되어야 합니다.

 

모든 어플리케이션은 항상 AndroidManifest.xml로 명명된 매니패스트 파일을 가지고 있어야 합니다. 매니패스트 파일은 구조화된 XML 파일입니다. 이 파일에는 컴포넌트(액티비티, 서비스, 브로드캐스트 수신자, 컨텐츠 제공자)들이 어떤 것들이 있는지와 링크 시에 필요한 라이브러리가 어떤 것들이 있는지와 어플리케이션의 권한(permission) 등이 기술되어 있습니다. 다음은 매니패스트 파일의 예입니다.

 

<?xml version="1.0" encoding="utf-8"?>
<manifest . . . >
    <application . . . >
        <activity android:name="com.example.project.FreneticActivity"
                  android:icon="@drawable/small_pic.png"
                  android:label="@string/freneticLabel" 
                  . . .  >
        </activity>
        . . .
    </application>
</manifest>

 

<activity> 요소의 name 속성에 이 액티비티를 구현한 Acitivity 하위 클래스 이름을 적습니다. 위 예의 경우는 "com.example.project.FreneticActivity"입니다. 만약 액티비티의 라벨이나 아이콘이 사용자에게 표시될 필요가 있을 경우는 labelicon도 기술합니다. 매니패스트 파일에는 <activity> 요소 외에 서비스를 기술하기 위한 <service>, 컨텐츠 제공자를 기술하기 위한 <provider>, 브로드캐스트 수신자를 기술하기 위한 <receiver> 요소가 있습니다.

 

인턴트 객체에는 타켓 컴포넌트를 명시적으로 적어 넣을 수 있지만, 타켓 컴포넌트를 명시적으로 적지 않을 수도 있습니다. 만약 타켓 컴포넌트를 명시적으로 적지 않으면 안드로이드는 인턴트 필터(intent filter)를 보고 어떤 컴포넌트를 활성화할 지 결정합니다. 다음은 매니패스트 파일에 기술된 인턴트 필터의 예입니다.

 

<?xml version="1.0" encoding="utf-8"?>
<manifest . . . >
    <application . . . >
        <activity android:name="com.example.project.FreneticActivity"
                  android:icon="@drawable/small_pic.png"
                  android:label="@string/freneticLabel" 
                  . . .  >
            <intent-filter . . . >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter . . . >
                <action android:name="com.example.project.BOUNCE" />
                <data android:mimeType="image/jpeg" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        . . .
    </application>
</manifest>

 

위 예는 앞의 매니패스트 파일에 인턴트 필터가 추가된 것입니다. 위 코드의 굵은 글씨로 표시된 부분이 추가된 부분입니다. 인텐트 필터에 <action>과 <category>, <data>요소가 있습니다. 첫 번째 인턴트 필터는 어플리케이션 론처(launcher)에서 실행될 컴포넌트의 경우에 사용되는 일반적인 액션, 카테고리 조합입니다. 즉, 액션은"android.intent.action.MAIN"을 사용하고, 카테고리는"android.intent.category.LAUNCHER"를 사용합니다. 매니패스트 파일에 대한 자세한 내용은 별로도 설명할 예정입니다.

 

4.3 액티비티와 태스크

 

(1) 태스크(task)

 

어떤 어플리케이션에서 다른 어플리케이션의 기능을 이용하고자 할 경우에 다른 어플리케이션에 Intent 객체를 인자로 startActivity() 메소드를 호출하면 됩니다. 그러면 사용자에게는 타 어플리케이션이 현 어플리케이션의 일부인 것처럼 보이게 됩니다. 실제로 안드로이드는 호출한 액티비티와 호출된 액티비티를 같은 태스크(task) 안에 유지시킵니다.

 

태스크(task)는 사용자가 마치 하나의 어플리케이션인 것처럼 경험하게 하는 것들을 말합니다. 즉, 태스크는 "스택에 쌓여 있는 관련된 액티비티들의 그룹"이라고 말할 수 있습니다. 스택의 밑바닥에 있는 루트 액티비티(root activity)가 태스크를 시작한 것입니다. 보통은 어플리케이션 론처에서 사용자가 선택한 액티비티가 루트 액티비티가 됩니다. 스택의 꼭대기에 있는 액티비티는 현재 동작 중인 포커스된 액티비티입니다.

 

어떤 액티비티가 다른 액티비티를 동작시키면 새로운 액티비티가 스택에 쌓입니다. 휴대폰에서 사용자가 BACK 키를 누르면 이전 화면으로 돌아가는데, 이 때 현재 액티비티가 스택에서 팝(pop)되고 이전 액티비티가 실행됩니다. 따라서 태스크는 "액티비티들이 쌓여 있는 스택"으로 단순히 생각하는 것이 쉽게 이해가 됩니다. 태스크가 매니패스트 파일 안에 있는 어떤 클래스나 요소(element)가 아니므로 오해를 하지 않도록 주의하세요.

 

하나의 태스크 안에 있는 액티비티들은 태스크 단위로 움직입니다. 태스크가 포그라운드(foreground)나 백그라운드(background)로 전환될 때 관련 태스크가 통째로 옮겨집니다. 이런 동작을 바꾸려면 매니패스트 파일의 <activity> 요소의 플래그(flag)나 속성을 변경해야 합니다. 매니패스트 파일의 인텐트 플래그(flag)에는 다음과 같은 것들이 있습니다.

 

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  • FLAG_ACTIVITY_SINGLE_TOP

 

<activity> 요소의 속성에는 다음과 같은 것들이 있습니다.

 

  • taskAffinity
  • launchMode
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwaysRetainTaskState
  • finishOnTaskLaunch

 

위 플래그나 속성들에 대해서는 자세한 설명을 생략합니다. 후에 다시 설명할 기회가 있을 것입니다.

 

(2) 인척 관계(affinity)

 

기본적으로 한 어플리케이션 안의 모든 액티비티들은 서로 인척 관계(affinity)에 있습니다. 인척 관계라고 하는 것은 같은 태스크에 속할 수 있는지 여부를 나타냅니다. 이런 인척 관계는 다른 어플리케이션과도 맺어질 수가 있습니다. 또한 같은 어플리케이션 안의 액티비티들이 서로 다른 인척 관계를 가질 수도 있습니다. 이런 인척 관계를 설정하는 방법은 매니패스트 파일의 <activity> 요소의 taskAffinity 속성으로 제어를 할 수 있습니다. 인척 관계를 설정하는 2 가지 방법은 다음과 같습니다.

 

  • FLAG_ACTIVITY_NEW_TASK 플래그를 이용한 방법
    기본적으로 startActivity()로 호출된 액티비티는 호출한 액티비티의 태스크에 포함됩니다. 단, startActivity()를 부를 때 intent 객체 인자에FLAG_ACTIVITY_NEW_TASK 플래그가 설정되어 있으면, 시스템이 새 태스크로 새 액티비티를 시작합니다. 기존 태스크에 포함된 경우는 해당 태스크에 포함시킵니다.
  • allowTaskReparenting 속성을 이용한 방법
    이 속성이 “true”인 경우, 어떤 태스크에 포함되어 있다가도 다른 인척 관계에 있는 태스크가 포그라운드가 될 때 이 태스크로 옮겨갈 수 있습니다.

 

(3) 론치 모드(launch mode)

 

<activity> 요소의 launchMode 속성에는 다음과 같은 4 가지가 있습니다.

 

  • "standard" (the default mode)
  • "singleTop”
  • "singleTask”
  • "singleInstance“

 

위 속성들을 해석하는 방법은 다음과 같이 4 가지 관점이 있습니다.

 

  • 인턴트에 반응한 액티비티를 어느 태스크가 소유하는가?
    "standard”나 "singleTop”의 경우는 startActivity()를 호출한 태스크가 호출된 액티비티를 소유합니다. (단, FLAG_ACTIVITY_NEW_TASK 플래그가 인턴트에 있는 경우는 예외입니다.) "singleTask”"singleInstance”의 경우는 항상 루트 액티비티가 됩니다. 즉, 항상 새 태스크가 됩니다.
  • 태스크가 여러 개의 인스턴스를 가질 수 있는가?
    "standard”나 "singleTop”의 경우는 여러 번 인스턴스화될 수 있습니다. 이 말은 하나의 액티비티가 여러 태스크에 속할 수 있다거나, 하나의 태스크에 여러 개의 같은 액티비티가 생길 수 있다는 뜻입니다. "singleTask”"singleInstance“의 경우는 단지 하나의 인스턴스만 가집니다.
  • 한 태스크 안에 같은 액티비티 인스턴스를 2 개 이상 가질 수 있는가?
    "singleInstance”인 경우는 단지 하나의 액티비티만 가지는 태스크를 만듭니다. 다른 액티비티를 부르면 불린 것은 마치FLAG_ACTIVITY_NEW_TASK 플래그가 설정된 것처럼 새로운 태스크로 들어갑니다. "standard”나 "singleTop” 또는 "singleTask”의 경우는 하나의 태스크 안에 여러 개의 액티비티가 포함될 수 있습니다.
  • 새로운 인턴트를 처리할 때 항상 새로운 인스턴스가 생성되는가?
    "standard”
    인 경우는 신규 인텐트는 항상 신규 인스턴스로 반응합니다."singleTop”인 경우는 신규 인텐트는 기존 인스턴스를 재사용하여 반응합니다. (단, 재사용된 액티비티는 타켓 태스크의 꼭대기에 있는 액티비티여야 합니다.) 꼭대기에 있는 액티비티가 아니면 재사용되지 않고 새 인스턴스가 생성되어 스택에 푸쉬(push)됩니다. 아래 그림을 참조 바랍니다.

 

(4) 스택 지우기

 

사용자가 오랫동안 태스크를 떠나 있으면, 시스템이 자동으로 그 태스크의 액티비티들을 지웁니다. 단 루트 액티비티는 남겨 놓습니다. 이런 경우 사용자가 다시 이 태스크로 돌아 왔을 때 루트 액티비티가 보여지게 됩니다. 이와 같은 동작이 기본 동작입니다. 만약 다른 동작을 하도록 하고 싶으면 앞에서 설명드린  <activity> 요소의 속성으로 제어할 수 있습니다. 다음의 3 가지 속성이 이를 제어하는 속성들입니다.

 

  • alwaysRetainTaskState 속성
    루트 액티비티의 속성이 “true”이면, 오랜 시간이 지나도 그대로 남아 있습니다.
  • clearTaskOnLaunch 속성
    루트 액티비티의 이 속성이 “true”이면, 사용자가 이 태스크를 떠났다가 돌아올 때마다 루트 액티비티만 남겨 놓고 나머지는 스택에서 삭제합니다.
  • finishOnTaskLaunch 속성
    사용자가 이 태스크를 떠났다가 돌아올 때마다 이 속성이 “true”인 액티비티만 스택에서 삭제합니다. 이 경우는 루트 액티비티도 삭제될 수 있습니다.

 

스택을 지우는 다른 방법으로 인턴트 객체의 FLAG_ACTIVITY_CLEAR_TOP 플래그를 사용하는 방법이 있습니다. 이 플래그를 사용하면 수신한 인턴트 객체를 처리할 수 없는 액티비티들이 타켓 태스크 스택에서 모두 삭제됩니다. 즉, 수신한 인턴트 객체를 처리할 수 있는 액티비티가 스택의 꼭대기에 나올 때까지 삭제가 일어납니다. 다만 처리 가능한 액티비티의 론치 모드가 "standard"인 경우는 이 액티비티도 스택에서 삭제되고, 새 액티비티가 생성되어 스택에 푸쉬됩니다. "standard"가 의미하는 것이 새 인턴트는 새 액티비티로 처리하라는 것이기 때문입니다.

 

(5) 진입점(entry point)

 

어떤 태스크의 진입점을 지정하는 방법은 앞에서도 설명하였듯이 매니패스트 파일의 액티비티의 <intent-filter>에 액션은 "android.intent.action.MAIN"을 사용하고, 카테고리는 "android.intent.category.LAUNCHER"를 사용하면 됩니다. 이런 진입점을 가지는 필터에는 아이콘과 라벨도 추가로 지정하여야 어플리케이션 론처에 표시가 됩니다.

 

이런 진입점을 가진 액티비티는 항상 "singleTask나 "singleInstance”로 시작해야 합니다. 왜냐하면 어플리케이션 론처에서 실행 가능한 것이 "singleTask"singleInstance”로 되어 있지 않으면, 실행 후 홈 키를 누른 후에는 다시 기존 태스크로 돌아갈 방법이 업습니다. 이와 비슷한 문제가 FLAG_ACTIVITY_CLEAR_TOP 플래그를 가진 액티비티에도 있습니다. 이런 것들은 활성화된 후에 다시 돌아올 다른 방법을 부가적으로 제공해 주어야 합니다. 만약 다시 돌아올 방법을 제공하지 못하는 경우는  <activity> 요소에 finishOnTaskLaunch를 "true"로 설정하여 종료가 되도록 해야 함에 주의하세요.

 

4.4 프로세스와 쓰레드

 

(1) 프로세스(process)

 

어떤 어플리케이션의 컴포넌트들 중 하나가 최초로 실행될 때, 안드로이드는 실행 쓰레드(thread) 하나를 가진 리눅스 프로세스(process)를 하나를 생성합니다. 기본적으로 이 어플리케이션의 모든 다른 컴포넌트들은 새로 생성된 이 프로세스와 쓰레드(메인 쓰레드라고 불림)에서 동작을 합니다. 컴포넌트가 다른 프로세스에서 실행되게 하려면 그 프로세스에 추가적인 쓰레드를 만들어서 실행해야 합니다.

 

어떤 컴포넌트가 어떤 프로세스에서 동작할 지는 매니패스트 파일에서 제어가 됩니다.<activity>, <service>, <receiver>, <provider> 요소의 process 속성으로 제어를 할 수 있습니다. process 속성으로 컴포넌트가 자신의 프로세스에서 동작할 지, 다른 프로세스에서 동작할 지 결정할 수 있습니다. 또한 리눅스 사용자  ID와 권한을 자신 것으로 할 지 다른 프로세스 것으로 할 지 결정할 수 있습니다. 각 컴포넌트에 개별적으로 속성을 지정하지 않고 <application> 요소의 process 속성으로 모든 컴포넌트에 적용되는 디폴트 값을 설정할 수도 있습니다.

모든 컴포넌트는 해당 프로세스의 메인 쓰레드에서 인스턴스화됩니다. 시스템 호출도 메인 쓰레드에서 동작합니다. 따라서 각 컴포넌트는 시스템에 의해서 불렸을 때는 시간이 오래 걸리거나 블럭킹(blocking)이 되는 연산(예, 네트웍 연산 또는 계산 루프)은 메인 쓰레드에서 수행하지 않도록 주의해야 합니다. 시간 오래 걸리는 것은 별도 쓰레드를 생성하는 것이 좋습니다.

 

안드로이드가 프로세스를 죽이는 경우는 메모리가 부족한 경우이거나 사용자에게 더 즉각적으로 서비스해야 하는 다른 프로세스의 요구가 있을 경우에 한합니다. 이런 상황이 닥쳤을 때 어떤 프로세스를 죽일지는 중요도에 따라 결정이 됩니다. 예를 들면 사용자에게 보이는 것과 보이지 않는 것이 있으면 보이지 않는 것이 중요도가 더 낮습니다. 안드로이드는 중요도가 낮은 보이지 않는 프로세스를 먼저 죽이게 됩니다. 중요도는 프로세스 상에서 동작하고 있는 컴포넌트의 상태에 따라서 달라집니다. 이에 대해서는 추후 다시 설명 드리겠습니다.

 

(2) 쓰레드(thread)

 

어플리케이션이 하나의 프로세스 상에서 동작하더라도 백그라운드 작업의 경우는 별도의 쓰레드를 만들어서 수행할 필요가 있다고 앞에서 설명하였습니다. 쓰레드를 만드는 방법은 표준 자바 Thread 객체를 사용하면 됩니다. 안드로이드는 쓰레드 관리를 쉽게 하도록 여러 클래스를 제공하고 있습니다. 예를 들면, 쓰레드 내에서 메시지 루프를 실행하기 위한Looper, 메시지 처리를 위한 Handler, 메시지 루프를 가진 쓰레드를 만들기 위한HandlerThread 등이 있습니다.

 

여러 쓰레드에서 동시에 실행되어도 문제가 없이 동작해야 하는 것을 쓰레드에 안전(thread-safe)하다고 합니다. 하나 이상의 쓰레드에서 호출될 수 있는 메소드는 쓰레드에 안전하게 작성되어야 합니다. 특히 원격에서 호출될 수 있는 메소드는 쓰레드에 안전하게 작성될 필요가 있습니다.

 

(3) RPC(Remote Procedure Call)

 

안드로이드는 경량의 RPC 메커니즘을 제공하고 있습니다. RPC는 지역 메소드 호출로 원격 프로세스의 메소드를 실행하여 그 결과를 반환받는 것입니다. 좀 더 자세히 설명하면, RPC는 운영체제가 이해할 수 있는 수준으로 메소드 호출과 관련 데이터를 쪼개서 다른 원격 프로세스에게 전송한 후에 다시 조립하여 원격에 있는 메소드를 호출하게 됩니다. 반환 값도 이런 식으로 쪼갠 후 조립하여 지역 메소드에 원격 메소드 호출 결과로 전달됩니다. 안드로이드가 이런 동작 메커니즘을 다 준비해 놓았으므로 개발자는 RPC 인터페이스 구현만 신경 쓰면 됩니다.

 

RPC 인터페이스는 메소드들로만 구성되어 있습니다. 여기에 정의된 모든 메소드는 동기화되어 실행이 됩니다. 메소드가 동기화되어 실행된다는 의미는 지역 메소드 호출은 원격 메소드의 실행이 종료될 때까지 블럭된다는 뜻입니다. 반환 값이 없더라도 동기화가 이루어집니다. RPC 인터페이스는 IDL(Interface Definition Language)로 선언됩니다. 안드로이드 SDK에서 제공되는 aidl 도구를 이용하여 IDL을 생성할 수 있습니다. RPC 인터페이스는 아래 그림과 같이 2 개의 내부 클래스로 정의됩니다.

 

 

 

위 그림에서 Stub 내부 클래스는 원격에서 제공해야 하는 서비스 기능입니다. 개발자가 구현해 주어야 하는 클래스이죠. 점선 박스로 표시된 내부 클래스는 IBinder를 구현한 것으로 안드로이드에 의해 사용되는 클래스입니다.

 

서비스와 클라이언트 간의 연결을 설정하는 방법에 대해 알아 보겠습니다. 우선 클라이언트에서는 원격에 있는 서비스와 연결이 성공하거나 실패하면 알림을 받을 수 있도록onServiceConnected()와 onServiceDisconnected()를 구현해야 합니다. 서비스 연결에 성공했을 때는 onServiceConnected() 메소드가 호출되고, 서비스 연결이 실패했을 때는 onServiceDisconnected() 메소드가 호출됩니다. 서비스 연결에 성공하여onServiceConnected() 메소드가 호출되면 이 메소드에서 bindService() 메소드를 호출하여 연결을 설정하면 됩니다. 반면에 서비스에서도 해 줘야 할 일이 있습니다. 서비스에서는 onBind() 메소드를 구현해 줘야 합니다. 이 메소드애서는 클라이언트에서 연결을 시도해 왔을 때 이를 수용할 지 거부할 지 결정할 수 있습니다. 이런 결정을 내릴 때onBind() 메소드의 인자로 전달되는 인턴트를 보고 결정할 수 있습니다. 만약 클라이언트의 연결 시도가 받아들여지면 Stub 하위 클래스의 인스턴스가 반환됩니다. 서비스가 연결을 수용하면 안드로이드는 onServiceConnected() 메소드를 호출하여 IBinder 객체를 클라이언트에 전달합니다.  IBinder 객체는 서비스에 의해 관리되는 Stub 하위 클래스에 대한 프락시(proxy) 역할을 합니다. 이 프락시를 통해서 클라이언트는 원격 서비스를 호출할 수 있습니다. 앞에서도 설명했지만  IBinder 메소드들은 쓰레드에 안전하게 구현이 되어야 합니다.

 

4.5 컴포넌트의 수명

 

어플리케이션의 컴포넌트는 수명이 있습니다. 다시 말해서 컴포넌트는 태어나서 죽게 됩니다. 안드로이드가 인스턴스를 만들 때 생명이 시작됩니다. 그리고 안드로이드가 인스턴스를 삭제할 때 생명이 끝나게 됩니다. 컴포넌트는 태어나서 죽기까지 활성화되기도 하고 비활성화도기도 합니다. 다시 말해, 활성화될 때는 사용자에게 보이고 비활성화될 때는 사용자에게 안 보이게 됩니다.

 

(1) 액티비티의 수명

 

액티비티도 컴포넌트 중의 하나이므로 생명을 가지고 있고 수명도 있습니다. 액티비티가 살아 있는 동안 다음 3 가지 상태에 있을 수 있습니다.

 

  • 활성(active) 또는 실행 중(running) 상태
    액티비티가 포그라운드 상태에 있을 때입니다. 다시 말해서 현재 태스크 스택의 꼭대기에 위치해 있을 때입니다. 이 상태에서는 사용자의 동작에 대해서 포커스를 가지게 됩니다.
  • 일시 정지(paused) 상태
    포커스를 잃지만 여전히 보이는 상태에 있을 때입니다. 다른 액티비티가 이 액티비티 위에 일부 또는 투명하게 덮은 경우에 해당합니다. 일시 정지 상태에서는 안드로이드가 메모리가 부족하면 컴포넌트를 죽일 수 있습니다.
  • 멈춤(stopped) 상태
    다른 액티비티에 의해 완전히 가려진 때입니다. 이럴 때는 사용자에게 더 이상 보이지 않는 경우입니다. 멈춤 상태에서도 안드로이드가 메모리가 부족하면 컴포넌트를 죽일 수 있습니다.

 

액티비티 수명 관련 메소드

 

액티비티의 위 3 가지 상태 간의 전이가 일어나면 안드로이드는 다음과 같은 액티비티의 보호된(protected) 메소드를 불러서 상태 변경을 알려 줍니다.

 

  • void onCreate(Bundle savedInstanceState)
  • void onStart()
  • void onRestart()
  • void onResume()
  • void onPause()
  • void onStop()
  • void onDestroy()

 

위 메소드들은 재정의가 가능한 메소드들입니다. 따라서 상태 전이가 있을 때 무언가 일을 하려면 위 메소드들을 재정의하면 됩니다. 예를 들어서 액티비티가 인스턴스화 된 후에 초기화 코드를 구현해 주고 싶다면 onCrate() 메소드를 재정의하면 됩니다. 일시 정지 상태에서 시스템에 의해 죽는 경우에 복구를 할 수 있도록 영구적인 데이터를 스토리지에 저장할 필요가 있을 때는 onPause() 메소드를 재정의하면 됩니다.

 

액티비티 상태 전이 다이어그램

 

액티비티의 전체 수명은 onCreate()와 onDestroy() 사이의 시간입니다. 눈에 보이는 상태에 있는 경우는 onStart()와 onStop() 사이의 시간입니다. 포그라운드(foreground) 상태에 있는 경우는 onResume()과 onPause() 사이의 시간입니다. 다음 그림은 액티비티의 탄생에서부터 죽음에 이르는 과정과 이 사이의 여러 상태 전이 과정을 보이는 다이어그램입니다.

 

 

위 다이어그램에서 보면 액티비티가 탄생하여 인스턴스화가 되면 onCreate()가 불리고, 눈에 보이는 상태가 되면 onStart()가 불리고, 포그라운드로 되면서 onResume()이 불립니다. 이 상태가 활성화되어 실행 중인 상태입니다. 그리고 잠들어서 백그라운드가 되면서 일시 정지되어 onPause()가 불리고, 눈에 보이지 않게 되면서 멈추게 되어 onStop()이 불리고, 죽으면서 onDestroy()가 불립니다. onPause()나 onStop() 상태에서는 시스템에 의해서 죽음을 당할 수 있고 사용자가 이 액티비티로 되돌아 오면 부활을 하여onCreate()가 다시 불립니다. 백그라운드에서 잠들어 있는 상태에서 포그라운드로 깨어난 상태가 되면서, 다시 말해 멈춤 상태에서 실행 상태로 바뀌면서 onRestart()onStart()가 연속으로 불립니다.

 

액티비티의 상태 저장


시스템이 액티비티를 죽을 수 있으므로, 사용자가 죽었던 액티비티로 되돌아 왔을 때 이전 상태를 복원할 수 있어야 합니다. 이전 상태를 복원하려면 onSaveInstance()라는 메소드를 구현하면 됩니다. 안드로이드는 액티비티를 죽이기 전에 이 메소드를 호출하여 나중에 부활 후 복원에 필요한 정보를 저장할 기회를 줍니다. onSaveInstance()에 인자로Bundle 객체를 전달해 줍니다. 여기에 이름-값 쌍을 저장하면 됩니다. 액티비티가 다시 부활하여 다시 시작할 때 Bundle 객체가 onCreate() 메소드에 다시 전달됩니다. 또한onStart()가 불린 후에 불리는 OnRestoreInstanceState() 함수에도 전달됩니다.onSaveInstance()라는 메소드는 액티비티가 죽을 때 항상 불리는 것이 아니고, 시스템에 의해서 죽을 때만 불리므로, 데이터를 영구적으로 저장할 목적으로 이 함수 이용하지는 말아야 합니다. 이런 목적을 위해서는 앞에서 설명했듯이 onPause() 함수에서 구현해야 합니다.


상태 전이 시 액티비티들의 호출 순서


어떤 액티비티가 다른 액티비티를 호출 시 두 액티비티는 상태가 변하게 됩니다. 상태가 변하면서 불리는 메서드의 순서를 잘 알고 있어야 합니다. 먼저, 현재 액티비티의 onPause()메소드가 호출됩니다. 그리고 신규 액티비티의 onCreate()onStart()onResume() 메소드가 순서대로 불립니다. 마지막으로 신규 액티비티가 보이지 않게 되면, 이 액티비티의onStop() 메소드가 호출됩니다.

 

(2) 서비스의 수명

 

서비스를 시작하는 2 가지 방법

 

서비스는 2 가지 방법으로 시작하거나 중지될 수 있습니다. 서비스가 시작된 후에 다른 누군가가 중지하거나 스스로 중지할 수 있습니다. 다른 서비스를 시작할 때는 Context.startService() 메소드를 호출합니다. 다른 서비스를 중지시킬 때는Context.stopService() 메소드를 호출합니다.

 

반면에 자기 스스로 서비스를 중지할 때는 Service.stopSelf() 또는Service.stopSelfResult() 메소드를 호출하면 됩니다. startService()는 여러 서비스에서 여러 번 불릴 수 있습니다. stopService()를 불렀다고 해서 서비스가 중지되는 것은 아닙니다. 여러 서비스에서 서비스를 시작한 횟수만큼 stopService()가 불려야 중지가 됩니다. 그렇다고 stopService()를 여러 번 부르면 안 되고 startService()를 불렀다면 한 번만 부르면 됩니다.


서비스를 동작시키는 두 번째 방법은 외부 노출된 인터페이스를 이용하는 것입니다. 클라이언트가 Service 객체에 연결한 후에, 이 연결을 이용하여 서비스를 호출하는 방법이 있습니다. 서비스와 연결 맺기와 연결 끊기를 위해서 호출되는 메소드는 다음과 같습니다.

 

  • 연결 맺기 : Context.bindService() 호출
  • 연결 끊기 : Context.unbindService() 호출

 

이렇게 하여 여러 클라이언트가 하나의 서비스에 바인드(bind)될 수 있습니다. 서비스에 바인드되기 위해서는 다음과 같이 호출을 해야 합니다.

 

  • 서비스 바인드 : bindService() 호출

 

예를 들어, startService()에 인텐트 객체를 넘겨서 음악을 연주한 후에, 나중에 현재 연주 중인 곡에 대한 정보를 알고 싶다면 bindService()를 호출하여 액티비티가 서비스와 연결을 맺을 수 있습니다. 이런 경우는 stopService()를 불러도 실제 서비스가 중지되지 않을 수 있습니다. bindService()가 여러 번 불렸을 수 있기 때문이며 마지막 바인딩이 끊어질 때에야 비로소 서비스가 중지됩니다.

 

서비스의 수명 관련 메소드


서비스의 수명에 관련된 3 가지 메소드는 다음과 같습니다. 서비스는 액티비티와는 달리 아래 3 개 외에는 없습니다. 서비스에는 onStop()과 같은 메소드가 없는 것에 특히 주의바랍니다.

 

  • void onCreate()
  • void onStart(Intent intent)
  • void onDestroy()

 

서비스의 탄생에서 죽음까지의 수명은 onCreate()와 onDestroy() 사이입니다.Context.startService() 또는 Context.bindService()가 불려서 서비스가 시작될 때는 항상 이 2 개 메소드가 호출됩니다. 서비스는 onStart() 메소드가 호출되면 활성 상태가 되었다가 이 메소드가 끝나면 비활성 상태가 됩니다. 클라이언트가startService() 메소드를 불러 서비스를 시작할 때 인자로 전달한 인텐트 객체가 서비스의 onStart() 메소드에 전달됩니다. 이 인자를 보고 어떤 서비스를 할 지 알 수 있습니다. 예를 들면, 인텐트 인자를 보고 연주할 음악이 무엇인지 알 수 있습니다. 이 메소드는startService()로 시작된 서비스에서만 호출됩니다.

 

바인드된 서비스에서 구현해야 콜백 메소드

 

서비스에서 구현해야 할 3 개의 콜백(callback) 메소드가 있습니다.
 

  • IBinder onBind(Intent intent)
    이 메소드의 인자는 bindService()에 전달된 인텐트 객체입니다. 반환 값은 클라이언트가 서비스와 인터랙션을 하는데 사용할 통신 채널입니다.
  • boolean onUnbind(Intent intent)
    이 메소드의 인자는 unbindService()에 전달된 인텐트 객체입니다.
  • void onRebind(Intent intent)
    새로운 클라이언트가 서비스에 연결하는 경우 onUnbind()에서 onRebind()를 부릅니다.

 

다음 그림은 콜백 메소드 호출을 설명한 다이어그램입니다. 서비스가 startService()로 시작된 경우와 bindService()로 시작된 경우에 각각 호출되는 메소드나 콜백 함수가 다릅니다. 바인드된 서비스에는 onStart() 메소드가 없는 대신 위 3 개의 콜백 함수가 있습니다.

 

 

(3) 브로드캐스트 수신자의 수명

 

브로드캐스트 수신자는 다음과 같이 하나의 콜백 메소드만 제공합니다.

 

  • void onReceive(Context curContext, Intent broadcastMsg)

 

브로드캐스트 메시지가 수신자에게 도착하면 안드로이드가 onReceive() 메소드를 호출합니다. 이 때 메시지를 담고 있는 인텐트 객체를 인자로 전달합니다. 브로드캐스트 수신자는 onReceive() 함수가 호출될 때만 활성화되고, 이 함수가 끝나면 다시 비활성화됩니다. 활성화 상태의 브로드캐스트 수신자를 가지고 있는 프로세스는 시스템에 의해서 죽임을 당하지 않습니다.
 

(4) 컴포넌트의 중요도 순위

 

안드로이드는 메모리가 부족할 때 죽일 프로세스를 선택합니다. 어떤 프로세스를 죽일지는 컴포넌트의 “5 단계 중요도 계층 구조”로 결정합니다. 계층 구조에서 중요도 순위가 가장 낮은 것이 먼저 죽임을 당합니다. 1 순위부터 5 순위까지 중요도 계층 구조는 다음과 같습니다.

 

  • 1 순위 : 포그라운드 프로세스
    사용자가 현재 사용 중인 프로세스로 포그라운드 상태에 있는 것들이 중요도 1 순위입니다. 사용자가 상호작용 중인 액티비티를 가진 프로세스가 여기에 속합니다. 사용자가 상호작용 중인 액티비티에 바인드된 서비스를 가진 프로세스도 여기에 속합니다. 수명 관련된 콜백 함수들(onCreate(), onStart(), onDestroy()) 중 하나를 실행 중인 서비스 객체를 가진 프로세스도 여기에 속합니다. 그리고 onReceive() 메소드를 실행 중인 BroadcastReceiver객체를 가진 프로세스도 여기에 속합니다.
  • 2 순위 : 보이는 프로세스
    포그라운드 컴포넌트를 가지고 있지 않으나 화면에 보이는 것들이 중요도 2 순위입니다. 포그라운드 상태는 아니나 사용자에게 여전히 보이는 상태의 액티비티를 가진 프로세스가 여기에 속합니다. onPause()가 불린 경우로, 이전 화면이 보이게 대화상자가 뜬 경우가 이런 상태입니다. 보이는 액티비티에 바인드된 서비스를 가진 프로세스도 여기에 속합니다.
  • 3 순위 : 서비스 프로세스
    startService() 
    메소드로 시작된 서비스 중 실행 중인 프로세스들이 중요도 3 순위입니다. 단, 위 1~2 순위에 속하지 않는 것들만 해당됩니다. 예를 들면, 음악을 연주 중인 서비스, 네트웍에서 다운로들 하고 있는 서비스가 여기에 속합니다.
  • 4 순위 : 백그라운드 프로세스
    보이지 않는 액티비티를 가진 프로세스들이 중요도 4 순위입니다.onStop()으로 중지된 것들이 여기에 속합니다. 이런 액티비티는 특히 많기 때문에 가장 오래 전에 사용된 것들부터 즉, LRU(Least Recently Used)로 죽임을 당합니다.
  • 5 순위 : 텅빈 프로세스
    활성화된 어플케이션 컴포넌트가 없는 프로세스들이 중요도 5 순위입니다. 이런 프로세스들은 대부분 프로세스 시작 시간을 줄이기 위해 캐쉬된 프로세스들이 여기에 속합니다.


'6. With IT > 6.1 Android' 카테고리의 다른 글

Meterial Design Example  (0) 2014.12.23
안드로이드 Affinity  (0) 2013.10.17
apk만들기  (0) 2013.03.05
안드로이드 위치정보찾기  (1) 2013.02.28
안드로이드 종합  (0) 2013.02.18