안드로이드 뷰, 액티비티, 플레그먼트를 이용한 화면 구성법

데스크탑 수준이 아닌 성능과 작은 화면에서 빠르고 최적화된 화면의 구성은 스마트폰에서 필수적이다. 내부적으로 구현되는 알고리즘이 아무리 우수하다고 할지라도 사용자가 보고 있는 화면과의 상호작용의 불편함이나 느린경우 사용자는 더 이상 애플리케이션을 사용하지 않을 가능성이 높다. 사용자는 간단하고 빠르게 작동하는 애플리케이션을 원하지 내부적인 알고리즘을 보고 애플리케이션을 사용하지 않는다. 이런 측면에서 안드로이드에서 화면을 구성하는 핵심 요소인 액티비티, 플레그먼트, 레이아웃, 뷰에 대해 내부적으로 어떻게 작동 되는지에 대해 살펴보고 빠른 반응을 하기 위한 최적화 기법에 대해서 소개한다.

또한 애플리케이션 하나로 스마트폰 뿐만 아니라 테블릿기기에서 화면구성을 최적화하는 방법에 대해서 살펴본다.

2.1 액티비티

액티비티는 안드로이드 애플리케이션 화면을 구성하는 핵심 요소중 하나이다. 하나의 화면을 구성하기 위해서는 반드시 하나의 액티비티가 존재해야하며 다음에 설명할 플레그먼트와, 레이아웃, 뷰들이 액티비티 내에 구성된다. 액티비티는 폰의 상태와 사용자의 액션에 따라 발생하는 생명주기를 가지고 있다. 예를 들어 사용자가 애플리케이션을 실행 하여 액티비티가 최초로 생성 되거나, 백키를 눌러 액티비티를 종료했더나, 홈키는 눌러 화면을 잠시 빠져나가는 등에 대한 상태를 가지고 있다.

<그림 2.1.1> 액티비티의  생명주기

 

onCreate()
액티비티가 최초생성 되거나, Pause 상태일때 메모리 부족으로 인해 액티비티가 제거되어 새롭게 생성해야 되는 경우 호출 된다. 보통 여기서 setContentView()를 통해 뷰가 생성되며, 데이터를 처리하는 작업이 진행된다. SaveInstance에 아무런 정보가 없다면 최초로 생성되는 것이며, 값이 있을 경우 메모리부족으로 인해 호출 되었다는 것을 알 수 있다.

onStart()
onCreate()후 호출되거나, 다른화면으로 빠져나가 대기 상태에서 다시 복귀될때 호출 된다.

onResume()
onStart()후 호출 되거나, 새로운 액티비티의 시작으로 잠시 정지된 상태에서 다시 화면에 표시될때 호출 된다. 여기서 사용자와 상호작용이 가능하다.

onPause()
새로운 액티비티가 실행되는 경우 호출되며 사용자와 상호작용이 중단된다. onPause()상태일때 메모리가 부족하게 되는 경우 메모리 확보를 위해 강제로 종료 될 수 있다.

onStop()
홈키를 누르는등 액티비티가 사용자에게 전혀 보이지 않는 상태이며, onPause()상태와 마찬가지로 메모리가 부족하게 되는 경우 강제 종료 될 수 있다.

onRestart()
onStop()상태에서 활성상태로 복귀할때 호출된다. onRestart()가 호출이 된 것은 onStop()중에 메모리가 부족하여 강제로 종료되지 않았다는 것이며, 그대로 복귀시킨다.

onDestory()
명시적으로 액티비티를 finish()로 종료하거나, 메모리 부족등으로 시스템에서 강제로 종료시키는 경우에 발생한다.

 

강제종료되는 액티비티

액티비티의 생명주기에서 onPause()와 onStop()일때 메모리 부족의 경우 액티비티가 강제로 종료된다. 이렇게 강제 종료가 되면 이전에 수행 했던 작업이 저장되지 않아 처음부터 새롭게 작업해야 되는 경우나 특정 정보를 이용해서 작업 해야 하는데 정보가 없어져 작업을 수행 할 수 없는 문제가 발생된다. 이런 문제점을 해결 하기 위해 저장할 수 있도록 저장과 복구를 할 수 있는 콜백 메서드를 제공한다.

onSaveInstanceState(Bundle)
onPause()전에 호출되며 Bundle 파라미터를 통해서 저장해야 될 정보를 저장 할 수 있다.

onRestoreInstanceState(Bundle)
액티비티가 메모리 부족으로 종료되어 복원을 위해 호출 되며, onStart()후에 호출된다. onCreate(Bundle)의 파라미터인 Bundle을 이용해서 복구를 할 수도있다.

onConfigurationChanged(Configuration)
AndroidManifest.xml에서 configChanges설정을 통해 액티비티가 새롭게 생성되지 않는 대신 호출 된다. 화면방향, 화면밀도, 화면사이즈등의 정보가 담긴 Configuration 파라미터와 함께 호출 된다.

액티비티 화면변화

일반적으로 화면회전 하게 되면 액티비티는 제거되고 새롭게 생성된다. 가로 화면 전용 레이아웃 리소스가 있다면  전혀다른 레이아웃으로 바꾸기 위해서 새롭게 생성되는 것은 정상이다. 가로 전용 레이아웃이 없고 단순히 뷰가 늘어 나게  가변 될 수 있도록 작업이 되어 있는 경우라면 새롭게 생성하는 것은 낭비이다. 이렇게 화면의 변화가 생길때 액티비티가 새롭게 생성 되는 경우를 피하고 싶은 경우 AndroidManifest.xml에서 configChanges를 설정하면 된다. 아래 코드는 화면의 방향이 바뀌었을때를 처리 하도록하였다.  안드로이드 3.2이후 orientation은 screenSize와 같이 사용해야 작동한다.

<코드 2.1.1> targetSdkVersion 안드로이드 3.2(API 13) 이전

<activity
  android:name=”.MainActivity”
  android:configChanges=”orientation” />

<코드 2.1.1> targetSdkVersion 안드로이드 3.2(API 13) 이후

<activity
  android:name=”.MainActivity”
  android:configChanges=”orientation|screenSize” />

 

2.2 뷰와 레이아웃

뷰는 사용자 인터페이스를 구성하기위한 기본요소이다. 화면에 사각영역으로 자리를 잡고 드로잉과 터치이벤트에 대한 처리를 당담하게 된다. 이 뷰는 1.1에서 설명한 액티비티내에서 구성될 수 있으며 앱 위젯이나 윈도우등에도 구성 될 수있다. 안드로이드에서는 기본적으로 뷰를 상속하여 만든 TextView, ImageView, Button, ProgressBar, ListView, GridView 등 훌륭한 뷰들이 존재하며 직접 상속해서 개발 할 수도 있다. 하나하나의 뷰를 화면에 배치를 좀 더 효율적으로 관리 하기위한  ViewGroup이 존재 한다. ViewGroup을 상속해서 만든 FrameLayout, LinerLayout, RelativeLayout, GridLayout 등을 레이아웃이라하며 안드로이드에서 기본적으로 제공되며, 직접 상속해서 개발 할 수 있다.  안드로이드에서 제공하는 기본적인 뷰와 레이아웃은 X장에서 자세히 살펴 보겠다.

<그림 2.2.1> 안드로이드의 기본 화면 구성

안드로이드이 기본적인 화면 구성은 그림 2.2.1과 같이 액티비티내에 레이아웃이 존재하며, 그 하위로 또 다른 레이아웃이나 뷰를 배치하게 된다.

 

레이아웃 인플레이터 시스템

안드로이드의 뷰와 레이아웃은 하나의 객체로 이루어져 있다. 그렇기 때문에 액티비티내 뷰또는 레이아웃을 생성 하기위해서는 코드를 통해서 객체화해야 한다. 만일 액티비티에 복잡한 레이아웃과 뷰로 구성이 되는 경우 UI를 구성하는 객체와 데이터를 구성하는 등의 다양한 객체가 섞여 구조적으로 문제가 생기게 된다. 안드로이드에서는 이런 문제점을 해결하기 위해 레이아웃의 정보를 XML 문서로 정의하여 객체를 생성하고 속성을 변경할 수 있는 시스템을 도입하였다.

LayoutInflater는 XML문서로 정의된 레이아웃 정보를 뷰객체로 만들어준다. 코드 2.2.1에서 LayoutInflater를 이용해서 inflate() 메서드로 코드 2.2.2의 레이아웃의 정보를 가진 XML문서를 뷰로 전환 해준다. 이렇게 레이아웃 XML문서를 뷰로 전환 하는 것을 안드로이드에서는 인플레이트한다고 칭한다.

<코드 2.2.1>

LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.main, null);

<코드 2.2.2> main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >


   <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="hello world"
    android:textSize="12sp" />

</RelativeLayout>

이렇게 인플레이터 시스템이 없을 경우 개발자는 직접 뷰 또는 레이아웃을 직접 객체화 시키는 코드를 작성해야 하며, 이 과정에 레이아웃과 뷰들간의 관계에 대해서 코드를 통해 눈으로 판단하기가 매우 힘들다. CODE 2.2.3은 CODE 2.2.2의 XML 레이아웃을 동일하게 작동하도록 코드를 통해 작성한 것이다. 간단한 예제를 통해서 보듯 XML이 훨씬 직관적이고 구조적인 것을 볼 수 있다.

<코드 2.2.3>

RelativeLayout layout = new RelativeLayout(getApplicationContext());
layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
 
TextView textView = new TextView(getApplicationContext());
textView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
textView.setText("hello world");
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);

layout.addView(textView);

안드로이드에서는 XML 레이아웃을 바로 그래픽으로 보여주는 것 뿐만 아니라 배치를 하거나 속성을 바꿀 수 있는 툴 을 제공하여 좀 더 쉽고  빠른 레이아웃 작업을 할 수 있도록 도와준다.

<그림 2.2.1> 안드로이드 레이아웃 편집 툴

레이아웃 편집 툴은 각종 안드로이드에서 제공되는 기본 뷰를 쉽게 배치할 수도 있으며, 다양한 단말에서 어떻게 보여지는지에 대한 미리보기도 지원한다.

 

액티비티와 레이아웃 리소스

액티비티는 단순히 화면을 구성하는 기본단위이며 액티비티 내에 뷰를 배치하여 화면을 구성해야 한다. 구성 후 액티비티 내부에 존재하는 뷰 객체를 찾아서 필요한 데이터를 뷰를 통해 표현해야 한다.

액티비티에 뷰를 구성하기위해서는 setContentView() 메서드를 호출하면 되는데, 2가지의 인자가 존재한다. 하나는 뷰 또 다른 하나는 레이아웃 리소스 아이디이다. 레이아웃 리소스를 인자로 주게되면 내부적으로 2.2.1에서 LayoutInflater를 통해 뷰 객체를 만든 후 뷰를 인자로 다시 넘기게 된다. 즉, 코드 2.2.4에서 리소스아이디를 인자로 주게 되면 내부적으로 코드 2.2.5와 같이 LayoutInflater로 레이아웃 리소스 아이디로 뷰를 인플레이트하게 된다.

<코드 2.2.4>

setContentView(R.layout.main);

<코드 2.2.5>

LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);

View view = inflater.inflate(R.layout.main, null);

setContentView(view);

이렇게 액티비티에 뷰가 보여지게 되면 수많은 뷰 객체 중 필요한 객체만 찾아와 속성을 변경하는 등의 작업이 필요하게 된다. findViewById() 메서드를 통해 레이아웃에 선언한 아이디로 해당 객체를 불러올 수 있다. 코드 2.2.6에서 TextView에 아이디값을 주었다. 아이디 값은 “@+id/아이디명” 으로 선언하게된다. 이미 정의된 아이디를 선언하려면 “+”를 제외한 “@id/아이디명”을 값으로 주면 된다.

<코드 2.2.6> main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >

  <TextView
    android:id="@+id/title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="hello world"
    android:textSize="12sp" />

</RelativeLayout>

<코드 2.2.4>

setContentView(R.layout.main);
TextView titleTextView = (TextView) findViewById(R.id.title);
titleTextView.setText(“Hi Android!”);

findViewById는 View를 리턴해주지만 내부적으로 TextView의 객체가 생성되어 있기 때문에 TextView로 캐스팅 하면된다. 이렇게 XML에서 선언한 TextView를 아이디를 통해 객체를 가져올 수 있으며, 속성을 변경 할 수 있다. 한 가지 주의할 점은 LayoutInflater는 XML문서를 위에서 아래로 읽기 때문에 레이아웃 내에 동일한 아이디를 가지게 되는 경우 XML에서 밑 부분에 선언된 아이디가 최종적인 뷰 객체가 된다.

 

2.3 플레그먼트

액티비티는 스마트폰을 기준으로 설계된 구조이기 때문에 하나의 하나의 화면을 구성을 할 수 있다. 즉, 한 화면에 액티비티 여러개를 구성하지 못한다. 스마트폰에서는 화면이 작기 때문에 하나의 화면에 액티비티 하나를 배치하는 것에 문제가 없었다. 하지만 태블릿기기가 등장하면서 한 화면에 여러개의 액티비티를 구성하여 화면을 좀 더 넓게 쓸 필요가 있었다. 레이아웃으로 화면을 좀 더 넓게 사용도 가능하지만, 액티비티에 종속적인 생명주기로 인해 관리가 매우 힘들다. 이런 문제점을 해결하기위해서 안드로이드 3.0 (허니콤) 부터 플레그먼트라는 개념을 도입하게 되었다. 액티비티를 조각화 하여 배치 할 수 있는 독립적인 부분이 있으며, 액티비티와 같은 생명주기를 가지고 있다. 그림 2.3.1에서 보는것과 같이 액티비티내에 다양한 플레그먼트를 배치하고 그 안에 레이아웃을 구성 했다.

<그림 2.3.1> 플레그먼트를 이용한 화면 구성

테블릿 기기의 경우 스마트폰과는 다르게 큰 공간을 가지고 있기 때문에 플레그먼트를 이용하게 된다면 공간을 효율적으로 사용가능 하다. 하나의 앱으로 다양한 스마트폰과 테블릿을 지원하기 위해서는 필수적인 요소이다.

플레그먼트는 그림 2.3.2에서 보듯이 액티비티에 비해 좀 더 명확성을 갖기위해 Created부분과 Destory부분이 세부적으로 나누어졌다. 하지만 크게 나누어 본다면 액비티비와 동일한 구조로 이루어 졌다.

 

<그림 2.3.2> 플레그먼트 생명주기와 액티비티와의 비교

onAttach()
플레그먼트가 액티비티 레이아웃에 포함되는 순간 호출되며 플레그먼트를 아직 생성되지 않았다.

onCreate()
플레그먼트가 생성되는 시점에 호출되며 화면에 보이지는 않는다.

onCreateView()
플레그먼트에 표시 할 레이아웃을 인플레이트 하여 뷰를 생성 할 수 있다. 플레그먼트가 최초로 생성되거나 메모리부족으로 인해 플레그먼트가 메모리상에서 제거되었다가 다시 생성되어야 하는 시점에 호출 된다.

onActivityCreated()
액티비티가 생성된 후에 호출되며 뷰가 만들어진 상태이기 때문에 뷰의 속성을 변경하는 등의 작업이 가능하다.

onStart()
액티비티와 동일하게 플레그먼트가 화면에 표시될때 호출된다. 아직은 사용자와 상호작용이 불가능 한 상태다.

onResume()
플레그먼트가 화면에 완전히 그렸으며, 사용자와 상호작용이 가능하다.

onPause()
플레그먼트가 화면에서 보이지 않는 상태로 액티비티와 동일하다. 액티비티에서는 새로운 액티비티가 뛰워 진것과 동일하게 플레그먼트도 백스택에 의해 추가된 경우에 호출된다.

onStop()
플레그먼트가 화면에서 보이지 않는 상태로 액티비티와 동일하다.

onDestoryView()
View 리소스를 해제 할 수 있도록 호출된다. 백스택을 사용한 경우 다시 돌아올때 onCreateView()가 호출된다.

onDestory()
플레그먼트 상태를 완전히 종료 할 수 있도록 호출 한다.

onDetach()
플레그먼트가 액티비티의 레이아웃에서 제거 될 때 호출된다.

onSaveInstanceState()
액티비티와 동일하게 플레그먼트가 메모리 부족으로 강제로 없어 지는 경우를 복원을 위해 Bundle에 정보를 저장 할 수 있다.