Avviso : quando la tua app esegue la procedura di verifica delle licenze sul lato client, per i potenziali utenti malintenzionati è più facile modificare o rimuovere la logica associata a questa procedura di verifica.
Per questo motivo, ti consigliamo vivamente di eseguire la verifica delle licenze lato server.
Dopo aver configurato un account editore e un ambiente di sviluppo (consulta la sezione Configurazione per la concessione delle licenze), puoi aggiungere la verifica delle licenze alla tua app con la libreria di verifica delle licenze (LVL).
L'aggiunta della verifica delle licenze con LVL prevede le seguenti attività:
- Aggiunta dell'autorizzazione per le licenze al file manifest dell'applicazione.
- Implementazione di una norma: puoi scegliere una delle implementazioni complete fornite nella LVL o crearne una personalizzata.
- Implementa un Obfuscator, se il tuo
Policy
memorizzerà nella cache i dati delle risposte delle licenze. - Aggiunta di codice per verificare la licenza nell'attività principale dell'applicazione.
- Implementazione di un DeviceLimiter (facoltativa e non consigliata per la maggior parte delle applicazioni).
Queste attività sono descritte nelle sezioni seguenti. Al termine dell'integrazione, dovresti essere in grado di compilare correttamente l'applicazione e iniziare i test, come descritto in Configurazione dell'ambiente di test.
Per una panoramica dell'insieme completo dei file di origine inclusi nell'LVL, consulta Riepilogo delle classi e delle interfacce LVL.
Aggiunta dell'autorizzazione per le licenze
Per poter utilizzare l'applicazione Google Play per inviare un controllo delle licenze al server, l'applicazione deve richiedere l'autorizzazione appropriata, com.android.vending.CHECK_LICENSE
. Se l'applicazione non dichiara l'autorizzazione per la licenza, ma tenta di avviare un controllo delle licenze, l'LVL genera un'eccezione di sicurezza.
Per richiedere l'autorizzazione per la licenza nella tua applicazione, dichiara un elemento <uses-permission>
come elemento secondario di <manifest>
, come segue:
<uses-permission
android:name="com.android.vending.CHECK_LICENSE" />
Ad esempio, ecco come l'applicazione di esempio LVL dichiara l'autorizzazione:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ..."> <!-- Devices >= 3 have version of Google Play that supports licensing. --> <uses-sdk android:minSdkVersion="3" /> <!-- Required permission to check licensing. --> <uses-permission android:name="com.android.vending.CHECK_LICENSE" /> ... </manifest>
Nota: al momento non puoi dichiarare l'autorizzazione CHECK_LICENSE
nel file manifest del progetto della libreria LVL, perché SDK Tools non la unirà nei manifest delle applicazioni dipendenti. Devi invece dichiarare l'autorizzazione nel manifest di ogni applicazione dipendente.
Implementazione di un criterio
Il servizio di licenze di Google Play non determina se deve essere concesso l'accesso alla tua applicazione a un determinato utente con una determinata licenza.
Piuttosto, questa responsabilità è lasciata a un'implementazione Policy
da te indicata nella tua applicazione.
Policy Controller è un'interfaccia dichiarata dall'LVL progettata per contenere la logica dell'applicazione per consentire o non consentire l'accesso degli utenti, in base al risultato di un controllo della licenza. Per utilizzare l'LVL, la tua applicazione deve fornire un'implementazione di Policy
.
L'interfaccia Policy
dichiara due metodi, allowAccess()
e processServerResponse()
, che vengono richiamati da un'istanza LicenseChecker
durante l'elaborazione di una risposta dal server delle licenze. Dichiara inoltre
un'enum chiamata LicenseResponse
, che specifica il valore di risposta della licenza
passato nelle chiamate a processServerResponse()
.
processServerResponse()
consente di pre-elaborare i dati di risposta non elaborati ricevuti dal server di licenze, prima di determinare se concedere l'accesso.Un'implementazione tipica estrae alcuni o tutti i campi dalla risposta alla licenza e li archivia localmente in un archivio permanente, ad esempio tramite l'archiviazione
SharedPreferences
, per garantire che i dati siano accessibili durante le chiamate delle applicazioni e i cicli di spegnimento del dispositivo. Ad esempio, un elementoPolicy
conserverebbe il timestamp dell'ultimo controllo riuscito, il conteggio dei nuovi tentativi, il periodo di validità della licenza e informazioni simili in un archivio permanente, anziché reimpostare i valori a ogni avvio dell'applicazione.Quando archivi i dati delle risposte in locale,
Policy
deve garantire che i dati siano offuscati (consulta la sezione Implementazione di un offuscamento di seguito).allowAccess()
determina se concedere all'utente l'accesso alla tua applicazione in base ai dati di risposta delle licenze disponibili (dal server di licenze o dalla cache) o altre informazioni specifiche dell'applicazione. Ad esempio, l'implementazione diallowAccess()
potrebbe prendere in considerazione criteri aggiuntivi, come l'utilizzo o altri dati recuperati da un server di backend. In tutti i casi, un'implementazione diallowAccess()
deve restituiretrue
solo se l'utente dispone della licenza per utilizzare l'applicazione, come stabilito dal server di licenze, o se si verifica un problema temporaneo di rete o di sistema che impedisce il completamento del controllo della licenza. In questi casi, la tua implementazione può mantenere un numero di risposte ai nuovi tentativi e consentire temporaneamente l'accesso fino al completamento del controllo della licenza successivo.
Per semplificare il processo di aggiunta delle licenze all'applicazione e per fornire un'illustrazione di come deve essere progettato un Policy
, l'LVL include due implementazioni Policy
complete che puoi utilizzare senza modifiche o adattarsi alle tue esigenze:
- ServerManagedPolicy, un
Policy
flessibile che utilizza impostazioni fornite dal server e risposte memorizzate nella cache per gestire l'accesso a varie condizioni di rete. - StrictPolicy, che non memorizza nella cache i dati delle risposte e consente l'accesso solo se il server restituisce una risposta concessa in licenza.
Per la maggior parte delle applicazioni, si consiglia di utilizzare ServerManagedPolicy. ServerManagedPolicy è l'impostazione predefinita LVL ed è integrata con l'applicazione di esempio LVL.
Linee guida per le norme personalizzate
Nell'implementazione delle licenze, puoi utilizzare uno dei criteri completi forniti in LVL (ServerManagedPolicy o StrictPolicy) oppure creare un criterio personalizzato. Per qualsiasi tipo di criterio personalizzato, esistono diversi importanti punti di progettazione da comprendere e tenere in considerazione nell'implementazione.
Il server di licenze applica limiti generali per le richieste per evitare un uso eccessivo delle risorse che potrebbe causare il denial of service. Quando un'applicazione supera il limite di richiesta, il server di licenze restituisce una risposta 503, che viene trasmessa alla tua applicazione come errore generale del server. Ciò significa che nessuna risposta relativa alla licenza sarà disponibile per l'utente finché il limite non viene reimpostato, il che può interessare l'utente per un periodo indefinito.
Se stai progettando una norma personalizzata, ti consigliamo di Policy
:
- Memorizza nella cache (e offusca correttamente) la risposta più recente relativa alla licenza nell'archiviazione permanente locale.
- Restituisce la risposta memorizzata nella cache per tutti i controlli delle licenze, finché la risposta memorizzata nella cache è valida, anziché inviare una richiesta al server di licenze.
Ti consigliamo vivamente di impostare la validità della risposta in base al valore aggiuntivo
VT
fornito dal server. Per ulteriori informazioni, vedi Informazioni aggiuntive per la risposta del server. - Utilizza un periodo di backoff esponenziale. Se un nuovo tentativo genera errori. Tieni presente che il client Google Play fa automaticamente dei tentativi per le richieste non riuscite, pertanto nella maggior parte dei casi non è necessario che il tuo
Policy
le riprovi. - Fornisce un "periodo di tolleranza" che consente all'utente di accedere alla tua applicazione per un periodo di tempo limitato o di un numero di utilizzi limitato, mentre è in corso un nuovo tentativo di controllo della licenza. Il periodo di tolleranza è vantaggioso per l'utente, in quanto consente l'accesso fino al completamento del controllo della licenza successivo e ti offre un vantaggio per l'applicazione di un limite rigido all'accesso alla tua applicazione quando non è disponibile una risposta alla licenza valida.
Progettare la tua Policy
in base alle linee guida elencate sopra è fondamentale, perché garantisce la migliore esperienza possibile agli utenti e al contempo offre un controllo efficace sulla tua applicazione anche in condizioni di errore.
Tieni presente che qualsiasi Policy
può utilizzare le impostazioni fornite dal server di licenze per gestire la validità e la memorizzazione nella cache, riprovare il periodo di tolleranza e altro ancora. L'estrazione delle impostazioni fornite dal server è semplice e il relativo utilizzo è vivamente consigliato. Vedi l'implementazione di ServerManagedPolicy per un esempio di come estrarre e utilizzare gli extra. Per un elenco delle impostazioni del server e informazioni su come utilizzarle, consulta la sezione Elementi aggiuntivi per la risposta del server.
Criterio gestito da server
L'LVL include un'implementazione completa e consigliata dell'interfaccia Policy
chiamata ServerManagedPolicy. L'implementazione è integrata con le classi LVL e funge da Policy
predefinito nella libreria.
ServerManagedPolicy gestisce tutte le risposte per le licenze e i nuovi tentativi. Memorizza nella cache tutti i dati di risposta localmente in un file SharedPreferences
, offuscandolo con l'implementazione Obfuscator
dell'applicazione. Ciò garantisce che i dati relativi alle risposte delle licenze siano protetti e persistenti durante i cicli di spegnimento e riaccensione del dispositivo. ServerManagedPolicy fornisce implementazioni concrete dei metodi di interfaccia processServerResponse()
e allowAccess()
e include anche una serie di metodi e tipi di supporto per la gestione delle risposte alle licenze.
È importante sottolineare che una caratteristica chiave di ServerManagedPolicy è l'utilizzo di impostazioni fornite dal server come base per la gestione delle licenze per tutto il periodo di rimborso di un'applicazione e attraverso condizioni di rete ed errore variabili.
Quando un'applicazione contatta il server di Google Play per il controllo delle licenze, il server aggiunge diverse impostazioni come coppie chiave-valore nel campo Extra di determinati tipi di risposta delle licenze. Ad esempio, il server fornisce valori consigliati per, ad esempio, il periodo di validità della licenza dell'applicazione, il periodo di tolleranza per i nuovi tentativi e il numero massimo di nuovi tentativi consentiti. ServerManagedPolicy estrae i valori dalla
risposta di licenza nel metodo processServerResponse()
e li controlla
nel metodo allowAccess()
. Per un elenco delle impostazioni fornite dal server utilizzate da ServerManagedPolicy, vedi Extra per la risposta del server.
Per praticità, prestazioni ottimali e per il vantaggio dell'utilizzo delle impostazioni delle licenze
del server Google Play, ti consigliamo vivamente di utilizzare ServerManagedPolicy come
licenza Policy
..
Se ti preoccupa la sicurezza dei dati relativi alle risposte delle licenze archiviati localmente in SharedPreferences
, puoi utilizzare un algoritmo di offuscamento più efficace o progettare un Policy
più rigido che non archivia i dati delle licenze. LVL include un esempio di Policy
. Per ulteriori informazioni, consulta StrictPolicy.
Per utilizzare ServerManagedPolicy, è sufficiente importarlo nell'attività, creare un'istanza e passare un riferimento all'istanza durante la creazione di LicenseChecker
. Per ulteriori informazioni, consulta Istantanei LicenseChecker e
LicenseCheckerCallback.
Criterio rigoroso
L'LVL include un'implementazione completa alternativa dell'interfaccia Policy
chiamata StrictPolicy. L'implementazione di StrictPolicy fornisce un criterio più restrittivo rispetto a ServerManagedPolicy, in quanto non consente all'utente di accedere all'applicazione a meno che al momento dell'accesso non venga ricevuta una risposta relativa alla licenza dal server che indichi che l'utente dispone della licenza.
La caratteristica principale di StrictPolicy è che non archivia nessun dato della risposta della licenza localmente in un archivio permanente. Poiché non sono archiviati dati, le richieste di nuovo tentativo non vengono monitorate e le risposte memorizzate nella cache non possono essere utilizzate per completare i controlli delle licenze. Policy
consente l'accesso solo se:
- La risposta relativa alla licenza viene ricevuta dal server di licenze.
- La risposta relativa alle licenze indica che l'utente dispone della licenza per accedere all'applicazione.
L'utilizzo di StrictPolicy è appropriato se la tua priorità principale è garantire che, in tutti i casi possibili, nessun utente sia autorizzato ad accedere all'applicazione a meno che non venga confermata la licenza dell'utente al momento dell'utilizzo. Inoltre, il criterio offre un po' più di sicurezza rispetto a ServerManagedPolicy: poiché non esistono dati memorizzati nella cache localmente, è impossibile che un utente malintenzionato possa manomettere i dati memorizzati nella cache e ottenere l'accesso all'applicazione.
Allo stesso tempo, questo Policy
presenta una sfida per gli utenti normali, poiché
significa che non saranno in grado di accedere all'applicazione quando non è disponibile una connessione di rete
(cellulare o Wi-Fi). Un altro effetto collaterale è che l'applicazione invierà più richieste di controllo delle licenze al server, dato che non è possibile utilizzare una risposta memorizzata nella cache.
Nel complesso, questo criterio rappresenta un compromesso tra un certo grado di comodità per gli utenti e la sicurezza assoluta e il controllo dell'accesso. Valuta con attenzione il compromesso
prima di utilizzare questo Policy
.
Per utilizzare StrictPolicy, devi semplicemente importarlo nella tua attività, creare un'istanza e passare un riferimento alla stessa durante la creazione di LicenseChecker
. Per ulteriori informazioni, consulta
Instantiate LicenseChecker e LicenseCheckerCallback
per ulteriori informazioni.
Una tipica implementazione Policy
deve salvare i dati di risposta della licenza per un'applicazione in un archivio permanente, in modo che siano accessibili durante tutte le chiamate delle applicazioni e i cicli di spegnimento del dispositivo. Ad esempio, Policy
manterrà il timestamp dell'ultimo controllo riuscito, il conteggio dei nuovi tentativi, il periodo di validità della licenza e informazioni simili in un archivio permanente, invece di reimpostare i valori a ogni avvio dell'applicazione. Il valore predefinito Policy
incluso in LVL, ServerManagedPolicy archivia i dati relativi alle risposte delle licenze in un'istanza SharedPreferences
, per garantire che i dati siano permanenti.
Poiché Policy
userà i dati archiviati delle risposte delle licenze per determinare se
consentire o meno l'accesso all'applicazione, deve garantire che tutti i dati
archiviati siano protetti e non possano essere riutilizzati o manipolati da un utente root su un
dispositivo. Nello specifico, Policy
deve sempre offuscare i dati prima di archiviarli, utilizzando una chiave univoca per l'applicazione e il dispositivo. L'offuscamento mediante una chiave specifica per l'applicazione e per il dispositivo è fondamentale, in quanto impedisce la condivisione dei dati offuscati tra applicazioni e dispositivi.
L'LVL aiuta l'applicazione ad archiviare i dati di risposta delle licenze in modo sicuro e persistente. Innanzitutto, fornisce un'interfaccia Obfuscator
che consente alla tua applicazione di fornire l'algoritmo di offuscamento
di sua scelta per i dati archiviati. Basandosi su questo, l'LVL fornisce la classe helper PreferenceObfuscator, che gestisce la maggior parte del lavoro delle chiamate alla classe Obfuscator
dell'applicazione e della lettura e della scrittura dei dati offuscati in un'istanza SharedPreferences
.
L'LVL fornisce un'implementazione Obfuscator
completa denominata
AESObfuscator che utilizza la crittografia AES per offuscare i dati. Puoi
utilizzare AESObfuscator nella tua applicazione senza modifiche oppure
puoi adattarlo alle tue esigenze. Se utilizzi un Policy
(ad esempio ServerManagedPolicy) che memorizza nella cache i dati di risposta delle licenze, ti consigliamo di utilizzare AESObfuscator come base per l'implementazione di Obfuscator
.
Per ulteriori informazioni, consulta la sezione successiva.
Offuscatore AES
LVL include un'implementazione completa e consigliata dell'interfaccia Obfuscator
chiamata AESObfuscator. L'implementazione è integrata con l'applicazione LVL di esempio e funge da Obfuscator
predefinito nella libreria.
AESObfuscator offre un offuscamento sicuro dei dati utilizzando l'algoritmo AES per criptare e decriptare i dati nel momento in cui vengono scritti o letti dallo spazio di archiviazione.
L'elemento Obfuscator
avvia la crittografia utilizzando tre campi di dati forniti
dall'applicazione:
- Un sale: un array di byte casuali da utilizzare per ogni offuscamento.
- Una stringa di identificatore dell'applicazione, in genere il nome del pacchetto dell'applicazione.
- Una stringa di identificatore del dispositivo, ricavata dal maggior numero possibile di origini specifiche del dispositivo, in modo da renderlo il più univoco.
Per utilizzare AESObfuscator, devi prima importarlo nella tua Attività. Dichiara un array finale statico privato che contiene i byte di sale e inizializzalo su 20 byte generati in modo casuale.
Kotlin
// Generate 20 random bytes, and put them here. private val SALT = byteArrayOf( -46, 65, 30, -128, -103, -57, 74, -64, 51, 88, -95, -45, 77, -117, -36, -113, -11, 32, -64, 89 )
Java
... // Generate 20 random bytes, and put them here. private static final byte[] SALT = new byte[] { -46, 65, 30, -128, -103, -57, 74, -64, 51, 88, -95, -45, 77, -117, -36, -113, -11, 32, -64, 89 }; ...
Quindi, dichiara una variabile che contiene un identificatore del dispositivo e genera un valore corrispondente in qualsiasi modo necessario. Ad esempio, l'applicazione di esempio inclusa nell'LVL esegue una query sulle impostazioni di sistema per android.Settings.Secure.ANDROID_ID
, che è univoca per ogni dispositivo.
Tieni presente che, a seconda delle API che utilizzi, la tua applicazione potrebbe dover richiedere autorizzazioni aggiuntive per acquisire informazioni specifiche del dispositivo.
Ad esempio, per eseguire query sull'TelephonyManager
al fine di ottenere il codice IMEI o i dati correlati del dispositivo, l'applicazione dovrà richiedere anche l'autorizzazione android.permission.READ_PHONE_STATE
nel file manifest.
Prima di richiedere nuove autorizzazioni all'unico scopo di acquisire informazioni specifiche del dispositivo da utilizzare nel tuo Obfuscator
, valuta in che modo ciò potrebbe influire sulla tua applicazione o sui suoi filtri su Google Play (dal momento che alcune autorizzazioni possono causare l'aggiunta del <uses-feature>
associato agli strumenti di creazione dell'SDK).
Infine, crea un'istanza di AESObfuscator passando il sale, l'identificatore dell'applicazione e l'identificatore del dispositivo. Puoi costruire direttamente l'istanza, durante la creazione di Policy
e LicenseChecker
. Ecco alcuni esempi:
Kotlin
... // Construct the LicenseChecker with a Policy. private val checker = LicenseChecker( this, ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)), BASE64_PUBLIC_KEY ) ...
Java
... // Construct the LicenseChecker with a Policy. checker = new LicenseChecker( this, new ServerManagedPolicy(this, new AESObfuscator(SALT, getPackageName(), deviceId)), BASE64_PUBLIC_KEY // Your public licensing key. ); ...
Per un esempio completo, vedi MainActivity nell'applicazione di esempio LVL.
Controllo della licenza da un'attività
Dopo aver implementato Policy
per la gestione dell'accesso alla tua applicazione, il passaggio successivo prevede l'aggiunta di un controllo delle licenze all'applicazione, che avvia una query al server di licenze, se necessario, e gestisce l'accesso all'applicazione in base alla risposta della licenza. Tutto il lavoro di aggiunta del controllo della licenza e gestione
della risposta si svolge nel file di origine Activity
principale.
Per aggiungere il controllo della licenza e gestire la risposta, devi:
- Aggiungere importazioni
- Implementa LicenseCheckerCallback come classe interna privata
- Crea un gestore per pubblicare post da LicenseCheckerCallback nel thread dell'interfaccia utente
- Instantiate LicenseChecker e LicenseCheckerCallback
- Chiama checkAccess() per avviare il controllo delle licenze
- Incorpora la chiave pubblica per le licenze
- Chiama il metodo onDestroy() di LicenseChecker per chiudere le connessioni IPC.
Queste attività sono descritte nelle sezioni seguenti.
Panoramica del controllo e della risposta delle licenze
Nella maggior parte dei casi, devi aggiungere il controllo delle licenze all'elemento Activity
principale dell'applicazione, nel metodo onCreate()
. Ciò garantisce che, quando l'utente avvia direttamente l'applicazione, il controllo della licenza venga richiamato immediatamente. In alcuni casi, puoi aggiungere controlli delle licenze anche in altre località. Ad esempio, se l'applicazione include più componenti delle attività che altre applicazioni possono avviare entro il giorno Intent
, puoi aggiungere controlli delle licenze in tali attività.
Il controllo delle licenze consiste in due azioni principali:
- Una chiamata a un metodo per avviare il controllo della licenza. Nel LVL, si tratta di una chiamata al metodo
checkAccess()
di un oggettoLicenseChecker
che crei. - Un callback che restituisce il risultato del controllo della licenza. Nell'LVL, si tratta di un'interfaccia
LicenseCheckerCallback
che implementi. L'interfaccia dichiara due metodi,allow()
edontAllow()
, richiamati dalla libreria in base al risultato del controllo delle licenze. Puoi implementare questi due metodi con la logica necessaria per consentire o impedire all'utente di accedere alla tua applicazione. Tieni presente che questi metodi non determinano se consentire l'accesso. La determinazione è responsabilità dell'implementazione diPolicy
. Piuttosto, questi metodi si limitano a fornire i comportamenti dell'applicazione su come consentire e non consentire l'accesso (e gestire gli errori dell'applicazione).I metodi
allow()
edontAllow()
forniscono un "motivo" per la risposta, che può essere uno dei valoriPolicy
,LICENSED
,NOT_LICENSED
oRETRY
. In particolare, devi gestire il caso in cui il metodo riceve la rispostaRETRY
perdontAllow()
e fornire all'utente un pulsante "Riprova", che potrebbe essere stato causato dal fatto che il servizio non era disponibile durante la richiesta.
Il diagramma riportato sopra illustra come avviene un tipico controllo delle licenze:
- Il codice nell'attività principale dell'applicazione crea istanze di oggetti
LicenseCheckerCallback
eLicenseChecker
. Durante la creazione diLicenseChecker
, il codice trasmetteContext
, un'implementazionePolicy
da utilizzare e la chiave pubblica dell'account editore per la concessione in licenza come parametri. - Il codice chiama quindi il metodo
checkAccess()
nell'oggettoLicenseChecker
. L'implementazione del metodo chiamaPolicy
per determinare se è presente una risposta per la licenza valida memorizzata nella cache localmente inSharedPreferences
.- In tal caso, l'implementazione
checkAccess()
chiamaallow()
. - In caso contrario,
LicenseChecker
avvia una richiesta di controllo della licenza che viene inviata al server di licenze.
Nota: il server di licenze restituisce sempre
LICENSED
quando esegui il controllo della licenza di una bozza di applicazione. - In tal caso, l'implementazione
- Quando viene ricevuta una risposta,
LicenseChecker
crea uno strumento di convalida delle licenze che verifica i dati della licenza firmata ed estrae i campi della risposta, quindi li passa al tuoPolicy
per un'ulteriore valutazione.- Se la licenza è valida,
Policy
memorizza nella cache la risposta inSharedPreferences
e invia una notifica allo strumento di convalida, che a sua volta chiama il metodoallow()
sull'oggettoLicenseCheckerCallback
. - Se la licenza non è valida,
Policy
invia una notifica allo strumento di convalida, che chiama il metododontAllow()
suLicenseCheckerCallback
.
- Se la licenza è valida,
- In caso di errore locale o del server recuperabile, ad esempio quando la rete non è disponibile per inviare la richiesta,
LicenseChecker
passa una rispostaRETRY
al metodoprocessServerResponse()
dell'oggettoPolicy
.Inoltre, entrambi i metodi di callback
allow()
edontAllow()
ricevono un argomentoreason
. In genere il motivo del metodoallow()
èPolicy.LICENSED
oPolicy.RETRY
, mentre il motivodontAllow()
è in generePolicy.NOT_LICENSED
oPolicy.RETRY
. Questi valori di risposta sono utili per mostrare una risposta appropriata per l'utente, ad esempio fornendo un pulsante "Riprova" quandodontAllow()
risponde conPolicy.RETRY
, il che potrebbe essere stato perché il servizio non era disponibile. - In caso di errore dell'applicazione, ad esempio quando l'applicazione tenta di controllare la licenza di un nome di pacchetto non valido,
LicenseChecker
passa una risposta di errore al metodoapplicationError()
di LicenseCheckerCallback.
Tieni presente che, oltre ad avviare il controllo della licenza e gestire il risultato, descritti nelle sezioni seguenti, la tua applicazione deve anche fornire un'implementazione dei criteri e, se Policy
archivia i dati di risposta (come ServerManagedPolicy), un'implementazione Obfuscator.
Aggiungi importazioni
Per prima cosa, apri il file del corso dell'attività principale dell'applicazione e importa
LicenseChecker
e LicenseCheckerCallback
dal pacchetto LVL.
Kotlin
import com.google.android.vending.licensing.LicenseChecker import com.google.android.vending.licensing.LicenseCheckerCallback
Java
import com.google.android.vending.licensing.LicenseChecker; import com.google.android.vending.licensing.LicenseCheckerCallback;
Se utilizzi l'implementazione predefinita di Policy
fornita con LVL,
ServerManagedPolicy, importala anche insieme all'oggetto AESObfuscator. Se utilizzi Policy
o Obfuscator
personalizzati, importali.
Kotlin
import com.google.android.vending.licensing.ServerManagedPolicy import com.google.android.vending.licensing.AESObfuscator
Java
import com.google.android.vending.licensing.ServerManagedPolicy; import com.google.android.vending.licensing.AESObfuscator;
Implementare LicenseCheckerCallback come classe interna privata
LicenseCheckerCallback
è un'interfaccia fornita dall'LVL per la gestione dei risultati di un controllo delle licenze. Per supportare le licenze tramite LVL, devi implementare LicenseCheckerCallback
e i relativi metodi per consentire o non consentire l'accesso all'applicazione.
Il risultato di un controllo della licenza è sempre una chiamata a uno dei metodi LicenseCheckerCallback
, effettuata in base alla convalida del payload della risposta, al codice di risposta del server stesso e a qualsiasi elaborazione aggiuntiva fornita dal tuo Policy
. La tua applicazione può implementare i metodi in qualsiasi modo necessario. In generale, è preferibile utilizzare metodi semplici, limitandoli alla gestione dello stato dell'interfaccia utente e dell'accesso alle applicazioni. Se vuoi aggiungere ulteriori elaborazioni delle risposte alle licenze, ad esempio contattando un server di backend o applicando vincoli personalizzati,
ti consigliamo di incorporare il codice in Policy
, anziché
inserirlo nei metodi LicenseCheckerCallback
.
Nella maggior parte dei casi, devi dichiarare la tua implementazione di
LicenseCheckerCallback
come classe privata all'interno della classe Activity principale
dell'applicazione.
Implementa i metodi allow()
e dontAllow()
in base alle esigenze. Per iniziare, puoi utilizzare nei metodi semplici comportamenti di gestione dei risultati, come la visualizzazione del risultato della licenza in una finestra di dialogo. Ciò ti consente di eseguire l'applicazione più rapidamente e può essere utile per il debug. In seguito, dopo aver determinato i comportamenti esatti che vuoi, puoi aggiungere una gestione più complessa.
Ecco alcuni suggerimenti per la gestione delle risposte senza licenza in dontAllow()
:
- Mostra all'utente una finestra di dialogo "Riprova" con un pulsante per avviare una nuova verifica delle licenze se il
reason
fornito èPolicy.RETRY
. - Visualizza una finestra di dialogo "Acquista questa applicazione" contenente un pulsante che rimanda direttamente all'utente alla pagina dei dettagli dell'applicazione su Google Play, da cui l'utente può acquistare l'applicazione. Per ulteriori informazioni su come configurare questi link, consulta la sezione Collegamento ai tuoi prodotti.
- Visualizza un avviso popup che indichi che le funzionalità dell'applicazione sono limitate perché non è concessa in licenza.
L'esempio seguente mostra come l'applicazione di esempio LVL implementa LicenseCheckerCallback
e mostra una finestra di dialogo che mostra i metodi che mostrano il controllo delle licenze.
Kotlin
private inner class MyLicenseCheckerCallback : LicenseCheckerCallback { override fun allow(reason: Int) { if (isFinishing) { // Don't update UI if Activity is finishing. return } // Should allow user access. displayResult(getString(R.string.allow)) } override fun dontAllow(reason: Int) { if (isFinishing) { // Don't update UI if Activity is finishing. return } displayResult(getString(R.string.dont_allow)) if (reason == Policy.RETRY) { // If the reason received from the policy is RETRY, it was probably // due to a loss of connection with the service, so we should give the // user a chance to retry. So show a dialog to retry. showDialog(DIALOG_RETRY) } else { // Otherwise, the user isn't licensed to use this app. // Your response should always inform the user that the application // isn't licensed, but your behavior at that point can vary. You might // provide the user a limited access version of your app or you can // take them to Google Play to purchase the app. showDialog(DIALOG_GOTOMARKET) } } }
Java
private class MyLicenseCheckerCallback implements LicenseCheckerCallback { public void allow(int reason) { if (isFinishing()) { // Don't update UI if Activity is finishing. return; } // Should allow user access. displayResult(getString(R.string.allow)); } public void dontAllow(int reason) { if (isFinishing()) { // Don't update UI if Activity is finishing. return; } displayResult(getString(R.string.dont_allow)); if (reason == Policy.RETRY) { // If the reason received from the policy is RETRY, it was probably // due to a loss of connection with the service, so we should give the // user a chance to retry. So show a dialog to retry. showDialog(DIALOG_RETRY); } else { // Otherwise, the user isn't licensed to use this app. // Your response should always inform the user that the application // isn't licensed, but your behavior at that point can vary. You might // provide the user a limited access version of your app or you can // take them to Google Play to purchase the app. showDialog(DIALOG_GOTOMARKET); } } }
Inoltre, devi implementare il metodo applicationError()
, che il LVL chiama per consentire alla tua applicazione di gestire errori non ripetibili. Per un elenco di questi errori, consulta la sezione Codici di risposta del server nella documentazione di riferimento sulle licenze. Puoi implementare
il metodo in qualsiasi modo necessario. Nella maggior parte dei casi, il metodo deve registrare il codice di errore e chiamare dontAllow()
.
Crei un gestore per la pubblicazione da LicenseCheckerCallback nel thread dell'interfaccia utente
Durante il controllo della licenza, l'LVL passa la richiesta all'applicazione Google Play, che gestisce la comunicazione con il server di licenze. L'LVL passa la richiesta tramite IPC asincrono (utilizzando Binder
), in modo che l'elaborazione effettiva e la comunicazione di rete non avvengano su un thread gestito dalla tua applicazione. Analogamente, quando l'applicazione Google Play riceve il risultato, richiama un metodo di callback su IPC, che a sua volta viene eseguito in un pool di thread IPC nel processo dell'applicazione.
La classe LicenseChecker
gestisce la comunicazione IPC dell'applicazione con l'applicazione Google Play, inclusa la chiamata che invia la richiesta e il callback che riceve la risposta. LicenseChecker
monitora anche le richieste di licenza aperte e gestisce i relativi timeout.
Per poter gestire correttamente i timeout ed elaborare le risposte in arrivo senza influire sul thread dell'interfaccia utente dell'applicazione, LicenseChecker
crea un thread in background al momento della creazione di un'istanza. Nel thread, esegue tutte le elaborazioni dei risultati del controllo delle licenze, indipendentemente dal fatto che il risultato sia una risposta ricevuta dal server o un errore di timeout. Al termine dell'elaborazione, l'LVL chiama i metodi LicenseCheckerCallback
dal thread in background.
Per la tua applicazione, ciò significa che:
- In molti casi, i metodi
LicenseCheckerCallback
verranno richiamati da un thread in background. - Questi metodi non saranno in grado di aggiornare lo stato o richiamare alcuna elaborazione nel thread dell'interfaccia utente, a meno che non crei un gestore nel thread dell'interfaccia utente e i tuoi metodi di callback non vengano pubblicati nel gestore.
Se vuoi che i tuoi metodi LicenseCheckerCallback
aggiornino il thread dell'interfaccia utente,
crea un valore Handler
nel metodo onCreate()
dell'attività principale,
come mostrato di seguito. In questo esempio, i metodi LicenseCheckerCallback
dell'applicazione di esempio LVL (vedi sopra) chiamano displayResult()
per aggiornare il thread dell'interfaccia utente tramite il metodo post()
del gestore.
Kotlin
private lateinit var handler: Handler override fun onCreate(savedInstanceState: Bundle?) { ... handler = Handler() }
Java
private Handler handler; @Override public void onCreate(Bundle savedInstanceState) { ... handler = new Handler(); }
Quindi, nei tuoi metodi LicenseCheckerCallback
, puoi utilizzare i metodi del gestore per pubblicare oggetti Runnable o Message nel Gestore. Ecco come l'applicazione di esempio inclusa nel file LVL pubblica un elemento eseguibile in un gestore nel thread dell'interfaccia utente per visualizzare lo stato della licenza.
Kotlin
private fun displayResult(result: String) { handler.post { statusText.text = result setProgressBarIndeterminateVisibility(false) checkLicenseButton.isEnabled = true } }
Java
private void displayResult(final String result) { handler.post(new Runnable() { public void run() { statusText.setText(result); setProgressBarIndeterminateVisibility(false); checkLicenseButton.setEnabled(true); } }); }
Crea un'istanza LicenseChecker e LicenseCheckerCallback
Nel metodo onCreate()
dell'attività principale, crea istanze private di LicenseCheckerCallback e LicenseChecker
. Devi prima istruire LicenseCheckerCallback
, perché devi passare un riferimento a quell'istanza quando chiami il costruttore per LicenseChecker
.
Quando crei un'istanza di LicenseChecker
, devi passare questi parametri:
- L'applicazione
Context
- Un riferimento all'implementazione
Policy
da utilizzare per il controllo delle licenze. Nella maggior parte dei casi, utilizzerai l'implementazione predefinita diPolicy
fornita da LVL, ServerManagedPolicy. - La variabile Stringa che contiene la chiave pubblica del tuo account editore per le licenze.
Se utilizzi ServerManagedPolicy, non dovrai accedere direttamente alla classe, ma puoi creare un'istanza della classe nel costruttore LicenseChecker
, come mostrato nell'esempio di seguito. Tieni presente che devi passare un riferimento a una nuova istanza di Obfuscator quando crei ServerManagedPolicy.
L'esempio seguente mostra la creazione di istanze di LicenseChecker
e LicenseCheckerCallback
dal metodo onCreate()
di una classe Activity.
Kotlin
class MainActivity : AppCompatActivity() { ... private lateinit var licenseCheckerCallback: LicenseCheckerCallback private lateinit var checker: LicenseChecker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... // Construct the LicenseCheckerCallback. The library calls this when done. licenseCheckerCallback = MyLicenseCheckerCallback() // Construct the LicenseChecker with a Policy. checker = LicenseChecker( this, ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)), BASE64_PUBLIC_KEY // Your public licensing key. ) ... } }
Java
public class MainActivity extends Activity { ... private LicenseCheckerCallback licenseCheckerCallback; private LicenseChecker checker; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Construct the LicenseCheckerCallback. The library calls this when done. licenseCheckerCallback = new MyLicenseCheckerCallback(); // Construct the LicenseChecker with a Policy. checker = new LicenseChecker( this, new ServerManagedPolicy(this, new AESObfuscator(SALT, getPackageName(), deviceId)), BASE64_PUBLIC_KEY // Your public licensing key. ); ... } }
Tieni presente che LicenseChecker
chiama i metodi LicenseCheckerCallback
dal thread della UI solo se esiste una risposta della licenza valida memorizzata nella cache localmente. Se il controllo della licenza viene inviato al server, i callback provengono sempre dal thread in background, anche per gli errori di rete.
Chiama checkAccess() per avviare il controllo delle licenze
Nella tua Attività principale, aggiungi una chiamata al metodo checkAccess()
dell'istanza LicenseChecker
. Nella chiamata, passa un riferimento all'istanza LicenseCheckerCallback
come parametro. Se devi gestire eventuali effetti speciali dell'interfaccia utente o la gestione dello stato prima della chiamata, potresti trovare utile chiamare checkAccess()
da un metodo wrapper. Ad esempio, l'applicazione LVL di esempio chiama checkAccess()
da un metodo wrapper doCheck()
:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... // Call a wrapper method that initiates the license check doCheck() ... } ... private fun doCheck() { checkLicenseButton.isEnabled = false setProgressBarIndeterminateVisibility(true) statusText.setText(R.string.checking_license) checker.checkAccess(licenseCheckerCallback) }
Java
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Call a wrapper method that initiates the license check doCheck(); ... } ... private void doCheck() { checkLicenseButton.setEnabled(false); setProgressBarIndeterminateVisibility(true); statusText.setText(R.string.checking_license); checker.checkAccess(licenseCheckerCallback); }
Incorpora la chiave pubblica per le licenze
Per ogni applicazione, il servizio Google Play genera automaticamente una coppia di chiavi pubblica/privata RSA a 2048 bit utilizzata per le licenze e la fatturazione in-app. La coppia di chiavi è associata all'applicazione in modo univoco. Sebbene sia associata all'applicazione, la coppia di chiavi non è la chiave che utilizzi per firmare le applicazioni (o da questa derivata).
Google Play Console espone la chiave pubblica per le licenze agli sviluppatori che hanno eseguito l'accesso alla Play Console, ma mantiene la chiave privata nascosta a tutti gli utenti in un luogo sicuro. Quando un'applicazione richiede il controllo della licenza per un'applicazione pubblicata nel tuo account, il server di licenze firma la risposta relativa alla licenza utilizzando la chiave privata della coppia di chiavi dell'applicazione. Quando l'LVL riceve la risposta, utilizza la chiave pubblica fornita dall'applicazione per verificare la firma della risposta relativa alla licenza.
Per aggiungere licenze a un'applicazione, devi ottenere la chiave pubblica per la licenza dell'applicazione e copiarla nell'applicazione. Per trovare la chiave pubblica della tua applicazione per la licenza:
- Vai a Google Play Console e accedi. Assicurati di accedere all'account da cui è stata pubblicata (o verrà pubblicata) l'applicazione che hai concesso in licenza.
- Nella pagina dei dettagli dell'applicazione, individua il link Servizi e API e fai clic.
- Nella pagina Servizi e API, individua la sezione Licenze e fatturazione in-app. La chiave pubblica per la licenza è indicata nel campo Il tuo codice licenza per questa applicazione.
Per aggiungere la chiave pubblica alla tua applicazione, basta copiare/incollare la stringa della chiave
dal campo nell'applicazione come valore della variabile String
BASE64_PUBLIC_KEY
. Durante la copia, assicurati di aver selezionato l'intera stringa di chiave, senza omettere alcun carattere.
Ecco un esempio dall'applicazione LVL di esempio:
Kotlin
private const val BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... " //truncated for this example class LicensingActivity : AppCompatActivity() { ... }
Java
public class MainActivity extends Activity { private static final String BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... "; //truncated for this example ... }
Chiama il metodo onDestroy() di LicenseChecker per chiudere le connessioni IPC
Infine, per consentire la pulizia dell'LVL prima che l'applicazione Context
venga modificata, aggiungi una chiamata al metodo onDestroy()
di LicenseChecker
dall'implementazione onDestroy()
dell'attività. La chiamata fa sì che LicenseChecker
chiuda correttamente qualsiasi connessione IPC aperta al servizio ILicensingService dell'applicazione Google Play e rimuovi qualsiasi riferimento locale al servizio e al gestore.
La mancata chiamata del metodo onDestroy()
di LicenseChecker
può causare problemi durante il ciclo di vita dell'applicazione. Ad esempio, se l'utente cambia l'orientamento dello schermo mentre è attivo un controllo delle licenze, l'applicazione Context
viene eliminata. Se l'applicazione non chiude correttamente la connessione IPC di LicenseChecker
, l'applicazione si arresta in modo anomalo alla ricezione della risposta. Analogamente, se l'utente esce dall'applicazione mentre è in corso un controllo della licenza, l'applicazione si arresta in modo anomalo alla ricezione della risposta, a meno che non abbia chiamato correttamente il metodo onDestroy()
di LicenseChecker
per disconnettersi dal servizio.
Ecco un esempio dall'applicazione di esempio inclusa nel LVL, dove mChecker
è l'istanza LicenseChecker
:
Kotlin
override fun onDestroy() { super.onDestroy() checker.onDestroy() ... }
Java
@Override protected void onDestroy() { super.onDestroy(); checker.onDestroy(); ... }
Se stai estendendo o modificando LicenseChecker
, potresti anche dover chiamare il metodo finishCheck()
di LicenseChecker
per ripulire eventuali connessioni IPC aperte.
Implementazione di un DeviceLimiter
In alcuni casi, è consigliabile che Policy
limiti il numero di dispositivi effettivi
a cui è consentito utilizzare una singola licenza. In questo modo si impedisce a un utente di spostare un'applicazione concessa in licenza su una serie di dispositivi e di utilizzarla su tali dispositivi con lo stesso ID account. Inoltre impedirebbe a un utente di "condividere" l'applicazione fornendo ad altri individui i dati dell'account associati alla licenza, che potrebbero quindi accedere a tale account sui propri dispositivi e accedere alla licenza dell'applicazione.
L'LVL supporta le licenze per dispositivo fornendo un'interfaccia DeviceLimiter
, che dichiara un singolo metodo, allowDeviceAccess()
. Quando un LicenseValidator gestisce una risposta
del server di licenze, chiama allowDeviceAccess()
, trasmettendo
una stringa User-ID estratta dalla risposta.
Se non vuoi supportare la limitazione dei dispositivi, non è necessario alcun intervento: la classe LicenseChecker
utilizza automaticamente un'implementazione predefinita denominata NullDeviceLimiter. Come suggerisce il nome, NullDeviceLimiter è una classe "no-op" il cui metodo allowDeviceAccess()
restituisce semplicemente una risposta LICENSED
per tutti gli utenti e i dispositivi.
Attenzione:le licenze per dispositivo non sono consigliate per la maggior parte delle applicazioni perché:
- Occorre fornire un server di backend per gestire la mappatura di utenti e dispositivi.
- inavvertitamente.
Offuscamento del codice
Per garantire la sicurezza della tua applicazione, in particolare per un'applicazione a pagamento che utilizza licenze e/o vincoli e protezioni personalizzati, è molto importante offuscare il codice dell'applicazione. Offuscare correttamente il codice rende più difficile per un utente malintenzionato decompilare il bytecode dell'applicazione, modificarlo, ad esempio rimuovendo il controllo della licenza, e ricompilarlo.
Per le applicazioni Android sono disponibili diversi programmi di offuscamento, tra cui ProGuard, che offre anche funzionalità di ottimizzazione del codice. L'utilizzo di ProGuard o di un programma simile per offuscare il codice è vivamente consigliato per tutte le applicazioni che utilizzano licenze di Google Play.
Pubblicazione di un'applicazione concessa in licenza
Al termine del test dell'implementazione della licenza, puoi pubblicare l'applicazione su Google Play. Segui i normali passaggi per preparare, firmare e pubblicare l'applicazione.
Dove ricevere assistenza
In caso di domande o in caso di problemi durante l'implementazione o il deployment della pubblicazione nelle tue applicazioni, utilizza le risorse di assistenza elencate nella tabella di seguito. Inserendo le tue query al forum corretto, puoi ricevere più rapidamente l'assistenza di cui hai bisogno.
Tipo di assistenza | Risorsa | Gamma di argomenti |
---|---|---|
Problemi di sviluppo e verifica | Google Gruppi: android-developers | download e integrazione LVL, progetti di libreria, Policy
domande, idee per l'esperienza utente, gestione delle risposte, Obfuscator , IPC, configurazione
dell'ambiente di test |
Stack Overflow: http://stackoverflow.com/questions/restricted/android | ||
Problemi relativi ad account, pubblicazione e deployment | Forum di assistenza di Google Play | Account editore, coppia di chiavi di licenza, account di test, risposte del server, risposte di test, deployment e risultati dell'applicazione |
Domande frequenti sull'assistenza per le licenze di mercato | ||
Issue Tracker LVL | Strumento di monitoraggio dei problemi del progetto di concessione in licenza | Segnalazioni di bug e problemi specifiche per le classi del codice sorgente LVL e le implementazioni dell'interfaccia |
Per informazioni generali su come pubblicare post per i gruppi elencati sopra, consulta la sezione Risorse della community nella pagina Risorse di assistenza per gli sviluppatori.
Risorse aggiuntive
L'applicazione di esempio inclusa con l'LVL fornisce un esempio completo di come avviare un controllo delle licenze e gestire il risultato, nella classe MainActivity
.