2011. 3. 8. 17:57ㆍ2010년/완전정복
안드로이드 응용 프로그램을 구성하는 4가지 구성요소에
CP(Content Provider), 서비스(Service), 브로드캐스트 리시버(Broadcast Receiver) 그리고 이번장에서 배울 액티비티(Activity)이다.
사용자가 직접 대면하기때문에 실제 좀 중요하다. 우리가보는 화면이 바로 액티비티니깐 바로 대면한다고 볼수 있다.
액티비티는 사용자와 인터페이스를 구성하기는 하지만 그 자체는 출력 기능이 없으므로 직접보이지 않는다. 그래서 반드시 뷰나 뷰 그룹을 가져야 한다.
액티비티가 생성될때마다 호출되는 setContentView메서드가 액티비티 안에 뷰를 배치하는 명령이다.
내부 액티비티 호출
import android.app.*;
import android.content.*;
import android.os.*;
import android.view.*;
import android.widget.*;
public class CallActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.callactivity);
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);
}
});
}
}
주 액티비티이며 callactivity layout를 가져와 button을 클릭했을때 startActivity는 액티비티를 호출할때 사용해 그 대상을 지정하는 Intent객체를 전달해준다.
인텐트에 관해서 이다음에 설명하겠다.
======================================================================
import android.app.*;
import android.os.*;
import android.view.*;
import android.widget.*;
public class SubActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.subactivity);
Button btnClose=(Button)findViewById(R.id.close);
btnClose.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
finish();
}
});
}
}
다른 화면이다 구조는 우리가 매일보는것과 같다. 단지 클릭버튼을 눌렸을때
finish()가 나와있다. 이는 finish()를 통해서 액티비티를 종료하는것이다.
=======================================================================================
마지막으로 이렇게 출력이 되지만. 한가지 빼먹은 것이 있다.
바로 매니페스트에서 이렇게 지정해줘야한다. 그래야 아~! 응용프로그램에서 액티비티를 찾을수 있다.
<activity android:name=".CallActivity" android:label="CallActivity" />
<activity android:name=".SubActivity" android:label="SubActivity" />
</application>
<uses-sdk android:minSdkVersion="8" />
액티비티 새로 추가하는 절차는 4가지로 나뉜다.
1. 새로 만들 액티비티의 레이아웃을 XML 파일에 정의한다.
2. 액티비티의 코드를 java파일에 작성한다.
3. 새로 추가한 액티비티를 매니페스트에 등록한다.
4. startActivity 메서드로 액티비티를 호출한다.
인텐트
액티비티에서 다른 액티비티를 호출하는 주된 이유는 뭔가 다른 작업을 시키기 위해서이다. 이런 액티비티끼리 서로 호출하려면 통신을 위한 장치가 필요한데 이게 바로 인텐트이다. 인텐트는 액티비티 뿐아니라 서비스 CP, BR등 컴포넌트들이 수행해야할 작업에 대한 정보를 가지며 작업 결과를 돌려주기 위해서도 사용된다.
인텐트의 생성자는 다음과 같다.
intent()
intent(intent o)
intent(String action[,Uri uri])
intent(Context packageContext,Class<?> cls)
intent(String action,Uri uri, Context packageContext, Class<?> cls)
여기서 컨텍스트 호출자의 정보는 보통 주로 this이다. cls 액티비티의 클래스 정보이다.
Action
실행하고자 하는 동작이며 인텐트를 통해 어떤 작업을 수행할 지를 지정한다.
ACTION_CALL 통화를 시작한다.
ACTION_DEIT 데이터를 표시하고 편집한다.
ACTION_MAIN 메인 액티비티를 실행한다. 입출력되는 데이터는 ㅇ벗다.
ACTION_VIEW 뭔가를 보여준다.
ACTION_DIAL 전화를 건다.
ACTION_BATTERY_LOW 배터리가 부족하다.
ACTION_HEADSET_PLUG 헤드셋이 장비에 접속되거나 분리되었다.
ACTION_SCREEN_ON 화면이 켜졌다.
ACTION_TIMEZONE_CHANGED 타임존이 변경되었다.
Data
동작에 필요한 상세 데이터를 제공 액션이 작업이라면 데이터는 작업거리라고 할수 있다.
예를 들어서 ACTION_CALL액션에 대해서는 누구에게 전화를 걸지를 정해야하고 ACTION_EDIT경우는 어떤 파일을 편집할것인지 지정해야한다. 그래서 액션의 목적이 되는대상은 너무나도 많다. 이런경
우 임의 대상을 할수 있는 URI타입으로 되어있다.
Action과 Data를 통해서 대상의 구성요소를 찾을수 있다. 하지만 정확하고 상세한 처리를 위해서 추가적인 정보가 더필요한경우가 있다. 그경우 밑에 있다.
Type
보통 타입이 애매한경우 예를 들어 확장자가 jpg 그림파일이지만 실제 포맷은 png인경우 아예 확장자가 없는경우가 있다. 원래 데이터의 타입은 경우 자동으로 판별이 가능하다. 앞서 애매한경우 호출자가 타입을 직접지정하면 운영체제가 더이상 자동으로 판별하지안하고 지정된 타입을 따른다.
Category
실행할 액션에 대한 좀더 추가적인 상세 정보를 제공한다. 제공하는 카테고리주에 CATEGORY_HOME태그가 붙은 액티비티는 시동중에 홈화면을 띄워준다.
Componet
인텐트를 처리할 컴포넌트를 명시적으로 지정한다. 즉 우리가 앞서 소스에서 클래스 자체를 명시적으로 지정함으로써 이정보를 참조한다는것이다.
Extras
컴포넌트로 전달되어야 할 추가적인 정보들이다.
엑스트라 데이터의 대표적인 예는 android.os.bundle이라는 안드로이드 클래스이다. 다른예로 android.intent.extra.EMAIL 이메일 주소를 보관하는데 사용한다. 그키에 값이 앞에 지정한것이다.
Flags
액티비티를 띄울 방법이나 액티비를 관리하는 방법등에 대한 옵션 정보들이 저장된다.
암시적 인텐트
앞에 소스는 같은 패키지에 속한 SubActivity를 호출한 명시적 인텐트엿다.
하지만 암시적은 다른 패키지에 속한 도다른 액티비티나 서비스를 호출할수있다. 또 권한만 있다면 특정 패키지의 특정 액티비티도 명시적으로 호출가능하다.
import java.io.*;
import android.app.*;
import android.content.*;
import android.net.*;
import android.os.*;
import android.view.*;
import android.widget.*;
public class CallOther extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.callother);
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;
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;
}
}
};
}
버튼 4개에서 각각 다른 인테느로 startActivity를 호출했다.
첫번째 버튼은 ACTION_VIEW로 구글 홈페이지를 열었다.
두번째 버튼은 ACTION_DIAL로 특정 전화번홀르 전달했다.
세번째 버튼은 SD카드에 저장된 이미지 파일을 대한 ACTION_VIEW를 요청했다(SD카드에 이미지파일이 없어 못함)
네번째버튼은 Input패키지 메인 액티비티를 띄웠다.(예제가 있었다.)
============================================================================================
==============================================================================================
인텐트라는것 상세하게 작성해야한다. 정확하게 선택하기위해서 컴포넌트에 대한 인텐트 정보를 인텐트 필터라고 하는데 컴포넌트가 어떤액션과 데이터를 처리할 수 있느는지에 대한 정보를 기술해 놓은것이다.
인텐트는 액티비티간에 인수와 리턴값을 전달하는 도구로도 사용된다. 이때는 주로 Bundle 타입의 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 naem, boolean defaultValue)
액티비티로 인수를 전달하고 계산된 결과를 리턴값으로 받는 액티비티는
public void startActivityForResult(intent intent, int requestCode) 두번째 인수가 하나 더 추가되는데 호출한 대상을 나타내는 식별자이며 리턴시에 누구에 대한 리턴인가를 구분할 때 사용한다.
그리고 호출된 액티비티가 종료되면 다음 메서드가 호출되므로 리턴값을 받으려면 이메서드를 재정의 해야한다.
protected void onActivityResult(int requestCode, int resultcode,Intent data)
requestCode는 액티비티를 호출할 때 전달한 요청코드이며 resultCode는 액티비티의 실행결과이다.
사실 이둘을 분석해 보면 누구에 대한 호출이 어떻게 처리되었는지 알수 있다.
==========================================================================================
import android.app.*;
import android.content.*;
import android.os.*;
import android.view.*;
import android.widget.*;
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);
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);
}
});
}
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case ACT_EDIT:
if (resultCode == RESULT_OK) {
mText.setText(data.getStringExtra("TextOut"));
}
break;
}
}
}
여기서 핵심으로 볼부분은 2가지이다.
intent.putExtra("TextIn", mText.getText().toString());
startActivityForResult(intent,ACT_EDIT);
intent를 명시적으로 지정해서 ActEdit라는 클래스로 정했다.
그리고 그에 대해 액티비티간에 통신을 위해 Extra 값으로 "TextIn"라는 고유 이름을 정하고 그에 대한 값을 현재 Text에 있는값을 넘겨준다.
리턴값을 돌려주는 액티비티를 호출한다. 당연히 Action은 데이터를 표시하는것이다.
onActivityResult
이는 액티비티가 종료할때 값을 받아오는것이다. 다음 밑에 소스에 대한 하나밖에 없는 액티비티이다. 그리고 Extra값으로 TextOut이라는 고유이름을 가져온다.
밑에 소스에서 액티비티를 TextIn을 통해 넘겨 줄것이다.
==========================================================================================
import android.app.*;
import android.content.*;
import android.os.*;
import android.view.*;
import android.widget.*;
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"));
findViewById(R.id.ok).setOnClickListener(mClickListener);
findViewById(R.id.cancel).setOnClickListener(mClickListener);
}
Button.OnClickListener mClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch(v.getId())
{
case R.id.ok:
Intent intent = new Intent();
intent.putExtra("TextOut", mEdit.getText().toString());
setResult(RESULT_OK,intent);
finish();
break;
case R.id.cancel:
setResult(RESULT_CANCELED);
finish();
break;
}
}
};
}
==========================================================================================
먼저 이소스는 시작하자마다 에디트 필드에서 가져온 인텐트에서 Extra의 "TextIn"의 가져온 텍스트를 가져온다
public final void setResult(int resultCode, Intent data) 이는 인텐트의 작업결과이다.
RESULT_OK로 텍스트 편집하여 리턴했다는뜻이며 RESULT_CANCELED은 추가정보 전달 필요없이 바로 액티비티를 finish()해버린다.
액티비티의 생명주기
실행(active,runnint)
일시정지(pause)
정지(stopped)
여기서 사실 액티비티가 만들어지고 시작하고 정지하고 없어지고 그닥 어려운건 없다. 단지 여기서는 혹시나 액티비티가 실행될때 메모리나 강제적으로 다른이유로 종류가 될때 onPause()가 실행된다는것이다. 그래서 보통 onPause()에서 예외처리를 많이 한다.
처음에 늘상 봤던 public void onCreate(Bundle savedInstanceState) 에게 바로 액티비티가 만들어지는것이다.
여기서 상태의 값을 저장에 대해서는 그냥 넘어간다. 뭐 사실 크게 없다. onPause()사용해서 프레프런스에 저장하는것과 public void onRestoreInstanceState(Bundle outState)를 이용해서 값을 저장하는 것이 있다.
하지만 액티비티에서 단일값이 아닌 객체나 배열을 저장할때 효율적으로 방법을 강구할 필요가있다.
그게 바로 Serializable과 Parcelable이다. 사실 이둘의 차이점을 책을 통해 비교했을때 사실 무엇이 좋은지 모르겠다. 하지만 객체값이 많을수록 속도점에서 Parcelable이좋다고한다. 그렇다고Serializable을 안쓰는것도 아니고 개인적으로 데이터베이스나 파일구조쪽은 Serializable이 낫고 액티비티간의 통신은 Parcelable이 좋다. 그리고 intent를 통해서 값을 전달할때 객체를 보낼수 없다 그래서 Serializable로된 배열이나 객체도 안된다. 그래서 Parcelable을 쓴다.
먼저 Serializable부터 보자
void putSerializable(String key, Serializable value)
Serializable getSerializable(String key)
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;
}
앞서 선을 그리는것에서 class Vertex가 Serializable을 상속받아서
private static final long serialVersionUID = 100L;
통해서 serialversionUID을 일관성있게 100L이라는 값을 주었다. 그리고 객체에서 액티비티에 값을 savedInstanceState.getSerializable("Curve");
통해서 가져온다. 물론 savedInstanceState!=NULL일때... 변경된 객체의값이 있으니 가져온다. 그리고 액티비티가 변경할때
public void onSaveInstanceState(Bundle outState) {
outState.putSerializable("Curve", arVertex);
}
통해서 현재의 객체에 값을 Curve에 String Key값을 넣어준다.
자바 문법의 Serializable 을 통해사용하면 정말 메소드 2개로 끝난다. 물론 사용자가 만든 Serializable은 좀더 복잡하기대문에 자바문법서로 참고해야한다.
다음은 Parcelable이다. Parcel은 소포,꾸러미라는 뜻이며 객체를 전달 가능한 형태로 포장한다고 생각하면 된다.
int describeContents():마샬링 방법을 지정하는 비트 마스크값을 지정하는데 특별한 방법을 사용하지 않으면 0을 리턴한다.
void writeToParcel(Parcel dest,int flags)객체의 필드들을 dest로 출력하여 꾸러미로 보낸다.
이 두메서드 외에 CREATOR라는 이름의 정적 객체를 제공해야 하는데 저장된 정보를 Parcel로 부터 읽어 들여 객체를 생성하는 역할을 한다.
void putParcelable (String key,Parcelable value)
void putParcelable (String key, Parcelable[] value)
void putParcelable (String key, Arraylist<?extends Parcelable> value)
T getparcelable(String key)
Parcelable[] getParcelableArray(String key)
ArrayList<T> getParcelableArrayList(String key)
메소드는 역시 2가지다. put과 get 가져오는 모양에 따라서 약간 틀리지만 거의 비슷하다. 앞서 Serializable과 별반 틀린게 없다.
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];
}
};
}
=====================================================================================
클래스를 일단 Parcelable을 상속받고 writeToParcel를통해서 dest로 저장하는 순서대로 Parcel 객체로 전송해준다. 그리고 Parcelable 객체가 CREATOR 정적객체를 제공받아 생성되며.. 아까 writeToParcel을 통해 dest받은 필드들을 read*를 통해서 값을 넘겨받는다.. 단 boolean은 나타낼수없어서 그냥 일반 배열로 받는다.
=====================================================================
탭
화면 하나를 구성하는 액티비티에 뷰 계층 통해 아주 많은 차일드 뷰를 배치할수 있다.
탭화면으로 된 액티비티를 구성하는 가장 쉬운방법이 바로 TabActivity로부터 상속받는것이다. TabActivity의 전영역을 TabHost가 채우고 안에 childView만 배치하면 된다. 내부탭 호스트객체 매서드는 다음과 같다. TabHost getTabHost()
import android.app.*;
import android.content.*;
import android.os.*;
import android.view.*;
import android.widget.*;
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));
/* 풀어 쓰기
TabHost.TabSpec spec = mTab.newTabSpec("tag");
spec.setIndicator("일반");
spec.setContent(R.id.opt_general);
mTab.addTab(spec);
*/
mTab.addTab(mTab.newTabSpec("tag")
.setIndicator("컴파일러")
.setContent(R.id.opt_compiler));
mTab.addTab(mTab.newTabSpec("tag")
.setIndicator("링커")
.setContent(R.id.opt_linker));
}
====================================================================
탭에 페이지를 추가할 때는 addTab메서드를 호출하는데
void addTab(TabHost.TabSpec tabSpec)
TabHost.TabSpec TabHost.newTabSpoc(String tag)
TabSpec 은 탭에 대한 정보를 관리하는 클래스이다.
TabHost.TabSpec setIndicator(CharSequence labe[,Drawable icon])
TabHost.TabSpec setContent(int viewId)
TabHost.TabSpec setContent(TabHost.TabContentFactory contentFactory)
TabHost.TabSpec setContent(Intent intent)
Indicator는 제목 문자열과 아이콘을 지정할수 있고. 내용에대해서 앞서 3가지가 있다.
사실 사용하기 나름이다.