6월 3일의 ⁠#Android11: 베타 버전 출시 행사에 참여하세요.

Android 11의 패키지 공개 상태

Android 11은 앱이 동일한 기기에 설치된 다른 앱을 쿼리하고 상호작용하는 방법을 변경합니다. 앱에서 Android 11을 타겟팅하는 경우 시스템에서 앱에 어떤 다른 앱이 표시되는지 알 수 있도록 앱의 매니페스트 파일에 <queries> 요소를 추가해야 할 수 있습니다.

<queries> 요소를 사용하면 앱이 상호작용해야 할 수도 있는 다른 앱을 설명할 수 있습니다. 단일 <queries> 요소 내에서 패키지 이름별 또는 인텐트 서명별로 앱을 지정할 수 있습니다.

queryIntentActivities()와 같은 다른 앱에 관한 결과를 반환하는 PackageManager 메서드는 호출하는 앱의 <queries> 선언에 따라 필터링됩니다. 또한 startService()와 같은 다른 앱과의 명시적 상호작용을 위해서는 타겟 앱이 <queries>의 선언 중 하나와 일치해야 합니다.

Google은 항상 소중한 의견에 귀 기울이고 있습니다. 이 간단한 설문조사를 통해 기능 사용 방식을 알려주세요. 특히 이 기능의 영향을 받은 사용 사례에 관해 알려주세요.

특정 패키지 쿼리 및 상호작용

쿼리하거나 상호작용하려는 특정 앱 세트(예: 앱과 통합된 앱 또는 사용 중인 서비스를 제공하는 앱)를 알고 있다면 <queries> 태그 내의 <package> 요소 세트에 패키지 이름을 포함합니다.

<manifest package="com.example.game">
    <queries>
        <package android:name="com.example.store" />
        <package android:name="com.example.services" />
    </queries>
    ...
</manifest>

인텐트 필터가 제공된 앱 쿼리 및 상호작용

앱이 특정 용도로 사용되는 앱 세트를 쿼리하거나 상호작용해야 할 수 있습니다. 그런데 포함할 구체적인 패키지 이름을 모를 수 있습니다. 이럴 때는 <queries> 태그에 인텐트 필터 서명을 나열할 수 있습니다. 그러면 앱이 일치하는 <intent-filter> 태그가 있는 앱을 검색할 수 있습니다.

다음 예에서는 앱이 JPEG 이미지 공유를 지원하는 설치된 앱을 확인할 수 있습니다.

<manifest package="com.example.game">
    <queries>
        <intent>
            <action android:name="android.intent.action.SEND" />
            <data android:mimeType="image/jpeg" />
        </intent>
    </queries>
    ...
</manifest>

<intent> 요소에는 몇 가지 제한사항이 있습니다.

  • 정확히 <action> 요소 하나를 포함해야 합니다.
  • <data> 요소에서 path, pathPrefix, pathPattern 또는 port 속성을 사용할 수 없습니다. 시스템은 각 속성 값을 일반 와일드 카드 문자(*)로 설정한 것처럼 동작합니다.
  • <data> 요소의 mimeGroup 속성을 사용할 수 없습니다.
  • 단일 <intent> 요소의 <data> 요소 내에서 다음 각 속성을 최대 한 번 사용할 수 있습니다.

    • mimeType
    • scheme
    • host

    이러한 속성을 여러 <data> 요소 전체에 배포하거나 단일 <data> 요소에서 사용할 수 있습니다.

<intent> 요소는 일반 와일드 카드 문자(*)를 몇 가지 속성의 값으로 지원합니다.

  • <action> 요소의 name 속성
  • <data> 요소(image/*)의 mimeType 속성의 하위유형
  • <data> 요소(*/*)의 mimeType 속성의 유형 및 하위유형
  • <data> 요소의 scheme 속성
  • <data> 요소의 host 속성

이전 목록에 달리 지정되지 않은 한 시스템에서는 텍스트와 와일드 카드 문자(예: prefix*)가 혼합된 경우를 지원하지 않습니다.

모든 앱 쿼리 및 상호작용

드물지만 앱이 포함된 구성요소와 상관없이 기기에 설치된 모든 앱을 쿼리하거나 상호작용해야 할 수 있습니다. Google Play와 같은 앱 스토어를 예로 들 수 있습니다. 앱에서 설치된 다른 모든 앱을 확인할 수 있도록 Android 11에는 QUERY_ALL_PACKAGES 권한이 도입되었습니다.

참고: 대부분의 상황에서 <queries> 태그를 선언하여 앱의 사용 사례를 실행할 수 있습니다.

향후 버전의 개발자 프리뷰에서 Google Play를 찾아 이 권한이 필요한 앱에 관한 가이드라인을 제공하세요.

패키지 필터링의 로그 메시지

패키지 공개 상태의 변경사항이 앱에 어떤 영향을 미치는지 자세히 알아보려면 패키지 필터링의 로그 메시지를 사용 설정하면 됩니다. Android 스튜디오에서 테스트 앱 또는 디버그 가능한 앱을 개발하고 있다면 이 기능이 사용 설정됩니다. 그 외에는 터미널 창에서 다음 명령어를 실행하여 수동으로 사용 설정할 수 있습니다.

adb shell pm log-visibility --enable your-package-name

그러면 패키지가 PackageManager 객체의 반환 값에서 필터링될 때마다 Logcat의 다음과 유사한 메시지가 표시됩니다.

I/AppsFilter: interaction: PackageSetting{7654321 \
  com.example.myapp/12345} -> PackageSetting{...} BLOCKED

변경사항 테스트

이 동작 변경사항이 앱에 적용되었는지 테스트하려면 다음 단계를 완료하세요.

  1. Android 스튜디오 3.6.1 이상을 설치합니다.
  2. Android 스튜디오에서 지원하는 최신 버전의 Gradle을 설치합니다.
  3. 앱의 targetSdkVersion'R'로 설정합니다.
  4. 앱의 매니페스트 파일에 <queries> 요소를 포함하지 않습니다.
  5. getInstalledApplications() 또는 getInstalledPackages()를 호출합니다. 두 메서드는 모두 필터링된 목록을 반환해야 합니다.
  6. 작동하지 않는 앱 기능을 확인합니다.
  7. 적절한 <queries> 항목을 도입하여 이러한 기능을 수정합니다.

변경사항의 영향을 받지 않는 사용 사례

다음 목록에는 <queries> 선언이 필요하지 않은 여러 사용 사례가 포함되어 있습니다.

  • 타겟 앱이 자체 앱입니다.
  • 암시적 인텐트를 사용하여 활동을 시작합니다. 앱은 암시적 인텐트를 사용하는 방법을 제한하여 다른 앱과 상호작용할 수 있습니다.
  • 앱이 핵심 Android 기능을 구현하는 특정 시스템 패키지(예: 미디어 제공업체)와 상호작용합니다.
  • 다른 앱에서 앱의 결과를 예상합니다. 이 상황은 앱이 콘텐츠 제공업체일 때, 다른 앱이 startActivityForResult()를 호출하여 앱을 호출할 때, 앱이 다른 앱에서 시작하거나 연결하려는 서비스일 때 적용됩니다.

예를 들어 앱에서 다른 앱이 콘텐츠 제공업체에 요청하면 시스템에서 앱이 다른 앱을 볼 수 있도록 허용합니다.

활동 시작에 제한사항 추가

Android 11에서는 앱의 startActivity() 호출이 다른 앱에서 인텐트를 처리하기보다는 ActivityNotFoundException을 야기해야 하는 시기를 지정할 수 있는 여러 플래그가 추가됩니다. 이러한 플래그를 사용하면 resolveActivity() 또는 queryIntentActivities()를 호출할 필요가 없습니다.

이러한 플래그는 특히 웹 인텐트에 유용합니다. 앱에서 설치된 앱에 딥 링크하려고 하지만 사용할 수 없을 때 맞춤 탭 또는 인앱 브라우저에 URL을 로드하여 인텐트 자체를 처리할 수 있습니다.

브라우저가 아닌 앱에서 웹 인텐트 실행

FLAG_ACTIVITY_REQUIRE_NON_BROWSER 인텐트 플래그를 포함하면 다음과 같은 경우에만 인텐트가 시작됩니다.

  • 브라우저가 아닌 앱은 인텐트를 직접 처리합니다.
  • 사용자는 명확성 대화상자에서 브라우저가 아닌 앱을 선택할 수 있습니다.

선택할 수 없으면 ActivityNotFoundException이 발생하고 앱에서 인텐트 자체를 처리해야 합니다.

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.
    tryLoadInCustomTabs(url) // Or use an in-app browser.
}

자바

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.
    tryLoadInCustomTabs(url); // Or use an in-app 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_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 URLs.
    tryLoadInCustomTabs(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_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 URLs.
    tryLoadInCustomTabs(url);
}