Dodawanie do aplikacji weryfikacji licencji po stronie klienta

Ostrzeżenie: gdy Twoja aplikacja przeprowadza proces weryfikacji licencji po stronie klienta, potencjalni hakerzy łatwiej mogą zmienić lub usunąć logikę powiązaną z tym procesem.

Dlatego zdecydowanie zalecamy przeprowadzenie weryfikacji licencji po stronie serwera.

Po skonfigurowaniu konta wydawcy i środowiska programistycznego (zobacz Konfigurowanie licencji) możesz dodać do aplikacji weryfikację licencji przy użyciu biblioteki weryfikacji licencji (Licencji Verification Library, LVL).

Dodanie weryfikacji licencji na LVL wymaga wykonania tych czynności:

  1. Dodaj uprawnienia dotyczące licencjonowania w pliku manifestu aplikacji.
  2. Wdrożenie zasad – możesz wybrać jedną z pełnych implementacji podanych w pozycji LVL lub utworzyć własną.
  3. Implementowanie zaciemniacza danych, jeśli Policy będzie zapisywać w pamięci podręcznej dane odpowiedzi dotyczących licencji.
  4. Dodaj kod sprawdzający licencję w głównej aktywności aplikacji.
  5. Wdrożenie funkcji DeviceLimiter (opcjonalne i niezalecane w przypadku większości aplikacji).

Opisy tych zadań znajdziesz w sekcjach poniżej. Po zakończeniu integracji możesz skompilować aplikację i rozpocząć testowanie, zgodnie z opisem w sekcji Konfigurowanie środowiska testowego.

Pełny zestaw plików źródłowych uwzględnionych w LVL znajdziesz w artykule Podsumowanie klas i interfejsów LVL.

Dodawanie uprawnień dotyczących licencjonowania

Aby aplikacja Google Play mogła wysłać do serwera weryfikację licencji, musi ona żądać odpowiednich uprawnień com.android.vending.CHECK_LICENSE. Jeśli aplikacja nie deklaruje uprawnień do licencjonowania, ale próbuje zainicjować sprawdzanie licencji, LVL zgłasza wyjątek w zakresie zabezpieczeń.

Aby poprosić w swojej aplikacji o zgodę na licencjonowanie, zadeklaruj element <uses-permission> jako element podrzędny elementu <manifest> w ten sposób:

<uses-permission android:name="com.android.vending.CHECK_LICENSE" />

Na przykład w ten sposób deklaruje uprawnienie w przykładowej aplikacji LVL:

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

Uwaga: obecnie nie można zadeklarować uprawnienia CHECK_LICENSE w pliku manifestu projektu biblioteki LVL, ponieważ narzędzia SDK nie scalą go z plikami manifestu aplikacji zależnych. Te uprawnienia musisz zadeklarować w pliku manifestu każdej niezależnej aplikacji.

Wdrażanie zasad

Usługa licencjonowania w Google Play nie określa, czy danemu użytkownikowi z daną licencją należy przyznać dostęp do aplikacji. Odpowiedzialność za tę odpowiedzialność ponosi raczej implementacja Policy, którą podasz w swojej aplikacji.

Policy to interfejs deklarowany przez LVL, który umożliwia działanie logiki aplikacji w celu przyznawania lub blokowania dostępu użytkownikom na podstawie wyników sprawdzania licencji. Aby można było używać LVL, Twoja aplikacja musi udostępniać implementację Policy.

Interfejs Policy deklaruje 2 metody: allowAccess() i processServerResponse(), które są wywoływane przez wystąpienie LicenseChecker podczas przetwarzania odpowiedzi serwera licencji. Deklaruje on też wyliczenie o nazwie LicenseResponse, które określa wartość odpowiedzi licencji przekazaną w wywołaniach do processServerResponse().

  • processServerResponse() umożliwia wstępne przetworzenie nieprzetworzonych danych odpowiedzi otrzymanych z serwera licencjonowania, zanim zdecyduje się, czy udzielić dostępu.

    W typowej implementacji wyodrębnia się niektóre lub wszystkie pola z odpowiedzi licencji i przechowuje dane lokalnie w trwałym magazynie, np. w pamięci SharedPreferences. Zapewni to dostęp do danych przy wszystkich wywołaniach aplikacji i cyklach zasilania urządzenia. Na przykład Policy przechowuje sygnaturę czasową ostatniego pomyślnego sprawdzenia licencji, liczbę ponownych prób, okres ważności licencji i podobne informacje w trwałym magazynie, zamiast resetować wartości przy każdym uruchomieniu aplikacji.

    Jeśli dane odpowiedzi są przechowywane lokalnie, Policy musi zadbać o to, aby dane były zaciemnione (patrz Implementacja zaciemnionego kodu poniżej).

  • allowAccess() określa, czy przyznać użytkownikowi dostęp do aplikacji na podstawie dostępnych danych odpowiedzi dotyczących licencji (z serwera licencjonowania lub z pamięci podręcznej) bądź innych informacji dotyczących aplikacji. Na przykład implementacja allowAccess() może uwzględniać dodatkowe kryteria, takie jak wykorzystanie lub inne dane pobrane z serwera backendu. We wszystkich przypadkach implementacja allowAccess() powinna zwracać wartość true tylko wtedy, gdy użytkownik ma licencję na korzystanie z aplikacji, zgodnie z ustawieniami serwera licencjonowania lub jeśli występuje przejściowy problem z siecią lub systemem, który uniemożliwia ukończenie sprawdzania licencji. W takich przypadkach implementacja może przechowywać liczbę ponownych odpowiedzi i tymczasowo zezwolić na dostęp do czasu zakończenia następnej kontroli licencji.

Aby uprościć proces dodawania licencji do aplikacji i ilustrować projekt elementu Policy, LVL zawiera 2 pełne implementacje typu Policy, których możesz użyć bez modyfikacji lub dostosować do swoich potrzeb:

  • ServerManagedPolicy – elastyczny Policy, który korzysta z ustawień dostarczanych przez serwer i odpowiedzi w pamięci podręcznej do zarządzania dostępem w różnych warunkach sieciowych,
  • StrictPolicy, który nie przechowuje żadnych danych odpowiedzi w pamięci podręcznej i umożliwia dostęp tylko wtedy, gdy serwer zwraca licencjonowaną odpowiedź.

W przypadku większości aplikacji zdecydowanie zalecamy korzystanie z ServerManagedPolicy. ServerManagedPolicy (zasada zarządzania serwerem) jest domyślną zasadą LVL i jest zintegrowana z przykładową aplikacją LVL.

Wytyczne dotyczące zasad niestandardowych

W implementacji licencjonowania możesz użyć jednej z pełnych zasad określonych w LVL (ServerManagedPolicy lub StrictPolicy) lub utworzyć zasadę niestandardową. W przypadku każdego typu niestandardowych zasad należy zrozumieć kilka ważnych kwestii projektowych, które trzeba wziąć pod uwagę podczas ich implementacji.

Serwer licencjonowania stosuje ogólne limity żądań, aby zapobiegać nadużywaniu zasobów, które może doprowadzić do zablokowania usługi. Gdy aplikacja przekracza limit żądań, serwer licencjonowania zwraca odpowiedź 503, która jest przekazywana do aplikacji jako ogólny błąd serwera. Oznacza to, że odpowiedź dotycząca licencji nie będzie dostępna dla użytkownika do czasu zresetowania limitu, co może mieć wpływ na użytkownika przez czas nieokreślony.

Jeśli tworzysz zasady niestandardowe, Policy:

  1. Buforuje (i prawidłowo zaciemnia) najnowszą udaną odpowiedź licencji w lokalnej pamięci trwałej.
  2. Zwraca odpowiedź z pamięci podręcznej w przypadku wszystkich kontroli licencji, dopóki pamięć podręczna jest ważna, a nie wysyła żądania do serwera licencji. Zdecydowanie zalecamy ustawienie poprawności odpowiedzi zgodnie z dodatkowym plikiem VT udostępnionym przez serwer. Więcej informacji znajdziesz w artykule Dodatki do odpowiedzi serwera.
  3. Używa wykładniczego okresu ponowienia – jeśli ponawianie jakichkolwiek żądań skutkuje błędami. Pamiętaj, że klient Google Play automatycznie ponawia nieudane żądania, więc w większości przypadków nie ma potrzeby, by Policy powtarzał je.
  4. Zapewnia „okres prolongaty”, który umożliwia użytkownikowi dostęp do aplikacji przez ograniczony czas lub określoną liczbę zastosowań, podczas gdy ponownie sprawdzana jest licencja. Okres prolongaty jest korzystny dla użytkownika, ponieważ zezwala na dostęp do czasu następnego sprawdzenia licencji. Korzystny jest dla Ciebie ograniczenie dostępu do aplikacji w przypadku braku ważnej odpowiedzi na żądanie licencji.

Zaprojektowanie Policy zgodnie z powyższymi wskazówkami ma kluczowe znaczenie, ponieważ zapewnia użytkownikom najlepsze wrażenia, a jednocześnie skuteczną kontrolę nad aplikacją nawet w przypadku błędów.

Pamiętaj, że każdy Policy może korzystać z ustawień dostarczonych przez serwer licencji, aby ułatwić m.in. zarządzanie ważnością i buforowaniem, a także ponawianie okresu prolongaty. Wyodrębnianie ustawień dostarczanych przez serwer jest bardzo łatwe, dlatego zdecydowanie zalecamy ich wykorzystanie. Przykład pobierania i używania dodatków znajdziesz w opisie ServerManagedPolicy. Listę ustawień serwera i informacje o tym, jak z nich korzystać, znajdziesz w artykule Dodatki do odpowiedzi serwera.

Zasada zarządzania serwerem

LVL obejmuje pełną i zalecaną implementację interfejsu Policy o nazwie ServerManagedPolicy. Implementacja jest zintegrowana z klasami LVL i służy jako domyślny element Policy w bibliotece.

ServerManagedPolicy (zasada zarządzania serwerem) zapewnia obsługę licencji i odpowiedzi na ponawianie prób. Wszystkie dane odpowiedzi zapisuje lokalnie w pamięci podręcznej w pliku SharedPreferences, maskując je w implementacji Obfuscator aplikacji. Zapewnia to bezpieczeństwo danych odpowiedzi dotyczących licencji i sprawia, że dane są wysyłane niezależnie od czasu wyłączenia urządzenia. ServerManagedPolicy udostępnia konkretne implementacje metod interfejsu processServerResponse() i allowAccess(), a także zestaw dodatkowych metod i typów zarządzania odpowiedziami w ramach licencji.

Ważną funkcją ServerManagedPolicy jest możliwość korzystania z ustawień udostępnianych przez serwer jako podstawy zarządzania licencjami w okresie zwrotu środków za aplikację oraz w zależności od zmieniających się warunków sieci i błędów. Gdy aplikacja kontaktuje się z serwerem Google Play w celu sprawdzenia licencji, w niektórych typach odpowiedzi licencji serwer dołącza kilka ustawień w postaci par klucz-wartość w polu dodatków. Serwer może na przykład podawać zalecane wartości okresu ważności licencji aplikacji, okresu prolongaty ponawiania próby i maksymalnej dozwolonej liczbie ponownych prób. ServerManagedPolicy wyodrębnia wartości z odpowiedzi licencji za pomocą metody processServerResponse() i sprawdza je za pomocą metody allowAccess(). Listę ustawień dostarczanych przez serwer i używanych przez serwer zarządzania zasadami znajdziesz w artykule Dodatkowe opcje odpowiedzi serwera.

Ze względu na wygodę, największą wydajność oraz korzyści wynikające z używania ustawień licencji z serwera Google Play zdecydowanie zalecamy użycie ServerManagedPolicy, ponieważ Policy używa licencji na Policy.

Jeśli obawiasz się o bezpieczeństwo danych odpowiedzi licencji, które są przechowywane lokalnie w usłudze SharedPreferences, możesz użyć silniejszego algorytmu zaciemniania kodu lub zaprojektować bardziej rygorystyczną zasadę Policy, która nie przechowuje danych licencji. Element LVL zawiera przykład takiej właściwości Policy – więcej informacji znajdziesz w sekcji StrictPolicy.

Aby użyć zasady zarządzania serwerem, po prostu zaimportuj ją do swojej aktywności, utwórz instancję i przekaż do niej odwołanie podczas tworzenia LicenseChecker. Więcej informacji znajdziesz w materiałach o ApplicationChecker i LicenseCheckerCallback.

Ścisła zasada

LVL obejmuje alternatywną, pełną implementację interfejsu Policy o nazwie StrictPolicy. Implementacja StrictPolicy zapewnia bardziej restrykcyjną zasadę niż ServerManagedPolicy, ponieważ nie zezwala użytkownikowi na dostęp do aplikacji, chyba że w momencie dostępu serwer otrzyma z serwera odpowiedź potwierdzającą, że użytkownik ma licencję.

Podstawową cechą zasady StrictPolicy polega na tym, że nie przechowuje ona żadnych danych odpowiedzi na licencje lokalnie w trwałym magazynie. Ponieważ nie są przechowywane żadne dane, żądania ponowienia próby nie są śledzone, a odpowiedzi w pamięci podręcznej nie mogą być używane do sprawdzania licencji. Policy zezwala na dostęp tylko wtedy, gdy:

  • Odpowiedź dotycząca licencji jest odbierana z serwera licencjonowania.
  • Odpowiedź dotycząca licencji wskazuje, że użytkownik ma licencję na dostęp do aplikacji.

Korzystanie z rygorystycznego zasad jest dobrym rozwiązaniem, jeśli Twoim głównym celem jest pewność, że w każdym przypadku żaden użytkownik nie będzie miał dostępu do aplikacji, chyba że w momencie użycia uzyska on licencję. Ponadto zapewnia ona nieco większe bezpieczeństwo niż ServerManagedPolicy, ponieważ dane nie są przechowywane lokalnie w pamięci podręcznej, więc złośliwy użytkownik nie ma możliwości manipulowania danymi przechowywanymi w pamięci podręcznej i uzyskiwania dostępu do aplikacji.

Jednocześnie Policy stanowi problem dla zwykłych użytkowników, ponieważ oznacza, że nie mogą oni uzyskać dostępu do aplikacji, gdy nie ma połączenia z siecią (komórkową lub Wi-Fi). Innym skutkiem ubocznym jest to, że aplikacja będzie wysyłać do serwera więcej żądań sprawdzania licencji, ponieważ użycie odpowiedzi z pamięci podręcznej nie jest możliwe.

Ogólnie ta zasada ogranicza wygodę użytkowników, zapewniając absolutne bezpieczeństwo i kontrolę nad dostępem. Zanim użyjesz tego parametru Policy, dokładnie zastanów się nad tymi kompromisami.

Aby użyć zasady StrictPolicy, zaimportuj ją do aktywności, utwórz instancję i przekaż do niej odwołanie podczas tworzenia LicenseChecker. Więcej informacji znajdziesz w materiałach na temat błyskawicznego obiektu LicenseChecker i LicenseCheckerCallback.

Typowa implementacja Policy musi zapisywać dane odpowiedzi licencji dla aplikacji w magazynie trwałym, aby były dostępne w ramach wywołań aplikacji i cykli zasilania urządzenia. Na przykład Policy przechowuje sygnaturę czasową ostatniego pomyślnego sprawdzenia licencji, liczbę ponownych prób, okres ważności licencji i podobne informacje w trwałym magazynie, a nie resetowanie wartości przy każdym uruchomieniu aplikacji. Domyślny Policy w LVL (ServerManagedPolicy) przechowuje dane odpowiedzi licencji w instancji SharedPreferences, aby zapewnić ich trwałość.

Policy będzie używać przechowywanych danych odpowiedzi dotyczących licencji do określenia, czy zezwolić na dostęp aplikacji, czy go zablokować. Dlatego musi zadbać o to, aby wszystkie przechowywane dane były bezpieczne i nie było można ich użyć ponownie ani zmodyfikować przez roota na urządzeniu. W szczególności Policy musi zaciemniać dane przed ich zapisaniem, używając klucza unikalnego dla aplikacji i urządzenia. Ukrywanie za pomocą klucza, który dotyczy zarówno aplikacji, jak i urządzenia, jest kluczowe, ponieważ zapobiega udostępnianiu zaciemnionych danych między aplikacjami i urządzeniami.

LVL pomaga aplikacji przechowywać dane odpowiedzi na żądanie licencji w bezpieczny i trwały sposób. Po pierwsze udostępnia interfejs Obfuscator, który pozwala aplikacji dostarczać wybrany przez siebie algorytm zaciemniania przechowywanych danych. Dzięki temu LVL udostępnia klasę pomocniczą PreferenceObfuscator, która obsługuje większość zadań związanych z wywoływaniem klasy Obfuscator aplikacji oraz odczytywanie i zapisywanie zaciemnionych danych w instancji SharedPreferences.

LVL udostępnia pełną implementację Obfuscator o nazwie AESObfuscator, która używa szyfrowania AES do zaciemniania danych. Możesz użyć narzędzia AESObfuscator w swojej aplikacji bez modyfikacji lub dostosować go do swoich potrzeb. Jeśli używasz obiektu Policy (na przykład ServerManagedPolicy), który przechowuje w pamięci podręcznej dane odpowiedzi dotyczących licencji, zdecydowanie zalecamy użycie AESObfuscator na potrzeby implementacji Obfuscator. Więcej informacji znajdziesz w następnej sekcji.

Moduł AESObfuscator

LVL zawiera pełną i zalecaną implementację interfejsu Obfuscator o nazwie AESObfuscator. Implementacja jest zintegrowana z przykładową aplikacją LVL i służy jako domyślny element Obfuscator w bibliotece.

AESObfuscator zapewnia bezpieczne zaciemnianie kodu przy użyciu AES do szyfrowania i odszyfrowywania danych w momencie, gdy są one zapisywane w pamięci lub z niej odczytywane. Obfuscator wykorzystuje 3 pola danych dostarczonych przez aplikację:

  1. sól – tablica losowych bajtów do użycia przy każdym (nie) zaciemnianiu kodu.
  2. Ciąg identyfikatora aplikacji, zwykle nazwa pakietu aplikacji.
  3. Ciąg identyfikatora urządzenia pobrany z jak największej liczby źródeł danych o urządzeniu, aby był maksymalnie niepowtarzalny.

Aby używać AESObfuscator, najpierw zaimportuj go do swojej aktywności. Zadeklarowanie prywatnej statycznej tablicy końcowej do przechowywania bajtów zaburzających i zainicjowania jej dla 20 losowo wygenerowanych bajtów.

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

Następnie zadeklaruj zmienną przechowującą identyfikator urządzenia i generując dla niej wartość w dowolny sposób. Na przykład przykładowa aplikacja uwzględniona w LVL wysyła zapytanie do ustawień systemowych dla zasobu android.Settings.Secure.ANDROID_ID, które są unikalne dla każdego urządzenia.

Pamiętaj, że w zależności od używanych interfejsów API aplikacja może wymagać dodatkowych uprawnień, aby uzyskać informacje dotyczące konkretnego urządzenia. Aby na przykład wysłać zapytanie do TelephonyManager w celu uzyskania numeru IMEI urządzenia lub powiązanych danych, aplikacja musi też poprosić o uprawnienie android.permission.READ_PHONE_STATE w swoim manifeście.

Zanim poprosisz o nowe uprawnienia w wyłącznym celu, czyli uzyskania informacji o konkretnym urządzeniu do wykorzystania w Obfuscator, zastanów się, jak może to wpłynąć na działanie Twojej aplikacji lub jej filtrowania w Google Play (ponieważ niektóre uprawnienia mogą powodować dodawanie powiązanych <uses-feature> uprawnień przez narzędzia do kompilacji SDK).

Na koniec utwórz instancję AESObfuscator, która przekazuje sól, identyfikator aplikacji i identyfikator urządzenia. Instancja możesz utworzyć bezpośrednio, jednocześnie tworząc Policy i LicenseChecker. Na przykład:

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

Pełny przykład znajdziesz w sekcji MainActivity w przykładowej aplikacji LVL.

Sprawdzanie licencji w ramach aktywności

Po wdrożeniu obiektu Policy do zarządzania dostępem do aplikacji następnym krokiem jest dodanie do niej kontroli licencji, co w razie potrzeby inicjuje zapytanie do serwera licencjonowania i zarządza dostępem do aplikacji na podstawie odpowiedzi licencji. Cała praca związana z dodaniem kontroli licencji i obsługą odpowiedzi odbywa się w głównym pliku źródłowym Activity.

Aby dodać sprawdzanie licencji i przetwarzać odpowiedź:

  1. Dodawanie importów
  2. Zaimplementuj LicenseCheckerCallback jako prywatną klasę wewnętrzną
  3. Utwórz moduł obsługi publikowania z metody LicenseCheckerCallback w wątku interfejsu użytkownika
  4. błyskawiczne sprawdzanie licencji i wywołanie zwrotne zdarzenia LicenseChecker.
  5. Wywołaj funkcję checkAccess(), aby rozpocząć sprawdzanie licencji.
  6. Umieszczanie klucza publicznego na potrzeby licencjonowania
  7. Wywołaj metodę onDestroy() w obiekcie LicenseChecker, aby zamknąć połączenia IPC.

Opisy tych zadań znajdziesz w sekcjach poniżej.

Omówienie sprawdzania licencji i odpowiadania na nie

W większości przypadków sprawdzenie licencji należy dodać do głównego pliku Activity aplikacji za pomocą metody onCreate(). Dzięki temu masz pewność, że gdy użytkownik bezpośrednio uruchomi Twoją aplikację, sprawdzanie licencji zostanie natychmiast uruchomione. W niektórych przypadkach możesz też dodać kontrole licencji w innych lokalizacjach. Jeśli na przykład aplikacja zawiera wiele komponentów aktywności, które inne aplikacje mogą uruchomić do Intent, możesz dodać kontrole licencji do tych działań.

Proces sprawdzania licencji obejmuje 2 główne czynności:

  • Wywołanie metody inicjowania sprawdzania licencji – w obiekcie LVL jest to wywołanie metody checkAccess() konstruowanego obiektu LicenseChecker.
  • Wywołanie zwrotne, które zwraca wynik sprawdzania licencji. W LVL jest to zaimplementowany interfejs LicenseCheckerCallback. Interfejs deklaruje 2 metody: allow() i dontAllow(), które są wywoływane przez bibliotekę w zależności od wyniku sprawdzania licencji. Te 2 metody wdraża się w dowolny sposób, określając przyznawanie lub blokowanie dostępu użytkownika do aplikacji. Pamiętaj, że te metody nie określają, czy zezwolić na dostęp – za tę decyzję odpowiada implementacja Policy. Metody te dostarczają po prostu informacji o tym, jak zezwalać na dostęp lub go blokować (oraz usuwać błędy aplikacji).

    Metody allow() i dontAllow() wskazują powód odpowiedzi, którym może być jedna z wartości Policy, LICENSED, NOT_LICENSED lub RETRY. W szczególności musisz zastosować się do przypadku, w którym metoda otrzyma odpowiedź RETRY dla elementu dontAllow(), i udostępnić użytkownikowi przycisk „Ponów”, który mógł wystąpić dlatego, że usługa była niedostępna w trakcie żądania.

Rysunek 1. Omówienie typowego sprawdzania licencji.

Powyższy diagram przedstawia typową kontrolę licencji:

  1. Kod w głównej aktywności aplikacji tworzy instancję LicenseCheckerCallback i LicenseChecker. Podczas tworzenia elementu LicenseChecker kod przekazuje jako parametry w elemencie Context, implementacji Policy do użycia oraz klucz publiczny konta wydawcy na potrzeby licencjonowania.
  2. Kod wywołuje następnie metodę checkAccess() w obiekcie LicenseChecker. Implementacja metody wywołuje Policy, aby określić, czy istnieje prawidłowa odpowiedź licencji przechowywana w pamięci podręcznej w SharedPreferences.
    • Jeśli tak, implementacja checkAccess() wywołuje metodę allow().
    • W przeciwnym razie LicenseChecker inicjuje żądanie sprawdzenia licencji, które jest wysyłane do serwera licencji.

    Uwaga: podczas sprawdzania licencji roboczej wersji aplikacji serwer licencji zawsze zwraca LICENSED.

  3. Po otrzymaniu odpowiedzi LicenseChecker tworzy obiekt LicenseValidator, który sprawdza dane podpisanej licencji i wyodrębnia pola odpowiedzi, a następnie przekazuje je do Policy w celu dalszej oceny.
    • Jeśli licencja jest prawidłowa, Policy zapisuje odpowiedź w pamięci podręcznej w SharedPreferences i powiadamia walidator, który następnie wywołuje metodę allow() w obiekcie LicenseCheckerCallback.
    • Jeśli licencja jest nieprawidłowa, Policy powiadamia walidator, który wywołuje metodę dontAllow() na urządzeniu LicenseCheckerCallback.
  4. W przypadku możliwego do odzyskania błędu lokalnego lub serwera, np. gdy sieć nie jest dostępna, aby wysłać żądanie, LicenseChecker przekazuje odpowiedź RETRY na metodę processServerResponse() obiektu Policy.

    Poza tym metody wywołania zwrotnego allow() i dontAllow() otrzymują argument reason. Powód metody allow() to zwykle Policy.LICENSED lub Policy.RETRY, a przyczyna dontAllow() to zwykle Policy.NOT_LICENSED lub Policy.RETRY. Te wartości odpowiedzi są przydatne, ponieważ pozwalają wyświetlać odpowiednią odpowiedź użytkownikowi, na przykład wyświetlając przycisk „Ponów”, gdy dontAllow() wysyła odpowiedź Policy.RETRY. Przyczyną może być to, że usługa była niedostępna.

  5. W przypadku błędu aplikacji, np. gdy aplikacja próbuje sprawdzić licencję z nieprawidłową nazwą pakietu, LicenseChecker przekazuje błąd na metodę applicationError() metody LicenseCheckerCallback.

Pamiętaj, że oprócz rozpoczęcia sprawdzania licencji i obsługi wyniku, jak opisano w sekcjach poniżej, aplikacja musi też udostępnić implementację zasad, a jeśli Policy przechowuje dane odpowiedzi (np. ServerManagedPolicy), implementację Obfuscator.

Dodaj importy

Najpierw otwórz plik klasy głównej aktywności aplikacji i zaimportuj LicenseChecker oraz LicenseCheckerCallback z pakietu 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;

Jeśli używasz domyślnej implementacji Policy udostępnianej w ramach LVL (ServerManagedPolicy), zaimportuj ją także razem z AESObfuscator. Jeśli używasz niestandardowych elementów Policy lub Obfuscator, zaimportuj je.

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;

Implementowanie funkcji LicenseCheckerCallback jako prywatnej klasy wewnętrznej

LicenseCheckerCallback to interfejs udostępniany przez LVL do obsługi wyników kontroli licencji. Aby obsługiwać licencjonowanie za pomocą LVL, musisz wdrożyć LicenseCheckerCallback i zastosowane w nim metody zezwalania lub blokowania dostępu do aplikacji.

Wynik sprawdzania licencji jest zawsze wywołaniem jednej z metod LicenseCheckerCallback, realizowanej na podstawie weryfikacji ładunku odpowiedzi, samego kodu odpowiedzi serwera i dodatkowego przetwarzania zapewnianego przez Policy. Metody te można wdrożyć w aplikacji w dowolny sposób. Ogólnie rzecz biorąc, najlepiej jest stosować proste metody, ograniczając je do zarządzania stanem interfejsu użytkownika i dostępem do aplikacji. Jeśli chcesz kontynuować przetwarzanie odpowiedzi licencji, na przykład skontaktować się z serwerem backendu lub zastosować ograniczenia niestandardowe, rozważ włączenie tego kodu do Policy zamiast umieszczania go w metodach LicenseCheckerCallback.

W większości przypadków implementację LicenseCheckerCallback należy zadeklarować jako klasę prywatną w głównej klasie aktywności aplikacji.

W razie potrzeby zaimplementuj metody allow() i dontAllow(). Na początek możesz korzystać z prostych sposobów obsługi wyników w metodach, takich jak wyświetlanie wyniku licencji w oknie. Przyspieszy to uruchomienie aplikacji i może pomóc w debugowaniu. Później, po określeniu pożądanych zachowań, możesz dodać bardziej złożoną obsługę.

Oto kilka sugestii dotyczących postępowania z nielicencjonowanymi odpowiedziami w dontAllow():

  • Wyświetl użytkownikowi okno „Spróbuj ponownie”, w tym przycisk rozpoczynania nowej licencji, jeśli podany reason to Policy.RETRY.
  • Wyświetl okno „Kup tę aplikację” zawierające przycisk, który zawiera precyzyjny link do strony z informacjami o aplikacji w Google Play, na której użytkownik może kupić aplikację. Więcej informacji na temat konfigurowania takich linków znajdziesz w artykule Tworzenie linków do swoich produktów.
  • Wyświetl toast z informacją, że funkcje aplikacji są ograniczone, ponieważ nie jest ona licencjonowana.

Przykład poniżej pokazuje, jak przykładowa aplikacja LVL implementuje element LicenseCheckerCallback, przy czym metody wyświetlające wynik sprawdzania licencji są wyświetlane w oknie.

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

Dodatkowo należy zaimplementować metodę applicationError(), która jest wywoływana przez LVL, aby aplikacja mogła obsługiwać błędy bez możliwości ponowienia próby. Listę takich błędów znajdziesz w sekcji Kody odpowiedzi serwera w dokumentacji dotyczącej licencji. Możesz wdrożyć tę metodę w dowolny sposób. W większości przypadków metoda powinna zarejestrować kod błędu i wywołać dontAllow().

utworzyć moduł obsługi publikowania z metody LicenseCheckerCallback w wątku interfejsu użytkownika,

Podczas sprawdzania licencji LVL przekazuje żądanie do aplikacji Google Play, która obsługuje komunikację z serwerem licencjonowania. LVL przekazuje żądanie przez asynchroniczny adres IPC (przy użyciu Binder), dzięki czemu rzeczywiste przetwarzanie i komunikacja sieciowa nie odbywają się w wątku zarządzanym przez aplikację. Podobnie gdy aplikacja Google Play otrzyma wynik, wywołuje metodę wywołania zwrotnego przez IPC, która z kolei jest wykonywana w puli wątków IPC w procesie aplikacji.

Klasa LicenseChecker zarządza komunikacją IPC aplikacji z aplikacją Google Play, w tym wywołaniem wysyłającym żądanie i wywołaniem zwrotnym, które odbiera odpowiedź. LicenseChecker śledzi też prośby o otwarte licencje i zarządza ich czasem oczekiwania.

Aby prawidłowo obsługiwała limity czasu oczekiwania i przetwarzała odpowiedzi przychodzące bez wpływu na wątek UI aplikacji, w momencie tworzenia instancji LicenseChecker generuje wątek w tle. W wątku przetwarza on wyniki sprawdzania licencji niezależnie od tego, czy jest to odpowiedź z serwera, czy też błąd przekroczenia limitu czasu. Po zakończeniu przetwarzania LVL wywołuje Twoje metody LicenseCheckerCallback z wątku w tle.

Dla Twojej aplikacji oznacza to, że:

  1. Metody LicenseCheckerCallback będą w wielu przypadkach wywoływane z wątku w tle.
  2. Te metody nie będą w stanie aktualizować stanu ani wywoływać żadnych operacji w wątku UI, chyba że utworzysz moduł obsługi w wątku UI i prześlesz swoje metody wywołań zwrotnych do modułu obsługi.

Jeśli chcesz, aby metody LicenseCheckerCallback aktualizowały wątek UI, skonfiguruj Handler w głównej metodzie onCreate() aktywności, jak pokazano poniżej. W tym przykładzie metody LicenseCheckerCallback w przykładowej aplikacji LVL (patrz wyżej) wywołują metodę displayResult() w celu zaktualizowania wątku UI za pomocą metody post() modułu obsługi.

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

Następnie w metodach LicenseCheckerCallback możesz użyć metod obsługi, aby publikować obiekty Runnable lub Message w module obsługi. Oto jak przykładowa aplikacja uwzględniona w LVL publikuje element wykonywalny do modułu obsługi w wątku interfejsu, aby wyświetlić stan licencji.

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

Tworzenie instancji LicenseChecker i LicenseCheckerCallback

W głównej metodzie onCreate() aktywności utwórz prywatne instancje poleceń LicenseCheckerCallback i LicenseChecker. Najpierw musisz zainicjować LicenseCheckerCallback, ponieważ podczas wywoływania konstruktora dla LicenseChecker trzeba przekazać do tej instancji odwołanie.

Podczas tworzenia instancji LicenseChecker musisz przekazać te parametry:

  • Aplikacja Context
  • Odwołanie do implementacji Policy używanej do sprawdzania licencji. W większości przypadków zostanie użyta domyślna implementacja Policy udostępniona przez LVL (ServerManagedPolicy).
  • zmienna Ciąg znaków przechowująca klucz publiczny konta wydawcy na potrzeby licencjonowania.

Jeśli korzystasz z ServerManagedPolicy, nie musisz mieć bezpośredniego dostępu do klasy, więc możesz utworzyć jej instancję w konstruktorze LicenseChecker, jak pokazano w poniższym przykładzie. Pamiętaj, że podczas tworzenia ServerManagedPolicy musisz przekazać odwołanie do nowej instancji obfumatora.

Przykład poniżej pokazuje utworzenie instancji LicenseChecker i LicenseCheckerCallback z metody onCreate() klasy 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.
            );
        ...
    }
}

Pamiętaj, że LicenseChecker wywołuje metody LicenseCheckerCallback z wątku UI tylko wtedy, gdy lokalnie znajduje się w pamięci podręcznej prawidłowa odpowiedź licencji. Jeśli sprawdzanie licencji jest wysyłane do serwera, wywołania zwrotne zawsze pochodzą z wątku w tle, nawet w przypadku błędów sieci.

Wywołaj metodę checkAccess(), aby rozpocząć sprawdzanie licencji.

W głównej aktywności dodaj wywołanie do metody checkAccess() instancji LicenseChecker. W wywołaniu jako parametr przekaż odwołanie do instancji LicenseCheckerCallback. Jeśli przed wywołaniem musisz obsługiwać specjalne efekty interfejsu lub zarządzanie stanem, przydatne może być wywołanie checkAccess() z metody wrapper. Na przykład przykładowa aplikacja LVL wywołuje metodę checkAccess() z metodą otoki 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);
    }

Umieszczanie klucza publicznego na potrzeby licencjonowania

Dla każdej aplikacji usługa Google Play automatycznie generuje 2048-bitową parę kluczy publiczny/prywatny RSA służącą do licencjonowania i rozliczeń w aplikacji. Para kluczy jest jednoznacznie powiązana z aplikacją. Para kluczy jest powiązana z aplikacją, ale nie jest taka sama jak klucz, którego używasz do podpisywania aplikacji (lub z niej pochodzi).

Konsola Google Play ujawnia klucz publiczny na potrzeby licencjonowania każdemu deweloperowi zalogowanemu w Konsoli Play. Klucz prywatny jest jednak ukryty przed wszystkimi użytkownikami w bezpiecznej lokalizacji. Gdy aplikacja żąda sprawdzenia licencji na aplikację opublikowaną na Twoim koncie, serwer licencjonowania podpisuje odpowiedź licencji przy użyciu klucza prywatnego pary kluczy aplikacji. Po otrzymaniu odpowiedzi LVL używa klucza publicznego udostępnionego przez aplikację, aby zweryfikować podpis odpowiedzi licencji.

Aby dodać licencjonowanie do aplikacji, musisz uzyskać jej klucz publiczny do licencjonowania i skopiować go do aplikacji. Aby znaleźć klucz publiczny aplikacji na potrzeby licencjonowania:

  1. Otwórz Konsolę Google Play i zaloguj się. Pamiętaj, aby zalogować się na konto, z którego aplikacja, którą licencjonujesz, została opublikowana (lub zostanie opublikowana).
  2. Na stronie z informacjami o aplikacji znajdź link Usługi i interfejsy API i kliknij go.
  3. Na stronie Usługi i interfejsy API znajdź sekcję Licencjonowanie i rozliczenia w aplikacji. Twój klucz publiczny na potrzeby licencjonowania jest podany w polu Twój klucz licencyjny dla tej aplikacji.

Aby dodać klucz publiczny do swojej aplikacji, skopiuj po prostu ciąg klucza z pola i wklej go w aplikacji jako wartość zmiennej ciągu znaków BASE64_PUBLIC_KEY. Podczas kopiowania sprawdź, czy został wybrany cały ciąg klucza, bez pominięcia żadnych znaków.

Oto przykład z przykładowej aplikacji LVL:

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

Wywołaj metodę onDestroy() w licencji LicenseChecker, aby zamknąć połączenia IPC

Aby umożliwić działanie LVL przed wprowadzeniem zmian w Twojej aplikacji (Context), dodaj wywołanie do metody LicenseChecker onDestroy() z implementacji onDestroy() Twojej aktywności. Wywołanie sprawia, że LicenseChecker prawidłowo zamyka wszystkie otwarte połączenie IPC z usługą ILicensingService w aplikacji Google Play oraz usuwa wszystkie lokalne odwołania do usługi i modułu obsługi.

Niewywołanie metody onDestroy() w LicenseChecker może spowodować problemy w całym cyklu życia aplikacji. Jeśli na przykład użytkownik zmieni orientację ekranu w trakcie sprawdzania licencji, aplikacja Context zostanie zniszczona. Jeśli Twoja aplikacja nie zamknie w prawidłowy sposób połączenia IPC z interfejsem LicenseChecker, po otrzymaniu odpowiedzi ulegnie awarii. Podobnie jeśli użytkownik zamknie aplikację w trakcie sprawdzania licencji, aplikacja ulegnie awarii po otrzymaniu odpowiedzi, chyba że poprawnie wywoła metodę onDestroy() systemu LicenseChecker, aby odłączyć się od usługi.

Oto przykład z przykładowej aplikacji uwzględnionej w LVL, gdzie mChecker to wystąpienie LicenseChecker:

Kotlin

    override fun onDestroy() {
        super.onDestroy()
        checker.onDestroy()
        ...
    }

Java

    @Override
    protected void onDestroy() {
        super.onDestroy();
        checker.onDestroy();
        ...
    }

Jeśli rozszerzasz lub modyfikujesz LicenseChecker, być może trzeba będzie również wywołać metodę finishCheck() LicenseChecker, aby wyczyścić wszystkie otwarte połączenia IPC.

Implementowanie funkcji DeviceLimiter

W niektórych przypadkach możesz chcieć, aby Policy ograniczała liczbę rzeczywistych urządzeń, na których można korzystać z jednej licencji. Uniemożliwia to użytkownikowi przeniesienie licencjonowanej aplikacji na kilka urządzeń i korzystanie z niej na tych urządzeniach z użyciem tego samego identyfikatora konta. Uniemożliwi to też użytkownikowi „udostępnianie” aplikacji przez udostępnienie informacji o koncie powiązanych z licencją innym osobom, które będą mogły zalogować się na to konto na swoich urządzeniach i uzyskać dostęp do licencji na aplikację.

LVL obsługuje licencjonowanie według urządzeń przez udostępnienie interfejsu DeviceLimiter, który deklaruje pojedynczą metodę allowDeviceAccess(). Gdy komponent LicenseValidator obsługuje odpowiedź z serwera licencjonowania, wywołuje metodę allowDeviceAccess(), przekazując ciąg znaków identyfikatora użytkownika wyodrębniony z odpowiedzi.

Jeśli nie chcesz obsługiwać ograniczeń urządzeń, nie musisz nic robić – klasa LicenseChecker automatycznie korzysta z domyślnej implementacji o nazwie NullDeviceLimiter. Jak sama nazwa wskazuje, NullDeviceLimiter to klasa „no-op”, której metoda allowDeviceAccess() po prostu zwraca odpowiedź LICENSED dla wszystkich użytkowników i urządzeń.

Uwaga: licencjonowanie poszczególnych urządzeń nie jest zalecane w przypadku większości aplikacji, ponieważ:

  • Wymaga to dostarczenia serwera backendu do zarządzania mapowaniem użytkowników i urządzeń,
  • Może to spowodować nieumyślną odmowę dostępu użytkownika do aplikacji, którą legalnie kupił na innym urządzeniu.

Ukrywanie kodu

Aby zapewnić bezpieczeństwo aplikacji, szczególnie w przypadku aplikacji płatnych, która korzysta z licencji lub niestandardowych ograniczeń i zabezpieczeń, bardzo ważne jest zaciemnienie kodu. Prawidłowe zaciemnianie kodu utrudnia złośliwemu użytkownikowi zdekompilowanie kodu bajtowego aplikacji, zmodyfikowanie go (np. przez usunięcie kontroli licencji) oraz ponowne skompilowanie go.

Dla aplikacji na Androida dostępnych jest kilka programów do zaciemniania kodu, w tym ProGuard, który oferuje również funkcje optymalizacji kodu. W przypadku wszystkich aplikacji korzystających z licencjonowania Google Play zdecydowanie zalecamy użycie ProGuard lub podobnego programu do zaciemniania kodu.

Publikowanie licencjonowanej aplikacji

Po zakończeniu testowania implementacji licencji możesz opublikować aplikację w Google Play. Wykonaj zwykłe czynności, by przygotować, podpisać, a następnie opublikować aplikację.

Gdzie uzyskać pomoc

Jeśli masz pytania lub problemy podczas wdrażania lub publikowania w aplikacjach, skorzystaj z zasobów pomocy wymienionych w tabeli poniżej. Kierując zapytania na właściwe forum, możesz szybciej uzyskać potrzebną pomoc.

Tabela 2. zasobów pomocy dla deweloperów dotyczących usługi licencjonowania w Google Play.

Typ pomocy Zasób Zakres tematów
Problemy podczas programowania i testowania Grupy dyskusyjne Google: android-developers Pobieranie i integracja LVL, projekty biblioteczne, Policypytania, pomysły na wrażenia użytkownika, obsługa odpowiedzi, Obfuscator, IPC, konfiguracja środowiska testowego
Stack Overflow: http://stackoverflow.com/questions/tagged/android
Problemy z kontami, publikowaniem i wdrażaniem Forum pomocy Google Play Konta wydawców, para kluczy licencyjnych, konta testowe, odpowiedzi serwera, odpowiedzi testowe, wdrażanie aplikacji i wyniki
Najczęstsze pytania dotyczące pomocy dotyczącej licencjonowania na rynku
Śledzenie problemów LVL Narzędzie do śledzenia problemów z projektem dotyczącym licencjonowania Raporty o błędach i problemach związane konkretnie z klasami kodu źródłowego LVL i implementacjami interfejsów

Ogólne informacje o tym, jak publikować w wymienionych wyżej grupach, znajdziesz w sekcji Zasoby społeczności na stronie z zasobami pomocy dla deweloperów.

Dodatkowe materiały

Przykładowa aplikacja dołączona do LVL zawiera pełny przykład rozpoczęcia sprawdzania licencji i obsługi wyniku w klasie MainActivity.