패키지 공개 상태를 제한하면서 일반적인 사용 사례 처리

이 문서에서는 앱이 다른 앱과 상호작용하는 사용 사례의 몇 가지 일반적인 예를 보여줍니다. 각 섹션에서는 Android 11(API 수준 30) 이상을 타겟팅하는 앱에서 고려해야 하는 설치된 다른 앱에 관한 패키지 공개 상태 선언 방법의 가이드를 제공합니다.

앱에서 Android 11 이상을 타겟팅하고 인텐트를 사용하여 다른 앱에서 활동을 시작한다면 가장 간단한 방법은 인텐트를 호출하고 사용 가능한 앱이 없는 경우 ActivityNotFoundException 예외를 처리하는 것입니다.

앱의 일부가 startActivity() 호출이 성공할 수 있는지 아는 것(예: UI 표시)에 의존하는 경우 앱 매니페스트의 <queries> 요소에 요소를 추가합니다. 일반적으로 이 새 요소는 <intent> 요소입니다.

URL 열기

이 섹션에서는 Android 11 이상을 타겟팅하는 앱에서 URL을 여는 방법을 설명합니다. 앱이 URL을 여는 방법에 따라 다음 하위 섹션의 예 중 하나를 따르세요.

브라우저 또는 다른 앱에서 URL 열기

URL을 열려면 웹 URL 로드 방법 섹션에 설명되어 있는 대로 ACTION_VIEW 인텐트 작업이 포함된 인텐트를 사용합니다. 이 인텐트를 사용하여 startActivity()를 호출하면 다음 중 하나가 발생합니다.

  • URL이 웹브라우저 앱에서 열립니다.
  • URL이 URL을 딥 링크로 지원하는 앱에서 열립니다.
  • 명확성 대화상자가 표시되고 이 대화상자로 사용자는 URL을 여는 앱을 선택할 수 있습니다.
  • 기기에 URL을 열 수 있는 앱이 설치되어 있지 않으므로 ActivityNotFoundException이 발생합니다. 특이한 경우입니다.

    ActivityNotFoundException이 발생하면 앱에서 파악하여 처리하는 것이 좋습니다.

startActivity() 메서드는 시스템의 패키지 공개 상태 동작에 영향을 받지 않으므로 <queries> 요소를 앱의 매니페스트에 추가하거나 기존 <queries> 요소를 변경할 필요가 없습니다.

브라우저가 사용 가능한지 확인

때에 따라서는 앱에서 URL을 열려고 하기 전에 기기에 사용 가능한 브라우저가 최소 하나 이상 있는지 또는 특정 브라우저가 기본 브라우저인지 확인하는 것이 좋습니다. 이때 매니페스트에 <queries> 요소의 일부로 다음 <intent> 요소를 포함합니다.

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="https" />
</intent>

queryIntentActivities()를 호출하고 웹 인텐트를 인수로 전달하면 경우에 따라 반환된 목록에 사용 가능한 브라우저 앱이 포함됩니다. 사용자가 브라우저가 아닌 앱에서 URL이 열리도록 기본 설정했다면 목록에는 브라우저 앱이 포함되지 않습니다.

맞춤 탭에서 URL 열기

맞춤 탭을 사용하면 앱에서 브라우저의 디자인과 분위기를 맞춤설정할 수 있습니다. 앱 매니페스트에서 <queries> 요소를 추가하거나 변경할 필요 없이 맞춤 탭에서 URL을 열 수 있습니다.

그러나 기기에 맞춤 탭을 지원하는 브라우저가 있는지 확인하거나 CustomTabsClient.getPackageName()을 사용하여 맞춤 탭으로 실행할 특정 브라우저를 선택하는 것이 좋습니다. 이 경우 매니페스트에 <queries> 요소의 일부로 다음 <intent> 요소를 포함합니다.

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>

브라우저가 아닌 앱에서 URL 처리

앱에서 맞춤 탭을 사용하여 URL을 열 수 있다고 해도 가능하면 브라우저가 아닌 앱에서 특정 URL을 열도록 허용하는 것이 좋습니다. 앱에 이 기능을 제공하려면 FLAG_ACTIVITY_REQUIRE_NON_BROWSER 인텐트 플래그를 설정하는 인텐트를 사용하여 startActivity() 호출을 시도합니다. 시스템에서 ActivityNotFoundException이 발생하면 앱에서 맞춤 탭으로 URL을 열면 됩니다.

인텐트에 이 플래그가 포함된 경우 startActivity()를 호출하면 다음 조건 중 하나가 발생할 때 ActivityNotFoundException이 발생합니다.

  • 호출로 인해 브라우저 앱이 직접 실행됩니다.
  • 호출로 인해 유일한 옵션이 브라우저 앱인 명확성 대화상자가 사용자에게 표시됩니다.

다음 코드 스니펫은 FLAG_ACTIVITY_REQUIRE_NON_BROWSER 인텐트 플래그를 사용하도록 로직을 업데이트하는 방법을 보여줍니다.

Kotlin

try {
    val intent = Intent(ACTION_VIEW, Uri.parse(url)).apply {
        // The URL should either launch directly in a non-browser app (if it's
        // the default), or in the disambiguation dialog.
        addCategory(CATEGORY_BROWSABLE)
        flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER
    }
    startActivity(intent)
} catch (e: ActivityNotFoundException) {
    // Only browser apps are available, or a browser is the default.
    // So you can open the URL directly in your app, for example in a
    // Custom Tab.
    openInCustomTabs(url)
}

자바

try {
    Intent intent = new Intent(ACTION_VIEW, Uri.parse(url));
    // The URL should either launch directly in a non-browser app (if it's the
    // default), or in the disambiguation dialog.
    intent.addCategory(CATEGORY_BROWSABLE);
    intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
    startActivity(intent);
} catch (ActivityNotFoundException e) {
    // Only browser apps are available, or a browser is the default.
    // So you can open the URL directly in your app, for example in a
    // Custom Tab.
    openInCustomTabs(url);
}

명확성 대화상자 피하기

사용자가 URL을 열 때 표시될 수 있는 명확성 대화상자를 표시하지 않고 대신 이러한 상황에서 URL을 직접 처리하려면 FLAG_ACTIVITY_REQUIRE_DEFAULT 인텐트 플래그를 설정하는 인텐트를 사용하면 됩니다.

인텐트에 이 플래그가 포함된 경우 startActivity()를 호출하면 호출로 인해 사용자에게 명확성 대화상자가 표시될 때 ActivityNotFoundException이 발생합니다.

인텐트에 이 플래그와 FLAG_ACTIVITY_REQUIRE_NON_BROWSER 인텐트 플래그가 모두 포함된 경우 startActivity()를 호출하면 다음 조건 중 하나가 발생할 때 ActivityNotFoundException이 발생합니다.

  • 호출로 인해 브라우저 앱이 직접 실행됩니다.
  • 호출로 인해 명확성 대화상자가 사용자에게 표시됩니다.

다음 코드 스니펫은 FLAG_ACTIVITY_REQUIRE_NON_BROWSER 플래그와 FLAG_ACTIVITY_REQUIRE_DEFAULT 플래그를 함께 사용하는 방법을 보여줍니다.

Kotlin

val url = URL_TO_LOAD
try {
    // In order for this intent to be invoked, the system must directly launch a
    // non-browser app.
    val intent = Intent(ACTION_VIEW, Uri.parse(url).apply {
        addCategory(CATEGORY_BROWSABLE)
        flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER or
                FLAG_ACTIVITY_REQUIRE_DEFAULT
    }
    startActivity(intent)
} catch (e: ActivityNotFoundException) {
    // This code executes in one of the following cases:
    // 1. Only browser apps can handle the intent.
    // 2. The user has set a browser app as the default app.
    // 3. The user hasn't set any app as the default for handling this URL.
    openInCustomTabs(url)
}

자바

String url = URL_TO_LOAD;
try {
    // In order for this intent to be invoked, the system must directly launch a
    // non-browser app.
    Intent intent = new Intent(ACTION_VIEW, Uri.parse(url));
    intent.addCategory(CATEGORY_BROWSABLE);
    intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_REQUIRE_NON_BROWSER |
            FLAG_ACTIVITY_REQUIRE_DEFAULT);
    startActivity(intent);
} catch (ActivityNotFoundException e) {
    // This code executes in one of the following cases:
    // 1. Only browser apps can handle the intent.
    // 2. The user has set a browser app as the default app.
    // 3. The user hasn't set any app as the default for handling this URL.
    openInCustomTabs(url);
}

파일 열기

앱에서 파일이나 첨부파일을 처리한다면(예: 기기에서 특정 파일을 열 수 있는지 확인) 일반적으로 파일을 처리할 수 있는 활동을 시작하는 것이 가장 쉽습니다. 이렇게 하려면 ACTION_VIEW 인텐트 작업과 특정 파일을 나타내는 URI가 포함된 인텐트를 사용합니다. 기기에서 사용할 수 있는 앱이 없다면 앱에서 ActivityNotFoundException을 포착할 수 있습니다. 예외 처리 로직에서 오류를 표시하거나 파일을 직접 처리해 볼 수 있습니다.

앱에서 다른 앱이 특정 파일을 열 수 있는지 미리 알아야 한다면 매니페스트에 <queries> 요소의 일부로 다음 코드 스니펫의 <intent> 요소를 포함합니다. 컴파일 시 파일 형식을 이미 알고 있다면 파일 형식을 포함합니다.

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
  <data android:mimeType="application/pdf" />
</intent>

그런 다음 인텐트와 함께 resolveActivity()를 호출하여 앱을 사용할 수 있는지 확인하면 됩니다.

URI 액세스 권한 부여

참고: 이 권장사항은 아래 내용과 관계없이 모든 앱에 적용됩니다.

  • 앱의 타겟 SDK 버전
  • 앱이 콘텐츠 제공자를 내보내는지 여부

Android 11 이상을 타겟팅하는 앱에서 콘텐츠 URI에 액세스할 수 있도록 앱의 인텐트는 FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION 인텐트 플래그 중 최소 하나를 설정하여 URI 액세스 권한을 선언해야 합니다.

Android 11 이상에서 URI 액세스 권한은 인텐트를 수신하는 앱에 다음과 같은 기능을 제공합니다.

  • 지정된 URI 권한에 따라 콘텐츠 URI가 표시되는 데이터를 읽거나 씁니다.
  • URI 권한과 일치하는 콘텐츠 제공자가 포함된 앱에 관한 공개 상태를 가져옵니다. 콘텐츠 제공자가 포함된 앱은 인텐트를 전송하는 앱과 다를 수 있습니다.

다음 코드 스니펫은 Android 11 이상을 타겟팅하는 다른 앱이 콘텐츠 URI의 데이터를 볼 수 있도록 URI 권한 인텐트 플래그를 추가하는 방법을 보여 줍니다.

Kotlin

val shareIntent = Intent(Intent.ACTION_VIEW).apply {
    flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
    data = CONTENT_URI_TO_SHARE_WITH_OTHER_APP
}

자바

Intent shareIntent = new Intent(Intent.ACTION_VIEW);
shareIntent.setFlags(FLAG_GRANT_READ_URI_PERMISSION);
shareIntent.setData(CONTENT_URI_TO_SHARE_WITH_OTHER_APP);

서비스에 연결

앱이 자동으로 표시되지 않는 서비스와 상호작용해야 하는 경우 <queries> 요소 내에서 적절한 인텐트 작업을 선언할 수 있습니다. 다음 섹션에서는 일반적으로 액세스되는 서비스를 사용한 예를 보여 줍니다.

텍스트 음성 변환 엔진에 연결

앱이 텍스트 음성 변환(TTS) 엔진과 상호작용하는 경우 매니페스트에 <queries> 요소의 일부로 다음 <intent> 요소를 포함하세요.

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.TTS_SERVICE" />
</intent>

음성 인식 서비스에 연결

앱이 음성 인식 서비스와 상호작용하는 경우 매니페스트의 <queries> 요소의 일부로 다음 <intent> 요소를 포함하세요.

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.speech.RecognitionService" />
</intent>

미디어 브라우저 서비스에 연결

클라이언트 미디어 브라우저 앱에서 매니페스트의 <queries> 요소 중 일부로 다음 <intent> 요소를 포함하세요.

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.media.browse.MediaBrowserService" />
</intent>

맞춤 기능 제공

맞춤설정 가능한 작업을 진행해야 하거나 다른 앱과의 상호작용을 기반으로 맞춤설정 가능 정보를 표시해야 하는 경우 인텐트 필터 서명을 사용하는 그러한 맞춤 동작을 매니페스트에서 <queries> 요소의 일부로 표현할 수 있습니다. 다음 섹션에서는 몇 가지 일반적인 시나리오에 관한 자세한 지침을 제공합니다.

SMS 앱 쿼리

앱이 기기에 설치된 SMS 앱 세트에 관해 알아야 하면(예를 들어 어떤 앱이 기기의 기본 SMS 핸들러인지 확인하려고) 다음 <intent> 요소를 매니페스트에 있는 <queries> 요소의 일부로 포함합니다.

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.SENDTO"/>
  <data android:scheme="smsto" android:host="*" />
</intent>

맞춤 공유 시트 만들기

가능하면 시스템에서 제공하는 Sharesheet를 사용하세요. 또는 매니페스트에 <queries> 요소의 일부로 다음 <intent> 요소를 포함합니다.

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.SEND" />
  <!-- Replace with the MIME type that your app works with, if needed. -->
  <data android:mimeType="image/jpeg" />
</intent>

queryIntentActivities() 호출과 같이 앱 로직에서 공유 시트를 빌드하는 프로세스는 이전 버전의 Android와 비교하여 변경되지 않은 상태로 유지됩니다.

맞춤 텍스트 선택 작업 표시

사용자가 앱에서 텍스트를 선택하면 선택된 텍스트에 실행할 수 있는 일련의 작업이 텍스트 선택 툴바에 표시됩니다. 이 툴바에 다른 앱의 맞춤 작업이 표시되어야 한다면 매니페스트의 <queries> 요소의 일부로 다음 <intent> 요소를 포함합니다.

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.PROCESS_TEXT" />
  <data android:mimeType="text/plain" />
</intent>

연락처를 위한 맞춤 데이터 행 표시

앱은 연락처 제공자에 맞춤 데이터 행을 추가할 수 있습니다. 연락처 앱에서 이 맞춤 데이터를 표시하려면 다음 작업을 할 수 있어야 합니다.

  1. 다른 앱에서 contacts.xml 파일 읽기
  2. 맞춤 MIME 유형에 상응하는 아이콘 로드

개발자의 앱이 연락처 앱이면 매니페스트에 <queries> 요소의 일부로 다음 <intent> 요소를 포함합니다.

<!-- Place inside the <queries> element. -->
<!-- Allows the app to read the "contacts.xml" file from the other apps. -->
<intent>
  <action android:name="android.accounts.AccountAuthenticator" />
</intent>
<!-- Allows the app to load an icon corresponding to the custom MIME type. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <data android:scheme="content" android:host="com.android.contacts"
        android:mimeType="vnd.android.cursor.item/*" />
</intent>