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 리치크루