Intent에 대한 이야기
게시일:
OverView
안드로이드 개발을 하다보면 Activity 단위로 레이아웃을 변경하게 되고 그 순간마다 startActivity를 호출한다. startActivity를 호출하는 방법을 보면 Activity를 직접적으로 명시하는 것이 아니라 Activity에 대한 정보를 담고 있는 Intent라는 객체를 파라미터로 호출을 진행하게 된다. 또 이 Intent라는 놈은 명시적 호출 뿐만 아니라 행위를 기반으로한 암시적 호출을 할 때도 사용된다. 그 뿐만 아니라 Activity의 flag를 세팅할 수 있으며 번들에 값들을 실어서 전달하는 것도 가능하다.
그렇다면 Intent라는 놈은 대체 무엇일까?
Intent란?
인텐트는 Message Object로, 안드로이드의 컴포넌트들 사이의 작업을 요청하는 경우에 사용된다. 즉, Activity, Service, Broadcast와 관련된 통신을 수행할 때 주로 사용됨을 의미하기도 한다.
근데 왜 꼭 intent가 필요할까? 안드로이드는 여러가지 app들이 하나의 앱처럼 동작하는 것이 특징이다. 인스타그램이라는 어플을 사용하다보면 앨범에서 사진을 선택하거나 카메라로 사진을 찍는 행위들이 내부에 포함되어 있다. 사실은 인스타그램 앱 자체에 구현되어 있지는 않지만 마치 하나의 App을 사용하는 것처럼 사용할 수 있다는 것이다. 이런 행위를 보장하기 위해서 Intent가 사용된다.
인텐트는 두 가지 유형으로 나뉘는떼 명시적 인텐트와 암시적 인텐트이다.
명시적 인텐트
명시적 인텐트는 특정 패키지 이름 또는 클래스 이름을 명시하여 호출하는 경우로 startActivity를 호출할 때 ExampleActivity를 명시적으로 언급하는 경우가 이에 해당된다. 명시적으로 호출한다는 의미는 다르게 표현하자면 호출하고자 하는 대상에 대한 정보를 확실하게 알고 있다는 말이기도 하다.
Intent intent = new Intent(this, ExampleActivity.class);
intent.putExtra("Data", "12345");
startActivity(intent);
위의 자바코드를 보면 명시적으로 ExampleActivity를 호출대상으로 정의하고 넘겨줄 Data라는 값으로 12345를 넣은 뒤 호출하는 행위를 수행한다. 위와 같이 명시적으로 대상이 선언되어 있는 경우를 명시적 인텐트라고 정의한다.
암시적 인텐트

암시적 인텐트는 직접 구성 요소에 대한 이름을 사용하기 보단 수행할 작업을 선언하여 다른 구성요소가 이를 처리할 수 있도록 하는 경우를 말한다. 예로 사진찍기라는 행위에 대한 요청을 하여 이를 수행할 수 있는 구성 요소 중 하나를 호출하여 처리를 위임하거나 지도를 표시하는 행위를 요청하여 지도앱으로부터 처리를 수행하도록 하는 경우가 이에 해당된다.
위의 그림을 보면 ActivityA가 startActivity를 통해 행위에 대한 정보를 명시한 인텐트를 호출한다. Android System은 이 인텐트 정보를 확인하여 행위를 처리할 수 있는 컴포넌트를 찾은 뒤 해당되는 컴포넌트가 존재하면 이를 실행해준다. 즉, Intent에 ActivityB가 명시적으로 선언되어 있지 않아도 행위를 기반으로 처리할 대상을 찾을 수 있음을 의미한다. 이 때 인텐트 필터를 기반으로 찾게 되는데 Manifest 파일에 선언된 필터와 비교를 하는 과정을 거치게 된다. 만약 해당 행위를 처리할 수 있는 컴포넌트가 하나 이상 존재하게 되면 사용자에게 어느 앱을 사용할 것인지를 요청하는 대화상자를 띄워준다. 이런 특징을 토대로 인텐트 필터에 대한 정의를 해보자면 Manifest 파일에 정의되어 있는 행동인데, 이는 처리를 하고자 하는 유형을 나타내기 위해 사용된다고 할 수 있다.
Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
startActivity(mapIntent);
ACTION_VIEW라는 행위를 수행할 수 있는 앱을 기기로부터 호출하는 예제인데 지도와 관련된 작업을 처리할 수 있는 앱이 존재한다면 해당 Intent 호출을 받아서 처리를 해준다. 보통 암시적 인텐트를 사용하는 경우는 해당 앱에서 수행할 수 없는 일들을 다른 앱에 위임할 때가 대부분이다.
<activity android:name="ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
암시적 인텐트를 받기 위해서는 Manifest에 위와 같이 action에 대한 정의가 되어 있어야 되며 data를 받기 위해서는 그 Uri 타입(scheme, host 등)에 대한 정의가 이루어져야 된다.
PendingIntent
Developer Docs에 의거하면 기본 목적은 외부 애플리케이션에 권한을 허가하여 안에 들어 있는 Intent를 마치 본인 앱의 자체 프로세스에서 실행하는 것처럼 사용하게 하는 것이라고 한다.
위의 블로그의 설명부분을 첨언하자면 notification과 같은 어플리케이션이 알림을 뿌려주고 사용자가 클릭을 하게 되면 notification이 다운로드가 완료된 음악 등에 대한 재생을 위한 Activity를 호출하게 되는데 바로 호출하는 것이 아니라 특수한 상황에 의거해 호출이 될 때 실행되어야 하는 경우에 사용된다고 한다.
public class PendingActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "PendingActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate");
ActivityPendingBinding binding = ActivityPendingBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
binding.button.setOnClickListener(this);
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onClick(View v) {
if(v.getId() == R.id.button){
generateNotification();
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void generateNotification() {
final int notificationId = 100;
Intent notificationIntent = new Intent(this, PendingNotificationActivity.class);
notificationIntent.putExtra("notificationId", notificationId);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel("channelId", "pendingActivity", NotificationManager.IMPORTANCE_DEFAULT);
assert notificationManager != null;
notificationManager.createNotificationChannel(channel);
Notification notification = new Notification.Builder(this, "channelId")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setTicker("pendingActivity")
.setWhen(System.currentTimeMillis())
.setContentTitle("pendingActivity")
.setContentText("pendingActivity")
.setContentIntent(pendingIntent)
.build();
notificationManager.notify(notificationId, notification);
}
}
위의 예제는 Notification을 pendingIntent로 처리한 예제이다. PendingNotificationActivity는 pendingIntent로 선언되는데, notification이 발생한 뒤 사용자가 해당 notification을 클릭하게 되면 PendingNotificationActivity을 호출하게 된다. 바로 이런 기능이 pending인데, 본인이 직접 수행하는 것이 아닌 어느 특정 상황에서 다른 컴포넌트에 의해 호출이 되도록 하기 때문이다.
public class PendingNotificationActivity extends AppCompatActivity {
private static final String TAG = "PendingNotificationActivity";
@SuppressLint("LongLogTag")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate");
ActivityPendingBinding binding = ActivityPendingBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
Intent intent = getIntent();
final int notificationId = intent.getIntExtra("notificationId", 0);
binding.button.setText(String.valueOf(notificationId));
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
}
}
notification을 클릭하면 notificationId라는 이름으로 putExtra를 통해 전달한 값인 100이 전달되어 button의 텍스트가 100으로 변경되는 것을 확인할 수 있다.