API GUIDE/2. App Components
2014. 3. 18. 20:56

저작권 표시 : 

 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.

 

현재 보시는 페이지는 안드로이드 오픈 소스 프로젝트에 의해 작성되고 공유된 작업물의 수정/번역본이며 크리에이티브 커먼즈 저작자 표시 2.5 라이센스에 기술된 조건의 사용 근거를 따른 것입니다.

 

본 문서의 원본은 http://developer.android.com/guide/components/activities.html이며 안드로이드 4.4 Kitkat 기준으로 설명되었습니다.





액티비티 라이프사이클 관리하기(Managing the Activity Lifecycle)



액티비티의 라이프사이클을 관리할 때 관련 콜백 메서드를 사용하면 강력하면서도 유연한 어플리케이션을 개발할 수 있다. 액티비티의 라이프사이클은 액티비티와 연계되어 있는 또 다른 액티비티, 태스크, 백스택에 의해 직접적인 영향을 받는다.


액티비티는 기본적으로 다음의 3개 상황에 처할 수 있다.


리줌 상태(Resumed)

이 상태에 있는 액티비티는 화면의 제일 전면에 있으며 사용자의 포커스를 갖고 있다. (이 상황은 종종 "실행 중"이라 일컫어지기도 한다.)


일시 정지 상태(Paused)

다른 액티비티가 화면의 제일 전면에서 포커스를 받고 있지만 해당 이 상태에 있는 액티비티는 여전히 그 일부가 사용자에게 보여지고 있다. 즉, 다른 액티비티의 일부분이 투명하거나 화면 전체를 완전히 메울 정도로 크지 않아서 이 상태에 있는 액티비티의 일부분이 보여지고 있는 상태다. 일시 중지된 액티비티는 완전한 상태로 살아있다.(Activity 객체는 모든 상태와 멤버의 정보 데이터를 보존하며 메모리에 남아 윈도우 매니저에 속하게 된다.) 하지만 메모리가 극도로 부족해지면 시스템에 의해서 강제 종료될 수도 있다.


완전 정지 상태(Stopped)

액티비티가 다른 액티비티로 완전히 가리워진 상태다.(액티비티는 현재 "백그라운드"에 있는 상태다.) 정지된 액티비티는 아직 살아있다. (Activity 객체는 마지막 상태와 멤버들의 정보를 모두 유지한 채 메모리에 남아있다. 하지만 윈도우 매니저에는 속하지 않는다.) 하지만 사용자에게는 더 이상 보여지지 않고 있으며 다른 곳에서 메모리가 필요하게 되면 시스템은 액티비티를 강제종료 시킬 수도 있다.


액티비티가 일시 정지되거나 완전 정지되면 시스템은 액티비티에 종료를 요구하거나 (finish() 메서드를 호출한다.) 그냥 프로세스를 강제 종료시키게 된다. 액티비티가 다시 열리면 (그냥 종료되거나 강제 종료 된 후에) 모든 것을 다시 생성해야 한다.


라이프사이클 콜백함수 구현하기(Implementing the lifecycle callbacks)

액티비티가 위에서 언급한 상태들 중의 하나로 상태가 변경될 때에는 다양한 콜백 메서드를 통해 알림을 받게 된다. 다시 말해, 액티비티의 상태 변화가 일어날 때 적절한 작업을 수행할 수 있는 콜백 메서드가 제공된다. 아래 코드는 액티비티의 기본적인 라이프사이클 메서드에 대한 예제다.


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
26
27
28
29
30
31
32
public class ExampleActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // The activity is being created.
    }
    @Override
    protected void onStart() {
        super.onStart();
        // The activity is about to become visible.
    }
    @Override
    protected void onResume() {
        super.onResume();
        // The activity has become visible (it is now "resumed").
    }
    @Override
    protected void onPause() {
        super.onPause();
        // Another activity is taking focus (this activity is about to be "paused").
    }
    @Override
    protected void onStop() {
        super.onStop();
        // The activity is no longer visible (it is now "stopped")
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // The activity is about to be destroyed.
    }
}
 


Note : 위 예제처럼 라이프사이클 메서드를 구현할 때는 어떤 작업을 수행하기 전에 부모클래스의 메서드를 먼저 호출해야 한다.


이 메서드들은 액티비티의 모든 라이프사이클에 관련되어 있다. 이 메서드들을 구현하게 되면 아래와 같은 액티비티 라이프사이클의 루프를 모니터링 해 볼 수 있다.


액티비티의 전체 라이프타임(entire lifetime)onCreate() 호출 시점 이후부터 onDestroy() 호출 시점 이전까지를 말한다. 액티비티는 onCreate() 메서드에서 (레이아웃 정의와 같은) 모든 준비를 완료해야 하며 onDestroy() 메서드에서는 남아 있는 모든 리소스를 해제시켜야 한다. 만일 액티비티에 네트워크로부터 데이터를 다운로드 받는 쓰레드가 동작하도록 되어 있다고 하면 onCreate()에서 쓰레드를 생성하고 onDestroy()에서 정지시켜야 한다.


액티비티의 비저블 라이프타임(visible lifetime)onStart() 호출 시점 이후부터 onStop() 호출 시점 이전까지를 말한다. 이 시간동안 사용자는 액티비티를 볼 수 있으며 소통할 수 있다. 예를 들어, 새로운 액티비티가 시작되면 onStop()이 호출되고 해당 액티비티는 더 이상 보여지지 않게 된다. onStart()가 호출될 때부터 onStop()이 호출될 때까지 사용자에게 액티비티를 보여주기 위해 필요한 리소스를 유지시킬 수 있다. 예를 들어, UI에 영향을 줄 만한 변화를 감지하기 위해 onStart()에서 BroadcastReceiver를 등록하고 사용자에게 UI를 더 이상 보여줄 필요가 없어지면 onStop()에서 등록을 해지할 수 있다. 사용자가 액티비티를 "보았다 안보았다"를 반복할 수 있기 때문에 액티비티의 전체 라이프타임 동안 onStart()와 onStop()은 여러 번 반복해서 호출될 수도 있다.


액티비티의 포어그라운드 라이프타임(foreground lifetime)onResume() 호출 시점 이후부터 onPause() 호출 시점 이전까지를 말한다. 이 라이프타임에서는 액티비티가 다른 어떤 액티비티보다도 화면의 맨 앞에 위치하게 되며 사용자의 입력을 받을 수 있게 된다. 액티비티는 자주 이 상태에 진입하기도 하고 빠져나가기도 한다. 기기가 슬립모드로 전환되거나 다이얼로그가 앞에 나오는 상황이 되면 액티비티는 이 라이프타임에서 빠져나가게 되며 반대로 슬립모드가 해제되거나 다이얼로그가 사라지면 다시 이 라이프타임으로 진입하게 된다. 이 라이프타임은 다른 라이프타임에 비해 매우 빈번하게 상태 변화가 일어나기 때문에 프로그램이 느려지지 않도록 하려면 onResume()onPause()를 매우 간단한 작업만 하도록 구현해야한다.


[그림 1]은 위의 3가지 루프와 액티비티의 상태변화가 어떤 순서로 일어나는지에 대해서 설명하고 있다.



그림 1. 액티비티의 라이프사이클



아래 [표 1]에서는 위 그림에 표시된 콜백 메서드들에 대한 좀 더 상세한 설명과 각 콜백 메서드가 액티비티의 라이프사이클 루프 중 어디에 속하는지 그리고 호출된 후에 액티비티가 강제 종료될 가능성이 있는지에 대해서 설명한다.


표 1. 액티비티 라이프사이클 콜백 메서드 요약

메서드명

 설명

호출 후

강제종료?

음 메서드

onCreate()

액티비티가 처음 생성되면 호출된다. 이메서드에서 뷰 생성이나 변수의 초기화 같은 셋업작업이 이루어져야 한다. 만일 이전에 실행되었을 때 마지막 상태가 저장되어 있었다면 (뒤에 나오는 액티비티 상태 저장하기(Saving Activity State) 참고) 이 메서드에 액티비티의 이전 상태에 대한 정보를 갖고 있는 Bundle 객체가 파라메터로 넘어온다.

이 메서드 다음에는 항상 onStart()가 실행된다.

아니오

onStart()

 

onRestart()

액티비티가 완전 정지 되었다가 다시 시작되기 바로 전에 호출된다.

이 메서드 다음에는 항상 onStart()가 실행된다.

아니오

onStart()

 

onStart()

액티비티가 사용자에게 보여지기 직전에 호출된다. 액티비티가 전면에 보여질거라면 다음에 onResume()이 호출되며 숨겨질거라면 다음에 onStop()이 호출된다.

아니오

onResum()
또는
onStop()

 

 

onResume()

액티비티가 사용자와 소통을 시작하기 바로 전에 호출된다. 이 시점에서 액티비티는 액티비티 스택의 최정점에 위치하게 되며 사용자의 입력을 받게 된다.

다음에 항상 onPause()가 호출된다.

아니오

onPause()

 

 

onPause()

시스템이 다른 액티비티를 막 재개하려고 할 때 호출된다. 이 메서드는 저장되지 않은 정보를 저장하거나 애니메이션처럼 CPU를 사용하는 작업을 중지할 때 사용된다. 이 메서드가 완료되어야만 다음 액티비티가 재개될 수 있기 때문에 이 메서드에서는 무슨 작업이 되었건 빠르게 수행하고 리턴해야한다.

액티비티가 다시 재개되느냐, 중지되느냐에 따라 다음 실행될 메서드는 onResume()이 될 수도 있고 onStop()이 될 수도 있다.

onResum()

또는
onStop()

 

onStop()

액티비티가 사용자에게 더 이상 보여지지 않게 되면 호출된다. 이 메서드는 액티비티가 파괴될 때나 다른 액티비티에 의해서 완전히 가리워질 때 발생한다.

사용자가 다시 이 액티비티를 불러내게 되면 다음 실행될 메서드는 onRestart()가 호출되며 그대로 액티비티가 종료된다면 onDestroy()가 호출된다.

onRestart()
또는
onDestroy()

onDestroy()

액티비티가 파괴되기 전에 호출된다.

액티비티가 수행할 수 있는 마지막 메서드다. 액티비티가 종료되거나(어디선가 finish()가 호출되거나), 메모리 부족으로 시스템이 액티비티를 강제 종료할 때 호출된다. 이 두가지의 호출상황은 isFinishing() 메서드로 구분이 가능하다.

없음.


"호출 후 강제종료?"라고 된 열은 말 그대로 시스템이 액티비티를 순간 강제 종료시킬 가능성이 있는지를 나타낸다. 3개 메서드가 "예"라고 표시되어 있다(onPause(), onStop(), onDestroy()). onPause()는 액티비티가 생성된 이후 3개의 메서드 중에서 첫번째 메서드이기 때문에 프로세스가 강제 종료될 때에도 수행이 보장되는 마지막 메서드다. 만일 시스템이 긴급하게 메모리를 확보해야 한다면 나머지 2개의 메서드(onStop(), onDestroy())는 반드시 수행된다고 보장하기는 어렵다. 따라서 사용자가 수정했다던가 하는 중요한 데이터는 onPause()에서 저장해 두어야 한다. 그러나 onPause()에서 보관해야하는 데이터에 대해서는 어느 정도 선별이 필요하다. onPause()가 수행을 종료해야만 다음 액티비티로의 상태 전환이 이루어지기 때문에 과도한 작업을 수행하면 사용자 경험이 느려지게 된다.


"강제 종료" 항목이 "아니오"라고 표시된 메서드들은 수행 중 프로세스가 강제 종료 되지 않도록 보호가 된다. 그래서 액티비티는 onPause()가 리턴되고 onResume()이 호출되기 전까지 강제 종료가 가능한 상태가 되며 onPause()가 다시 호출되고 리턴될 때까지 강제 종료의 대상에서 제외된다.


Note : [표 1]에서 강제 종료 여부가 "아니오"라고 표시되어 있다고 하더라도 시스템에 의해서 강제 종료될 수 있다. 더 이상 시스템 자원이 없는 극한의 상태가 된다면 강제 종료될 수도 있다. 액티비티가 강제 종료되는 상황에 대해 더 많은 정보가 필요하다면 Processes and Threading 문서를 참조하기 바란다.


액티비티 상태 저장하기(Saving activity state)

액티비티 라이프사이클 관리하기(Managing the Acitivity Lifecycle)의 소개 부분에서 액티비티가 일시 정지(pause)되거나 완전 정지(stop)될 때 액티비티의 상태가 유지되는 것에 대해 간략히 언급하였다. 액티비티가 일시 정지되거나 완전 정지되어도 Activity 오브젝트는 여전히 메모리에 로드되어 있는 상태이기 때문에 모든 멤버의 정보나 마지막 상태 정보도 모두 유지된다. 그래서 액티비티가 다시 화면의 맨 앞으로 나오게 되면(resume 될 때) 모든 정보가 그대로 남아있게 된다.


그러나 시스템이 메모리를 확보하기 위해 액티비티를 파괴하게 되면 시스템이 다시 액티비티를 복원(resume)시켜 간단히 원래 상태로 돌아올 수가 없다. 대신에 사용자가 다시 그 액티비티로 돌아가려고 하면 시스템은 액티비티 오브젝트를 다시 생성시킨다. 하지만 사용자는 시스템이 액티비티를 파괴하고 재생성한 일은 알지 못하기 때문에 마지막에 보았던 상태 그대로 유지되어 있을 것으로 생각한다. 이런 상황에서는 onSaveInstanceState()라는 또다른 콜백 메서드를 구현하여 액티비티의 중요한 정보를 유지시킬 수 있다.


시스템은 액비티티가 파괴되기 전에 onSaveInstanceState()를 호출한다. 시스템은 이 메서드를 호출할 때 액티비티의 상태 정보를 저장할 수 있도록 Bundle이라는 것을 넘겨준다. BundleputString()putInt()와 같은 메서드(리치크루 : 이 이름의 메서드들 외에 다른 메서드들도 있다.)를 사용하여 이름과 값이 짝을 이루는 형태(name-value pair)로 정보를 저장할 수 있다. 이후에 시스템이 어플리케이션 프로세스를 파괴하고 사용자가 다시 되돌아오는 상황이 발생하면 시스템은 액티비티를 재생성하고 onCreate()onRestoreInstanceState()에 Bundle을 넘겨준다. 이 두 개의 메서드를 사용하면 onSaveInstanceState()에서 저장한 상태 정보를 Bundle에서 추출하여 액티비티의 상태를 복구할 수 있다. 만일 복구해야할 정보가 없다면 Bundlenull값으로 넘어온다(액티비티가 처음 생성될 때 이런 상황이 된다.).


그림 2. 온전한 상태로 사용자 포커스를 다시 얻게되는 두 가지 상황.
(1) 액티비티가 파괴되고 재생성될 때 이전에 저장된 상태를 복구하거나
(2) 액티비티가 정지되었다가 복원(resume)되면 상태가 온전하게 유지된다.


Note : onSaveInstanceState() 메서드는 액티비티가 파괴되기 전에 반드시 호출되는 것은 아니다. 상태 정보를 저장할 필요가 없는 경우도 있기 때문이다. 예를 들어, 사용자가 "뒤로"버튼을 눌러서 액티비티를 종료하는 것은 사용자에게 명백하게 종료 의도가 있는 것이기 때문에 onSaveInstanceState()가 호출되지 않는다. 시스템이 onSaveInstanceState()를 호출하는 시점은 onStop()전이나 onPause()전일 수 있다.


그러나 onSaveInstanceState() 메서드에서 아무런 작업을 하지 않거나 심지어 이 메서드를 구현하지 않아도 Activity 클래스에서 기본적으로 구현해 놓은 onSaveInstanceState()가 일부 액티비티의 상태를 복구해 준다. 구체적으로 설명하자면 레이아웃에 있는 View들이 저장할 필요가 있는 정보를 저장할 수 있도록 각 View의 onSaveInstanceState() 메서드를 호출해 준다. 안드로이드 프레임워크에 있는 거의 대부분의 위젯은 시각적으로 보여지는 내용의 변화는 재생성될 때 다시 복구할 수 있도록 자체적으로 적절한 메서드를 구현하고 있다. 예를 들어, EditText는 사용자가 입력한 텍스트를 저장하며 CheckBox는 체크된 상태를 저장한다. 각 위젯이 이렇게 자동으로 상태를 저장할 수 있도록 하기 위해 해야할 일은 각 위젯에 유니크ID를 부여하는 것이다.(android:id 속성 사용) 위젯이 ID를 갖지 않는다면 시스템은 위젯의 상태를 저장할 수가 없다.


레이아웃에 있는 뷰가 상태를 저장하지 않도록 하려면 android:saveEnabeld 속성을 "false"로 설정하거나 setSaveEnabled() 메서드를 호출하면된다. 일반적으로 액티비티의 상태를 다른 방법으로 복구할 게 아니라면 이 항목을 해제시켜서는 안된다.

이렇게 액티비티의 상태를 자동으로 저장하도록 기본 onSaveInstanceState() 메서드가 구현되어 있긴 하지만 다른 추가적인 정보를 저장하기 위해서 이 메서드를 오버라이드 해야 할 수도 있다. UI외에도 UI와 연결되어 있는 멤버 변수들은 자동으로 저장되고 복구되지 않기 때문에 직접 onSaveInstanceState() 메서드를 구현해 줘야 한다.


기본적으로 구현되어 있는 onSaveInstanceState() 메서드는 UI의 상태를 저장해주고 있기 때문에 추가정보를 저장하기 위해서 메서드를 오버라이딩하려면 다른 작업을 하기 전에 항상 부모클래스의 onSaveInstanceState()를 먼저 호출해야 한다. 같은 맥락으로 onRestoreInstanceState()를 구현할 때도 부모클래스의 메서드를 먼저 호출해야만 UI의 상태가 복구된다.


Note : onSaveInstanceState() 는 반드시 호출된다고 보장할 수 없기 때문에 액티비티의 일시적 데이터(UI의 상태)를 저장할 때만 사용해야 하며 영구적으로 저장해야 할 때는 사용해선 안된다. 대신 사용자가 액티비티를 떠날 때에 (데이터베이스에 저장하는 것과 같이) 영구적으로 보관해야 하는 데이터는 onPause()에서 저장해야만 한다.


여러분이 만든 앱이 상태를 얼마나 잘 복구하는지 확인해 보려면 그냥 기기를 돌려서 화면이 회전되도록 해보면 된다. 화면의 방향이 바뀌면(가로방향에서 세로방향으로 또는 세로방향에서 가로방향으로) 시스템은 다른 방향의 레이아웃에 맞는 대체 자원을 적용시키기 위해서 액티비티를 파괴하고 새로 생성한다. 사용자들은 앱을 사용하면서 화면을 회전시키는 경우가 빈번하기 때문에 이런 상황 하나만으로도 액티비티가 파괴되고 재생성될 때 UI의 상태를 저장하고 복구하는게 얼마나 중요한지 알 수 있다.


설정 변화 핸들링하기(Handling configuration changes)

기기의 설정 일부는 실행 도중에 변경될 수도 있다(화면 방향, 키보드 설정, 언어 등과 같은 설정들). 이런 것들이 변경되면 안드로이드는 실행 중인 액티비티를 재생성하게 된다(시스템은 onDestroy()를 호출하고 바로 onCreate()를 호출한다.). 이렇게 하는 이유는 프로그램 작성자가 제공한 리소스들을 변경된 설정에 맞게 자동으로 다시 적용시키기 위해서다(예를 들어, 가로화면과 세로화면에 맞는 레이아웃을 따로 만들어 두었다면 사용자가 기기를 회전시켰을 때 그에 맞는 레이아웃을 다시 적용시키는 것이다.).


만일 위헤서 설명한 대로 화면의 방향이 바뀌고 그에 따라 액티비티의 상태가 복구될 수 있도록 프로그램을 작성했다면 그 앱은 액티비티의 라이프사이클 도중에 예상치 못한 상황에도 좀 더 유연해지게 된다.


이렇게 재시작되는 상황을 잘 대처하기 위해서는 앞서 설명한대로 onSaveInstanceState()에서 액티비티의 상태를 저장하고 onRestoreInstanceState()(또는 onCreate())에서 복구되도록 하는 것이 최선의 방법이다.


실행 중에 발생하는 설정 변경과 그에 대한 처리 방법에 대해 좀 더 알고 싶다면 Handling Runtime Changes를 참고하기 바란다.


액티비티간 콜백 메서드의 수행 순서(Coordinating activities)

어떤 액티비티가 다른 액티비티를 구동시키면 두 액티비티는 모두 라이프사이클의 상태 변화가 생긴다. 처음 액티비티는 일시정지(pause)된 후에 완전정지(stop)되며 (물론 액티비티가 뒤쪽에서 조금이라도 보이게 되면 안전정지(stop)되지는 않는다.) 반면에 다른 액티비티는 생성(create)이 된다. 이런 경우 이 두 개의 액티비티가 디스크나 다른 곳에 데이터를 저장하여 서로 정보를 공유하는 방식을 사용한다면, 두 번째 액티비티가 생성되기 전에 첫번째 액티비티는 완전하게 정지되지 않는다는 사실을 반드시 알고 있어야 한다. 좀 더 정확히 얘기하면 두번째 액티비티를 생성하는 프로세스와 첫번째 액티비티를 정지하는 프로세스가 겹쳐져서 수행된다는 말이다.


라이프사이클 콜백 메서드의 순서, 특히 같은 프로세스에서 하나의 액티비티가 다른 액티비티를 구동할 때의 순서는 명확하게 정해져 있다. 다음은 액티비티A가 액티비티B를 구동시킬 때의 동작 순서를 나열한 것이다.


  1. 액티비티A의 onPause() 메서드가 실행된다.
  2. 액티비티B의 onCreate(), onStart(), onResume()이 순서대로 실행된다.(액티비티B가 사용자 포커스를 받게 된다.)
  3. 이제 액티비티A가 화면에서 더 이상 보이지 않게 되면 onStop() 메서드가 실행된다.


이러한 라이프사이클 콜백 메서드의 순서를 참고하면 하나의 액티비티에서 다른 액티비티로 정보가 옮겨지는 것을 문제없이 수행할 수 있다. 예를 들어, 다음 액티비티에서 읽어내야 할 데이터를 첫번째 액티비티가 데이터베이스에 써 놓아야 한다면 onStop() 대신에 onPause()에서 데이터베이스 쓰기 작업을 수행해야만 한다.

posted by 리치크루

API GUIDE/2. App Components
2014. 3. 4. 22:13

저작권 표시 : 

 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.

 

현재 보시는 페이지는 안드로이드 오픈 소스 프로젝트에 의해 작성되고 공유된 작업물의 수정/번역본이며 크리에이티브 커먼즈 저작자 표시 2.5 라이센스에 기술된 조건의 사용 근거를 따른 것입니다.

 

본 문서의 원본은 http://developer.android.com/guide/components/activities.html이며 안드로이드 4.4 Kitkat 기준으로 설명되었습니다.






액티비티 개요(Activities)


Activity는 전화 걸기, 사진 찍기, 이메일 보내기, 맵 보기등과 같이 사용자가 뭔가 하려고 할 때 앱과 상호작용 할 수 있도록 화면을 제공하는 앱 컴포넌트다. 각 액티비티에는 UI를 그려넣을 수 있는 윈도우가 주어진다. 윈도우는 일반적으로 화면을 가득 메우지만 화면보다 작아서 다른 위도우의 위에 떠 있도록 할 수도 있다.


어플리케이션은 보통 서로 관계가 느슨한 여러 개의 액티비티로 구성되어 있다. 일반적으로 하나의 액티비티가 "main"액티비티로 지정이 되고 앱이 실행될 때 제일 처음 사용자에게 보여진다. 그리고나서 각 액티비티는 상황에 따라 다른 동작을 수행할 액티비티를 시작시킨다. 새로운 액티비티가 시작될 때마다 먼저번의 액티비티는 일시정지(pause)되고 시스템은 그 정지(stop)된 액티비티를 그대로 스택(백 스택; back stack)에 쌓아놓는다. 그리고 새로운 액티비티가 시작되면 백 스택에 푸시되고 포커스를 갖게 된다. 백스택은 스택의 기본 개념인 "Last In, First Out" 메커니즘을 따르기 때문에 사용자가 현재 액티비티에서 작업을 마치고 "뒤로"버튼을 누르면 스택의 마지막 액티비티는 스택에서 꺼내어지고 (그리고 파괴된다.) 이전의 액티비티가 복원이 된다. (한마디로 스택의 마지막 액티비티가 화면의 최전면에 표시되고 포커스를 갖게 되며 스택에서 꺼내지면 소멸된다. 백 스택은 Tasks and Back Stack에서 좀 더 자세히 논의된다.)


새로운 액티비티가 시작되어 현재 액티비티가 중지될 때, 이 변화된 상태는 액티비티의 라이프사이클 콜백 메서드를 통해 알려지게 된다. 상태변화(생성될 때, 정지될 때, 다시 복원될 때, 파괴될 때)를 알려주는 액티비티 콜백 메서드는 몇 종류가 있는데 이 메서드들은 상태 변화시에 앱이 특정 작업을 할 수 있는 기회를 제공하게 된다. 예를 들어, 액티비티가 정지되면 네트워크나 데이터베이스 연결 오브젝트 같은 덩치가 큰 오브젝트를 해제할 수도 있다. 액티비티가 정상 가동되면 해당 자원을 다시 획득할 수 있고 중단됐던 작업을 재개할 수 있다. 이런 상태의 변화는 액티비티 라이프사이클 전반에 걸쳐 발생된다.


이 문서에서는 (액티비티의 라이프사이클이 어떻게 동작하는 지를 포함해서) 액티비티를 만들고 사용하는 법에 대해 논의한다. 그래서 여러분이 다양한 액티비티의 상태 변화를 적절히 다룰 수 있도록 한다.



액티비티 생성하기(Creating an Activity)



액티비티를 생성하기 위해서는 Activity 클래스(또는 이미 존재하는 Activity의 서브클래스)의 서브클래스를 생성해야 한다. 서브클래스에서는 액티비티가 생성되고, 정지, 복귀, 파괴되는 등의 다양한 상태변화를 알려주는 콜백 메서드를 구현해야 한다. 가장 중요한 콜백 메서드는 다음의 2개다.


onCreate()

이 메서드는 반드시 구현해야 한다. 시스템은 액티비티가 생성될 때 이 메서드를 호출한다. 기본적인 컴포넌트의 초기화를 이 메서드에서 구현한다. 이메서드를 구현할 때 가장 중요한 것은 액티비티의 UI를 위한 레이아웃을 반드시 setContentView()를 통해 정해줘야 한다.


onPause()

시스템은 사용자가 액티비티를 떠날 때 가정 먼저 이 메서드를 호출한다.(이 메서드가 호출되었다고는 해도 액티비티가 항상 파괴되는 것을 의미하지는 않는다.) 이 메서드는 사용자가 현재 세션에서 떠날 때 (사용자가 다시 돌아오지 않고 프로세스가 종료될 수 있기 때문에) 영구보존해야 하는 정보를 저장해야 하는 곳이다.


라이프사이클 콜백 함수는 여러 종류가 있다. 액티비티 간의 UI를 적절하게 제공하고 액티비티가 정지되거나 심지어는 파괴되는 갑작스런 상황에도 대응하기 위해서는 이 콜백 메서드들을 활용해야만 한다. 모든 라이프사이클 콜백 메서드는 뒤쪽 섹션(액티비티 라이프사이클 관리하기; Managing the Activity Lifecycle)에서 더 자세히 다룬다.


UI 구현하기(Implementing a user interface)

액티비티의 UI는 View 클래스로부터 파생된 뷰콘트롤들로 만들어진다. 각 뷰콘트롤들은 액티비티의 윈도우 안에서 일정 부분의 직사각형 영역을 차지하고 동작하며 사용자와 상호작용을 하도록 되어 있다. 예를 들어, 버튼 뷰는 사용자가 터치하면 특정 동작을 시작하게 된다.


안드로이드는 레이아웃을 디자인하고 꾸밀 수 있도록 많은 뷰콘트롤들을 제공한다. "위젯"은 버튼, 텍스트 필드, 체크 박스 또는 이미지 같이 사용자에게 보여지고 입력을 처리하는 뷰콘트롤들이다. "레이아웃"은 ViewGroup을 상속받은 뷰이며 linear 레이아웃, grid 레이아웃, relative 레이아웃과 같이 각각 다른 레이아웃 모델을 제공한다. ViewViewGroup 클래스를 상속받거나 또는 상속받아 만들어진 기존의 클래스를 다시 상속받아 나만의 새로운 위젯과 레이아웃을 만들어서 액티비티 레이아웃에 사용할 수도 있다.


뷰를 사용하여 레이아웃을 만드는 가장 일반적인 방법은 앱리소스에 저장된 XML 레이아웃 파일을 이용하는 것이다. 이 방법을 사용하면 디자인과 소스코드를 분리하여 관리할 수 있다. 이렇게 만들어진 레이아웃은 레이아웃의 리소스ID를 setContentView()에 넘겨 액티비티의 UI로 사용할 수 있다. 그러나 액티비티의 코드안에서 새로운 View를 만들고 ViewGroup에 집어넣을 수도 있다. 그런 후에 최상위 ViewGroupsetContentView() 메서드에 넘겨 사용할 수 있다.


UI를 만드는 법은 User Interface 문서를 참고하기 바란다.


매니페스트에 액티비티 선언하기(Declaring the activity in the manifest)

만들어둔 액티비티가 시스템에서 액세스가 가능하도록 하려면 manifest 파일에 반드시 선언을 해 주어야 한다. 액티비티를 선언하려면 아래 예제처럼 <application> 엘리먼트의 내부에 <activity> 엘리먼트로 선언한다.


1
2
3
4
5
6
7
<manifest ... >
  <application ... >
      <activity android:name=".ExampleActivity" />
      ...
  </application ... >
  ...
</manifest >
 


이 엘리먼트에는 액티비티의 레이블, 아이콘, 테마 스타일 같은 속성을 정의하기 위한 몇몇 속성들이 있다. android:name 속성은 액티비티의 클래스명만 입력해 주면 된다. 일단 앱을 게시하고나면 앱의 숏컷과 같은 기능이 제대로 동작하지 않는 문제가 발생하기 때문에 이 이름은 바꿔줄 수가 없다. (이 문제에 대한 내용은 블로그의 Things That Cannot Change 포스트를 읽어보기 바란다.)


매니페스트에 액티비티를 선언하는 법에 대해 좀 더 자세히 알고 싶으면 <activity> 엘리먼트의 도움말을 참고하기 바란다.


인텐트 필터 사용하기(Using intent filters)

다른 앱에서 액티비티를 활성화시킬 수 있도록 하기 위해서 - <intent-filter> 엘리먼트를 사용하여 - 다양한 인텐트 필터를 <activity> 엘리먼트의 아래에 둘 수 있다.


안드로이드 SDK 툴을 사용하여 새로운 앱을 생성하면 텅빈 액티비티가 자동으로 생성된다. 이 액티비티는 "main" 액션과 "launcher" 카테고리를 나타내는 인텐트 필터가 자동으로 추가되어 있다. 인텐트 필터를 다음과 같이 생성된다.


1
2
3
4
5
6
<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
 


위 예제에서는 <action> 엘리먼트는 이 액티비티가 어플리케이션의 "주"진입점임을 선언하고 있으면 <category> 엘리먼트는 이 액티비티가 시스템의 앱 런처(사용자가 액티비티를 시작할 수 있도록 하는) 목록에 표시되도록 선언하고 있다.


만일 다른 앱에서 액티비티를 활성화시키는 것을 막고자 한다면 다른 인텐트 필터는 필요없다. 위 예제처럼 오직 하나의 액티비티만 "main" 액션과 "launcher" 카테고리를 선언하면 된다. 다른 앱에서 사용하지 못하도록 만든 다른 액티비티들은 인텐트 필터를 가져서는 안되며 명시적인 인텐트를 통해서 시작시킨다. (이 내용은 다음 섹션에서 설명된다.)


하지만 액티비티가 암시적 인텐트에 반응하도록 하려면 (어떤 어플리케이션에서 발생된 암시적 인텐트이건 관계없이) 액티비티에 적절한 인텐트 필터를 정의해 주어야 한다. 응답하고자 하는 각 종류별 인텐트에 대해서 <intent-filter>를 사용하여 인텐트 필터를 정의해 준다. <intent-filter>에는 필히 <action> 엘리먼트가 포함되어야 하며 액션에 따라 <category>, <data> 엘리먼트가 포함되어야 할 수도 있다. 이 엘리먼트들의 조합에 따라 액티비티가 응답하고자 하는 인텐트를 정의할 수 있다.


어떻게 하면 인텐트에 액티비티가 응답하도록 할 수 있는지에 대해 더 많은 정보를 얻고 싶다면 인텐트와 인텐트 필터(Intents and Intent Filters) 문서를 참고하기 바란다.



액티비티 구동하기(Starting an Activity)



다른 액티비티를 실행시키려면 실행시킬 액티비티에 대한 설명이 담긴 Intent를 파라메터로 하여 startActivity() 메서드를 호출한다. 인텐트는 실행시키려고 하는 정확한 액티비티를 지정하고 수행하고자 하는 액션에 대해 설명을 담고 있다. (시스템은 이 인텐트의 정보를 바탕으로 최적의 액티비티 - 다른 어플리케이션에 속한 액티비티라고 하더라도 - 찾아준다.) 인텐트는 실행된 액티비티가 액션을 수행하는 데 사용할 수 있는 데이터를 전달해 줄 수도 있다.


동일 어플리케이션의 액티비티라면 이미 존재하는 액티비티를 간단하게 실행시켜야 할 때도 있다. 이것은 실행시키고자 하는 액티비티의 클래스명을 가지고 명시적 인텐트를 생성함으로써 가능하다. 아래의 예제는 하나의 액티비티가 SignInActivity라는 이름을 가진 다른 액티비티를 실행시키는 방법에 대한 것이다.


1
2
Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);
 


그러나 여러분이 만든 액티비티의 데이터를 사용해서 이메일 또는 문자메시지를 보내거나 상태 업데이트 같은 액션을 수행해야 할 수도 있다. 이런 경우 여러분의 어플리케이션에 해당 액션을 수행할 수 있는 기능이 없으면 그 액션이 수행 가능한 다른 어플리케이션의 액티비티를 통해서 수행해야 한다. 이럴 때 인텐트가 아주 유용하다. 수행하고자 하는 액션에 대한 내용을 인텐트에 담아 생성하면 시스템은 다른 어플리케이션에서 적절한 액티비티를 찾아 실행시켜준다. 만일 해당 인텐트를 처리할 수 있는 액티비티가 여러 개 존재한다면 사용자는 그 중에서 하나를 골라 실행시킬 수 있다. 다음은 사용자가 이메일 메시지를 보내려고 할 때 인텐트를 생성하는 예제 코드다.


1
2
3
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
 


EXTRA_EMAIL 부가정보키와 함께 이메일을 받는 수신자들의 이메일 주소를 문자열 배열로 만들어 인텐트에 추가한다. 이메일 어플리케이션이 이 인텐트를 받게 되면 부가정보에 있는 문자열의 배열을 읽어 이메일 작성폼의 "수신자" 항목에 입력시킨다. 이 때, 이메일 어플리케이션의 액티비티가 실행되고 사용자가 일을 마치면 여러분의 액티비티로 다시 복귀하게 된다.


액티비티 구동시키고 결과 받기(Starting an activity for a result)

경우에 따라서는 여러분이 실행시킨 액티비티로부터 어떤 결과를 돌려받아야 할 때가 있다. 이럴 때는 startActivity() 메서드 대신에 startActivityForResult() 메서드를 호출한다. 실행시킨 액티비티로부터 결과를 돌려받기 위해서는 onActivityResult() 콜백 메서드를 구현해 주어야 한다. 실행시킨 액티비티의 일이 완료되면 결과를 Intent에 담아 onActivityResult() 메서드를 통해 넘겨준다.


아래 코드는 사용자로 하여금 연락처 중에 하나를 선택하도록 하고 선택된 연락처 정보를 사용해서 어떤 작업을 수행하도록 하는 예제다. 이 예제는 인텐트를 생성하고 그 결과를 처리하는 법을 보여준다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void pickContact() {
    // Create an intent to "pick" a contact, as defined by the content provider URI
    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
    startActivityForResult(intent, PICK_CONTACT_REQUEST);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
        // Perform a query to the contact's content provider for the contact's name
        Cursor cursor = getContentResolver().query(data.getData(),
        new String[] {Contacts.DISPLAY_NAME}, nullnullnull);
        if (cursor.moveToFirst()) { // True if the cursor is not empty
            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
            String name = cursor.getString(columnIndex);
            // Do something with the selected contact's name...
        }
    }
}
 


이 예제는 액티비티로 되돌려지는 결과를 활용하기 위해 사용해야 하는 onActivityResult() 메서드에 대한 기본 로직을 보여주고 있다. 첫번째 if문은 조건 검사로써 요청에 대한 작업이 성공이었는지 - 성공이었다면 resultCodeRESULT_OK일 것이다. - 그리고 요청했던 작업에 대한 결과로 호출된 것이 맞는 지 - 예제에서는 requestCode의 값이 startActivityForResult()를 호출할 때 넘겨 준 요청코드와 맞는지를 확인하고 있다. 조건 검사가 끝나면 반환된 Intent(data 파라메터)의 데이터를 쿼리하여 액티비티의 결과를 처리한다.


데이터는 ContentResolver(예제에서는 getContentResolver() 메서드로 획득)로 컨텐트 프로바이더에 쿼리하여 Cursor를 획득하고 이 Cursor를 이용해 읽고자 하는 데이터를 다시 쿼리한다. 더 많은 정보는 Content Providers 문서를 참고하기 바란다.


이 섹션의 내용에 대해 더 자세히 알고 싶다면 인텐트와 인텐트 필터(Intents and Intent Filters) 문서를 참고하기 바란다.



액티비티 닫기(Shutting Down an Activity)



액티비티는 finish() 메서드를 호출하여 닫을 수 있다. 또한 finishActivity() 메서드를 사용하면 앞서 구동시킨 다른 액티비티도 닫을 수 있다.


Note : 대부분의 경우, 이런 메서드들을 직접 호출하여 액티비티를 종료하지 말아야 한다. 액티비티 라이프사이클에 대해 설명하고 있는 다음 섹션의 내용과 같이 안드로이드 시스템이 액티비티의 수명을 관리하고 있기 때문에 액티비티를 직접 종료시킬 필요가 없다. 이 메서드들은 사용자가 예상하지 못한 상황에 맞닥뜨리게 되는 부정적인 상황을 초래하기 때문에 이 액티비티에 사용자가 머물러서는 안된다는 확실한 이유가 있을 때만 직접 호출해야 한다.


posted by 리치크루

API GUIDE/2. App Components
2014. 3. 3. 08:33


 

 

안드로이드 앱 독학으로 개발하기 - 일반적인 인텐트(Common Intents) (2)


안드로이드 개발자 사이트에 있는 글 중에 현재까지 1/10도 번역하지 못했지만 지금까지의 글만 보면 안드로이드는 인텐트 없이 아무것도 할 수 것이 아닌가 할 정도록 인텐트를 반복적으로 설명하고 있습니다. 물론, 액티비티, 서비스 등등 다른 중요 키워드들도 많았지만 인텐트만큼은 아니었지요.


이렇게 강조하고 있다는 것은 당연히 매우 중요하다는 뜻이겠죠. 그런 인텐트를 전부 다 외운다는 건 어렵겠지만 이 글에 나오는 것들만 알고 있으면 앱 개발에 있어 큰 문제는 없을 것 같습니다.


이제 일반적인 인텐트의 종류가 얼마남지 않았습니다. 자 그럼 끝까지 다 읽어서 인텐트를 확실하게 알고 개발해 보도록 합시다.


무슨 일이든 기초가 중요합니다. 그러니 지금 이 순간 열심히 공부해서 멋진 안드로이드 앱을 개발해 보자구요. 언제나 그렇듯 오역, 오타, 깨진 링크가 있으면 제보 부탁드립니다.


 

저작권 표시 : 

 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.

 

현재 보시는 페이지는 안드로이드 오픈 소스 프로젝트에 의해 작성되고 공유된 작업물의 수정/번역본이며 크리에이티브 커먼즈 저작자 표시 2.5 라이센스에 기술된 조건의 사용 근거를 따른 것입니다.

 

본 문서의 원본은 http://developer.android.com/guide/components/intents-common.html이며 안드로이드 4.4 Kitkat 기준으로 설명되었습니다.


일반적인 인텐트(Common Intents)



파일 저장소(File Storage)



지정한 타입의 파일 획득하기(Retrieve a specific type of file)

사용자가 문서나 사진같은 파일을 선택하고 그 파일을 앱에서 참조할 수 있도록 요청하려면 ACTION_GET_CONTENT 액션을 사용하고 원하는 MIME 타입을 지정한다. 앱으로 반환된 파일의 참조는 현재의 액티비티가 소멸 될 때 까지만 일시적으로 사용가능한 것이기 때문에 나중에 다시 읽을 수 있도록 하려면 파일의 복사본을 만들어 두어야 한다. 이 인텐트는 사용자가 새로운 파일을 만드는 것도 가능하도록 해준다. 예를 들어, 저장된 사진을 선택하도록 하는 대신에 카메라로 새로운 사진을 촬영하도록 할 수도 있다.


결과 Intent는 파일을 가리키는 URI를 포함한 채 onActivityResult() 콜백 메서드로 전달된다. URI는 http: URI, file: URI, content: URI 중 어떤 것이라도 될 수 있다. 하지만 컨텐트 프로바이더로 접근 가능하고 (content: URI) openFileDescriptor() 메서드로 파일 스트림을 만들 수 있는 파일만 선택가능ㅎ도록 제한하고 싶다면 인텐트에 CATEGORY_OPENABLE 카테고리를 추가해 준다.


안드로이드 4.3(API 레벨 18)이상에서는 인텐트에 EXTRA_ALLOW_MULTIPLE을 추가하고 true로 설정하면 여러 개의 파일을 선택하도록 할 수도 있다. 그런 후에 getClipData() 메서드로 ClipData 오브젝트를 구하면 선택된 파일들을 각각 액세스 할 수 있다.


액션(Action)

ACTION_GET_CONTENT


데이터 URI 스키마(Data URI Scheme)

필요없음


MIME Type

사용자가 선택할 파일의 타입에 상응하는 MIME 타입



부가정보(Extras; 선택사항)

EXTRA_ALLOW_MULTIPLE

사용자가 동시에 여러 개의 파일을 선택할 수 있는지 여부를 나타내는 boolean(true or false) 값

EXTRA_LOCAL_ONLY

외부 서비스로부터 다운로드 받는 파일이 아니라 기기내에 존재하는 파일만 선택하도록 할 지 여부를 나타내는 boolean(true or false) 값


카테고리(Category; 선택사항)

CATEGORY_OPENABLE

openFileDescriptor()로 열어서 파일 스트림을 구할 수 있는 "열 수 있는" 파일만 선택하도록 하기 위한 값.


사진 파일을 얻는 인텐트 예제 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static final int REQUEST_IMAGE_GET = 1;

public void selectImage() {
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(intent, REQUEST_IMAGE_GET);
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_GET && resultCode == RESULT_OK) {
        Bitmap thumbnail = data.getParcelable("data");
        Uri fullPhotoUri = data.getData();
        // Do work with photo saved at fullPhotoUri
        ...
    }
}


사진 파일을 반환하는 인텐트 필터 예제 :

1
2
3
4
5
6
7
8
9
10
11
<activity ...>
    <intent-filter>
        <action android:name="android.intent.action.GET_CONTENT" />
        <data android:type="image/*" />
        <category android:name="android.intent.category.DEFAULT" />
        <!-- The OPENABLE category declares that the returned file is accessible
             from a content provider that supports OpenableColumns
             and ContentResolver.openFileDescriptor() -->

        <category android:name="android.intent.category.OPENABLE" />
    </intent-filter>
</activity>

특정 타입의 파일 열기(Open a specific type of file)

안드로이드 4.4 이상에서는 앱에서 사용할 파일의 복사본을 만드는 대신에 (ACTION_GET_CONTENT 액션을 사용할 때) ACTION_OPEN_DOCUMENT 액션과 MIME 타입을 지정하여 다른 앱에서 관리중인 파일을 열도록 요청할 수 있다. 또한 ACTION_CREATE_DOCUMENT를 사용하면 여러분의 앱으로 쓰기가 가능한 새로운 문서를 만들도록 할 수도 있다. 예를 들어, 이미 저장되어 있는 PDF문서를 선택하도록 하는 대신에 ACTION_CREATE_DOCUMENT를 사용해서 새로운 문서가 만들어질 위치를 사용자가 선택하도록 할 수 있다.(그 위치는 다른 앱에서 관리하는 문서 저장공간이다.) 그러면 여러분의 앱은 새로운 문서를 작성할 수 있는 위치의 URI를 획득할 수 있다.


ACTION_GET_CONTENT 액션을 사용하면 onActivityResult()를 통해 해당 파일의 URI를 돌려준다. 반면에 ACTION_OPEN_DOCUMENT, ACTION_CREATE_DOCUMENT를 사용하면 항상 DocumentsProvider를 통해 선택된 파일에 대한 content: URI를 되돌려준다. 그러면 openFileDescriptor()를 사용해서 파일을 열 수 있고 DocumentsContract.Document의 컬럼을 사용해 상세한 내용을 쿼리해 볼 수 있다.


반환된(획득된) URI는 앱이 동작하는 동안에는 파일을 읽고 쓸 수가 있다. 그래서 ACTION_OPEN_DOCUMENT를 사용하면 파일의 복사본을 만들어 두어야 하는 ACTION_GET_CONTENT와는 달리 아무때나 파일을 열고 수정할 수 있기 때문에 상당히 유용하다.


인텐트에 EXTRA_ALLOW_MULTIPLE을 추가하고 true로 설정하면 사용자가 여러 개의 파일을 선택하도록 할 수 있다. 사용자가 하나의 아이템만 선택하면 getData() 메서드로 그 아이템을 획득할 수 있다. 사용자가 여러 개의 아이템을 선택하면 getData()는 null을 리턴하고 대신에 getClipData()로 부터 반환되는 ClipData 오브젝트를 통해 각 아이템을 획득할 수 있다.


Note : ACTION_OPEN_DOCUMENTACTION_CREATE_DOCUMENT를 사용하려면 MIME 타입을 반드시 설정해야 하며 CATEGORY_OPENABLE 카테고리를 반드시 선언해야 한다. 필요하다면 부가정보키 EXTRA_MIME_TYPES를 사용하여 MIME 타입의 배열을 추가하면 하나 이상의 MIME 타입을 설정할 수도 있다. 단, 이 방법을 사용할 때는 setType() 메서드를 사용하여 메인 MIME 타입에 "*/*"를 세팅해 준다.


액션(Action)

ACTION_OPEN_DOCUMENT 또는

ACTION_CREATE_DOCUMENT


데이터 URI 스키마(Data URI Scheme)

필요없음


MIME Type

사용자가 선택해야 하는 파일타입에 상응하는 MIME 타입


부가정보(Extras; 선택사항)

EXTRA_MIME_TYPES

앱이 요구하는 파일 타입들에 대한 MIME 타입의 배열. 부가정보에 이 키 값을 사용하면 메인 MIME 타입은 setType() 메서드를 통하여 반드시 "*/*"로 세팅해 주어야 한다.

EXTRA_ALLOW_MULTIPLE

사용자가 동시에 여러 개의 파일을 선택할 수 있는지 여부를 나타내는 boolean(true or false) 값

EXTRA_TITLE

ACTION_CREATE_DOCUMENT 액션을 사용할 때 파일 이름을 지정한다.

EXTRA_LOCAL_ONLY

외부 서비스로부터 다운로드 받는 파일이 아니라 기기내에 존재하는 파일만 선택하도록 할 지 여부를 나타내는 boolean(true or false) 값


카테고리(Category)

CATEGORY_OPENABLE

openFileDescriptor()로 열어서 파일 스트림을 구할 수 있는 "열 수 있는" 파일만 선택하도록 하기 위한 값.


사진 파일을 얻는 인텐트 예제 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static final int REQUEST_IMAGE_OPEN = 1;

public void selectImage() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.setType("image/*");
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    // Only the system receives the ACTION_OPEN_DOCUMENT, so no need to test.
    startActivityForResult(intent, REQUEST_IMAGE_OPEN);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_OPEN && resultCode == RESULT_OK) {
        Uri fullPhotoUri = data.getData();
        // Do work with full size photo saved at fullPhotoUri
        ...
    }
}


사실 서드파티 앱은 ACTION_OPEN_DOCUMENT 액션이 담긴 인텐트를 처리할 수가 없다. 대신 시스템이 받아서 통합 UI를 통해 다양한 앱으로부터 사용가능한 모든 파일을 디스플레이 해 준다.


여러분의 앱에 있는 파일들을 이 UI에 표시되고 다른 앱에서 열어볼 수 있도록 하려면 DocumentsProvider를 구현해야 하며 인텐트 필터에 PROVIDER_INTERFACE("android.content.action.DOCUMENTS_PROVIDER")가 포함되어 있어야 한다. 다음은 그 예제다.


1
2
3
4
5
6
7
8
<provider ...
    android:grantUriPermissions="true"
    android:exported="true"
    android:permission="android.permission.MANAGE_DOCUMENTS">

    <intent-filter>
        <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
    </intent-filter>
</provider>


다른 앱에 있는 파일을 만들고 사용하는 법에 대해 더 알고 싶다면 Stroage Access Framework 가이드를 참고하기 바란다.



지도(Maps)



지도 상에 위치 표시하기(Show a location on a map)

지도를 열어보려면 ACTION_VIEW 액션을 사용하고 아래에 정의된 스키마 중의 하나로 인텐트에 위치정보를 지정해 준다.


액션(Action)

ACTION_VIEW


데이터 URI 스키마(Data URI Scheme)

geo:latitude,longitude

주어진 위도와 경도로 맵에 위치를 표시한다.

예 : "geo:47.6,-122.3"


geo:latitude,longitude?z=zoom

주어진 위도와 경도를 정해진 줌 레벨로 지도에 표시한다. 줌 레벨1은 지구 전체를 보여주며 주어진 위도와 경도가 화면 중앙에 위치된다. 가장 높은(확대된) 줌 레벨은 23이다.

예 : "geo:47.6,-122.3?z=11"


geo:0,0?q=lat,lng(label)

주어진 위도와 경도를 라벨과 함께 표시한다.

예 : "geo:0,0?q=34.99,-106.61(Treasure)"


geo:0,0?q=my+street+address

"my street address"의 위치를 표시한다.(특정 주소나 위치 찾기 가능한 주소)

예 : "geo:0,0?q=1600+amphitheatre+parkway+ca"


MIME Type

필요없음


인텐트 예제 :

1
2
3
4
5
6
7
public void showMap(Uri geoLocation) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(geoLocation);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}


인텐트 필터 예제 :

1
2
3
4
5
6
7
<activity ...>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="geo" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>



음악 / 동영상(Music or Video)



음악 파일 재생하기

음악 파일을 재생하려면 ACTION_VIEW 액션을 사용하고 인텐트 데이터에 파일을 가리키는 URI 위치를 지정한다.


액션(Action)

ACTION_VIEW


데이터 URI 스키마(Data URI Scheme)

file:<URI>

content:<URI>

http:<URL>


MIME Type

"audio/*"

"application/ogg"

"application/x-ogg"

"application/itunes"

또는 앱이 필요로 하는 다른 타입


인텐트 예제 :

1
2
3
4
5
6
7
public void playMedia(Uri file) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(file);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}


인텐트 필터 예제 :

1
2
3
4
5
6
7
8
<activity ...>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <data android:type="audio/*" />
        <data android:type="application/ogg" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>



전화(Phone)



전화 걸기(Initiate a phone call)

전화 앱을 열고 전화번호가 입력되도록 하려면 ACTION_DIAL 액션을 사용하고 아래 정의된 URI 스키마를 사용하여 전화번호를 지정한다. 전화 앱이 실행되면 전화번호가 입력된 상태가 되지만 반드시 사용자가 "통화"버튼을 눌러야만 전화가 걸린다.


액션(Action)

ACTION_DIAL


데이터 URI 스키마(Data URI Scheme)

tel:<phone-number>


MIME Type

필요없음


유효한 전화번호는 the IETF RFC 3966에 정의되어 있다. 유효한 전화번호의 예는 다음과 같다.

  • tel:2125551212
  • tel:(212) 555 1212


전화 다이얼러는 전화번호 같은 일반적인 스키마에서 유연하게 잘 동작한다. 그래서 스킴의 내용이 규칙에 정확히 맞지 않아도 Uri.parse() 메서드를 사용하는데는 큰 문제가 없다. 하지만 사용해 본 적이 없고 잘 동작할 지 확실하지 않다면 Uri.fromParts() 메서드를 대신 사용하도록 한다.


인텐트 예제 :

1
2
3
4
5
6
7
public void dialPhoneNumber(String phoneNumber) {
    Intent intent = new Intent(Intent.ACTION_DIAL);
    intent.setData(Uri.parse("tel:" + phoneNumber));
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}



환경 설정(Settings)



환경 설정의 특정 부분 열기(Open a specific section of Settings)

사용자가 시스템 설정의 일부분을 바꿀 수 있도록 특정 환경 설정 창을 열고 싶을 때는 다음의 액션 중 하나를 액션으로 사용한다.


액션(Action)

ACTION_SETTINGS

ACTION_WIRELESS_SETTINGS

ACTION_AIRPLANE_MODE_SETTINGS

ACTION_WIFI_SETTINGS

ACTION_APN_SETTINGS

ACTION_BLUETOOTH_SETTINGS

ACTION_DATE_SETTINGS

ACTION_LOCALE_SETTINGS

ACTION_INPUT_METHOD_SETTINGS

ACTION_DISPLAY_SETTINGS

ACTION_SECURITY_SETTINGS

ACTION_LOCATION_SOURCE_SETTINGS

ACTION_INTERNAL_STORAGE_SETTINGS

ACTION_MEMORY_CARD_SETTINGS


이 외에 또다른 설정 화면에 대한 정보는 Settings 화면을 참조하기 바란다.


데이터 URI 스키마(Data URI Scheme)

필요없음


MIME Type

필요없음


인텐트 예제 :

1
2
3
4
5
6
public void openWifiSettings() {
    Intent intent = new Intent(Intent.ACTION_WIFI_SETTINGS);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}



문자 메세지(Text Messaging)



첨부와 함께 SMS/MMS 메시지 보내기(Compose an SMS/MMS message with attachment)

SMS 또는 MMS 문자 메시지를 보내려면 다음의 인텐트 액션을 사용하고 전화번호, 제목, 내용 등과 같은 메시지의 세부 내용은 다음 목록에 있는 부가정보키를 사용하여 지정한다.


액션(Action)

ACTION_SENDTO 또는

ACTION_SEND 또는

ACTION_SEND_MULTIPLE


데이터 URI 스키마(Data URI Scheme)

sms:<phone_number>

smsto:<phone_number>

mms:<phone_number>

mmsto:<phone_number>


이 스키마들은 모두 동일하게 취급된다.


MIME Type

PLAIN_TEXT_TYPE ("text/plain")

"image/*"

"video/*"


부가정보(Extras; 선택사항)

"subject"

메시지의 제목을 위한 문자열(보통 MMS에만 필요하다.)

"sms_body"

메시지 본문을 위한 문자열

EXTRA_STREAM

첨부하고자 하는 사진이나 동영상을 가리키는 Uri. 만일 ACTION_SEND_MULTIPLE 액션을 사용한다면 첨부하려는 사진들이나 동영상들의 Uri로 이루어진 ArrayList 값이 되어야 한다.


인텐트 예제 :

1
2
3
4
5
6
7
8
9
public void composeMmsMessage(String message, Uri attachment) {
    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setType(HTTP.PLAIN_TEXT_TYPE);
    intent.putExtra("sms_body", message);
    intent.putExtra(Intent.EXTRA_STREAM, attachment);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}


이 인텐트가 이메일 앱이나 소셜 앱이 아닌 문자메시지 앱으로 처리되도록 하고 싶다면 ACTION_SENDTO 액션을 사용하고 데이터 스키마에 "smsto:"를 포함시키도록 한다. 다음은 그 예제다.


1
2
3
4
5
6
7
8
9
public void composeMmsMessage(String message, Uri attachment) {
    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setData(Uri.parse("smsto:"));  // This ensures only SMS apps respond
    intent.putExtra("sms_body", message);
    intent.putExtra(Intent.EXTRA_STREAM, attachment);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}


인텐트 필터 예제 :

1
2
3
4
5
6
7
8
<activity ...>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <data android:type="text/plain" />
        <data android:type="image/*" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>


Note : 안드로이드 4.4버전 이상에서 기본 SMS 앱으로 사용가능한 SMS/MMS 메세지 앱을 개발 중이라면 몇몇 추가적인 액션을 인텐트 필터에 추가로 구현해야 한다. 더 자세한 정보는 Telephony 문서를 참조하기 바란다.



웹 브라우저(Web Browser)



웹 URL 열기(Load a web URL)

웹 피이지를 열려면 ACTION_VIEW 액션을 사용하고 인텐트 데이터에 웹 URL을 지정한다.


액션(Action)

ACTION_VIEW


데이터 URI 스키마(Data URI Scheme)

http:<URL>

https:<URL>

이 스키마들은 모두 동일하게 취급된다.


MIME Type

PLAIN_TEXT_TYPE ("text/plain")

"text/html"

"application/xhtml+xml"

"application/vnd.wap.xhtml+xml"


인텐트 예제 :

1
2
3
4
5
6
7
public void openWebPage(String url) {
    Uri webpage = Uri.parse(url);
    Intent intent = new Intent(Intent.ACTION_VIEW, webpage);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}


인텐트 필터 예제 :

1
2
3
4
5
6
7
8
9
10
11
<activity ...>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <!-- Include the host attribute if you want your app to respond
             only to URLs with your app's domain. -->

        <data android:scheme="http" android:host="www.example.com" />
        <category android:name="android.intent.category.DEFAULT" />
        <!-- The BROWSABLE category is required to get links from web pages. -->
        <category android:name="android.intent.category.BROWSABLE" />
    </intent-filter>
</activity>


Tip : 여러분의 앱이 웹 사이트와 비슷한 기능을 제공한다면 그 웹사이트를 가리키는 URL을 포함한 인텐트 필터를 선언해야 한다. 그러면 여러분의 앱을 설치한 사용자가 여러분의 웹 사이트를 가리키는 이메일이나 다른 웹 사이트의 링크를 누르게 되면 웹 페이지 대신에 여러분의 앱이 실행된다.


웹 검색하기(Perform a web search)

웹 검색을 하려면 ACTION_WEB_SEARCH 액션을 사용하고 SearchManager.QUERY 부가정보키에 검색할 문자열을 지정해 준다.


액션(Action)

ACTION_WEB_SEARCH


데이터 URI 스키마(Data URI Scheme)

필요없음


MIME Type

필요없음


부가정보(Extras)

SearchManager.QUERY

검색할 문자열


인텐트 예제 :

1
2
3
4
5
6
7
public void searchWeb(String query) {
    Intent intent = new Intent(Intent.ACTION_SEARCH);
    intent.putExtra(SearchManager.QUERY, query);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}


이번 글은 여기까지 입니다.


그럼 다음 글에서 뵙겠습니다.


다음 글 >> 

이전 글 >> 일반적인 인텐트(Common Intents) (1)


posted by 리치크루

API GUIDE/2. App Components
2014. 2. 28. 07:34


 


안드로이드 앱 독학으로 개발하기 - 일반적인 인텐트(Common Intents) (1)


지금까지 앞서 진행되어 왔던 글들을 보면 항상 "인텐트"를 연발해 왔습니다.


수도 없이 들어온 탓에 인텐트라는 것이 뭘 하는 건지는 대충 알겠는데 막상 사용법은 어떻게 되고 또 어떤 것들이 있는지는 확실히 알지 못했습니다.


이번 글은 그런 궁금증을 어느정도 해결해 주는 글이 아닐까합니다. 안드로이드 개발자 사이트에 있는 도움말이 개발자들을 참 세심하게 배려했다는 생각을 갖게 되는 글입니다.


물론 안드로이드 API에 존재하는 인텐트가 이 글에 나오는 것들이 전부는 아닙니다. 하지만 많이 사용되는 일부를 추려서 설명한 만큼 이 글을 독파하고 나면 다른 인텐트들도 어떻게 사용해야할 지 쉽게 접근할 수 있으리라 생각합니다.


이후 제 블로그에서도 지금의 번역글을 기반으로 하여 개발하는 과정을 다뤄볼까 생각중입니다.(물론, 지금은 실력이 안되서 곤란하지만 ^^;) 그 때까지 기초를 다진다고 생각하시고 읽어주시기 바랍니다.


그럼 공부 열심히 하시고 언제나 그렇듯 오역, 오타, 깨진 링크가 있으면 제보 부탁드립니다.


 

저작권 표시 : 

 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.

 

현재 보시는 페이지는 안드로이드 오픈 소스 프로젝트에 의해 작성되고 공유된 작업물의 수정/번역본이며 크리에이티브 커먼즈 저작자 표시 2.5 라이센스에 기술된 조건의 사용 근거를 따른 것입니다.

 

본 문서의 원본은 http://developer.android.com/guide/components/intents-common.html이며 안드로이드 4.4 Kitkat 기준으로 설명되었습니다.


일반적인 인텐트(Common Intents)


인텐트는 Intent오브젝트 안에 단순한 액션("지도를 본다." 또는 "사진을 찍는다."처럼)을 기술함으로써 다른 앱의 액티비티를 실행시킬 수 있다. 이런 종류의 인텐트는 실행시킬 컴포넌트를 지정하지 않기 때문에 암시적 인텐트라고 부른다. 하지만 수행할 액션과 함께 액션을 수행하는데 필요한 정보를 정해준다.


startActivity()startActivityForResult()를 호출할 때 암시적 인텐트를 넘겨주면 시스템은 인텐트를 다룰 수 있는 앱을 결정하고 해당하는 Activity를 실행시킨다. 만일 인텐트를 다룰 수 있는 앱이 여러 개 존재한다면 시스템은 사용자가 그 중에서 하나를 고를 수 있도록 다이얼로그를 띄운다.


이 글은 일반적인 액션으로 사용될 수 있는 몇몇 암시적 인텐트를 소개한다. 각 센션에서는 같은 액션을 수행할 수 있는 능력을 외부로 알리는 intent filter의 생성방법에 대해서도 함께 보여준다.


Caution : 만일 암시적 인텐트를 처리할 수 있는 앱이 단말에 설치되어 있지 않다면 startActivity() 메서드를 호출하는 순간 앱은 크래시되고 말 것이다. 먼저 인텐트를 처리할 수 있는 앱이 존재하는지를 확인하기 위하여 Intent 오브젝트를 사용해 resolveActivity() 메서드를 호출한다. 결과가 null이 아니면 인텐트를 처리할 수 있는 앱이 최소 하나는 존재한다는 뜻이며 startActivity()를 호출해도 안전하다. 반면에 결과가 null이면 해당 인텐트를 사용해서는 안되며 가능하다면 인텐트를 사용하게 되는 기능을 비활성화시키는 것이 좋다.


만약, 인텐트나 인텐트 필터를 생성하는 법을 잘 모른다면 인텐트와 인텐트 필터(Intents and Intent Filters)를 먼저 읽기 바란다.


카메라(Camera)



사진이나 동영상을 찍고 결과물 돌려받기
(Capture a picture or video and return it)

카메라 앱을 열고 사진이나 동영상 결과물을 돌려받기 위해서는 ACTION_IMAGE_CAPTUREACTION_VIDEO_CAPTURE액션을 사용한다. 또한 EXTRA_OUTPUT 부가정보 키에 카메라가 사진이나 동영상을 저장할 URI위치를 지정한다.


액션(Action)

ACTION_IMAGE_CAPTURE 또는 ACTION_VIDEO_CAPTURE


데이터 URI 스키마(Data URI Scheme)

필요없음


MIME Type

필요없음


부가정보(Extras)

EXTRA_OUTPUT

카메라 앱에서 사진이나 동영상 파일을 저장하기 위한 URI위치(Uri 오브젝트 사용)


카메라 앱이 정상종료되고 호출자의 액티비티로 포커스가 돌아오면(호출한 앱은 onActivityResult() 콜백을 받게 된다.) EXTRA_OUTPUT으로 지정한 URI위치에 있는 사진이나 동영상을 액세스할 수 있다.


Note : ACTION_IMAGE_CAPTURE를 사용해 사진을 촬영하며 카메라 앱은 결과 Intent내에 "data"라는 필드명으로 Bitmap형태의 썸네일을 제공하기도 한다.


인텐트 예제 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static final int REQUEST_IMAGE_CAPTURE = 1;
static final Uri mLocationForPhotos;

public void capturePhoto(String targetFilename) {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT,
            Uri.withAppendedPath(mLocationForPhotos, targetFilename);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bitmap thumbnail = data.getParcelable("data");
        // Do other work with full size photo saved in mLocationForPhotos
        ...
    }
}


이 인텐트로 어떻게 촬영하고 저장위치를 위한 올바른 Uri를 생성하는 법에 대한 보다 자세한 설명은 Taking Photos SimplyTaking Videos Simply를 읽어보기 바란다.


인텐트 필터 예제 :

1
2
3
4
5
6
<activity ...>
    <intent-filter>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>


연락처 앱(Contacts/People App)



연락처 선택하기(Select a contact)

사용자가 연락처를 선택하도록 하고 연락처의 정보를 완전히 액세스하려면 ACTION_PICK 액션을 사용하고 MIME 타입은 Contacts.CONTENT_TYPE을 사용해야 한다.


onActivityResult() 콜백함수로 돌려받은 Intent에는 선택된 연락처가 content:의 스키마(scheme)를 갖는 URI로 포함되어 있다. 앱이 READ_CONTACTS 권한을 갖고 있지 않더라도 반환된 결과는 Contacts Provider API를 통해 연락처의 내용을 읽을 수 있는 임시권한을 부여해준다.


Tip : 만일 전화번호나 이메일 주소와 같은 연락처 정보의 일부분만 액세스하려고 한다면 다음 섹션(연락처의 특정 데이터 선택하기; select specific contact data)을 참고 바란다.


액션(Action)

ACTION_PICK


데이터 URI 스키마(Data URI Scheme)

필요없음


MIME Type

Contacts.CONTENT_TYPE


인텐트 예제 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static final int REQUEST_SELECT_CONTACT = 1;

public void selectContact() {
    Intent intent = new Intent(Intent.ACTION_PICK);
    intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(intent, REQUEST_SELECT_CONTACT);
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_SELECT_CONTACT && resultCode == RESULT_OK) {
        Uri contactUri = data.getData();
        // Do something with the selected contact at contactUri
        ...
    }
}


연락처 URI의 상세 정보를 읽는 법은 Retrieving Details for a Contact를 참고하기 바란다. 다시한번 말하자면 위에서 설명한 인텐트로 획득한 연락처 URI의 상세정보를 읽을 때는 READ_CONTACTS 권한이 필요없다.


연락처의 특정 데이터 선택하기(Select specific contact data)

사용자가 연락처를 선택하도록 하고 선택된 연락처의 내용 중 전화번호, 이메일 주소등의 일부 정보를 획득하려면 액션은 ACTION_PICK를 사용하고 MIME 타입은 아래 목록에 표시된 컨텐트 타입 중 하나를 사용한다. 예를 들어, 연락처의 전화번호를 가져오고 싶으면 MIME 타입에 CommonDataKinds.Phone.CONTENT_TYPE을 사용하면 된다.


만일 연락처의 내용 중에서 한 가지 데이터만 필요하다면 ContactsContract.CommonDataKinds 클래스의 CONTENT_TYPE을 사용하는 것이 Contacts.CONTENT_TYPE(앞에서 설명 되었다.)을 사용하는 것보다 더 효과적인다. 왜냐하면 ContactsContract.CommonDataKinds 클래스의 CONTENT_TYPE을 사용하여 얻은 결과는 ContactsProvider에 복잡한 쿼리를 하지 않고도 원하는 정보에 직접 액세스 할 수 있기 때문이다.


onActivityResult() 콜백을 통해 전달되는 결과 Intent는 선택된 연락처 정보를 가리키는 content: URI를 포함하고 있다. 응답된 결과는 앱이 READ_CONTACTS 퍼미션을 갖고 있지 않아도 연락처 정보를 읽어낼 수 있는 임시 퍼미션을 부여해 준다.


액션(Action)

ACTION_PICK


데이터 URI 스키마(Data URI Scheme)

필요없음


MIME Type

CommonDataKinds.Phone.CONTENT_TYPE

연락처의 전화번호를 추출한다.

CommonDataKinds.Email.CONTENT_TYPE

연락처의 이메일 주소를 추출한다.

CommonDataKinds.StructuredPostal.CONTENT_TYPE

연락처의 우편번호를 추출한다.

ContactsContract에는 더 많은 CONTENT_TYPE이 있다.


인텐트 예제 :

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
26
27
28
static final int REQUEST_SELECT_PHONE_NUMBER = 1;

public void selectContact() {
    // Start an activity for the user to pick a phone number from contacts
    Intent intent = new Intent(Intent.ACTION_PICK);
    intent.setType(CommonDataKinds.Phone.CONTENT_TYPE);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(intent, REQUEST_SELECT_PHONE_NUMBER);
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_SELECT_PHONE_NUMBER && resultCode == RESULT_OK) {
        // Get the URI and query the content provider for the phone number
        Uri contactUri = data.getData();
        String[] projection = new String[]{CommonDataKinds.Phone.NUMBER};
        Cursor cursor = getContentResolver().query(contactUri, projection,
                nullnullnull);
        // If the cursor returned is valid, get the phone number
        if (cursor != null && cursor.moveToFirst()) {
            int numberIndex = cursor.getColumnIndex(CommonDataKinds.Phone.NUMBER);
            String number = cursor.getString(numberIndex);
            // Do something with the phone number
            ...
        }
    }
}


연락처 보기(View a contact)

저장된 연락처의 상세정보를 표시하려면 액션은 ACTION_VIEW를 사용하고 데이터에는 표시하려는 연락처를 content: URI 형태로 지정해 준다.


연락처 URI를 얻기 위해서는 주로 두 가지 방법을 사용한다.

  • 앞에서 설명했던 ACTION_PICK을 사용하여 얻어진 연락처 URI를 사용한다.(이 방법을 사용하기 위해서 필요한 퍼미션은 따로 없다.)
  • Retrieving a List of Contacts에 기술된 방법으로 모든 연락처의 목록에 직접 액세스한다.(이 방법을 사용하기 위해서는 READ_CONTACTS 퍼미션이 필요하다.)


액션(Action)

ACTION_VIEW


데이터 URI 스키마(Data URI Scheme)

content:<URI>


MIME Type

필요없음. 타입은 연락처 URI로 부터 유추가 가능한다.


인텐트 예제 :

1
2
3
4
5
6
public void viewContact(Uri contactUri) {
    Intent intent = new Intent(Intent.ACTION_VIEW, contactUri);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}


저장된 연락처 수정(Edit an existing contact)

저장된 연락처를 수정하려면 액션은 ACTION_EDIT, 데이터에는 content: URI의 형태로 연락처를 지정해 준다. 그리고 부가정보에 수정될 항목과 수정될 내용을 입력해 준다. 수정될 항목은 ContactsContract.Intents.Insert에 정의된 상수로써 정해줄 수 있다.


연락처 URI를 얻기 위해서는 주로 두 가지 방법을 사용한다.

  • 앞에서 설명했던 ACTION_PICK을 사용하여 얻어진 연락처 URI를 사용한다.(이 방법을 사용하기 위해서 필요한 퍼미션은 따로 없다.)
  • Retrieving a List of Contacts에 기술된 방법으로 모든 연락처의 목록에 직접 액세스한다.(이 방법을 사용하기 위해서는 READ_CONTACTS 퍼미션이 필요하다.)


액션(Action)

ACTION_EDIT


데이터 URI 스키마(Data URI Scheme)

content:<URI>


MIME Type

필요없음. 타입은 연락처 URI로 부터 유추가 가능한다.


부가정보(Extras; 선택사항)

ContactsContract.Intents.Insert에 정의된 항목을 하나 이상 추가할 수 있다.


인텐트 예제 :

1
2
3
4
5
6
7
8
public void editContact(Uri contactUri, String email) {
    Intent intent = new Intent(Intent.ACTION_EDIT);
    intent.setDataAndType(contactUri, Contacts.CONTENT_TYPE);
    intent.putExtra(Intents.Insert.EMAIL, email);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}

연락처를 수정하는 법에 대한 정보는 Modifying Contacts Using Intents에 더 많이 있다.


연락처 추가(Insert a contact)

새로운 연락처를 추가하려면 ACTION_INSERT 액션을 사용하고 MIME 타입은 Contacts.CONTENT_TYPE을 지정한다. 그리고 부가정보에 수정될 항목과 수정될 내용을 입력해 준다. 수정될 항목은 ContactsContract.Intents.Insert에 정의된 상수로써 정해줄 수 있다.


액션(Action)

ACTION_INSERT


데이터 URI 스키마(Data URI Scheme)

필요없음.


MIME Type

Contacts.CONTENT_TYPE


부가정보(Extras; 선택사항)

ContactsContract.Intents.Insert에 정의된 항목을 하나 이상 추가할 수 있다.


인텐트 예제 :

1
2
3
4
5
6
7
8
9
public void insertContact(String name, String email) {
    Intent intent = new Intent(Intent.ACTION_INSERT);
    intent.setType(Contacts.CONTENT_TYPE);
    intent.putExtra(Intents.Insert.NAME, name);
    intent.putExtra(Intents.Insert.EMAIL, email);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}

연락처를 추가하는 법에 대한 더 많은 정보는 Modifying Contacts Using Intents를 참고하기 바란다.


이메일(Email)



이메일 쓰기(Compose an email with optional attachments)

이메일을 사용하기 위해서는 첨부가 있는지 여부에 따라 아래의 액션 중 하나를 사용하고 아래의 목록에 있는 부가정보 키에 맞춰 수신인이나 제목과 같은 세부사항을 이메일에 포함시킬 수 있다.


액션(Action)

ACTION_SENDTO (첨부가 없을 때) 또는

ACTION_SEND (하나만 첨부할 때) 또는

ACTION_SEND_MULTIPLE (여러 개를 첨부할 때)


데이터 URI 스키마(Data URI Scheme)

필요없음.


MIME Type

PLAIN_TEXT_TYPE("text/plain")

"*/*"


부가정보(Extras; 선택사항)

Intent.EXTRA_EMAIL

모든 수신인 이메일 주소의 문자열 배열

Intent.EXTRA_CC

모든 참조자 이메일 주소의 문자열 배열

Intent.EXTRA_BCC

모든 숨김 참조자 이메일 주소의 문자열 배열

Intent.EXTRA_SUBJECT

이메일 제목을 나타내는 문자열

Intent.EXTRA_TEXT

이메일의 본문을 나타내는 문자열

Intent.EXTRA_STREAM

첨부한 파일을 가리키는 Uri. 만일 ACTION_SEND_MULTIPLE 액션을 사용하였다면 Uri 오브젝트를 포함하는 ArrayList.


인텐트 예제 :

1
2
3
4
5
6
7
8
9
10
public void composeEmail(String[] addresses, String subject, Uri attachment) {
    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setType("*/*");
    intent.putExtra(Intent.EXTRA_EMAIL, addresses);
    intent.putExtra(Intent.EXTRA_SUBJECT, subject);
    intent.putExtra(Intent.EXTRA_STREAM, attachment);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}


문자 메세지, SNS 앱이 아닌 반드시 이메일로 인텐트를 처리하고 싶다면 ACTION_SENDTO 액션을 사용하고 데이터 스키마는 "mailto:"로 지정해 준다. 아래는 그 예제다.



1
2
3
4
5
6
7
8
9
public void composeEmail(String[] addresses, String subject) {
    Intent intent = new Intent(Intent.ACTION_SENDTO);
    intent.setData(Uri.parse("mailto:")); // only email apps should handle this
    intent.putExtra(Intent.EXTRA_EMAIL, addresses);
    intent.putExtra(Intent.EXTRA_SUBJECT, subject);
    if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
}



인텐트 필터 예제 :

1
2
3
4
5
6
7
8
9
10
11
12
<activity ...>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <data android:type="*/*" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.SENDTO" />
        <data android:scheme="mailto" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>




다음 글에서 이 글(일반적인 인텐트)의 내용을 계속 이어갑니다.


긴 글들은 타이핑하는 것도 꽤 큰 일이지만 보는 사람도 지겨워질 수 있을 것 같아 한 번 쉬어갑니다.


다음 글 >> 일반적인 인텐트(Common Intents) (2)

이전 글 >> 인텐트와 인텐트 필터(Intents and Intent Filters) (2)


posted by 리치크루

API GUIDE/2. App Components
2014. 2. 25. 22:59

 

이전 글인 인텐트와 인텐트 필터(Intents and Intent Filters)에 이어지는 글입니다.


언제나 그렇듯 오역, 오타, 깨진 링크가 있으면 제보 부탁드립니다.


 

저작권 표시 : 

 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.

 

현재 보시는 페이지는 안드로이드 오픈 소스 프로젝트에 의해 작성되고 공유된 작업물의 수정/번역본이며 크리에이티브 커먼즈 저작자 표시 2.5 라이센스에 기술된 조건의 사용 근거를 따른 것입니다.

 

본 문서의 원본은 http://developer.android.com/guide/components/intents-filters.html이며 안드로이드 4.4 Kitkat 기준으로 설명되었습니다.

 

암시적 인텐트 수신하기



여러분의 앱이 암시적 인텐트를 다룰 수 있다는 것을 시스템에 알리려면 매니페스트 파일<intent-filter>엘리먼트를 사용하여 하나 이상의 인텐트 필터를 선언해야 한다. 각 인텐트 필터는 인텐트의 액션, 데이터, 카테고리에 기반하여 수용 가능한 인텐트의 타입을 지정한다. 인텐트 필터의 조건을 뚫고 통화할 수 있는 인텐트가 있다면 시스템은 암시적 인텐트를 여러분의 앱에 전달해 줄 것이다.


Note : 명시적 인텐트는 컴포넌트에 인텐트 필터가 있건 없건 상관없이 항상 지정된 컴포넌트에 배달된다.


앱 컴포넌트는 독립된 일 하나하나에 대해서 각각의 필터를 사용해야 한다. 예를 들어, 이미지 갤러리앱은 두 개의 필터를 갖게 될 것이다.; 하나의 필터는 이미지를 보여주기만 하고 나머지 하나는 이미지를 편집하는 필터로 말이다. 액티비티가 시작되면 Intent를 확인해서 Intent에 있는 정보에 대해 어떤 행동을 해야할 지 결정해야 한다.(편집 창을 보여줄지 말지를 결정해야 한다.)


Manifest 파일의 <intent-fillter>엘리먼트를 통해 선언된 인텐트 필터는 앱 컴포넌트(<activity>엘리먼트 같은) 안쪽에 네스트 되어야 한다. <intent-filter>안쪽에는 다음의 3개 엘리먼트 중 하나 이상을 사용하여 인텐트의 타입을 정해줘야 한다.


<action>

name속성에 수용하고자 하는 인텐트 액션을 선언한다. 값은 클래스 상수가 아닌 액션을 문자열로 써야한다.


<data>

URI데이터(scheme, host, port, path등)와 MIME타입의 다양한 점을 지정할 수 있도록 하나 이상은 속성을 사용하여 수용하고자 하는 데이터의 타입을 결정한다.


<category>

name속성을 사용하여 수용하고자 하는 인텐트 카테고리를 선언한다. 값은 클래스 상수가 아닌 카테고리를 문자열로 써야한다.


Note : 암시적 인텐트를 받기 위해서는 인텐트 필터에 CATEGORY_DEFAULT 카테고리를 반드시 포함시켜야 한다. startActivity()startActivityForResult()은 모든 인텐트가 CATEGORY_DEFAULT 카테고리를 선언했다고 여기기 때문이다. 만일 카테고리에 이 값을 선언하지 않았다면 암시적 인텐트는 받을 수 없다.


아래 예제는 ACTION_SEND의 액션과 텍스트 타입을 데이터를 갖는 인텐트를 수용하도록 인텐트 필터를 선언하는 법에 대한 것이다.


1
2
3
4
5
6
7
<activity android:name="ShareActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>

<action>, <data>, <category>엘리먼트 중 하나 이상을 포함하는 필터를 만드는 것이 좋다. 이렇게 하면 다룰 수 있는 인텐트를 필터에 선언된 엘리먼트들과 비교하여 그 조합된 조건에 맞도록 요구하는 컴포넌트가 되기 때문에 정확히 원하는 인텐트를 지정할 수 있게 된다.


특정 액션, 데이터, 카테고리 타입의 조합에 맞는 여러 종류의 인텐트를 다루도록 하고 싶다면 여러 개의 인텐트 필터를 선언해야 한다.


컴포넌트 액세스 제한하기(Restricting access to components)

인텐트 필터를 사용하는 것은 다른 앱이 여러분의 컴포넌트를 실행시키는 것을 제한하는 보안대책은 아니다. 인텐트 필터가 특정한 종류의 암시적 인텐트에 응답하도록 컴포넌트를 제한했다고 하더라도 개발자가 컴포넌트의 이름을 결정했다면 다른 앱이 명시적 인텐트를 사용하여 컴포넌트를 시작할 수 있는 잠재적 가능성은 있다. "오직 여러분의 앱에서만" 여러분의 컴포넌트를 실행시켜야 한다는 사실일 중요하다면 해당 컴포넌트의 exported 속성을 "false"로 설정해야 한다.


암시적 인텐트는 인텐트의 각각 세가지 엘리먼트를 필터와 비교하는 테스트를거치게 된다. 다시 말하자면 암시적 인텐트가 컴포넌트에 전달되려면 세 가지 테스트를 모두 통과해야만 하는 것이다. 각각의 엘리먼트를 테스트해서 하나라도 맞지 않는다면 안드로이드 시스템은 그 인텐트를 컴포넌트에 전달하지 않는다. 하지만 두스의 인텐트 필터를 갖고 있는 컴포넌트라면 또 다른 필터의 조건에 맞는 인텐트로 판단되어 인텐트가 전달될 수도 있다. 시스템이 어떻게 인텐트를 resolve하는지에 대한 추가 정보는 뒤에 나오는 Intent Resolution 섹션에서 제공된다.


Caution : 우연히 다른 앱이 Service를 실행시키는 일이 발생하지 않도록 하려면 여러분이 만든 서비스는 항상 명시적 인텐트를 사용하여 시작시키도록 서비스에 인텐트 필터를 선언하지 않도록 한다.


Note : 모든 액티비티의 인텐트 필터는 매니페스트 파일을 통해 선언되어야 한다. 하지만 브로드캐스트 리시버를 위한 필터는 registerReceiver() 메서드를 호출해서 동적으로 등록이 가능하다. 반대로 unregisterReceiver()를 통해 등록된 리시버를 해제시키는 것도 가능하다. 이런 메서드들을 사용하면 앱이 실행되는 동안 특정 브로드캐스트에 대해 일정 기간동안만 반응하도록 할 수가 있다.



필터 예제(Example filters)

인텐트 필터의 동작에 대한 이해를 돕기 위해 아래와 같은 예제를 준비했다. 이 예제는 소셜 공유앱의 매니페스트 파일을 일부 발췌한 것이다.


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
<activity android:name="MainActivity">
    <!-- This activity is the main entry, should appear in app launcher -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity">
    <!-- This activity handles "SEND" actions with text data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.SEND_MULTIPLE"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

첫번째 액티비티인 MainActivity는 앱의 주 진입점이다. 즉, 사용자가 런치 아이콘으로 앱을 시작시킬 때 열리는 액티비티다.


  • ACTION_MAIN액션(리치크루 : 앞 필터 예제에서는 adnroid.intent.action.Main)은 앱의 주 진입점임을 알리며 다른 데이터는 필요로 하지 않는다.
  • CATEGORY_LAUNCHER(리치크루 : 앞 필터 예제에서는 android.intent.category.LAUNCHER)는 해당 액티비티의 아이콘이 시스템의 앱 런처에 추가되어 위치해야 함을 의미한다. 만일 <activity>엘리먼트에 icon으로 아이콘을 지정하지 않았다면 시스템은 <application>엘리머트에 지정된 아이콘을 대신 사용한다.

앱 럽처에 액티비티가 표시되도록 하기 위해서는 이 두 가지(ACTION_MAIN과 CATEGORY_LAUNCHER)가 함께 짝을 이루어야한다.


두 번째 액티비티인 ShareActivity는 텍스트와 미디어 컨텐츠의 공유가 가능하도록 하려고 한다. 사용자는 MainActivity에서 이 액티비티로 넘어오는 경우도 있겠지만 다른 앱을 사용하다가 발생하는 암시적 인텐트를 통해 MainActivity를 거치지 않고 바로 이 액티비티를 만날 수도 있다.


Note : application/vnd.google.panorama360+jpg라는 MIME 타입은 Google panorama API를 통해 다룰 수 있는 파노라마 사진의 특수한 데이터 타입이다.



펜딩 인텐트 사용하기(Using a Pending Intent)



PendingIntent 오브젝트는 Intent 오브젝트의 랩퍼 오브젝트다. PendingIntent의 주목적은 외부 어플리케이션이 Intent를 사용할 때 여러분의 앱이 동작하는 프로세스에서 실행되는 것처럼 사용권한을 부여하기 위해 사용된다.


펜딩 인텐트의 주용도는 다음과 같다. :

  • Notification에 맞는 액션을 사용자가 수행하려할 때 사용될 인텐트를 선언한다.(안드로이드 시스템의 NotificationManagerIntent를 실행한다.)
  • App Widget을 통해 사용자가 액션을 취할 때 사용될 인텐트를 선언한다.(홈 스크린앱은 Intent를 실행한다.)
  • 미래의 특정시간에 사용될 인텐트를 선언한다.(안드로이드 시스템의 AlarmManagerIntent를 실행한다.)


각 Intent 오브젝트는 특정타입의 앱 컴포넌트(액티비티, 서비스, 브로드캐스트 리시버)가 다룰 수 있도록 구성한다. 그래서 PendingIntent도 똑같이 특정 타입의 앱 컴포넌트에서 다뤄질 수 있도록 구성해야한다. 펜딩 인텐트를 이용할 때는 startActivity() 같은 호출을 하지 않는다. 대신 PendingIntent가 각 생성 메서드를 통해 생성될 때 의도하는 컴포넌트 타입을 선언해 주어야 한다. 각 생성 메서드는 다음과 같다.


다른 앱으로부터 펜딩 인텐트를 건네받지 않는 한 PenndingIntent를 생성하는 방법은 위의 메서드들을 사용하는 방법뿐이다.


각 메서드들은 파라메터로 (1) 현재 앱의 Context, (2) 래핑하려는 Intent, (3) 인텐트가 어떻게 사용되어야 하는지(예를 들어, 인텐트가 한 번 이상 사용될 수 있는지)를 나타내는 하나 이상의 플래그를 취한다.


펜딩 인텐트의 사용에 관한 더 많은 정보는 NotificationApp widgets API 가이드 같이 각 사용 케이스에 따른 문서에서 제공된다.


인텐트의 결정성(Intent Resolution)



시스템에서는 암시적 인텐트를 받아 액티비티를 실행시켜야 할 때 인텐트와 인텐트 필터를 다음 세가지 사항에 기초하여 비교함으로써 최적의 액티비티를 찾아낸다.

  • 인텐트 액션
  • 인텐트 데이터(URI와 데이터 타입)
  • 인텐트 카테고리


이후에 섹션에서는 인텐트가 어떤 인텐트 필터를 가진 컴포넌트와 어떻게 적절히 짝을 찾게 되는지 설명한다.


액션 테스트(Action test)

수용하고자 하는 인텐트의 액션을 지정하기 위한 것이 <action>엘리먼트다. 인텐트 필터 안에서 정의하게 되며 아예 선언을 안 할 수도 있고 하나 이상, 여러 개를 선언할 수도 있다. 예제는 다음과 같다.


1
2
3
4
5
<intent-filter>
    <action android:name="android.intent.action.EDIT" />
    <action android:name="android.intent.action.VIEW" />
    ...
</intent-filter>

이 필터를 통과하려면 Intent 내의 액션은 필터에 명기된 액션 중 하나와 일치해야만 한다.


만일 필터가 어떤 액션도 포함하지 않으면 인텐트와 매칭 시킬 게 없다. 때문에 모든 인텐트가 테스트를 통과하지 못한다. 반면에 액션을 지정하지 않은 Intent가 있다면 그 인텐트는 테스트를 통과할 수 있게 된다.(필터에 적어도 하나의 액션이 포함되어 있다면)


카테고리 테스트(Category test)

수용하려는 인텐트의 카테고리를 지정하려면 <category>엘리먼트를 사용하며 이 엘리먼트는 사용하지 않거나 하나 이상을 선언할 수 있다. 예제는 다음과 같다.


1
2
3
4
5
<intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    ...
</intent-filter>

인텐트가 카테고리 테스트를 통과하기 위해서는 인텐트의 모든 카테고리가 필터의 카테고리와 일치해야만 한다. 인텐트 필터 내의 카테고리가 인텐트에 지정된 것 이상으로 선언되어 있다면 그 인텐트는 테스트를 통과하게 되지만 그 반대의 경우는 통과할 수 없다. 그래서 인텐트에 지정된 카테고리가 없으면 필터에 선언된 카테고리가 있건 없건 무조건 테스트를 통과하게 된다.


Note : 안드로이드는 startActivity()startActivityForResult()를 통해 전달되는 모든 암시적 인텐트에 자동으로 CATEGORY_DEFAULT를 추가시킨다. 그렇게 때문에 암시적 인텐트를 받고자 하는 액티비티는 인텐트 필터에 "android.intent.category.DEFAULT"를 반드시 포함시켜야 한다.(앞의 <intent_filter>예제처럼 말이다.)


데이터 테스트(Data test)

수용하려는 인텐트 데이터를 지정하려면 <data>엘리먼트를 사용해야 하며 이 엘리먼트는 사용하지 않거나 하나 이상을 선언할 수 있다.


1
2
3
4
5
<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>

<data>엘리먼트는 URI 스트럭쳐와 데이터 타입(MIME 미디어 타입)을 지정할 수 있다. URI를 표현할 때는 각각의 부분을 나타내는 별도의 속성들이 있다.(schem, host, port, path)


<scheme>://<host>:<port>/<path>


예를 들어, 다음과 같은 URI가 있다면


content://com.example.project:200/folder/subfolder/etc


이 URI에서 스킴(scheme)은 content, 호스트(host)는 com.example.project, 포트(port)는 200, 경로(path)는 folder/subfolder/etc가 된다.


이러한 각각의 속성들은 <data>엘리먼트 내에서 선택적으로 사용될 수는 있지만 선형적 의존성을 갖고 있다.

  • 스키마가 지정되지 않으면 호스트는 무시된다.
  • 홋트가 지정되지 않으면 포트는 무시된다.
  • 스킴과 호스트가 모두 지정되지 않으면 경로는 무시된다.


인텐트의 URI와 필터의 URI를 비교할 때는 다음과 같이 필터 URI의 부분, 부분만을 비교한다.

  • 필터에 스킴만 지정되었으면 그 스킴을 갖는 모든 URI는 필터와 일치하는 것으로 본다.
  • 필터에 경로는 없이 스킴과 권한만 지정되었으면 경로와 상관없이 스킴과 권한이 같은 URI는 필터를 통과하게 된다.
  • 필터에 스킴, 권한, 경로가 지정되어 있으면 오직 같은 스킴, 권한, 경로를 가진 URI만 필터를 통과할 수 있다.


Note : 경로 지정에는 경명의 일부에 무엇이 와도 매칭된 것으로 간주하는 와일드카드(* 표시)를 포함시킬 수 있다.


데이터 테스트는 필터와 인텐트의 URI와 MIME 타입을 비교하며 그 규칙은 다음과 같다.

  1. URI와 MIME 타입이 모두 지정되지 않은 인텐트는 URI와 MIME 타입 모두를 지정하지 않은 필터만 통과할 수 있다.
  2. URI는 포함하고 있지만 MIME 타입을 포함하지 않은 (타입이 명시적이지도 않고 URI에 의해 유추할 수도 없는) 인텐트는 URI 포맷은 지정하고 MIME 타입은 지정하지 않은 필터만 통과할 수 있다.
  3. MIME 타입은 포함하고 있지만 URI는 포함하지 않은 인텐트는 같은 MIME 타입이 지정되어 있고 URI 포맷은 지정하지 않은 필터만 통과할 수 있다.
  4. URI와 MIME 타입을 포함한 (타입이 명시적이거나 URI로 부터 유추가 가능한) 인텐트는 필터에서 지정한 타입과 매칭되면 일단 MIME 타입 부분만 테스트를 통과한다. URI 부분의 테스트는 필터에서 지정한 URI와 동일해도 통과할 수 있으며 필터에는 URI를 지정하지 않았지만 URI가 content: 또는 file: 형태라면 통과할 수 있다. 즉, 컴포넌트는 필터에 MIME 타입만 지정한 경우 content:와 file: 데이터는 수용할 수 있는 것으로 간주된다.


마지막의 (D)규칙은 컴포넌트가 파일이나 컨텐트 프로바이더라는 로컬 데이터를 수용할 수 있음을 의미한다. 그래서 컴포넌트들의 필터는 데이터 타입만 지정하고 content:와 file: 스킴은 명시적으로 지정하지 않아도 된다. 아래 예제는 이런 상황에 대한 전형적인 예시다. 예시에서는 컴포넌트가 컨텐트 프로바이더로 부터 이미지 데이터를 받아들이고 디스플레이 할 수 있다는 것을 보여주고 있다.


1
2
3
4
<intent-filter>
    <data android:mimeType="image/*" />
    ...
</intent-filter>

대부분의 쓸모있는 데이터는 컨텐트 프로바이더를 통해 제공받을 수 있기 때문에 필터는 데이터 타입만 지정하고 URI는 지정하지 않는 것이 대체로 일반적인다.


또다른 일반적인 구성은 필터에 스킴과 데이터 타입만 지정하는 경우다. 아래 예제의 <data>엘리먼트는 액션 수행을 위해 네트워크로부터 제공되는 비디오 데이터를 사용할 수 있음을 나타내고 있다.


1
2
3
4
<intent-filter>
    <data android:scheme="http" android:type="video/*" />
    ...
</intent-filter>

인텐트 매칭(Intent matching)

인텐트는 실행시킬 타겟 컴포넌트를 찾기 위해 사용되기도 하지만 기기의 컴포넌트 세트에 대한 무언가를 찾을 때도 사용된다. 예를 들어, 홈 앱은 ACTION_MAIN 액션과 CATEGORY_LAUNCHER 카테고리를 지정한 인텐트 필터를 가진 모든 액티비티를 찾아서 앱 런쳐에 채워넣는다.


여러분의 앱에서도 이와 비슷한 방법으로 사용할 수 있다. PackageManager는 특정 인텐트를 수용할 수 있는 모든 컴포넌트를 알려주는 query...() 메서드 세트를 가지고 있으며 인텐트에 응답하는 최적의 컴포넌트를 결정해주는 resolve...() 메서드 시리즈도 갖고 있다. 예를 들어, queryIntentActivities() 메서드는 인자로 넘겨주는 인텐트를 수행할 수 있는 모든 액티비티의 목록을 반환해주며 queryIntentServices()는 서비스 목록을 반환해 준다. 이 메서드들은 컴포넌트를 실행시키는 것이 아니다. 단순히 응답가능한 컴포넌트들의 목록만을 제공해 주는 것이다. 비슷한 메서드로 브로드캐스트 리시버를 찾아주는 queryBroadcastReceivers() 메서드가 있다.



오늘 포스트를 다 올리고 자야겠다는 생각에 밤 11시를 넘겨버렸네요.


예전에는 새벽 1시, 2시까지도 깨어 있다가 아침에는 지각할까 걱정하며 허겁지겁 문턱을 나섰습니다.


그런데 언제부터인가 '새벽 1, 2시까지 할 일이 있으면 차라리 아침 5시, 6시부터 시작하자'라는 쪽으로 바람직하게 바뀌었습니다.


바람직하게 바뀌기는 했는데... '나이들면 새벽잠이 없어진다더라....'는 말이 자꾸 머릿속에 멤도는 건...


젠장 그래서 그랬던건가?... ^^;


그래도 아침일찍 일어나는 게 효율적이긴 합니다.


다음 글 >> 일반적인 인텐트(Common Intents) (1)

이전 글 >> 인텐트와 인텐트 필터(Intents and Intent Filters) (1)





posted by 리치크루

API GUIDE/2. App Components
2014. 2. 20. 23:40

 

이번 글을 인텐트와 인텐트 필터(Intents and Intent Filter)입니다.

글을 읽으시기 전에 간략히 설명을 드리려고 합니다.


앞서 올린 글에서도 나온 적이 있지만 안드로이드는 이미 다른 프로그램에서 구현되어 있는 기능을 내 프로그램에서 힘들게 구현할 필요없이 내 것인양 가져다 쓸 수 있습니다.


하지만 가져다 쓰려면 다른 프로그램에게 '내가 너의 기능을 가져다 쓰려고 한다. 내가 그 기능을 쓰려는 목적(intent)은 이러이러하다.'라고 알려주어야 하죠. 이 때 사용하는 것이 인텐트(Intent; 계획, 의지, 목적) 되겠습니다.


그와는 반대로 다른 프로그램 입장에서는 '내 기능은 이러이러한 목적으로만 가져다 쓸 수 있어.'라고 선을 그어 다른 프로그램에서 들어오는 요구들을 걸러내야 하겠죠. 이 때 사용하는 것이 인텐트 필터(Intent Filter)입니다.


물론, 인텐트의 기능이 다른 프로그램의 일부를 가져다 쓰는 데에만 있는 것은 아닙니다만 가장 주요한 기능임에는 틀림없습니다.


그럼 공부 열심히 하시고 언제나 그렇듯 오역, 오타, 깨진 링크가 있으면 제보 부탁드립니다.


 

저작권 표시 : 

 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.

 

현재 보시는 페이지는 안드로이드 오픈 소스 프로젝트에 의해 작성되고 공유된 작업물의 수정/번역본이며 크리에이티브 커먼즈 저작자 표시 2.5 라이센스에 기술된 조건의 사용 근거를 따른 것입니다.

 

본 문서의 원본은 http://developer.android.com/guide/components/intents-filters.html이며 안드로이드 4.4 Kitkat 기준으로 설명되었습니다.

 

인텐트와 인텐트 필터(Intents and Intent Filters)


Intent는 다른 앱 컴포넌트에 어떤 행동을 요구할 때 사용하는 메시징 오브젝트다. 컴포넌트간에 커뮤니케이션을 할 때 인텐트를 사용하는 데에는 여러 방법이 있지만 기초적인 사용법은 다음의 3가지 경우다.


  • 액티비티(Activity)를 실행시키기 위해
    Activity는 앱의 한 화면을 의미한다. startActivity()를 호출할 때 Intent를 전달함으로써 Activity의 새로운 인스턴스를 만들어 실행시킬 수 있다. Intent는 액티비티를 실행시킬 수 있으며 그에 필요한 데이터를 담고 있다.
    만일, 시작시킨 액티비티가 종료될 때 어떤 결과를 돌려받으려면 startActivityForResult() 메서드를 호출한다. 그러면 여러분의 액티비티의 onActivityResult() 콜백함수에 또다른 Intent 오브젝트가 결과로써 돌아온다. 자세한 내용은 Activities 가이드를 참조하기 바란다.

  • 서비스를 실행시키기 위해
    Service는 UI없이 백그라운드에서 동작하는 컴포넌트다. 서비스는 일회성 동작(파일을 다운로드하는 것 같은)을 위해 사용되며 startService()를 호출할 때 Intent를 전달함으로써 Service를 시작시킬 수 있다. Intent는 서비스를 실행시킬 수 있으며 그에 필요한 데이터를 담고 있다.
    만일 서비스가 클라이언트-서버 인터페이스로 디자인되었다면 bindService()Intent와 함께 전달함으로써 다른 컴포넌트의 서비스에 바인딩할 수 있다. 자세한 내용은 Services 가이드를 참조하기 바란다.

  • 브로드캐스트를 전달하기 위해
    브로드캐스트는 어떤 앱이라도 받을 수 있는 메시지다. 시스템은 부팅이 완료되었다거나 기기의 충전이 시작되었다거나 하는 것과 같은 다양한 시스템 이벤트를 브로드캐스팅한다. 여러분도 다른 앱들에게 sendBroadcast(), sendOrderedBroadcast(), sendStickyBroadcast()와 같은 메서드를 통해 브로드캐스트 할 수 있으며 그 브로드캐스트의 내용은 함수를 호출할 때 Intent를 통해 전달하게 된다.


인텐트의 종류(Intent Types)



인텐트는 다음과 같이 두 종류로 나뉜다.

  • 명시적 인텐트는 이름(클래스 전체이름)으로 컴포넌트를 지정하여 실행시킬 수 있다. 보통 여러분이 작성하는 앱에 있는 컴포넌트는 이 방법을 사용해서 실행시킨다. 여러분이 시작시키려고 하는 액티비티나 서비스의 클래스 이름을 알고 있기 때문이다. 예를 들어, 사용자의 행동에 따라 새로운 액티비티를 띄우거나 백그라운드에서 파일을 다운로드 받는 등의 작업이 이에 해당한다.

  • 암시적 인텐트는 특정 컴포넌트의 이름을 지정하지는 않지만 수행하고자 하는 일반적인 행동을 선언한다. 그러면 그 행동을 수행할 수 있는 다른 앱의 컴포넌트가 실행되어진다. 예를 들어, 맵상에 사용자의 위치를 표시하려고 할 때 지도위에 위치표시가 가능한 앱들에게 암시적 인텐트를 사용하여 요청할 수 있다.


액티비티나 서비스를 시작하기 위해 명시적 인텐트를 생성하면 시스템은 Intent 오브젝트에 지정된 앱 컴포넌트를 즉시 시작시킨다.


 

 그림 1. 시스템이 다른 액티비티를 실행시키기 위해서 암시적 인텐트를 전달하는 방법

[1] 액티비티 "A"는 특정 액션을 정의한 Intent를 만들어서 startActivity() 메서드에 전달한다. [2] 안드로이드 시스템은 인텐트와 매칭되는 인텐트 필터를 가진 앱을 찾는다. 매칭되는 앱을 찾으면, [3] 시스템은 매칭된 액티비티(액티비티 "B")의 onCreate()Intent를 넘겨 액티비티 "B"를 실행시킨다.


암시적인 인텐트를 생성하게 되면 안드로이드 시스템은 기기에 설치된 앱들이 manifest 파일에 선언한 인텐트 필터에 따라 인텐트의 내용과 부합되는 앱 컴포넌트를 찾는다. 조건과 일치하는 앱 컴포넌트를 찾았다면 실행을 시키고 Intent 오브젝트를 전달한다. 만일 여러 개의 앱 컴포넌트를 찾았다면 시스템은 대화상자를 띄워 사용자가 실행된 앱을 선택하도록 한다.


인텐트 필터는 앱의 컴포넌트가 특정 인텐트를 받아서 처리할 능력이 있다고 선언하는 것이다. 다시 말하면 여러분의 앱이 어떤 인텐트의 내용을 처리할 수 있는 능력이 있고 그 인텐트에 대한 필터를 선언했다면 다른 앱이 해당 인텐트를 가지고 여러분의 컴포넌트를 직접 실행시킬 수도 있다는 말이 된다. 같은 말이지만 액티비티에 대해 어떤 인텐트 필터도 선언하지 않았다면 그 액티비티를 실행시킬 수 있는 방법은 명시적 인텐트를 사용하는 것 뿐이다.


Caution : 여러분이 작성한 앱의 안전성을 유지하고자 한다면 서비스에 대해서는 인텐트 필터를 사용하지 말고 항상 명시적 인텐트만으로 Service가 시작될 수 있도록 하는 게 좋다. 서비스를 암시적 인텐트로 시작시키는 것은 매우 위험하다. 왜냐하면 그 인텐트에 대해 어떤 서비스가 응답할 지 확실하지도 않을 뿐더러 사용자도 서비스가 시작되는 것을 볼 수 없기 때문이다.


인텐트 만들기(Building an Intent)



Intent 오브젝트는 어떤 컴포넌트가 시작될 지에 대한 정보(정확한 컴포넌트의 이름이나 인텐트의 요청을 처리할 수 있는 컴포넌트의 범주)와 인텐트를 수신한 컴포넌트가 적절한 업무를 수행하기 위한 정보를 포함하고 있다.


Intent에 포함되어 있는 주요 정보는 다음과 같다.


컴포넌트의 이름(Component name)

실행시킬 컴포넌트의 이름이다.


이 항목은 옵션사항이다. 하지만 인텐트를 명시적으로 만드는 아주 중요한 정보다. 명시적이라는 것은 해당 인텐트에 명시된 컴포넌트 명을 갖는 컴포넌트에만 인텐트가 전달 될 수 있다는 것을 의미한다. 컴포넌트 이름이 명시되지 않으면 암시적인 인텐트가 되며 시스템은 인텐트에 있는 부차적인 정보(이후에 설명될 액션, 데이터, 카테고리 같은 정보들)에 기반하여 인텐트를 수신할 컴포넌트를 결정하게 된다. 그렇기 때문에 여러분의 앱 내에 있는 컴포넌트에 인텐트를 전달하려고 한다면 컴포넌트 이름을 지정해 주어야 한다.


Note : Service를 시작시킬 때는 항상 컴포넌트 이름을 정해야 한다. 이름을 정해주지 않으면 여러분이 보낸 인텐트에 대해 어떤 서비스가 응답했는지 알 수가 없고 사용자도 서비스가 시작되는 것을 볼 수가 없다.


Intent의 이 필드는 ComponentName 오브젝트로써 앱의 패키지명을 포함하는 전체 클래스명을 사용하여 정해줄 수 있다. com.example.ExampleActivity 처럼 말이다. 컴포넌트의 이름은 setComponent(), setClass(), setClassName() 메서드 중의 하나를 호출하여 설정하거나 Intent의 생성자를 통해 설정할 수 있다.


액션(Action)

액션은 'view' 또는 'pick'과 같이 수행해야 하는 일반적인 행동에 대한 문자열이다.


브로드캐스트에 사용되는 인텐트는 앞으로 수행할 액션이 아니다. 이미 발생해서 보고되어야 할 이벤트다. 액션은 인텐트의 나머지 부분 - 데이터와 부가정보 - 에 어떤 정보가 들어있을 지 정하게 된다.


여러분이 만든 앱 내에서 또는 여러분이 만든 앱에서 다른 앱으로 보내는 인텐트에 여러분이 만든 액션을 사용할 수도 있다. 하지만 일반적으로 사용할 때는 Intent 클래스와 다른 프레임워크 클래스에 정의된 액션 상수(각 종류의 액션은 사실상 상수로 정의되어 있다. 아래에 기술된 'ACTION_VIEW'나 'ACTION_SEND'도 특정 상수를 표현하고 있는 상수 이름에 불과하다.)를 사용하는 것이 좋다. 아래는 액티비티를 시작시키는 몇몇 일반적인 액션들이다.


ACTION_VIEWE

사용자에게 갤러리앱에서 사진을 보여주거나 지도앱에서 특정 주소의 위치를 보여주는 것과 같이 뭔가를 보여줘야 할 때 이 액션을 가진 인텐트를 사용하여 startActivity()를 호출한다.


ACTION_SEND

'공유하기' 인텐트로도 알려져 있으며 이메일 앱이나 소셜 공유앱과 같은 앱을 통해 사용자가 어떤 정보를 공유하려고 할 때 인텐트에 이 액션을 담아 startActivity()를 호출한다.


일반적인 액션에 대해 정의된 더 많은 상수를 알고 싶다면 Intent 클래스의 도움말을 참고하기 바란다. 환경 설정앱의 특정 화면을 불러오는 액션이 Settings에 정의되어 있는 것처럼 또다른 액션들이 안드로이드 프레임워크 내의 어딘가에 정의되어 있다.


인텐트에 액션을 지정하려면 setAction() 함수를 호출하거나 Intent의 생성자를 이용할 수 있다.


사용자 정의 액션을 정의하고 싶다면 아래 예제처럼 여러분이 작성한 패키지의 이름을 접두사로 사용하는 것이 좋다.


1
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";


데이터(Data)

액션을 수행하기 위해 필요한 URI(Uri 오브젝트) 그리고/또는 데이터의 MIME 타입. 제공되는 데이터의 타입은 일반적으로 인텐트의 액션에 좌우된다. 예를 들어, 액션이 ACTION_EDIT라면 데이터는 수정하려고 하는 문서의 URI를 포함해야 한다.


인텐트를 생성할 때 데이터의 URI뿐만 아니라 URI의 종류(MIME 타입)까지 지정하는 것이 중요하게 여겨질 때가 자주 있다. 예를 들어, 이미지를 보여주는 액티비티는 URI의 포맷이 비슷하다고 하더라도 오디오 파일을 플레이하지는 못할 것이다. 그렇기 때문에 시스템이 인텐트에 가장 적합한 컴포넌트를 찾기 위해서는 MIME 타입이 지정되어 있어야 한다. 그러나 때로는 URI를 통해 MIME 타입을 추측할 수도 있다. 데이터가 기기내에 존재하며 MIME 타입을 알 수 있게 해주는 ContentProvider에 의해 제어되는 URI라면 특히나 쉽게 추측이 가능하다.


데이터 URI만 설정하려면 setData()를 호출한다. MIME 타입만 설정하려면 setType()을 호출한다. 필요하다면 setDataAndType()을 사용해서 데이터 URI와 MIME 타입을 한꺼번에 설정할 수도 있다.


Caution : URI와 MIME 타입을 둘 모두 설정하려면 setData()setType()을 호출하면 안된다. 왜냐하면 이 두개의 함수들은 다른 하나의 값을 null로 만들어버리기 때문이다. 둘 모두를 설정하려면 반드시 setDataAndType() 함수를 사용한다.


카테고리(Category)

인텐트를 다루게 될 컴포넌트의 종류에 대한 추가 정보를 담고 있는 문자열로써 몇 개든 설정할 수 있다. 그러나 대부분의 인텐트는 카테고리 값을 필요로 하지 않는다. 다음은 몇몇 일반적인 카테고리들에 대한 설명이다.


CATEGORY_BROWSABLE

타겟 액티비티는 이미지나 이메일 메시지 같이 링크로써 참조되는 데이터를 디스플레이하기 위하여 웹 브라우저로써 시작되어야 한다.


CATEGORY_LAUNCHER

태스크 초기화 액티비티여야 하며 시스템 어플리케이션 런쳐의 목록 중 하나여야 한다.


카테고리 전체 목록은 Intent 클래스에 대한 도움말을 참고하기 바란다.


addCategory() 메서드를 사용하여 카테고리를 설정할 수 있다.


앞서 설명한 컴포넌트 이름, 액션, 데이터, 카테고리는 인텐트의 특성을 정하게 된다. 안드로이드 시스템은 이러한 속성들을 읽어들여 시작되어야 할 앱 컴포넌트를 결정할 수 있다.


그러나 인텐트에는 어떤 앱 컴포넌트가 시작될 지 결정하는데는 아무런 영향을 끼지지 않는 부가정보를 담을 수가 있다. 다음은 인텐트에 담을 수 있는 부가정보(Extras)에 대한 설명이다.


부가정보(Extras)

키(key)와 값(value)의 조합으로 구성되며 요청된 액션을 수행하기 위해 필요한 추가 정보다. 어떤 액션들은 특별한 URI가 필요하기 때문에 부가정보(Extras)를 사용한다.


엑스트라 데이터는 다양한 putExtra() 메서드(시리즈로 준비되어 있다.)를 통해 추가할 수 있다. 이 함수들은 모두 키(key)와 값(value)을 파라메터로 넘겨줘야 한다. 모든 부가 정보를 Bundle 오브젝트로 만들 수 있으며  putExtras() 메서드를 통해 Intent에 집어넣을 수 있다.


예를 들어, ACTION_SEND라는 액션으로 이메일을 보내기 위해 인텐트를 만든다면 EXTRA_EMAIL키에 "수신인"을 지정할 수 있으며 EXTRA_SUBJECT키에 "제목"을 지정할 수 있다.


Intent 클래스에는 "EXTRA_*"로 시작하는 많은 표준 데이터 타입의 상수들이 준비되어 있다. 만일, 여러분만의 부가정보 키를 만들고 싶다면 아래 예제처럼 앱의 패키지명을 접두사로 포함시키는 것을 잊지 않도록 한다.(반드시 이렇게 해야하는지는 모르겠지만 이미 존재하는 다른 변수와의 충돌을 피하기 위해서가 아닌가 싶다.)


1
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";


플래그(Flags)

플래그는 Intent 클래스에 속하며 인텐트의 메타 데이터로써의 기능을 한다. 이것은 안드로이드 시스템이 액티비티를 어떻게 시작할 지(액티비티가 어떤 task에 속해야할 지 같은) 그리고 시작된 후에 어떻게 처리되어야 할 지(최근 사용 액티비티의 목록에 포함시킬 지 여부와 같은)를 지시하기도 한다.


더 많은 정보는 setFlags()를 참조하기 바란다.


명시적 인텐트의 사용 예제(Example explicit intent)

명시적 인텐트는 여러분의 앱에 있는 액티비티나 서비스와 같이 특정 앱 컴포넌트가 실행되도록 하는 데 사용된다. 명시적 인텐트를 생성하려면 Intent 오브젝트에 컴포넌트 이름을 정해준다. 그 외 인텐트의 다른 모든 속성은 선택사항이다.


예를 들어보자. 웹상에서 파일을 다운로드하도록 만들어진 DownloadService라는 서비스가 여러분든이 만드는 앱내에 구현되어 있다면 아래 코드와 같이 이 서비스를 실행시킬 수 있다.


1
2
3
4
5
// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

Intent(Context, Class) 생성자에 앱의 Context와 컴포넌트의 Class 오브젝트를 넘겨준다. 그러면 명시적인 인텐트가 생성되어 DownloadService 클래스를 실행시킨다.


서비스를 만들고 시작시키는 방법을 더 알고 싶다면 Services 가이드를 참고하기 바란다.


암시적 인텐트의 사용 예제(Example implicit intent)

암시적 인텐트는 기기에 설치된 앱 중에서 특정 액션을 수행할 수 있는 앱들에게 일을 대신 수행하도록 부탁(지시)하기 위하여 액션 정보를 지정한다. 암시적 인텐트를 사용해서 여러분의 앱은 해당 액션을 수행할 수 없지만 다른 앱은 수행이 가능할 것 같을 때 아주 유용하다.


예를 들어, 사용자가 다른 사람에게 공유하고자 하는 컨텐츠를 갖고 있을 때 액션은 ACTION_SEND를 부가정보에는 컨텐츠를 담아 인텐트를 생성한다. 그리고 그 인텐트로 startActivity()를 호출하면 그 컨텐츠를 공유할 수 있는 앱들 중 하나를 사용자가 선택해서 실행시킬 수 있다.


Caution : 암시적 인텐트에 설정된 액션에 대응할 만한 앱이 없을 수도 있다. 이런 상황에서 startActivity()를 호출하게 되면 앱이 비정상적으로 종료된다. 해당 인텐트를 받아 처리할 수 있는 앱이 있는지 확인하려면 IntentresolveActivity()에 넘겨 결과를 확인한다. 만일 결과가 null이 아니고 적어도 하나 이상의 앱이 검색되었다면 startActivity()를 안전하게 호출할 수 있다. 하지만 결과가 null이면 그 인텐트는 사용하면 안된다. 가능하다면 해당 인텐트로 문제가 발생하는 기능은 동작하지 않도록 미리 손을 써두는 것이 좋다.


1
2
3
4
5
6
7
8
9
10
// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType(HTTP.PLAIN_TEXT_TYPE); // "text/plain" MIME type

// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
}

Note : 이 경우 URI는 사용되지 않았지만 데이터 타입은 부가정보에 있는 컨텐츠의 데이터 타입으로 선언되었다.


startActivity()가 호출되면 시스템은 설치된 모든 앱 중에서 해당 인텐트를 처리할 수 있는 앱은 어떤 것이 있는지 결정을 한다.(인텐트는 ACTION_SEND 액션을 가지고 있으며 'text/plain'데이터를 가지고 있다.) 만일, 이 인텐트를 다룰 수 있는 앱이 하나 뿐이라면 그 앱은 바로 실행이 되고 인텐트를 받게 된다. 반면, 여러 개의 앱이 검색되면 시스템은 해당 앱들의 목록을 대화상자에 보여주고 사용자가 그 중에 사용한 앱을 선택하도록 한다.


앱 선택기 적용하기

암시적 앱에 대응하는 앱이 하나 이상이면 사용자는 그 중 하나의 앱을 선택하여 기본 앱으로 만들 수도 있다. 이것은 앞으로 동일한 액션에 대해 같은 앱이 실행되도록 하는 아주 유용한 방법이다. 예를 들어 웹페이지를 보여주는 앱은 여러 개가 있지만 사용자는 종종 웹페이지를 볼 때 하나의 앱만을 사용하는 경향이 있다. 이런 경우에 기본앱으로 설정하면 매번 사용자가 실행될 앱을 선택하지 않고 기본앱이 바로 실행되도록 할 수 있다.


 

 그림 2. 앱 선택기 대화상자


그러나 인텐트에 대응하는 앱이 여러 개 있고 사용자가 매번 다른 앱을 사용하고 싶어한다면 선택기 대화상자를 확실하게 보여줘야 한다. 앱 선택기 대화상자는 사용자에게 매번 액션을 실행할 앱을 선택하도록 한다. (이 때 사용자는 해당 액션에 대해 기본 앱을 지정할 수 없다.) 예를 들어, ACTION_SEND 액션으로 무언가를 공유한다고 할 때 사용자는 매번 상황에 따라 사용하고자 하는 앱이 달라질 것이다. 이럴 때는 (그림 2)처럼 매번 앱 선택기 대화상자를 보여줘야 한다.


앱 선택기 대화상자를 표시하려면 createChooser()를 사용하여 Intent를 생성하고 생성된 인텐트를 startActivity()로 넘겨준다. 다음은 그 예제코드다.


1
2
3
4
5
6
7
8
9
10
11
12
13
Intent intent = new Intent(Intent.ACTION_SEND);
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show chooser
Intent chooser = Intent.createChooser(intent, title);

// Verify the intent will resolve to at least one activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
}

이 예제는 createChooser() 메서드에 전달된 인텐트에 대응할 수 있는 앱의 리스트를 보여주며 대화상자의 타이틀도 주어진 텍스트로 표시된다.


'인텐트와 인텐트 필터'는 내용이 좀 기네요.

웬만하면 하나의 포스트에 다 담아보려 했는데 스압때문에 Part.1 과 Part.2로 나누어 게재합니다.


다음 글 >> 인텐트와 인텐트 필터(Intents and Intent Filters) (2)

이전 글 >> 시스템 퍼미션(System Permissions)





posted by 리치크루

API GUIDE/1. Introduction
2014. 2. 16. 19:22

이번 글은 보안에 관련된 글입니다.


짧은 영어 실력 때문이기도 하지만 보안에 대해서 지식이 부족한 터라 번역하는데에 시간이 많이 소요되었고 이해가 안되는 부분도 많았습니다.


그래서 번역이 좀 부실하네요. ^^;


오역에 대한 제보는 저 뿐만 아니라 이곳을 방문하시는 분 모두에게 도움이 될 겁니다. 많은 제보 부탁드립니다.

 

 


저작권 표시 : 

 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.

 

현재 보시는 페이지는 안드로이드 오픈 소스 프로젝트에 의해 작성되고 공유된 작업물의 수정/번역본이며 크리에이티브 커먼즈 저작자 표시 2.5 라이센스에 기술된 조건의 사용 근거를 따른 것입니다.

 

본 문서의 원본은 http://developer.android.com/guide/topics/security/permissions.html이며 안드로이드 4.4 Kitkat 기준으로 설명되었습니다.

 


시스템 퍼미션(System Permissions)

 

안드로이드는 각 어플리케이션이 ID(리눅스 사용자 ID와 그룹 ID)로 구분되는 사용 권한이 분리되어 있는 운영체제다. 시스템의 각 부분들도 ID로 서로 분리되어 있다. 이러한 방식으로 리눅스는 어플리케이션을 또다른 어플리케이션이나 시스템과 분리시켜 동작시킨다.

 

여기에 추가로 세분화된 보안 기능을 통해 URI마다 임시 사용권한을 부여하여 특정 데이터에 액세스할 수 있도록 할 수도 있다.

 

이 문서는 안드로이드에 의한 보안 기능을 개발자가 어떻게 사용할 수 있는지에 대해서 설명한다. 좀 더 일반적인 안드로이드 보안의 개요는 안드로이드 오픈 소스 프로젝트에서 제공된다.

 

 

보안 아키텍쳐(보안시스템의 구조; Security Archtecture) 


 

안드로이드 보안 아키텍쳐의 주요 골자는 기본적으로 어떠한 어플리케이션도 또다른 어플리케이션이나 운영체제, 사용자와 충돌이 있을만한 동작을 할 권한을 주지 않는다는 것이다. 충돌이 있을만한 동작이라 함은 사용자의 개인정보(주소록이나 이메일 같은)나 또다른 어플리케이션의 파일의 액세스, 네트워크 사용, 기기가 계속 켜져있게 하는 등의 동작으로 의미한다.

 

각각의 안드로이드 어플리케이션은 별도의 프로세스 샌드박스 안에서 동작하기 때문에 공유된 자원이나 데이터를 사용하고 싶다면 확실히 의사를 밝혀야 한다. 이것은 공유된 자원이나 데이터를 사용할 수 있는 권한을 선언함으로써 이루어진다. 어플리케이션이 필요한 권한을 선언하면 안드로이드 시스템은 어플리케이션이 설치될 때 이를 사용자에게 알리고 승인을 받는다. 안드로이드는 런타임(실행 중)에 동적으로 권한을 부여하지는 않는다. 왜냐하면 이런 방법은 사용자를 혼란시켜 보안에 헛점이 발생할 소지가 있기 때문이다.

 

샌드박스(sandbox)

 

보호된 영역 내에서 프로그램을 동작시키는 것으로, 외부 요인에 의해 악영향이 미치는 것을 방지하는 보안 모델. ‘아이를 모래밭(샌드 박스)의 밖에서 놀리지 않는다’라고 하는 말이 어원이라고 알려져 있다. 이 모델에서는 외부로부터 받은 프로그램을 보호된 영역, 즉 ‘상자’ 안에 가두고 나서 동작시킨다. ‘상자’는 다른 파일이나 프로세스로부터는 격리되어 내부에서 외부를 조작하는 것은 금지되고 있다.

 

출처 - [네이버 지식백과] 샌드박스 [sandbox] (IT용어사전, 한국정보통신기술협회)

 

어플리케이션 샌드박스는 어플리케이션 빌드를 위한 기술에 의존하지는 않는다. Dalvik VM은 보안의 경계가 아니며 어떤 앱이더라도 네이티브 코드로 실행이 가능하다(the Android NDK 참조). 모든 형식의 어플리케이션(Java, Native, 하이브리드)은 같은 방식으로 샌드박스 안에서 동작하게 되며 서로에 대해 동일한 보안 수준을 갖게 된다.

 

어플리케이션 서명하기(Application Signing) 


 

모든 APK(.apk 파일)는 개발자를 나타내는 개인키로 확인 서명을 해 줘야 한다. 이 확인키는 어플리케이션의 제작자를 구분해준다. 이 확인키는 인증을 받을 필요도 없으며 안드로이드 어플리케이션을 위한 자기 스스로의 증명키다. 확인키의 목적은 어플리케이션의 저자를 구분하기 위한 것이다. 이것은 시스템이 어플리케이션에 signature-level 퍼미션에 액세스 할 수 있도록 하거나 다른 어플리케이션과 동일한 리눅스 ID를 받고자 하는 요청을 할 수 있도록 해준다.

 

사용자 ID와 파일 액세스(User IDs and File Access)


 

앱을 설치할 때 안드로이드는 각 패키지에 고유의 리눅스 사용자 ID를 부여한다. ID는 프로그램이 기기에 설치되어 있는 동안 상수로써 존재하게 된다. 다른 기기에서는 같은 패키지라고 하더라도 다른 사용자 ID를 가질 수도 있다. 중요한 것은 각각의 패키지가 하나의 기기내에서는 고유한 UID를 갖는다는 사실이다.

 

보안 시스템은 프로세스 레벨에서 실행되기 때문에 두 개의 패키지가 서로 다른 유저ID를 가지고 있다면 일반적으로는 같은 프로세스에서 실행되기는 어렵다. 각 패키지가 같은 사용자 ID를 갖도록 하려면 각 패키지의 AndroidManifest.xml파일에 있는 manifest 태그의 sharedUserID 속성을 사용해야 한다. 이렇게 하면 (같은 사용자 ID를 갖게 되면) ID와 권한이 동일한 어플리케이션으로 취급받게 된다. 보안을 유지하고 싶다면 오직 두 개의 어플리케이션에서만 같은 사용자 ID를 갖도록 해야한다. 같은 사용자 ID를 갖도록 하려면 두 개의 앱은 같은 서명으로 발행되어야 하며 같은 sharedUserID를 요청해야 한다.


어플리케이션에서 저장된 모든 데이터는 해당 어플리케이션의 사용자 ID에 귀속되며 일반적으로 다른 패키지에서는 접근이 제한된다. getSharedPreferences(String, int), openFileOutput(String, int), 또는 openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)를 사용하여 새로운 파일을 생성할 때 다른 패키지에서 파일을 읽고 쓸 수 있도록 하기 위해서 MODE_WORLD_READABLE 과/또는 MODE_WORLD_WRITEABLE 플래그를 사용할 수 있다. 이 플래그를 설정하려면 이 파일은 여러분이 만든 어플리케이션에 속해있어야 하며 설정이 완료되면 다른 어플리케이션에서도 파일을 읽고 쓸 수 있도록 전역 권한이 부여된다. 


퍼미션 사용하기(Using Permissions)



안드로이드 어플리케이션을 처음 생성하면 기본적으로 아무런 퍼미션도 갖고 있지 않다. 이것은 어플리케이션이 기기의 어떤 데이터에도 영향을 끼칠 수 없다는 것을 의미한다. 기기에서 보호되고 있는 기능을 사용할 수 있도록 하려면 어플리케이션에서 필요로 하는 권한을 AndroidManifest.xml에 하나 이상의 <uses-permission>태그로 선언해줘야 한다.


아래 예제는 수신되는 SMS를 감시하기 위해 필요한 권한을 선언하는 방법이다.


1
2
3
4
5
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.app.myapp" >

    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    ...
</manifest>

 

어플리케이션에서 요청했던 퍼미션은 어플리케이션이 설치될 때 패키지 설치관리자에 의해 부여 된다. 이런 퍼미션은 그냥 부여되는 것이 아니라 퍼미션을 요청한 어플리케이션의 서명을 확인하고 사용자에게 의견을 물어본 후에 결정되는 것이다. 어플리케이션이 실행되고 있는 동안에 사용자에게 퍼미션을 요청하는 일은 없다. 앱은 설치할 때 받은 퍼미션이 있다면 원할 때 언제든 연관된 기능을 사용할 수 있지만 퍼미션이 없다면 연관된 기능을 사용하려할 때 사용자에게 다시 물어보는 일 없이 무조건 실패하게 된다.


가끔 권한의 부재 때문에 securityException이 발생하기도 한다. 그러나 이 예외가 항상 발생하는 것은 아니다. 예를 들어, sendBroadcast(Intent) 함수는 호출을 하고 리턴이 된 후에 각 리시버에 데이터가 전달이 되면 그 때서야 권한을 확인하게 된다. 그래서 만일 권한 부재로 인한 문제가 있어도 예외가 발생했는지 알 수가 없다. 하지만 거의 대부분의 경우 권한 부재로 인한 문제는 시스템 로그에 기록이 남게 된다.


하지만 일반적인 사용자의 경우(구글 플레이 스토에서 앱을 설치하는 경우), 앱에서 요청하는 권한을 사용자가 승인하지 않으면 앱을 설치되지 않는다. 그렇게 때문에 일반적으로 권한 부재에 따른 실행 중 오류는 크게 걱정하지 않아도 된다.


안드로이드 시스템에서 제공하는 권한은 Manifest.permission에서 확인할 수 있다. 어떤 어플리케이션은 자체적인 권한을 정의하고 요구할 수도 있게 때문에 사용 가능한 모든 권한의 목록은 아니다.


자체적인 권한은 프로그램이 동작하는 도중에 여러 곳에서 요청될 수 있다.


  • 시스템에서 호출하려고 하는 순간 어플리케이션이 특정 함수를 실행하는 것을 막기 위하여
  • 액티비티를 시작할 때 어플리케이션이 다른 어플리케이션의 액티비티를 실행하는 것을 막기 위하여
  • 브로드케스트를 보내거나 받을 때 브로드캐스트를 받는 쪽이나 브로드캐스트를 보내는 쪽을 제어하기 위해
  • 컨텐트 프로바이더에 접근하여 뭔가 작업을 수행하려고 할 때
  • 서비스에 바인딩하거나 서비스를 시작할 때


Caution : 시간이 지나면 어떤 API를 실행하기 위해 이전에는 요구하지 않았던 어떤 권한을 요구할 수도 있기때문에 새로운 제한 조건이 플랫폼에 추가될 지도 모른다. 기존의 앱은 사용중인 API를 자유로이 사용할 수 있다고 생각하기 때문에 안드로이드는 앱이 새로운 플랫폼에서 문제가 되는 것을 방지하기 위해 앱의 매니페스트에 새로운 권한 요구를 추가시킨다. 안드로이드는 targetSdkVersion 값을 기반으로 새로운 권한이 추가되어야 할 지 여부를 판단하게 된다. 만일 새로운 권한이 추가된 플랫폼의 버전보다 이 값이 낮다면 권한을 추가하게 된다.


예를 들어, WRITE_EXTERNAL_STORAGE 권한은 공유된 저장 공간에 접근하는 것을 제한하기 위해 API 레벨 4부터 추가되었다. 만일, 여러분의 앱에 설정된 targetSdkVersion이 3이하라면 그 이후의 안드로이드 버전에서는 여러분의 앱에 이 권한이 추가될 것이다.


만일 이런 상황이 벌어진다면 여러분의 앱이 실제로 그 권한을 필요로 하지 않더라도 구글 플레이에서 사용자가 앱을 설치하려고 하면 권한을 요구하게 된다는 사실을 명심하기 바란다.


여러분의 앱이 필요로 하지 않는 권한이 기본적으로 요구되지 않도록 하려면 항상 targetSdkVersion이 가능한한 최대한 높게 유지되도록 업데이트 해줘야 한다. 새로운 플랫폼에 어떤 권한이 새로 추가되었는지 확인하려면 Build.VERSION_CODES 문서를 참고할 수 있다.



권한을 선언하고 요청하기(Declaring and Enforcing Permissions)


 

권한을 요청하기 위해서는 먼저 AndroidManifest.xml<permission> 태그를 사용하여 권한을 선언해야만 한다.


예를 들어, 어떤 어플리케이션이 여러분의 앱에 있고 외부 실행이 가능한 액티비티 중에 하나를 시작하려고 한다면 아래와 같이 권한을 선언해야 한다.


1
2
3
4
5
6
7
8
9
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.me.app.myapp" >

    <permission android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY"
        android:label="@string/permlab_deadlyActivity"
        android:description="@string/permdesc_deadlyActivity"
        android:permissionGroup="android.permission-group.COST_MONEY"
        android:protectionLevel="dangerous" />

    ...
</manifest>

<protectionLevel> 속성은 필수 사항이며 링크된 문서에 설명된 대로 어플리케이션이 요구하는 권한을 사용자에게 어떻게 알릴 것이며 누가 권한을 가질 수 있는지 시스템에 알려준다.


<permissionGroup>은 옵션 사항이며 시스템이 사용자에게 요구 권한을 표시하는 것을 돕기 위해서만 사용된다. 여러분은 통상적으로 이 속성을 표준 시스템 그룹(android.Manifest.permission_group에 있는 그룹)에 적용하거나 여러분이 정한 특별한 경우에 사용하게 되는데 이 속성은 사용자에게 보여주는 권한 요청 UI를 단순화시키기 위해 기존 그룹에 사용하는 것이 좋다.


필요한 권한을 선언할 때는 레이블과 설명이 함께 제공되어야 한다. 이것들은 문자열 리소스로써 사용자에게 권한의 리스트(android:label)를 보여주거나 하나의 권한에 대해 자세한 설명(android:description)을 보여줄 때 사용된다.  레이블은 권한을 주어 보호하려는 기능을 잘 표현할 수 있는 한두개 정도의 단어로 구성되도록 한다. 설명은 권한이 보호하려는 게 무엇인지에 대해 두 개의 문장으로 구성해야 한다. 첫번째 문장은 권한이 의미하는 바가 무엇인지, 두번째 문장은 사용자가 어플리케이션에 해당 권한을 부여했을 때 발생할 수 있는 나쁜 점은 무엇인지에 대해 쓰여져야 한다.


다음은 CALL_PHONE이라는 권한에 대한 레이블과 설명에 대한 예제다.


1
2
3
4
5
<string name="permlab_callPhone">directly call phone numbers</string>
    <string name="permdesc_callPhone">Allows the application to call
        phone numbers without your intervention. Malicious applications may
        cause unexpected calls on your phone bill. Note that this does not
        allow the application to call emergency numbers.</string>


현재 시스템에 정의된 권한들은 설치된 앱과 쉘커맨드 adb shell pm list permissions를 통해서 알아볼 수 있다. 설치된 앱을 통해 확인하는 방법은 설정 > 어플리케이션으로 이동한 후 하나의 앱을 선택하고 아래로 스크롤하면 된다. 개발자를 위한 방법으로는 adb에 '-s'옵션을 주면 사용자가 보는 것과 비슷하게 퍼미션들이 표시된다.


$ adb shell pm list permissions -s
All Permissions:

Network communication: view Wi-Fi state, create Bluetooth connections, full
Internet access, view network state

Your location: access extra location provider commands, fine (GPS) location,
mock location sources for testing, coarse (network-based) location

Services that cost you money: send SMS messages, directly call phone numbers

...


AndroidManifest.xml을 통해서 퍼미션 적용하기(Enforcing Permissions in AndroidManifest.xml)


시스템이나 어플리케이션의 전체 컴포넌트들에 대한 액세스를 제한하기 위한 고수준의 권한은 AndroidManifest.xml을 통해서 적용이 가능하다. 이러한 모든 요청은 제한하고자 하는 컴포넌트의 android:permission 속성을 설정하고 권한의 이름을 정해줘야 한다.


Activity 퍼미션(<activity>태그에 적용한다.)은 해당 액티비티의 시작 가능여부를 제한한다. 이 때 권한은 Context.startActivity()Activity.startActivityForResult()를 호출할 때 확인한다. 만일 호출자가 요구되는 권한을 갖고 있지 않으면 호출할 때 SecurityException이 발생한다.


Service 퍼미션(<service>태그에 적용한다.)은 해당 서비스를 시작시키거나 바인딩 할 수 있는지 여부를 제한한다. 권한은 Context.startService(), Context.stopService(), Context.bindService()를 하는 동안 확인한다. 만일 호출자가 요구된 권한을 갖고 있지 않으면 SecurityException이 발생한다.


BroadcastReceiver 퍼미션(<receiver>태그에 적용한다.)은 해당 리시버에 브로드캐스트를 보내는 것을 제한한다. 권한은 Context.sendBroadcast() 함수가 리턴(종료)을 하고 목표 리시버에 브로드캐스트 내용을 전달하려고 할 때 체크하게 된다. 그래서 권한 부족이라는 문제가 발생해도 호출자에 예외상황을 알리지 않는다. 그저 인텐트만 전달하지 못 할 뿐이다. 같은 이유로 브로드캐스트리시버를 프로그램 코드에서 등록하는 Context.registerReceiver()의 사용 여부를 결정짓는 권한도 마찬가지 상황에 처하게 된다. 정반대의 상황으로 Context.sendBroadcast()를 호출할 때 브로드캐스트를 수신하는 브로드캐스트 리시버를 제한하도록 퍼미션을 사용할 수도 있다.(이후 내용 참조)


ContentProvider의 퍼미션(<provider>태그로 적용시킬 수 있다.)은 ContentProvider의 데이터에 액세스하는 것을 제한한다. (컨텐트 프로바이더는 이후에 설명될 URI 퍼미션이라 불리는 중요한 보안장치를 추가로 가지고 있다.) 컨텐트 프로바이더는 다른 컴포넌트들과 달리 두 개의 퍼미션 속성을 설정할 수 있다. android:readPermission은 프로바이더로 부터 읽는 것을 제한하고 android:writePermission은 쓰는 것을 제한한다. 만일 읽기와 쓰기에 대해 퍼미션을 요구하는 프로바이더는 쓰기 권한을 가졌다고 해도 읽기를 수행할 수 없다는 것을 명심하자. 퍼미션 확인은 프로바이더를 처음 획득하게 될 때(읽기, 쓰기 권한이 둘 다 없다면 SecurityException이 발생한다.)와 프로바이더를 통해 작업을 수행할 때 이루어진다. ContentResolver.query() 함수를 사용하려면 읽기 권한이 필요하고 ContentResolver.insert(), ContentResolver.update(), ContentResolver().delete() 함수를 사용하려면 쓰기 권한이 필요하다. 이 모든 경우에 필요한 권한을 갖고 있지 않다면 SecurityException 예외가 발생한다.


브로드캐스팅 할 때 권한 적용하기(Enforcing Permissions when Sending Broadcasts)


등록된 BroadcastReceiver에 인텐트를 보낼 때 권한을 요구하는 것(앞서 설명되었다.) 외에도 브로드캐스트를 보낼 때 받는 쪽에도 권한을 요구할 수 있다. 퍼미션 문자열과 함께 Context.sendBroadcast()를 호출하게 되면 리시버가 속한 어플리케이션은 여러분이 보낸 브로드캐스트를 받기 위해서 요구된 퍼미션을 갖고 있어야만 한다.


리시버와 브로드캐스터 양쪽 모두 퍼미션을 요구할 수 있다는 사실을 명심하기 바란다. 이 경우 관련 대상에 전달되는 인텐트는 퍼미션 검사를 통과할 수 있어야 한다.


다른 사용권한 사용하기(Other Permission Enforcement)


세분화된 퍼미션은 서비스로의 어떠한 호출에도 적용할 수 있다. 그러면 어떻게 세분화되어 있는 많은 퍼미션을 가지고 있는지 알 수 있을까? 이것은 Context.checkCallingPermission() 메서드를 통해 해결할 수 있다. 필요한 퍼미션 문자열과 함께 호출하면 현재 호출하고 있는 프로세스가 퍼미션을 갖고 있는지를 나타내는 정수를 반환한다. 이 메서드는 일반적으로 서비스나 다른 프로세스로 부터 게시된 IDL 인터페이스를 통해 호출되는 경우에만 사용할 수 있다.(마지막 문장은 무슨 뜻인지 모르겠습니다. ^^;)


퍼미션을 확인하는 다른 유용한 방법도 많이 있다. 만일 여러분이 다른 프로세스의 PID를 알고 있다면 Context.checkPermission(String, int, int)메서드를 사용하여 해당 PID를 갖고 있는 프로세스가 적절한 퍼미션을 갖고 있는지 체크할 수 있다. 만일 다른 어플리케이션의 패키지 이름을 알고 있다면 패키지 매니저의 메서드인 PackageManager.checkPermission(String, String)을 통해 해당 패키지가 지정된 퍼미션을 갖고 있는지 알아낼 수 있다.


URI 사용권한(URI Permissions)



지금까지 설명한 표준 퍼미션 시스템은 컨텐트프로바이더를 사용할 때 종종 부족한 감이 있다. 컨텐트 프로바이더는 읽고 쓰기 퍼미션으로 자신을 지키고자 할 것이다. 반면 특정 URI를 다른 어플리케이션에 넘겨 그쪽에서 작업이 이루어지도록 해야할 수도 있다. 전형적인 예로 이메일에 첨부된 파일을 들 수 있다. 메일은 민감한 개인정보이기 때문에 퍼미션을 주어 액세스로부터 보호해야 한다. 그러나 만일 첨부된 파일이 이미지이고 그 이미지를 이미지 뷰어 앱으로 보려면 어떻게 해야할까. 이미지 뷰어 앱은 모든 이메일에 대해 퍼미션을 가질 필요가 없었기 때문에 십중팔구 첨부파일을 열어보기 위한 퍼미션을 갖고 있지 않을 것이다.


이 문제에 대한 해결은 URI 퍼미션이 답이다.; 액티비티를 시작하거나 액티비티에 결과를 반환할 때 호출자 쪽에서는 Intent.FLAG_GRAND_READ_URI_PERMISSION 과/또는 Intent.FLAG_GRANT_WRITE_URI_PERMISSION을 설정할 수 있다. 이 플래그들은 인텐트의 특정 URI를 수신하는 액티비티가 프로바이더의 데이터에 대해 퍼미션을 갖고 있건 말건 상관없이 특정 URI에 대해 접근할 수 있는 권한을 부여해준다.


이 메카니즘은 사용자와의 상호작용(첨부파일을 열거나 특정 연락처를 선택하는 등의 행동)을 통해 세분화된 퍼미션에 임시로 권한을 부여하는게 가능하도록 해준다.


그러나 이런 세분화된 URI 퍼미션을 부여하는 것은 URI를 보유하고 있는 컨텐트 프로바이더와 약간의 협력이 필요하다. 컨텐트 프로바이더는 이 기능을 구현하고 android:grantUriPermissions 속성이나 <grand-uri_permission>태그를 이용해 기능을 지원한다는 사실을 외부로 알려야 한다.


Context.grantUriPermission(), Context.revokeUriPermission(), Context.checkUriPermission() 메서드를 통해 좀 더 많은 정보를 구할 수 있다.



안드로이드 보안!!! 정말 어려웠습니다.


내용에 많은 오류가 있겠지만 잘 걸러서 읽어주세요.


다음 글 >> 인텐트와 인텐트 필터(Intents and Intent Filters) (1)

이전 글 >> 기기 호환성(Device Compatibility)

 


posted by 리치크루

API GUIDE/1. Introduction
2014. 2. 16. 19:21

저작권 표시 : 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.


현재 보시는 페이지는 안드로이드 오픈 소스 프로젝트에 의해 작성되고 공유된 작업물의 수정/번역본이며 크리에이티브 커먼즈 저작자 표시 2.5 라이센스에 기술된 조건의 사용 근거를 따른 것입니다.


본 문서의 원본은 http://developer.android.com/guide/practices/compatibility.html이며 안드로이드 4.4 Kitkat 기준으로 설명되었습니다.


기기 호환성(Device Compatibility)


안드로이드는 전화, 태블릿, TV에 이르기까지 매우 다양한 기기에서 동작하도록 디자인되었다. 이것은 개발자로써 수많은 잠재 고객을 갖고 있음을 의미한다. 여러분의 앱이 이러한 모든 기기에서 성공을 거두길 바란다면 사양이 가지각색인 기기들에서도 잘 동작하고 다양한 해상도의 기기들에 대해서 유연하게 대응할 수 있는 UI를 제공해야한다.


이러한 것을 쉽게 하기 위해서 개발자가 다양한 환경에 맞는 앱 리소스를 제공하면 안드로이드가 환경에 따라 적절한 앱 리소스를 사용하는 동적인 앱을 제공하는 구조로 되어 있다. 예를 들어, 앱에서 "안녕하세요."라는 문자열을 사용한다고 하면 영어를 사용하는 기기에서는 "Hello"라는 문자열을, 한국어를 사용하는 기기에서는 "안녕하세요."가 표시될 수 있도록 두 개의 문자열 리소스를 하나의 앱에 포함시키는 것이다. 그러면 안드로이드가 기기에서 사용되는 언어가 영어이면 "Hello"를, 한국어이면 "안녕하세요."를 표시하게 된다. 그래서 여러분이 여러 개의 디자인과 리소스를 준비하면 다양한 기기에 최적화된 UI를 갖는 단일 애플리케이션 패키지(APK)를 만들어 낼 수 있다.


그러나 필요하다면 여러분의 앱이 요구하는 기기의 사양을 지정하여 구글 플레이 스토어에서 여러분의 앱이 설치될 수 있는 기기들을 선별할 수 있다. 이 글에서는 어떻게 하면 여러분의 앱을 설치할 수 있는 기기를 제어할 수 있는지 그 방법을 설명한다. 여러분의 앱이 다양한 사양의 기기들에서 동작하도록 제어하는 방법에 대해 더 많은 정보를 얻고 싶다면 Supporting Different Devices를 읽어보기 바란다.


"호환성"이란 무엇을 의미하는가?
(What Does "Compatibility" Mean?)



안드로이드 개발에 대한 글을 읽다보면 여러 곳에서 "호환성(compatibility)"이라는 용어를 맞닥뜨리게 된다. 호환성이라는 것은 크게 기기의 호환성과 앱의 호환성으로 생각해 볼 수 있다.


안드로이드는 오픈 소스 프로젝트이기 때문에 어느 하드웨어 제조사에서 만들어진 기기라도 안드로이드 운영체제를 탑재시킬 수 있다. 그러나 안드로이드 실행 환경에 맞추어 개발된 앱이 올바르게 실행될 수 있어야 그 기기를 "안드로이드 호환"이라고 할 수 있다. 안드로이드 실행환경에 대한 자세한 내용은 Android compatibility program에 정의되어 있으며 각 기기는 호환성 테스트 슈트(CTS; Compatibility Test Suite)를 통과해야만 한다.


앱 개발자 입장에서는 대상 기기가 안드로이드 호환 기종인지 아닌지에 대해서는 고민할 필요가 없다. 왜냐하면 안드로이드 호환 기종만이 구글 플레이 스토어에 접속할 수 있기 때문이다. 따라서 구글 플레이 스토어를 통해 앱을 설치하였다면 그 기기는 안드로이드 호환 기기인 것이다.


그러나 모든 기기의 구성에 대해서 여러분의 앱이 호환성을 가지고 있는지 확실히 할 필요가 있다. 왜냐하면 안드로이드는 매우 다양한 기기에 탑재되어 있으며 모든 기기의 사양이 다 같지는 않기 때문이다. 예를 들어, 어떤 기기는 나침반 센서가 없을 수 도 있다. 만일 여러분이 만든 앱의 핵심기능이 나침반 센서를 반드시 필요로 한다면 그 앱은 나침반 센서가 있는 기기에서만 호환이 되는 것이다.


기기에 대한 앱의 가용성 제어하기
(Controlling Your App's Availability to Devices)



안드로이드는 플랫폼 API를 통해 앱이 활용할 수 있는 다양한 기능을 제공한다. 일부 기능은 하드웨어를 기반(나침반 센서 같은)으로 한 것이고 일부 기능은 소프트웨어를 기반(앱 위젯 같은)으로 한 것이며 또 일부는 플랫폼 버전에 기반한 것이다. 모든 기기가 모든 기능을 지원하는 것은 아니다. 그래서 여러분의 앱이 필요로하는 기능에 따라 기기에 대한 앱의 가용성을 제어해야 할 수도 있다.


여러분의 앱을 최대한 많은 사람들이 사용할 수 있게 하려면 하나의 APK로 최대한 많은 종류의 기기에서 동작할 수 있도록 노력해야 한다. 대부분의 경우에는 실행 중에 특정 기능이 사용되지 않도록 하거나 다양한 기기의 환경에 맞춰 다른 앱 리소스를 제공함으로써 보다 많은 기기에서 동작하도록 할 수 있다. 다양한 기기의 환경이라하면 스크린의 해상도가 다르다거나 사용언어가 다르다거나 하는 것을 말한다. 하지만 필요하다면 다음의 조건들을 제약사항으로 하여 여러분이 작성한 앱이 설치될 기기를 제한할 수도 있다.


기기의 지원 기능(Device features)

기기의 지원 기능에 따라 앱의 가용성을 결정할 수 있도록 하기 위해서 안드로이드는 특정한 기능마다 고유의 기능 코드를 부여하고 있다. 예를 들어, 나침반 센서의 기능 코드는 FEATURE_SENSOR_COMPASS이며 앱 위젯을 위한 기능 코드는 FEATURE_APP_WIDGETS이다.


필요하다면 매니페스트 파일에 <uses-feature>엘리먼트를 선언하여 지정된 기능을 지원하지 않는 기기는 앱이 설치되지 않도록 할 수 있다.


예를 들어, 여러분의 앱이 나침반 센서 없이는 동작할 수 없는 구조라면 다음과 같이 매니페스트에 필수 요구사항으로 나침반 센서를 선언할 수도 있다.


1
2
3
4
5
<manifest ... >
    <uses-feature android:name="android.hardware.sensor.compass"
                  android:required="true" />

    ...
</manifest>
 


구글 플레이 스토어는 여러분의 앱에서 요청하는 기능과 사용자의 기기가 지원하는 기능들을 비교하여 앱이 기기에서 올바르게 동작할 수 있는지를 판단한다. 여러분의 앱이 요구하는 여러 기능을 하나라도 지원하지 않는 기기가 있다면 그 기기는 여러분이 만든 앱을 설치할 수 없다.


그러나 기기에 없는 기능이 여러분의 앱이 동작하는데 꼭 필요한 기능이 아니라면 required 속성을 "false"로 설정하고 실행 중에(runtime) 기기의 기능을 체크할 수도 있다. 앱의 특정 기능이 해당 기기에서 동작할 수 없는 상태라면 그 기기에서만 특정 기능이 동작하지 않도록 하면 된다. 이것은 hasSystemFeature()를 호출하여 기기가 해당 기능을 지원하는지 여부를 확인함으로써 구현이 가능하다.


1
2
3
4
5
PackageManager pm = getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_SENSOR_COMPASS)) {
    // This device does not have a compass, turn off the compass feature
    disableCompassFeature();
}


구글 플레이 스토어에서 특정 기능이 없는 기기를 필터링하는 방법에 대해 더 알고 싶다면 Filters on Google Play 문서를 읽어보기 바란다.


Note : 일부 시스템 퍼미션은 암시적으로 기기의 특정 기능을 요구하기도 한다. 예를 들어 여러분의 앱이 블루투스의 사용 권한을 요청하게 되면 이는 묵시적으로 기기의 FEATURE_BLUETOOTH를 필요로 하게 된다. 이런 경우 <uses-feature>태그의 required속성을 "false"로 설정하여 필터링에 걸리지 않도록 할 수 있다. 암시적으로 기기의 특정 기능을 요구하는 상황에 대해 좀 더 많은 정보가 필요하다면 Permissions that Imply Feature Requirements를 읽어보기 바란다.


플랫폼 버전(Platform version)

어떤 기기는 안드로이드 4.0이 설치되어 있고 또 어떤 기기는 안드로이드 4.4가 설치되어 있을 수 있다. 안드로이드 플랫폼 버전이 올라가면서 이전 버전에서는 존재하지 않아서 사용할 수 없는 새로운 API가 추가되기도 한다. 각 플랫폼 버전은 어떤 API 세트가 사용되는지 알기 쉽도록 하기 위해 API 레벨이라는 것을 갖고 있다. 안드로이드 1.0의 API 레벨은 1이고 안드로이드 4.4의 API 레벨은 19로 하는 것처럼 말이다.


매니페스트에 <uses-sdk>태그에 minSdkVersion 속성을 이용해 여러분의 앱이 호환되는 최소 API 레벨을 지정할 수 있다.


예를 들어, Calendar Provider API는 안드로이드 4.0(API 레벨 14)에서 추가됐다. 만일 여러분의 앱이 이 API가 없이는 동작할 수 없다면 다음과 같이 앱이 요구하는 최소 플랫폼 버전을 API 레벨 14라고 선언해야 한다.


1
2
3
4
<manifest ... >
    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" />
    ...
</manifest>


minSdkVersion 속성을 앱이 호환되는 최소 버전을 선언하는 데 쓰이며 targetSdkVersion 속성을 앱이 최적화 되어 있는 최상위 버전을 선언하는 데 쓰인다.


새로운 안드로이드 버전은 이전 버전의 API를 사용한 앱에 대해 호환성을 갖도록 만들어진다. 그렇기 때문에 도큐먼트가 제공되는 안드로이드 API는 미래에 출시될 새로운 안드로이드 버전에서도 항상 호환성을 갖게 될 것이다.


Note : targetSdkVersion속성은 지정된 값 보다 더 높은 플랫폼 버전이 설치되어 있더라도 여러분의 앱을 설치하는 데는 문제가 없다. 하지만 이 속성은 중요하다. 왜냐하면 새로운 플랫폼 버전에서 변경된 내용을 적용할 지 말지를 결정하기 때문이다. 만일 여러분이 targetSdkVersion를 최신 버전으로 업데이트 하지 않았다면 최신 버전의 플랫폼이 동작하고 있더라도 여러분이 앱을 만들었던 이전 버전의 API가 하던 동작방식대로 실행되기 때문이다. 예를 들어, 안드로이드 4.4의 변경된 내용 중의 한 가지를 살펴보자. 안드로이드 4.4에서는 AlarmManager API로 생성된 알람은 기본적으로 정확하지 않다. 그래서 시스템에 등록된 앱의 알람들을 일괄적으로 처리하고 전력소비를 낮춘다. 하지만 여러분이 지정한 타겟 API 레벨이 19보다 낮다면 알람으로 인해 전력소비를 낮추는 잇점은 활용할 수 없게 된다.


하지만 여러분이 만든 앱의 주요 기능이 아닌 일부가 최신 버전의 API를 사용한다고 하자. 그렇다면 실행 중에 기기에 설치된 API 레벨을 확인하고 특정 기능을 구현하는데 요구되는 API 레벨보다 레벨이 낮다면 적절히 기능을 낮춰 줘야한다. 이런 경우 minSdkVersion은 주요 기능이 동작할 수 있는 최소한으로 지정하고 SDK_INT(시스템 버전)을 코드네임 상수인 Build.VERSION_CODES와 비교하여 여러분이 원하는 API 레벨인지 확인해야 한다. 아래는 그 예제다.


1
2
3
4
5
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
    // Running on something older than API level 11, so disable
    // the drag/drop features that use ClipboardManager APIs
    disableDragAndDrop();
}

스크린 구성(Screen configuration)

안드로이드는 전화기에서 태블릿, TV까지 다양한 크기의 기기에서 동작한다. 기기들을 스크린 종류에 따라 분류하기 위해 안드로이드는 각 기기들마다 두 가지 특징으로 한정짓는다. - 스크린의 사이즈(스크린의 물리적인 크기)와 스크린의 해상도(흔히 DPI라고 불리는 스크린에 있는 픽셀의 해상도.) 기기마다 서로 다은 이런 특징들을 단순히 하기 위해 안드로이드는 이러한 변수들을 쉽게 그룹화하기 위하여 다음과 같은 분류 기준을 사용한다.

  • 4종류 사이즈 : small, normal, large, xlarge
  • 몇몇 종류의 해상도 : mdpi(medium), hdpi(hdpi), xhdpi(extra high), xxhdpi(extra-extra high) 그리고 기타 종류들.

기본적으로 여러분의 앱은 모든 스크린 사이즈와 해상도에 대해 호환이 된다. 왜냐하면 시스템이 UI 레이아웃과 이미지 리소스를 각 스크린에 맞춰 적절히 조절해주기 때문이다. 하지만 사용자에게 보다 나은 환경을 제공하기 위해 다양한 스크린 사이즈에 특화된 레이아웃과 다양한 스크린 해상도에 최적화된 비트맵 이미지를 제공해야 한다.


다양한 스크린의 종류에 맞게 대체 리소스를 생성하는 법과 특정 사이즈의 스크린을 가진 기기에서 앱 설치가 제한되도록 하는 법에 대해 더 알고 싶다면 Supporting Different Screens를 읽어라.


업무적인 이유에 의한 앱의 가용성 제어
(Controlling Your App's Availability for Business Reasons)



기기의 특성 때문에 여러분이 만든 앱의 가용성을 제한하는 것 외에 업무적인 또는 법적인 문제로 앱의 가용성을 제한해야 할 때가 있다. 예를 들어 런던의 지하철 시간표를 제공하는 어떤 앱이 있다고 하자. 그렇다면 이 앱은 영국에 있는 사람이 아니라면 그다지 필요가 없다. 이런 경우 구글 플레이 스토어는 개발자 콘솔을 통해 기술적인 이유가 아닌 사용자의 로케일이나 무선통신의 상태에 따라 앱의 가용성을 제한하는 옵션을 제공한다.


하드웨어의 기능을 필요로하는 기술적인 호환성에 기인한 필터링은 앱의 APK 파일에 있는 정보에 의해 적용된다. 하지만 지리적 위치와 같은 비기술적인 문제로 인한 필터링은 구글 플레이의 개발자 콘솔을 통해 적용된다.


번역이 생각보다 쉬운 일이 아니네요.


원문을 읽을 때는 사전 찾아가며 떠듬떠듬 읽어도 무슨 뜻인지는 대충 알겠는데 우리말로 자연스럽게 만들기란 참 쉽지 않은 작업이네요.


앞으로 더 열심히 해야겠네요. 영어공부를...


다음 글 >> 시스템 퍼미션(System Permissions)

이전 글 >> 응용프로그램의 기초(Application Fundamentals)


posted by 리치크루

API GUIDE/1. Introduction
2014. 2. 16. 19:20

저작권 표시 :

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.


현재 보시는 페이지는 안드로이드 오픈 소스 프로젝트에 의해 작성되고 공유된 작업물의 수정/번역본이며 크리에이티브 커먼즈 저작자 표시 2.5 라이센스에 기술된 조건의 사용 근거를 따른 것입니다.


본 문서의 원본은 http://developer.android.com/guide/components/fundamentals.html이며 안드로이드 4.4 Kitkat 기준으로 설명되었습니다.

 

응용프로그램의 기초
(Application Fundamentals)


안드로이드의 앱은 자바라는 언어로 작성된다. 안드로이드 SDK툴은 여러분이 작성한 코드와 데이터, 리소스 파일들을 안드로이드 패키지(확장자가 .apk인 파일)로 컴파일한다. 하나의 APK파일은 안드로이드 앱의 모든 내용을 포함하며 안드로이드가 설치된 기기에서 앱을 설치할 수 있도록 해 준다.


기기에 설치가 되면 각 안드로이드 앱은 그 앱만의 독립된 공간에 존재하게 된다.

  • 안드로이드 운영체제는 다수의 사용자를 지원하는 리눅스 시스템이며 각 앱은 안드로이드의 사용자가 된다.
  • 기본적으로 시스템은 각 앱마다 고유한 리눅스 사용자 ID를 부여한다.(ID는 시스템에 의해서만 사용되며 앱은 알 수 없다.) 시스템은 앱에 속한 모든 파일들에 권한 설정을 하여 사용자 ID를 부여받은 앱이 그 파일들을 접근할 수 있도록 한다.
  • 각 프로세스는 각각 별도의 전용 가상머신(VM)을 갖고 있기 때문에 앱들은 다른 앱과 독립된 공간에서 실행된다.
  • 기본적으로, 모든 앱은 전용 리눅스 프로세스 안에서만 실행된다. 안드로이드는 앱의 구성요소들 중 어느 것이라도 실행될 필요가 있으면 프로세스를 시작시킨다.

이러한 방법으로 안드로이드 시스템은 주요권한을 최소한(principle of least privilege)으로 배분한다. 이것은 각 앱이 기본적으로 작업에 필요한 권한만을 부여받게 되는 것이며 그 이상의 권한은 없다는 이야기다. 각 앱은 권한이 부여되지 않은 시스템 영역을 접근하지 못하게 되어 시스템 보안이 안전하게 유지된다.


그러나 앱끼리 데이터를 공유하거나 시스템의 서비스를 사용할 수 있는 방법은 있다.

  • 두 개의 앱이 리눅스 사용자 ID를 공유하도록 할 수 있으며 이러한 경우 각 앱은 서로의 파일들을 사용할 수 있다. 시스템의 자원을 보존하기 위해서 같은 ID를 갖는 앱들은 같은 리눅스 프로세스에서 실행되고 같은 가상머신(VM)을 사용하도록 할 수 있다.(이 때 앱들은 같은 권한을 가지고 있어야 한다.)
  • 앱은 연락처, 단문메세지, 저장장치, 카메라, 블루투스 등과 같은 장치를 사용하기 위해서 권한을 요청할 수 있다. 모든 앱은 설치할 때 이러한 권한 요청을 하고 사용자가 허락해야만 한다.

지금까지 안드로이드 앱이 시스템에서 어떻게 동작하는지에 대해서 다루었다. 이 문서의 나머지 내용은 다음과 같다.

  • 앱을 구성하는 핵심 프레임워크 컴포넌트들(요소들)
  • 앱에서 선언하는 컴포넌트들과 사용하고자 하는 장치에 대해서 기술하는 매니페스트 파일
  • 앱과 분리되어 있고 다양한 기기의 구성에 따라 간편하게 최적화할 수 있도록 하는 리소스


앱 컴포넌트(App Components)



앱 컴포넌트는 안드로이드 앱의 기본 요소들이다. 앱을 구성하는 컴포넌트가 어떤 것이냐에 따라서 시스템이 앱을 시작시키는 방법이나 시점이 달라진다. 일부 컴포넌트는 사용자나 다른 앱에 의해서 시작되지만 모든 컴포넌트가 그런 것은 아니다. 그러나 사용자나 다른 앱에 의해서 시작되지 않는 컴포넌트가 필요없는 것은 아니다. 각각의 컴포넌트는 모두 그들만의 존재 이유가 있으며 특정한 역할을 수행한다. – 각 컴포넌트는 앱의 전체적인 동작을 구성하는 그들만의 유일한 행동패턴을 가지고 있다.


앱 컴포넌트는 4개의 종류가 있다. 각 유형별로 서로 다른 사용 목적과 라이프사이클(lifecycle; 역주 – 생성되고 파괴되는 주기)을 갖고 있다.


4종류의 앱 컴포넌트는 다음과 같다.(역주 – 4종류는 액티비티, 서비스, 브로드캐스트 리시버, 컨텐트 프로바이더)


액티비티(Activities)

액티비티는 한 개의 화면을 말한다. 예를 들어, 이메일 앱은 새로운 이메일의 리스트를 보여주는 액티비티, 이메일을 작성하는 액티비티, 이메일을 읽을 수 있는 액티비티를 가질 수 있다. 액티비티들은 모두 합쳐져서 이메일 앱을 구성하고 있지만 각기 독립성을 가지고 있다. 무슨 말이냐하면 다른 앱이 이 중 하나의 액티비티를 실행시킬 수 있다는 말이다.(물론 이메일 앱에서 허락을 해줘야만 가능하겠지만) 예를 들어, 카메라 앱은 촬영한 사진을 공유하기 위해서 새로운 이메일을 작성하는 액티비티를 실행시킬 수 있다.


액티비티는 보통 Activity의 서브클래스를 구현한다. 이에 대해서 좀 더 자세히 알려면 Activities 개발자 가이드를 참고하기 바란다.


서비스(Services)

서비스 컴포넌트는 백그라운드에서 오랜시간 동작하거나 리모트 프로세스를 수행하는 컴포넌트다. 서비스는 사용자 인터페이스. 즉, 화면을 제공하지 않는다. 예를 들어, 사용자가 다른 앱을 사용 중 일 때 백그라운드에서 음악을 재생하거나 사용자가 액티비티를 동작시키는 행위에 방해를 하지 않으면서 네트워크로 데이터를 가져오는 일을 수행하는 것이 서비스 컴포넌트다. 액티비티 같은 다른 컴포넌트는 서비스를 시작하고 실행되도록 하거나 서비스와 상호작용하기 위해서 바인딩하기도 한다.


서비스는 Service클래스의 서브클래스를 만들어 구현하며 좀 더 자세한 내용은 Services 개발자 가이드를 참조하기 바란다.


컨텐트 프로바이더(Content providers)

컨텐트 프로바이더는 공유되는 앱의 데이터를 관리한다. 여러분은 데이터를 파일, SQLite 데이터베이스, 웹 등의 앱이 접근할 수 있는 영구 저장소에 저장할 수 있다. 컨텐트 프로바이더를 통해 다른 앱이 데이터에 쿼리를 날리거나 수정할 수도 있다.(물론 이런 경우 컨텐트 프로바이더가 허락을 해줘야 하겠지만) 예를 들어, 안드로이드 시스템은 사용자 연락처 정보를 관리하고 컨텐트 프로바이더를 제공한다. 그래서 어떤 앱이든 권한만 충분하다면 사람들의 연락처 정보를 읽고 쓸 수 있다.( ContactsContract.Data 같은 것을 통해서 할 수 있다.)


컨텐트 프로바이더는 공유되지 않는 사적인 데이터를 읽고 쓰는데도 유용하다. 예를 들어, Note Pad 샘플 같은 경우에는 노트내용을 저장하는데 컨텐트 프로바이더를 사용한다.


컨텐트 프로바이더는 ContentProvider를 상속받아 구현하며 다른 앱이 트랜젝션을 수행할 수 있도록 표준 API세트를 구현해 줘야만 한다. 좀 더 많은 것이 알고 싶다면 Content Providers 개발자 가이드를 참고하기 바란다.


브로드캐스트 리시버(Broadcast receivers)

브로드캐스트 리시버는 시스템 전체에 브로드캐스팅되는 알림에 응답하도록 하는 컴포넌트다. 많은 알림(예를 들어, 화면이 꺼지거나 배터리 잔량이 얼마 없거나 사진이 새로 촬영되었다거나 하는 등의)이 시스템에서 발생한다. 앱들도 브로드캐스팅을 할 수 있다. 예를 들어, 다른 앱에서도 쓸만한 데이터를 다운로드했을 때 알려주는 알림 같은 경우에 브로드캐스팅을 할 수 있다. 브로드 캐스트 리시버는 화면에 표시되는 UI를 가지고 있지는 않지만 브로트캐스팅 이벤트가 발생했을 때 사용자에게 알리기 위하여 상태바 알림을 생성할 수 있다. 그러나 일반적으로 브로드캐스트 리시버는 이벤트를 다른 컴포넌트에 전달해주는 관문같은 존재이며 최소의 작업만을 한다. 예를 들어, 이벤트가 발생하면 그 이벤트에 맞는 작업을 하는 서비스를 초기화 시켜주기만 하는 것이다.


브로드 캐스트 리시버는 BroadcastReceiver를 상속받아 구현하며 각 브로드캐스트 리시버는 Intent 객체를 전달한다. 좀 더 많은 정보는 BroadcastReceiver 클래스를 보면된다.


안드로이드 시스템의 큰 특징은 어떤 앱이라도 다른 앱의 컴포넌트를 시작시킬 수 있다는 것이다. 예를 들어 사용자가 기기에 장착된 카메라로 사진을 촬영하도록 하고 싶다면 기기에는 아마도 사진 촬영앱이 이미 존재할테고 여러분이 만든 앱에서는 사진촬영 기능을 새롭게 구현하지 않고 그 앱을 사용하면 된다. 이 때 앱을 프로젝트에 포함시키거나 링크를 걸거나 할 필요는 없다. 그냥 사진찍는 앱에 있는 액티비티를 시작하기만 하면 된다. 촬영이 완료되면 사진은 여러분이 만든 앱으로 반환되며 그냥 반환된 사진을 사용하기만 하면 된다. 그러면 사용자에게는 카메라 앱이 마치 여러분이 작성한 앱의 일부인 것 처럼 보여진다.


시스템이 컴포넌트를 실행시킬 때에는 먼저 그 컴포넌트가 속해있는 앱을 먼저 실행시키고(실행중이 아니라면) 컴포넌트의 실행을 위한 클래스의 인스턴스를 만든다. 예를 들어, 여러분의 앱이 사진을 찍는 앱의 액티비티를 실행시키려고 하면 그 액티비티는 여러분의 앱이 실행되는 프로세스가 아니라 카메라 앱의 프로세스에서 실행되는 것이다. 그래서 대부분의 시스템과는 달리 안드로이드 앱은 하나의 프로그램 진입점(Entry point)을 갖지 않는다(다른 시스템에서의 프로그램과 달리 main() 함수가 없다.).


시스템은 각 앱이 서로 다른 프로세스에서 실행되도록 하며 서로의 프로세스에 접근할 수 있는 권한을 제한하고 있기 때문에 여러분이 작성한 앱에서 다른 앱의 컴포넌트를 실행시킬 수가 없다. 그래서 다른 앱의 컴포넌트를 실행시키려면 그 컴포넌트에 맞는 인텐트를 시스템에 전달하여야 한다. 그러면 시스템은 여러분의 앱을 대신해서 그 컴포넌트를 실행시켜 준다.


컴포넌트 실행시키기(Activating Components)


4종류의 컴포넌트 중에서 3가지 - 액티비티, 서비스, 브로드캐스트 리시버 - 는 인텐트라고 하는 비동기 메세지에 의해서 실행시킬 수 있다. 인텐트는 컴포넌트가 여러분의 앱에 속해있건 아니건 서로 다른 컴포넌트를 묶어주는 역할을 한다.(인텐트를 컴포넌트간의 메신저라고 생각하면 되겠다.)


인텐트는 Intent오브젝트로 생성되며 특정 컴포넌트 또는 특정 타입의 컴포넌트를 실행시킬 수 있는 메세지를 담고 있다. 인텐트는 때에 따라 명시적일 수도 있으며 암시적일 수도 있다.


액티비티와 서비스를 위한 인텐트는 수행해야 하는 작업(예를 들어 "view" 또는 "send" 같은 작업)이 무엇인지에 대해 정의하고 있으며 더 세부적인 작업내용(URI)까지도 포함할 수 있다. 어떤 경우에는 결과를 돌려받기 위해 액티비티를 실행시키기도 하며 이런 경우 액티비티는 그 결과는 Intent에 담아 돌려준다.(예를 들어, 사용자가 연락처를 선택하고 그 결과를 돌려받는 경우가 그렇다. 이 때 반환된 인텐트는 선택된 연락처의 URI를 포함하게 된다.)


역주 :

URI(Uniform resource identifier)는 URL(Uniform resource locator)과 URN(Uniform resource name)을 포함하고 한 단계 더 큰 개념이다.


URL은 실제의 네트워크 경로를 가리키며 네트워크상의 리소스 접근에 사용된다.


URN은 자원에 대해 영속적이고 유일하며 위치에 독립적인 이름을 제공하기 위해 존재한다.


브로드캐스트 리시버를 위한 인텐트는 어떤 상황에 대한 내용만 포함하고 있으면 된다.(예를 들어, 기기의 배터리가 얼마남지 않았다면 "battery is low"라는 문자열만 포함시켜서 브로드캐스팅하면 된다.)


마지막으로 남은 컴포넌트 타입인 컨텐트 프로바이더는 인텐트가 아닌 컨텐트 리졸버(Content Resolver)를 통해 실행된다. 컨텐트 리졸버는 컨텐트 프로바이더와 모든 직접적인 소통을 하기 때문에 컴포넌트가 프로바이더에 지시할 어떤 작업이 있으면 프로바이더를 직접 실행시키지 말고 컨텐트 리졸버를 통해서 해야만 한다. 컴포넌트가 프로바이더를 직접적으로 실행시키 못하도록 이렇게 분리시켜 놓은 이유는 보안상의 문제 때문이다.


각 종류의 컴포넌트를 실행시킬 때에는 각기 다른 함수를 사용하여야 하며 그 함수들은 다음과 같다.


인텐트에 대해 보다 자세히 알고 싶으면 Intents and Intent Filters를 참고하고 다른 컴포넌트에 대해 더 알고 싶다면 Activities, Services, Broadcast Receiver, Content Providers를 참고하기 바란다.


매니페스트 파일(The Manifest File)



안드로이드 시스템이 앱 내의 컴포넌트를 실행시키는 것도 그냥 되는 것은 아니다. 어떤 앱에 어떤 컴포넌트가 존재하는지 알기 위해서는 각 앱의 "AndroidManifest.xml"이라는 매니페스트 파일을 읽어야만 한다. 그렇기 때문에 여러분이 앱을 작성할 때는 프로젝트의 루트에 있는 이 파일에 앱 내의 모든 컴포넌트에 대해 기록해 두어야만 한다.


매니페스트는 앱의 컴포넌트를 선언하는 것 뿐만아니라 여러가지 역할을 하게 되며 그 역할은 다음과 같다.

  • 앱이 필요로하는 퍼미션(권한)을 설정한다. 예를 들어, 인터넷 사용 또는 연락처 열람등과 같은 권한을 말한다.
  • 앱에 사용된 API가 요구하는 최소 API 레벨을 설정한다.
  • 앱이 사용을 요구하는 기기의 하드웨어 기능이나 소프트웨어 기능(카메라, 블루투스서비스, 멀티터치 스크린 등)을 선언한다.
  • Google Maps library 같은 외부 라이브러리에 연결해서 사용해야 하는 경우 외부 API 라이브러리를 선언한다.
  • 이외에도 더 많은 일을 한다.


컴포넌트 선언(Declaring components)

매니페스트가 하는 가장 주요한 일은 앱에서 사용되는 컴포넌트에 대해서 시스템에 알려주는 것이다. 예를 들어, 매니페스트 파일은 다음과 같이 액티비티를 선언할 수 있다.


1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:icon="@drawable/app_icon.png" ... >
        <activity android:name="com.example.project.ExampleActivity"
                  android:label="@string/example_label" ... >

        </activity>
        ...
    </application>
</manifest>

<application>엘리먼트 내의 android:icon 속성은 앱을 나타내는 아이콘 리소스를 지정한다.


<activity>엘리먼트 내의 android:name 속성은 Activity서브클래스의 전체 클레스네임을 지정하며 android:label 속성은 사용자에게 보여지는 액티비티의 라벨로 사용될 문자열을 지정한다.


모든 앱 컴포넌트는 다음과 같은 방법으로 선언해야만 한다.

  • 액티비티는 <activity>엘리먼트를 사용하여 선언한다.
  • 서비스는 <service>엘리먼트를 사용하여 선언한다.
  • 브로드캐스트 리시버는 <receiver>엘리먼트를 사용하여 선언한다.
  • 컨텐트 프로바이더는 <provider>엘리먼트를 사용하여 선언한다.

소스안에 포함은 되어 있지만 매니페스트에 선언되지 않은 액티비티, 서비스, 컨텐트 프로바이더는 시스템에서 알 수 있는 방법도 없고 절대로 실행될 수도 없다. 하지만 브로드캐스트 리시버는 매니페스트에 선언하거나 코드내에서 BroadcastReceiver 오브젝트를 사용하여 동적으로 생성하고 registerReceiver()를 호출하여 시스템에 등록시킬 수도 있다.


매니페스트 파일을 어떻게 구성하는지에 대해서 더 알고 싶으며 The AndroidManifest.xml File 문서를 참조한다.


컴포넌트의 수행능력 선언하기(Declaring component capabilities)

앞서 컴포넌트 실행시키기에서 언급한대로 액티비티, 서비스, 브로드캐스트는 Intent를 사용해서 시작시킬 수가 있다. 인텐트 내에 타겟 컴포넌트를 컴포넌트 클래스 이름을 사용하여 명시적으로 지정함으로써 그렇게 할 수 있었다. 그러나 인텐트의 진짜 힘은 암시적인 인텐트의 컨셉에 있다. 암시적인 인텐트는 단순히 수행할 액션이 무엇인지만 정하면 된다.(선택적으로 액션 수행을 위한 데이터를 더해 줄 수도 있다.) 그러면 시스템에서는 그 액션을 수행할 수 있는 컴포넌트를 찾아준다. 만일 인텐트의 액션을 수행할 수 있는 컴포넌트가 여러 개 있다면 사용자가 그 중 하나를 선택하도록 해 준다.


시스템이 인텐트에 응답할 수 있는 컴포넌트를 찾아내는 것은 기기내의 다른 앱이 매니페스트에서 제공하는 인텐트 필터가 있기 때문에 가능하다. 


매니페스트 내에 액티비티를 선언할 때는 선택적으로 앱이 어떤 일을 수행할 수 있는지를 정의하는 인텐트 필터를 포함시킬 수 있다. 매니페스트 파일에서 <intent-filter>엘리먼트를 사용해서 인텐트 필터를 정의할 수 있다.


예를 들어, 새로운 이메일을 작성할 수 있는 앱을 만들었다면 다음과 같이 "send"인텐트에 응답할 수 있는 인텐트 필터를 선언할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
<manifest ... >
    ...
    <application ... >
        <activity android:name="com.example.project.ComposeEmailActivity">
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <data android:type="*/*" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest>
 
  

이렇게 매니페스트를 작성한 후에, 다른 앱에서 startActivity()함수를 통해 ACTION_SEND를 보내면 사용자가 새로운 이메일을 작성하고 전송할 때 여러분이 만든 액티비티가 실행될 수도 있다.


인텐트 필터에 대해서 좀 더 알고 싶다면 인텐트와 인텐트 필터(Intents and Intent Filters) 문서를 참조하기 바란다.


앱의 요구사항 선언하기(Declaring app requirements)

안드로이드를 탑재한 기기는 매우 다양하다. 그리고 그 기기들이 모두 같은 사양과 기능을 가지고 있는 것은 아니다. 그래서 여러분이 만든 앱에서 꼭 필요한 사양을 만족하지 못하는 기기는 걸러내야 하는데 그런 작업은 매니페스트 파일을 통해서 가능하다. 대부분의 이러한 선언은 안드로이드 시스템이 참조하지는 않고 Google Play같은 외부 서비스에서 사용자가 본인들의 기기에 맞는 앱을 검색할 때 필터링용으로 사용된다.


예를 들어, 여러분의 앱이 카메라와 안드로이드 2.1(API Level 7) 이후 버전에서만 제공하는 API를 필요로한다면 그 요구사항을 다음과 같이 매니페스트 파일에 선언할 수 있다.


1
2
3
4
5
6
<manifest ... >
    <uses-feature android:name="android.hardware.camera.any"
                  android:required="true" />

    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
    ...
</manifest>


이제 카메라가 없거나 설치된 안드로이드 버전이 2.1보다 낮다면 구글 플레이 스토어를 통해서는 여러분의 앱을 설치할 수가 없다.


그러나 여러분의 앱이 카메라를 사용하기는 하지만 필수적인 것이 아니라면 매니페스트에 그 상태도 기록해 줄 수가 있다. 이런 상황에서는 required 속성을 "false"로 설정해야 한다. 대신에 앱이 실행되는 동안에는 기기에 카메라가 있는지 없는지를 코드상에서 체크하고 카메라 관련 기능을 적절히 제어하는 방법을 구현해 주어야 한다.


여러가지 다양한 기기에서 여러분의 앱이 안정적으로 동작할 수 있도록 관리하는 방법에 대해 더 많은 정보가 필요하다면

기기 호환성(Device Compatibility) 문서를 참고하기 바란다.


앱 리소스(App Resources)


안드로이드 앱은 단순히 코드외에도 이미지, 오디오 파일 그리고 앱의 시각적인 구성에 관련된 여러가지 리소스들로 구성된다. 예를 들어, XML파일에 애니메이션, 메뉴, 스타일, 컬러, 레이아웃과 같은 것들을 정의해야한다. 앱리소스를 이용하면 코드 수정 없이도 기기들의 다양한 구성(언어나 스크린 해상도 등과 같은)에 맞게 앱을 업데이트 할 수 있다.


안드로이드 프로젝트에 포함된 모든 리소스는 SDK빌드 도구로부터 정수로 된 유일 ID를 부여받는다. 여러분은 이 ID를 소스코드나 XML에서 사용할 수 있다. 예를 들어, res/drawable/ 디렉토리에 저장된 logo.png 파일이 있다면 R.drawable.logo라는 ID를 부여받게 될 것이고 이것을 통해 이미지를 참조하고 UI상에 표시할 수 있다.


소스코드와 분리된 리소스를 사용함으로써 누릴 수 있는 가장 큰 이점 중 하나는 기기마다 다른 상황에 맞는 리소스를 제공할 수 있다는 것이다. 예를 들어, XML파일에 UI표시용 문자열을 선언했다면 다른 언어로 번역해서 다른 파일로 저장해 둘 수 있다. 그리고나서 언어 구분자에 맞는 디렉토리를 만들고(불어의 문자열들은 res/values-fr/) 기기에 파일을 옮긴다. 그리고 기기의 언어설정을 맞추면 안드로이드 시스템이 UI에 맞는 적절한 언어를 적용해 준다.


안드로이드는 앱리소스를 위한 많은 다른 구분자를 지원한다. 앞서 설명한 언어를 위한 구분자외에도 기기의 회전방향이나 스크린 사이즈에 맞는 구분자도 있다. 세로로 긴 방향의 화면과 가로로 긴 화면은 당연히 화면 구성이 달라진다. 만일 두 개의 다른 레이아웃에 맞춰 리소스를 제공한다면 기기의 방향성에 맞춰 시스템이 적절한 레이아웃을 보여 줄 수 있게 된다.


앱 리소스에 대해 더 알고 싶다면 Providing Resources 문서를 참고하기 바란다.


드디어 한 개 끝냈네요.


우습게 보고 덤볐는데... 안드로이드 개발자 사이트를 다 번역한다면 두툼한 책이 한권 될지도 모르겠군요.


힘내서 다음 문서로 넘어가보도록 하겠습니다. 파이팅~~!


다음 글 >> 기기 호환성(Device Compatibility)

 

'API GUIDE > 1. Introduction' 카테고리의 다른 글

시스템 퍼미션(System Permissions)  (0) 2014.02.16
기기 호환성(Device Compatibility)  (0) 2014.02.16
posted by 리치크루