Tuesday, December 15, 2009

화면의 구성 요소들

이 글은 http://developer.android.com/guide/topics/ui/index.html 을 요약 정리한 것입니다.


View

뷰는 View 클라스의 오브젝트인데 이는 화면의 레이아웃과 컨텐트같은 요소들을 저장하고 있는 것이라 볼수 있겠습니다. 뷰 오브젝트는 레이아웃이나 그 내부에 놓여지는 오브젝트들의 정보를 저장하고 드로잉, 포커스 변화, 스크롤, 입력키나 제스추어등의 인터렉션을 핸들합니다.

View Group

뷰 그룹은 Viewgroup 클라스의 오브젝트로서 다른 뷰들을 모아 관리하거나 할 때 사용되는 특별한 뷰입니다. 하나의 뷰는 옆의 그림에서 보는 것 처럼 내부에 다른 뷰나 뷰 그룹을 하부에 포함할수도 있고 독자적으로 존재할수도 있습니다.

이렇게 뷰와 뷰 그룹이 정의되고 여기에 layout을 연결하고 Parameter 값을 주면서 뷰의 모양을 확실히 그려내게 됩니다.

Layout

레이아웃은 뷰의 서브클라스로 계층 구조내에서 하부에 붙어 뷰내 콘트롤들의 위치를 제어하는 역할을 담당합니다. 레이아웃을 자바 프로그램을 통해서 정의될수도 있지만 대부분 XML 레이아웃 파일에 따로 정의됩니다.

기본적으로 제공되는 레이아웃은
1. FrameLayout: 가장 간단한 레이아웃으로 내부에 있는 뷰나 콘트롤들은 왼편꼭대기에 겹쳐서 차곡차곡 쌓아놓습니다.
2. LinearLayout: 내부의 요소들을 횡이나 종으로 쭉 나열합니다.
3. RelativeLayout: 여기에서는 특정내부요소를 다른 내부요소에 관련되어 위치할수 있게 합니다.
4. TableLayout: 열과 횡의 테이블 모습을 이용해야할때 쓰입니다.
5. AbsoluteLayout: 각각 내부요소들의 위치가 좌표로 주어집니다.
더욱 자세한 내용은 http://code.google.com/android/devel/ui/layout.html 을 참조합니다.

아래의 예는 가장 간단한 화면을 설명합니다. 내부요소를 위-아래로 쭉 나열하는 리니어 레이아웃을 준비한 후 그 내부에 TextView 와 Button 위짓을 집어넣었습니다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent" 
              android:layout_height="fill_parent"
              android:orientation="vertical" >
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Hello, I am a TextView" />
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, I am a Button" />
</LinearLayout>
.

Widget

위짓도 레이아웃과 같이 뷰의 서브클라스로 존재합니다. 위짓이란 그 하나로 완전히 모습을 갖춘 텍스트 필드나 버튼 같은 오브젝트들을 칭합니다. 이들은 화면의 구성요소가 되기위해 하나하나 오브젝트화되어있습니다. 간단한 버튼 부터 복잡한 시계까지 위짓은 그 종류가 많습니다. 다음 링크를 그 자세한 내용을 확인할 수 있습니다. http://developer.android.com/reference/android/widget/package-summary.html

UI Event

뷰, 뷰그룹, 위짓으로 화면을 구성하고 나면 이제 사용자의 의도가 잡아내는 화면상의 이벤트들을 생각할 차례입니다.
이벤트를 준비하는 방법을 흔히 두가지로 생각합니다.

첫째: 이벤트를 View 클라스에 이미 존재하는 Interface들 (예; View.OnClickListener, View.OnTouchListener, View.OnKeyListener )을 이용 리스너를 만들고 이걸 뷰에 연결하는겁니다.
private OnClickListener mCorkyListener = new OnClickListener() {
    public void onClick(View v) {
      // 여기에 클릭이 들어오면 무엇을 할건지에 대한 프로그램을 넣습니다.
    }
};

protected void onCreate(Bundle savedValues) {
    Button button = (Button)findViewById(R.id.corky);   // 레이아웃에서 버튼을 찾아 오브젝트로 정의합니다.
    button.setOnClickListener(mCorkyListener);  // 리스너를 버튼 오브젝트에 연결합니다.
    ...
}

둘째: 이게 훨신 더 쉽고 예제는 이 방법으로 합니다. 클라스를 만들때 이미 정의되어있는 리스너들이 있는데 이것에 수정(override)하여 쓰는겁니다.
public class ExampleActivity extends Activity implements OnClickListener {
    protected void onCreate(Bundle savedValues) {
        ...
        Button button = (Button)findViewById(R.id.corky);     // 레이아웃에서 버튼을 찾아 오브젝트로 정의합니다
        button.setOnClickListener(this);                                // 리스너를 버튼 오브젝트에 연결합니다.
    }

    // OnClickListener 콜백시에 무엇을 할지를 정합니다.
    public void onClick(View v) {
      // 여기에 클릭이 들어오면 무엇을 할건지에 대한 프로그램을 넣습니다.
    }
    ...
}


Menu

메뉴에는 두 가지가 있습니다.
1. Menu 버튼을 누름에 따라 나오는 메뉴와
2. 화면을 계속 누르고 있으면 나오는 Context Menu 입니다.

Menu 버튼을 에서는 onCreateOptionsMenu() 에 메뉴 아이템들을 넣고, onOptionsItemSelected() 으로 어느 아이템이 선택되었는지를 잡아냅니다.
Context Menu에서는 onCreateContextMenu() 에 메뉴 아이템들을 넣고, onContextItemSelected() 으로 어느 아이템이 선택되었는지를 잡아냅니다.

User Interface 관련 고려사항

Dimension

px
Pixels - corresponds to actual pixels on the screen.

in
Inches - based on the physical size of the screen.

mm
Millimeters - based on the physical size of the screen.

pt
Points - 1/72 of an inch based on the physical size of the screen.

dp
Density-independent Pixels - an abstract unit that is based on the physical density of the screen. These units are relative to a 160 dpi screen, so one dp is one pixel on a 160 dpi screen. The ratio of dp-to-pixel will change with the screen density, but not necessarily in direct proportion. Note: The compiler accepts both "dip" and "dp", though "dp" is more consistent with "sp".

sp
Scale-independent Pixels - this is like the dp unit, but it is also scaled by the user's font size preference. It is recommend you use this unit when specifying font sizes, so they will be adjusted for both the screen density and user's preference.

이중 가장 많이 사용되는 것은 dp 와 sp 입니다. dp로 정하면 화면의 크기나, density 가 다른화면이라도 어느정도 시스템이 알아서 보정을 해주여 개발이 쉬어집니다.


Configuration 변경

아래의 주요 시스템 세팅 변경은 Activity 의 onConfigurationChanged으로 감지가 되어 실시간으로 프로그램이 대응하는 것이 가능합니다..
orientation: 스크린이 세로에서 가로로 바뀌는 변경사항.
keyboardHidden: 키보드가 보여지거나 숨겨지는 변경사항 .
fontScale: 사용자가 원하는 폰트의 크기를 변경하는 것.
locale: 사용자가 언어 세팅을 변경하는것.
keyboard: 키보드의 종류가 바뀌는 사항

이를 위해서는 Manifest 파일의 해단 Activity 의 옆에 아래와 같이 android:configChanges 를 먼저 선언합니다.
<activity android:name="RosaryList" android:configChanges="keyboardHidden|orientation"></activity>

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    [ ... Update any UI based on resource values ... ]
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        [ ... React to different orientation ... ]
    }
    if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO) {
        [ ... React to changed keyboard visibility ... ]
    }
}

어플리케이션 라이프 사이클

어플리케이션 자체가 실행되고 종료되는 전 과정에 있어 수행되고 있는 프로그램이 자신을 컨트롤하지 못하며 안드로이드의 리소스 매니져의 역할에 대해 매우 취약하다. 리소스매니저의 판단에 의해 강제 종료되는 상화도 생길수있다라는 것인데 백그라운드로 실행되는 어플리케이션인 서비스가 그 좋은 예라할 것이다. 그러므로 프로그램이 더 이상 보이지 않게 될때 프로그램 자체의 상태를 저장하고 프로그램이 전면에서 다시 돌면 그 저장된것을 참조하여 계속 작업의 수행을 지속하는 개념의 프로그래밍이 요구된다.

안드로이드에서 라이프사이클을 가지는 큰 대상으로 아래의 세가지 컴포넌트를 꼽고, 다음의 protected methods 를 이용하여 적용된다.

1. Activity
void onCreate(Bundle savedInstanceState)
void onStart()
void onRestart()
void onResume()
void onPause()
void onStop()
void onDestroy()

2. Service
void onCreate()
void onStart(Intent intent)
void onDestroy()

3. BroadcastReceiver
void onReceive(Context curContext, Intent broadcastMsg)

안드로이드 패키지, 테스크, 프로세스

ANDROID PACKAGE

안드로이드 패키지(Android Package): 어플리케이션 프로그램코드와 리소스 파일을 총괄하여 일컫는 명칭. 어플리케이션을 나누어주는 단위가 됩니다. 예를 들어보면, 안드로이드 마켓에 개발자가 만든 프로그램을 올릴때 이 패키지의 단위로 올라가게됩니다. 흔히 Apk 라는 확장자를 가진 파일로 만들어 안드로이드 마켓에 업로드를 하게 되면 그때부터는 패키지의 이름이 프로그램의 아이디와 같이 사용됩니다.

TASK

태스크 (Task): 하나의 태스크는 일반적으로 하나의 어플리케이션이며, 홈스크린상에서 하나의 프로그램 아이콘으로 보여진다. 여러개의 액티비티(Activity)를 가진 하나의 안드로이드 패키지가 만들어지면 그 액티비티들 중 하나가 맨 꼭대기 레벨의 시작점이 된다. 이 액티비티는 manifest.xml 파일의 Intent-filter내에 아래와 같이 정의되는데, 태스크의 시작이 된다. 이후 다른 액티비티들은 이미 만들어진 하나의 태스크의 다른 부분으로 존재한다.
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

좀 기술적인 이야기이지만 새로운 태스크가 시작되는 것인가 와 기존 태스크의 부분으로 시작하는것인가를 가르는 것은 Activity Intent가 Intent.FLAG_ACTIVITY_NEW_TASK flag 를 가지고 시작하느냐 아니냐에 있다.

(이 테스크 개념은 안드로이드 초창기에 용되던 개념이었는지 지금은 별로 중요하지 않습니다. 그래도 초창기에 언급되던 것이고 번역을 해 놓았으니 지우지 않고 그냥두겠습니다.)


PROCESS

프로세스 (Process): 낮은레벨의 커널근처에 있는 어플리케이션이 실행되는 프로세스를 지칭합니다. 일반적으로 하나의 안드로이드 패키지에는 하나의 프로세스가 존재합니다.

안드로이드 어플리케이션 구성요소

안드로이드 개발의 바이블과 같은 Android Dev Guide 에 가면

http://developer.android.com/guide/topics/fundamentals.html

안드로이드 어플리케이션을 구성하는 기본요소들을 일단 다음과 같이 보고 있음을 봅니다.
1. Activity
2. Service
3. Content Provider
4. Intents
5. Broadcast Receiver

이것은 매우중요함으로 달달 외워야 합니다.

Activity, Service, Broadcast Receiver들은 실제 실핼되는 프로그램들입니다. Activity는 눈앞에서 실행되는 Service는 보이지 않게 백그라운드로 실행되는, Broadcast Receiver는 시스템에 무슨일이 있을때(예, 배터리상태, 키보드보임) 신호가 나오면 그것을 받아 처리하는 눔입니다. Intents는 이 프로그램들 사이에서의 다리와 같은 역할을 합니다. 한 프로그램이 다른프로그램으로 다른 작업을 요구하거나 데이터를 넘겨줄때 이용되는 교량의 역할입니다. Content Provider는 시스템에 저장되어있는 콘텐트들을 어플리케이션이 자유자제로 공유할수 있게 합니다. 이 콘텐트는 주소(URI)의 개념으로 표시되는데 각 어플리케이션은 이 URI를 알고 있으면 자유자제로 데이터를 공유할 수 있습니다

서비스(Service)

서비스는 한 어플리케이션 내의 보이지 않는 작업이라보면 됩니다. 보이지 않는 관계로 스트린 레이아웃을 붙여줄 필요가 없습니다. 보이지 않는 상태로 실행되면서 데이터입출력에 관련된 작업을 수행하고 수행결과 신호 (Notification) 을 줍니다.

서비스도 나름대로의 Life cycle 을 가집니다. OnCreate - OnStart - OnDestroy 로 이어지는것이 기본 흐름인데 한번 시작된 서비스는 onStart 의 내용이 끝난후 바로 onDestroy 로 가는것이 아니라, 다음실행을 위해 대기상태로 들어갑니다. 프로세스가 계속 살아있는것이죠. 그리고, 프로세스가 살아있는 한은 다시 onCreate 으로 들어가지 않고, 서비스를 외부에서 실행시키면 onStart 의 단계로 바로 들어가게 됩니다. 하지만 시스템에 리소스가 모자라면 OS 가 그 프로세스를 죽이게 됩니다. 이때 onDestroy 가 작동되고, 이 서비스가 다시 시작하는 시점에는 onCreate 가 작동됩니다.

서비스의 시작은 일반적으로 startService() 를 사용하며, 종료에는 stopService() 가 사용됩니다.

Friday, December 11, 2009

Manifest 파일

안드로이드 어플리케이션을 구성하는 가장 중요한 기본요소들을 보면 다음과 같다.
1. Activity
2. Service
3. Content Provider
4. Intents
5. Broadcast Receiver
6. Notifications

이 구성요소들을 정의,나열하고 그 메타 데이터들을 주는것이 Manifest 파일의 역할이다.
파일은 각각의 위에 나열된 구성요소에 대한 노드를 가지고 있고 그 내부에 이름들과 그 연결이 정의된다. 또한 사용 아이콘, 현 버젼정보, 프로그램 이름과 인텐트들이 어떻게 넘겨지는지에 대한 정보도 정의될수있다.

시작노드는 manifest로 시작된다.
그 내부에 application노드에서 메타데이터들이 지정되고, 타이틀, 아이콘, 테마 들이 정의된다.
application 노드는 한 manifest에 한개밖에 지정할수 밖에 없는데 이는 또한 다른 어플리케이션 구성요소들이 정의되는 노드의 기반이 된다. 이 내부에는 Activity노드나 Provider 노드. Receiver, Permission등의 노드들이 위치한다.

예로 안드로이드 폰에 기본으로 달려나오는 알람시계의 Manifest 를 보자.
맨 먼저볼것은 큰 노드의 구성이다.

위에 user-permission 부분이
아래에 application 노드가 있다.
application 의 내부에는 1개의 콘텐트제공자, 6개의 액티비티, 3개의 리시버, 1개의 서비스가 달려있음을 본다.

그러면, 프로그램을 시작하면 제일먼저 시작되는 액티비티는 무엇인가?
AlarmClock 이다. action.MAIN, category.DEFAULT, category.LAUNCHER 가 인텐트 필터에 달려있다.

이외에도 프로그램의 distribution 을 어느 안드로이드 버젼으로, 어느 화면 사이즈의 기기로 제한할것인가에 관련된 속성도 주는 것이 가능하다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.alarmclock"> 
 
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> 
    <uses-permission android:name="android.permission.WAKE_LOCK"/> 
    <uses-permission android:name="android.permission.VIBRATE"/> 
    <uses-permission android:name="android.permission.WRITE_SETTINGS" /> 
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> 
    <uses-permission android:name="android.permission.READ_PHONE_STATE" /> 
 
    <application android:label="@string/app_label"
                 android:icon="@drawable/ic_launcher_alarmclock"> 
 
        <provider android:name="AlarmProvider" android:authorities="com.android.alarmclock" /> 
 
        <activity android:name="AlarmClock" android:label="@string/app_label"
                android:icon="@drawable/ic_widget_analog_clock"
                android:configChanges="orientation|keyboardHidden|keyboard|navigation"> 
            <intent-filter> 
                <action android:name="android.intent.action.MAIN" /> 
                <category android:name="android.intent.category.DEFAULT" /> 
                <category android:name="android.intent.category.LAUNCHER" /> 
            </intent-filter> 
        </activity> 
 
        <activity android:name="SettingsActivity" android:label="@string/settings"> 
            <intent-filter> 
                <action android:name="android.intent.action.MAIN" /> 
            </intent-filter> 
        </activity> 
 
        <activity android:name="SetAlarm" android:label="@string/set_alarm"
                android:configChanges="orientation|keyboardHidden|keyboard|navigation" /> 
 
        <activity android:name="AlarmAlert"
                android:excludeFromRecents="true"
                android:theme="@style/alarm_alert"
                android:launchMode="singleInstance"
                android:taskAffinity=""
                android:configChanges="orientation|keyboardHidden|keyboard|navigation"/> 
 
        <!-- This activity is basically the same as AlarmAlert but with a more
             generic theme. It also shows as full screen (with status bar) but
             with the wallpaper background. --> 
        <activity android:name="AlarmAlertFullScreen"
                android:excludeFromRecents="true"
                android:theme="@android:style/Theme.Wallpaper.NoTitleBar"
                android:launchMode="singleInstance"
                android:taskAffinity=""
                android:configChanges="orientation|keyboardHidden|keyboard|navigation"/> 
 
        <activity android:name="ClockPicker" /> 
 
        <receiver android:name="AlarmReceiver"> 
            <intent-filter> 
               <action android:name="com.android.alarmclock.ALARM_ALERT" /> 
               <action android:name="alarm_killed" /> 
               <action android:name="cancel_snooze" /> 
            </intent-filter> 
        </receiver> 
 
        <!-- This service receives the same intent as AlarmReceiver but it does
             not respond to the same broadcast. The AlarmReceiver will receive
             the alert broadcast and will start this service with the same
             intent. The service plays the alarm alert and vibrates the device.
             This allows the alert to continue playing even if another activity
             causes the AlarmAlert activity to pause. --> 
        <service android:name="AlarmKlaxon"> 
            <intent-filter> 
                <action android:name="com.android.alarmclock.ALARM_ALERT" /> 
            </intent-filter> 
        </service> 
 
        <receiver android:name="AlarmInitReceiver"> 
            <intent-filter> 
                <action android:name="android.intent.action.BOOT_COMPLETED" /> 
                <action android:name="android.intent.action.TIME_SET" /> 
                <action android:name="android.intent.action.TIMEZONE_CHANGED" /> 
            </intent-filter> 
        </receiver> 
 
        <receiver android:name="AnalogAppWidgetProvider" android:label="@string/analog_gadget"
         android:icon="@drawable/ic_widget_analog_clock"> 
            <intent-filter> 
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> 
            </intent-filter> 
            <meta-data android:name="android.appwidget.provider" android:resource="@xml/analog_appwidget" /> 
        </receiver> 
    </application> 
</manifest> 

참고로 위 Manifest 의 출처는
http://android.git.kernel.org/?p=platform/packages/apps/AlarmClock.git;a=tree
이다.