Manifest에 대한 이야기

게시일:

OverView

다시 새로운 카테고리와 함께 돌아왔다. 안드로이드 개발을 더욱 더 체계적으로 공부하기 위해서 각각의 컴포넌트들과 안드로이드 개발과 관련된 내용들에 대한 공부를 진행하고 실습을 토대로 각각의 파트에 대한 프토토타입 코드를 생성하여 개발에 활용할 생각이다. Android Developer Docs를 기준으로 영역을 넓혀나갈 것이고 가장 첫 주제는 Manifest로 선정하였다.

Manifest란 무엇일까? 영어의 어원을 보면 clear, explicit 등과 비슷하게 명백하다는 의미를 지니고 있는 단어이다. 안드로이드에 Manifest가 있다는 것은 무언가에 대한 명백한 표현을 위해 존재한다는 사실을 유추할 수 있다. 사실 안드로이드 개발을 해보면 저절로 해당 부분에 대해서 익힐 수 있긴 하지만 첫 주제로 앱 메니페스트를 잡아서 한 번 세부적으로 보는 것도 나쁘지 않을 것으로 보였다.

Android 시스템은 사용자가 특정 앱을 클릭한 경우 해당 앱에 대한 구성 요소를 실행하기 위해서 앱의 매니페스트 파일을 읽어온다. 여기서 말하는 매니페스트 파일은 Manifest.xml을 의미하며 프로젝트 소스 루트 디렉터리에 존재한다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="wizley.android.playground">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

Android Studio로 안드로이드 프로젝트를 처음 생성하면 자동으로 다음과 같이 구성되어 있는 것을 확인할 수 있다. 이렇게 xml로 적혀있는 명세서를 토대로 시스템에서는 해당 앱에 대한 정보를 파악한다는 이미이다. 선언되는 정보에는 여러가지가 있지만 Android 빌드 도구, Android 운영체제 및 Goole Play앱에 관한 필수 정보들이 다 해당 파일에 들어가게 된다.

Content of Manifest

Manifest 파일에 선언되는 정보의 종류는 크게 4종류로 나눌 수 있다.

PackageName

가장 먼저 앱의 패키지 이름이다. 자바에서 패키지는 코드의 네임스페이스를 의미하는데 Android는 앱의 패키지 이름을 통해 여러 컴포넌트에 접근을 한다. 위의 xml파일 부분에서 package로 명시된 wizley.android.playground가 앱의 패키지 이름을 담당하며 이는 APK로 빌드하는 과정에서 빌드 도구에 의해 사용된다.

package를 사용하는 목적은 2가지로 나뉘는데 첫 번째는 R.java 클래스의 네임스페이스로 적용되어 앱 리소스에 액세스하는데 사용되는 용도이며, 두 번째는 매니페스트에 정의된 클래스들의 경로를 파악하는데 사용된다. 예를 들어 MainActivity 클래스가 존재할 경우 다른 앱에도 같은 이름의 클래스가 존재할 수 있는데 Android는 package 네임을 네임스페이스로 활용하여 wizley.android.playground.MainActivity로 특정 액티비티에 접근이 가능한 것이다. 이 특성 때문에 package 이름과 코드가 들어 있는 프로젝트의 기본 패키지의 이름은 항상 일치해야된다.

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "wizley.android.playground"
        minSdkVersion 16
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

package이름은 또 다른 중요한 정보로 사용되는데 APK가 컴파일 되고 나면 가지게 되는 어플리케이션ID를 나타낸다는 점이다. 빌드 도구가 프로젝트의 build.gradle 파일에 있는 applicationId라는 속성의 값을 package의 값으로 대체하는데 이 정보는 Google Play에서 해당 앱에 대한 유일한 식별을 위한 방법으로 사용된다. 물론 package와 applicationID을 다르게 적용하는 것도 가능하다. 결국 applicationID가 해당 앱의 고유 이름을 담당하게 되는데 이로 인하여 Context.getPackageName() 메서드는 applicationID를 반환한다. (만약 package와 applicationID가 다를 경우 빌드 도구가 빌드를 종료하게 될 경우 appID 정보를 매니페스트로 복사하여 대체하게 된다)

App Components

안드로이드에는 아주 유명한 구성요소로 4가지가 존재한다. Activity, Service, Broadcast Receiver 그리고 Content Provider가 이에 해당 되는데 연관된 클래스 들에 대한 정보는 Manifest에 정의되어야 한다. 만약 정의하지 않았다면 해당 클래스는 실행되지 않는다.

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

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

MainActivity에 대한 정의는 activity로 감싸진 윗 부분을 의미한다. 같은 방식으로 service, receiver, provider라는 이름으로 정의를 할 수 있다. 마찬가지로 Manifest에는 존재하지만 실제 클래스 경로에 파일이 존재하지 않을 경우에도 빌드를 실패한다.

그리고 intent-filter라는 정보가 존재하는데 액티비티, 서비스, 브로드캐스트 리시버는 인텐트를 통해 활성화된다. 인텐트는 안드로이드에서 실행할 객체에 대한 정의를 한 메시지인데 수행 시 필요한 값, 구성 요소 등에 대한 부분이 포함된다. 만약 명시적으로 Manifest에 선언된다면 Android는 해당 정보를 토대로 앱의 구성요소를 찾는데 활용한다. 위의 action 부분을 보면 android.intent.action.MAIN이 선언되었는데 해당 action은 앱이 실행되는 순간에 엔트리 포인트로 정의할 Activity를 정의하는데 사용되며 android.intent.category.LAUNCHER는 런처 메뉴에 해당 앱을 표시하도록 하는 역할을 한다.

Intent-Filter

위의 링크를 확인하면 여러 filter에 대한 정보를 확인 가능하다. Manifest.xml의 구성요소를 자세히 보면 application 레이어 아래에 activity 등의 구성요소가 선언이 되어야 하며 application 레벨의 속성들은 하위요소들에게 영향을 준다. 즉 application 레벨로 클래스가 선언되었을 경우 해당 요소를 하위 클래스에서 다 사용할 수 있다는 의미가 된다.

Permission

Manifest의 또 다른 중요한 역할은 권한에 대한 부분이 정의된다는 것이다. Android의 샌박 정책에 의해 인터넷 접속, 파일 읽고/쓰기 등의 권한이 기본적으로 제한되어 있는데 이를 위한 권한을 요청하여야 한다. 그리고 그 권한은 각각 uses-permission 레이블로 각각 정의되며 Manifest에 아래와 같이 선언되어야 한다.

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

앱을 처음 실행하거나 실행중에 권한 요청 Dialog가 발생하는 경우가 바로 위에 대한 권한이 필요한 경우에 사용자에게 승인을 요청하기 위함이다. 만약 선언되지 않은 권한에 대한 사용이 요청될 시 무조건 실패하게 된다.

Device Compatibility

<manifest>
<uses-feature android:name="android.hardware.camera" android:required="false" />
</manifest>

비슷한 유형으로 uses-feature가 존재하는데 이는 Device 마다 특성이 다르기 때문에 기기 호환성을 위해 존재한다. 좀 더 쉬운 예로 어떤 Device는 LTE를 지원하는데 또 다른 Device는 WIFI만을 지원하는 것과 같이 말이다. 이렇게 Device에서 지원할 수도 있고 지원하지 않을 수도 있는 경우에 사용되는데 required를 통해 위의 경우 hardware가 camera를 지원하지 않을 경우에도 앱을 사용할 수 있도록 해주는 것이다. false가 적용되지 않은 경우 hard requirement로 판단하고 필수적인 요소로 존재하는 경우에만 사용이 가능해진다. 즉 PackageManager.hasSystemFeature() 에 대한 결과가 false인 경우 required=false로 선언된 경우 해당 feature에 대한 기능이 비활성화 된 채로 된다는 의미이다. GooglePlay도 feature를 기반으로 필터링한 결과를 보여주고 지원하지 않을 경우 설치 자체를 못하도록 하기 때문에 app을 등록하기 위해서는 해당 부분에 대해 신경을 쓸 필요가 있다.

feature-filtering

위의 링크를 통해 Hardware 및 Software Feature에 대한 정보를 확인 가능하다. 또한 uses-sdk 라벨을 통해 후속 플랫폼 버전에서 이전 버전에서는 사용하지 못하는 추가된 API를 사용하는 경우가 존재하는데 이를 위해 최소 버전을 나타내기 위한 정보를 추가할 수도 있다.

defaultConfig {
    applicationId "wizley.android.playground"
    minSdkVersion 16
    targetSdkVersion 29
    versionCode 1
    versionName "1.0"

    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

build.gradle의 minSdkVersion이 해당 정보를 나타낸다.

 <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="26" />

위와 같이 manifest 상에도 정의할 수 있지만 build.gradle에 의해 오버라이딩 되기 때문에 이를 주의해야 한다.