A incorporação de atividades otimiza apps em dispositivos de tela grande dividindo a janela de tarefa de um app em duas atividades ou duas instâncias.
Caso seu app seja composto por várias atividades, a incorporação permite melhorar a experiência do usuário em tablets, dobráveis e dispositivos ChromeOS.
A incorporação não requer refatoração do código. Para determinar como o app mostra as atividades (lado a lado ou empilhadas), crie um arquivo de configuração XML ou faça chamadas da API Jetpack WindowManager.
O suporte a telas pequenas é mantido automaticamente. Quando o app está em um dispositivo com uma tela pequena, as atividades são empilhadas uma sobre a outra. Em telas grandes, as atividades são mostradas lado a lado. O sistema determina a apresentação com base na configuração criada, e nenhuma lógica de ramificação é necessária.
A incorporação de atividades comporta mudanças de orientação do dispositivo e funciona perfeitamente em dispositivos dobráveis, empilhando e desempilhando atividades conforme o dispositivo é dobrado e aberto.
Essa incorporação pode ser feita na maioria dos dispositivos de tela grande que executam o Android 12L (nível 32 da API) e versões mais recentes.
Dividir janela de tarefas
A incorporação de atividades divide a janela de tarefas do app em dois contêineres: primário e secundário. Os contêineres guardam as atividades iniciadas pela atividade principal ou por outras atividades que já estão neles.
As atividades são empilhadas no contêiner secundário conforme são iniciadas, e ele é empilhado sobre o contêiner principal em telas pequenas. Portanto, o empilhamento de atividades e a navegação de retorno são consistentes com a ordem das atividades já integradas ao app.
A incorporação permite mostrar atividades de várias maneiras. O app pode dividir a janela de tarefas iniciando duas atividades lado a lado simultaneamente:
Ou então, uma atividade que ocupa toda a janela da tarefa pode criar uma divisão iniciando outra lado a lado:
As atividades que já estão em uma divisão e compartilhando uma janela de tarefas podem iniciar outras atividades das seguintes maneiras:
Ao lado, por cima da outra atividade:
Ao lado e com a divisão movida para a lateral, ocultando a atividade principal anterior:
A atividade é iniciada por cima, ou seja, na mesma pilha de atividades:
A atividade é iniciada em tela cheia na mesma tarefa:
Navegação de retorno
Diferentes tipos de aplicativos podem ter diferentes regras de navegação de retorno no estado de janela de uma tarefa dividida, dependendo das dependências entre as atividades ou de como os usuários acionam o evento de retorno, por exemplo:
- Juntas: se as atividades estão relacionadas e uma delas não é mostrada sem a outra, a navegação de retorno pode ser configurada para concluir as duas.
- Sozinhas: se as atividades são totalmente independentes, a navegação de retorno em uma atividade não afeta o estado de outra na janela de tarefas.
O evento de retorno é enviado para a última atividade em foco ao usar a navegação por botões. Com a navegação baseada em gestos, o evento "Voltar" é enviado à atividade em que o gesto ocorreu.
Layout com vários painéis
A API Jetpack WindowManager permite criar uma atividade que incorpora o layout de vários painéis em dispositivos de tela grande com o Android 12L (nível 32 da API) ou mais recente e em alguns dispositivos com versões anteriores da plataforma. Os apps que são baseados em várias atividades em vez de fragmentos ou layouts com base em visualização, como SlidingPaneLayout
, podem oferecer uma experiência do usuário melhor em telas grandes sem refatorar o código-fonte.
Um exemplo comum é uma divisão do painel de detalhes e lista. Para garantir uma apresentação de alta qualidade, o sistema inicia a atividade de lista e o app inicia imediatamente a atividade de detalhes. O sistema de transição aguarda até que as duas atividades sejam desenhadas e depois as mostra juntas. Para o usuário, as atividades são iniciadas como se fossem apenas uma.
Dividir atributos
É possível especificar como a janela da tarefa é proporcional entre os contêineres divididos e como eles são dispostos em relação aos outros.
Para as regras definidas em um arquivo de configuração XML, use os seguintes atributos:
splitRatio
: define as proporções do contêiner. O valor é um número de ponto flutuante no intervalo aberto (0,0, 1,0).splitLayoutDirection
: especifica como os contêineres de divisão são dispostos em relação aos outros. Os valores incluem:ltr
: da esquerda para a direitartl
: da direita para a esquerdalocale
: o uso deltr
ourtl
é determinado pela configuração de localidade
Confira os exemplos na seção Configuração de XML abaixo.
Para regras criadas usando as APIs WindowManager, crie um objeto SplitAttributes
com SplitAttributes.Builder
e chame estes métodos do builder:
setSplitType()
: define as proporções dos contêineres divididos. ConsulteSplitAttributes.SplitType
para ter acesso a argumentos válidos, incluindo o métodoSplitAttributes.SplitType.ratio()
.setLayoutDirection()
: define o layout dos contêineres. ConsulteSplitAttributes.LayoutDirection
para conferir os possíveis valores.
Consulte a API WindowManager abaixo para alguns exemplos.
Marcadores de posição
As atividades de marcador de posição são atividades secundárias vazias que ocupam uma área de divisão de atividade. Elas precisam ser substituídas por outra atividade que inclua conteúdo. Por exemplo, uma atividade de marcador de posição pode ocupar o lado secundário de uma divisão de atividades em um layout de detalhes da lista até um item da lista ser selecionado. Nesse momento, uma atividade contendo as informações detalhadas do item da lista selecionada substitui o marcador.
Por padrão, o sistema mostra marcadores somente quando há espaço suficiente para uma divisão de atividade. Os marcadores de posição são concluídos automaticamente quando o tamanho de exibição muda para uma largura ou altura muito pequena para mostrar uma divisão. Quando o espaço permitir, o sistema vai reiniciar o marcador com um estado reinicializado.
No entanto, o atributo stickyPlaceholder
de um método SplitPlaceholderRule
ou setSticky()
do SplitPlaceholder.Builder
pode substituir o comportamento padrão. Se o atributo ou método especifica um valor true
, o sistema mostra o marcador de posição como a principal atividade na janela de tarefas quando a exibição é redimensionada para uma tela de painel único a partir de uma tela de dois painéis. Confira um exemplo na seção Configuração dividida.
Mudanças no tamanho das janelas
Quando as mudanças na configuração do dispositivo reduzem a largura da janela de tarefas de tal modo que ela não seja grande o suficiente para um layout de vários painéis, as atividades não marcadoras de posição no painel secundário da janela de tarefas são empilhadas sobre as atividades no painel principal. Por exemplo, quando um dispositivo dobrável com tela grande do tamanho de um tablet é dobrado para o tamanho de um smartphone, ou quando a janela do app é redimensionada no modo de várias janelas.
As atividades de marcador de posição são exibidas apenas quando há espaço na tela de largura suficiente para uma divisão. Em telas menores, o marcador é dispensado automaticamente. Quando o espaço na tela se torna grande o suficiente, o marcador é recriado. Consulte Marcadores de posição acima.
O empilhamento da atividade é possível porque o WindowManager organiza as atividades no painel secundário na ordem z, acima das atividades no painel principal.
Várias atividades no painel secundário
A atividade B inicia a atividade C sem outras flags de intent:
Resultando na seguinte ordem z de atividades na mesma tarefa:
Portanto, em uma janela de tarefas menor, o aplicativo diminui para uma única atividade com C na parte de cima da pilha:
Voltar na janela menor é navegar pelas atividades empilhadas umas sobre as outras.
Se a configuração da janela de tarefas for restaurada para um tamanho maior que possa acomodar vários painéis, as atividades são exibidas lado a lado.
Divisões empilhadas
A atividade B inicia a atividade C na lateral e muda a divisão ao lado:
O resultado é a seguinte ordem z das atividades na mesma tarefa:
Em uma janela de tarefas menor, o aplicativo diminui para uma única atividade com C na parte de cima:
Orientação de retrato fixo
A configuração do manifesto android:screenOrientation permite que os apps restrinjam as atividades às orientações de retrato ou paisagem. Para melhorar a experiência do usuário em dispositivos de tela grande, como tablets e dobráveis, os fabricantes de dispositivos (OEMs) podem ignorar as solicitações de orientação da tela e colocar o app com efeito letterbox em orientação de retrato em telas no modo paisagem ou vice-versa.
Da mesma forma, quando a incorporação de atividades está ativada, os OEMs podem personalizar os dispositivos para mostrar atividades no modo retrato fixo com efeito letterbox na orientação de paisagem em telas grandes (largura ≥ 600 dp). Quando uma atividade no modo retrato fixo inicia uma outra, o dispositivo pode mostrar as duas atividades lado a lado em uma tela de dois painéis.
Sempre adicione a propriedade android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
ao arquivo de manifesto do app para informar aos dispositivos que o app é compatível com a incorporação de atividades (consulte Configurar divisões abaixo). Os dispositivos personalizados pelo OEM podem determinar se as atividades no modo retrato fixo vão ter efeito letterbox.
Configurar divisões
As regras de divisão configuram divisões de atividade. Você pode definir regras de divisão em um arquivo de configuração XML ou fazendo chamadas da API Jetpack WindowManager.
Nos dois casos, o app precisa acessar a biblioteca WindowManager e informar ao sistema que o app implementou a incorporação de atividades.
Faça o seguinte:
Adicione a dependência da biblioteca WindowManager mais recente ao arquivo
build.gradle
do módulo do app, por exemplo:implementation 'androidx.window:window:1.1.0-beta02'
A biblioteca WindowManager fornece todos os componentes necessários para a incorporação de atividades.
Informe ao sistema que o app implementou a incorporação de atividades.
Adicione a propriedade
android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
ao elemento <application> do arquivo de manifesto do app e defina o valor como "true", por exemplo:<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>
Na versão 1.1.0-alpha06 do WindowManager e mais recentes, as divisões de incorporação de atividades são desativadas, a menos que a propriedade seja adicionada ao manifesto e definida como verdadeira.
Além disso, os fabricantes de dispositivos usam a configuração para ativar recursos personalizados para apps com suporte à incorporação de atividades. Por exemplo, os dispositivos podem usar o efeito letterbox em uma atividade somente retrato em telas no modo paisagem para orientar na transição para um layout de dois painéis quando uma segunda atividade é iniciada. Consulte a seção Orientação fixa no modo retrato.
Configuração de XML
Para criar uma implementação de incorporação de atividade baseada em XML, siga estas etapas:
Crie um arquivo de recurso XML que faça o seguinte:
- Definir atividades que compartilham uma divisão.
- Configurar as opções de divisão.
- Criar um marcador de posição para o contêiner secundário de uma divisão quando o conteúdo não estiver disponível
- Especificar as atividades que nunca devem fazer parte de uma divisão.
Exemplo:
<!-- 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>
Crie um inicializador.
O componente
RuleController
da WindowManager analisa o arquivo de configuração XML e disponibiliza as regras para o sistema. UmInitializer
da biblioteca Startup do Jetpack disponibiliza o arquivo XML para oRuleController
na inicialização do app. Assim, as regras entram em vigor quando uma atividade é iniciada.Para criar um inicializador, faça o seguinte:
Adicione a dependência da biblioteca Startup mais recente ao seu arquivo
build.gradle
no módulo. Por exemplo:implementation 'androidx.startup:startup-runtime:1.1.1'
Crie uma classe que implemente a interface
Initializer
.O inicializador disponibiliza as regras de divisão para
RuleController
transmitindo o ID do arquivo de configuração XML (main_split_config.xml
) para o métodoRuleController.parseRules()
.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(); } }
Crie um provedor de conteúdo para as definições da regra.
Adicione
androidx.startup.InitializationProvider
ao arquivo de manifesto do app como um<provider>
. Inclua uma referência à implementação do inicializadorRuleController
,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>
O
InitializationProvider
descobre e inicializa oSplitInitializer
antes que o métodoonCreate()
do app seja chamado. Como resultado, as regras de divisão entram em vigor quando a atividade principal do app é iniciada.
API WindowManager
É possível implementar a incorporação de atividades de forma programática com algumas chamadas de API. Faça as chamadas no método onCreate()
de uma subclasse de Application
para garantir que as regras entrem em vigor antes do início de uma atividade.
Para criar uma divisão de atividade de forma programática, faça o seguinte:
Crie uma regra de divisão:
Crie um
SplitPairFilter
que identifique as atividades que compartilham a divisão: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 );
Adicione o filtro a um conjunto:
Kotlin
val filterSet = setOf(splitPairFilter)
Java
Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter);
Crie atributos de layout para a divisão:
Kotlin
val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build()
Java
final SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build();
O
SplitAttributes.Builder
cria um objeto que contém atributos de layout:setSplitType
: define como a área de exibição disponível é alocada para cada contêiner de atividade. O tipo de divisão especifica a proporção da área de exibição disponível alocada para o contêiner principal, enquanto o secundário ocupa o restante dessa área.setLayoutDirection
: especifica como os contêineres de atividade são dispostos em relação aos outros, sendo o contêiner principal o primeiro.
Crie um objeto
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();
O
SplitPairRule.Builder
cria e configura a regra:filterSet
: contém filtros de pares divididos que determinam quando aplicar a regra identificando atividades com a mesma divisão.setDefaultSplitAttributes
: aplica atributos de layout à regra.setMinWidthDp
: define a largura mínima de exibição (em pixels de densidade independente, dp) que permite uma divisão.setMinSmallestWidthDp
: define o valor mínimo (em dp) que a menor das duas dimensões de tela precisa ter para permitir uma divisão, seja qual for a orientação do dispositivo.setMaxAspectRatioInPortrait
: define a proporção máxima de exibição (altura:largura) na orientação retrato em que as divisões de atividade são mostradas. Se a proporção de uma tela no modo retrato for maior que o máximo permitido, as divisões serão desativadas independente da largura da tela. Observação: o valor padrão é 1,4, o que resulta em atividades ocupando toda a janela de tarefas na orientação retrato na maioria dos tablets. Consulte tambémSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
esetMaxAspectRatioInLandscape
. O valor padrão para o modo paisagem éALWAYS_ALLOW
.setFinishPrimaryWithSecondary
: define como a conclusão de todas as atividades no contêiner secundário afeta as atividades no contêiner principal.NEVER
indica que o sistema não deve finalizar as atividades principais quando todas as atividades no contêiner secundário terminarem. Consulte Finalizar atividades.setFinishSecondaryWithPrimary
: define como a finalização de todas as atividades no contêiner principal afeta as atividades no secundário.ALWAYS
indica que o sistema sempre deve finalizar as atividades no contêiner secundário quando todas as do principal terminarem. Consulte Finalizar atividades.setClearTop
: especifica se todas as atividades no contêiner secundário são finalizadas quando uma nova atividade é iniciada nele. "False" especifica que novas atividades são empilhadas sobre as que já estão no contêiner secundário.
Acesse à instância Singleton do
RuleController
da WindowManager e adicione a regra:Kotlin
val ruleController = RuleController.getInstance(this) ruleController.addRule(splitPairRule)
Java
RuleController ruleController = RuleController.getInstance(this); ruleController.addRule(splitPairRule);
Crie um marcador de posição para o contêiner secundário quando o conteúdo não estiver disponível:
Crie um
ActivityFilter
que identifique a atividade com que o marcador compartilha uma divisão de janela de tarefas:Kotlin
val placeholderActivityFilter = ActivityFilter( ComponentName(this, ListActivity::class.java), null )
Java
ActivityFilter placeholderActivityFilter = new ActivityFilter( new ComponentName(this, ListActivity.class), null );
Adicione o filtro a um conjunto:
Kotlin
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
Java
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter);
Crie um
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();
O
SplitPlaceholderRule.Builder
cria e configura a regra:placeholderActivityFilterSet
: contém filtros de atividade que determinam quando aplicar a regra identificando atividades associadas a ela.Intent
: especifica o início da atividade do marcador de posição.setDefaultSplitAttributes
: aplica atributos de layout à regra.setMinWidthDp
: define a largura mínima de exibição (em dp) que permite uma divisão.setMinSmallestWidthDp
: define o valor mínimo (em dp) que a menor das duas dimensões de tela precisa ter para permitir uma divisão, seja qual for a orientação do dispositivo.setMaxAspectRatioInPortrait
: define a proporção máxima de exibição (altura:largura) na orientação retrato em que as divisões de atividade são mostradas. Observação: o valor padrão é 1,4, o que resulta em atividades preenchendo a janela de tarefas na orientação retrato na maioria dos tablets. Consulte tambémSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
esetMaxAspectRatioInLandscape
. O valor padrão para o modo paisagem éALWAYS_ALLOW
.setFinishPrimaryWithPlaceholder
: define como a finalização da atividade do marcador de posição afeta as atividades no contêiner principal. "ALWAYS" indica que o sistema sempre deve finalizar as atividades no contêiner principal ao término do marcador de posição. Consulte Finalizar atividades.setSticky
: determina se a atividade do marcador de posição aparece na parte de cima da pilha em telas pequenas quando o marcador é mostrado pela primeira vez em uma divisão com largura mínima suficiente.
Adicione a regra ao
RuleController
da WindowManager:Kotlin
ruleController.addRule(splitPlaceholderRule)
Java
ruleController.addRule(splitPlaceholderRule);
Especifique as atividades que nunca devem fazer parte de uma divisão:
Crie um
ActivityFilter
que identifique uma atividade que sempre vai ocupar toda a área de tela da tarefa:Kotlin
val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null )
Java
ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null );
Adicione o filtro a um conjunto:
Kotlin
val expandedActivityFilterSet = setOf(expandedActivityFilter)
Java
Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter);
Crie uma interface
ActivityRule
:Kotlin
val activityRule = ActivityRule.Builder(expandedActivityFilterSet) .setAlwaysExpand(true) .build()
Java
ActivityRule activityRule = new ActivityRule.Builder( expandedActivityFilterSet ).setAlwaysExpand(true) .build();
O
ActivityRule.Builder
cria e configura a regra:expandedActivityFilterSet
: contém filtros de atividade que determinam quando aplicar a regra identificando as atividades que você quer excluir das divisões.setAlwaysExpand
: especifica se a atividade vai preencher toda a janela de tarefas.
Adicione a regra ao
RuleController
da WindowManager:Kotlin
ruleController.addRule(activityRule)
Java
ruleController.addRule(activityRule);
Incorporação entre apps
No Android 13 (nível 33 da API) e em versões mais recentes, os apps podem incorporar atividades de outros apps. A incorporação de atividades entre aplicativos, ou entre UIDs, permite a integração visual de atividades de vários apps Android. O sistema mostra uma atividade do app host e uma atividade incorporada de outro app na tela lado a lado ou na parte de cima e de baixo, assim como na incorporação de atividades em um único app.
Por exemplo, o app Configurações pode incorporar a atividade do seletor de plano de fundo do app WallpaperPicker:
Modelo de confiança
Os processos do host que incorporam atividades de outros apps podem redefinir a apresentação das atividades incorporadas, incluindo tamanho, posição, corte e transparência. Hosts maliciosos podem usar esse recurso para enganar os usuários e criar clickjacking (link em inglês) ou outros ataques de correção da interface.
Para evitar o uso indevido da incorporação de atividades entre apps, o Android exige que os apps ativem esse recurso. Os apps podem designar hosts como confiáveis ou não confiáveis.
Hosts confiáveis
Para permitir que outros aplicativos incorporem e controlem totalmente a exibição de atividades do seu app, especifique o certificado SHA-256 do aplicativo host no atributo android:knownActivityEmbeddingCerts
dos elementos <activity>
ou <application>
do arquivo de manifesto do app.
Defina o valor de android:knownActivityEmbeddingCerts
como uma string:
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
... />
Se preferir, para especificar vários certificados, use uma matriz de strings:
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
... />
que faz referência a um recurso como este:
<resources>
<string-array name="known_host_certificate_digests">
<item>cert1</item>
<item>cert2</item>
...
</string-array>
</resources>
Os proprietários de apps podem receber um resumo de certificado SHA executando a tarefa signingReport
do Gradle. A síntese do certificado é a impressão digital SHA-256 sem os dois-pontos de separação. Para mais informações, consulte Executar um relatório de assinatura e Como autenticar seu cliente.
Hosts não confiáveis
Para permitir que qualquer app incorpore as atividades do seu app e controle a apresentação dele, especifique o atributo android:allowUntrustedActivityEmbedding
nos elementos <activity>
ou <application>
no manifesto do app, por exemplo:
<activity
android:name=".MyEmbeddableActivity"
android:allowUntrustedActivityEmbedding="true"
... />
O valor padrão do atributo é falso, o que impede a incorporação de atividades entre apps.
Autenticação personalizada
Para diminuir os riscos da incorporação não confiável de atividades, crie um mecanismo de autenticação personalizado que verifique a identidade do host. Se você conhecer os certificados do host, use a biblioteca androidx.security.app.authenticator
para autenticar. Se o host for autenticado depois de incorporar sua atividade, você vai poder mostrar o conteúdo real. Caso contrário, informe ao usuário que a ação não foi permitida e bloqueie o conteúdo.
Use o método ActivityEmbeddingController#isActivityEmbedded()
da biblioteca WindowManager do Jetpack para verificar se um host está incorporando sua atividade, por exemplo:
Kotlin
fun isActivityEmbedded(activity: Activity): Boolean { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity) }
Java
boolean isActivityEmbedded(Activity activity) { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity); }
Restrição de tamanho mínimo
O sistema Android aplica a altura e a largura mínimas especificadas no elemento <layout>
do manifesto do app a atividades incorporadas. Se um aplicativo não especificar a altura e a largura mínimas, os valores padrão do sistema não vão ser aplicados (sw220dp
).
Se o host tentar redimensionar o contêiner incorporado para um tamanho menor que o mínimo, ele vai ser expandido para ocupar todos os limites da tarefa.
<activity-alias>
Para que a incorporação de atividades confiáveis ou não confiáveis funcione com o elemento <activity-alias>
, a propriedade android:knownActivityEmbeddingCerts
ou android:allowUntrustedActivityEmbedding
precisa ser aplicada à atividade de destino em vez do alias. A política que verifica a segurança no servidor do sistema é baseada nas sinalizações definidas no destino, não no alias.
Aplicativo host
Os aplicativos host implementam a incorporação de atividades entre apps da mesma maneira que a incorporação de atividades em apps únicos. Os objetos SplitPairRule
e SplitPairFilter
ou ActivityRule
e ActivityFilter
especificam atividades incorporadas e divisões da janela de tarefas. As regras de divisão são definidas estaticamente em XML ou durante a execução usando chamadas da API WindowManager do Jetpack.
Se um aplicativo host tentar incorporar uma atividade que não tenha ativado esse recurso, a atividade vai ocupar todos os limites da tarefa. Como resultado, os aplicativos host precisam saber se as atividades de destino permitem a incorporação entre apps.
Se uma atividade incorporada iniciar uma nova atividade na mesma tarefa que não ativou a incorporação entre apps, ela vai ocupar todos os limites da tarefa em vez de sobrepor a atividade no contêiner incorporado.
Um aplicativo host pode incorporar as próprias atividades sem restrições, desde que as atividades sejam iniciadas na mesma tarefa.
Exemplos de divisão
Dividir da tela cheia
Nenhuma refatoração é necessária. É possível definir a configuração da divisão
de forma estática ou no momento da execução e, em seguida, chamar
Context#startActivity()
sem outros parâmetros.
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Dividir por padrão
Quando a página de destino de um aplicativo é projetada para ser dividida em dois contêineres em telas grandes, a experiência do usuário é melhor quando ambas as atividades são criadas e apresentadas de forma simultânea. No entanto, o conteúdo pode não estar disponível para o contêiner secundário da divisão até que o usuário interaja com a atividade no contêiner principal, por exemplo, quando o usuário seleciona um item em um menu de navegação. Uma atividade de marcador de posição pode preencher o espaço vazio até que o conteúdo possa ser mostrado no contêiner secundário da divisão. Consulte Marcadores de posição acima.
Para criar uma divisão com um marcador, crie um e o associe à atividade principal:
<SplitPlaceholderRule
window:placeholderActivityName=".PlaceholderActivity">
<ActivityFilter
window:activityName=".MainActivity"/>
</SplitPlaceholderRule>
Divisão de link direto
Quando um app recebe uma intent, a atividade de destino pode ser mostrada como a parte secundária de uma divisão de atividades. Por exemplo, um pedido para mostrar uma tela de detalhes com informações sobre um item de uma lista; Em telas pequenas, os detalhes são mostrados em toda a janela de tarefas. Em dispositivos maiores, ao lado da lista.
O pedido de inicialização precisa ser encaminhado para a atividade principal, e a atividade de detalhes de destino deve ser iniciada em uma divisão. O sistema escolhe automaticamente a apresentação correta (empilhada ou lado a lado) com base na largura de tela disponível.
Kotlin
override fun onCreate(savedInstanceState Bundle?) { . . . RuleController.getInstance(this) .addRule(SplitPairRule.Builder(filterSet).build()) startActivity(Intent(this, DetailActivity::class.java)) }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { . . . RuleController.getInstance(this) .addRule(new SplitPairRule.Builder(filterSet).build()); startActivity(new Intent(this, DetailActivity.class)); }
O destino do link direto pode ser a única atividade que precisa estar disponível para o usuário na pilha de navegação de retorno. Você pode evitar a ação de dispensar a atividade dos detalhes e deixar apenas a atividade principal:
Em vez disso, é possível finalizar as duas atividades ao mesmo tempo usando o
atributo finishPrimaryWithSecondary
:
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".ListActivity"
window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>
Consulte Atributos de configuração abaixo.
Várias atividades em contêineres divididos
O empilhamento de várias atividades em um contêiner dividido permite que os usuários acessem conteúdo localizado em partes diferentes. Por exemplo, com uma divisão dos detalhes da lista, o usuário pode precisar acessar uma seção de subdetalhes, mas manter a atividade principal no lugar:
Kotlin
class DetailActivity { . . . fun onOpenSubDetail() { startActivity(Intent(this, SubDetailActivity::class.java)) } }
Java
public class DetailActivity { . . . void onOpenSubDetail() { startActivity(new Intent(this, SubDetailActivity.class)); } }
A atividade de subdetalhes é posicionada acima da atividade detalhada, que fica ocultada:
O usuário pode voltar ao nível de detalhes anterior navegando de volta pela pilha:
As atividades se empilham umas sobre as outras como comportamento padrão quando são iniciadas de uma atividade no mesmo contêiner secundário. Atividades iniciadas do contêiner principal em uma divisão ativa também acabam no contêiner secundário, na parte de cima da pilha de atividades.
Atividades em uma nova tarefa
Quando as atividades em uma janela de tarefas dividida iniciam atividades em uma nova tarefa, a nova tarefa é separada da tarefa que inclui a divisão e preenche a janela toda. A tela "Recentes" mostra duas tarefas: uma na divisão e a nova tarefa.
Substituir atividades
As atividades podem ser substituídas na pilha de contêineres secundária. Por exemplo, quando a atividade principal é usada para a navegação de nível mais alto e a atividade secundária é um destino selecionado. Cada seleção da navegação de nível superior precisa iniciar uma nova atividade no contêiner secundário e remover as atividades que estavam lá anteriormente.
Se o app não finaliza a atividade no contêiner secundário quando a seleção de navegação mudar, a navegação de retorno pode ficar confusa quando a divisão é recolhida (quando o dispositivo está dobrado). Por exemplo, se você tiver um menu no painel principal e telas A e B empilhadas no painel secundário, quando o usuário dobrar o smartphone, B vai estar em cima de A e A em cima do menu. Ao navegar de volta de B, o usuário vê A em vez do menu.
Nesses casos, é necessário remover a tela A da backstack.
O comportamento padrão ao iniciar uma atividade na lateral em um novo contêiner acima de uma
divisão existente é colocar os novos contêineres secundários na parte de cima e manter
os antigos na backstack. É possível configurar as divisões para limpar os contêineres secundários anteriores
com clearTop
e iniciar novas atividades normalmente.
<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))) } }
Java
public class MenuActivity { . . . void onMenuItemSelected(int selectedMenuItem) { startActivity(new Intent(this, classForItem(selectedMenuItem))); } }
Como alternativa, use a mesma atividade secundária e envie novas intents da atividade principal (menu) que sejam resolvidas para a mesma instância, mas acionem uma atualização de estado ou de interface no contêiner secundário.
Várias divisões
Os apps podem oferecer navegação em vários níveis iniciando outras atividades na lateral.
Quando uma atividade em um contêiner secundário inicia uma nova atividade ao lado, uma nova divisão é criada sobre a divisão existente.
A pilha de retorno contém todas as atividades que foram abertas anteriormente para que os usuários possam navegar para a divisão A/B após a finalização de C.
Para criar uma nova divisão, inicie a nova atividade na lateral pelo contêiner secundário existente. Declare as configurações para as divisões A/B e B/C, e inicie a atividade C normalmente pela B:
<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)) } }
Java
public class B { . . . void onOpenC() { startActivity(new Intent(this, C.class)); } }
Reagir a mudanças de estado da divisão
Atividades diferentes em um app podem ter elementos da interface que executam a mesma função. Por exemplo, um controle que abre uma janela contendo as configurações da conta.
Se duas atividades que têm um elemento da interface em comum estão divididas, é redundante e pode ser confuso mostrar o elemento nas duas atividades.
Para saber quando as atividades estão em uma divisão, verifique o fluxo de SplitController.splitInfoList
ou registre um listener com um SplitControllerCallbackAdapter
e confira as mudanças no estado da divisão. Em seguida, ajuste a interface de forma adequada:
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 } } }
Java
@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); }); }
As corrotinas podem ser iniciadas em qualquer estado do ciclo de vida, mas isso geralmente acontece no estado STARTED
para conservar recursos. Consulte Usar corrotinas do Kotlin com componentes que reconhecem o ciclo de vida para saber mais.
É possível fazer callbacks em qualquer estado do ciclo de vida, inclusive quando uma atividade é
interrompida. Os listeners geralmente precisam ficar registrados em onStart()
e
não registrados em onStop()
.
Modal em tela cheia
Algumas atividades impedem que os usuários interajam com o aplicativo até que uma ação especificada seja realizada. Por exemplo, uma atividade de tela de login, uma tela de confirmação de política ou uma mensagem de erro. As atividades modais precisam ser impedidas de aparecer em uma divisão.
Uma atividade pode ser forçada a sempre preencher a janela de tarefas usando a configuração de expansão:
<ActivityRule
window:alwaysExpand="true">
<ActivityFilter
window:activityName=".FullWidthActivity"/>
</ActivityRule>
Finalizar atividades
Os usuários podem finalizar atividades em qualquer lado da divisão deslizando a partir da borda da tela:
Se o dispositivo estiver configurado para usar o botão "Voltar" em vez da navegação por gestos, a entrada será enviada para a atividade em foco, ou seja, a atividade que foi tocada ou iniciada por último.
O efeito que a finalização de todas as atividades de um contêiner tem no contêiner oposto depende da configuração de divisão.
Atributos de configuração
É possível especificar atributos de regra de par dividido para configurar como a conclusão de todas as atividades em um lado da divisão afeta as atividades do outro lado da divisão. Os atributos são:
window:finishPrimaryWithSecondary
: como a conclusão de todas as atividades no contêiner secundário afeta as atividades no contêiner principalwindow:finishSecondaryWithPrimary
: como a conclusão de todas as atividades no contêiner principal afeta as atividades no contêiner secundário
Os valores possíveis dos atributos incluem:
always
: sempre conclui as atividades no contêiner associadonever
: nunca conclui as atividades no contêiner associadoadjacent
: conclui as atividades no contêiner associado quando os dois contêineres são mostrados lado a lado, mas não quando estão empilhados
Exemplo:
<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>
Configuração padrão
Quando todas as atividades em um contêiner de uma divisão terminarem, o contêiner restante vai ocupar a janela inteira:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Finalizar atividades em conjunto
Finalize as atividades no contêiner principal de forma automática quando todas as do contêiner secundário forem concluídas:
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Finalize as atividades no contêiner secundário automaticamente quando todas as do contêiner principal forem concluídas:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Finalize as atividades ao mesmo tempo quando todas as do contêiner principal ou secundário forem concluídas:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Finalizar várias atividades em contêineres
Se várias atividades forem empilhadas em um contêiner dividido, finalizar uma atividade na parte de baixo da pilha não conclui automaticamente as atividades na parte de cima.
Por exemplo, se duas atividades estiverem no contêiner secundário, C em cima de B:
E a configuração da divisão é definida pelas definições das atividades A e B:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
A conclusão da atividade de cima mantém a divisão.
Concluir a atividade de baixo (raiz) do contêiner secundário não remove as atividades acima dele, e assim, retém a divisão.
Todas as outras regras para finalizar as atividades em conjunto, por exemplo, a conclusão da atividade secundária com a primária, também são executadas:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
E quando a divisão estiver configurada para finalizar as partes primária e secundária juntas:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Mudar propriedades de divisão no momento da execução
Não é possível mudar as propriedades de uma divisão atualmente ativa e visível. Mudar as regras de divisão afeta a inicialização de outras atividades e novos contêineres, mas não divisões existentes e ativas.
Para mudar as propriedades de divisões ativas, conclua a atividade lateral ou as atividades na divisão e reinicie para a lateral novamente com uma nova configuração.
Extrair uma atividade de uma divisão para tela cheia
Crie uma nova configuração que mostre a tela cheia com a atividade lateral e, em seguida, reinicie a atividade com uma intent que é resolvida na mesma instância.
Conferir o suporte para divisão no momento da execução
A incorporação de atividades pode ser realizada no Android 12L (nível 32 da API) e versões mais recentes. No entanto, ela também está disponível em alguns dispositivos com versões anteriores da plataforma. Para conferir a disponibilidade do recurso no momento da execução, use a propriedade SplitController.splitSupportStatus
ou o método SplitController.getSplitSupportStatus()
:
Kotlin
if (SplitController.getInstance(this).splitSupportStatus == SplitController.SplitSupportStatus.SPLIT_AVAILABLE) { // Device supports split activity features. }
Java
if (SplitController.getInstance(this).getSplitSupportStatus() == SplitController.SplitSupportStatus.SPLIT_AVAILABLE) { // Device supports split activity features. }
Se as divisões não estiverem disponíveis, as atividades serão iniciadas na parte de cima da pilha, seguindo o modelo de incorporação que não é de atividade.
Impedir a substituição pelo sistema
Os fabricantes de dispositivos Android (fabricantes de equipamentos originais, ou OEMs) podem implementar a incorporação de atividades como uma função do sistema do dispositivo. O sistema especifica regras de divisão para apps com várias atividades e substitui o comportamento de janelamento. A substituição do sistema força os apps com várias atividades a entrar em um modo de incorporação definido pelo sistema.
Esse tipo de incorporação de atividades pode melhorar a apresentação do app com o uso de layouts de vários painéis, como list-detail, sem realizar nenhuma mudança no app. No entanto, ela também pode gerar layouts de app incorretos, bugs ou conflitos.
O app pode impedir ou permitir a incorporação de atividades do sistema definindo uma propriedade no arquivo de manifesto do app, por exemplo:
<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>
O nome da propriedade é definido no objeto WindowProperties
da API Jetpack WindowManager.
Defina o valor como false
se o app implementar a incorporação de atividades ou se você
quiser impedir que o sistema use as regras
da incorporação. Defina como true
para permitir o uso
da incorporação de atividades definida pelo sistema.
Limitações, restrições e advertências
- Apenas o app host da tarefa, que é identificado como o proprietário da atividade raiz, pode organizar e incorporar outras atividades à tarefa. Se as atividades que oferecem suporte a divisões e incorporação forem executadas em uma tarefa que pertença a um aplicativo diferente, as incorporações e divisões não funcionarão para essas atividades.
- As atividades só podem ser organizadas em uma tarefa. Iniciar uma atividade em uma nova tarefa sempre a coloca em uma nova janela expandida fora das divisões existentes.
- Apenas atividades no mesmo processo podem ser organizadas e colocadas em uma divisão. O callback
SplitInfo
só informa atividades que pertencem ao mesmo processo, já que não há como saber sobre atividades em processos diferentes. - Cada par ou regra de atividade única se aplica apenas para atividades iniciadas após a regra ser registrada. Atualmente, não há como atualizar as divisões existentes ou as propriedades visuais delas.
- A configuração do filtro de par dividido precisa corresponder às intents usadas ao iniciar completamente as atividades. A correspondência ocorre no momento em que uma nova atividade é iniciada do processo do aplicativo. Por isso, ela pode não saber sobre os nomes de componentes que são resolvidos mais tarde no processo do sistema ao usar intents implícitas. Se o nome de um componente não for conhecido no momento da inicialização, um caractere curinga pode ser usado (“*/*”) e a filtragem pode ser realizada com base na ação da intent.
- Atualmente, não há como mover atividades entre contêineres ou para dentro e fora de divisões após serem criadas. As divisões são criadas pela biblioteca WindowManager apenas quando novas atividades com regras correspondentes são iniciadas, e as divisões são destruídas quando a última atividade em um contêiner de divisão é finalizada.
- As atividades podem ser reiniciadas quando a configuração é alterada. Portanto, quando uma divisão é criada ou removida e os limites de atividades mudam, a atividade pode passar pela destruição completa da instância anterior e criação da nova. Como resultado, os desenvolvedores de apps precisam ter cuidado, por exemplo, com a inicialização de novas atividades usando callbacks do ciclo de vida.
- Os dispositivos precisam incluir a interface de extensões de janela para oferecer suporte à incorporação de atividades. Quase todos os dispositivos de tela grande que executam o Android 12L (nível 32 da API) ou versões mais recentes incluem a interface. No entanto, alguns dispositivos de tela grande que não são capazes de executar várias atividades não incluem a interface de extensões de janela. Se um dispositivo de tela grande não oferece suporte para o modo de várias janelas, ele pode não realizar a incorporação de atividades.
Outros recursos
- Codelab: Criar um layout de detalhes e listas com incorporação de atividades e do Material Design
- Programa de aprendizado — Incorporação de atividades
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Criar um layout de detalhes e listas com incorporação de atividades e do Material Design
- Modo de compatibilidade do dispositivo
- Suporte ao modo de várias janelas