Arquivos de expansão do APK

O Google Play requer que o APK compactado que os usuários transferem por download não ultrapasse 100 MB. Para a maioria dos aplicativos, isso é mais do que suficiente para todo o código e os recursos. No entanto, alguns aplicativos precisam de mais espaço para gráficos de alta fidelidade, arquivos de mídia ou outros recursos grandes. Anteriormente, se o tamanho do download compactado do aplicativo ultrapassasse 100 MB, era necessário hospedar e fazer o download dos recursos adicionais quando o usuário abrisse o aplicativo. Hospedar e exibir os arquivos extras pode ser caro e a experiência do usuário costuma ser inferior à ideal. Para tornar esse processo mais fácil para você e mais agradável para os usuários, o Google Play permite anexar dois arquivos de expansão grandes que complementam o APK.

O Google Play hospeda os arquivos de expansão do aplicativo, os exibe no dispositivo e você não paga nada por isso. Os arquivos de expansão são salvos no local de armazenamento compartilhado do dispositivo (o cartão SD ou partição montada em USB, também conhecidos como armazenamento externo), em que o aplicativo pode acessá-los. Na maioria dos dispositivos, o Google Play faz o download dos arquivos de expansão junto com o download do APK. Assim, o app terá tudo que precisar quando o usuário o abrir pela primeira vez. Em alguns casos, no entanto, o aplicativo precisa fazer o download dos arquivos no Google Play quando é iniciado.

Se você quiser evitar o uso de arquivos de expansão e o tamanho do download compactado do aplicativo for maior que 100 MB, faça upload do aplicativo usando o Android App Bundle, que permite até 200 MB de tamanho de download compactado. Além disso, como pacotes de apps permitem gerar APKs e assinar com o Google Play, os usuários fazem o download de APKs otimizados apenas com o código e os recursos necessários para executar o aplicativo. Você não precisa criar, assinar nem gerenciar vários APKs ou arquivos de expansão, e os usuários terão downloads menores e mais otimizados.

Visão geral

Sempre que você fizer upload de um APK usando o Google Play Console, terá a opção de adicionar um ou dois arquivos de expansão ao APK. Cada arquivo pode ter até 2 GB e pode ser de qualquer formato, mas recomendamos que você use um arquivo compactado para economizar largura de banda durante o download. Conceitualmente, cada arquivo de expansão desempenha um papel diferente:

  • O arquivo de expansão principal é o arquivo de expansão primário dos recursos adicionais exigidos pelo aplicativo.
  • O arquivo de expansão patch é opcional e está destinado a pequenas atualizações do arquivo de expansão principal.

Embora seja possível usar os dois arquivos de expansão da maneira que quiser, recomendamos que o arquivo de expansão principal forneça os recursos principais e seja atualizado raramente (se for). O arquivo de expansão "patch" precisa ser menor e servir como uma “operadora de patch”, sendo atualizado com cada versão principal ou conforme necessário.

No entanto, mesmo que a atualização do app exija apenas um novo arquivo de expansão de patch, você ainda precisa fazer upload de um novo APK com o versionCode atualizado no manifesto. O Play Console não permite o upload de um arquivo de expansão para um APK existente.

Observação: o arquivo de expansão de patch é semanticamente igual ao arquivo de expansão principal. Você pode usar cada arquivo da maneira que quiser.

Formato do nome do arquivo

Todos os arquivos de expansão enviados podem ser de qualquer formato (ZIP, PDF, MP4 etc.). Também é possível usar a ferramenta JOBB para encapsular e criptografar um conjunto de arquivos de recursos e patches subsequentes para esse conjunto. Independentemente do tipo de arquivo, o Google Play os considera blobs binários opacos e renomeia os arquivos usando o seguinte esquema:

[main|patch].<expansion-version>.<package-name>.obb

Há três componentes neste esquema:

main ou patch
Especifica se o arquivo de expansão é principal ou de patch. Pode haver somente um arquivo principal e um de patch para cada APK.
<expansion-version>
Este é um número inteiro que corresponde ao código da versão do APK com que a expansão é associada primeiro (corresponde ao valor android:versionCode do app).

"Primeiro" é enfatizado porque, embora o Play Console permita que você reutilize um arquivo de expansão com um novo APK, o nome do arquivo não é modificado. Ele retém a versão aplicada durante o primeiro upload.

<package-name>
Nome do pacote no estilo Java do aplicativo.

Por exemplo, suponha que a versão do APK seja 314159 e o nome do pacote seja com.example.app. Se você fizer o upload de um arquivo de expansão principal, o arquivo será renomeado para:

main.314159.com.example.app.obb

Local de armazenamento

Quando o Google Play faz o download dos arquivos de expansão para um dispositivo, eles são salvos no local de armazenamento compartilhado do sistema. Para garantir o comportamento adequado, não exclua, mova nem renomeie os arquivos de expansão. Caso seu aplicativo precise realizar o download no próprio Google Play, salve os arquivos no mesmo local.

O método getObbDir() retorna o local específico dos arquivos de expansão no seguinte formato:

<shared-storage>/Android/obb/<package-name>/

Nunca há mais de dois arquivos de expansão para cada app nesse diretório. Um é o arquivo de expansão principal e o outro é o arquivo de expansão do patch (se necessário). As versões anteriores são substituídas quando você atualiza o aplicativo com novos arquivos de expansão. A partir do Android 4.4 (API de nível 19), os aplicativos podem ler arquivos de expansão OBB sem permissão de armazenamento externo. No entanto, algumas implementações do Android 6.0 (API de nível 23) e versões posteriores ainda exigem permissão. Portanto, declare a permissão READ_EXTERNAL_STORAGE no manifesto do aplicativo e solicite permissão no momento da execução da seguinte maneira:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Para o Android 6 e posteriores, a permissão de armazenamento externo precisa ser solicitada no momento da execução. No entanto, algumas implementações do Android não precisam de permissão para ler arquivos OBB. O snippet de código a seguir mostra como verificar o acesso de leitura antes de solicitar a permissão de armazenamento externo:

Kotlin

val obb = File(obb_filename)
var open_failed = false

try {
    BufferedReader(FileReader(obb)).also { br ->
        ReadObbFile(br)
    }
} catch (e: IOException) {
    open_failed = true
}

if (open_failed) {
    // request READ_EXTERNAL_STORAGE permission before reading OBB file
    ReadObbFileWithPermission()
}

Java

File obb = new File(obb_filename);
 boolean open_failed = false;

 try {
     BufferedReader br = new BufferedReader(new FileReader(obb));
     open_failed = false;
     ReadObbFile(br);
 } catch (IOException e) {
     open_failed = true;
 }

 if (open_failed) {
     // request READ_EXTERNAL_STORAGE permission before reading OBB file
     ReadObbFileWithPermission();
 }

Se você precisar descompactar o conteúdo dos arquivos de expansão, não exclua os arquivos de expansão OBB e não salve os dados descompactados no mesmo diretório. Salve os arquivos descompactados no diretório especificado por getExternalFilesDir(). No entanto, se possível, é melhor usar um formato de arquivo de expansão que permita a leitura direta do arquivo em vez de exigir a descompactação dos dados. Por exemplo, fornecemos um projeto de biblioteca chamado Biblioteca APK Expansion Zip que lê seus dados diretamente no arquivo ZIP.

Atenção: diferentemente dos arquivos APK, qualquer arquivo salvo no armazenamento compartilhado pode ser lido pelo usuário e por outros aplicativos.

Dica: se você empacotar arquivos de mídia em um arquivo ZIP, poderá usar chamadas de reprodução de mídia nos arquivos com controles de compensação e comprimento (como MediaPlayer.setDataSource() e SoundPool.load()) sem precisar descompactar o ZIP. Para que isso funcione, não realize compactação adicional nos arquivos de mídia ao criar pacotes ZIP. Por exemplo, ao usar a ferramenta zip, use a opção -n para especificar os sufixos de arquivo que não podem ser compactados:
zip -n .mp4;.ogg main_expansion media_files

Processo de download

Na maioria das vezes, o Google Play faz o download e salva os arquivos de expansão ao mesmo tempo em que faz o download do APK no dispositivo. No entanto, em alguns casos, o Google Play não pode fazer o download dos arquivos de expansão ou o usuário pode ter excluído os arquivos de expansão salvos anteriormente. Para gerenciar essas situações, o aplicativo precisa fazer o download dos arquivos quando a atividade principal for iniciada, usando um URL fornecido pelo Google Play.

O processo de download de um nível superior funciona assim:

  1. O usuário escolhe instalar seu aplicativo no Google Play.
  2. Se o Google Play conseguir fazer o download dos arquivos de expansão, que é o caso da maioria dos dispositivos, o download será feito junto com o APK.

    Se o Google Play não conseguir fazer o download dos arquivos de expansão, ele só fará o download do APK.

  3. Quando o usuário inicia seu aplicativo, o app precisa verificar se os arquivos de expansão já estão salvos no dispositivo.
    1. Em caso afirmativo, seu app estará pronto para ser usado.
    2. Em caso negativo, seu aplicativo precisará fazer o download dos arquivos de expansão via HTTP no Google Play. Seu aplicativo precisa enviar uma solicitação ao cliente do Google Play usando o serviço de licenciamento do app do Google Play, que responde com o nome, o tamanho e o URL de cada arquivo de expansão. Com essas informações, você faz o download dos arquivos e os salva no local de armazenamento.

Cuidado: é fundamental que você inclua o código necessário para fazer o download dos arquivos de expansão no Google Play caso os arquivos não estejam no dispositivo quando seu aplicativo for iniciado. Como discutido na seção a seguir sobre Fazer o download dos arquivos de expansão, disponibilizamos uma biblioteca que simplifica bastante esse processo e faz o download de um serviço com uma quantidade mínima de código.

Lista de verificação de desenvolvimento

Veja um resumo das tarefas a serem executadas para usar arquivos de expansão com seu aplicativo:

  1. Primeiro, determine se o tamanho do download compactado do aplicativo precisa ter mais de 100 MB. O espaço é precioso e é necessário manter o tamanho de download total o menor possível. Caso seu aplicativo use mais de 100 MB para fornecer diversas versões dos recursos gráficos para várias densidades de tela, considere publicar vários APKs, em que cada APK contenha apenas os recursos necessários para as telas a que se destina. Para ter os melhores resultados ao publicar no Google Play, faça upload de um Android App Bundle que inclua todos os recursos e código compilados do app, mas transfira a geração e assinatura do APK para o Google Play.
  2. Determine os recursos de aplicativos que precisam ser separados do APK e agrupe-os em um arquivo para usá-los como o arquivo de expansão principal.

    Normalmente, você precisa usar somente o segundo arquivo de expansão de patch ao atualizar o arquivo de expansão principal. No entanto, caso seus recursos excedam o limite de 2 GB para o arquivo de expansão principal, você pode usar o arquivo de patch para o restante dos recursos.

  3. Desenvolva seu aplicativo para que ele use os recursos dos arquivos de expansão no local de armazenamento compartilhado do dispositivo.

    Lembre-se de que é importante evitar excluir, mover ou renomear os arquivos de expansão.

    Caso seu aplicativo não exija um formato específico, sugerimos que você crie arquivos ZIP para os arquivos de expansão e leia-os usando a Biblioteca APK Expansion Zip.

  4. Adicione lógica à atividade principal do aplicativo para verificar se os arquivos de expansão estão no dispositivo durante a inicialização. Se os arquivos não estiverem no dispositivo, use o serviço de licenciamento do aplicativo do Google Play para solicitar URLs para os arquivos de expansão, faça o download deles e salve-os.

    Para reduzir significativamente a quantidade de código que precisa ser escrita e garantir uma boa experiência do usuário durante o download, recomendamos que você use a Biblioteca Downloader para implementar seu comportamento de download.

    Se você criar o próprio serviço de download em vez de usar a biblioteca, lembre-se de não mudar o nome dos arquivos de expansão e salvá-los no local de armazenamento correto.

Depois de concluir o desenvolvimento do aplicativo, siga o guia Testar os arquivos de expansão.

Regras e limitações

A adição de arquivos de expansão do APK é um recurso disponível quando você faz upload do app usando o Play Console. Ao fazer upload do aplicativo pela primeira vez ou atualizar um aplicativo que usa arquivos de expansão, você precisa estar ciente das seguintes regras e limitações:

  1. Os arquivos de expansão não podem ter mais de 2 GB.
  2. Para fazer o download dos arquivos de expansão no Google Play, o usuário precisa ter adquirido seu aplicativo no Google Play. O Google Play não fornecerá os URLs dos arquivos de expansão se o aplicativo tiver sido instalado por outros meios.
  3. Ao fazer o download dentro do seu aplicativo, o URL que o Google Play fornece para cada arquivo é exclusivo para cada download e expira pouco tempo depois de ser fornecido ao aplicativo.
  4. Se você atualizar seu aplicativo com um novo APK ou fizer upload de vários APKs para o mesmo aplicativo, poderá selecionar os arquivos de expansão que enviou para um APK anterior. O nome do arquivo de expansão não muda. Ele mantém a versão recebida pelo APK a que o arquivo foi originalmente associado.
  5. Se você usar arquivos de expansão combinados com vários APKs para fornecer diferentes arquivos de expansão a diferentes dispositivos, ainda será necessário fazer upload de APKs diferentes a cada dispositivo para oferecer um valor versionCode único e declarar filtros diferentes para cada APK.
  6. Não é possível emitir uma atualização para o app modificando somente os arquivos de expansão. Faça upload de um novo APK para atualizar seu app. Se as mudanças dizem respeito apenas aos recursos nos seus arquivos de expansão, você pode atualizar seu APK simplesmente modificando versionCode (e talvez também versionName).

  7. Não salve outros dados no diretório obb/. Se precisar descompactar dados, salve-os no local especificado por getExternalFilesDir().
  8. Não exclua nem renomeie o arquivo de expansão .obb (a menos que esteja fazendo uma atualização). Isso fará com que o Google Play (ou o próprio aplicativo) faça o download repetidamente do arquivo de expansão.
  9. Ao atualizar um arquivo de expansão manualmente, exclua o arquivo de expansão anterior.

Fazer o download dos arquivos de expansão

Na maioria dos casos, o Google Play faz o download e salva os arquivos de expansão no dispositivo enquanto instala ou atualiza o APK. Dessa forma, os arquivos de expansão estarão disponíveis quando seu aplicativo for iniciado pela primeira vez. No entanto, em alguns casos, o próprio aplicativo precisa fazer o download dos arquivos de expansão solicitando-os de um URL fornecido em uma resposta do serviço de licenciamento do aplicativo do Google Play.

A lógica básica de que você precisa para fazer o download dos arquivos de expansão é a seguinte:

  1. Quando seu aplicativo for iniciado, procure os arquivos de expansão no local de armazenamento compartilhado (no diretório Android/obb/<package-name>/).
    1. Se os arquivos de expansão estiverem lá, seu aplicativo poderá continuar.
    2. Se os arquivos de expansão não estiverem lá:
      1. Faça uma solicitação usando o licenciamento de aplicativos do Google Play para ver os nomes, tamanhos e URLs dos arquivos de expansão deles.
      2. Use os URLs fornecidos pelo Google Play para fazer o download dos arquivos de expansão e salvá-los. Você precisa salvar os arquivos no local de armazenamento compartilhado (Android/obb/<package-name>/) e usar o nome de arquivo exato fornecido pela resposta do Google Play.

        Observação: o URL fornecido pelo Google Play para os arquivos de expansão é exclusiva para cada download, e cada um expira pouco tempo depois de ser fornecido ao aplicativo.

Se o aplicativo for gratuito (não for pago), você provavelmente não usou o serviço de licenciamento do aplicativo. Ele é projetado principalmente para aplicar políticas de licenciamento ao seu aplicativo e garantir que o usuário tenha o direito de usá-lo (a pessoa pagou por ele no Google Play). Para facilitar a funcionalidade do arquivo de expansão, o serviço de licenciamento foi aprimorado para oferecer uma resposta ao seu aplicativo que inclui o URL dos arquivos de expansão do aplicativo hospedados no Google Play. Assim, mesmo que o app seja gratuito para os usuários, será necessário incluir a Biblioteca License Verification (LVL, na sigla em inglês) para usar os arquivos de expansão de APK. Obviamente, caso o app seja gratuito, você não precisará aplicar a verificação de licença. A biblioteca só será necessária para fazer a solicitação que retorna o URL dos arquivos de expansão.

Observação: independente de o app ser gratuito ou não, o Google Play vai retornar os URLs dos arquivos de expansão somente se o usuário tiver adquirido seu app no Google Play.

Além da LVL, você precisa de um conjunto de códigos que faça download dos arquivos de expansão usando uma conexão HTTP e os salve no local adequado do armazenamento compartilhado do dispositivo. Conforme você cria esse procedimento no aplicativo, há vários problemas que precisam ser considerados:

  • O dispositivo pode não ter espaço suficiente para os arquivos de expansão. Por isso, verifique antes de iniciar o download e avise o usuário se não houver espaço suficiente.
  • Os downloads de arquivos precisam ocorrer em um serviço em segundo plano para evitar o bloqueio da interação do usuário e permitir que ele saia do aplicativo enquanto o download é concluído.
  • Vários erros podem ocorrer durante a solicitação e o download. É necessário gerenciá-los corretamente.
  • A conectividade de rede pode mudar durante o download. Então, é necessário lidar com essas mudanças e, se for interrompido, retomar o download quando possível.
  • Enquanto o download ocorre em segundo plano, crie uma notificação que indique o progresso do download, notifique o usuário quando ele for concluído e leve-o de volta ao seu aplicativo quando selecionado.

Para simplificar esse trabalho para você, criamos a Biblioteca Downloader, que solicita os URLs do arquivo de expansão pelo serviço de licenciamento, faz o download dos arquivos de expansão, executa todas as tarefas listadas acima e até permite pausar e retomar o download. Ao adicionar a Biblioteca Downloader e alguns hooks de código ao aplicativo, quase todo o trabalho para fazer o download dos arquivos de expansão já está codificado para você. Desse modo, para proporcionar a melhor experiência do usuário com esforço mínimo da sua parte, recomendamos que você use a Biblioteca Downloader para fazer o download dos arquivos de expansão. As informações nas seções a seguir explicam como integrar a Biblioteca ao seu aplicativo.

Se preferir desenvolver sua própria solução para fazer o download dos arquivos de expansão usando os URLs do Google Play, siga a documentação do licenciamento de apps para executar uma solicitação de licença e recupere os nomes, tamanhos e URLs dos arquivos de expansão a partir dos extras de resposta. Use a classe APKExpansionPolicy (incluída na Biblioteca de Verificação e Licença) como sua política de licenciamento, que captura os nomes, tamanhos e URLs do arquivo de expansão do serviço de licenciamento.

Sobre a Biblioteca Downloader

Para usar os arquivos de expansão do APK no seu aplicativo e proporcionar a melhor experiência do usuário com o mínimo de esforço, recomendamos que você use a Biblioteca Downloader incluída no pacote da Biblioteca Google Play APK Expansion. Essa biblioteca faz o download dos arquivos de expansão em um serviço em segundo plano, mostra uma notificação ao usuário com o status de download, gerencia a perda de conectividade da rede, retoma o download quando possível e muito mais.

Para implementar downloads de arquivos de expansão usando a Biblioteca Downloader, você só precisa fazer o seguinte:

  • Estenda uma subclasse especial Service e uma subclasse BroadcastReceiver para que cada uma delas precise apenas de algumas linhas de código.
  • Adicione lógica à atividade principal que verifique se o download dos arquivos de expansão já foi feito e, se não, invoque o processo de download e exiba uma interface de progresso.
  • Implemente uma interface de callback com alguns métodos na atividade principal que recebam atualizações sobre o andamento do download.

As seções a seguir explicam como configurar o aplicativo usando a Biblioteca Downloader.

Preparação para usar a Biblioteca Downloader

Para usar a Biblioteca Downloader, você precisa fazer o download de dois pacotes do SDK Manager e adicionar as bibliotecas apropriadas ao aplicativo.

Primeiro, abra o Android SDK Manager (Tools > SDK Manager) e em Appearance & Behavior > System Settings > Android SDK, selecione a guia SDK Tools para selecionar e fazer o download do seguinte:

  • Pacote da Biblioteca Google Play Licensing
  • Pacote da Biblioteca Google Play APK Expansion

Crie um novo módulo de biblioteca para a Biblioteca de Verificação de Licença e para a Biblioteca Downloader. Para todas as bibliotecas:

  1. Selecione File > New > New Module.
  2. Na janela Create New Module, selecione Android Library e, em seguida, selecione Next.
  3. Especifique um app/nome da biblioteca como "Google Play License Library" e "Google Play Downloader Library", escolha Minimum SDK level e selecione Finish.
  4. Selecione File > Project Structure.
  5. Selecione a guia Properties e, em Library Repository, insira a biblioteca do diretório <sdk>/extras/google/ (play_licensing/ para a Biblioteca de Verificação de Licença ou play_apk_expansion/downloader_library/ para a Biblioteca Downloader).
  6. Selecione OK para criar o novo módulo.

Observação: a Biblioteca Downloader depende da Biblioteca de Verificação de Licença. Adicione a Biblioteca de Verificação de Licença às propriedades do projeto da Biblioteca Downloader.

Ou, usando uma linha de comando, atualize seu projeto para incluir as bibliotecas:

  1. Mude os diretórios para o <sdk>/tools/.
  2. Execute android update project com a opção --library para adicionar a LVL e a Biblioteca Downloader ao projeto. Exemplo:
    android update project --path ~/Android/MyApp \
    --library ~/android_sdk/extras/google/market_licensing \
    --library ~/android_sdk/extras/google/market_apk_expansion/downloader_library
    

Com a Biblioteca de Verificação de Licença e a Biblioteca Downloader adicionadas ao app, você poderá integrar rapidamente o recurso de download de arquivos de expansão do Google Play. O formato escolhido para os arquivos de expansão e a forma como você os lê no armazenamento compartilhado são uma implementação diferente, que precisa ser considerada com base nas necessidades do seu aplicativo.

Dica: o pacote de expansão do APK inclui um app de exemplo que ensina a usar a Biblioteca Downloader em um aplicativo. O exemplo usa uma terceira biblioteca disponível no pacote de expansão do APK, a Biblioteca APK Expansion ZIP. Se você planeja usar arquivos ZIP para os arquivos de expansão, sugerimos que também adicione a Biblioteca APK Expansion ZIP ao seu aplicativo. Para mais informações, consulte a seção abaixo Usar a Biblioteca APK Expansion ZIP.

Declarar permissões do usuário

Para fazer o download dos arquivos de expansão, a Biblioteca Downloader requer várias permissões que precisam ser declaradas no arquivo de manifesto do seu aplicativo. São elas:

<manifest ...>
    <!-- Required to access Google Play Licensing -->
    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />

    <!-- Required to download files from Google Play -->
    <uses-permission android:name="android.permission.INTERNET" />

    <!-- Required to keep CPU alive while downloading files
        (NOT to keep screen awake) -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <!-- Required to poll the state of the network connection
        and respond to changes -->
    <uses-permission
        android:name="android.permission.ACCESS_NETWORK_STATE" />

    <!-- Required to check whether Wi-Fi is enabled -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

    <!-- Required to read and write the expansion files on shared storage -->
    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

Observação: por padrão, a Biblioteca Downloader requer a API de nível 4, mas a Biblioteca APK Expansion Zip requer a API de nível 5.

Implementar o serviço de download

Para executar downloads em segundo plano, a Biblioteca Downloader fornece a própria subclasse Service chamada DownloaderService que precisa ser estendida. Além de fazer o download dos arquivos de expansão para você, o DownloaderService também:

  • registra um BroadcastReceiver que detecta mudanças na conectividade de rede do dispositivo (a transmissão CONNECTIVITY_ACTION) para pausar o download quando necessário (como devido à perda de conectividade) e retomá-lo quando possível (quando há conectividade);
  • programa um alarme RTC_WAKEUP para tentar fazer o download novamente nos casos em que o serviço é encerrado;
  • cria um Notification personalizado que exibe o progresso do download e quaisquer erros ou mudanças de estado;
  • permite que o aplicativo pause e retome o download manualmente;
  • verifica se o armazenamento compartilhado está ativado e disponível, se os arquivos já existem e se há espaço suficiente, tudo antes de fazer o download dos arquivos de expansão. Em seguida, o usuário será notificado se algum desses itens não for verdadeiro.

Tudo o que você precisa fazer é criar uma classe no aplicativo que estenda a classe DownloaderService e modifique três métodos para fornecer detalhes específicos do aplicativo:

getPublicKey()
Isso retornará uma string que é a chave pública RSA codificada em Base64 para sua conta de editor, disponível na página de perfil do Play Console (consulte Configurar para licenciamento).
getSALT()
Isso retornará uma matriz de bytes aleatórios que o licenciamento Policy usa para criar um Obfuscator. O sal garante que seu arquivo SharedPreferences ofuscado em que os dados de licenciamento são salvos será exclusivo e não detectável.
getAlarmReceiverClassName()
Isso retornará o nome da classe BroadcastReceiver no seu aplicativo, que receberá o alarme indicando que o download precisa ser reiniciado (o que pode acontecer se o serviço de download parar inesperadamente).

Por exemplo, veja uma implementação completa de DownloaderService:

Kotlin

// You must use the public key belonging to your publisher account
const val BASE64_PUBLIC_KEY = "YourLVLKey"
// You should also modify this salt
val SALT = byteArrayOf(
        1, 42, -12, -1, 54, 98, -100, -12, 43, 2,
        -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
)

class SampleDownloaderService : DownloaderService() {

    override fun getPublicKey(): String = BASE64_PUBLIC_KEY

    override fun getSALT(): ByteArray = SALT

    override fun getAlarmReceiverClassName(): String = SampleAlarmReceiver::class.java.name
}

Java

public class SampleDownloaderService extends DownloaderService {
    // You must use the public key belonging to your publisher account
    public static final String BASE64_PUBLIC_KEY = "YourLVLKey";
    // You should also modify this salt
    public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98,
            -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
    };

    @Override
    public String getPublicKey() {
        return BASE64_PUBLIC_KEY;
    }

    @Override
    public byte[] getSALT() {
        return SALT;
    }

    @Override
    public String getAlarmReceiverClassName() {
        return SampleAlarmReceiver.class.getName();
    }
}

Aviso: atualize o valor BASE64_PUBLIC_KEY para que seja a chave pública pertencente à sua conta de editor. Você pode encontrar a chave no Developers Console nas informações do seu perfil. Isso é necessário até mesmo ao testar seus downloads.

Lembre-se de declarar o serviço no arquivo de manifesto:

<app ...>
    <service android:name=".SampleDownloaderService" />
    ...
</app>

Implementar o receptor de alarme

Para monitorar o progresso dos downloads de arquivos e reiniciar o download, se necessário, o DownloaderService agendará um alarme RTC_WAKEUP que entrega uma Intent a um BroadcastReceiver no app. Você precisa definir o BroadcastReceiver para chamar uma API da Biblioteca Downloader que verifica o status do download e o reinicia, se necessário.

Só é necessário substituir o método onReceive() para chamar DownloaderClientMarshaller.startDownloadServiceIfRequired().

Exemplo:

Kotlin

class SampleAlarmReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        try {
            DownloaderClientMarshaller.startDownloadServiceIfRequired(
                    context,
                    intent,
                    SampleDownloaderService::class.java
            )
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
    }
}

Java

public class SampleAlarmReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            DownloaderClientMarshaller.startDownloadServiceIfRequired(context,
                intent, SampleDownloaderService.class);
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Observe que essa é a classe a que você precisa retornar o nome no método getAlarmReceiverClassName() do serviço (consulte a seção anterior).

Lembre-se de declarar o destinatário no arquivo de manifesto:

<app ...>
    <receiver android:name=".SampleAlarmReceiver" />
    ...
</app>

Iniciar o download

A principal atividade do aplicativo, iniciada pelo ícone na tela de início, é responsável por verificar se os arquivos de expansão já estão no dispositivo e iniciar o download, se não estiverem.

Para iniciar o download usando a Biblioteca Downloader são necessários os seguintes procedimentos:

  1. Verifique se o download dos arquivos foi feito.

    A Biblioteca Downloader inclui algumas APIs na classe Helper para ajudar nesse processo:

    • getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)
    • doesFileExist(Context c, String fileName, long fileSize)

    O app de exemplo fornecido no pacote de expansão do APK pode chamar o método a seguir no método onCreate() da atividade para verificar se os arquivos de expansão já existem no dispositivo:

    Kotlin

    fun expansionFilesDelivered(): Boolean {
        xAPKS.forEach { xf ->
            Helpers.getExpansionAPKFileName(this, xf.isBase, xf.fileVersion).also { fileName ->
                if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false))
                    return false
            }
        }
        return true
    }
    

    Java

    boolean expansionFilesDelivered() {
        for (XAPKFile xf : xAPKS) {
            String fileName = Helpers.getExpansionAPKFileName(this, xf.isBase,
                xf.fileVersion);
            if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false))
                return false;
        }
        return true;
    }
    

    Nesse caso, cada objeto XAPKFile contém o número da versão e o tamanho de um arquivo de expansão conhecido e um booleano que indica se é o arquivo de expansão principal. Consulte a classe SampleDownloaderActivity do app de exemplo para mais detalhes.

    Se esse método retornar falso, o aplicativo iniciará o download.

  2. Inicie o download chamando o método estático DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass).

    O método utiliza os seguintes parâmetros:

    • context: Context do app.
    • notificationClient: uma PendingIntent para iniciar a atividade principal. É usada na Notification que o DownloaderService cria para mostrar o progresso do download. Quando o usuário seleciona a notificação, o sistema invoca a PendingIntent fornecida aqui e precisa abrir a atividade que mostra o progresso do download (geralmente a mesma atividade que iniciou o download).
    • serviceClass: o objeto Class para a implementação de DownloaderService, obrigatório para iniciar o serviço e iniciar o download, se necessário.

    O método retorna um número inteiro que indica se o download é obrigatório ou não. Os valores possíveis são:

    • NO_DOWNLOAD_REQUIRED: retornados se os arquivos já existirem ou se um download já estiver em andamento.
    • LVL_CHECK_REQUIRED: retornados se uma verificação de licença for necessária para conseguir os URLs do arquivo de expansão.
    • DOWNLOAD_REQUIRED: retornados se os URLs do arquivo de expansão já forem conhecidos, mas não tiverem sido transferidos por download.

    O comportamento para LVL_CHECK_REQUIRED e DOWNLOAD_REQUIRED é essencialmente o mesmo e você normalmente não precisa se preocupar com eles. Na atividade principal que chama startDownloadServiceIfRequired(), você pode simplesmente verificar se a resposta é NO_DOWNLOAD_REQUIRED. Se a resposta for algo diferente de NO_DOWNLOAD_REQUIRED, a Biblioteca Downloader iniciará o download e será necessário atualizar a IU da atividade para exibir o progresso do download (consulte a próxima etapa). Se a resposta for NO_DOWNLOAD_REQUIRED, os arquivos estarão disponíveis e o aplicativo poderá ser iniciado.

    Exemplo:

    Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        // Check if expansion files are available before going any further
        if (!expansionFilesDelivered()) {
            val pendingIntent =
                    // Build an Intent to start this activity from the Notification
                    Intent(this, MainActivity::class.java).apply {
                        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
                    }.let { notifierIntent ->
                        PendingIntent.getActivity(
                                this,
                                0,
                                notifierIntent,
                                PendingIntent.FLAG_UPDATE_CURRENT
                        )
                    }
    
    
            // Start the download service (if required)
            val startResult: Int = DownloaderClientMarshaller.startDownloadServiceIfRequired(
                    this,
                    pendingIntent,
                    SampleDownloaderService::class.java
            )
            // If download has started, initialize this activity to show
            // download progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // This is where you do set up to display the download
                // progress (next step)
                ...
                return
            } // If the download wasn't necessary, fall through to start the app
        }
        startApp() // Expansion files are available, start the app
    }
    

    Java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Check if expansion files are available before going any further
        if (!expansionFilesDelivered()) {
            // Build an Intent to start this activity from the Notification
            Intent notifierIntent = new Intent(this, MainActivity.getClass());
            notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                                    Intent.FLAG_ACTIVITY_CLEAR_TOP);
            ...
            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                    notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
            // Start the download service (if required)
            int startResult =
                DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                            pendingIntent, SampleDownloaderService.class);
            // If download has started, initialize this activity to show
            // download progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // This is where you do set up to display the download
                // progress (next step)
                ...
                return;
            } // If the download wasn't necessary, fall through to start the app
        }
        startApp(); // Expansion files are available, start the app
    }
    
  3. Quando o método startDownloadServiceIfRequired() retornar algo diferente de NO_DOWNLOAD_REQUIRED, crie uma instância de IStub chamando DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService). O IStub fornece uma vinculação entre a atividade e o serviço de download, de modo que a atividade receba callbacks sobre o andamento do download.

    Para instanciar seu IStub chamando CreateStub(), passe uma implementação da interface IDownloaderClient e sua implementação do DownloaderService. A próxima seção, Receber o progresso do download, aborda a interface do IDownloaderClient, que você precisa implementar na classe Activity para atualizar a IU da atividade quando o estado do download mudar.

    Recomendamos que você chame CreateStub() para instanciar seu IStub durante o método onCreate() da atividade depois que startDownloadServiceIfRequired() iniciar o download.

    Por exemplo, no exemplo de código anterior para onCreate(), você pode responder ao resultado do startDownloadServiceIfRequired() da seguinte forma:

    Kotlin

            // Start the download service (if required)
            val startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(
                    this@MainActivity,
                    pendingIntent,
                    SampleDownloaderService::class.java
            )
            // If download has started, initialize activity to show progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // Instantiate a member instance of IStub
                downloaderClientStub =
                        DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService::class.java)
                // Inflate layout that shows download progress
                setContentView(R.layout.downloader_ui)
                return
            }
    

    Java

            // Start the download service (if required)
            int startResult =
                DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                            pendingIntent, SampleDownloaderService.class);
            // If download has started, initialize activity to show progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // Instantiate a member instance of IStub
                downloaderClientStub = DownloaderClientMarshaller.CreateStub(this,
                        SampleDownloaderService.class);
                // Inflate layout that shows download progress
                setContentView(R.layout.downloader_ui);
                return;
            }
    

    Depois que o método onCreate() for retornado, sua atividade receberá uma chamada para onResume(), que é onde você precisa chamar connect() no IStub, transmitindo o Context do aplicativo. Por outro lado, chame disconnect() no callback onStop() da atividade.

    Kotlin

    override fun onResume() {
        downloaderClientStub?.connect(this)
        super.onResume()
    }
    
    override fun onStop() {
        downloaderClientStub?.disconnect(this)
        super.onStop()
    }
    

    Java

    @Override
    protected void onResume() {
        if (null != downloaderClientStub) {
            downloaderClientStub.connect(this);
        }
        super.onResume();
    }
    
    @Override
    protected void onStop() {
        if (null != downloaderClientStub) {
            downloaderClientStub.disconnect(this);
        }
        super.onStop();
    }
    

    Chamar connect() em IStub vincula sua atividade ao DownloaderService de forma que sua atividade receba callbacks referentes a mudanças no estado do download pela interface IDownloaderClient.

Receber o progresso do download

Para receber atualizações sobre o progresso do download e interagir com o DownloaderService, implemente a interface IDownloaderClient da Biblioteca Downloader. Normalmente, a atividade que você usa para iniciar o download precisa implementar essa interface para exibir o progresso do download e enviar solicitações ao serviço.

Os métodos de interface necessários para IDownloaderClient são:

onServiceConnected(Messenger m)
Depois de instanciar IStub na atividade, você receberá uma chamada para esse método, que transmite um objeto Messenger conectado à instância de DownloaderService. Para enviar solicitações ao serviço, como pausar e retomar downloads, chame DownloaderServiceMarshaller.CreateProxy() para receber a interface IDownloaderService conectada ao serviço.

Uma implementação recomendada é semelhante ao seguinte:

Kotlin

private var remoteService: IDownloaderService? = null
...

override fun onServiceConnected(m: Messenger) {
    remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
        downloaderClientStub?.messenger?.also { messenger ->
            onClientUpdated(messenger)
        }
    }
}

Java

private IDownloaderService remoteService;
...

@Override
public void onServiceConnected(Messenger m) {
    remoteService = DownloaderServiceMarshaller.CreateProxy(m);
    remoteService.onClientUpdated(downloaderClientStub.getMessenger());
}

Com o objeto IDownloaderService inicializado, você pode enviar comandos para o serviço de download, como pausar e retomar o download (requestPauseDownload() e requestContinueDownload()).

onDownloadStateChanged(int newState)
O serviço de download chama este método quando ocorre uma mudança no estado de download, como o início ou conclusão dele.

O valor newState será um dos diversos valores possíveis especificados por uma das constantes STATE_* da classe IDownloaderClient.

Para fornecer uma mensagem útil aos seus usuários, você pode solicitar uma sequência correspondente para cada estado chamando Helpers.getDownloaderStringResourceIDFromState(). Isso retorna o ID do recurso para uma das strings agrupadas com a Biblioteca Downloader. Por exemplo, a string "Download pausado porque você está em roaming" corresponde a STATE_PAUSED_ROAMING.

onDownloadProgress(DownloadProgressInfo progress)
O serviço de download chama este método para exibir um objeto DownloadProgressInfo, que descreve várias informações sobre o progresso do download, incluindo tempo estimado restante, velocidade atual, progresso geral e total para que você possa atualizar a IU de progresso do download.

Dica: para exemplos desses callbacks que atualizam a IU de progresso do download, consulte o SampleDownloaderActivity no app de exemplo fornecido com o pacote de expansão do APK.

Alguns métodos públicos para a interface IDownloaderService que podem ser úteis são:

requestPauseDownload()
Pausa o download.
requestContinueDownload()
Retoma um download pausado.
setDownloadFlags(int flags)
Define as preferências do usuário para os tipos de rede em que não há problema fazer o download dos arquivos. A implementação atual é compatível com uma sinalização, FLAGS_DOWNLOAD_OVER_CELLULAR, mas você pode adicionar outras. Por padrão, essa sinalização não está ativada. Portanto, o usuário precisa ter uma conexão Wi-Fi para fazer o download de arquivos de expansão. É recomendável fornecer uma preferência de usuário para ativar os downloads pela rede celular. Nesse caso, você pode chamar:

Kotlin

remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
    ...
    setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR)
}

Java

remoteService
    .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);

Usar APKExpansionPolicy

Se você decidir criar seu próprio serviço de download em vez de usar a Biblioteca Downloader do Google Play, ainda é preciso usar a APKExpansionPolicy fornecida na Biblioteca de Verificação de Licença. A classe APKExpansionPolicy é quase idêntica à ServerManagedPolicy (disponível na Biblioteca de Verificação de Licença do Google Play), mas inclui mais tratamento para os extras de resposta de arquivo de expansão do APK.

Observação: se você usar a Biblioteca Downloader conforme discutido na seção anterior, a biblioteca realizará toda a interação com APKExpansionPolicy. Não será necessário usar essa classe diretamente.

A classe inclui métodos para ajudar você a ter as informações necessárias sobre os arquivos de expansão disponíveis:

  • getExpansionURLCount()
  • getExpansionURL(int index)
  • getExpansionFileName(int index)
  • getExpansionFileSize(int index)

Para mais informações sobre como usar o APKExpansionPolicy quando não estiver usando a Biblioteca Downloader, consulte a documentação de Adicionar licenciamento ao seu aplicativo, que explica como implementar uma política de licença como esta.

Ler o arquivo de expansão

Depois que seus arquivos de expansão do APK forem salvos no dispositivo, a maneira como você lerá arquivos dependerá do tipo de arquivo usado. Como discutido na visão geral, seus arquivos de expansão podem ser qualquer tipo de arquivo desejado, mas são renomeados usando um formato de nome de arquivo específico e são salvos em <shared-storage>/Android/obb/<package-name>/.

Independentemente de como você lê os arquivos, verifique sempre se o armazenamento externo está disponível para leitura. Há uma possibilidade de que o usuário tenha o armazenamento conectado a um computador por USB ou tenha removido o cartão SD.

Observação: quando seu aplicativo for iniciado, sempre verifique se o espaço de armazenamento externo está disponível e legível chamando getExternalStorageState(). Isso retorna uma das várias strings possíveis que representam o estado do armazenamento externo. Para que possa ser lido pelo aplicativo, o valor de retorno precisa ser MEDIA_MOUNTED.

Receber os nomes dos arquivos

Conforme descrito na visão geral, seus arquivos de expansão do APK são salvos usando um formato de nome de arquivo específico:

[main|patch].<expansion-version>.<package-name>.obb

Para ver o local e os nomes dos arquivos de expansão, use os métodos getExternalStorageDirectory() e getPackageName() para construir o caminho para os arquivos.

Veja um método que você pode usar no aplicativo para receber uma matriz contendo o caminho completo para os dois arquivos de expansão:

Kotlin

fun getAPKExpansionFiles(ctx: Context, mainVersion: Int, patchVersion: Int): Array<String> {
    val packageName = ctx.packageName
    val ret = mutableListOf<String>()
    if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
        // Build the full path to the app's expansion files
        val root = Environment.getExternalStorageDirectory()
        val expPath = File(root.toString() + EXP_PATH + packageName)

        // Check that expansion file path exists
        if (expPath.exists()) {
            if (mainVersion > 0) {
                val strMainPath = "$expPath${File.separator}main.$mainVersion.$packageName.obb"
                val main = File(strMainPath)
                if (main.isFile) {
                    ret += strMainPath
                }
            }
            if (patchVersion > 0) {
                val strPatchPath = "$expPath${File.separator}patch.$mainVersion.$packageName.obb"
                val main = File(strPatchPath)
                if (main.isFile) {
                    ret += strPatchPath
                }
            }
        }
    }
    return ret.toTypedArray()
}

Java

// The shared path to all app expansion files
private final static String EXP_PATH = "/Android/obb/";

static String[] getAPKExpansionFiles(Context ctx, int mainVersion,
      int patchVersion) {
    String packageName = ctx.getPackageName();
    Vector<String> ret = new Vector<String>();
    if (Environment.getExternalStorageState()
          .equals(Environment.MEDIA_MOUNTED)) {
        // Build the full path to the app's expansion files
        File root = Environment.getExternalStorageDirectory();
        File expPath = new File(root.toString() + EXP_PATH + packageName);

        // Check that expansion file path exists
        if (expPath.exists()) {
            if ( mainVersion > 0 ) {
                String strMainPath = expPath + File.separator + "main." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strMainPath);
                if ( main.isFile() ) {
                        ret.add(strMainPath);
                }
            }
            if ( patchVersion > 0 ) {
                String strPatchPath = expPath + File.separator + "patch." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strPatchPath);
                if ( main.isFile() ) {
                        ret.add(strPatchPath);
                }
            }
        }
    }
    String[] retArray = new String[ret.size()];
    ret.toArray(retArray);
    return retArray;
}

Você pode chamar esse método passando a ele o Context do seu aplicativo e a versão do arquivo de expansão que você quer.

Há várias maneiras de determinar o número da versão do arquivo de expansão. Uma maneira simples é salvar a versão em um arquivo SharedPreferences quando o download começar, consultando o nome do arquivo de expansão com o método getExpansionFileName(int index) da classe APKExpansionPolicy. Então, você poderá ver o código da versão lendo o arquivo SharedPreferences quando quiser acessar o arquivo de expansão.

Para mais informações sobre como ler no armazenamento compartilhado, consulte a documentação do Armazenamento de dados.

Usar a Biblioteca APK Expansion Zip

O pacote de expansão do APK do Google Market inclui uma biblioteca chamada "Biblioteca APK Expansion Zip", localizada em <sdk>/extras/google/google_market_apk_expansion/zip_file/. Essa é uma biblioteca opcional que ajuda você a ler seus arquivos de expansão quando eles são salvos como arquivos ZIP. O uso dessa biblioteca permite que você leia com facilidade os recursos dos arquivos de expansão ZIP como um sistema de arquivos virtual.

A Biblioteca APK Expansion Zip inclui as seguintes classes e APIs:

APKExpansionSupport
Fornece alguns métodos para acessar nomes de arquivos de expansão e arquivos ZIP:
getAPKExpansionFiles()
O mesmo método mostrado acima que retorna o caminho completo do arquivo para os dois arquivos de expansão.
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)
Retorna um ZipResourceFile que representa a soma do arquivo principal e do arquivo de patch. Ou seja, se você especificar mainVersion e patchVersion, isso retornará um ZipResourceFile que fornece acesso de leitura a todos os dados, com os dados do arquivo de patch mesclados com o arquivo principal.
ZipResourceFile
Representa um arquivo ZIP no armazenamento compartilhado e realiza todo o trabalho para fornecer um sistema de arquivos virtual com base nos arquivos ZIP. Você pode ter uma instância usando APKExpansionSupport.getAPKExpansionZipFile() ou com ZipResourceFile transmitindo o caminho para o arquivo de expansão. Essa classe inclui uma variedade de métodos úteis, mas você geralmente não precisa acessar a maioria deles. Dois métodos importantes são:
getInputStream(String assetPath)
Fornece um InputStream para ler um arquivo no arquivo ZIP. O assetPath precisa ser o caminho para o arquivo desejado, relativo à raiz do conteúdo do arquivo ZIP.
getAssetFileDescriptor(String assetPath)
Fornece um AssetFileDescriptor para um arquivo dentro do arquivo ZIP. O assetPath precisa ser o caminho para o arquivo desejado, relativo à raiz do conteúdo do arquivo ZIP. Isso é útil para algumas APIs do Android que exigem um AssetFileDescriptor, como algumas APIs MediaPlayer.
APEZProvider
A maioria dos aplicativos não precisa usar essa classe. Essa classe define um ContentProvider que organiza os dados dos arquivos ZIP usando um provedor de conteúdo Uri para fornecer acesso a arquivos para determinadas APIs do Android que esperam ter acesso a Uris de arquivos de mídia. Por exemplo, isso é útil se você quiser reproduzir um vídeo com VideoView.setVideoURI().

Pular a compactação ZIP de arquivos de mídia

Se você estiver usando arquivos de expansão para armazenar arquivos de mídia, um arquivo ZIP ainda permitirá que você use chamadas de reprodução de mídia Android que fornecem controles de compensação e comprimento (como MediaPlayer.setDataSource() e SoundPool.load()). Para que isso funcione, não realize mais compactação nos arquivos de mídia ao criar os pacotes ZIP. Por exemplo, ao usar a ferramenta zip, use a opção -n para especificar os sufixos de arquivo que não podem ser compactados:

zip -n .mp4;.ogg main_expansion media_files

Ler em um arquivo ZIP

Ao usar a Biblioteca APK Expansion Zip, a leitura de um arquivo do ZIP geralmente requer o seguinte:

Kotlin

// Get a ZipResourceFile representing a merger of both the main and patch files
val expansionFile =
        APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion)

// Get an input stream for a known file inside the expansion file ZIPs
expansionFile.getInputStream(pathToFileInsideZip).use {
    ...
}

Java

// Get a ZipResourceFile representing a merger of both the main and patch files
ZipResourceFile expansionFile =
    APKExpansionSupport.getAPKExpansionZipFile(appContext,
        mainVersion, patchVersion);

// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

O código acima oferece acesso a qualquer arquivo que exista no arquivo de expansão principal ou de patch, lendo em um mapa mesclado de todos os arquivos de ambos arquivos. Tudo o que você precisa para fornecer o método getAPKExpansionFile() é o android.content.Context do app e o número da versão do arquivo de expansão principal e do arquivo de expansão de patch.

Se preferir ler em um arquivo de expansão específico, você pode usar o construtor ZipResourceFile com o caminho para o arquivo de expansão desejado:

Kotlin

// Get a ZipResourceFile representing a specific expansion file
val expansionFile = ZipResourceFile(filePathToMyZip)

// Get an input stream for a known file inside the expansion file ZIPs
expansionFile.getInputStream(pathToFileInsideZip).use {
    ...
}

Java

// Get a ZipResourceFile representing a specific expansion file
ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip);

// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

Para mais informações sobre como usar essa biblioteca para arquivos de expansão, consulte a classe SampleDownloaderActivity do app de exemplo, que inclui mais código para verificar os arquivos transferidos por download usando verificação cíclica de redundância (CRC, na sigla em inglês). Saiba que, se você usar essa amostra como base para sua própria implementação, será necessário declarar o tamanho do byte dos arquivos de expansão na matriz xAPKS.

Testar seus arquivos de expansão

Antes de publicar seu aplicativo, há duas coisas que precisam ser testadas: a leitura e o download dos arquivos de expansão.

Testar leituras de arquivos

Antes de fazer upload do seu aplicativo para o Google Play, teste a capacidade do app de ler os arquivos no armazenamento compartilhado. Você só precisa adicionar os arquivos ao local apropriado no armazenamento compartilhado do dispositivo e iniciar o aplicativo:

  1. No seu dispositivo, crie o diretório adequado no armazenamento compartilhado em que o Google Play salvará seus arquivos.

    Por exemplo, se o nome do seu pacote for com.example.android, será necessário criar o diretório Android/obb/com.example.android/ no espaço de armazenamento compartilhado. Conecte o dispositivo de teste ao computador para ativar o armazenamento compartilhado e crie esse diretório manualmente.

  2. Adicione manualmente os arquivos de expansão a esse diretório. Não se esqueça de renomear os arquivos para que correspondam ao formato de nome de arquivo usado pelo Google Play.

    Por exemplo, independentemente do tipo de arquivo, o arquivo de expansão principal do aplicativo com.example.android precisa ser main.0300110.com.example.android.obb. O código da versão pode ser o valor que você quiser. Não se esqueça:

    • O arquivo de expansão principal sempre começa com main e o arquivo de patch começa com patch.
    • O nome do pacote sempre corresponde ao do APK a que o arquivo está anexado no Google Play.
  3. Agora que os arquivos de expansão estão no dispositivo, você pode instalar e executar seu aplicativo para testar esses arquivos.

Veja alguns lembretes sobre como gerenciar os arquivos de expansão:

  • Não exclua nem renomeie os arquivos .obb de expansão (mesmo se você descompactar os dados para um local diferente). Isso fará com que o Google Play (ou o próprio aplicativo) faça o download repetidamente do arquivo de expansão.
  • Não salve outros dados no diretório obb/. Se precisar descompactar dados, salve-os no local especificado por getExternalFilesDir().

Testar o download de arquivos

Como seu aplicativo precisa fazer o download manualmente dos arquivos de expansão quando é aberto pela primeira vez, é importante testar esse processo para garantir que o aplicativo possa consultar as URLs, fazer o download dos arquivos e salvá-los no dispositivo.

Para testar a implementação do procedimento de download manual do aplicativo, você pode publicá-lo na faixa de teste interna. Assim, ele só fica disponível para testadores autorizados. Se tudo funcionar conforme o esperado, seu aplicativo começará a fazer o download dos arquivos de expansão assim que a atividade principal começar.

Observação: antes, era possível testar um app fazendo upload de uma versão de "rascunho" não publicada. Esse recurso não está mais disponível. Em vez disso, é necessário publicá-lo em uma faixa de teste interna, fechada ou aberta. Para saber mais, consulte Aplicativos de rascunho não são mais compatíveis.

Atualizar seu app

Uma das grandes vantagens de usar arquivos de expansão no Google Play é a capacidade de atualizar seu aplicativo sem fazer de novo o download de todos os recursos originais. Como o Google Play permite que você forneça dois arquivos de expansão para cada APK, você pode usar o segundo arquivo como um patch que fornece atualizações e novos recursos. Isso evita a necessidade de fazer o download novamente do arquivo de expansão principal, que pode ser grande e caro para os usuários.

O arquivo de expansão de patch é tecnicamente igual ao arquivo de expansão principal e nem o sistema Android nem o Google Play fazem correções reais entre os arquivos de expansão principais e de patch. O código do aplicativo precisa executar os patches necessários.

Se você usar arquivos ZIP como arquivos de expansão, a Biblioteca APK Expansion Zip presente no pacote de expansão do APK incluirá a capacidade de mesclar o arquivo de patch com o arquivo de expansão principal.

Observação: mesmo que você só precise fazer modificações no arquivo de expansão do patch, atualize o APK para que o Google Play faça uma atualização. Se você não precisar de mudanças de código no aplicativo, basta atualizar versionCode no manifesto.

Contanto que você não modifique o arquivo de expansão principal associado ao APK no Play Console, os usuários que já instalaram o aplicativo não farão o download do arquivo de expansão principal. Os usuários existentes receberão apenas o APK atualizado e o novo arquivo de expansão de patch (mantendo o arquivo de expansão principal anterior).

Veja a seguir alguns problemas relacionados às atualizações de arquivos de expansão:

  • Pode haver somente dois arquivos de expansão por vez para o aplicativo. Um arquivo de expansão principal e um arquivo de expansão de patch. Durante uma atualização em um arquivo, o Google Play exclui a versão anterior (assim como seu aplicativo precisará fazer ao realizar atualizações manuais).
  • Ao adicionar um arquivo de expansão de patch, o sistema Android não corrige seu aplicativo nem o arquivo de expansão principal. Projete seu aplicativo para ser compatível com os dados de patches. No entanto, o pacote de expansão do APK inclui uma biblioteca para usar arquivos ZIP como arquivos de expansão, que mescla os dados do arquivo de patch com o arquivo de expansão principal para que você possa ler facilmente todos os dados dos arquivos de expansão.
.