1. 액티비티의 일생
생명주기(Life Cycle) : 액티비티가 시작, 실행, 활성, 비활성화 ,정지, 종료되는 일련의 상태를 순환하는데 이것을 생명주기라고 한다.
시스템은 태스크(Task)의 실행 중인 액티비티를 스택(Stack)으로 관리한다. 새 액티비티가 제일 위에 배치되고, 제일 위 액티비티가 종료되면 바로 아래쪽에 있는 액티비티가 활성화된다.
스택(Stack)상 액티비티는 다음과 같은 3가지 상태중 하나이다.
실행(active, running) : 사용자가 직접 사용하는 상태이다. 스택의 제일 위에 있으며 화면상에서도 제일 위에 있다. 입력 포커스를 가지며 사용자의 입력을 직접 처리한다.
정지(stopped) : 다른 액티비티에 의해 가려진 상태이며 사용자 눈에 보이지 않는다. 그러나 정보를 유지하고 있으므로 언제든지 다시 활성화될 수 있다. 시스템은 메모리가 부족하면 정지 상태의 액티비티를 언제든지 강제 종료할 수 있다.
일시 정지(pause) : 포커스는 잃었지만 사용자에게 보이는 상태이다. 위쪽에 다른 액티비티가 있지만 화면 전체를 다 가리지 않았거나 반투명한 경우가 이에 해당한다.
<출처 : https://kairo96.gitbooks.io/android/content/ch2.4.1.html >
메서드 |
해야 할 일 |
onCreate |
액티비티를 초기화한다. 중지했다가 재시작하는 경우라면 액티비티 이전 상태 정보인 Bundle이 전달되며 이 정보대로 재초기화한다. |
onRestart |
재시작될 때 호출된다. 특별히 할 일은 없다. |
onStart |
액티비티가 사용자에게 보이기 직전에 호출된다. |
onResume |
사용자와 상호작용을 하기 직전에 호출된다. 이 단계에서 스택의 제일 위로 올라온다. |
onPause |
다른 액티비티가 실행될 때 호출된다. 이 단계에서 미저장한 데이터가 있으면 저장하고 애니메이션은 중지해야 한다. 이 메서드가 리턴되어야 새 액티비티가 활성화되므로 시간을 너무 오래 끌어서는 안된다. |
onStop |
액티비티가 사용자에게 보이지 않게 될 때 호출된다. |
onDestroy |
액티비티가 파괴될 때 호출된다. 시스템에 의해 강제로 종료되는 것인지, finish 메서드 호출에 의해 스스로 종료되는 것인지는 isFInishing 메서드로 조사할 수 있다. |
액티비티가 생성될 때 호출되는 onCreate는 반드시 구현해야 하며 마법사가 미리 메서드를 재정의해 놓는다. 이후 super.onCreate를 호출하여 Activity의 기본 초기화를 수행한다.
다음으로 setContentView 메서드를 호출하여 액티비티 안을 "뷰"로 채운다. (그렇지 않으면 사용자 눈에 아무 것도 보이지 않는다.)
onStart, onStop, onDestroy는 구현하는 경우가 많지 않고
onPause메서드는 대부분의 경우 구현해야 한다. onPause가 호출된 이후를 킬러블(Killable) 상태라고 하며 시스템이 언제든지 액티비티를 강제로 종료할 수 있다.
만약 여기서 액티비티가 강제종료되면 onStop과 onDestroy가 호출되지 않으므로( 위 그림 참고), onPause에서 저장되지 않은 정보를 반드시 저장해야 한다. 그래서 저장할 데이터가 있는 프로그램은 onPause를 반드시 구현해야 한다.
[ 부모 XML ]
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="부모 액티비티"
/>
<Button
android:id="@+id/callchild"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="차일드 호출"
/>
</LinearLayout>
[ 자식 XML ]
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="부모 액티비티"
/>
<Button
android:id="@+id/callchild"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="차일드 호출"
/>
</LinearLayout>
[ 부모 코드 ]
public class ExerciseExam extends Activity {
public class ActParent extends Activity {
static final String TAG = "ActParent";
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_excercise_exam);
}
public void mOnClick(View v) {
Log.i(TAG, "startActivity");
Intent intent = new Intent(this, ExerciseExam2.class);
startActivity(intent);
}
public void onStart() {
super.onStart();
Log.i(TAG, "onStart");
}
public void onResume() {
super.onResume();
Log.i(TAG, "onResume");
}
public void onPause() {
super.onPause();
Log.i(TAG, "onPause");
}
public void onRestart() {
super.onRestart();
Log.i(TAG, "onRestart");
}
public void onStop() {
super.onStop();
Log.i(TAG, "onStop");
}
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy");
}
}
}
[ 자식 코드 ]
public class ExerciseExam2 extends Activity {
static final String TAG = "ActChild";
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_exercise_exam2);
}
public void onStart() {
super.onStart();
Log.i(TAG, "onStart");
}
public void onResume() {
super.onResume();
Log.i(TAG, "onResume");
}
public void onPause() {
super.onPause();
Log.i(TAG, "onPause");
}
public void onRestart() {
super.onRestart();
Log.i(TAG, "onRestart");
}
public void onStop() {
super.onStop();
Log.i(TAG, "onStop");
}
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy");
}
}
[ 복습하기 ]
로그 : 시스템이나 응용프로그램의 동작에 대한 상세한 기록.
Log.d (Debug) // 로그 실행 중에 제거된다.
Log.e (Error) // 심각한 에러상황
Log.w (Warning) // 경미한 경고
Log.i (Information) // 단순한 정보
Log.v (Verbose) // 의미 없음
부모 액티비티가 생성될 때 onCreate, onStart, onResume이 순서대로 호출되고, 초기화를 거친다.
차일드를 호출하면 가장 먼저 부모의 onPause가 호출되어 부모의 동작을 정지시킨다.
자식 액티비티가 호출되면 onCreate, onStart, onResume이 호출되어 초기화를 거친다.
여기까지 과정이 진행되면 부모 액티비티는 onStop이 되어서 부모는 동면 상태에 빠진다.
위 로그캣으로 두 가지 사실을 알 수 있다.
1. 부모가 onPause를 리턴하기 전까지는 차일드의 초기화가 진행되지 않는다.
따라서 onPause에서 과다하게 시간을 끌면 안된다.
2. 차일드로 전환될 때 부모는 잠시 정지되는 것이지 파괴되는 것이 아니다.
따라서 onDestroy는 호출되지 않으며 차일드는 부모의 멤버를 참조할 수 있다.
아래는 차일드를 종료하면 아래와 같은 로그캣이 찍힌다.
다시, 부모 액티비티 마저도 종료가 되면 onPause, onStop, onDestroy가 호출되면서 완전히 정리된다.
이제는 위와 같은 그림이 눈에 보일 것이다.
2. 상태 저장
onPause가 호출된 이후를 킬러블(Killable) 상태라고 하는데, 이때 시스템이 액티비티를 강제 종료할 수 있다. 이때 모든 프로그램은 이에 대한 대비를 해야 한다.
먼저, 강제종료를 유도할 수 있는 환경을 만들기 위해서 "화면의 방향을 바꾸기"를 하겠다. 화면 전환이나 언어, 입력 장치 등이 바뀌면 "액티비티를 재생성"해야 하는데, 그 이유는 조건에 따라 사용할 리소스가 완전히 달라지기 때문이다.
터치를 하면 점이 움직인다. 그런데 화면전환을 하면 아래 그림과 같이 처음 위치로 리셋된다.
따라서 액티비티가 종료되거나 전환되기 전에 "저장"했다가 재실행될 때 복구되는 코드를 만들어보자.
[기존 코드]
public class ExerciseExam extends Activity {
private MyView vw;
int x;
int y;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
x = 50;
y = 50;
vw = new MyView(this);
vw.setFocusable(true);
vw.setFocusableInTouchMode(true);
setContentView(vw);
getWindow().getDecorView().setBackgroundColor(Color.BLACK);
}
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);
}
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 -= 15;
invalidate();
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
x += 15;
invalidate();
return true;
case KeyEvent.KEYCODE_DPAD_UP:
y -= 15;
invalidate();
return true;
case KeyEvent.KEYCODE_DPAD_DOWN:
y += 15;
invalidate();
return true;
}
}
return false;
}
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
x += 15;
y += 15;
invalidate();
return true;
}
return false;
}
}
}
< 기존 코드 > <변경된 코드>
<변경된 코드> 맨 하단을 보면 onSaveInstanceState 메서드를 호출하여 정보를 저장할 기회를 제공한다.
인수로는 Bundle 객체가 전달되는 것을 알 수 있다. ( 1. Bundle 타입 : 문자열로 된 이름과 임의 타입의 값을 저장하는 일종의 맵(Map)이다.)
2. super.onCreate(outState)를 통해서 onCreate 메서드를 재정의. 이 예제에서는 x값을 저장한다.
3. onCreate는 인수로 받은 Bundle 객체가 null 이면 처음 시작하는 것이므로 최초값으로 초기화하고 null이 아니면 재시작되는 것이므로 번들에 저장된 이전값을 복구한다.
4. 이 예제는 x값만 저장하며 y값은 다른 방법으로 저장하려고 한다. y를 저장하지 않은 이유는 정보의 성격에 따라 저장 방법이 달라지는 것을 보여주기 위함이다.
아래 코드를 통해 y값을 다른 방법으로 저장해보자.
y는 저장위치도 다르고 저장하는 방법도 다르다.
프로그램이 킬러블(Killable) 상태가 되기 직전인 onPause에서 Preferences에 영구적으로 저장한다.
x는 임시적인 정보에 해당하며 y는 영구적인 정보에 해당한다.
종료했다가 다시 실행해도 살아 있어야 할 정보는 영구 정보이고, 그렇지 않은 것은 임시정보이다.
임시적인 정보는 onSaveInstanceState에서 시스템이 제공하는 번들에 저장하고
영구적인 정보는 Preferences나 데이터베이스 같은 물리적인 파일에 저장한다.
임시적인 정보는 실행중인 동안만 유지되고
영구적인 정보는 다음 실행될 때까지 유지된다.
따라서 바탕화면을 나갔다 들어오면 임시적 정보에 해당하는 x는 초기화되고 y만 보존된다.
onPause는 영구 정보를 저장하는 최적의 시점
onSaveInstanceState는 임시 정보를 저장하는 적합한 시점.
3. 객체 저장
정수값은 크기가 작아 액세스 속도도 빠르고 저장 및 복구가 쉽다.
그러나 큰 객체나 배열을 저장할 때는 최대한 신속하게 저장하기 위한 방법이 있다.
객체를 저장할 떄는 자바의 시리얼라이즈(Serializable) 기능이 유용하다.
자바는 언어 차원에서 객체를 일차원의 데이터를 저장하는 기능을 제공한다.
Serializable 인터페이스를 상속받으면 디폴트 직렬화 알고리즘이 적용되어 클래스의 모든 인스턴스 필드가 순서대로 저장된다.
void putSerializable (Stirng key, Serializable value) // 저장
Serializable getSerializable (String key) // 읽기
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]);
}
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) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList("Curve", arVertex); /* void putSerializable (String key, Serializable value)이름과 객체를 전달하면 저장 및 복구가 자동으로 수행된다.
onSaveInstanceState 메서드에서 번들의 putSerializable을 호출하여 배열 전체를 Curve라는 이름으로 저장하였다. 곡선을 구성하는 모든 정점의 정보가 번들 객체에 고스란히 저장되며 onCreate에서 번들의 이 배열을 통째로 읽어낸다.
*/
}
protected class MyView extends View {
Paint mPaint;
public MyView(Context context) {
super(context);
// Paint 객체 미리 초기화
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(3);
mPaint.setAntiAlias(true);
}
public void onDraw(Canvas canvas) {
canvas.drawColor(0xffe0e0e0);
// 정점을 순회하면서 선분으로 잇는다.
for (int i=0;i<arVertex.size();i++) {
if (arVertex.get(i).draw) {
canvas.drawLine(arVertex.get(i-1).x, arVertex.get(i-1).y,
arVertex.get(i).x, arVertex.get(i).y, mPaint);
}
}
}
// 터치 이동시마다 정점들을 추가한다.
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
arVertex.add(new Vertex(event.getX(), event.getY(), false));
return true;
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
arVertex.add(new Vertex(event.getX(), event.getY(), true));
invalidate();
return true;
}
return false;
}
}
}
앙어려우 ㅓㅠ
'프로그래밍 > 안드로이드' 카테고리의 다른 글
[안드로이드 오류] getColor deprecated (0) | 2016.09.09 |
---|---|
[안드로이드] 액티비티(Activity)와 인텐트(Intent) (3) | 2016.08.11 |
[안드로이드] 커스텀 대화상자(Custom Dialog) (0) | 2016.08.10 |
[안드로이드] 대화상자 (1) | 2016.08.09 |
[안드로이드] 커스텀 위젯(Custom Widget) (1) | 2016.08.07 |