Configurar a entrega sob demanda

Os módulos de recursos permitem separar determinados recursos do módulo básico do app e incluí-los no pacote de apps. Com o Play Feature Delivery, o usuário pode fazer o download desses componentes sob demanda e instalá-los depois da instalação do APK básico do app.

Por exemplo, imagine um app de mensagens de texto com a funcionalidade de capturar e enviar mensagens com imagens, mas apenas uma pequena porcentagem de usuários envia esse tipo de mensagem. Pode ser interessante incluir o envio de mensagens com imagens como um módulo de recursos disponível para download. Dessa forma, o download inicial do app será menor para todos os usuários, e apenas os usuários que enviarem mensagens com imagens precisarão fazer o download desse componente extra.

Esse tipo de modularização é mais trabalhoso e pode exigir a refatoração do código existente do app. Portanto, analise atentamente quais recursos do seu app seriam mais beneficiados com a disponibilização para usuários sob demanda. Para entender melhor as diretrizes e os casos de uso mais indicados para recursos sob demanda, leia Práticas recomendadas de UX para entrega on demand.

Para modularizar gradativamente os recursos do app ao longo do tempo sem ativar opções avançadas de entrega, como a entrega sob demanda, configure a entrega no momento da instalação.

Esta página ajuda você a adicionar um módulo de recursos ao seu projeto de app e configurá-lo para entrega sob demanda. Antes de começar, verifique se você está usando o Android Studio 3.5 ou uma versão mais recente e o Plug-in do Android para o Gradle 3.5.0 ou mais recente.

Configurar um novo módulo para entrega sob demanda

A maneira mais fácil de criar um novo módulo de recursos é usando o Android Studio 3.5 ou uma versão mais recente. Como os módulos de recursos têm uma dependência inerente no módulo básico, só é possível adicioná-los a projetos de apps existentes.

Para adicionar um módulo de recursos ao projeto do app usando o Android Studio, faça o seguinte:

  1. Abra o projeto do app no ambiente de desenvolvimento integrado.
  2. Selecione File > New > New Module na barra de menus.
  3. Na caixa de diálogo Create New Module, selecione Dynamic Feature Module e clique em Next.
  4. Na seção Configure your new module, faça o seguinte:
    1. No menu suspenso, selecione a opção Base application module para o projeto do seu app.
    2. Especifique um nome para o módulo em Module name. O ambiente de desenvolvimento integrado usa esse nome para identificar o módulo como um subprojeto do Gradle no arquivo de configurações do Gradle. Quando o pacote de apps é criado, o Gradle usa o último elemento do nome do subprojeto para inserir o atributo <manifest split> no manifesto do módulo de recursos.
    3. Especifique o nome do pacote do módulo. Por padrão, o Android Studio sugere um nome de pacote que combina o nome do pacote raiz do módulo base com o nome do módulo especificado na etapa anterior.
    4. Selecione uma opção em Minimum API level para definir o nível mínimo de API com que o módulo será compatível. Esse valor deve corresponder ao do módulo básico.
  5. Clique em Next.
  6. Na seção Module Download Options, faça o seguinte:

    1. Use a opção Module title para especificar o título do módulo em até 50 caracteres. A plataforma usa esse título para identificar o módulo para o usuário, por exemplo, durante uma confirmação se o usuário quer fazer o download do módulo. Por isso, o módulo base do app precisa incluir o título do módulo como um recurso de string, que você pode traduzir. Durante a criação do módulo usando o Android Studio, o ambiente de desenvolvimento integrado adiciona o recurso de string ao módulo base para você e injeta a seguinte entrada no manifesto do módulo de recursos:

      <dist:module
          ...
          dist:title="@string/feature_title">
      </dist:module>
      
    2. No menu suspenso em Install-time inclusion, selecione Do not include module at install-time. O Android Studio injeta o seguinte no manifesto do módulo para refletir sua escolha:

      <dist:module ... >
        <dist:delivery>
            <dist:on-demand/>
        </dist:delivery>
      </dist:module>
      
    3. Marque a caixa ao lado de Fusing se você quiser que esse módulo esteja disponível para dispositivos com Android 4.4 (API de nível 20) e anterior e seja incluído em vários APKs. Isso significa que você pode ativar o comportamento sob demanda para esse módulo e desativar a fusão para omiti-lo em dispositivos que não são compatíveis com o download e a instalação de APKs divididos. O Android Studio injeta o seguinte no manifesto do módulo para refletir sua escolha:

      <dist:module ...>
          <dist:fusing dist:include="true | false" />
      </dist:module>
      
  7. Clique em Finish.

Quando o Android Studio terminar de criar seu módulo, inspecione o conteúdo no painel Project (selecione View > Tool Windows > Project na barra de menus). O código, os recursos e a organização padrão precisam ser semelhantes aos do módulo padrão do app.

Em seguida, será necessário implementar a funcionalidade de instalação on demand usando a Biblioteca Play Core.

Incluir a Play Core Library no seu projeto

Antes de começar, adicione a Biblioteca Play Core ao seu projeto.

Solicitar um módulo on demand

Quando seu app precisar usar um módulo de recurso, ele poderá solicitar um enquanto estiver em primeiro plano com a classe SplitInstallManager. Ao fazer uma solicitação, seu app precisa especificar o nome do módulo, conforme definido pelo elemento split no manifesto do módulo de destino. Quando você cria um módulo de recurso usando o Android Studio, o sistema de compilação usa o nome do módulo fornecido para injetar essa propriedade no manifesto do módulo no tempo de compilação. Para ver mais informações, leia sobre os manifestos do módulo de recursos.

Por exemplo, imagine um app que tenha um módulo sob demanda para capturar e enviar mensagens gráficas usando a câmera do dispositivo, e esse módulo sob demanda especifica split="pictureMessages" no manifesto. O exemplo a seguir usa SplitInstallManager para solicitar o módulo pictureMessages (com um módulo adicional para alguns filtros promocionais):

Kotlin

// Creates an instance of SplitInstallManager.
val splitInstallManager = SplitInstallManagerFactory.create(context)

// Creates a request to install a module.
val request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build()

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener { sessionId -> ... }
    .addOnFailureListener { exception ->  ... }

Java

// Creates an instance of SplitInstallManager.
SplitInstallManager splitInstallManager =
    SplitInstallManagerFactory.create(context);

// Creates a request to install a module.
SplitInstallRequest request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build();

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener(sessionId -> { ... })
    .addOnFailureListener(exception -> { ... });

Quando seu app solicita um módulo sob demanda, a biblioteca Play Core usa uma estratégia "disparar e esquecer". Isto é, ela envia a solicitação para fazer o download do módulo para a plataforma, mas não monitora se a instalação foi bem-sucedida. Para avançar a jornada do usuário após a instalação ou manipular adequadamente os erros, monitore o estado da solicitação.

Observação: não há problema em solicitar um módulo de recurso que já esteja instalado no dispositivo. A API considerará instantaneamente que a solicitação foi concluída se detectar que o módulo já está instalado. Além disso, depois que um módulo é instalado, o Google Play o mantém atualizado automaticamente. Ou seja, quando você faz upload de uma nova versão do seu pacote de apps, a plataforma atualiza todos os APKs instalados que pertencem ao seu app. Para saber mais, leia Gerenciar atualizações do app.

Para ter acesso ao código e aos recursos do módulo, seu app precisa ativar a biblioteca SplitCompat. A SplitCompat não é necessária para os Instant Apps Android.

Adiar a instalação de módulos sob demanda

Se você não precisar que o app faça o download de um módulo sob demanda e o instale imediatamente, poderá adiar a instalação para quando o app estiver em segundo plano. Um exemplo disso é se você quiser pré-carregar algum material promocional para uma versão mais recente do app.

Você pode especificar um módulo para download posterior usando o método deferredInstall(), conforme mostrado abaixo. E, diferentemente de SplitInstallManager.startInstall(), seu app não precisa estar em primeiro plano para iniciar uma solicitação de instalação adiada.

Kotlin

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(listOf("promotionalFilters"))

Java

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(Arrays.asList("promotionalFilters"));

As solicitações de instalações adiadas são mais eficazes, e não é possível acompanhar o progresso delas. Portanto, antes de tentar acessar um módulo especificado para a instalação adiada, você precisa verificar se o módulo foi instalado. Se você precisar que o módulo esteja disponível imediatamente, use SplitInstallManager.startInstall() para solicitá-lo, conforme mostrado na seção anterior.

Monitorar o estado da solicitação

Para atualizar uma barra de progresso, acionar uma intent após a instalação ou manipular adequadamente um erro de solicitação, você precisa detectar as atualizações de estado da tarefa SplitInstallManager.startInstall() assíncrona. Antes de começar a receber atualizações para sua solicitação de instalação, registre um listener e acesse o ID da sessão para a solicitação, conforme mostrado abaixo.

Kotlin

// Initializes a variable to later track the session ID for a given request.
var mySessionId = 0

// Creates a listener for request status updates.
val listener = SplitInstallStateUpdatedListener { state ->
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
}

// Registers the listener.
splitInstallManager.registerListener(listener)

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener { sessionId -> mySessionId = sessionId }
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener { exception ->
        // Handle request errors.
    }

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener)

Java

// Initializes a variable to later track the session ID for a given request.
int mySessionId = 0;

// Creates a listener for request status updates.
SplitInstallStateUpdatedListener listener = state -> {
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
};

// Registers the listener.
splitInstallManager.registerListener(listener);

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener(sessionId -> { mySessionId = sessionId; })
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener(exception -> {
        // Handle request errors.
    });

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener);

Gerenciar erros de solicitação

Lembre-se de que a instalação sob demanda de módulos de recursos pode falhar, assim como a instalação de apps nem sempre é bem-sucedida. A falha na instalação pode ocorrer devido a problemas como baixo armazenamento do dispositivo, falta de conectividade de rede ou o usuário não ter feito login na Google Play Store. Para ver sugestões sobre como lidar com essas situações de maneira eficiente para o usuário, confira nossas diretrizes de UX para entrega sob demanda.

Em relação ao código, é possível resolver falhas no download ou na instalação de um módulo usando addOnFailureListener(), conforme mostrado abaixo.

Kotlin

splitInstallManager
    .startInstall(request)
    .addOnFailureListener { exception ->
        when ((exception as SplitInstallException).errorCode) {
            SplitInstallErrorCode.NETWORK_ERROR -> {
                // Display a message that requests the user to establish a
                // network connection.
            }
            SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> checkForActiveDownloads()
            ...
        }
    }

fun checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .sessionStates
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                // Check for active sessions.
                for (state in task.result) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        }
}

Java

splitInstallManager
    .startInstall(request)
    .addOnFailureListener(exception -> {
        switch (((SplitInstallException) exception).getErrorCode()) {
            case SplitInstallErrorCode.NETWORK_ERROR:
                // Display a message that requests the user to establish a
                // network connection.
                break;
            case SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED:
                checkForActiveDownloads();
            ...
    });

void checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .getSessionStates()
        .addOnCompleteListener( task -> {
            if (task.isSuccessful()) {
                // Check for active sessions.
                for (SplitInstallSessionState state : task.getResult()) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        });
}

A tabela abaixo descreve os estados de erro que seu app pode precisar gerenciar.

Código do erro Descrição Ação sugerida
ACTIVE_SESSIONS_LIMIT_EXCEEDED A solicitação foi rejeitada porque o download de pelo menos uma solicitação está sendo feito no momento. Verifique se há algum download de uma solicitação que ainda esteja sendo feito, conforme mostrado no exemplo acima.
MODULE_UNAVAILABLE O Google Play não consegue encontrar o módulo solicitado com base na versão atual instalada do app, dispositivo e conta do Google Play do usuário. Se o usuário não tiver acesso ao módulo, notifique-o.
INVALID_REQUEST O Google Play recebeu a solicitação, mas ela não é válida. Verifique se as informações incluídas na solicitação estão completas e precisas.
SESSION_NOT_FOUND Uma sessão para um determinado ID de sessão não foi encontrada. Se você estiver tentando monitorar o estado de uma solicitação pelo ID da sessão, verifique se o ID da sessão está correto.
API_NOT_AVAILABLE A biblioteca Play Core não é compatível com o dispositivo atual. Ou seja, o dispositivo não pode fazer o download de recursos e instalá-los sob demanda. Para dispositivos com o Android 4.4 (nível da API 20) ou anterior, você precisa incluir módulos de recursos no momento da instalação usando a propriedade de manifesto dist:fusing. Para saber mais, leia o manifesto do módulo de recursos.
NETWORK_ERROR A solicitação falhou devido a um erro de rede. Peça que o usuário estabeleça uma conexão de rede ou mude para uma rede diferente.
ACCESS_DENIED O app não pode registrar a solicitação devido a permissões insuficientes. Isso normalmente ocorre quando o app está em segundo plano. Tente fazer a solicitação quando o app retornar ao primeiro plano.
INCOMPATIBLE_WITH_EXISTING_SESSION A solicitação contém um ou mais módulos que já foram solicitados, mas ainda não foram instalados. Crie uma nova solicitação que não inclua módulos já solicitados pelo seu app ou aguarde até que todos os módulos solicitados no momento sejam instalados antes de tentar novamente a solicitação.

Lembre-se de que a solicitação de um módulo que já foi instalado não é resolvida em um erro.

SERVICE_DIED O serviço responsável pelo processamento da solicitação foi eliminado. Tente fazer a solicitação novamente.

Seu SplitInstallStateUpdatedListener recebe um SplitInstallSessionState com esse código de erro, status FAILED e ID da sessão -1.

INSUFFICIENT_STORAGE O dispositivo não tem espaço de armazenamento livre suficiente para instalar o módulo de recursos. Informe ao usuário que não há espaço de armazenamento suficiente para instalar o recurso.
SPLITCOMPAT_VERIFICATION_ERROR, SPLITCOMPAT_EMULATION_ERROR, SPLITCOMPAT_COPY_ERROR O SplitCompat não conseguiu carregar o módulo do recurso. Esses erros devem ser resolvidos automaticamente após a próxima reinicialização do app.
PLAY_STORE_NOT_FOUND O app Play Store não está instalado no dispositivo. Informe ao usuário que o app Play Store é necessário para fazer o download do recurso.
APP_NOT_OWNED O app não foi instalado pelo Google Play, e o recurso não pode ser transferido por download. Na Play Core versão 1.9 ou mais recente, esse erro só pode ocorrer em instalações adiadas. Se você quiser que o usuário faça o download do app no Google Play, use o método startInstall(), que pode receber a confirmação de usuário necessária (Play Core versão 1.9 ou mais recente).
INTERNAL_ERROR Ocorreu um erro interno na Play Store. Tente fazer a solicitação novamente.

Se um usuário solicitar o download de um módulo sob demanda e ocorrer um erro, considere mostrar uma caixa de diálogo com duas opções para o usuário: Tentar novamente (para fazer a solicitação de novo) e Cancelar (que abandona a solicitação). Para oferecer suporte adicional, você também precisa fornecer o link Help (Ajuda), que direciona os usuários para a Central de Ajuda do Google Play.

Gerenciar atualizações de estado

Depois de registrar um listener e registrar o ID da sessão para sua solicitação, use StateUpdatedListener.onStateUpdate() para gerenciar alterações de estado, conforme mostrado abaixo.

Kotlin

override fun onStateUpdate(state : SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIED) {
       // Retry the request.
       return
    }
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.DOWNLOADING -> {
              val totalBytes = state.totalBytesToDownload()
              val progress = state.bytesDownloaded()
              // Update progress bar.
            }
            SplitInstallSessionStatus.INSTALLED -> {

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
            }
        }
    }
}

Java

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIES) {
       // Retry the request.
       return;
    }
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.DOWNLOADING:
              int totalBytes = state.totalBytesToDownload();
              int progress = state.bytesDownloaded();
              // Update progress bar.
              break;

            case SplitInstallSessionStatus.INSTALLED:

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
        }
    }
}

Os estados possíveis para sua solicitação de instalação estão descritos na tabela abaixo.

Estado de solicitação Descrição Ação sugerida
PENDING A solicitação foi aceita, e o download deve começar em breve. Inicialize componentes da IU, por exemplo, uma barra de progresso, para fornecer feedback ao usuário sobre o download.
REQUIRES_USER_CONFIRMATION O download requer confirmação do usuário. Geralmente, esse status ocorrerá se o app não tiver sido instalado pelo Google Play. Peça que o usuário confirme o download do recurso no Google Play. Para saber mais, vá para a seção sobre como receber a confirmação do usuário.
BAIXANDO O download está em andamento. Se você mostrar uma barra de progresso para o download, use os métodos SplitInstallSessionState.bytesDownloaded() e SplitInstallSessionState.totalBytesToDownload() para atualizar a IU (consulte a amostra de código acima desta tabela).
DOWNLOADED O dispositivo fez o download do módulo, mas a instalação ainda não começou. Os apps precisam ativar a biblioteca SplitCompat para ter acesso a módulos transferidos por download e evitar esse estado. Isso é necessário para acessar o código e os recursos do módulo de recursos.
INSTALLING O dispositivo está instalando o módulo no momento. Atualize a barra de progresso. Esse estado normalmente é curto.
INSTALLED O módulo está instalado no dispositivo. Acesse o código e o recurso no módulo para continuar a jornada do usuário.

Se o módulo for para um Instant App Android executado no Android 8.0 (API de nível 26) ou versões mais recentes, você precisará usar splitInstallHelper para atualizar componentes do app com o novo módulo.

FAILED A solicitação falhou antes da instalação do módulo no dispositivo. Peça que o usuário tente fazer a solicitação novamente ou cancelá-la.
CANCELING O dispositivo está cancelando a solicitação. Para saber mais, vá para a seção sobre como cancelar uma solicitação de instalação.
CANCELED A solicitação foi cancelada.

Receber a confirmação do usuário

Em alguns casos, o Google Play pode exigir a confirmação do usuário antes de atender a uma solicitação de download. Por exemplo, se o app não tiver sido instalado pelo Google Play ou se você estiver tentando fazer um download grande usando dados móveis. Nesses casos, o status da solicitação informará REQUIRES_USER_CONFIRMATION, e o app precisará da confirmação do usuário antes que o dispositivo possa fazer o download dos módulos na solicitação e os instalar. O app precisará pedir a confirmação do usuário desta forma:

Kotlin

override fun onSessionStateUpdate(state: SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          /* activity = */ this,
          // You use this request code to later retrieve the user's decision.
          /* requestCode = */ MY_REQUEST_CODE)
    }
    ...
 }

Java

@Override void onSessionStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          /* activity = */ this,
          // You use this request code to later retrieve the user's decision.
          /* requestCode = */ MY_REQUEST_CODE);
    }
    ...
 }

O status da solicitação é atualizado dependendo da resposta do usuário:

  • Se o usuário aceitar a confirmação, o status da solicitação mudará para PENDING e o download continuará.
  • Se o usuário negar a confirmação, o status da solicitação mudará para CANCELED.
  • Se o usuário não fizer uma seleção antes da caixa de diálogo ser destruída, o status da solicitação permanecerá como REQUIRES_USER_CONFIRMATION. Seu app pode pedir novamente que o usuário conclua a solicitação.

Para receber um callback com a resposta do usuário, use onActivityResult(), conforme mostrado abaixo.

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  if (requestCode == MY_REQUEST_CODE) {
    // Handle the user's decision. For example, if the user selects "Cancel",
    // you may want to disable certain functionality that depends on the module.
  }
}

Java

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == MY_REQUEST_CODE) {
    // Handle the user's decision. For example, if the user selects "Cancel",
    // you may want to disable certain functionality that depends on the module.
  }
}

Cancelar uma solicitação de instalação

Se o app precisar cancelar uma solicitação antes de ser instalado, poderá invocar o método cancelInstall() usando o ID de sessão da solicitação, conforme mostrado abaixo.

Kotlin

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId)

Java

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId);

Acessar módulos

Para acessar o código e os recursos de um módulo transferido por download, seu app precisa ativar a Biblioteca SplitCompat para seu app e cada atividade nos módulos de recursos que seu app transferir por download.

No entanto, a plataforma enfrenta as seguintes restrições para acessar o conteúdo de um módulo, por algum tempo (dias, em alguns casos) após o download do módulo:

  • A plataforma não pode aplicar novas entradas de manifesto introduzidas pelo módulo.
  • A plataforma não pode acessar os recursos do módulo para componentes da IU do sistema, por exemplo, as notificações. Se você precisar usar esses recursos imediatamente, inclua-os no módulo básico do seu app.

Ativar a SplitCompat

Para que seu app acesse código e recursos de um módulo transferido por download, você precisa ativar a SplitCompat usando apenas um dos métodos descritos nas seções a seguir.

Depois de ativar a SplitCompat para seu app, você também precisa ativar a biblioteca SplitCompat para todas as atividades nos módulos de recurso aos quais você quer que seu app tenha acesso.

Declarar SplitCompatApplication no manifesto

A maneira mais simples de ativar a SplitCompat é declarar SplitCompatApplication como a subclasse Application no manifesto do seu app, conforme mostrado abaixo.

<application
    ...
    android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
</application>

Depois que o app for instalado em um dispositivo, você poderá acessar o código e os recursos nos módulos de recursos transferidos por download automaticamente.

Invocar a SplitCompat no momento da execução

Você também pode ativar a SplitCompat em atividades ou serviços específicos no momento da execução. Ativar a SplitCompat dessa maneira é necessário para iniciar atividades incluídas em módulos de recursos. Para fazer isso, modifique attachBaseContext, conforme mostrado abaixo.

Se você tiver uma classe Application personalizada, faça com que SplitCompatApplication seja estendida para ativar a SplitCompat para seu app, conforme mostrado abaixo.

Kotlin

class MyApplication : SplitCompatApplication() {
    ...
}

Java

public class MyApplication extends SplitCompatApplication {
    ...
}

SplitCompatApplication simplesmente modifica ContextWrapper.attachBaseContext() para incluir SplitCompat.install(Context applicationContext). Se você não quiser que a classe Application estenda SplitCompatApplication, modifique o método attachBaseContext() manualmente, da seguinte maneira:

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this);
}

Caso seu módulo sob demanda seja compatível com apps instantâneos e apps instalados, você poderá invocar condicionalmente a SplitCompat da seguinte maneira:

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this)
    }
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this);
    }
}

Ativar a SplitCompat para atividades do módulo

Depois de ativar a SplitCompat para seu app de base, você precisará ativá-la para todas as atividades que o app transferir para um módulo de recurso. Para fazer isso, use o método SplitCompat.installActivity() da seguinte maneira:

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this);
}

Acessar componentes definidos em módulos de recursos

Iniciar uma atividade definida em um módulo de recursos

Você pode iniciar atividades definidas em módulos de recursos usando o método startActivity() depois de ativar a SplitCompat.

Kotlin

startActivity(Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...))

Java

startActivity(new Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...));

O primeiro parâmetro para setClassName é o nome do pacote do app, e o segundo é o nome completo da classe da atividade.

Quando houver uma atividade em um módulo de recursos transferido por download sob demanda, será necessário ativar a SplitCompat na atividade.

Iniciar um serviço definido em um módulo de recursos

Você pode iniciar serviços definidos em módulos de recursos usando o método startService() depois de ativar a SplitCompat.

Kotlin

startService(Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...))

Java

startService(new Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...));

Exportar um componente definido em um módulo de recursos

Não inclua componentes Android exportados em módulos opcionais.

O sistema de compilação mescla entradas de manifesto para todos os módulos no módulo base. Se um módulo opcional contiver um componente exportado, ele poderá ser acessado mesmo antes da instalação do módulo e poderá causar uma falha devido à ausência do código quando o componente for invocado em outro app.

Isso não é um problema para componentes internos, eles podem ser acessados apenas pelo app, que pode verificar se o módulo está instalado antes de acessar o componente.

Se você precisar de um componente exportado e quiser que o conteúdo dele também esteja em um módulo opcional, considere implementar um padrão de proxy. Você pode fazer isso adicionando um componente de proxy exportado na base. Quando acessado, o componente de proxy poderá confirmar se o módulo que contém o conteúdo está presente. Se o módulo estiver presente, o componente de proxy poderá iniciar o componente interno do módulo usando uma Intent, transmitindo a intent do app autor da chamada. Se o módulo não estiver presente, o componente poderá fazer o download dele ou retornar uma mensagem de erro adequada ao app autor da chamada.

Acessar o código e recursos dos módulos instalados

Se você ativar a SplitCompat para o contexto do seu aplicativo base e as atividades no módulo de recursos, poderá usar o código e os recursos de um módulo de recursos como se fizessem parte do APK base após a instalação do módulo opcional.

Acessar o código em um módulo diferente

Acessar o código base de um módulo

O código que está no módulo base pode ser usado diretamente por outros módulos. Não é necessário fazer nada especial, basta importar e usar as classes de que você precisa.

Acessar o código de um módulo em outro

Um objeto ou classe de um módulo não pode ser acessado estaticamente em outro módulo diretamente, mas é possível acessá-lo indiretamente usando a reflexão.

Contudo, devido aos custos de desempenho da reflexão, tome cuidado para não usá-la com frequência. Em casos de uso complexos, use frameworks de injeção de dependência, como o Dagger 2, (link em inglês) para garantir que apenas uma chamada de reflexão seja feita por ciclo de vida do aplicativo.

Para simplificar as interações com o objeto após a instanciação, recomendamos definir uma interface no módulo base e a implementação dela no módulo de recursos. Por exemplo:

Kotlin

// In the base module
interface MyInterface {
  fun hello(): String
}

// In the feature module
object MyInterfaceImpl : MyInterface {
  override fun hello() = "Hello"
}

// In the base module, where we want to access the feature module code
val stringFromModule = (Class.forName("com.package.module.MyInterfaceImpl")
    .kotlin.objectInstance as MyInterface).hello();

Java

// In the base module
public interface MyInterface {
  String hello();
}

// In the feature module
public class MyInterfaceImpl implements MyInterface {
  @Override
  public String hello() {
    return "Hello";
  }
}

// In the base module, where we want to access the feature module code
String stringFromModule =
   ((MyInterface) Class.forName("com.package.module.MyInterfaceImpl").getConstructor().newInstance()).hello();

Acessar recursos em um módulo diferente

Depois que um módulo for instalado, você poderá acessar os recursos dele da maneira padrão, com duas ressalvas:

  • Se você estiver acessando um recurso em um módulo diferente, o módulo não terá acesso ao identificador do recurso, mas o recurso ainda poderá ser acessado pelo nome. O pacote usado para fazer referência ao recurso é o pacote do módulo em que o recurso está definido.
  • Se quiser acessar recursos existentes de um módulo recém-instalado em um outro módulo instalado do app, você precisará fazer isso usando o contexto do aplicativo. O contexto do componente que está tentando acessar os recursos ainda não será atualizado. Como alternativa, você pode recriar esse componente, por exemplo, chamando o método Activity.recreate() ou reinstalar a SplitCompat após a instalação do módulo de recursos.

Carregar o código nativo usando um módulo opcional

Depois que uma divisão for instalada, o código nativo dela poderá ser carregado invocando o System.loadLibrary(libName) padrão. Para apps instantâneos, fornecemos um método especial.

Se você estiver usando System.loadLibrary() para carregar seu código nativo e a biblioteca nativa depender de outra biblioteca no módulo, primeiro, carregue manualmente essa outra biblioteca.

Se você estiver usando o método dlopen() no código nativo para carregar uma biblioteca definida em um módulo opcional, ele não funcionará com caminhos de biblioteca relativos. A melhor solução é recuperar o caminho absoluto da biblioteca do código Java com o método ClassLoader.findLibrary() e usá-lo na chamada dlopen(). Faça isso antes de inserir o código nativo ou use uma chamada JNI do código nativo para o Java.

Acessar Instant Apps Android instalados

Depois que um módulo do Instant App Android for relatado como INSTALLED, você poderá acessar o código e os recursos dele usando um contexto de app atualizado. Um contexto que seu app cria antes de instalar um módulo (por exemplo, um que já esteja armazenado em uma variável) não possui o conteúdo do novo módulo. Mas um novo contexto tem essa informação, que pode ser acessada, por exemplo, usando createPackageContext.

Kotlin

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                val newContext = context.createPackageContext(context.packageName, 0)
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                val am = newContext.assets
            }
        }
    }
}

Java

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                AssetManager am = newContext.getAssets();
        }
    }
}

Instant Apps Android no Android 8.0 e versões posteriores

Ao solicitar um módulo sob demanda para um Instant App Android no Android 8.0 (API de nível 26) e versões mais recentes, depois que uma solicitação de instalação for relatada como INSTALLED, você precisará atualizar o app com o contexto do novo módulo por meio de uma chamada para SplitInstallHelper.updateAppInfo(Context context). Caso contrário, o app ainda não estará ciente do código e dos recursos do módulo. Depois de atualizar os metadados do app, você precisa carregar o conteúdo do módulo durante o próximo evento da linha de execução principal invocando um novo Handler, conforme mostrado abaixo.

Kotlin

override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                // You need to perform the following only for Android Instant Apps
                // running on Android 8.0 (API level 26) and higher.
                if (BuildCompat.isAtLeastO()) {
                    // Updates the app’s context with the code and resources of the
                    // installed module.
                    SplitInstallHelper.updateAppInfo(context)
                    Handler().post {
                        // Loads contents from the module using AssetManager
                        val am = context.assets
                        ...
                    }
                }
            }
        }
    }
}

Java

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
            // You need to perform the following only for Android Instant Apps
            // running on Android 8.0 (API level 26) and higher.
            if (BuildCompat.isAtLeastO()) {
                // Updates the app’s context with the code and resources of the
                // installed module.
                SplitInstallHelper.updateAppInfo(context);
                new Handler().post(new Runnable() {
                    @Override public void run() {
                        // Loads contents from the module using AssetManager
                        AssetManager am = context.getAssets();
                        ...
                    }
                });
            }
        }
    }
}

Carregar bibliotecas C/C++

Se você quiser carregar bibliotecas C/C++ de um módulo que o dispositivo já tenha transferido para um Instant App, use SplitInstallHelper.loadLibrary(Context context, String libName), conforme mostrado abaixo.

Kotlin

override fun onStateUpdate(state: SplitInstallSessionState) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.INSTALLED -> {
                // Updates the app’s context as soon as a module is installed.
                val newContext = context.createPackageContext(context.packageName, 0)
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, “my-cpp-lib”)
                ...
            }
        }
    }
}

Java

public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.INSTALLED:
                // Updates the app’s context as soon as a module is installed.
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, “my-cpp-lib”);
                ...
        }
    }
}

Limitações conhecidas

  • Não é possível usar o Android WebView em uma atividade que acessa recursos de um módulo opcional. Isso ocorre devido a uma incompatibilidade entre o WebView e a SplitCompat no nível 28 e anteriores da API do Android.
  • Não é possível armazenar no cache do app objetos ApplicationInfo do Android, os conteúdos deles ou objetos que os contenham. Sempre busque esses objetos conforme necessário em um contexto de app. Armazenar esses objetos em cache pode causar uma falha no app ao instalar um módulo de recurso.

Gerenciar módulos instalados

Para verificar quais módulos de recursos estão instalados no dispositivo atualmente, você pode chamar SplitInstallManager.getInstalledModules(), que retorna um Set<String> dos nomes dos módulos instalados, como mostrado abaixo.

Kotlin

val installedModules: Set<String> = splitInstallManager.installedModules

Java

Set<String> installedModules = splitInstallManager.getInstalledModules();

Desinstalar módulos

Você pode solicitar que o dispositivo desinstale módulos invocando SplitInstallManager.deferredUninstall(List<String> moduleNames), conforme mostrado abaixo.

Kotlin

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(listOf("pictureMessages", "promotionalFilters"))

Java

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(Arrays.asList("pictureMessages", "promotionalFilters"));

As desinstalações do módulo não ocorrem imediatamente. Ou seja, o dispositivo faz a desinstalação em segundo plano, conforme necessário, para economizar espaço de armazenamento. Você pode confirmar se o dispositivo excluiu um módulo invocando SplitInstallManager.getInstalledModules() e inspecionando o resultado, conforme descrito na seção anterior.

Fazer o download de outros recursos de idioma

Com os pacotes de apps, os dispositivos fazem o download apenas do código e dos recursos necessários para executar o app. Assim, para recursos de idioma, o dispositivo de um usuário faz o download apenas dos recursos de idioma do app que correspondem a um ou mais idiomas selecionados atualmente nas configurações do dispositivo.

Se você quiser que seu app tenha acesso a outros recursos de idioma, por exemplo, para implementar um seletor de idioma no app, use a biblioteca Play Core para fazer o download sob demanda. O processo é semelhante ao de fazer o download de um módulo de recurso, conforme mostrado abaixo.

Kotlin

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply()
...

// Creates a request to download and install additional language resources.
val request = SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build()

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request)

Java

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply();
...

// Creates a request to download and install additional language resources.
SplitInstallRequest request =
    SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build();

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request);

A solicitação é processada como se fosse uma solicitação de um módulo de recurso. Ou seja, você pode monitorar o estado da solicitação como faria normalmente.

Caso seu app não exija os recursos de idioma adicionais imediatamente, você poderá adiar a instalação para quando o app estiver em segundo plano, conforme mostrado abaixo.

Kotlin

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Java

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

Acessar recursos de idiomas transferidos por download

Para ter acesso aos recursos de idioma transferidos por download, seu app precisa executar o método SplitCompat.installActivity() no método attachBaseContext() de cada atividade que requer acesso a esses recursos, conforme mostrado abaixo.

Kotlin

override fun attachBaseContext(base: Context) {
  super.attachBaseContext(base)
  SplitCompat.installActivity(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
  super.attachBaseContext(base);
  SplitCompat.installActivity(this);
}

Para cada atividade em que você quer usar os recursos de idioma que o app transferiu por download, atualize o contexto base e defina uma nova localidade com Configuration:

Kotlin

override fun attachBaseContext(base: Context) {
  val configuration = Configuration()
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
  val context = base.createConfigurationContext(configuration)
  super.attachBaseContext(context)
  SplitCompat.install(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
  Configuration configuration = new Configuration();
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));
  Context context = base.createConfigurationContext(configuration);
  super.attachBaseContext(context);
  SplitCompat.install(this);
}

Para que essas mudanças entrem em vigor, é necessário recriar sua atividade depois que o novo idioma estiver instalado e pronto para uso. Você pode usar o método Activity#recreate().

Kotlin

when (state.status()) {
  SplitInstallSessionStatus.INSTALLED -> {
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate()
  }
  ...
}

Java

switch (state.status()) {
  case SplitInstallSessionStatus.INSTALLED:
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate();
  ...
}

Desinstalar outros recursos de idioma

Assim como os módulos de recursos, é possível desinstalar outros recursos a qualquer momento. Antes de solicitar uma desinstalação, você pode determinar primeiro quais idiomas estão atualmente instalados, como mostrado a seguir.

Kotlin

val installedLanguages: Set<String> = splitInstallManager.installedLanguages

Java

Set<String> installedLanguages = splitInstallManager.getInstalledLanguages();

Depois, você pode decidir quais idiomas serão desinstalados usando o método deferredLanguageUninstall(), conforme mostrado abaixo.

Kotlin

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Java

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

Instalações do módulo de teste local

A biblioteca Play Core permite testar localmente a capacidade do seu app de fazer o seguinte, sem se conectar à Play Store:

Esta página descreve como implantar os APKs divididos do seu app no dispositivo de teste para que a Play Core use esses APKs automaticamente para simular solicitações, downloads e instalação de módulos da Play Store.

Embora não seja necessário fazer mudanças na lógica do app, você precisa atender aos seguintes requisitos:

Criar um conjunto de APKs

Crie os APKs divididos do seu app da seguinte maneira, se ainda não tiver feito isso:

  1. Crie um pacote de apps usando um dos seguintes métodos:
  2. Use bundletool para gerar um conjunto de APKs para todas as configurações do dispositivo com o seguinte comando:

    bundletool build-apks --local-testing
      --bundle my_app.aab
      --output my_app.apks
    

A sinalização --local-testing inclui metadados nos manifestos dos APKs que permitem que a biblioteca Play Core saiba usar os APKs divididos locais para testar a instalação de módulos de recursos sem se conectar à Play Store.

Implantar seu app no dispositivo

Depois de criar um conjunto de APKs usando a sinalização --local-testing, use bundletool para instalar a versão base do app e transferir outros APKs para o armazenamento local do dispositivo. Você pode executar as duas ações com o seguinte comando:

bundletool install-apks --apks my_app.apks

Agora, quando você iniciar o app e concluir o fluxo do usuário para fazer o download e instalar um módulo de recursos, a biblioteca Play Core usará os APKs que bundletool transferiu para o armazenamento local do dispositivo.

Simular um erro de rede

Para simular instalações de módulo da Play Store, a biblioteca Play Core usa uma alternativa ao SplitInstallManager, chamada FakeSplitInstallManager, para solicitar o módulo. Quando você usa bundletool com a sinalização --local-testing para criar um conjunto de APKs e implantá-lo no dispositivo de teste, ela inclui metadados que instruem a biblioteca Play Core a alternar automaticamente as chamadas de API do seu app para invocar FakeSplitInstallManager, em vez de SplitInstallManager.

FakeSplitInstallManager inclui uma sinalização booleana que pode ser ativada para simular um erro de rede na próxima vez que seu app solicitar a instalação de um módulo. Para acessar FakeSplitInstallManager nos testes, é possível conseguir uma instância dele usando o FakeSplitInstallManagerFactory, conforme mostrado abaixo:

Kotlin

// Creates an instance of FakeSplitInstallManager with the app's context.
val fakeSplitInstallManager = FakeSplitInstallManagerFactory.create(context)
// Tells Play Core Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true)

Java

// Creates an instance of FakeSplitInstallManager with the app's context.
FakeSplitInstallManager fakeSplitInstallManager =
    FakeSplitInstallManagerFactory.create(context);
// Tells Play Core Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true);