File di espansione APK

Google Play richiede che l'APK compresso scaricato dagli utenti non superi i 100 MB. Per la maggior parte delle app, questo è molto spazio per il codice e gli asset dell'app. Tuttavia, alcune app richiedono più spazio per immagini ad alta fedeltà, file multimediali o altri asset di grandi dimensioni. In precedenza, se le dimensioni di download compresse dell'app superavano i 100 MB, dovevi ospitare e scaricare personalmente le risorse aggiuntive quando l'utente apre l'app. Hosting e pubblicazione di file aggiuntivi possono essere costosi e l'esperienza utente è spesso meno del previsto. Per rendere questo processo più semplice per te e più piacevole per gli utenti, Google Play ti consente di allegare due file di espansione di grandi dimensioni che integrano il tuo APK.

Google Play ospita i file di espansione della tua app e li pubblica sul dispositivo senza alcun costo. I file di espansione vengono salvati nell'archiviazione condivisa del dispositivo (la scheda SD o la partizione montabile USB; chiamata anche memoria "esterna") da cui l'app può accedervi. Sulla maggior parte dei dispositivi, Google Play scarica i file di espansione nello stesso momento in cui scarica l'APK, in modo che la tua app abbia tutto ciò di cui ha bisogno quando l'utente la apre per la prima volta. In alcuni casi, tuttavia, l'app deve scaricare i file da Google Play all'avvio dell'app.

Se preferisci evitare di utilizzare file di espansione e le dimensioni di download compresse dell'app sono superiori a 100 MB, ti consigliamo di caricare l'app utilizzando Android App Bundle, che consente dimensioni di download compresse fino a 200 MB. Inoltre, poiché l'utilizzo di app bundle influenza la generazione di APK e la firma su Google Play, gli utenti scaricano APK ottimizzati solo con il codice e le risorse di cui hanno bisogno per eseguire l'app. Non è necessario creare, firmare e gestire più APK o file di espansione e gli utenti ottengono download di dimensioni inferiori e più ottimizzati.

Panoramica

Ogni volta che carichi un APK utilizzando Google Play Console, hai la possibilità di aggiungere uno o due file di espansione all'APK. Ogni file può avere una dimensione massima di 2 GB e qualsiasi formato tu scelga, ma consigliamo di utilizzare un file compresso per risparmiare larghezza di banda durante il download. Concettualmente, ogni file di espansione svolge un ruolo diverso:

  • Il file di espansione principale è il file di espansione principale per le risorse aggiuntive richieste dalla tua app.
  • Il file di espansione patch è facoltativo e destinato ai piccoli aggiornamenti del file di espansione principale.

Anche se puoi utilizzare i due file di espansione come preferisci, ti consigliamo di fare in modo che gli asset principali vengano caricati dal file di espansione principale, che dovrebbe essere aggiornato raramente o mai. Il file di espansione patch deve essere più piccolo e fungere da "vettore di patch", viene aggiornato a ogni release principale o secondo necessità.

Tuttavia, anche se l'aggiornamento dell'app richiede solo un nuovo file di espansione patch, devi comunque caricare un nuovo APK con un versionCode aggiornato nel file manifest. Play Console non consente di caricare un file di espansione in un APK esistente.

Nota: il file di espansione patch è semanticamente uguale al file di espansione principale e puoi utilizzare ogni file come preferisci.

Formato del nome file

Ogni file di espansione caricato può avere un formato qualsiasi (ZIP, PDF, MP4 e così via). Puoi anche utilizzare lo strumento JOBB per incapsulare e criptare un set di file di risorse e patch successive per quel set. Indipendentemente dal tipo di file, Google Play li considera blob binari opachi e li rinomina utilizzando il seguente schema:

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

Questo schema comprende tre componenti:

main o patch
Specifica se il file è il file principale o di espansione patch. Possono esserci un solo file principale e un solo file patch per ogni APK.
<expansion-version>
Si tratta di un numero intero che corrisponde al codice di versione dell'APK a cui è prima associata l'espansione (corrisponde al valore android:versionCode dell'app).

L'opzione"First" è enfatizzata perché, sebbene Play Console ti consenta di riutilizzare un file di espansione caricato con un nuovo APK, il nome del file di espansione non cambia, in quanto mantiene la versione applicata al file quando hai caricato il file per la prima volta.

<package-name>
Nome del pacchetto in stile Java della tua app.

Ad esempio, supponiamo che la versione dell'APK sia 314159 e che il nome del pacchetto sia com.example.app. Se carichi un file di espansione principale, il file viene rinominato in questo modo:

main.314159.com.example.app.obb

Posizione archiviazione

Quando Google Play scarica i file di espansione su un dispositivo, li salva nella posizione di archiviazione condivisa del sistema. Per garantire un comportamento corretto, non devi eliminare, spostare o rinominare i file di espansione. Nel caso in cui sia necessario che l'app esegua il download da Google Play, devi salvare i file nello stesso percorso.

Il metodo getObbDir() restituisce la posizione specifica dei file di espansione nel formato seguente:

<shared-storage>/Android/obb/<package-name>/
  • <shared-storage> è il percorso dello spazio di archiviazione condiviso, disponibile dal giorno getExternalStorageDirectory().
  • <package-name> è il nome del pacchetto in stile Java della tua app, disponibile da getPackageName().

Per ogni app non sono mai presenti più di due file di espansione in questa directory. Uno è il file di espansione principale e l'altro è il file di espansione patch (se necessario). Le versioni precedenti vengono sovrascritte quando aggiorni l'app con nuovi file di espansione. A partire da Android 4.4 (livello API 19), le app possono leggere i file di espansione OBB senza autorizzazione per l'archiviazione esterna. Tuttavia, alcune implementazioni di Android 6.0 (livello API 23) e versioni successive richiedono comunque l'autorizzazione, pertanto dovrai dichiarare l'autorizzazione READ_EXTERNAL_STORAGE nel file manifest dell'app e richiedere l'autorizzazione in fase di runtime, come indicato di seguito:

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

Per Android 6 e versioni successive, l'autorizzazione per l'archiviazione esterna deve essere richiesta in fase di runtime. Tuttavia, alcune implementazioni di Android non richiedono l'autorizzazione per leggere i file OBB. Il seguente snippet di codice mostra come verificare l'accesso in lettura prima di chiedere l'autorizzazione per l'archiviazione esterna:

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 devi decomprimere i contenuti dei file di espansione, non eliminare i file di espansione OBB in seguito e non salvare i dati non pacchettizzati nella stessa directory. Salva i file non pacchettizzati nella directory specificata da getExternalFilesDir(). Tuttavia, se possibile, è preferibile utilizzare un formato file di espansione che consenta di leggere direttamente dal file, invece di dover decomprimere i dati. Ad esempio, abbiamo fornito un progetto di libreria chiamato APK Expansion Zip Library, che legge i dati direttamente dal file ZIP.

Attenzione: a differenza dei file APK, tutti i file salvati nello spazio di archiviazione condiviso possono essere letti dall'utente e da altre app.

Suggerimento: se stai pacchettizzando i file multimediali in un file ZIP, puoi utilizzare le chiamate di riproduzione dei contenuti multimediali sui file con controlli di offset e lunghezza (come MediaPlayer.setDataSource() e SoundPool.load()), senza dover decomprimere il file ZIP. Affinché questo comando funzioni, non devi eseguire un'ulteriore compressione dei file multimediali durante la creazione dei pacchetti ZIP. Ad esempio, quando usi lo strumento zip, devi usare l'opzione -n per specificare i suffissi di file che non devono essere compressi:
zip -n .mp4;.ogg main_expansion media_files

Procedura di download

Nella maggior parte dei casi, Google Play scarica e salva i file di espansione nello stesso momento in cui scarica l'APK sul dispositivo. Tuttavia, in alcuni casi Google Play non può scaricare i file di espansione oppure l'utente potrebbe aver eliminato file di espansione scaricati in precedenza. Per gestire queste situazioni, l'app deve poter scaricare autonomamente i file quando inizia l'attività principale, utilizzando un URL fornito da Google Play.

La procedura generale di download ha il seguente aspetto:

  1. L'utente sceglie di installare la tua app da Google Play.
  2. Se Google Play riesce a scaricare i file di espansione (come nel caso della maggior parte dei dispositivi), li scarica insieme all'APK.

    Se Google Play non riesce a scaricare i file di espansione, scarica solo l'APK.

  3. Quando l'utente avvia l'app, quest'ultima deve controllare se i file di espansione sono già salvati sul dispositivo.
    1. Se sì, l'app è pronta per l'uso.
    2. In caso contrario, l'app deve scaricare i file di espansione tramite HTTP da Google Play. La tua app deve inviare una richiesta al client Google Play utilizzando il servizio di licenza delle app di Google Play, che risponde con il nome, le dimensioni del file e l'URL di ogni file di espansione. Queste informazioni consentono di scaricare i file e salvarli nella posizione di archiviazione appropriata.

Attenzione: è fondamentale includere il codice necessario per scaricare i file di espansione da Google Play nel caso in cui i file non siano già presenti sul dispositivo all'avvio dell'app. Come spiegato nella sezione seguente relativa al download dei file di espansione, abbiamo reso disponibile una libreria che semplifica notevolmente questo processo ed esegue il download da un servizio con una quantità minima di codice.

Elenco di controllo per lo sviluppo

Di seguito è riportato un riepilogo delle attività che devi eseguire per utilizzare i file di espansione con la tua app:

  1. Innanzitutto, stabilisci se le dimensioni di download compresse della tua app devono essere superiori a 100 MB. Lo spazio è prezioso, pertanto ti consigliamo di ridurre il più possibile le dimensioni totali del download. Se la tua app utilizza più di 100 MB per fornire più versioni delle risorse grafiche per più densità dello schermo, ti consigliamo di pubblicare più APK in cui ogni APK contiene soltanto le risorse necessarie per le schermate scelte come target. Per ottenere risultati ottimali quando pubblichi su Google Play, carica un Android App Bundle, che include tutte le risorse e il codice compilato dell'app, ma impedisce la generazione e la firma degli APK su Google Play.
  2. Stabilisci quali risorse dell'app separare dall'APK e pacchettizzale in un file da utilizzare come file di espansione principale.

    Normalmente, devi utilizzare il secondo file di espansione patch solo quando esegui aggiornamenti al file di espansione principale. Tuttavia, se le tue risorse superano il limite di 2 GB per il file di espansione principale, puoi utilizzare il file di patch per il resto degli asset.

  3. Sviluppa l'app in modo che utilizzi le risorse dei file di espansione nella posizione di archiviazione condivisa del dispositivo.

    Ricorda che non è necessario eliminare, spostare o rinominare i file di espansione.

    Se la tua app non richiede un formato specifico, ti suggeriamo di creare file ZIP per i file di espansione e di leggerli utilizzando la Libreria ZIP di espansione dell'APK.

  4. Aggiungi logica all'attività principale della tua app per verificare se i file di espansione sono presenti sul dispositivo all'avvio. Se i file non si trovano sul dispositivo, utilizza il servizio di licenza delle app di Google Play per richiedere gli URL dei file di espansione, quindi scaricali e salvali.

    Per ridurre notevolmente la quantità di codice da scrivere e garantire un'esperienza utente positiva durante il download, ti consigliamo di utilizzare la Libreria Downloader per implementare il comportamento di download.

    Se invece di utilizzare la libreria crei il tuo servizio di download, tieni presente che non devi modificare il nome dei file di espansione e devi salvarli nella posizione di archiviazione appropriata.

Una volta terminato lo sviluppo dell'app, segui la guida per il test dei file di espansione.

Regole e limitazioni

L'aggiunta di file di espansione APK è una funzionalità disponibile quando carichi la tua app tramite Play Console. Quando carichi la tua app per la prima volta o la aggiorni che utilizza file di espansione, devi tenere conto delle seguenti regole e limitazioni:

  1. Ogni file di espansione non può superare i 2 GB.
  2. Per scaricare i file di espansione da Google Play, l'utente deve aver acquistato la tua app da Google Play. Google Play non fornirà gli URL per i file di espansione se l'app è stata installata con altri mezzi.
  3. Quando esegui il download dall'app, l'URL fornito da Google Play per ciascun file è univoco per ogni download e ogni file scade poco dopo essere stato fornito all'app.
  4. Se aggiorni la tua app con un nuovo APK o carichi più APK per la stessa app, puoi selezionare i file di espansione che hai caricato per un APK precedente. Il nome del file di espansione non cambia: viene conservata la versione ricevuta dall'APK a cui il file era stato originariamente associato.
  5. Se utilizzi file di espansione in combinazione con più APK per fornire file di espansione diversi per dispositivi diversi, devi comunque caricare APK separati per ciascun dispositivo, in modo da fornire un valore versionCode univoco e dichiarare filtri diversi per ogni APK.
  6. Non puoi aggiornare l'app modificando solo i file di espansione: devi caricare un nuovo APK per aggiornare l'app. Se le modifiche riguardano soltanto gli asset dei file di espansione, puoi aggiornare l'APK semplicemente modificando versionCode (e magari versionName).

  7. Non salvare altri dati nella directory obb/. Se devi decomprimere alcuni dati, salvali nella posizione specificata da getExternalFilesDir().
  8. Non eliminare o rinominare il file di espansione .obb (a meno che non esegui un aggiornamento). In caso contrario, Google Play (o la tua app stessa) scaricherà ripetutamente il file di espansione.
  9. Quando aggiorni manualmente un file di espansione, devi eliminare il file di espansione precedente.

Download dei file di espansione

Nella maggior parte dei casi, Google Play scarica e salva i file di espansione sul dispositivo nello stesso momento in cui installa o aggiorna l'APK. In questo modo, i file di espansione sono disponibili al primo avvio dell'app. Tuttavia, in alcuni casi l'app deve scaricare i file di espansione richiedendoli da un URL fornito in una risposta del servizio di licenze delle app di Google Play.

La logica di base necessaria per scaricare i file di espansione è la seguente:

  1. All'avvio dell'app, cerca i file di espansione nel percorso di archiviazione condiviso (nella directory Android/obb/<package-name>/).
    1. Se sono presenti i file di espansione, significa che è tutto pronto e l'app può continuare.
    2. Se i file di espansione non sono presenti:
      1. Effettua una richiesta utilizzando le licenze delle app di Google Play per recuperare nomi, dimensioni e URL dei file di espansione dell'app.
      2. Utilizza gli URL forniti da Google Play per scaricare i file di espansione e salvare i file di espansione. Devi salvare i file nella posizione di archiviazione condivisa (Android/obb/<package-name>/) e usare il nome file esatto fornito dalla risposta di Google Play.

        Nota: l'URL fornito da Google Play per i file di espansione è univoco per ogni download, ognuno dei quali scade poco dopo essere stato fornito alla tua app.

Se la tua app è senza costi (non a pagamento), è probabile che tu non abbia utilizzato il servizio di licenza delle app. È stata pensata principalmente per applicare le norme relative alle licenze dell'app e per garantire che l'utente abbia il diritto di utilizzarla (che l'utente ha legittimamente pagato su Google Play). Per semplificare la funzionalità dei file di espansione, il servizio di licenze è stato migliorato in modo da fornire una risposta alla tua app contenente l'URL dei file di espansione dell'app ospitati su Google Play. Pertanto, anche se la tua app è senza costi per gli utenti, devi includere la libreria di verifica licenza (LVL) per utilizzare i file di espansione APK. Ovviamente, se l'app è senza costi, non è necessario applicare la verifica della licenza: è sufficiente che la libreria esegua la richiesta che restituisce l'URL dei file di espansione.

Nota: che la tua app sia senza costi o meno, Google Play restituisce gli URL dei file di espansione solo se l'utente ha acquisito l'app da Google Play.

Oltre all'LVL, è necessario un set di codice che scarichi i file di espansione tramite una connessione HTTP e li salvi nella posizione corretta nello spazio di archiviazione condiviso del dispositivo. Durante la creazione di questa procedura nella tua app, ci sono diversi aspetti che dovresti prendere in considerazione:

  • Il dispositivo potrebbe non avere spazio sufficiente per i file di espansione, quindi verifica prima di iniziare il download e avvisa l'utente se lo spazio non è sufficiente.
  • I download di file devono avvenire in un servizio in background per evitare di bloccare l'interazione dell'utente e consentirgli di uscire dall'app durante il completamento del download.
  • Durante la richiesta e il download possono verificarsi una serie di errori che devi gestire con cautela.
  • La connettività di rete può cambiare durante il download, quindi devi gestire queste modifiche e, in caso di interruzione, riprendere il download quando possibile.
  • Mentre il download viene eseguito in background, devi fornire una notifica che indica l'avanzamento del download, avvisa l'utente al termine e riporta l'utente all'app quando viene selezionato.

Per semplificare questa operazione, abbiamo creato la Libreria Downloader, che richiede gli URL dei file di espansione tramite il servizio di licenze, scarica i file di espansione, esegue tutte le attività elencate sopra e consente persino all'attività di mettere in pausa e riprendere il download. Aggiungendo la libreria di download e alcuni hook di codice all'app, quasi tutto il lavoro necessario per scaricare i file di espansione è già stato programmato. Pertanto, al fine di offrire la migliore esperienza utente con il minimo sforzo per tuo conto, ti consigliamo di utilizzare la libreria di download per scaricare i file di espansione. Le informazioni riportate nelle sezioni seguenti spiegano come integrare la libreria nella tua app.

Se preferisci sviluppare una tua soluzione per scaricare i file di espansione utilizzando gli URL di Google Play, devi seguire la documentazione relativa alle licenze delle app per eseguire una richiesta di licenza, quindi recuperare i nomi, le dimensioni e gli URL dei file di espansione dagli extra della risposta. Devi utilizzare la classe APKExpansionPolicy (inclusa nella libreria di verifica delle licenze) come criterio di licenza, che acquisisce i nomi, le dimensioni e gli URL dei file di espansione del servizio di licenze.

Informazioni sulla libreria di download

Per utilizzare i file di espansione APK nella tua app e offrire la migliore esperienza utente con il minimo sforzo per tuo conto, ti consigliamo di utilizzare la libreria di download inclusa nel pacchetto della libreria di espansione APK di Google Play. Questa libreria scarica i file di espansione in background, mostra una notifica per l'utente con lo stato del download, gestisce la perdita della connettività di rete, riprende il download quando possibile e altro ancora.

Per implementare i download dei file di espansione utilizzando la libreria dei download, è sufficiente:

  • Estendi una sottoclasse Service e una sottoclasse BroadcastReceiver speciali, ognuna delle quali richiede solo poche righe di codice.
  • Aggiungi un po' di logica all'attività principale per verificare se i file di espansione sono già stati scaricati e, in caso contrario, richiama il processo di download e mostra un'UI di avanzamento.
  • Implementa un'interfaccia di callback con alcuni metodi nell'attività principale che riceva aggiornamenti sull'avanzamento dei download.

Le sezioni seguenti spiegano come configurare l'app utilizzando la libreria di download.

Preparazione all'utilizzo della libreria dei download

Per utilizzare la libreria dei download, devi scaricare due pacchetti da SDK Manager e aggiungere le librerie appropriate alla tua app.

Innanzitutto, apri Gestione SDK Android (Strumenti > Gestore SDK) e in Aspetto e comportamento > Impostazioni di sistema > SDK Android, seleziona la scheda Strumenti SDK per selezionare e scaricare:

  • Pacchetto della libreria di licenze di Google Play
  • Pacchetto della libreria di espansione APK di Google Play

Crea un nuovo modulo della libreria per la libreria di verifica delle licenze e la libreria dei download. Per ogni biblioteca:

  1. Seleziona File > New > New Module (File > Nuovo > Nuovo modulo).
  2. Nella finestra Crea nuovo modulo, seleziona Libreria Android, quindi seleziona Avanti.
  3. Specifica un nome dell'app/della libreria, ad esempio "Libreria licenze Google Play" e "Libreria dei download di Google Play", scegli Livello minimo dell'SDK, quindi seleziona Fine.
  4. Seleziona File > Struttura del progetto.
  5. Seleziona la scheda Proprietà e, in Repository della libreria, inserisci la libreria dalla directory <sdk>/extras/google/ (play_licensing/ per la libreria di verifica delle licenze o play_apk_expansion/downloader_library/ per la libreria di downloader).
  6. Seleziona OK per creare il nuovo modulo.

Nota: la libreria dei download dipende dalla libreria di verifica delle licenze. Assicurati di aggiungere la libreria di verifica delle licenze alle proprietà del progetto della libreria dei download.

In alternativa, dalla riga di comando, aggiorna il progetto in modo da includere le librerie:

  1. Cambia le directory nella directory <sdk>/tools/.
  2. Esegui android update project con l'opzione --library per aggiungere sia il LVL sia la libreria Downloader al tuo progetto. Ad esempio:
    android update project --path ~/Android/MyApp \
    --library ~/android_sdk/extras/google/market_licensing \
    --library ~/android_sdk/extras/google/market_apk_expansion/downloader_library
    

Dopo aver aggiunto alla tua app sia la libreria di verifica delle licenze sia la libreria dei download, potrai integrare rapidamente la possibilità di scaricare i file di espansione da Google Play. Il formato che scegli per i file di espansione e la modalità di lettura dallo spazio di archiviazione condiviso sono un'implementazione separata che dovresti prendere in considerazione in base alle tue esigenze dell'app.

Suggerimento: il pacchetto di espansione APK include un'app di esempio che mostra come utilizzare la libreria dei downloader in un'app. L'esempio utilizza una terza libreria disponibile nel pacchetto di espansione APK, chiamata libreria ZIP di espansione APK. Se prevedi di utilizzare file ZIP per i file di espansione, ti suggeriamo di aggiungere anche la libreria ZIP di espansione APK alla tua app. Per ulteriori informazioni, consulta la sezione riportata di seguito sull'utilizzo della libreria ZIP di espansione APK.

Dichiarazione delle autorizzazioni utente

Per poter scaricare i file di espansione, la libreria downloader richiede diverse autorizzazioni che devi dichiarare nel file manifest dell'app. Sono:

<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>

Nota: per impostazione predefinita, la libreria Downloader richiede il livello API 4, mentre l'APK Expansion Zip Library richiede il livello API 5.

Implementazione del servizio di downloader

Per eseguire i download in background, la libreria dei download fornisce la propria sottoclasse Service chiamata DownloaderService, che devi estendere. Oltre al download automatico dei file di espansione, il DownloaderService:

  • Registra un BroadcastReceiver che rimane in ascolto delle modifiche alla connettività di rete del dispositivo (la trasmissione CONNECTIVITY_ACTION) per sospendere il download quando necessario (ad esempio a causa di una perdita di connettività) e riprenderlo quando possibile (la connettività viene acquisita).
  • Pianifica un allarme RTC_WAKEUP per riprovare il download nei casi in cui il servizio viene interrotto.
  • Crea un elemento Notification personalizzato che mostra l'avanzamento del download ed eventuali errori o modifiche dello stato.
  • Consente all'app di mettere in pausa e riprendere manualmente il download.
  • Verifica che l'archivio condiviso sia montato e disponibile, che i file non esistano già e che lo spazio sia sufficiente prima di scaricare i file di espansione. Poi avvisa l'utente se una di queste non è vera.

Tutto ciò che devi fare è creare una classe nell'app che estenda la classe DownloaderService e sostituisca tre metodi per fornire dettagli specifici sull'app:

getPublicKey()
Deve restituire una stringa che corrisponda alla chiave pubblica RSA con codifica Base64 per il tuo account publisher, disponibile nella pagina del profilo in Play Console (consulta la sezione Configurazione per le licenze).
getSALT()
Deve restituire un array di byte casuali che la licenza Policy utilizza per creare una Obfuscator. Il sale garantisce che il file SharedPreferences offuscato in cui sono salvati i dati delle licenze sia univoco e non rilevabile.
getAlarmReceiverClassName()
Questo deve restituire il nome della classe BroadcastReceiver nell'app che dovrebbe ricevere l'allarme che indica che il download deve essere riavviato (il che potrebbe verificarsi se il servizio di downloader si arresta in modo imprevisto).

Ad esempio, ecco un'implementazione completa di 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();
    }
}

Nota: devi aggiornare il valore BASE64_PUBLIC_KEY in modo che diventi la chiave pubblica appartenente al tuo account editore. Puoi trovare la chiave nella Console per gli sviluppatori tra le informazioni del profilo. Questa operazione è necessaria anche durante il test dei download.

Ricorda di dichiarare il servizio nel file manifest:

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

Implementazione del ricevitore di allarme

Per monitorare l'avanzamento dei download del file e riavviare il download, se necessario, l'DownloaderService pianifica un allarme RTC_WAKEUP che invia un Intent a un BroadcastReceiver nella tua app. Devi definire il valore BroadcastReceiver per chiamare un'API dalla libreria dei downloader che verifica lo stato del download e, se necessario, riavvialo.

Devi semplicemente sostituire il metodo onReceive() per chiamare DownloaderClientMarshaller.startDownloadServiceIfRequired().

Ecco alcuni esempi:

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();
        }
    }
}

Tieni presente che questa è la classe per la quale devi restituire il nome nel metodo getAlarmReceiverClassName() del tuo servizio (vedi la sezione precedente).

Ricorda di dichiarare il destinatario nel file manifest:

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

Avvio del download

L'attività principale nell'app (quella avviata dall'icona in Avvio applicazioni) è responsabile di verificare se i file di espansione sono già sul dispositivo e di avviare il download, in caso contrario.

L'avvio del download utilizzando la libreria dei download richiede le seguenti procedure:

  1. Controlla se i file sono stati scaricati.

    La libreria Downloader include alcune API della classe Helper per aiutarti in questo processo:

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

    Ad esempio, l'app di esempio fornita nel pacchetto di espansione APK chiama il seguente metodo nel metodo onCreate() dell'attività per verificare se i file di espansione esistono già sul 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;
    }
    

    In questo caso, ogni oggetto XAPKFile contiene il numero di versione e le dimensioni di un file di espansione noto, nonché un valore booleano che indica se si tratta del file di espansione principale. (per informazioni dettagliate, vedi la classe SampleDownloaderActivity dell'app di esempio).

    Se questo metodo restituisce false, l'app deve iniziare il download.

  2. Avvia il download chiamando il metodo statico DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass).

    Il metodo accetta i seguenti parametri:

    • context: Context della tua app.
    • notificationClient: un PendingIntent per avviare l'attività principale. Questo valore viene utilizzato nel Notification creato da DownloaderService per mostrare l'avanzamento del download. Quando l'utente seleziona la notifica, il sistema richiama il PendingIntent che fornisci qui e dovrebbe aprire l'attività che mostra l'avanzamento del download (di solito la stessa attività che ha avviato il download).
    • serviceClass: l'oggetto Class per l'implementazione di DownloaderService, necessario per avviare il servizio e iniziare il download, se necessario.

    Il metodo restituisce un numero intero che indica se il download è obbligatorio o meno. I valori possibili sono:

    • NO_DOWNLOAD_REQUIRED: viene restituito questo valore se i file esistono già o se è già in corso un download.
    • LVL_CHECK_REQUIRED: restituito se è richiesta la verifica della licenza per acquisire gli URL dei file di espansione.
    • DOWNLOAD_REQUIRED: viene restituito questo valore se gli URL dei file di espansione sono già noti, ma non sono stati scaricati.

    Il comportamento di LVL_CHECK_REQUIRED e DOWNLOAD_REQUIRED è sostanzialmente lo stesso e normalmente non è il tuo caso. Nella tua attività principale che chiama startDownloadServiceIfRequired(), puoi semplicemente controllare se la risposta è NO_DOWNLOAD_REQUIRED. Se la risposta è diversa da NO_DOWNLOAD_REQUIRED, la libreria dei download inizia il download e devi aggiornare l'interfaccia utente delle attività per visualizzare l'avanzamento del download (vedi il passaggio successivo). Se la risposta è NO_DOWNLOAD_REQUIRED, i file sono disponibili e l'app può essere avviata.

    Ecco alcuni esempi:

    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 il metodo startDownloadServiceIfRequired() restituisce informazioni diverse da NO_DOWNLOAD_REQUIRED, crea un'istanza di IStub chiamando DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService). IStub fornisce un'associazione tra le tue attività al servizio di download, in modo che l'attività riceva callback sull'avanzamento del download.

    Per creare un'istanza di IStub chiamando CreateStub(), devi passare un'implementazione dell'interfaccia IDownloaderClient e dell'implementazione di DownloaderService. La prossima sezione, Ricezione dell'avanzamento dei download, illustra l'interfaccia di IDownloaderClient, che in genere dovresti implementare nella classe Activity per poter aggiornare l'interfaccia utente dell'attività quando lo stato del download cambia.

    Ti consigliamo di chiamare CreateStub() per creare un'istanza di IStub durante il metodo onCreate() dell'attività, dopo che startDownloadServiceIfRequired() ha avviato il download.

    Ad esempio, nell'esempio di codice precedente per onCreate(), puoi rispondere al risultato di startDownloadServiceIfRequired() come segue:

    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;
            }
    

    Dopo aver restituito il metodo onCreate(), la tua attività riceve una chiamata a onResume(), dove dovresti chiamare connect() su IStub, trasmettendo il valore Context dell'app. Al contrario, devi chiamare disconnect() nel callback onStop() dell'attività.

    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();
    }
    

    La chiamata a connect() su IStub associa la tua attività a DownloaderService in modo che l'attività riceva callback relativi alle modifiche allo stato di download attraverso l'interfaccia IDownloaderClient.

Ricezione dell'avanzamento del download in corso...

Per ricevere aggiornamenti relativi all'avanzamento dei download e per interagire con DownloaderService, devi implementare l'interfaccia IDownloaderClient della Libreria downloader. In genere, l'attività che utilizzi per avviare il download dovrebbe implementare questa interfaccia per visualizzare l'avanzamento del download e inviare richieste al servizio.

I metodi di interfaccia richiesti per IDownloaderClient sono:

onServiceConnected(Messenger m)
Dopo aver creato un'istanza di IStub nella tua attività, riceverai una chiamata a questo metodo, che trasmette un oggetto Messenger connesso alla tua istanza di DownloaderService. Per inviare richieste al servizio, ad esempio per mettere in pausa e riprendere i download, devi chiamare DownloaderServiceMarshaller.CreateProxy() per ricevere l'interfaccia IDownloaderService collegata al servizio.

Un'implementazione consigliata ha il seguente aspetto:

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());
}

Con l'oggetto IDownloaderService inizializzato, puoi inviare comandi al servizio di downloader, ad esempio per mettere in pausa e riprendere il download (requestPauseDownload() e requestContinueDownload()).

onDownloadStateChanged(int newState)
Il servizio di download richiama questa funzionalità quando si verifica una modifica dello stato di download, ad esempio l'inizio o il completamento del download.

Il valore newState sarà uno dei numerosi valori possibili specificati in una delle costanti STATE_* della classe IDownloaderClient.

Per fornire un messaggio utile ai tuoi utenti, puoi richiedere una stringa corrispondente a ogni stato chiamando Helpers.getDownloaderStringResourceIDFromState(). Questo restituisce l'ID risorsa per una delle stringhe in bundle con la libreria di download. Ad esempio, la stringa "Download in pausa perché sei in roaming" corrisponde a STATE_PAUSED_ROAMING.

onDownloadProgress(DownloadProgressInfo progress)
Il servizio di download effettua la chiamata per consegnare un oggetto DownloadProgressInfo, che descrive varie informazioni sull'avanzamento del download, tra cui il tempo rimanente stimato, la velocità attuale, l'avanzamento complessivo e il totale per consentirti di aggiornare l'UI di avanzamento del download.

Suggerimento: per esempi di questi callback che aggiornano l'interfaccia utente di avanzamento del download, vedi SampleDownloaderActivity nell'app di esempio fornita con il pacchetto di espansione APK.

Di seguito sono riportati alcuni metodi pubblici che potresti trovare utili per l'interfaccia IDownloaderService:

requestPauseDownload()
Metti in pausa il download.
requestContinueDownload()
Riprende un download in pausa.
setDownloadFlags(int flags)
Imposta le preferenze utente per i tipi di rete da cui puoi scaricare i file. L'implementazione corrente supporta un flag, FLAGS_DOWNLOAD_OVER_CELLULAR, ma puoi aggiungerne altri. Per impostazione predefinita, questo flag non è abilitato, quindi l'utente deve essere connesso a una rete Wi-Fi per scaricare i file di espansione. Potresti voler indicare una preferenza all'utente per abilitare i download tramite la rete mobile. In questo caso, puoi chiamare:

Kotlin

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

Java

remoteService
    .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);

Utilizzo di APKExpansionPolicy

Se decidi di creare un tuo servizio di downloader personalizzato invece di utilizzare la Libreria Downloader di Google Play, devi comunque usare il APKExpansionPolicy fornito nella Libreria di verifica delle licenze. La classe APKExpansionPolicy è quasi identica a ServerManagedPolicy (disponibile nella libreria di verifica delle licenze di Google Play), ma include una gestione aggiuntiva per gli extra per le risposte del file di espansione APK.

Nota: se utilizzi la Libreria Downloader come spiegato nella sezione precedente, la libreria esegue tutte le interazioni con il APKExpansionPolicy, in modo da non dover utilizzare direttamente questa classe.

La classe include metodi per aiutarti a ottenere le informazioni necessarie sui file di espansione disponibili:

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

Per ulteriori informazioni su come utilizzare APKExpansionPolicy quando non usi la Libreria Downloader, consulta la documentazione relativa all'aggiunta di licenze all'app, che spiega come implementare un criterio di licenza come questo.

Lettura del file di espansione

Dopo aver salvato i file di espansione APK sul dispositivo, la modalità di lettura dei file dipende dal tipo di file utilizzato. Come illustrato nella panoramica, i file di espansione possono essere di qualsiasi tipo, ma vengono rinominati utilizzando un determinato formato di nome file e vengono salvati in <shared-storage>/Android/obb/<package-name>/.

Indipendentemente da come leggi i file, dovresti sempre controllare innanzitutto che la memoria esterna sia disponibile per la lettura. È possibile che l'utente abbia montato lo spazio di archiviazione su un computer tramite USB o che abbia effettivamente rimosso la scheda SD.

Nota: all'avvio dell'app, devi sempre controllare se lo spazio di archiviazione esterno è disponibile e leggibile chiamando il numero getExternalStorageState(). Questo restituisce una delle numerose stringhe possibili che rappresentano lo stato della memoria esterna. Affinché la tua app possa leggerlo, il valore restituito deve essere MEDIA_MOUNTED.

Recupero dei nomi dei file

Come descritto nella panoramica, i file di espansione APK vengono salvati utilizzando un formato di nome file specifico:

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

Per ottenere la posizione e i nomi dei file di espansione, devi utilizzare i metodi getExternalStorageDirectory() e getPackageName() per creare il percorso dei file.

Di seguito è riportato un metodo che puoi utilizzare nella tua app per ottenere un array contenente il percorso completo di entrambi i file di espansione:

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;
}

Puoi chiamare questo metodo passando Context all'app e alla versione del file di espansione desiderato.

Esistono diversi modi per determinare il numero di versione del file di espansione. Un modo semplice è salvare la versione in un file SharedPreferences all'inizio del download eseguendo una query sul nome del file di espansione con il metodo getExpansionFileName(int index) della classe APKExpansionPolicy. Puoi quindi ottenere il codice di versione leggendo il file SharedPreferences quando vuoi accedere al file di espansione.

Per ulteriori informazioni sulla lettura dallo spazio di archiviazione condiviso, consulta la documentazione sull'archiviazione dei dati.

Utilizzare la libreria ZIP di espansione dell'APK

Il pacchetto di espansione APK di Google Market include una libreria denominata libreria ZIP di espansione APK (situata in <sdk>/extras/google/google_market_apk_expansion/zip_file/), che è una libreria facoltativa che ti consente di leggere i file di espansione quando vengono salvati come file ZIP. Questa libreria consente di leggere facilmente le risorse dai file di espansione ZIP come file system virtuale.

La libreria Zip di espansione APK include le seguenti classi e API:

APKExpansionSupport
Offre alcuni metodi per accedere ai nomi dei file di espansione e ai file ZIP:
getAPKExpansionFiles()
Lo stesso metodo illustrato sopra che restituisce il percorso file completo a entrambi i file di espansione.
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)
Restituisci un ZipResourceFile che rappresenta la somma sia del file principale sia del file di patch. In altre parole, se specifichi sia mainVersion sia patchVersion, viene restituito un ZipResourceFile che fornisce accesso in lettura a tutti i dati, con i dati del file di patch uniti al file principale.
ZipResourceFile
Rappresenta un file ZIP nello spazio di archiviazione condiviso ed esegue tutto il lavoro per fornire un file system virtuale basato sui tuoi file ZIP. Puoi ottenere un'istanza utilizzando APKExpansionSupport.getAPKExpansionZipFile() o con ZipResourceFile passando il percorso al file di espansione. Questa classe include una serie di metodi utili, ma in genere non è necessario accedere alla maggior parte di questi. Ecco un paio di metodi importanti:
getInputStream(String assetPath)
Fornisce un valore InputStream per leggere un file all'interno del file ZIP. assetPath deve essere il percorso del file desiderato, rispetto alla directory principale dei contenuti del file ZIP.
getAssetFileDescriptor(String assetPath)
Fornisce un AssetFileDescriptor per un file all'interno del file ZIP. assetPath deve essere il percorso del file desiderato, rispetto alla directory principale dei contenuti del file ZIP. Questo è utile per alcune API Android che richiedono un AssetFileDescriptor, come alcune API MediaPlayer.
APEZProvider
Per la maggior parte delle app non è necessario utilizzare questo corso. Questa classe definisce un ContentProvider che esegue il marshling dei dati dei file ZIP tramite un fornitore di contenuti Uri per fornire l'accesso ai file per alcune API Android che si aspettano l'accesso Uri ai file multimediali. Ciò è utile, ad esempio, se vuoi riprodurre un video con VideoView.setVideoURI().

Saltare la compressione ZIP dei file multimediali

Se utilizzi file di espansione per archiviare file multimediali, un file ZIP ti consente comunque di usare le chiamate di riproduzione di contenuti multimediali Android che offrono controlli di offset e lunghezza (come MediaPlayer.setDataSource() e SoundPool.load()). Affinché questa operazione funzioni, non devi eseguire un'ulteriore compressione dei file multimediali durante la creazione dei pacchetti ZIP. Ad esempio, quando usi lo strumento zip, dovresti usare l'opzione -n per specificare i suffissi di file che non devono essere compressi:

zip -n .mp4;.ogg main_expansion media_files

Lettura da un file ZIP

Quando utilizzi la libreria ZIP di espansione dell'APK, la lettura di un file ZIP richiede in genere quanto segue:

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);

Il codice riportato sopra consente di accedere a qualsiasi file esistente nel file di espansione principale o nel file di espansione patch, leggendo da una mappa unita di tutti i file di entrambi i file. Tutto ciò che ti serve per fornire il metodo getAPKExpansionFile() sono l'app android.content.Context e il numero di versione sia del file di espansione principale sia del file di espansione delle patch.

Se preferisci leggere da un file di espansione specifico, puoi utilizzare il costruttore ZipResourceFile con il percorso del file di espansione desiderato:

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);

Per ulteriori informazioni sull'utilizzo di questa libreria per i file di espansione, consulta la classe SampleDownloaderActivity dell'app di esempio, che include codice aggiuntivo per verificare i file scaricati utilizzando CRC. Tieni presente che, se utilizzi questo esempio come base per la tua implementazione, è necessario dichiarare le dimensioni in byte dei file di espansione nell'array xAPKS.

Test dei file di espansione

Prima di pubblicare l'app, devi testare due cose: lettura dei file di espansione e download dei file.

Test delle letture dei file

Prima di caricare l'app su Google Play, devi verificare la capacità dell'app di leggere i file dallo spazio di archiviazione condiviso. Devi soltanto aggiungere i file nella posizione appropriata nello spazio di archiviazione condiviso del dispositivo e avviare l'app:

  1. Sul dispositivo, crea la directory appropriata nello spazio di archiviazione condiviso in cui Google Play salverà i tuoi file.

    Ad esempio, se il nome del pacchetto è com.example.android, devi creare la directory Android/obb/com.example.android/ nello spazio di archiviazione condiviso. Collega il dispositivo di test al computer per montare l'archivio condiviso e creare manualmente questa directory.

  2. Aggiungi manualmente i file di espansione alla directory. Assicurati di rinominare i file in modo che corrispondano al formato del nome file che verrà utilizzato da Google Play.

    Ad esempio, indipendentemente dal tipo di file, il file di espansione principale dell'app com.example.android deve essere main.0300110.com.example.android.obb. Il codice di versione può essere qualsiasi valore desiderato. Ricorda:

    • Il file di espansione principale inizia sempre con main, mentre il file di patch inizia con patch.
    • Il nome del pacchetto corrisponde sempre a quello dell'APK a cui è allegato il file su Google Play.
  3. Ora che i file di espansione sono sul dispositivo, puoi installare ed eseguire l'app per testare i file di espansione.

Di seguito sono riportate alcune informazioni da ricordare sulla gestione dei file di espansione:

  • Non eliminare o rinominare i file di espansione .obb, anche se decomprimi i dati in una posizione diversa. In questo modo Google Play (o la tua app stessa) scaricherà ripetutamente il file di espansione.
  • Non salvare altri dati nella directory obb/. Se devi decomprimere alcuni dati, salvali nella posizione specificata da getExternalFilesDir().

Test dei download di file

Poiché a volte l'app deve scaricare manualmente i file di espansione alla prima apertura, è importante testare questo processo per assicurarti che l'app possa cercare gli URL, scaricare i file e salvarli sul dispositivo.

Per testare l'implementazione della procedura di download manuale per la tua app, puoi pubblicarla nel canale di test interno in modo che sia disponibile solo per i tester autorizzati. Se tutto funziona come previsto, l'app dovrebbe iniziare a scaricare i file di espansione non appena inizia l'attività principale.

Nota: in precedenza, potevi testare un'app caricando una versione "bozza" non pubblicata. Questa funzionalità non è più supportata. Devi pubblicarli in un canale di test interno, chiuso o aperto. Per ulteriori informazioni, consulta la sezione Le bozze di app non sono più supportate.

Aggiornamento dell'app

Uno dei grandi vantaggi dell'utilizzo dei file di espansione su Google Play è la possibilità di aggiornare l'app senza dover scaricare nuovamente tutti gli asset originali. Poiché Google Play ti consente di fornire due file di espansione per ogni APK, puoi utilizzare il secondo file come una "patch" che fornisce aggiornamenti e nuovi asset. In questo modo non sarà necessario scaricare nuovamente il file di espansione principale, che potrebbe essere di grandi dimensioni e costoso per gli utenti.

Il file di espansione patch è tecnicamente uguale al file di espansione principale e né il sistema Android né Google Play eseguono l'applicazione di patch tra i file di espansione principali e quelli di espansione delle patch. Il codice dell'app deve eseguire autonomamente le patch necessarie.

Se utilizzi file ZIP come file di espansione, la libreria ZIP di espansione APK inclusa nel pacchetto di espansione APK include la possibilità di unire il file di patch al file di espansione principale.

Nota: anche se devi solo apportare modifiche al file di espansione delle patch, devi comunque aggiornare l'APK per consentire a Google Play di eseguire un aggiornamento. Se non hai bisogno di modifiche al codice nell'app, devi semplicemente aggiornare versionCode nel manifest.

Se non modifichi in Play Console il file di espansione principale associato all'APK, gli utenti che hanno installato in precedenza la tua app non scaricheranno il file di espansione principale. Gli utenti esistenti ricevono solo l'APK aggiornato e il nuovo file di espansione delle patch (contenente il file di espansione principale precedente).

Di seguito sono riportati alcuni problemi da tenere presente in merito agli aggiornamenti dei file di espansione:

  • Possono esserci solo due file di espansione alla volta per l'app. Un file di espansione principale e un file di espansione patch. Durante l'aggiornamento di un file, Google Play elimina la versione precedente (e lo stesso deve fare la tua app quando esegui aggiornamenti manuali).
  • Quando aggiungi un file di espansione patch, il sistema Android non corregge effettivamente l'app o il file di espansione principale. Devi progettare l'app in modo che supporti i dati delle patch. Tuttavia, il pacchetto di espansione APK include una libreria per utilizzare i file ZIP come file di espansione, che unisce i dati del file patch nel file di espansione principale in modo da poter leggere facilmente tutti i dati dei file di espansione.