Android에서 실행 가능한 액티비티는 말그대로 실행할 수 있는 액티비티를 말합니다. 어떤 액티비티를 실행했을 때 실행이 안될 수 있는데요. 미리 체크를 한다면 다른 예외처리를 할 수 있기 때문에, 액티비티 실행 전에 꼭 체크를 해야 합니다.
실행가능한 액티비티
Context.startActivity(..)는 실행 가능한 Activity만 실행시킵니다. Activity가 존재하지 않거나, disabled되었다면 실행하지 못합니다.
실행 가능한 액티비티가 2개 이상 있어도 문제입니다. startActivity
API가 어떤 액티비티를 실행해야 할지 모르기 때문입니다. 이럴 땐 ResolverActivity
를 띄웁니다. ResolverActivity
는 어떤 앱을 실행할지 사용자에게 물어보는 액티비티입니다.
실행가능한 액티비티인지 미리 체크해야하는 이유는, 원하는 액티비티가 실행 불가능이거나 ResolverActivity가 뜰 수 있어서, 개발자가 의도한 결과와 다를 수 있기 때문입니다. 예를들어 A라는 액티비티가 떠야 다음 작업이 진행되는데, A가 시스템에 의해 비활성화되는 등의 이유로 실행되지 않을 수 있기 때문입니다. 또한 다른 앱의 액티비티를 실행하는 경우, 앱의 버전이 달라져 예전에 알고 있던 액티비티가 삭제되었을 수 있습니다.
실행가능한 액티비티 찾는 방법
실행가능한 액티비티를 찾는 방법은 아래 두개의 API를 적절히 이용하는 것입니다.
- PackageManager.queryIntentActivities: 설정된 조건에 맞는 액티비티를 찾아줍니다.
- PackageManager.resolveActivity: 설정에 맞는 액티비티를 찾고, 이 중에 어떤 액티비티를 실행할지 결정해줍니다.
queryIntentActivities
는 인자로 전달되는 인텐트에 맞는 액티비티를 찾아줍니다.
리턴되는 액티비티가 1개라면, 저 인텐트를 인자로 startActivity
를 호출했을 때 원하는 액티비티가 실행됩니다.
반면에, 리턴되는 인텐트가 2개라면, startActivity
를 호출했을 때 ResolverActivity가 실행되거나, 2개 중에 1개의 액티비티가 실행될 수 있습니다.
resolveActivity
를 사용한다면 어떤 앱이 실행되는지 알 수 있습니다. 이 API는 내부적으로 queryIntentActivities
를 사용하여 리스트를 가져오며, 액티비티가 두개 이상인 경우 사용자가 Preferred activity(ResolverActivity에서 always로 실행하기로 설정하면 등록됩니다)로 등록된 것을 리턴해줍니다. 그렇지 않다면 ResolverActivity를 리턴해줍니다.
startActivity
도 내부적으로 resolveActivity
를 사용합니다.
그렇기 때문에 사전에 이 API로 어떤 앱이 실행될지 예측할 수 있습니다.
queryIntentActivities
는 사용할 필요가 없을까요? 다른 앱의 액티비티를 실행한다고 했을 때, 그 액티비티가 설치되었는지, 설치되었다면 비활성화되었는지 활성화되었는지 알 수 있습니다.
예제를 구현해보면서 좀 더 자세히 알아보겠습니다.
예제 코드(Explicit intent)
PackageManager.queryIntentActivities()
로 특정 Intent에 대한 결과로 ResolveInfo가 리턴된다면 현재 실행가능한 Component를 의미합니다.
startActivity()를 호출했을 때, queryIntentActivities()
의 결과로 리턴된 Activity가 1개 뿐이라면 그 Activity가 실행됩니다.
하지만 2개 이상이라면 Preferred activity 설정 유무에 따라서 ResolverActivity가 실행될 수 있습니다.
아래 코드를 사용하면 실행가능한지 사전에 확인해볼 수 있습니다. queryIntentActivities로 관련 액티비티가 있는지 확인하고, resolveActivity로 정말 실행이 되는지 다시 확인합니다.
String targetPackageName = "com.google.android.apps.photos";
String targetClassName = "com.google.android.apps.photos.home.HomeActivity";
Intent intent = new Intent();
intent.setComponent(new ComponentName(targetPackageName, targetClassName));
List<ResolveInfo> ris = getPackageManager().queryIntentActivities(intent, 0);
if (ris != null && ris.size() > 0) {
ResolveInfo ri = getPackageManager().resolveActivity(intent, 0);
if (ri != null) {
if (ri.activityInfo.name.equals("ResolverActivity")) {
Log.d(TAG, "If this intent is started, " +
"then ResolveActivity(ChooserActivity) will be started");
} else {
Log.d(TAG, "If this intent is started, " +
"then this component will be started");
startActivity(intent);
}
}
} else {
Log.d(TAG, "No activties found");
}
위 코드는 Explicit intent이기 때문에 1개의 Activity만 리턴됩니다. 그래서 ResolverActivity가 실행될 가능성이 없습니다.
If this intent is started, then this component will be started
예제 코드(Implicit intent)
아래 코드는 Implicit intent를 사용한 예제입니다.
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> ris = getPackageManager().queryIntentActivities(intent, 0);
if (ris != null && ris.size() > 0) {
ResolveInfo ri = getPackageManager().resolveActivity(intent, 0);
if (ri != null) {
if (ri.activityInfo.name.equals("com.android.internal.app.ResolverActivity")) {
Log.d(TAG, "If this intent is started, " +
"then ResolveActivity(ChooserActivity) will be started");
} else {
Log.d(TAG, "If this intent is started, " +
"then this component will be started");
startActivity(intent);
}
}
} else {
Log.d(TAG, "No activties found");
}
이 인텐트에 해당하는 액티비티가 매우 많기 때문에 resolveActivity()
는 ResolverActivity를 리턴하였습니다. 만약 이 인텐트에 대해서 어떤 액티비티가 preferred로 설정되었다면 ResolverActivity가 리턴되지 않고 preferred activity로 설정된 액티비티가 리턴됩니다.
(Preferred activity는 ResolverActivity화면에서 어떤 앱을 선택하고 always
버튼을 클릭하면 Preferred activity로 설정됩니다.)
결과 로그는 아래와 같습니다.
If this intent is started, then ResolveActivity(ChooserActivity) will be started
Related Posts
- Android 14 - 사진/동영상 파일, 일부 접근 권한 소개
- Android - adb push, pull로 파일 복사, 다운로드
- Android 14 - 암시적 인텐트 변경사항 및 문제 해결
- Jetpack Compose - Row와 Column
- Android 13, AOSP 오픈소스 다운로드 및 빌드
- Android 13 - 세분화된 미디어 파일 권한
- Android 13에서 Notification 권한 요청, 알림 띄우기
- Android 13에서 'Access blocked: ComponentInfo' 에러 해결
- 에러 해결: android gradle plugin requires java 11 to run. you are currently using java 1.8.
- 안드로이드 - 코루틴과 Retrofit으로 비동기 통신 예제
- 안드로이드 - 코루틴으로 URL 이미지 불러오기
- Android - 진동, Vibrator, VibrationEffect 예제
- Some problems were found with the configuration of task 에러 수정
- Query method parameters should either be a type that can be converted into a database column or a List
- 우분투에서 Android 12 오픈소스 다운로드 및 빌드
- Android - ViewModel을 생성하는 방법
- Android - Transformations.map(), switchMap() 차이점
- Android - Transformations.distinctUntilChanged() 소개
- Android - TabLayout 구현 방법 (+ ViewPager2)
- Android - 휴대폰 전화번호 가져오는 방법
- Android 12 - Splash Screens 알아보기
- Android 12 - Incremental Install (Play as you Download) 소개
- Android - adb 명령어로 bugreport 로그 파일 추출
- Android - adb 명령어로 App 데이터 삭제
- Android - adb 명령어로 앱 비활성화, 활성화
- Android - adb 명령어로 특정 패키지의 PID 찾기
- Android - adb 명령어로 퍼미션 Grant 또는 Revoke
- Android - adb 명령어로 apk 설치, 삭제
- Android - adb 명령어로 특정 패키지의 프로세스 종료
- Android - adb 명령어로 screen capture 저장
- Android - adb 명령어로 System 앱 삭제, 설치
- Android - adb 명령어로 settings value 확인, 변경
- Android 12 - IntentFilter의 exported 명시적 선언
- Android - adb 명령어로 공장초기화(Factory reset)
- Android - adb logcat 명령어로 로그 출력