활동 삽입은 애플리케이션의 작업 창을 두 개의 활동 또는 동일한 활동의 두 개의 인스턴스로 분할하여 대형 화면 기기에서 앱을 최적화합니다.
앱이 여러 활동으로 구성된 경우 활동 삽입을 사용하면 태블릿, 폴더블, ChromeOS 기기에서 향상된 사용자 환경을 제공할 수 있습니다.
활동 삽입에는 코드 리팩터링이 필요하지 않습니다. XML 구성 파일을 만들거나 Jetpack WindowManager API를 호출하여 앱이 활동을 표시하는 방법, 즉 나란히 표시할지 스택으로 표시할지를 결정합니다.
작은 화면 지원은 자동으로 유지됩니다. 화면이 작은 기기에 앱이 있는 경우 활동이 서로 스택됩니다. 대형 화면에서는 활동이 나란히 표시됩니다. 생성한 구성에 따라 시스템에서 표현 방식을 결정하며 분기화 로직이 필요하지 않습니다.
활동 삽입은 기기 방향 변경을 지원하고 폴더블 기기에서 원활하게 작동하여 기기를 접거나 펼칠 때 활동을 스택하고 스택 해제합니다.
활동 삽입은 Android 12L(API 수준 32) 및 이후 버전을 실행하는 대부분의 대형 화면 기기에서 지원됩니다.
작업 창 분할
활동 삽입은 앱 작업 창을 기본 컨테이너와 보조 컨테이너로 분할합니다. 컨테이너는 기본 활동에서 실행된 활동, 또는 이미 컨테이너에 있는 다른 활동에서 실행된 활동을 보유합니다.
활동이 실행되면 보조 컨테이너에 스택되고, 작은 화면에서 보조 컨테이너는 기본 컨테이너 위에 스택됩니다. 따라서 활동 스택 및 뒤로 탐색이 앱에 이미 내장된 활동의 순서와 일치합니다.
활동 삽입을 사용하면 다양한 방식으로 활동을 표시할 수 있습니다. 앱은 두 활동을 나란히 동시 실행하여 작업 창을 분할할 수 있습니다.
또는 전체 작업 창을 차지하고 있는 활동은 새 활동을 나란히 실행하여 분할을 만들 수 있습니다.
이미 분할에 표시되며 작업 창을 공유하는 활동은 다음과 같은 방법으로 다른 활동을 실행할 수 있습니다.
측면으로, 다른 활동 위:
측면으로, 분할의 좌우를 전환하여 이전 기본 활동을 숨김:
기존 위치의 위에(동일한 활동 스택) 활동 실행:
동일한 작업에서 활동 전체 크기 창 실행:
뒤로 탐색
다양한 유형의 애플리케이션은 분할 작업 창 상태에서 활동 간의 종속성이나 사용자가 뒤로 이벤트를 트리거하는 방식(아래의 예)에 따라 서로 다른 뒤로 탐색 규칙을 가질 수 있습니다.
- 동시: 활동이 관련되어 있어서 두 활동을 따로 표시해서는 안 되는 경우 둘 다 종료하도록 뒤로 탐색을 구성할 수 있습니다.
- 단독: 활동이 완전히 독립적인 경우 한 활동의 뒤로 탐색이 작업 창에 있는 다른 활동의 상태에 영향을 주지 않습니다.
버튼 탐색을 사용할 때 마지막으로 포커스가 맞춰진 활동으로 뒤로 이벤트가 전송됩니다.
동작 기반 탐색의 경우:
Android 14 (API 수준 34) 이하: 뒤로 이벤트는 동작이 발생한 활동에 전송됩니다. 사용자가 화면 왼쪽에서 스와이프하면 뒤로 이벤트가 분할 창의 왼쪽 창에 있는 활동으로 전송됩니다. 사용자가 화면 오른쪽에서 스와이프하면 뒤로 이벤트가 오른쪽 창의 활동으로 전송됩니다.
Android 15 (API 수준 35) 및 이후 버전
동일한 앱의 여러 활동을 처리할 때 동작은 스와이프 방향과 관계없이 상위 활동을 종료하여 보다 통합된 환경을 제공합니다.
서로 다른 앱의 두 활동 (오버레이)이 포함된 시나리오에서는 뒤로 이벤트가 버튼 탐색 동작에 따라 포커스가 있는 마지막 활동으로 이동합니다.
다중 창 레이아웃
Jetpack WindowManager를 사용하면 Android 12L (API 수준 32) 이상을 사용하는 대형 화면 기기와 이전 플랫폼 버전을 사용하는 일부 기기에서 활동 삽입 다중 창 레이아웃을 빌드할 수 있습니다. 프래그먼트나 뷰 기반 레이아웃(예: SlidingPaneLayout
)이 아닌 여러 활동을 기반으로 하는 기존 앱은 소스 코드를 리팩터링하지 않고도 개선된 대형 화면 사용자 환경을 제공할 수 있습니다.
한 가지 일반적인 예는 목록-세부정보 분할입니다. 높은 품질의 표현을 위해 시스템에서 목록 활동을 시작한 후에 애플리케이션이 즉시 세부정보 활동을 시작합니다. 전환 시스템은 두 활동이 모두 그려질 때까지 대기했다가 두 활동을 함께 표시합니다. 두 활동은 사용자의 관점에서 하나로 실행됩니다.
속성 분할
분할 컨테이너 간 작업 창의 비율과 컨테이너가 다른 컨테이너에 대해 배치되는 방식을 지정할 수 있습니다.
XML 구성 파일에 정의된 규칙의 경우 다음 속성을 설정해야 합니다.
splitRatio
: 컨테이너 비율을 설정합니다. 값은 개구간 (0.0, 1.0) 내의 부동 소수점 숫자입니다.splitLayoutDirection
: 분할 컨테이너가 다른 컨테이너에 대해 배치되는 방식을 지정합니다. 이 속성에는 다음과 같은 값이 있습니다.ltr
: 왼쪽에서 오른쪽rtl
: 오른쪽에서 왼쪽locale
: 언어 설정에 따라ltr
또는rtl
로 결정됨
예시는 XML 구성 섹션을 참고하세요.
WindowManager API를 사용하여 만든 규칙의 경우 SplitAttributes.Builder
를 사용하여 SplitAttributes
객체를 만들고 다음 빌더 메서드를 호출합니다.
setSplitType()
: 분할 컨테이너의 비율을 설정합니다.SplitAttributes.SplitType.ratio()
메서드를 비롯하여 유효한 인수는SplitAttributes.SplitType
을 참고하세요.setLayoutDirection()
: 컨테이너의 레이아웃을 설정합니다. 가능한 값은SplitAttributes.LayoutDirection
을 참고하세요.
예시는 WindowManager API 섹션을 참고하세요.
자리표시자
자리표시자 활동은 활동 분할의 한 영역을 차지하는 빈 보조 활동입니다. 궁극적으로 콘텐츠가 포함된 다른 활동으로 대체됩니다. 예를 들어 자리표시자 활동은 목록의 항목이 선택될 때까지 목록-세부정보 레이아웃에서 활동 분할의 보조 측면을 차지할 수 있습니다. 목록의 항목이 선택되면 이 목록 항목에 관한 세부정보가 포함된 활동이 자리표시자를 대체합니다.
기본적으로 시스템은 활동 분할을 위한 공간이 충분한 경우에만 자리표시자를 표시합니다. 너무 작아서 분할을 표시할 수 없는 너비나 높이로 디스플레이 크기가 변경되면 자리표시자는 자동으로 종료됩니다. 공간이 허락하는 경우 시스템은 다시 초기화된 상태로 자리표시자를 다시 실행합니다.
하지만 SplitPlaceholder.Builder
에 있는 SplitPlaceholderRule
또는 setSticky()
메서드의 stickyPlaceholder
속성은 기본 동작을 재정의할 수 있습니다. 속성이나 메서드가 true
값을 지정하면 디스플레이가 두 개의 창 디스플레이에서 단일 창 디스플레이로 크기가 조절될 때 시스템에서 이 자리표시자를 작업 창의 최상위 활동으로 표시합니다(아래 예의 분할 구성 참고).
창 크기 변경
기기 구성 변경으로 인해 작업 창 너비가 줄어들어 다중 창 레이아웃에 충분하지 않게 되면 (예: 대형 화면 폴더블 기기가 태블릿 크기에서 휴대전화 크기로 접히거나 멀티 윈도우 모드에서 앱 창 크기가 조절되는 경우) 작업 창의 보조 창에 있는 자리표시자 이외의 활동이 기본 창의 활동 위에 쌓입니다.
자리표시자 활동은 분할에 맞게 디스플레이 너비가 충분한 경우에만 표시됩니다. 작은 화면에서는 자리표시자가 자동으로 닫힙니다. 디스플레이 영역이 다시 충분히 커지면 자리표시자가 재생성됩니다. (자리표시자 섹션을 참고하세요.)
WindowManager가 기본 창의 활동 위에 보조 창의 활동을 z 순서로 지정하므로 활동 스택이 가능합니다.
보조 창의 여러 활동
활동 B가 추가 인텐트 플래그 없이 제자리에서 활동 C를 시작합니다.
동일한 작업에서 다음과 같이 활동이 z 순서로 지정됩니다.
따라서 작은 작업 창에서는 애플리케이션이 스택 맨 위에 있는 C를 포함한 단일 활동으로 축소됩니다.
작은 창에서 뒤로 이동하면 활동 위에 스택된 활동을 탐색하게 됩니다.
작업 창 구성이 여러 창을 수용할 수 있는 큰 크기로 복원되면 활동이 다시 나란히 표시됩니다.
스택된 분할
활동 B가 측면에 활동 C를 시작하고 분할의 좌우를 전환합니다.
동일한 작업에서 다음과 같이 활동이 z 순서로 지정됩니다.
작은 작업 창에서는 애플리케이션이 맨 위에 있는 C를 포함한 단일 활동으로 축소됩니다.
고정 세로 방향
android:screenOrientation 매니페스트 설정을 사용하면 앱의 활동을 세로 또는 가로 방향으로 제한할 수 있습니다. 태블릿 및 폴더블과 같은 대형 화면 기기에서 사용자 환경을 개선하기 위해 기기 제조업체(OEM)는 화면 방향 요청을 무시하고 가로 모드 디스플레이에서 세로 방향으로 앱을 레터박스 처리하거나 세로 모드 디스플레이에서 가로 방향으로 앱을 레터박스 처리할 수 있습니다.
마찬가지로 활동 삽입이 사용 설정된 경우 OEM은 대형 화면(너비 600dp 이상)의 가로 모드 방향에서 고정 세로 방향 활동을 레터박스 처리하도록 기기를 맞춤설정할 수 있습니다. 고정 세로 모드 활동이 두 번째 활동을 실행하면 기기는 두 개의 창이 있는 디스플레이에 두 활동을 나란히 표시할 수 있습니다.
항상 android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
속성을 앱 매니페스트 파일에 추가하여 앱에서 활동 삽입을 지원한다는 것을 기기에 알립니다 (분할 구성 섹션 참고). 그러면 OEM 맞춤 기기에서 고정 세로 방향 활동의 레터박스 처리 여부를 결정할 수 있습니다.
분할 구성
분할 규칙은 활동 분할을 구성합니다. 분할 규칙은 XML 구성 파일에서 또는 Jetpack WindowManager API를 호출하여 정의합니다.
어느 경우든 앱은 WindowManager 라이브러리에 액세스해야 하고 앱에서 활동 삽입을 구현했음을 시스템에 알려야 합니다.
다음 단계를 따르세요.
최신 WindowManager 라이브러리 종속 항목을 앱의 모듈 수준
build.gradle
파일에 추가합니다. 예를 들면 다음과 같습니다.implementation 'androidx.window:window:1.1.0-beta02'
WindowManager 라이브러리는 활동 삽입에 필요한 모든 구성요소를 제공합니다.
앱이 활동 삽입을 구현했음을 시스템에 알립니다.
앱 매니페스트 파일의 <application> 요소에
android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
속성을 추가하고 값을 true로 설정합니다. 예를 들면 다음과 같습니다.<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <property android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED" android:value="true" /> </application> </manifest>
WindowManager 버전 1.1.0-alpha06 이상에서는 활동 삽입 분할이 사용 중지됩니다. 단, 속성이 매니페스트에 추가되고 true로 설정되는 경우는 예외입니다.
또한 기기 제조업체는 이 설정을 사용하여 활동 삽입을 지원하는 앱의 맞춤 기능을 사용 설정합니다. 예를 들어 기기는 가로 모드 디스플레이에서 세로 모드 전용 활동을 레터박스 처리하여, 두 번째 활동이 시작될 때 창 두 개 레이아웃으로 전환되도록 활동의 방향을 설정할 수 있습니다 (세로 모드로 고정된 방향 참고).
XML 구성
활동 삽입의 XML 기반 구현을 만들려면 다음 단계를 완료하세요.
다음을 실행하는 XML 리소스 파일을 만듭니다.
- 분할을 공유하는 활동 정의
- 분할 옵션 구성
- 콘텐츠를 사용할 수 없는 경우 분할의 보조 컨테이너에 맞는 자리표시자를 만듭니다.
- 분할에 포함되면 안 되는 활동을 지정합니다.
예를 들면 다음과 같습니다.
<!-- main_split_config.xml --> <resources xmlns:window="http://schemas.android.com/apk/res-auto"> <!-- Define a split for the named activities. --> <SplitPairRule window:splitRatio="0.33" window:splitLayoutDirection="locale" window:splitMinWidthDp="840" window:splitMaxAspectRatioInPortrait="alwaysAllow" window:finishPrimaryWithSecondary="never" window:finishSecondaryWithPrimary="always" window:clearTop="false"> <SplitPairFilter window:primaryActivityName=".ListActivity" window:secondaryActivityName=".DetailActivity"/> </SplitPairRule> <!-- Specify a placeholder for the secondary container when content is not available. --> <SplitPlaceholderRule window:placeholderActivityName=".PlaceholderActivity" window:splitRatio="0.33" window:splitLayoutDirection="locale" window:splitMinWidthDp="840" window:splitMaxAspectRatioInPortrait="alwaysAllow" window:stickyPlaceholder="false"> <ActivityFilter window:activityName=".ListActivity"/> </SplitPlaceholderRule> <!-- Define activities that should never be part of a split. Note: Takes precedence over other split rules for the activity named in the rule. --> <ActivityRule window:alwaysExpand="true"> <ActivityFilter window:activityName=".ExpandedActivity"/> </ActivityRule> </resources>
이니셜라이저를 만듭니다.
WindowManager
RuleController
구성요소는 XML 구성 파일을 파싱하고 시스템에 제공할 규칙을 만듭니다. Jetpack Startup 라이브러리Initializer
는 앱 시작 시RuleController
에 제공할 XML 파일을 만들어 모든 활동이 시작될 때 규칙이 적용되도록 합니다.이니셜라이저를 만들려면 다음 단계를 따르세요.
최신 Jetpack Startup 라이브러리 종속 항목을 모듈 수준
build.gradle
파일에 추가합니다. 예를 들면 다음과 같습니다.implementation 'androidx.startup:startup-runtime:1.1.1'
Initializer
인터페이스를 구현하는 클래스를 만듭니다.이니셜라이저는 XML 구성 파일 (
main_split_config.xml
)의 ID를RuleController.parseRules()
메서드에 전달하여RuleController
에서 사용할 수 있는 분할 규칙을 만듭니다.Kotlin
class SplitInitializer : Initializer<RuleController> { override fun create(context: Context): RuleController { return RuleController.getInstance(context).apply { setRules(RuleController.parseRules(context, R.xml.main_split_config)) } } override fun dependencies(): List<Class<out Initializer<*>>> { return emptyList() } }
Java
public class SplitInitializer implements Initializer<RuleController> { @NonNull @Override public RuleController create(@NonNull Context context) { RuleController ruleController = RuleController.getInstance(context); ruleController.setRules( RuleController.parseRules(context, R.xml.main_split_config) ); return ruleController; } @NonNull @Override public List<Class<? extends Initializer<?>>> dependencies() { return Collections.emptyList(); } }
규칙 정의에 맞는 콘텐츠 제공자 만들기
androidx.startup.InitializationProvider
를 앱 매니페스트 파일에<provider>
로 추가합니다.RuleController
이니셜라이저SplitInitializer
구현에 대한 참조를 포함합니다.<!-- AndroidManifest.xml --> <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <!-- Make SplitInitializer discoverable by InitializationProvider. --> <meta-data android:name="${applicationId}.SplitInitializer" android:value="androidx.startup" /> </provider>
InitializationProvider
는 앱의onCreate()
메서드가 호출되기 전에SplitInitializer
를 탐색하고 초기화합니다. 따라서 분할 규칙은 앱의 기본 활동이 시작할 때 적용됩니다.
WindowManager API
몇 개의 API 호출을 사용하여 활동 삽입을 프로그래매틱 방식으로 구현할 수 있습니다. 활동이 시작되기 전에 규칙이 적용되도록 Application
의 서브클래스에 있는 onCreate()
메서드에서 API를 호출합니다.
프로그래매틱 방식으로 활동 분할을 만들려면 다음 단계를 따르세요.
분할 규칙 만들기:
분할을 공유하는 활동을 식별하는
SplitPairFilter
를 만듭니다.Kotlin
val splitPairFilter = SplitPairFilter( ComponentName(this, ListActivity::class.java), ComponentName(this, DetailActivity::class.java), null )
Java
SplitPairFilter splitPairFilter = new SplitPairFilter( new ComponentName(this, ListActivity.class), new ComponentName(this, DetailActivity.class), null );
필터 세트에 필터를 추가합니다.
Kotlin
val filterSet = setOf(splitPairFilter)
Java
Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter);
분할의 레이아웃 속성을 만듭니다.
Kotlin
val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build()
자바
final SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build();
SplitAttributes.Builder
는 레이아웃 속성을 포함하는 객체를 만듭니다.setSplitType()
: 사용 가능한 디스플레이 영역이 각 활동 컨테이너에 어떻게 할당되는지를 정의합니다. 비율 분할 유형은 기본 컨테이너에 할당된 가용 디스플레이 영역의 비율을 지정합니다. 보조 컨테이너는 가용 디스플레이 영역의 나머지 부분을 차지합니다.setLayoutDirection()
: 활동 컨테이너가 다른 컨테이너를 기준으로 어떻게 배치되는지를 지정합니다. 우선 기본 컨테이너를 기준으로 합니다.
SplitPairRule
을 빌드합니다.Kotlin
val splitPairRule = SplitPairRule.Builder(filterSet) .setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER) .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS) .setClearTop(false) .build()
Java
SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet) .setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER) .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS) .setClearTop(false) .build();
SplitPairRule.Builder
는 다음과 같은 규칙을 만들고 구성합니다.filterSet
: 분할을 공유하는 활동을 식별하여 규칙을 적용할 시기를 결정하는 분할 쌍 필터를 포함합니다.setDefaultSplitAttributes()
: 규칙에 레이아웃 속성을 적용합니다.setMinWidthDp()
: 분할을 사용 설정하는 최소 디스플레이 너비를 밀도 독립형 픽셀 (dp) 단위로 설정합니다.setMinSmallestWidthDp()
: 기기 방향과 관계없이 두 디스플레이 크기 중 더 작은 값이 분할을 사용할 수 있는 최솟값 (dp)을 설정합니다.setMaxAspectRatioInPortrait()
: 세로 모드 방향에서 활동 분할이 표시되는 최대 디스플레이 가로세로 비율 (높이:너비)을 설정합니다. 세로 모드 디스플레이의 가로세로 비율이 최대 가로세로 비율을 초과하면 디스플레이 너비와 관계없이 분할이 사용 중지됩니다. 참고: 기본값은 1.4이며, 이 경우 활동이 대부분의 태블릿에서 세로 모드 방향의 전체 작업 창을 차지합니다.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
및setMaxAspectRatioInLandscape()
도 참고하세요. 가로 모드의 기본값은ALWAYS_ALLOW
입니다.setFinishPrimaryWithSecondary()
: 보조 컨테이너의 모든 활동이 종료될 때 기본 컨테이너의 활동에 어떠한 영향을 미치는지 설정합니다.NEVER
는 보조 컨테이너의 모든 활동이 종료될 때 시스템에서 기본 활동을 종료하면 안 된다는 것을 나타냅니다 (활동 종료 참고).setFinishSecondaryWithPrimary()
: 기본 컨테이너의 모든 활동이 종료될 때 보조 컨테이너의 활동에 어떠한 영향을 미치는지 설정합니다.ALWAYS
는 기본 컨테이너의 모든 활동이 종료될 때 시스템에서 항상 보조 컨테이너의 활동을 종료해야 함을 나타냅니다 (활동 종료 참고).setClearTop()
: 컨테이너에서 새 활동이 실행될 때 보조 컨테이너의 모든 활동이 종료되는지를 지정합니다.false
값은 새 활동이 보조 컨테이너에 이미 있는 활동 위에 스택됨을 지정합니다.
WindowManager
RuleController
의 싱글톤 인스턴스를 가져오고 규칙을 추가합니다.Kotlin
val ruleController = RuleController.getInstance(this) ruleController.addRule(splitPairRule)
자바
RuleController ruleController = RuleController.getInstance(this); ruleController.addRule(splitPairRule);
콘텐츠를 사용할 수 없는 경우 보조 컨테이너의 자리표시자를 만듭니다.
자리표시자가 작업 창 분할을 공유하는 활동을 식별하는
ActivityFilter
를 만듭니다.Kotlin
val placeholderActivityFilter = ActivityFilter( ComponentName(this, ListActivity::class.java), null )
Java
ActivityFilter placeholderActivityFilter = new ActivityFilter( new ComponentName(this, ListActivity.class), null );
필터 세트에 필터를 추가합니다.
Kotlin
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
자바
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter);
SplitPlaceholderRule
를 만듭니다.Kotlin
val splitPlaceholderRule = SplitPlaceholderRule.Builder( placeholderActivityFilterSet, Intent(context, PlaceholderActivity::class.java) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build()
Java
SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder( placeholderActivityFilterSet, new Intent(context, PlaceholderActivity.class) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build();
SplitPlaceholderRule.Builder
는 다음과 같은 규칙을 만들고 구성합니다.placeholderActivityFilterSet
: 자리표시자 활동과 연결된 활동을 식별하여 규칙을 적용할 시기를 결정하는 활동 필터를 포함합니다.Intent
: 자리표시자 활동의 실행을 지정합니다.setDefaultSplitAttributes()
: 규칙에 레이아웃 속성을 적용합니다.setMinWidthDp()
: 분할을 허용하는 최소 디스플레이 너비(밀도 독립형 픽셀(dp))를 설정합니다.setMinSmallestWidthDp()
: 기기 방향과 관계없이 두 디스플레이 크기 중 더 작은 값이 분할을 허용하기 위해 취해야 할 최솟값 (dp)을 설정합니다.setMaxAspectRatioInPortrait()
: 세로 모드 방향에서 활동 분할이 표시되는 최대 디스플레이 가로세로 비율 (높이:너비)을 설정합니다. 참고: 기본값은 1.4이며, 이 경우 활동이 대부분의 태블릿에서 세로 모드 방향의 전체 작업 창을 채웁니다.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
및setMaxAspectRatioInLandscape()
도 참고하세요. 가로 모드의 기본값은ALWAYS_ALLOW
입니다.setFinishPrimaryWithPlaceholder()
: 자리표시자 활동 종료가 기본 컨테이너의 활동에 어떠한 영향을 미치는지를 설정합니다. ALWAYS는 자리표시자가 종료되면 시스템이 항상 기본 컨테이너의 활동을 종료해야 함을 나타냅니다 (활동 종료 참고).setSticky()
: 자리표시자가 충분한 최소 너비로 분할에 처음 표시된 후에 자리표시자 활동이 작은 디스플레이의 활동 스택 상단에 표시될지를 결정합니다.
WindowManager
RuleController
에 규칙 추가:Kotlin
ruleController.addRule(splitPlaceholderRule)
Java
ruleController.addRule(splitPlaceholderRule);
분할에 포함되면 안 되는 활동을 지정합니다.
항상 전체 작업 디스플레이 영역을 차지해야 하는 활동을 식별하는
ActivityFilter
를 만듭니다.Kotlin
val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null )
Java
ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null );
필터 세트에 필터를 추가합니다.
Kotlin
val expandedActivityFilterSet = setOf(expandedActivityFilter)
Java
Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter);
ActivityRule
을 만듭니다.Kotlin
val activityRule = ActivityRule.Builder(expandedActivityFilterSet) .setAlwaysExpand(true) .build()
Java
ActivityRule activityRule = new ActivityRule.Builder( expandedActivityFilterSet ).setAlwaysExpand(true) .build();
ActivityRule.Builder
는 다음과 같은 규칙을 만들고 구성합니다.expandedActivityFilterSet
: 분할을 제외할 활동을 식별하여 규칙을 적용할 시기를 결정하는 활동 필터를 포함합니다.setAlwaysExpand()
: 활동이 전체 작업 창을 채워야 하는지 지정합니다.
WindowManager
RuleController
에 규칙 추가:Kotlin
ruleController.addRule(activityRule)
Java
ruleController.addRule(activityRule);
교차 애플리케이션 삽입
Android 13 (API 수준 33) 이상에서는 앱이 다른 앱의 활동을 삽입할 수 있습니다. 교차 애플리케이션 활동 또는 교차 UID 활동 삽입을 사용하면 Android 애플리케이션의 활동을 시각적으로 통합할 수 있습니다. 시스템은 단일 앱 활동 삽입에서처럼 호스트 앱의 활동과 다른 앱의 삽입된 활동을 화면에 나란히 표시하거나 위아래로 표시합니다.
예를 들어 설정 앱에서 WallpaperPicker 앱의 배경화면 선택기 활동을 삽입할 수 있습니다.
신뢰 모델
다른 앱의 활동을 삽입하는 호스트 프로세스는 크기, 위치, 자르기, 투명도 등 삽입된 활동의 표현을 재정의할 수 있습니다. 악의적인 호스트는 이 기능을 사용하여 사용자를 현혹하고 클릭재킹 또는 기타 UI 수정 공격을 유발할 수 있습니다.
교차 앱 활동 삽입의 오용을 방지하기 위해 Android의 앱에서는 활동 삽입을 허용하도록 선택해야 합니다. 앱은 호스트를 신뢰할 수 있는 호스트 또는 신뢰할 수 없는 호스트로 지정할 수 있습니다.
신뢰할 수 있는 호스트
다른 애플리케이션이 앱의 활동을 삽입하고 이러한 앱 활동 표현을 완전히 제어할 수 있게 하려면 앱 매니페스트 파일에서 <activity>
또는 <application>
요소의 android:knownActivityEmbeddingCerts
속성에 호스트 애플리케이션의 SHA-256 인증서를 지정합니다.
android:knownActivityEmbeddingCerts
의 값을 다음과 같은 문자열로 설정하거나
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
... />
또는 (여러 인증서를 지정하려면) 문자열로 구성된 배열로 설정합니다.
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
... />
다음과 같이 리소스를 참조합니다.
<resources>
<string-array name="known_host_certificate_digests">
<item>cert1</item>
<item>cert2</item>
...
</string-array>
</resources>
앱 소유자는 Gradle signingReport
작업을 실행하여 SHA 인증서 다이제스트를 가져올 수 있습니다. 인증서 다이제스트는 구분용 콜론이 포함되지 않은 SHA-256 지문입니다. 자세한 내용은 서명 보고서 실행 및 클라이언트 인증을 참고하세요.
신뢰할 수 없는 호스트
모든 앱이 앱의 활동을 삽입하고 이러한 앱 활동 표현을 제어할 수 있게 하려면 앱 매니페스트의 <activity>
요소나 <application>
요소에 android:allowUntrustedActivityEmbedding
속성을 지정합니다. 예를 들면 다음과 같습니다.
<activity
android:name=".MyEmbeddableActivity"
android:allowUntrustedActivityEmbedding="true"
... />
속성의 기본값은 false이며, 이 경우 교차 앱 활동 삽입이 방지됩니다.
맞춤 인증
신뢰할 수 없는 활동 삽입의 위험을 완화하려면 호스트 ID를 확인하는 맞춤 인증 메커니즘을 만듭니다. 호스트 인증서를 알고 있는 경우 androidx.security.app.authenticator
라이브러리를 사용하여 인증합니다. 활동이 삽입된 후 호스트가 인증하는 경우 실제 콘텐츠를 표시할 수 있습니다. 그렇지 않은 경우 사용자에게 작업이 허용되지 않았음을 알리고 콘텐츠를 차단할 수 있습니다.
Jetpack WindowManager 라이브러리의 ActivityEmbeddingController#isActivityEmbedded()
메서드를 사용하여 호스트가 활동을 삽입하는지 여부를 확인합니다. 예를 들면 다음과 같습니다.
Kotlin
fun isActivityEmbedded(activity: Activity): Boolean { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity) }
Java
boolean isActivityEmbedded(Activity activity) { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity); }
최소 크기 제한
Android 시스템은 앱 매니페스트의 <layout>
요소에 지정된 최소 높이 및 너비를 삽입된 활동에 적용합니다. 애플리케이션에서 최소 높이와 너비를 지정하지 않으면 시스템 기본값(sw220dp
)이 적용됩니다.
호스트가 삽입된 컨테이너의 크기를 최솟값보다 작게 조정하려고 하면 삽입된 컨테이너가 전체 작업 경계를 차지하도록 확장됩니다.
<activity-alias>
신뢰할 수 있거나 신뢰할 수 없는 활동 삽입이 <activity-alias>
요소와 함께 작동하려면 별칭이 아닌 타겟 활동에 android:knownActivityEmbeddingCerts
또는 android:allowUntrustedActivityEmbedding
을 적용해야 합니다. 시스템 서버에서 보안을 확인하는 정책은 별칭이 아닌 타겟에 설정된 플래그를 기반으로 합니다.
호스트 애플리케이션
호스트 애플리케이션은 단일 앱 활동 삽입을 구현하는 것과 동일한 방식으로 교차 앱 활동 삽입을 구현합니다. SplitPairRule
및 SplitPairFilter
객체 또는 ActivityRule
및 ActivityFilter
객체는 삽입된 활동 및 작업 창 분할을 지정합니다. 분할 규칙은 XML에서 정적으로 정의되거나 런타임에 Jetpack WindowManager API 호출을 사용하여 정의됩니다.
호스트 애플리케이션이 교차 앱 삽입을 선택하지 않은 활동을 삽입하려고 하면 활동이 전체 작업 경계를 차지합니다. 따라서 호스트 애플리케이션은 타겟 활동이 교차 앱 삽입을 허용하는지 여부를 알아야 합니다.
삽입된 활동이 동일한 작업에서 새 활동을 시작하고 이 새 활동이 교차 앱 삽입을 선택하지 않은 경우 활동은 삽입된 컨테이너에서 활동을 오버레이하는 대신 전체 작업 경계를 차지합니다.
호스트 애플리케이션은 활동이 동일한 작업에서 실행되는 경우 제한 없이 자체 활동을 삽입할 수 있습니다.
분할 예
전체 크기 창에서 분할
리팩터링할 필요가 없습니다. 정적으로 또는 런타임으로 분할의 구성을 정의한 다음 추가 매개변수 없이 Context#startActivity()
를 호출할 수 있습니다.
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
기본적으로 분할
애플리케이션의 방문 페이지가 대형 화면에서 두 컨테이너로 분할되도록 설계된 경우 두 활동이 모두 생성되고 동시에 표현될 때 사용자 환경이 가장 좋습니다. 하지만 사용자가 기본 컨테이너의 활동과 상호작용 (예: 사용자가 탐색 메뉴에서 항목 선택)할 때까지 분할의 보조 컨테이너에 콘텐츠를 사용하지 못할 수도 있습니다. 자리표시자 활동은 분할의 보조 컨테이너에 콘텐츠가 표시될 수 있을 때까지 공백을 채울 수 있습니다 (자리표시자 섹션 참고).
자리표시자로 분할을 만들려면 자리표시자를 만들고 기본 활동과 연결합니다.
<SplitPlaceholderRule
window:placeholderActivityName=".PlaceholderActivity">
<ActivityFilter
window:activityName=".MainActivity"/>
</SplitPlaceholderRule>
딥 링크 분할
앱이 인텐트를 수신하면 대상 활동이 활동 분할의 보조 부분으로 표시될 수 있습니다. 예를 들어 목록의 항목에 관한 정보가 포함된 세부정보 화면을 표시하는 요청이 있습니다. 작은 디스플레이에서는 전체 크기 작업 창에, 큰 기기에서는 목록 옆에 세부정보가 표시됩니다.
실행 요청은 기본 활동으로 라우팅되고 대상 세부정보 활동은 분할에서 실행됩니다. 시스템은 사용할 수 있는 디스플레이 너비에 따라 올바른 표시 방법(스택 또는 나란히 표시)을 자동으로 선택합니다.
Kotlin
override fun onCreate(savedInstanceState Bundle?) { . . . RuleController.getInstance(this) .addRule(SplitPairRule.Builder(filterSet).build()) startActivity(Intent(this, DetailActivity::class.java)) }
자바
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { . . . RuleController.getInstance(this) .addRule(new SplitPairRule.Builder(filterSet).build()); startActivity(new Intent(this, DetailActivity.class)); }
딥 링크 대상은 뒤로 탐색 스택에서 사용자가 사용할 수 있는 유일한 활동일 수도 있으므로 세부정보 활동을 닫고 기본 활동만 남기는 것을 방지하는 것이 좋습니다.
대신 finishPrimaryWithSecondary
속성을 사용하여 두 활동을 동시에 종료할 수 있습니다.
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".ListActivity"
window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>
구성 속성 섹션을 참고하세요.
분할 컨테이너의 여러 활동
분할 컨테이너에 여러 활동을 스택하면 사용자가 딥 콘텐츠에 액세스할 수 있습니다. 예를 들어 목록-세부정보 분할에서 사용자가 하위 세부정보 섹션으로 이동하되 기본 활동은 제자리에 유지해야 할 수도 있습니다.
Kotlin
class DetailActivity { . . . fun onOpenSubDetail() { startActivity(Intent(this, SubDetailActivity::class.java)) } }
Java
public class DetailActivity { . . . void onOpenSubDetail() { startActivity(new Intent(this, SubDetailActivity.class)); } }
하위 세부정보 활동이 세부정보 활동 위에 놓여 세부정보 활동이 숨겨집니다.
그러면 사용자는 스택에서 뒤로 이동하여 이전의 세부정보 수준으로 돌아갈 수 있습니다.
여러 활동을 서로 위에 스택하는 것은 이러한 활동이 동일한 보조 컨테이너의 한 활동에서 실행되는 경우 기본 동작입니다. 활성 분할 내의 기본 컨테이너에서 실행된 활동도 활동 스택 맨 위에 있는 보조 컨테이너에 배치됩니다.
새 작업의 활동
분할 작업 창의 활동이 새 작업에서 활동을 시작하는 경우 이 새 작업은 분할을 포함하는 작업에서 분리되어 전체 크기 창으로 표시됩니다. 최근 항목 화면에는 분할에서의 작업과 새 작업, 두 가지 작업이 표시됩니다.
활동 대체
활동이 보조 컨테이너 스택에서 대체될 수 있습니다. 예를 들어 기본 활동이 최상위 탐색에 사용되고 보조 활동이 선택된 대상인 경우가 있습니다. 최상위 탐색에서 선택한 각 항목은 보조 컨테이너에서 새 활동을 시작하고 이전에 보조 컨테이너에 존재하던 하나 이상의 활동을 삭제해야 합니다.
탐색 선택이 변경될 때 앱이 보조 컨테이너의 활동을 종료하지 않으면 분할이 접힐 때 (기기가 접힐 때) 뒤로 탐색이 혼란스러울 수도 있습니다. 예를 들어 기본 창에 메뉴가 있고 보조 창에 화면 A와 B가 스택되어 있는 경우 사용자가 휴대전화를 접으면 B는 A 위에, A는 메뉴 위에 배치됩니다. 사용자가 B에서 뒤로 이동하면 메뉴 대신 화면 A가 표시됩니다.
이 경우 화면 A를 백 스택에서 삭제해야 합니다.
기존 분할에서 측면으로 새 컨테이너에 실행할 때의 기본 동작은 새 보조 컨테이너를 맨 위에 두고 이전 컨테이너를 백 스택에 유지하는 것입니다. clearTop
을 사용하여 이전 보조 컨테이너를 지우고 정상적으로 새 활동을 실행하도록 분할을 구성할 수 있습니다.
<SplitPairRule
window:clearTop="true">
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenA"/>
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>
Kotlin
class MenuActivity { . . . fun onMenuItemSelected(selectedMenuItem: Int) { startActivity(Intent(this, classForItem(selectedMenuItem))) } }
자바
public class MenuActivity { . . . void onMenuItemSelected(int selectedMenuItem) { startActivity(new Intent(this, classForItem(selectedMenuItem))); } }
또는 동일한 보조 활동을 사용하고 기본 (메뉴) 활동에서는 동일한 인스턴스로 확인되지만 상태 또는 UI 업데이트를 보조 컨테이너에 트리거하는 새 인텐트를 전송합니다.
여러 분할
앱은 추가 활동을 측면에 실행하여 여러 수준의 딥 탐색을 제공할 수 있습니다.
보조 컨테이너의 활동이 새 활동을 측면에 실행하면 기존 분할 위에 새 분할이 생성됩니다.
백 스택에는 이전에 열린 모든 활동이 포함되어 있으므로 사용자는 C를 종료한 후 A/B 분할로 이동할 수 있습니다.
새 분할을 만들려면 기존의 보조 컨테이너에서 측면으로 새 활동을 실행합니다. A/B 분할과 B/C 분할에 모두 구성을 선언하고 정상적으로 B에서 활동 C를 실행합니다.
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
<SplitPairFilter
window:primaryActivityName=".B"
window:secondaryActivityName=".C"/>
</SplitPairRule>
Kotlin
class B { . . . fun onOpenC() { startActivity(Intent(this, C::class.java)) } }
자바
public class B { . . . void onOpenC() { startActivity(new Intent(this, C.class)); } }
분할 상태 변경에 관한 반응
앱의 다양한 활동에는 동일한 기능을 실행하는 UI 요소가 있을 수 있습니다. 예를 들어 계정 설정이 포함된 창을 여는 컨트롤이 있습니다.
공통된 UI 요소를 가진 두 활동이 분할되는 경우 중복되어 이 요소를 두 활동 모두에서 표시하면 혼란스러울 수 있습니다.
활동이 분할에 포함되는 시점을 알아보려면 SplitController.splitInfoList
흐름을 확인하거나 SplitControllerCallbackAdapter
에 리스너를 등록하여 분할 상태의 변경사항을 확인하세요. 그런 다음 UI를 다음과 같이 조정합니다.
Kotlin
val layout = layoutInflater.inflate(R.layout.activity_main, null) val view = layout.findViewById<View>(R.id.infoButton) lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance. .collect { list -> view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE } } }
자바
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { . . . new SplitControllerCallbackAdapter(SplitController.getInstance(this)) .addSplitListener( this, Runnable::run, splitInfoList -> { View layout = getLayoutInflater().inflate(R.layout.activity_main, null); layout.findViewById(R.id.infoButton).setVisibility( splitInfoList.isEmpty() ? View.VISIBLE : View.GONE); }); }
코루틴은 모든 수명 주기 상태에서 실행할 수 있지만, 일반적으로 리소스를 보존하기 위해 STARTED
상태에서 실행됩니다 (자세한 내용은 수명 주기 인식 구성요소와 함께 Kotlin 코루틴 사용 참고).
활동이 중지되는 시점을 포함하여 모든 수명 주기 상태에서 콜백을 호출할 수 있습니다. 리스너는 일반적으로 onStart()
에 등록되어야 하고 onStop()
에서 등록 취소되어야 합니다.
전체 크기 창 모달
일부 활동은 지정된 작업이 실행될 때까지 사용자가 애플리케이션과 상호작용하지 못하도록 차단합니다. 예를 들어 로그인 화면 활동, 정책 확인 화면, 오류 메시지 등이 있습니다. 모달 활동은 분할에 표시되지 않아야 합니다.
확장 구성을 사용하여 활동이 항상 작업 창을 채우도록 강제할 수 있습니다.
<ActivityRule
window:alwaysExpand="true">
<ActivityFilter
window:activityName=".FullWidthActivity"/>
</ActivityRule>
활동 종료
사용자는 디스플레이 가장자리에서 스와이프하여 분할의 한쪽 측면의 활동을 종료할 수 있습니다.
기기가 동작 탐색 대신 뒤로 버튼을 사용하도록 설정된 경우 포커스가 맞춰진 활동(마지막으로 터치되거나 실행된 활동)으로 입력이 전송됩니다.
한 컨테이너의 모든 활동이 종료될 때 반대 컨테이너에 미치는 영향은 분할 구성에 따라 다릅니다.
구성 속성
분할 쌍 규칙 속성을 지정하면 분할의 한쪽에서 모든 활동이 종료될 때 분할의 다른 쪽에 있는 활동에 어떤 영향을 미치는지 구성할 수 있습니다. 속성은 다음과 같습니다.
window:finishPrimaryWithSecondary
: 보조 컨테이너의 모든 활동이 종료될 때 기본 컨테이너의 활동에 미치는 영향window:finishSecondaryWithPrimary
: 기본 컨테이너의 모든 활동이 종료될 때 보조 컨테이너의 활동에 미치는 영향
가능한 속성 값은 다음과 같습니다.
always
: 연결된 컨테이너의 활동을 항상 종료함never
: 연결된 컨테이너의 활동을 종료하지 않음adjacent
: 두 컨테이너가 서로 인접해 표시되는 경우 연결된 컨테이너에서 활동을 종료하지만 두 컨테이너가 스택된 경우에는 활동을 종료하지 않음
예를 들면 다음과 같습니다.
<SplitPairRule
<!-- Do not finish primary container activities when all secondary container activities finish. -->
window:finishPrimaryWithSecondary="never"
<!-- Finish secondary container activities when all primary container activities finish. -->
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
기본 구성
분할된 한 컨테이너의 모든 활동이 종료되면 나머지 컨테이너가 전체 창을 차지합니다.
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
활동 함께 종료
보조 컨테이너의 모든 활동이 종료되면 기본 컨테이너의 활동을 자동으로 종료합니다.
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
기본 컨테이너의 모든 활동이 종료되면 보조 컨테이너의 활동을 자동으로 종료합니다.
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
기본 컨테이너 또는 보조 컨테이너의 모든 활동이 종료되면 활동을 함께 종료합니다.
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
컨테이너의 여러 활동 종료
분할 컨테이너에 여러 활동이 스택된 경우 스택 맨 아래에 있는 활동을 종료해도 맨 위에 있는 활동이 자동으로 종료되지는 않습니다.
예를 들어 보조 컨테이너에 두 활동, 즉 활동 B 위에 활동 C가 있으며
분할 구성이 활동 A와 활동 B의 구성에 의해 정의되는 경우
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
맨 위 활동을 종료하면 분할이 유지됩니다.
보조 컨테이너의 맨 아래 (루트) 활동을 종료해도 그 위에 있는 활동은 삭제되지 않으며 분할도 유지됩니다.
기본 활동과 함께 보조 활동을 종료하는 경우와 같이 활동을 함께 종료하기 위한 추가 규칙도 실행됩니다.
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
기본과 보조를 함께 종료하도록 분할이 구성된 경우 다음과 같습니다.
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
런타임 시 분할 속성 변경
활성 상태이며 표시되는 분할의 속성은 변경할 수 없습니다. 분할 규칙을 변경하면 추가 활동 실행 및 새 컨테이너에 영향을 주지만 기존의 활성 분할에는 영향을 주지 않습니다.
활성 분할의 속성을 변경하려면 분할의 측면 활동을 종료한 후 새 구성으로 다시 측면으로 실행합니다.
동적 분할 속성
Jetpack WindowManager 1.4 이상에서 지원하는 Android 15 (API 수준 35) 및 이후 버전은 다음을 비롯하여 활동 삽입 분할의 구성 가능성을 지원하는 동적 기능을 제공합니다.
- 창 확장: 양방향 드래그 가능한 구분선을 사용하면 사용자가 분할된 프레젠테이션에서 창 크기를 조절할 수 있습니다.
- 활동 스택 고정: 사용자가 한 컨테이너의 콘텐츠를 고정하고 컨테이너의 탐색을 다른 컨테이너의 탐색과 격리할 수 있습니다.
- 대화상자 전체 화면 어둡게 처리: 대화상자를 표시할 때 앱은 전체 작업 창을 어둡게 처리할지 아니면 대화상자를 연 컨테이너만 어둡게 처리할지 지정할 수 있습니다.
창 확장
창 확장을 사용하면 사용자가 이중 창 레이아웃에서 두 활동에 할당된 화면 공간의 양을 조정할 수 있습니다.
창 구분선의 모양을 맞춤설정하고 구분선의 드래그 가능한 범위를 설정하려면 다음 단계를 따르세요.
DividerAttributes
인스턴스 만들기구분선 속성을 맞춤설정합니다.
color
: 드래그 가능한 창 분리자의 색상입니다.widthDp
: 드래그 가능한 창 구분자의 너비입니다. 시스템에서 구분자 너비를 결정하도록WIDTH_SYSTEM_DEFAULT
로 설정합니다.드래그 범위: 창이 차지할 수 있는 화면의 최소 비율입니다. 범위는 0.33~0.66입니다. 시스템에서 드래그 범위를 결정하도록 하려면
DRAG_RANGE_SYSTEM_DEFAULT
로 설정합니다.
Kotlin
val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) if (WindowSdkExtensions.getInstance().extensionVersion >= 6) { splitAttributesBuilder.setDividerAttributes( DividerAttributes.DraggableDividerAttributes.Builder() .setColor(getColor(context, R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ) } val splitAttributes: SplitAttributes = splitAttributesBuilder.build()
자바
SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT); if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) { splitAttributesBuilder.setDividerAttributes( new DividerAttributes.DraggableDividerAttributes.Builder() .setColor(ContextCompat.getColor(context, R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ); } SplitAttributes splitAttributes = splitAttributesBuilder.build();
활동 스택 고정
활동 스택 고정을 사용하면 사용자가 분할된 창 중 하나를 고정하여 사용자가 다른 창 내에서 탐색하는 동안 활동이 그대로 유지되도록 할 수 있습니다. 활동 스택 고정은 향상된 멀티태스킹 환경을 제공합니다.
앱에서 활동 스택 고정을 사용 설정하려면 다음 단계를 따르세요.
고정할 활동의 레이아웃 파일에 버튼을 추가합니다(예: 목록-세부정보 레이아웃의 세부정보 활동).
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/detailActivity" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" tools:context=".DetailActivity"> <TextView android:id="@+id/textViewItemDetail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="36sp" android:textColor="@color/obsidian" app:layout_constraintBottom_toTopOf="@id/pinButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.appcompat.widget.AppCompatButton android:id="@+id/pinButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/pin_this_activity" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/> </androidx.constraintlayout.widget.ConstraintLayout>
활동의
onCreate()
메서드에서 버튼에 onclick 리스너를 설정합니다.Kotlin
pinButton = findViewById(R.id.pinButton) pinButton.setOnClickListener { val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build() val pinSplitRule = SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build() SplitController.getInstance(applicationContext).pinTopActivityStack(taskId, pinSplitRule) }
자바
Button pinButton = findViewById(R.id.pinButton); pinButton.setOnClickListener( (view) => { SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build(); SplitPinRule pinSplitRule = new SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build(); SplitController.getInstance(getApplicationContext()).pinTopActivityStack(getTaskId(), pinSplitRule); });
대화상자 전체 화면 어둡게 처리
활동은 일반적으로 대화상자에 관심을 끌기 위해 디스플레이를 어둡게 합니다. 활동 삽입의 경우 통합된 UI 환경을 위해 대화상자를 연 활동이 포함된 창뿐만 아니라 듀얼 창 디스플레이의 두 창 모두 어둡게 처리해야 합니다.
WindowManager 1.4 이상에서는 대화상자가 열릴 때 기본적으로 전체 앱 창이 어두워집니다 (EmbeddingConfiguration.DimAreaBehavior.ON_TASK
참고).
대화상자를 연 활동의 컨테이너만 어둡게 설정하려면 EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK
을 사용하세요.
분할에서 전체 크기 창으로 활동 추출
측면 활동 전체 크기 창을 표시하는 새 구성을 만든 다음 동일한 인스턴스로 확인되는 인텐트로 활동을 다시 실행합니다.
런타임 시 분할 지원 확인
활동 삽입은 Android 12L (API 수준 32) 이상에서 지원되지만 이전 플랫폼 버전을 실행하는 일부 기기에서도 사용할 수 있습니다. 이 기능의 사용 가능성을 런타임 시 확인하려면 SplitController.splitSupportStatus
속성 또는 SplitController.getSplitSupportStatus()
메서드를 사용하세요.
Kotlin
if (SplitController.getInstance(this).splitSupportStatus == SplitController.SplitSupportStatus.SPLIT_AVAILABLE) { // Device supports split activity features. }
자바
if (SplitController.getInstance(this).getSplitSupportStatus() == SplitController.SplitSupportStatus.SPLIT_AVAILABLE) { // Device supports split activity features. }
분할이 지원되지 않으면 활동이 비활동 삽입 모델에 따라 활동 스택의 맨 위에서 실행됩니다.
시스템 재정의 방지
Android 기기 제조업체(OEM)는 기기 시스템의 기능으로 활동 삽입을 구현할 수 있습니다. 시스템은 여러 활동이 있는 앱에 분할 규칙을 지정하여 앱의 윈도잉 동작을 재정의합니다. 시스템 재정의는 여러 활동이 있는 앱에 시스템 정의 활동 삽입 모드를 강제 적용합니다.
시스템 활동 삽입은 앱을 변경하지 않고도 list-detail과 같은 다중 창 레이아웃을 통해 앱 프레젠테이션을 개선할 수 있습니다. 그러나 시스템의 활동 삽입으로 인해 잘못된 앱 레이아웃, 버그가 발생하거나 시스템 활동 삽입이 앱에서 구현된 활동 삽입과 충돌할 수도 있습니다.
앱은 앱 매니페스트 파일에 속성을 설정하여 시스템 활동 삽입을 방지하거나 허용할 수 있습니다. 예를 들면 다음과 같습니다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
android:value="true|false" />
</application>
</manifest>
속성 이름은 Jetpack WindowManager의 WindowProperties
객체에 정의됩니다. 앱이 활동 삽입을 구현하거나 시스템에서 활동 삽입 규칙을 앱에 적용하지 못하도록 하려면 이 값을 false
로 설정합니다. 이 값을 true
로 설정하면 시스템이 시스템에서 정의한 활동 삽입을 앱에 적용할 수 있습니다.
제한, 제약, 주의사항
- 작업의 루트 활동의 소유자로 식별되는 작업의 호스트 앱만 다른 활동을 구성하고 작업에 삽입할 수 있습니다. 삽입 및 분할을 지원하는 활동이 다른 애플리케이션에 속한 작업에서 실행되는 경우 이러한 활동에는 삽입 및 분할이 작동하지 않습니다.
- 단일 작업 내에서만 활동을 구성할 수 있습니다. 새 작업에서 활동을 실행하면 항상 기존 분할 외부의 새로운 확장 창에 활동이 배치됩니다.
- 동일한 프로세스의 활동만 구성하고 분할에 배치할 수 있습니다.
SplitInfo
콜백은 동일한 프로세스에 속한 활동만 보고합니다. 다른 프로세스의 활동에 관해 알 수 있는 방법이 없기 때문입니다. - 각 쌍 또는 단일 활동 규칙은 규칙 등록 후 발생하는 활동 실행에만 적용됩니다. 기존 분할이나 시각적 속성을 업데이트할 수 있는 방법은 현재 없습니다.
- 분할 쌍 필터 구성은 활동을 완전히 실행할 때 사용된 인텐트와 일치해야 합니다. 이러한 일치는 애플리케이션 프로세스에서 새로운 활동이 시작될 때 발생합니다. 따라서 암시적 인텐트를 사용할 때 시스템 프로세스의 후반에 확인되는 구성요소 이름을 모를 수도 있습니다. 실행 시점에 구성요소 이름을 알 수 없는 경우 와일드 카드 ('*/*')를 대신 사용할 수 있으며 인텐트 작업을 기반으로 필터링할 수 있습니다.
- 분할이 생성된 후 컨테이너 간에 또는 분할 내부나 외부로 활동을 이동할 수 있는 방법은 현재 없습니다. 일치하는 규칙이 있는 새 활동이 실행될 때만 WindowManager 라이브러리에 의해 분할이 생성되며 분할 컨테이너의 마지막 활동이 종료될 때 분할이 소멸됩니다.
- 구성이 변경될 때 활동을 다시 실행할 수 있으므로 분할을 만들거나 삭제하고 활동 경계가 변경되면 활동은 이전 인스턴스가 완전히 소멸되고 새 인스턴스가 생성되는 과정을 거칠 수 있습니다. 따라서 앱 개발자는 수명 주기 콜백에서 새 활동을 실행하는 등의 작업에 주의해야 합니다.
- 활동 삽입을 지원하려면 기기에 창 확장 프로그램 인터페이스가 포함되어야 합니다. Android 12L (API 수준 32) 이상을 실행하는 거의 모든 대형 화면 기기에는 이 인터페이스가 포함되어 있습니다. 그러나 여러 활동을 실행할 수 없는 일부 대형 화면 기기에는 창 확장 프로그램 인터페이스가 포함되어 있지 않습니다. 대형 화면 기기에서 멀티 윈도우 모드를 지원하지 않으면 활동 삽입을 지원하지 않을 수도 있습니다.
추가 리소스
- Codelab:
- 학습 개발자 과정: 활동 삽입
- 샘플 앱: activity-embedding