Activity


엑티비티 생성과 호출

코드

public class CallActivity extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.callactivity);
	}

	public void mOnClick(View v) {
		Intent intent = new Intent(this, SubActivity.class);
		startActivity(intent);
	}
}
<activity android:name=".c17_activity.CallActivity" android:label="CallActivity" />
  • name의 경우 액티비티의 패키지명을 포함한 풀 경로를 지정하되 같은 패키지에 속해 있을 때는 앞에 . 을 찍거나 클래스명만 적어도 무방하다.

Intent

생성자의 종류

  • 1 Intent (): 잘 사용 안함
  • 2 Intent (Intent o)
  • 3 Intent (String action [, Uri uri])
  • 4 Intent (Context packageContext, Class<?> cls): 서브 액티비티 호출할 때 사용
  • 5 Intent (String action, Uri uri, Context packageContext, Class<?> cls)

4 번째 구조로 만든 intent이다. Explicit Intent라고 한다.

Intent intent = new Intent(this, SubActivity.class)
startActivity(intent);

Implicit Intent

  • Action
    • ACTION_CALL // Activity // 통화를 시작한다.
    • ACTION_BATTERY_LOW // BR // 배터리가 부족하다.

거의 왠만한 액션들은 모두 정의가 되어 있다. String 타입으로 define 되어 있는 것이다.
사용자가 개인적으로 정의해서 사용할 수도 있다. 그 경우 중복을 막기위해서 package name을 앞에 붙인다.
액션을 조사하거나 변경 할때는 gecAction setAction을 사용 한다.

  • Data: 목적어에 해당 한다.
  • Type: 자동으로 판별이 가능하다.
    • http:// 웹페이지
    • te: 전화번호
    • jpg 이미지
  • Category: 여러개를 줄 수 있다.
  • Component: 이 속성이 지정되면 Explicit로 바뀐다.
  • Extras: 추가적인 정보를 전달 할 때 사용 한다. 키와 값의 쌍으로 저장되어 컴포넌트에게 전달 되며 리턴 용도로도 사용된다. 이름만 중복되지 않으면 얼마든지 많은 정보를 전달할 수 있다.
    • putExtra로 여러 벌 오버로딩 되어 있으므로 저장되는 정보 타입을 그냥 넣어주면 알아서 맞게 동적 로딩 된다.
    • getIntExtra getStringExtra등으로 제공된다.
  • Flags: 액티비티를 띄울 방법이나 액티비티를 관리하는 방법 등에 대한 옵션 정보가 저장된다.

액티비티간의 통신

값의 전달

  • Intent putExtra (String name, int value)
  • Intent putExtra (String name, String value)
  • Intent putExtra (String name, boolean value)

값 읽기

  • int getIntExtra (String name, int deafultValue)
  • String getStringExtra (String name)
  • boolean getBooleanExtra (String name, boolean defaultValue)

리턴값을 돌려줘야 할 경우 다음의 메서드를 사용 한다.

  • void startActivityForResult (Intent intent, int requestCode)
    • 두 번째 인수가 하나 더 추가되는데 호출한 대상을 나타내는 식별자이며 리턴 시에 누구에 대한 리턴인지 구분할 때 사용한다.

호출된 액티비티가 종료되면 다음의 메서드가 호출 된다.

  • void onActivityResult (int requestCode, int resultCode, Intent data)
    • requestCode는 액티비티를 호출할 때 전달한 요청코드이며 requestCode는 액티비티의 실행 결과이다.

예제코드
호출하는 부분

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);

		mText = (TextView)findViewById(R.id.text);
	}
	
	public void mOnClick(View v) {
		switch (v.getId()) {
		case R.id.btnedit:
			Intent intent = new Intent(this, ActEdit.class);
			intent.putExtra("TextIn", mText.getText().toString());
			startActivityForResult(intent,ACT_EDIT);
			break;
		}
	}
	
	protected void onActivityResult (int requestCode, int resultCode, Intent data) {
		switch (requestCode) {
		case ACT_EDIT:
			if (resultCode == RESULT_OK) {
				mText.setText(data.getStringExtra("TextOut"));
			}
			break;
		}
	}
}

처리하는 부분

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();
		String text = intent.getStringExtra("TextIn");
		if (text != null) {
			mEdit.setText(text);
		}
	}

	public void mOnClick(View v) {
		switch (v.getId()) {
		case R.id.btnok:
			Intent intent = new Intent();
			intent.putExtra("TextOut", mEdit.getText().toString());
			setResult(RESULT_OK,intent);
			finish();
			break;
		case R.id.btncancel:
			setResult(RESULT_CANCELED);
			finish();
			break;
		}
	}
}
  • getIntent
  • void setResult(int resultCode, Intent data)
  • getStringExtra

Implicit Intent

public class CallOther extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.callother);
	}

	public void mOnClick(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;
		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);
			String sd = Environment.getExternalStorageDirectory().getAbsolutePath();
			Uri uri = Uri.fromFile(new File(sd + "/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.memo", "exam.memo.Memo"));
			//intent.setClassName("exam.memo", "exam.memo.Memo");
			startActivity(intent);
			break;
		}
	}
}

액티비티 라이프 사이클

엑티비티의 라이프 사이클은 아래와 같다.


  • onCreate: 액티비티를 초기화한다. 중지했다가 재시작하는 경우라면 액티비티의 이전 상태 정보인 Bundle이 전달되며 이 정보대로 재초기화한다.

  • onRestart: 재시작될 때 호출된다. 특별히 할일은 없다.

  • OnResume: 사용자와 상호작용을 하기 직전에 호출된다. 이 단계에서 스택의 제일 위로 올라온다.

  • onPause: another activity comes into the foreground. 단순히 Pause고 곧 있다가 실행이 될 때를 의미한다. 요즘 멀티 엑티비티가 되면서 더 빈번하게 차이점이 생기는 문제이다. 포커스는 잃었지만 사용자에게 보이는 상태이다. 위쪽에 다른 엑티비티가 있지만 화면 전체를 다 가리지 않았거나 반투명한 경우가 이에 해당한다. 즉 뭔가 다이얼로그 창 같은게 위에 있는 상태이다. 살아 있는 상태와 같지만 시스템에 의해 강제 종료될 수 있다. 그리고 다른 액티비티가 실행 될 때도 onPause 이후에 onCreate가 수행 되므로 너무 오랜시간을 작업해서는 안된다. 즉, 이 단계에서 미저장한 데이터가 있으면 저장하고 애니메이션은 중지해야 한다. 이 메서드가 리턴되어야 새 액티비티가 활성화되므로 시간을 너무 오래 끌어서는 안 된다.

  • onStop: The activity is no longer visible. 이 상태에서는 액티비티가 완전히 가려져서 사용자에게 보이지 않는 상태이다. 이 상태에서는 액티비티가 백그라운드에 있는것으로 간주 된다. 정지되어 있는 동안 액티비티 인스턴스 및 멤버 변수와 같은 모든 상태 정보가 유지되지만, 어떠한 코드도 실행할 수 없다.

  • onDestory: 액티비티가 파괴될 때 호출된다. 시스템에 의해 강제로 종료되는 것인지 아니면 finish 메서드 호출에 의해 스스로 종료하는 것인지 isFinishing 메서드로 조사할 수 있다. 대부분의 앱은 이 메서드를 구현할 필요가 없다. 왜냐하면 액티비티와 함께 지역 클래스 참조가 소멸되고 액티비티가 onPause() 및 onStop 중에 대부분 정리 작업을 수행하기 때문이다. 하지만 액티비티가 onCreate 중에 생성한 백그라운드 스레드 또는 제대로 닫지 않으면 메모리 누수를 야기할 수 있는 다른 장시간 실행되는 리소스를 포함하는 경우 onDestroy중에 액티비티를 중단시켜야 한다. finish()를 호출한 경우 onPuase onStop을 거치지 않고 즉시 onDestroy를 호출 한다.

호출 순서 분석
각각의 callback 함수에 logcat으로 메시지를 출력하게 한후 새로운 액티비티를 실행해서 lifecycle을 분석 한다.

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.actparent);
	}
	
	public void mOnClick(View v) {
		Log.i(TAG, "startActivity");
		Intent intent = new Intent(this, ActChild.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 ActChild extends Activity {
	static final String TAG = "ActChild"; 

	public void onCreate(Bundle savedInstanceState) {
		Log.i(TAG, "onCreate");
		super.onCreate(savedInstanceState);
		setContentView(R.layout.actchild);
	}
	
	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");
	}
}

실행 결과 분석

또 다른 예제로 Rotation했을 때를 분석 한다.

나중에 핸들러, 스레드, 연기된 러너블등이 개입하면 좀 더 복잡해 진다.

상태 저장

Rotation으로 테스트 한다. 쉽게 강제 종료를 시뮬레이션 할 수 있다.

시스템은 액티비티를 종료하기 직전에 onSaveInstanceState 메서드를 호출하여 정보를 저장할 기회를 제공한다.
인수로 Bundle객체를 전달하는데 Bundle은 문자열로 된 이름과 임의 타입의 값을 저장하는 일종의 맵이다.

인텐트의 Extras 멤버가 바로 Bundle 타입이며 임의의 정보를 원하는 만큼 저장할 수 있다.
따라서 onSaveInstanceState를 재정의하여 저장하고자 하는 값을 번들에 저장해 두고 종료한다.

아무리 강제종료 되어도 onSaveInstanceState는 한번 호출되니 이곳에서 Bundle에 key와 value 쌍으로 데이터를 저장하면 된다.

아래 그림은 공식 홈페이지에서 발췌 한 것이다. 이것만으론 이해하기 어려워서 추가적인 실험도 아래와 같이 진행 했다.

시스템은 액티비티 중지 작업을 시작할 때 onSaveInstanceState()(1)를 호출한다. 따라서 Activity 인스턴스가 재생성되어야 하는 경우 저장하고자 하는 추가 상태 데이터를 지정할 수 있다. 액티비티가 소멸되고 동일한 인스턴스가 재생성되어야 하는 경우, 시스템은 onCreate() 메서드(2) 및 onRestoreInstanceState() 메서드(3) 모두에 (1)에서 정의된 상태 데이터를 전달한다.

onSaveInstanceState 호출 시점

rotation을 할 경우 onPause이후에 호출 된다.

08-20 14:04:58.340 10849-10849/andexam.ver6 I/SaveState2: onPause
08-20 14:04:58.341 10849-10849/andexam.ver6 I/SaveState2: onSaveInstanceState
08-20 14:04:58.341 10849-10849/andexam.ver6 I/SaveState2: onStop
08-20 14:04:58.341 10849-10849/andexam.ver6 I/SaveState2: onDestroy

home버튼을 누를 경우 onPause이후에 호출 된다.

08-20 14:09:00.965 10849-10849/andexam.ver6 I/SaveState2: onPause
08-20 14:09:00.996 10849-10849/andexam.ver6 I/SaveState2: onSaveInstanceState
08-20 14:09:00.996 10849-10849/andexam.ver6 I/SaveState2: onStop

back버턴을 눌러서 destropy를 호출시키면 실행 되지 않는다.

08-20 14:08:39.397 10849-10849/andexam.ver6 I/SaveState2: onPause
08-20 14:08:39.709 10849-10849/andexam.ver6 I/SaveState2: onStop
08-20 14:08:39.709 10849-10849/andexam.ver6 I/SaveState2: onDestroy

phone call일 때도 호출되지 않는다.

  • onPause-> onResume

onRestoreInstance 호출 시점

rotation할 때는 onStart이후에 호출된다.

08-20 14:04:58.352 10849-10849/andexam.ver6 I/SaveState2: onStart
08-20 14:04:58.352 10849-10849/andexam.ver6 I/SaveState2: onRestoreInstanceState
08-20 14:04:58.353 10849-10849/andexam.ver6 I/SaveState2: onResume

home버튼을 누르고 다시 시작 했을 때는 실행 되지 않는다.

08-20 14:10:34.761 10849-10849/andexam.ver6 I/SaveState2: onRestart
08-20 14:10:34.762 10849-10849/andexam.ver6 I/SaveState2: onStart
08-20 14:10:34.762 10849-10849/andexam.ver6 I/SaveState2: onResume

phone call일 때도 호출되지 않는다.

  • onPause-> onResume

뭔가 애매한대 reference문서의 가이드 라인은 아래와 같다.

void onRestoreInstanceState (Bundle savedInstanceState)

  • This method is called between onStart() and onPostCreate(Bundle).

void onSaveInstanceState (Bundle outState)

  • If called, this method will occur before onStop(). There are no guarantees about whether it will occur before or after onPause().

저장 복구 패턴 1

	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);
		vw.setFocusableInTouchMode(true);
		setContentView(vw);
	}

	public void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putInt("x", x);
	}

onCreate에서 하지 않고 onRestoreInstanceState에서 수행 해도 된다.
onRestoreInstanceState는 내부 적으로 복원할 저장 상태가 있을 경우에만 호출되므로 Bundle null인지 확인할 필요는 없다.

	public void onRestoreInstanceState(Bundle outState) {
		super.onRestoreInstanceState(outState);
		x = outState.getInt("x");
	}

앱이 종료 되었다가 실행되어도 값을 저장하고 있으려면, 실행 시간 동안만 유지되는 Bundle에 저장해서는 안된다.
이 때는 Preference를 이용해서 저장한다.

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);
		vw.setFocusableInTouchMode(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);
	}
}

객체 저장

객체를 신속하게 저장 할 때는 자바의 Serialization을 사용 한다.
사용하는 메서드는 다음과 같다.

  • void putSerializable (String key, Serializable value)
  • Serializable getSerializable (String key)

이름과 객체만 전달하면 저장 및 복구가 자동으로 수행된다.
저장 하려고 하는 객체는 interface Serializable 키워드만 상속 받으면 된다.
하지만 interface인대도 구현할 매서드가 존재하지 않는다. 이러한 interface Marker Interface라고 부른다.
이렇게 하는 이유는 다중상속을 가능하게하고 추후에 instanceof에 의해서 해당 class가 Serilalizable에서 파생되었는지 알아내고 그에 대한 처리를 하기 위함이다.

코드

	public void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(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;
	}
	

또 다른 방법 Parcelable인터페이스를 구현하는 방법
Parcel은 원래 프로세스간의 통신을 위한 메시지 저장 장치인데 번들이 꾸러미 입출력 기능을 제공하므로 같은 프로세스의 세션간 데이터 저장 및 복구에도 사용 가능하다.

저장 대상인 클래스는 Parcelable 인터페이스를 상속받아야 하며,
describeContents() writeToParcel을 구현 해야한다.

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];
		}
		
	};
}
  • describeContents() 별다른 기능이 없으믈 0을 리턴한다.
  • writeToParcel이 부분을 구현 해야 한다.
    boolean타입에 대해서는 단일값 입출력 메서드를 제공하지 않으므로 배열 형태로 포장해서 출력 해야 한다.

CREATOR라는 이름의 정적 객체는 저장된 정보를 Parcel로 부터 읽어들여 객체를 생성하는 역할을 한다.

생성 방법

  • publci static final Parcelable.Creator CREATOR = new Creator()
  • T createFromParcel (Parcel source)
    • 해당 메서드는 source로 부터 정수 두 개와 논리값 하나를 순서대로 읽어 Vertex 객체 하나를 생성한다.
  • Vertex[] newArray(int size)
    • newArray 메서드는 전달된 크기대로 배열을 생성하여 리턴한다.

참고문헌

  1. d.android: 액티비티 재생성
  2. d.android: 액티비티 시작
  3. 안드로이드 정복 4판, 김상형


'Computer Science > Android Application' 카테고리의 다른 글

Broadcast Receiver  (0) 2017.08.16
Service  (0) 2017.08.16
Activity  (0) 2017.08.16
쓰레드 (Thread)  (0) 2017.08.04
이벤트 (Event)  (0) 2017.08.04
Gradle  (0) 2016.06.03

+ Recent posts