ch11. 액티비티

Download Report

Transcript ch11. 액티비티

액티비티 (Activity)
안드로이드 프로그래밍 정복(Android Programming Complete
Guide)
Contents
 학습목표
 안드로이드 응용 프로그램을 구성하는 가장 중요한 컴포넌트인 액티비티
에 대하여 학습한다.
 인텐트를 통해 액티비티끼리 통신하는 방법과 액티비티의 생명 주기를
관리하여 상태를 저장하는 방법에 대하여 연구한다.
 내용
 액티비티 (Activity)
 생명 주기 (Life Cycle)
 복잡한 액티비티
2/43
1. 액티비티 (Activity)
 액티비티 추가
 액티비티는 안드로이드 응용 프로그램을 구성하는 4가지 컴포넌트 중 하나로 가장 빈번
히 사용되며 사용자를 대면한다는 면에서 실질적으로 가장 중요한 요소이다.
 화면 하나에 대응되며 입출력 기능이 없어 내부에 뷰나 뷰 그룹을 가져야 한다.
 setContentView 메서드
• 액티비티가 생성될 때마다 호출되며, 액티비티 안에 뷰를 배치하는 명령이다.
 액티비티 하나는 독립된 기능을 수행하며, 서로 중첩되지 않는다.
 액티비티 추가 예제
• 메인 액티비티 – callactivity.xml, 서브 액티비티 – subactivity.xml
activity/callactivity.xml
activity/subactivity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="메인 액티비티입니다."
android:textSize="30sp"
android:textColor="#ff0000" />
<Button
android:id="@+id/call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Call" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="메인에서 호출한 서브입니다."
android:textSize="20sp"
android:textColor="#00ff00" />
<Button
android:id="@+id/close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Close" />
</LinearLayout>
3/43
1. 액티비티 (Activity)
 액티비티 추가 예제
• 레이아웃을 정의하는 xml 문서 하나와 동작을 정의하는 java 파일을 쌍으로 생성해야 액티비티
하나가 정의된다.
• callactivity.xml
- 추가 액티비티를 호출할 버튼 하나와 메인을 나타내는 문자열 하나를 배치한다.
• subactivity.xml
- res/layout 폴더에 파일을 생성하고 텍스트 뷰와 버튼 하나를 배치한다.
• Callactivity.java
- 액티비티를 호출할 때 startActivity 메서드를 사용, 인수로 호출할 대상을 지정하는 Intent 객체를 전달한다.
• SubActivity.java
- Activity 클래스를 상속받으며 onCreate를 반드시 재정의하고 super.onCreate까지 호출하여 기본적인 초기화를
수행해야 한다.
- 이후 setContentView 메서드를 호출, 액티비티 안에 뷰나 뷰 그룹을 채운다.
activity/CallActivity.java
activity/SubActivity.java
public class CallActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.callactivity);
public class SubActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.subactivity);
Button btnCall=(Button)findViewById(R.id.call);
btnCall.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(CallActivity.this, SubActivity.class);
startActivity(intent);
}
});
}
}
Button btnClose=(Button)findViewById(R.id.close);
btnClose.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
finish();
}
});
}
}
4/43
1. 액티비티 (Activity)
• 보안상의 이유로 응용 프로그램에 포함된 모든 액티비티는
반드시 매니페스트에 등록해야 한다.
( 매니페스트에 등록되지 않은 액티비티는 존재하지 않는
것으로 취급된다. )
• 현재 예제는 메인에서 Call 버튼을 눌러 서브 액티비티를
호출할 때 startActivity 메서드가 SubActivity를 찾지 못하
여 예외가 발생하며 다운된다.
• 매니페스트에 SubActivity를 등록한다.
[ 액티비티 추가 예제 exception error ]
<activity android:name=".CallActivity" android:label="CallActivity" />
<activity android:name=".SubActivity" android:label="SubActivity" />
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>
- 액티비티의 이름과 타이틀 바에 표시할 제목은 최소한 지정해야 한다.
- 액티비티의 이름은 패키지명을 포함한 풀 경로로 지정하되 같은 패키지에 속해 있을 시 앞에 ‘.’을 찍는다.
※ 액티비티 추가 절차
①
새로 만들 액티비티의 레이아웃을 XML 파일에 정의.
②
액티비티의 코드를 java 파일에 작성.
③
새로 추가한 액티비티를 매니페스트에 등록.
④
startActivity 메서드로 액티비티 호출.
[ 액티비티 추가 예제 최종 결과 ]
5/43
1. 액티비티 (Activity)
 인텐트 (Intent)
 메인 액티비티 혼자 작업을 수행하면 반복이 심해지고 형식성도 떨어지므로 다른 액티
비티를 호출하여 일을 분담한다.
• ( 메서드가 다른 메서드를 호출하여 작업을 분담하는 이유와 동일하다. )
 인텐트
• 안드로이드의 컴포넌트끼리 통신하기 위한 메시지 시스템.
• 액티비티 및 컴포넌트간 수행할 작업에 대한 정보 및 작업 결과 리턴을 위해서도 사용된다.
• 액티비티 호출 메서드
void startActivity (Intent intent)
- 인수 : 인텐트 안에 호출 대상 및 작업 내용 정보가 들어있다.
- 메서드 호출 전에 먼저 인턴트 객체에 설정해야 한다.
• 인텐트 생성자
Intent ()
Intent (Intent o)
Intent (String action [,uri uri]
Intent (Context packageContext, Class<?> cls)
Intent (String action, Uri uri, Context packageContext, Class<?> cls)
- 디폴트 생성자, 복사 생성자는 사용이 드물며 액션, uri, 클래스 등을 취하는 생성자들이 주로 사용된다.
- Intent (Context packageContext, Class<?> cls)
: 내부의 서브 액티비티 호출 시 사용되는 생성자. 액티비티 클래스 구현을 위한 컨텍스트,
호출할 액티비티 클래스 정보가 인수로 전달된다.
6/43
1. 액티비티 (Activity)
• 실행 중에 액티비티를 생성해야 하므로 클래스 정보가 필요하다.
• 예제의 호출문
Intent intent = new Intent(CallActivity.this, SubActivity.class);
startActivity(intent)
- 호출자 : 메인 익티비티 자신
- 호출 대상 : SubActivity
- startActivity 메서드 : 인텐트의 정보를 참조하여 CallActivity를 부모로 하는 SubActivity를 호출.
• 명시적 인텐트 (Explicit intent)
- 인텐트에 호출할 대상 컴포넌트가 분명히 명시.
- 주로 같은 응용 프로그램내의 서브 액티비티를 호출할 때 사용되며, 권한에 따라 외부 응용 프로그램의
액티비티도 호출 가능하다.
• 암시적 인텐트 (Implicit intent)
- 호출 대상이 분명히 정해지지 않은 인텐트.
- 주로 다른 응용 프로그램의 컴포넌트를 호출할 때 사용된다.
- 호출할 대상을 정확히 찾을 수 있도록 상세한 정보가 적성되어야 한다.
※ 운영체제에는 인텐트의 정보를 참조하여 호출할 컴포넌트를 검색하는 알고리즘이 작성되어
있고, 시스템은 적절한 컴포넌트를 찾기 위해 설치된 모든 응용 프로그램의 컴포넌트를 조사한다.
※ 따라서 매니페스트에 컴포넌트의 정보를 가급적 상세히 밝혀 놓아야 한다.
7/43
1. 액티비티 (Activity)
 Action
• 실행하고자 하는 동작으로 인텐트를 통해 어떤 작업을 수행할 지 지정한다.
• 시스템이 제공하는 표준 동작 List
액션
대상
설명
ACTION_CALL
액티비티
통화 시작.
ACTION_EDIT
액티비티
데이터 표시 및 편집한다.
ACTION_MAIN
액티비티
메인 액티비티 실행. (입출력 데이터 없음)
ACTION_VIEW
액티비티
뭔가를 보여줌.
ACTION_DIAL
액티비티
전화를 건다.
ACTION_BATTERY_LOW
BR
배터리 부족.
ACTION_HEADSET_PLUG
BR
헤드셋이 장비에 접속 또는 분리됨.
ACTION_SCREEN_ON
BR
화면 켜짐.
ACTION_TIMEZONE_CHANGED
BR
타임존 변경.
- 통상적으로 사용되는 액션이 거의 다 정의되어 있으며, 사용자가 임의의 동작을 정의할 수 있다.
- 동작의 종류가 다양하므로 문자열 타입으로 지정되어 있다.
- getAction, setAction 메서드 : 액션을 조사하거나 변경 시 사용.
※ 중복의 가능성이 있으므로 커스텀 액션 정의 시 패키지명을 접두로 붙이는 것이 좋다.
8/43
1. 액티비티 (Activity)
 Data
• 동작에 필요한 상세 데이터를 제공하며, 단독 액션의 경우 별도의 Data가 필요하지 않고, 그 외의
액션은 목적어에 해당하는 정보인 데이터가 필요하다.
• 대상의 종류가 광범위하여 임의의 대상을 가리킬 수 있는 URI 타입으로 되어있다.
• getData, setData : 데이터 액세스 시 사용.
 Type
• 대부분 자동으로 판별 가능하다.
• getType, setType : 타입이 애매하거나 자동 판별을 신뢰할 수 없는 경우 데이터의 MIME 타입 지정.
 Category
• 실행할 액션에 대한 추가 정보를 제공.
• addCategory, removeCategory : 카테고리 추가, 제거
 Component
• 인텐트를 처리할 컴포넌트를 명시적으로 지정. (속성 사용 시 다른 정보를 참조하지 않는다.)
 Extras
• 그 외 컴포넌트로 전달되어야 할 추가적인 정보.
• putExtra, getIntExtra, getStringExtra
 Flags
• 액티비티를 띄울 방법이나 액티비티 관리 방법 등의 옵션 정보들이 저장된다.
• setFlag – 플래그 전체 대입, addFlags – 특정 플래그 추가로 지정
9/43
1. 액티비티 (Activity)
 암시적 인텐트
 다른 패키지에 속한 임의의 액티비티나 서비스를 호출할 수 있으며, 권한이 있을 시에
특정 패키지의 특정 액티비티를 명시적으로 호출할 수 있다.
 응용 프로그램끼리 철저히 격리되는 안드로이드에서 인텐트를 사용하여 경계를 넘나들
수 있으며, 인텐트는 운영체제가 제공하는 시스템 전역적인 메시지 시스템이다.
 CallOther 예제
• 레이아웃은 수직 레이어안에 각 액티비티를 호출하기 위한 버튼 4개를 배치한다.
activity/CallOther.java
activity/CallOther.java
public class CallOther extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.callother);
case R.id.dial:
intent = new Intent(Intent.ACTION_DIAL, Uri.parse(
"tel:015-123-4567"));
startActivity(intent);
break;
case R.id.picture:
intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.fromFile(new File("/sdcard/test.jpg"));
intent.setDataAndType(uri, "image/jpeg");
startActivity(intent);
break;
case R.id.other:
intent = new Intent(Intent.ACTION_MAIN);
intent.setComponent(new ComponentName("exam.Input",
"exam.Input.Input"));
//intent.setClassName("exam.Input", "exam.Input.Input");
startActivity(intent);
break;
}
findViewById(R.id.web).setOnClickListener(mClickListener);
findViewById(R.id.dial).setOnClickListener(mClickListener);
findViewById(R.id.picture).setOnClickListener(mClickListener);
findViewById(R.id.other).setOnClickListener(mClickListener);
}
Button.OnClickListener mClickListener = new View.OnClickListener() {
public void onClick(View v) {
Intent intent;
switch (v.getId()) {
case R.id.web:
intent = new Intent(Intent.ACTION_VIEW,
Uri.parse("http://www.google.com"));
startActivity(intent);
break;
}
};
}
10/43
1. 액티비티 (Activity)
• 실행 시 4개의 버튼은 각각 다른 인텐트로 startActivity를 호출한다.
• 첫 번째 버튼 : ACTION_VIEW로 구글 홈페이지 open.
- ACTION_VIEW 액션을 지정하여 의사를 밝히고 목적어로 http://www.google.com 라는 웹 주소를 Uri 형태로
변환하여 데이터로 전달한다.
- 시스템은 웹 사이트 주소를 처리할 수 있는 컴포넌트를 검색, 미리 설치된 웹 브라우저가 선택되어 실행되며
데이터인 Uri가 전달되어 홈페이지가 열린다.
• 두 번째 버튼 : ACTION_DIAL로 특정 전화번호를 전달.
- ACTION_DIAL 액션과 015-123-4567 번호를 전달, 내장 다이얼러가 열리며 전화를 걸 준비를 한다.
• 세 번재 버튼 : SD 카드에 저장된 이미지 파일에 대한 ACTION_VIEW 요청.
- SD 카드 루트에 test.jpg가 미리 복사되 있는 것을 가정하므로 파일이 준비되어 있어야 한다.
- 파일의 경우 확장만으로 타입을 정확히 알기 어려우므로, MIME 타입을 지정해야 한다.
• 네 번째 버튼 : Input 패키지의 Input 메인 액티비티를 표시한다.
- Input 예제가 설치되어 있어야 하며, 시스템에 jpeg 파일에 대한 전문적인 뷰어가 설치되어 있
을 시 내장 이미지 뷰어 대신 전문 뷰어가 실행된다.
- 인텐트에 대해 setComponent 메서드로 호출할 클래스를 지정하였으므로 명시적 인텐트이다.
- 첫 번째 인수 : 액티비티가 속한 패키지의 경로 지정.
- 두 번째 인수 : 실행할 액티비티 클래스의 풀 경로 지정.
- 주석 처리된 setClassName 메서드도 동일한 동작을 수행하며, 이 방식을 사용하면 외부 패키지의 액티비티도
직접 호출 가능하다. (단, 인텐트 필터에 MAIN이라 지정된 액티비티만 호출 가능.)
※ 암시적 인텐트는 의도만을 지정할 뿐 어떤 액티비티가 실행될지는 미리 알 수 없고, 시스템이 해
당 인텐트를 분석하여 요구를 가장 잘 처리할 수 있는 액티비티를 검색하여 표시한다.
11/43
1. 액티비티 (Activity)
 호출하는 쪽에서는 가급적 정확한 선택을 위해 인텐트를 상세히 작성해야 하며, 호출 받
는 쪽에서도 선택 받기 위해 컴포넌트에 대한 정보를 가급적 상세히 제공해야 한다.
 인텐트 필터(Intent Filter)
• 컴포넌트에 대한 인텐트 정보를 말하며, 컴포넌트가 어떤 액션과 데이터를 처리할 수 있는지에 대
한 정보가 기술되어 있다.
• 응용 프로그램의 매니페스트에 기록된다.
• 운영체제가 인텐트 필터를 검색하는 알고리즘은 상세히 공개되어 있으며, 점수제라 할 수 있다.
- 1순위 : 액션과 카테고리의 일치성.
- 2순위 : 데이터의 일치성.
- 여러 개가 검색될 경우 일치도가 가장 높은 것을 선택, 빌트인과 써드 파티 응용 프로그램에 대한 차별은 없다.
[ 첫 번째 버튼 결과 ]
[ 두 번째 버튼 결과 ]
[ 세 번째 버튼 결과 ]
[ 네 번째 버튼 결과 ]
12/43
1. 액티비티 (Activity)
 액티비티간의 통신
 인텐트는 액티비티간에 인수와 리턴값을 전달하는 도구로도 이용된다.
 Bundle 타입의 Extras를 활용하며 이름과 값의 쌍으로 된 임의 타입의 정보를 원하는 개
수만큼 전달 가능하다.
 Extras에 값을 저장하는 메서드
• 거의 모든 타입에 대해 오버로딩되어 있다.
Intent putExtra (String name, int value)
Intent putExtra (String name, String value)
Intent putExtra (String name, boolean value)
- name : 전달하는 인수의 이름이며, 중복되지 않으면 자유롭게 지정 가능하다.
 Extras 에 저장된 값은 아래의 메서드로 꺼낸다.
int getIntExtra (String name, int defaultValue)
String getStringExtra (String name)
Boolean getBooleanExtra (String name, boolean defaultValue)
• 값이 전달되지 않았을 경우 두 번째 인수로 지정한 디폴트가 리턴, 문자열은 null이 리턴된다.
• 입력뿐 아니라 리턴값을 돌려줄 때 사용 가능하며, 적당한 이름으로 값을 넣어두면 호출원에서 약
속된 이름으로 리턴값을 꺼내 사용한다.
13/43
1. 액티비티 (Activity)
 액티비티로 인수 전달, 결과를 리턴값으로 돌려받는 예제.
• 인수와 리턴값 : 인텐트를 통해 전달.
• 호출을 요청한 대상에 대한 정보가 포함되어야 한다.
• 리턴값을 돌려주는 액티비티는 아래의 메서드로 호출한다.
public void startActivityForResult (Intent intent, int requestCode)
- 두 번째 인수 : 호출한 대상을 나타내는 식별자, 리턴시에 누구에 대한 리턴인가를 구분한다.
- 호출되는 액티비티별로 고유의 번호를 붙여야 하며, 0 이상의 중복되지 않는 정수를 넘기며, 음수일 경우
리턴받지 않는다.
• 호출된 액티비티가 종료 후 아래의 메서드가 호출된다.
protected void onActivityResult (int requestCode, int resultCode, Intent intent)
- 리턴값을 받으려면 메서드를 재정의해야 한다.
- requestCode : 액티비티를 호출할 때 전달한 요청 코드.
- resultCode : 액티비티의 실행 결과.
• 리턴값은 data 인텐트 안에 포함되어 있으므로 data 안에 Extras를 읽어 구할 수 있다.
14/43
1. 액티비티 (Activity)
 CommActivity 예제
• 문자열 편집 액티비티를 구현한 예제.
• 메인 액티비티는 텍스트 뷰 하나와 버튼 배치, 문자열 편집을 위한 서브 액티비티로 구성.
- 메인 액티비티 : CommActivity
- 서브 액티비티 : ActEdit
activity/CommActivity.java
public class CommActivity extends Activity {
TextView mText;
final static int ACT_EDIT = 0;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.commactivity);
activity/ActEdit.java
public class ActEdit extends Activity {
EditText mEdit;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.actedit);
mEdit = (EditText)findViewById(R.id.stredit);
Intent intent = getIntent();
mEdit.setText(intent.getStringExtra("TextIn"));
mText = (TextView)findViewById(R.id.text);
Button btnOK=(Button)findViewById(R.id.ok);
btnOK.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("TextOut", mEdit.getText().toString());
setResult(RESULT_OK,intent);
finish();
}
});
Button btnEdit=(Button)findViewById(R.id.edit);
btnEdit.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(CommActivity.this, ActEdit.class);
intent.putExtra("TextIn", mText.getText().toString());
startActivityForResult(intent,ACT_EDIT);
}
});
}
Button btnCancel=(Button)findViewById(R.id.cancel);
btnCancel.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case ACT_EDIT:
if (resultCode == RESULT_OK) {
mText.setText(data.getStringExtra("TextOut")); }
break;
}
}
}
}
}
15/43
1. 액티비티 (Activity)
• 메인 액티비티
- 요청 코드 0번을 가지는 ACT_EDIT로 정의, 버튼의 클릭 리스너에서
호출한다.
- TextIn라는 이름의 Extras로 텍스트 뷰의 문자열을 읽어 인텐트에 저장.
- startActivityForResult 메서드로 ActEdit 액티비티 호출
[ 메인 액티비티 실행 결과 ]
- 호출원은 서브 액티비티 실행 결과를 onActivityResult 메서드로 요청
코드와 처리 결과, 인텐트를 인수로 전달받아, 이 정보를 참조하여
액티비티의 동작 결과를 사용한다.
- 인텐트로 전달된 추가정보중 TextOut 라는 이름의 문자열을 읽어
텍스트 뷰에 대입한다.
• 서브 액티비티
- 에디트 하나, 버튼 두개 배치.
- onCreate에서 getIntent() 메서드로 액티비티로 전달된 인텐트를 구한다.
- 인텐트의 getStringExtra 메서드를 호출, TextIn 라는 이름으로 전달된
문자열을 읽어 에디트 뷰에 대입한다.
[ 서브 액티비티 실행 결과 ]
- 최초 시작시에 호출원에서 전달한 문자열을 표시하며, 편집 가능하다.
- 결과 리턴 시 새 인텐트 객체를 생성, 사용자가 편집한 문자열을 추가
정보의 TextOut 라는 이름으로 저장. 아래의 메서드로 결과를 대입한다.
public final void setResult (int resultCode, Intent intent)
- resultCode는 인텐트 작업 결과로, 편집 완료 시 RESULT_OK를 리턴,
취소 시 RESULT_CANCEL을 리턴한다.
[ 문자열 편집 후 메인 액티비티 결과 ]
- 두 번째 인수로 리턴값을 실어 전달한다.
- 결과 대입 후 finish 메서드 호출, 액티비티를 종료하고, Cancel 버튼을
누르면 RESULT_CANCEL만 넘긴다.
16/43
1. 액티비티 (Activity)
 한 액티비티에서 여러 개의 서브 액티비티를 호출한다면 요청 코드로 리턴값의 의미를
구분해야 한다.
• 모든 액티비티가 리턴 시 onActivityResult 메서드를 호출하므로 요청 코드가 없을 때 구분할 방법
이 없다.
- 예제의 경우 호출되는 액티비티가 하나밖에 없으므로 onActivityResult에서 요청 코드를 확인할 필요가 없다.
• 앞의 예제 에 폰트 변경 기능을 추가할 경우.
- 폰트 정보를 입력받는 ActFont 서브 액티비티 추가, 요청 코드를 1번으로 정의해야 한다.
- ACT_FONT라는 상수를 1로 정의, startActivityForResult (intent, ACT_FONT)로 호출하고, 리턴 시 아래와 같
은 요청 코드에 따라 분기해야 한다.
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case ACT_EDIT:
// 리턴된 문자열 사용
break;
case ACT_FONT:
//리턴된 폰트 사용
break;
}
}
 동일한 액티비티에 대해 서로 다른 요청 코드를 사용할 수 있다.
• 메인 액티비티에 문자열이 두 개이면, 편집 대상 문자열에 따라 요청 코드가 달라야 한다.
• 편집 액티비티 자체는 요청 코드를 구분할 필요가 없지만 리턴되었을 때 어떤 요청에 의한 리턴
인가에 따라 리턴값을 사용할 곳이 달라진다.
17/43
2. 생명 주기 (Life Cycle)
 액티비티의 일생
 안드로이드는 멀티태스킹을 지원하지만, 적은 용량의 메모리, 좁은 화면 등 여러 가지
제약이 있다.
 여러 개의 프로그램을 동시에 실행, 오버랩시켜 한번에 하나의 프로그램만 표시한다.
 생명 주기
• 액티비티는 시작, 실행, 활성, 비활성화, 정지, 종료되는 일련의 상태를 순환.
• 사용자의 선택이나 시스템의 자원 사정에 따라 액티비티의 상태는 끊임없이 변한다.
 시스템은 태스크의 실행중인 액티비티들을 스택으로 관리한다.
• 액티비티가 생성되면 스택의 제일 위에 놓여 활성화되며, 스택 제일 위의 액티비티가 종료되면 바
로 아래쪽의 액티비티가 활성화된다.
• 스택의 액티비티는 넣고 빼기만 할 뿐 순서가 바뀌지 않는다.
• 스택상의 액티비티는 아래 세 가지 상태중의 하나이다.
- 실행 (active, running)
: 사용자가 직접 사용하는 상태. 스택의 제일 위에 위치하며, 화면상에도 가장 위에 위치하여 입력 포커스를
가지고 사용자의 입력을 직접 처리한다.
- 일시 정지 (pause)
: 포커스는 잃었지만 사용자에게는 보이는 상태. 위쪽에 다른 액티비티가 있지만 화면 전체를 다 가리지
않았거나 반투명한 경우에 해당한다. 살아있는 상태와 같지만 시스템에 의해 강제 종료될 수 있다.
- 정지 (stopped)
: 다른 액티비티에 의해 완전히 가려진 상태. 사용자의 눈에 보이지는 않지만, 모든 정보를 다 유지하고 있어
재활성화가 가능하다. 시스템은 메모리 부족 시 정지 상태의 액티비티를 언제든지 강제 종료할 수 있다.
18/43
2. 생명 주기 (Life Cycle)
 액티비티의 전체 생명 주기와 상태 변화 시에 호출되는 메서드는 아래와 같다.
• 액티비티는 처음 생성되어 완전히 파괴될 때까지 아래의 메서드들을 순서대로 거친다.
• 일부 생략될 수 있으며 활성화 상태가 자주 변경되면 onPause와 onResume는 여러 번 호출되기도
한다.
시작
onCreate
onRestart
onStart
Back키로
액티비티로
돌아왔을 때
강제종료
onResume
포그라운드가
될때
포그라운드가
될때
실행
다른 액티비티가
앞에 올 때
메모리가
부족할 때
onPause
보이지 않게 될 때
onStop
onDestroy
종료
19/43
2. 생명 주기 (Life Cycle)
• 메서드 정보
메서드
해야 할 일
onCreate
액티비티를 초기화하며, 중지 후 재시작하는 경우엔 액티비티의 이전 상태 정보인 Bundle이
전달되며 이 정보대로 재초기화한다.
onRestart
재시작될 때 호출된다.
onStart
onResume
액티비티가 사용자에게 보이기 직전에 호출된다.
사용자와 상호작용을 하기 직전에 호출되며, 이 단계에서 스택의 제일 위로 올라온다.
onPause
다른 액티비티가 실행될 때 호출되며, 이 단계에서 미저장한 데이터가 있으면 저장하고, 애니
메이션은 중지해야 한다. 이 메서드가 리턴되어야 새 액티비티가 활성화된다.
onStop
액티비티가 사용자에게 보이지 않게 될 때 호출된다.
onDestroy
액티비티가 파괴될 때 호출된다. 시스템에 의한 강제 종료인지 finish 메서드 호출에 의해 스
스로 종료하는 것인지는 isFinishing 메서드로 조사 가능하다.
20/43
2. 생명 주기 (Life Cycle)
• onCreate
- 액티비티가 생성될 때 호출되며 반드시 구현. super의 onCreate를 호출, Activity의 기본 초기화를 먼저
수행해야 하며, 다른 메서드들도 모두 동일하다.
- 다음은 setcontentView 메서드를 호출, 액티비티 안을 뷰로 채운다.
• 나머지 메서드는 필요 시 구현한다.
- onStart, onStop의 경우 실용성이 떨어지며 onDestroy도 구현하는 경우가 많지 않고, onPause 메서드는
대부분의 경우 구현해야 한다.
- onPause가 호출된 이후를 킬러블(Killable) 상태라 하며 시스템이 언제든지 액티비티를 강제 종료할 수 있다.
- 강제 종료된 프로그램은 onStop와 onDestroy가 호출되지 않으므로 호출이 보장된 onPause가 실질적으로
생명 주기의 마지막이며, 미저장 정보들은 onPause에서 반드시 저장해야 한다.
- 시스템은 액티비티를 중지시키기 전에 onSaveInstanceState 메서드를 호출, 임시적인 데이터를 저장할 기회를
제공하며, 이 메서드의 인수로 Bundle 객체가 전달된다.
※ 시스템은 응용 프로그램을 임의로 죽이기 전에 onPause 메서드의 호출을 보장하여 정보를 저장
할 최소한의 기회를 제공하므로, 저장할 데이터가 있는 경우 onPause 메서드는 반드시 구현해야
한다.
21/43
2. 생명 주기 (Life Cycle)
 상태 저장
 메모리 부족 등 여러 가지 이유로 프로그램이 강제 종료되는 상황은 흔하지 않으나, 낮
은 확률로 강제 종료될 가능성이 존재하므로 모든 프로그램은 이에 대한 대비가 필요하
다.
 강제 종료를 유도할 수 있는 가장 쉬운 방법은 화면의 방향은 전환하는 것이다.
 모바일 장비에서 화면의 방향 전환 시 액티비티가 재생성되며, 언어나 입력 장치 등이
변경될 때도 비슷한 상황이 발생한다.
• 환경 변화에 대해 액티비티를 재생성하는 이유는 조건에 따라 사용할 리소스가 완전히 달라질 수
있기 때문이다.
• 레이아웃이 판이하게 다를 경우 실행 상태에서 전환하는 것보다 재생성하는 것이 가장 안전하고
깔끔하다.
• 레이아웃은 onCreate에서 전개되어 액티비티 내부에 채워지므로 리소스가 바뀌면 생명 주기를
onCreate부터 다시 시작해야 변경된 리소스를 적용할 수 있다.
22/43
2. 생명 주기 (Life Cycle)
 SaveState 예제
• 액티비티는 x, y라는 좌표값을 멤버로 가지며, 이 정보가 저장해야할 중요한 정보라 가정한다.
• MyView는 이 위치에 동그라미 하나를 그리며 방향키 입력을 받아 x, y의 값을 변경한다.
• 실행 시 x, y의 초기값인 (50, 50) 좌표에 원이 그려지고, 방향키를 눌러 임의의 위치로 옮길 수 있
다.
activity/SaveState.java - 1
activity/SaveState.java - 2
public class SaveState extends Activity {
private MyView vw;
int x;
int y;
public boolean onKeyDown(int KeyCode, KeyEvent event) {
super.onKeyDown(KeyCode, event);
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (KeyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT:
x -= 5;
invalidate();
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
x += 5;
invalidate();
return true;
case KeyEvent.KEYCODE_DPAD_UP:
y -= 5;
invalidate();
return true;
case KeyEvent.KEYCODE_DPAD_DOWN:
y += 5;
invalidate();
return true;
}
}
return false;
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
x = 50;
y = 50;
vw = new MyView(this);
vw.setFocusable(true);
setContentView(vw);
}
protected class MyView extends View {
public MyView(Context context) {
super(context);
}
public void onDraw(Canvas canvas) {
Paint p = new Paint();
p.setColor(Color.GREEN);
canvas.drawCircle(x, y, 16, p);
}
}
}
23/43
2. 생명 주기 (Life Cycle)
• 화면 중앙으로 원을 옮긴 후, Back 키를 눌러 뒤로 돌아갔다가 예제를 다시 실행하면 초기 위치
인 (50, 50)으로 좌표가 리셋된다.
• 종료하지 않고 Ctrl+F11키를 눌러 에뮬레이터의 방향을 전환해도 좌표값이 리셋되며, 비슷한 상
황이 메모리 부족으로 인한 강제 종료시에도 발생한다.
[ 화면 방향 전환 후 리셋된 화면 ]
[ 예제 실행 후 원 위치를 화면 중앙으로 옮김 ]
24/43
2. 생명 주기 (Life Cycle)
 SaveState2 예제
• 액티비티가 종료되기 전 값을 저장한 뒤 재실행될 때 원래대로 복구한다.
• 시스템은 액티비티가 종료하기 직전에 onSaveInstanceState 메서드를 호출, 정보를 저장할 기회를
제공한다.
• 문자열로 된 이름과 임의 타입의 값을 저장하는 일종의 맵인 Bundle 객체를 인수로 전달한다.
- 인텐트의 Extras 멤버가 Bundle 타입이며, 임의의 정보들을 원하는 만큼 저장할 수 있다.
- 이 메서드를 재정의하여 저장하고자 하는 값을 번들에 저장해두고 종료한다.
• 저장된 정보는 재실행시 onCreate 메서드
로 전달된다.
- 인수로 받은 번들 객체가 null이면 처음
시작하는 것이므로 최초값으로 초기화.
- null이 아니면 재시작되는 것이므로 이전값을
복구.
• onCreate의 복구 코드 대신 아래의 매서드
를 재정의해도 효과는 동일하다.
public void onRestoreInstanceState (Bundle outState) {
x = outState.getInt(“x”);
}
- onRestoreInstanceState는 onStart 메서드 다음
으로 호출되므로 필요한 모든 초기화가 완료된
후 안전하게 복구할 수 있는 시점이다.
- 예제에서 x 값은 번들에 저장, y 값은 저장하
지 않는다.
activity/SaveState2.java
public class SaveState2 extends Activity {
private MyView vw;
int x;
int y;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
x = 50;
} else {
x = savedInstanceState.getInt("x");
}
y = 50;
vw = new MyView(this);
vw.setFocusable(true);
setContentView(vw);
}
public void onSaveInstanceState(Bundle outState) {
outState.putInt("x", x);
}
===== MyView는 앞 예제와 동일함 =====
25/43
2. 생명 주기 (Life Cycle)
 SaveState3 예제
• x와 y 값 모두 저장하는 예제로, y는 저장 위치 및 방법이 다르다.
• 프로그램이 킬러블 상태가 되기 직전인 onPause에서 프레프런스에 영구적으로 저장한다.
• 저장된 정보는 x와 동일하게 onCreate에서 복구하되 번들이 아닌 프레프런스에서 읽어온다.
• 예제에서 x는 임시적인 정보에 해당하며 y
는 영구적인 정보에 해당한다.
- 임시적 정보 : onSaveInstanceState에서 시스템
이 제공하는 번들에 저장.
( 실행 중인 동안만 유지됨. )
- 영구적 정보 : 프레프런스나 데이터베이스
등의 물리적인 파일에 저장해야 한다.
( 다음 실행 시까지 유지됨. )
• onPause와 onSaveInstanceState 메서드는
둘 다 정보를 저장하기 위한 시점이다.
- onPause : 영구 정보를 저장하는 시점이며,
액티비티의 생명주기에 포함되어 액티비티가
중지되는 상황이면 반드시 호출된다.
- onSaveInstanceState : 임시 정보를 저장
하기에 적합한 시점이며, 생명주기에 포함되어
있지 않아 필요할 때만 호출된다.
activity/SaveState3.java
public class SaveState3 extends Activity {
private MyView vw;
int x;
int y;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
x = 50;
} else {
x = savedInstanceState.getInt("x");
}
SharedPreferences pref = getSharedPreferences("SaveState",0);
y = pref.getInt("y", 50);
vw = new MyView(this);
vw.setFocusable(true);
setContentView(vw);
}
protected void onPause() {
super.onPause();
SharedPreferences pref = getSharedPreferences("SaveState",0);
SharedPreferences.Editor edit = pref.edit();
edit.putInt("y", y);
edit.commit();
}
public void onSaveInstanceState(Bundle outState) {
outState.putInt("x", x);
}
===== MyView는 앞 예제와 동일함 =====
26/43
2. 생명 주기 (Life Cycle)
 객체 저장
 정수 등의 단일값이 아닌 큰 객체나 배열을 저장할 때 신속한 저장을 이해 효율적인 방
법이 필요하다.
 객체 저장 시 자바의 시리얼라이즈 기능이 유용하게 쓰인다.
• 자바는 언어 차원에서 객체를 일차원의 데이터로 저장하는 시리얼라이즈 기능을 제공한다.
• Serializable 인터페이스를 상속받아 사용하며, 디폴트 직렬화 알고리즘이 적용되어 클래스의 모든
인스턴스 필드들이 순서대로 저장된다.
• 커스텀 시리얼라이즈 역시 가능하다.
• 시리얼라이즈를 제공하는 객체는 번들의 두 메서드를 이용한다.
void putSerializable (String key, Serializable value)
Serializable getSerializable (String key)
- 이름과 객체만 전달하면 저장 및 복구가 자동으로 수행된다.
 SaveCurve 예제
• 복잡한 모양의 자유 곡선을 번들에 저장하는 예제이다.
• 자유 곡선은 좌표와 그리기 여부 등의 정보를 가진 정점들의 배열에 저장되는 거대한 정보이지만
시리얼라이즈 기능을 이용하여 한번에 저장할 수 있다.
• 예제의 주 자료 구조인 arVertex는 Vertex 객체에 대한 배열이다.
- ArrayList 클래스는 기본적으로 시리얼라이즈를 지원하므로 그 요소인 Vertex 클래스만 시리얼라이즈를
지원하면 배열 전체를 직렬화할 수 있다.
- 클래스 선언문에 implements Serializable 구문만 넣어주면 된다.
27/43
2. 생명 주기 (Life Cycle)
• onSaveInstanceState 메서드에서 번들의 PutSerializable를 호출하여 배열 전체를 Curve라는 이름으
로 저장한다.
• onCreate : 번들에서 이 배열을 통째로 읽어 복원한다.
- getSerializable 메서드는 Serializable 타입을 리턴하므로 원래 타입으로 캐스팅해야 한다.
activity/SaveCurve.java
public class SaveCurve extends Activity {
private MyView vw;
ArrayList<Vertex> arVertex;
@SuppressWarnings("unchecked")
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vw = new MyView(this);
setContentView(vw);
if (savedInstanceState == null) {
arVertex = new ArrayList<Vertex>();
} else {
arVertex =
(ArrayList<Vertex>)savedInstanceState.getSerializable("Curve");
}
}
public void onSaveInstanceState(Bundle outState) {
outState.putSerializable("Curve", arVertex);
}
public class Vertex implements Serializable {
private static final long serialVersionUID = 100L;
Vertex(float ax, float ay, boolean ad) {
x = ax;
y = ay;
Draw = ad;
}
float x;
float y;
boolean Draw;
}
==== MyView 는 FreeLine 예제와 동일함 ====
}
[ SaveCurve 예제 실행 결과 ]
28/43
2. 생명 주기 (Life Cycle)
 SaveCurve2 예제
• 시리얼라이즈와 비슷한 기법인 Parcelable 인터페이스를 이용하여 구현한다.
• Parcel
- 소포, 꾸러미라는 뜻으로, 객체를 전달 가능한 형태로 포장하는 것을 의미한다.
- 원래 프로세스간의 통신을 위한 메시지 저장 장치이며, 번들이 꾸러미 입출력 기능을 제공하므로 같은
프로세스의 세션간 데이터 저장 및 복구에도 사용 가능하다.
activity/SaveCurve2.java - 1
class Vertex implements Parcelable {
Vertex(float ax, float ay, boolean ad) {
x = ax;
y = ay;
Draw = ad;
}
float x;
float y;
boolean Draw;
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(x);
dest.writeFloat(y);
dest.writeBooleanArray( new boolean[] { Draw } );
}
public static final Parcelable.Creator<Vertex> CREATOR = new
Creator<Vertex>() {
public Vertex createFromParcel(Parcel source) {
int x = source.readInt();
int y = source.readInt();
boolean[] td = new boolean[1];
source.readBooleanArray(td);
return new Vertex(x, y, td[0]);
}
activity/SaveCurve2.java - 2
public Vertex[] newArray(int size) {
return new Vertex[size];
}
};
}
public class SaveCurve2 extends Activity {
private MyView vw;
ArrayList<Vertex> arVertex;
int Count;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vw = new MyView(this);
setContentView(vw);
if (savedInstanceState == null) {
arVertex = new ArrayList<Vertex>();
} else {
arVertex = savedInstanceState.getParcelableArrayList("Curve");
}
}
public void onSaveInstanceState(Bundle outState) {
outState.putParcelableArrayList("Curve", arVertex);
}
==== MyView 는 FreeLine 예제와 동일함 ====
}
29/43
2. 생명 주기 (Life Cycle)
• 저장 대상인 클래스는 Parcelable 인터페이스를 상속받아야 하며 아래의 두 메서드를 구현해야 한
다.
- int describeContents() : 마샬링 방법을 지정하는 비트 마스크값을 지정, 특별한 방법을 사용하지 않으면 0을
리턴한다.
- void writeToParcel (Parcel dest, int flags) : 객체의 필드들을 dest로 출력하여 꾸러미로 보내고, 저장하고자 하는
필드들을 순서대로 Parcel 객체로 전송한다.
- Parcel은 기본 타입에 대해 write*, read* 메서드를 제공하며 각 타입의 배열을 액세스하는 메서드도 제공한다.
(단, booblean 타입에 대해서는 단일값 입출력 메서드를 제공하지 않으므로 배열 형태로 포장해서 출력한다.)
• 이 외 CREATOR라는 이름의 정적 객체를 제공해야 하며, 저장된 정보를 Parcel로부터 읽어 들여
객체를 생성하는 역할을 한다.
- 반드시 static final이어야 하며, 이너 클래스는 static final 멤버를 가질 수 없으므로 Vertex를 외부 클래스로
독립시키고, CERATOR 객체의 두 메서드는 Parcel로부터 T 타입 객체 하나를 생성, T 타입의 배열을 size
크기로 생성한다.
• createFromParcel 메서드는 source로부터 정수 두 개와 논리값 하나를 순서대로 읽어 Vertex 객체
하나를 생성하고, newArray 메서드는 전달된 크기대로 배열을 생성하여 리턴한다.
• Parcelable 객체를 번들에 입출력할 때 아래의 메서드를 사용하며, 객체 하나, 객체의 배열,
ArrayList 배열 등을 각각 입출력할 수 있다.
void putParcelable (String key, Parcelable value)
void putParcelabelArray (String key, Parcelable[] value)
void putParcelableArrayList (String key, ArrayList<? extends Parcelable> value)
T getParcelable (String key)
Parcelable[] getParcelableArray (String key)
ArrayList<T> getParcelableArrayList (String key)
30/43
2. 생명 주기 (Life Cycle)
 특정 변화에 대해서 액티비티를 그대로 유지하도록 지정할 수 있으나, 스스로 변화에 대
해 대응할 수 있을 때만 이 방법을 이용한다.
• 단순히 화면만 다시 그리거나 일부 정보가 잠시 안보여도 문제되지 않을 때가 해당되며, 레이아웃
이나 리소스가 완전히 바뀔 때는 이 방법을 사용할 수 없다.
• 매니페스트의 configChanges 속성 중 아래 속성들의 조합으로 액티비티 파괴를 거부할 변화를 지
정한다.
값
설명
mcc
SIM의 mcc(Mobile Country Code)가 변경되었을 때
mnc
SIM의 mnc(Mobile Network Code)가 변경되었을 때
locale
touchscreen
keyboard
keyboardHidde
언어가 변경되었을 때
터치스크린 장비가 변경되었을 때. 통상의 경우 거의 발생하지 않는다.
키보드가 변경되었을 때. ex) 외장 키보드 장착
키보드 상태가 바뀌었을 때. ex) 슬라이딩 키보드를 꺼내거나 숨길때
navigation
네비게이션이 변경되었을 때
orientation
화면의 방향이 바뀌었을 때
fontScale
기본 폰트가 바뀌었을 때
※ 안드로이드는 모든 변화에 대해 액티비티를 재시작하지만, 매니페스트의 configChanges를 기록
해 놓으면 해당 변화에 대해서는 액티비티를 재시작하지 않고 onConfigurationChanges 메서드를
호출하여 환경이 바뀌었음을 알려주며 여기서 환경 변화에 필요한 처리를 한다.
31/43
2. 생명 주기 (Life Cycle)
 SaveCurve3 예제
• FreeLine 예제와 동일한 코드를 가지되 매니페스트에 기록된 액티비티의 속성이 다르다.
• 방향 전환 시 키보드에도 변화가 발생하므로 keyboardHidden 값도 반드시 같이 지정해야 하며,
방향만 지정하고 키보드 변화를 지정하지 않으면 여전히 재시작된다.
activity/SaveCurve3.xml
<activity android:name=".SaveCurve3"
android:label="SaveCurve3"
android:configChanges="orientation|keyboardHidden"
/>
32/43
3. 복잡한 액티비티
 탭
 액티비티에는 뷰 계층을 통해 많은 차일드 뷰를 배치할 수 있으나, 모바일 장비의 화면
이 넓지 않으므로 보여줄 수 있는 정보의 양에 제약이 있다.
 입출력해야 할 정보가 많을 시 성격에 따라 그룹을 나누어 화면을 겹쳐서 표현하며, 탭
위젯을 주로 사용한다.
• TabHost 클래스가 탭 화면을 구성하며 이 안에 탭별로 페이지들이 배치된다.
• 상단의 탭과 중앙의 프레임 등 두 개의 차일드로 구성
- 상단의 TabWidget은 페이지 구성을 보여주고 사용자로부터 탭 클릭을 입력 받아 페이지를 전환한다.
- 아래쪽 프레임에는 각 페이지의 내용물에 해당하는 뷰나 액티비티들이 배치된다.
[ 안드로이드 주소록 화면 ]
33/43
3. 복잡한 액티비티
 탭 화면으로 된 액티비티를 구성하는 가장 쉬운 방법은 TabActivity로부터 상속받는 것
이다.
• 탭 자체가 레이아웃을 포함할 수 있는 컨테이너이므로 위젯으로 배치하는 것보다 액티비티 전체
를 가득 채우는 것이 일반적이다.
• TabActivity 전 영역을 TabHost가 채우고 있으므로 탭 호스트 안에 차일드 뷰만 배치하면 된다.
• 내부의 탭 호스트 객체는 아래의 메서드로 구한다.
TabHost getTabHost ()
 TebTest 예제
• 탭 호스트의 차일드인 프레임은 기본적으로 비어있으며, 표시하고자 하는 내용물을 커스텀 레이
아웃으로 작성한 후 빈 프레임을 교체하면 뷰가 배치된다.
activity/tebtest.xml - 1
activity/tebtest.xml - 2
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:id="@+id/opt_general"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="프로젝트명" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="변경" />
</LinearLayout>
<LinearLayout android:id="@+id/opt_compiler" android:orientation="vertical"
android:layout_width="fill_parent" android:layout_height="fill_parent">
<CheckBox
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="최적화하기" />
<CheckBox
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="경고 무시하기" />
</LinearLayout>
<TextView android:id="@+id/opt_linker" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:text="링커의 옵션"/>
</FrameLayout>
34/43
3. 복잡한 액티비티
• 리니어 두 개와 텍스트 뷰 하나를 배치하며, 전체 레이아웃이 프레임이므로 이 셋은 같은 위치에
배치, 탭이 이 세 차일드 중 선택된 하나만 스위칭해 가며 보여준다.
• 탭에 페이지 추가 시 addTab 메서드를 호출하며, 인수로 TabSpec 객체를 전달한다.
void addTab (TabHost.TabSpec tabSpec)
TabHost.TabSpec TabHost.newTabSpec (String tag)
• TabSpec 클래스
- 탭에 대한 정보를 관리하는 클래스.
- 탭 정보는 탭 위젯 상단에 나타나는 제목인 인디케이터와 페이지의 내용에 해당하는 내용 뷰, 탭의 내부적인
명칭인 태그 세 가지로 구성된다.
- 공개된 생성자가 없어 직접 생성할 수 없으며 TabHost의 newTabSpec 메서드로 생성한다.
- 태그는 newTabSpec의 생성자 인수로 지정, 인디케이터와 내용 뷰는 아래의 메서드로 지정한다.
TabHost.TabSpec setIndicator (CharSequence label [, Drawable icon])
TabHost.TabSpec setContent (int viewId)
TabHost.TabSpec setContent (TabHost.TabContentFactory contentFactory)
TabHost.TabSpec setContent (Intent intent)
- 인디케이터에 제목 문자열과 아이콘을 지정할 수 있다.
- 내용 뷰는 세 가지 방식으로 지정 가능하며, 방법은 이후 TabTest, TabTest2, TabTest3 예제에서 설명한다.
35/43
3. 복잡한 액티비티
 TabTest 예제
• 뷰의 ID를 지정함으로써 탭의 페이지를 채우며, tabtest 레이아웃을 전개하여 탭 호스트의 프레임
아래에 두고, 세 개의 탭을 등록한다.
• TabSpec 객체를 만들고 인디케이터와 내용 뷰를 지정한다.
• newTabSpec와 TabSpec 각 메서드는 모두 TabSpec 자체를 리턴하므로 연쇄적인 호출이 가능하며
리턴된 TabSpec 객체를 addTab 메서드로 바로 전달 가능하다.
• TabSpec 객체를 생성하고 속성들을 설정, TabHost의 addTab 메서드를 호출하여 탭을 등록할 수
있다.
activity/TabTest.java
public class TabTest extends TabActivity {
TabHost mTab;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TabHost mTab = getTabHost();
LayoutInflater inflater = LayoutInflater.from(this);
inflater.inflate(R.layout.tabtest, mTab.getTabContentView(), true);
mTab.addTab(mTab.newTabSpec("tag“)
.setIndicator("일반“)
.setContent(R.id.opt_general));
mTab.addTab(mTab.newTabSpec("tag“)
.setIndicator("컴파일러“)
.setContent(R.id.opt_compiler));
mTab.addTab(mTab.newTabSpec("tag“)
.setIndicator("링커“)
.setContent(R.id.opt_linker));
}
}
[ TabTest 예제 실행 결과 ]
36/43
3. 복잡한 액티비티
 TabTest2 예제
• setContent 메서드로 탭의 내용 뷰를 생성하는 팩토리를 전달하여 실행중에 뷰를 생성한다.
• TabContentFactory 인터페이스는 아래의 메서드를 정의하며 탭 호스트로부터 전달된 태그명으로
부터 적당한 뷰를 생성해 리턴하는 역할을 한다.
View createTabContent (String tag)
- tag 문자열을 해석, 실행중에 뷰를 생성하거나 전개해 리턴하면 해당 뷰가 탭 호스트의 차일드가 된다.
- 각 페이지의 태그명에 따라 생성될 페이지가 달라지므로 태그명은 모두 달라야 한다.
activity/TabTest2.java
public class TabTest2 extends TabActivity {
TabFactory factory;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
factory = new TabFactory(this);
TabHost tabHost = getTabHost();
Drawable icon = getResources().getDrawable(R.drawable.icon);
tabHost.addTab(tabHost.newTabSpec("General")
.setIndicator("일반", icon).setContent(factory));
tabHost.addTab(tabHost.newTabSpec("Compiler")
.setIndicator("컴파일러", icon).setContent(factory));
tabHost.addTab(tabHost.newTabSpec("Linker")
.setIndicator("링커", icon).setContent(factory));
}
}
class TabFactory implements TabHost.TabContentFactory {
Context mCon;
TabFactory(Context context) { mCon = context; }
// 탭의 고유한 태그가 전달된다.
public View createTabContent(String tag) {
TextView text = new TextView(mCon);
text.setText("Tab View of " + tag);
return text; }
}
[ TabTest2 예제 실행 결과 ]
37/43
3. 복잡한 액티비티
• 실제 프로젝트에서는 문자열 이름이 아닌 대상 뷰를 생성할 수 있는 좀 더 의미 있는 정보를 태그
로 전달하여 원하는 뷰를 생성해야 한다.
• 이 방법은 실행 중에 페이지의 내용을 결정할 수 있다는 점에서 활용성이 높다.
• 별도의 팩토리 구현 객체를 만드는 대신 액티비티가 팩토리를 구현해도 결과는 동일하다.
- 액티비티가 createTabContent 메서드를 구현하고 팩토리 객체는 this로 지정한다.
- 메서드의 소속만 변했을 뿐 내용면에서는 동일하지만, 액티비티에 부담이 간다는 점에서 구조적으로
바람직하지 않다.
public class TabTest2 extends TabActivity implements TabHost.TabContentFactory {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TabHost tabHost = getTabHost();
Drawable icon = getResources().getDrawable(R.drawable.icon);
tabHost.addTab(tabHost.newTabSpec("General")
.setIndicator("일반", icon)
.setContent(this));
tabHost.addTab(tabHost.newTabSpec("Compiler")
.setIndicator("컴파일러", icon)
.setContent(this));
tabHost.addTab(tabHost.newTabSpec("Linker")
.setIndicator("링커", icon)
.setContent(this))
}
public View createTabContent(String tag) {
TextView text = new TextView(this);
text.setText("Tab View of " + tag);
return text;
}
}
38/43
3. 복잡한 액티비티
 TabTest3 예제
• 인텐트를 내용뷰로 전달하여 액티비티를 차일드로 포함하며, 같은 패키지에 속한 다른 액티비티
들을 차일드로 포함시켰다.
• 이 방법을 사용하면 미리 작성된 액티비티들을 한 액티비티 안에서 통합적으로 재활용할 수 있다
는 장점이 있다.
- Curve 탭에서 자유 곡선을 그릴 수 있으며 State 탭에서 원을 움직일 수 있다.
- 포커스는 위쪽의 탭 위젯이 가지고 있으며 좌우 이동키가 페이지 전환의 의미로 해석되므로 State 탭에
포커스를 주려면 먼저 아래쪽 키를 눌러 포커스를 가져와야 한다.
activity/TabTest3.java
public class TabTest3 extends TabActivity {
TabHost mTab;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TabHost tabHost = getTabHost();
tabHost.addTab(tabHost.newTabSpec("tag")
.setIndicator("Curve")
.setContent(new Intent(this, SaveCurve.class)));
tabHost.addTab(tabHost.newTabSpec("tab2")
.setIndicator("State")
.setContent(new Intent(this, SaveState.class)));
tabHost.addTab(tabHost.newTabSpec("tab3")
.setIndicator("Edit")
.setContent(new Intent(this, ActEdit.class)));
}
}
[ TabTest3 예제 실행 결과 ]
39/43
3. 복잡한 액티비티
 페이지 겹치기
 TabHost 위젯은 운영체제가 제공하므로 간편하게 사용 가능하며 방법 역시 복잡하지 않
으나, 모양이 너무 획일적이고 디자인 역시 사무적이며, 자유도가 떨어진다.
• 위쪽의 탭 위젯이 화면 면적을 지나치게 많이 차지하고 탭은 무조건 화면 위쪽에 나타난다.
• TabActivity는 전체 화면을 다 차지하여 다른 위젯을 둘 공간이 전혀 없으며 탭 이외의 다른 위젯
을 같이 배치하는 것이 불가능하다.
 CustomTab 예제
activity/customtab.xml - 1
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<FrameLayout
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout android:id="@+id/opt_general" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:visibility="visible">
<EditText
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="프로젝트명" />
<Button
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="변경" />
</LinearLayout>
<LinearLayout android:id="@+id/opt_compiler" android:orientation="vertical"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:visibility="invisible">
<CheckBox
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="최적화하기" />
activity/customtab.xml - 2
<CheckBox
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="경고 무시하기" />
</LinearLayout>
<TextView android:id="@+id/opt_linker"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:text="링커의 옵션" android:visibility="invisible" />
</FrameLayout>
<RadioGroup
android:orientation="horizontal" android:layout_width="fill_parent"
android:layout_height="65px" android:layout_weight="0"
android:gravity="center_vertical" android:background="#808080"
android:checkedButton="@+id/btn1" >
<RadioButton
android:id="@+id/btn1" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_weight="1"
android:text="일반" />
<RadioButton
android:id="@+id/btn2" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_weight="1"
android:text="컴파일러" />
<RadioButton
android:id="@+id/btn3" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_weight="1"
android:text="링커" />
</RadioGroup>
</LinearLayout>
40/43
3. 복잡한 액티비티
• 탭 호스트를 사용하지 않고 직접 페이지를 겹친 예제이다.
• 전체 레이아웃은 수직 리니어이며, 프레임과 라디오 그룹 두 개가 배치되어 있다.
- 탭의 기본 레이아웃은 프레임이지만 탭 전환을 위한 UI가 필요하므로 프레임을 감싸는 외부 레이아웃이 하나
더 필요하다.
- 프레임 안에는 페이지들이 배치되어 있으며, 라디오 그룹에는 페이지를 선택하는 버튼이 배치, 이 둘이
수직 리니어로 묶여있다.
• 코드에서는 라디오 버튼을 클릭할 때 대응되는 페이지로 전환하고 나머지 페이지를 숨긴다.
activity/CustomTab.java - 1
activity/CustomTab.java - 2
case R.id.btn3:
ChangePage(3);
break;
}
public class CustomTab extends Activity {
View mPage1, mPage2, mPage3;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.customtab);
}
};
void ChangePage(int page) {
mPage1.setVisibility(View.INVISIBLE);
mPage2.setVisibility(View.INVISIBLE);
mPage3.setVisibility(View.INVISIBLE);
mPage1 = findViewById(R.id.opt_general);
mPage2 = findViewById(R.id.opt_compiler);
mPage3 = findViewById(R.id.opt_linker)
((Button)findViewById(R.id.btn1)).setOnClickListener(mClickListener);
((Button)findViewById(R.id.btn2)).setOnClickListener(mClickListener);
((Button)findViewById(R.id.btn3)).setOnClickListener(mClickListener);
switch (page) {
case 1:
mPage1.setVisibility(View.VISIBLE);
break;
case 2:
mPage2.setVisibility(View.VISIBLE);
break;
case 3:
mPage3.setVisibility(View.VISIBLE);
break;
}
}
Button.OnClickListener mClickListener = new Button.OnClickListener() {
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1:
ChangePage(1);
break;
case R.id.btn2:
ChangePage(2);
break;
}
}
41/43
3. 복잡한 액티비티
• 이 예제의 핵심은 ChangePage 메서드이며, 각 버튼 클릭 리스너에서 해당 페이지를 인수로 하여
ChangePage 메서드를 호출한다.
- 이 메서드는 모든 페이지를 숨긴 후 클릭된 페이지만 보인다.
- 프레임에 많은 페이지를 중첩시켜 놓을 수 있으므로 페이지 개수의 제한은 없다.
- 페이지 변경시마다 해야 할 초기화가 있다면 이 메서드에서 같이 처리한다.
[ CustomTab 예제 실행 결과 ]
42/43
안드로이드 프로그래밍 정복(Android Programming Complete
Guide)