Android programming - Intent(인텐트)


Intent(인텐트)란, 안드로이드 4대 컴포넌트(activity, service, broadcast receiver, content provider)가 서로 데이터를 주고 받기 위한 메세지 기능을 한다.


인텐트는 명시적인텐트와 암시적인텐트로 나뉜다.




1. 명시적 인텐트


명시적 인텐트는 메세지를 보낼 대상 액티비티를 지정하여 사용하는 방법이다.

일반적으로 명시적 인텐트는 사용자가 새로운 액티비티를 직접 호출할때 사용된다.

가장 기본적이고 간단한 사용예시는 다음과 같다.


* Intent 기초 사용예시

// Intent객체를 생성한다. -> 생성자의 첫번째 인자가 메세지를 보내는 액티비티, 두번째 인자가 이제 곧 호출될 액티비티를 의미한다.

Intent intent = new Intent(getApplicationContext(), SecondActivity.class);

startActivity(intent);


위의 예시코드는 단순히 SecondActivity를 호출하는 것으로 끝난다.

하지만 필요 시 intent객체에 putExtra() 메소드를 사용하여 데이터를 필요한만큼 넣은 후, startActivity()를 통하여 다른 액티비티로 전달 할 수 있다.


이때, 인텐트 객체를 받은 액티비티는 넘겨진 데이터에 getStringExtra(), getIntExtra(), getBooleanExtra() 등의 메소드를 사용하여 접근할 수 있다. (getStringArrayExtra(), getIntArrayExtra() 등을 사용하여 배열을 통째로 주고받을 수도 있다!)


* Intent 기초 사용예시 2

(1) Intent 객체 송신 액티비티

int num = 1;

int[] intArr = new int[] { 1, 2, 3, 4, 5 };

String[] strArr = new String[] { "aaa", "bbb", "ccc" };


Intent intent = new Intent(getApplicationContext(), SecondActivity.class);

intent.putExtra("data1", num);

intent.putExtra("data2", intArr);

intent.putExtra("data3", strArr);

startActivity(intent);


(2) Intent 객체 수신 액티비티(SecondActivity)

Intent intent = getIntent();

int num = intent.getIntExtra("data1");

int[] intArr = intent.getIntArrayExtra("data2");

String[] strArr = intent.getStringArrayExtra("data3");

// ... 전달받은 데이터 사용


tip) 전달받은 intent가 null일 수 있으니 예외처리 해주면 굿


Intent passedIntent = getIntent();

processIntent(passedIntent);


private void processedIntent(Intent intent){
    if(intent != null) { .. }

}




2. 암시적 인텐트


명시적 인텐트가 사용자가 직접 두 액티비티를 생성하고 코딩한 뒤 각각 인자로 넣어주는 것이었다고 하면,

암시적 인텐트는 안드로이드에서 제공하는 기존 응용프로그램을 실행시키는 것이다.

intent에 인자를 어떤 것을 넣느냐에 따라 전화걸기/웬 브라우징/갤러리 실행/음악, 동영상 실행 등등이 가능하다.



* 암시적 Intent 기초 사용예시

(1) 웹 페이지 열기

Intent mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://naver.com"));

startActivity(mIntent);


(2) 119에 전화걸기

Intent mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("tel:/119"));

startActivity(mIntent);


(3) 휴대폰 갤러리 열기

Intent mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("content://media/internal/images/media"));

startActivity(mIntent);






+ 18.1.10 내용추가


***중요***

 직접만든 클래스의 객체를 넘기는 경우, 해야할 일. (혹은 위에서 지원하지 않는 데이터타입 객체인 경우 ex)ArrayList<String>, java.util.Calendar)


인텐트는 주어진 내장함수를 사용하여 여러 자료형들을 주고 받을 수 있다.

근데 아래의 사진에 없는,  인텐트가 내장함수로 지원하지 않는 자료형을 주고받을 경우에는 어떻게 해야할까?

(저의 경우에는 java.util.Calendar 객체를 보내야 할 일이 생겨서 알아보게 되었습니다. 외에도 직접 만든 클래스의 Object를 보내고받는 경우에도 유용하게 쓰일 것 같습니다.)





(1) 클래스의 모든 attribute에 대하여 setter와 getter 함수를 만들어준다.

(2) Serializable 클래스를 implement 한다. 

(Parcelable 클래스를 implement하여 Creator를 사용하는 방법도 있는듯. 해보진 않았다.)  --> 19.07.17 내용 추가 / Parcelable을 사용해야만 하는 경우가 있다ㅜㅜ 관련내용은 가장 밑에 넣겠다.

(3) serialVersionID 를 설정해주는 것이 좋다고 한다. 필수는 아니니 참고만하고 넘어가도록 하겠다.


이렇게 만든 클래스의 객체를 보낸다.

보내는 과정은 위에서 기본자료형들을 보내던 코드와 동일하다.

그냥 intent.putExtra("아무 이름", class instance ); 해주면 된다.


받는 경우에는, 보내려는 객체의 클래스의 이름이 예를 들어 Attribute라고 한다면,

Attribute attr  = (Attribute) intent.getSerializableExtra("아무 이름");

과 같이 캐스팅하여 오브젝트를 넘겨 받으면 된다.




+19.04.16 추가내용


새로운 액티비티를 실행시키는 것 뿐만아니라, 액티비티가 실행된 후

종료되기 전에 자신을 호출한 부모 액티비티에게 응답을 하는 경우에도 intent를 사용한다.


이러한 경우, 단순히 startActivity(intent) 가 아니라, startActivityForResult(intent, 호출코드) 와 같이 사용하고,

이는 함수명 그대로, 호출한 액티비티가 종료되면 결과값을 받을 목적으로 호출하게 되는 것이다.

여러 다른 액티비티에서 결과값을 전달 할 수 있기 때문에, 호출코드 번호로 구분하게 된다.


호출된 액티비티에 '완료' 또는 '돌아가기' 등의 버튼이 있고, 이 버튼을 누르면 해당 액티비티가 종료되며 이전의 액티비티가 다시 보여진다고 하자.


해당 버튼의 onClick메소드에 이런식의 코드를 작성하게 될 것이다.


예시)


@Override

public void onClick(View v)

{

Intent intent = new Intent();

intent.putExtra("이름","값"); // 부모액티비티에게 전달할 값들을 intent에 덧붙인다


setResult(Activity.RESULT_OK, intent); // 정상적으로 종료되었다는 것을 알리는 상수값 활용. Activity.RESULT.CANCELED 등 다른값도 있다.


finish(); //종료하고 기존 액티비티로 전환

}



이렇게 자식 액티비티에서 전달한 내용을 다시 부모 액티비티에서 받아야 할텐데, 이를 위한 기본적인 코드는 다음과 같다.


우선 onActivityResult()라는 함수를 override method한다.


@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data){...} 

//파라미터1: 애초에 자식액티비티를 호출할 때 사용했던 호출코드번호 [ startActivityForResult(intent, 호출코드) ] <- 이거!

//파라미터2: 자식액티비티에서 전달했던 응답 상수값(Activity.RESULT_OK)

//파라미터3: 전달받은 intent 객체(데이터가 담겨있으면 꺼내쓴다.)


예시)

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data)

{

super.onActivityResult(requestCode, resultCode, data);


if (requestCode == 101){
      String name = data.getStringExtra("이름");

//do something with data...

// ...

}



// 조금더 내용 추가

자식 액티비티를 실행시킬 때, 새로운 액티비티 전체가 새롭게 화면을 띄워지는 것을 원치않고, 마치 대화상자 하나 띄우듯이 startActivity하고싶다면, AndroidManifest.xml 에서 해당 액티비티 속성을 바꾸면된다.

<activity android:name=".exampleActivity" android:label="상단에 띄워질 액티비티 이름" android:theme="@style/Theme.AppCompat.Light.Dialog"> 등...

(자신의 화면 스타일에 맞게 커스터마이징이 조금 필요하다)



// 내용 추가 (액티비티를 위한 플래그)

// 내용 출처) https://www.edwith.org/boostcourse-android/lecture/17065/


메인 액티비티에서 메뉴 액티비티를 실행하는 경우 액티비티에서 시스템을 통해 다른 액티비티를 실행하는 형태가 됩니다.

그런데 화면이 없는 서비스라는 것에서 액티비티를 실행하는 경우도 있습니다.

이 때는 액티비티를 새로 만들어 실행할 수도 있지만 이미 사용자가 보고 있는 액티비티 위에 액티비티를 실행하게 되는 경우가 생길 수도 있습니다.

결국 동일한 액티비티인데 두 개가 만들어지게 되고 가장 위에 있는 것만 보이는 상황이 만들어지는 거죠.

이 때 맨 위에 있는 액티비티를 finish 메소드로 없애더라도 사용자에게는 동일한 모양의 화면이 다시 보이게 되는 문제가 발생합니다.

이런 경우에 플래그를 사용하면 동일한 액티비티인 경우 이미 화면에 보이는 액티비티를 그대로 사용할 수 있도록 해 줍니다.

대표적으로 사용되는 플래그 중의 하나가 FLAG_ACTIVITY_SINGLE_TOP 인데 다음과 같이 플래그를 사용할 때와 사용하지 않을 때를 비교해볼 수 있습니다.

새로 실행하려는 액티비티와 화면에 보이는 액티비티가 동일한 액티비티인 경우 메모리에 새로 만들지 않고 화면에 보이는 액티비티를 그대로 보여줍니다.

그런데 이미 만들어져서 화면에 보이고 있는 액티비티를 그대로 사용므로 onCreate 메소드가 호출되지 않습니다.

onCreate 메소드는 액티비티가 메모리에 처음 만들어질 때 자동으로 호출되는데 화면이 이미 만들어져 있으니 호출되지 않는 거죠.

이때는 onNewIntent 라는 메소드가 호출됩니다.

이 메소드가 별도로 호출되는 이유는 인텐트를 전달받기 위해서입니다.

onCreate가 호출되는 경우에는 그 안에서 getIntent 메소드를 이용해 전달받은 인텐트 객체를 확인할 수 있지만 onCreate 메소드가 호출되지 않는 경우에는 onNewIntent 메소드 안에서 인텐트 객체를 확인할 수 있도록 합니다.




19.07.17 내용 추가 - 

Parcelable 클래스를 상속받으면, ArrayList<직접_만든_클래스> 까지 intent에 넣어서 다른 액티비티에 보낼 수 있다. (클래스의 멤버변수 중 BitmapDrawable객체가 있는 경우까지 



* ArrayList에 담길 직접만든클래스 정의 - 예시

public class Review implements Parcelable {
BitmapDrawable userIcon;
String userName, time, comment;
int recommendCount;
float rating;

public Review() {
}

public Review(BitmapDrawable userIcon, String userName, String time, String comment, int recommendCount, float rating) {
this.userIcon = userIcon;
this.userName = userName;
this.time = time;
this.comment = comment;
this.recommendCount = recommendCount;
this.rating = rating;
}

/** Parcelabe implement 하면서 추가된 부분 **/
public Review(Parcel in) {
// *** BitmapDrawable도 아래와같은 방식으로 넘길 수 있다. ***
Bitmap bitmap = in.readParcelable(BitmapDrawable.class.getClassLoader());

this.userIcon = new BitmapDrawable(bitmap);
this.userName = in.readString();
this.time = in.readString();
this.comment = in.readString();
this.recommendCount = in.readInt();
this.rating = in.readFloat();
}

/** Parcelabe implement 하면서 추가된 부분 **/
@Override
public int describeContents() {
return 0;
}

/** Parcelabe implement 하면서 추가된 부분 **/
@Override // 주의! 생성자에서의 멤버변수 초기화 순서와 동일해야한다.
public void writeToParcel(Parcel parcel, int i) {
Bitmap bitmap = userIcon .getBitmap();

parcel.writeParcelable(bitmap, i );
parcel.writeString(this.userName);
parcel.writeString(this.time);
parcel.writeString(this.comment);
parcel.writeInt(this.recommendCount);
parcel.writeFloat(this.rating);
}

/** Parcelabe implement 하면서 추가된 부분 **/
// Parcelable implement 하는 경우 CREATOR를 만들어줘야한다.
// 아래는 리턴할때 클래스 이름만 바꾸고 복붙하면 됨.
@SuppressWarnings("rawtypes")
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
@Override
public Object createFromParcel(Parcel parcel) {
return new Review(parcel);
}

@Override
public Review[] newArray(int size) {
return new Review[size];
}
};

// ... 이외에도 각 멤버변수에 대하여 getter setter를 작성해주어야 한다. 생략함
}



* A 액티비티에서 B 액티비티로 ArrayList<T> 보내기 - PutParcelableArrayListExtra()

Intent intent = new Intent(getApplicationContext(), ReviewActivity.class);
// 아래에서 reviews는 넘기고픈 ArrayList<Review> 객체이다.
intent.putParcelableArrayListExtra("key", reviews);
startActivity(intent);



* B 액티비티에서 ArrayList<T> 받기 - PutParcelableArrayListExtra()

ArrayList<Review> reviews = new ArrayList<Review>();
reviews = intent.getParcelableArrayListExtra("key");



+ Recent posts