HOME > android > tips

안드로이드 - 실행가능한 Activity인지 확인하기

JSFollow22 Oct 2018

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