Pliki rozszerzeń APK

Google Play wymaga, aby skompresowany plik APK, który pobierają użytkownicy, nie był większy niż 100 MB. W przypadku większości aplikacji jest to wystarczająco dużo miejsca na kod i zasoby aplikacji. Niektóre aplikacje potrzebują jednak więcej miejsca na grafikę, pliki multimedialne i inne duże zasoby w wysokiej jakości. Wcześniej, gdy rozmiar skompresowanego pliku do pobrania przekraczał 100 MB, dodatkowe zasoby trzeba było hostować i pobierać samodzielnie, gdy użytkownik otwiera aplikację. Hosting i wyświetlanie dodatkowych plików może być kosztowne, a wygoda użytkowników często nie jest idealna. Aby ułatwić Ci ten proces i zapewnić użytkownikom większą wygodę, Google Play umożliwia załączanie 2 dużych plików rozszerzeń, które uzupełniają plik APK.

Google Play przechowuje pliki rozszerzeń Twojej aplikacji i udostępnia je na urządzeniu bezpłatnie. Pliki rozszerzeń są zapisywane w pamięci współdzielonej urządzenia (na karcie SD lub partycji podłączanej przez USB, zwanej też w pamięci zewnętrznej), z której aplikacja może korzystać. Na większości urządzeń Google Play pobiera pliki rozszerzeń w momencie pobierania pliku APK, dzięki czemu aplikacja ma wszystko, czego potrzebuje, gdy użytkownik otworzy ją po raz pierwszy. W niektórych przypadkach musi jednak pobrać pliki z Google Play podczas uruchamiania.

Jeśli nie chcesz używać plików rozszerzeń, a skompresowany rozmiar pobieranej aplikacji przekracza 100 MB, prześlij ją za pomocą pakietów Android App Bundle, które umożliwiają uzyskanie skompresowanego rozmiaru pobieranego pliku do 200 MB. Poza tym korzystanie z pakietów aplikacji powoduje opóźnienie generowania plików APK i podpisywania ich w Google Play, dlatego użytkownicy pobierają zoptymalizowane pliki APK tylko z kodem i zasobami niezbędnymi do uruchomienia Twojej aplikacji. Nie musisz tworzyć i podpisywać wielu plików APK ani plików rozszerzeń ani nimi zarządzać. Użytkownicy mogą pobierać mniejsze, lepiej zoptymalizowane pliki APK.

Przegląd

Za każdym razem, gdy przesyłasz pakiet APK przy użyciu Konsoli Google Play, możesz dodać do niego jeden lub dwa pliki rozszerzeń. Każdy plik może mieć maksymalnie 2 GB i dowolny format, ale zalecamy użycie pliku skompresowanego, aby zmniejszyć obciążenie sieci podczas pobierania. Zasadniczo każdy plik rozszerzający pełni inną rolę:

  • Główny plik rozszerzający to główny plik rozszerzający zawierający dodatkowe zasoby wymagane przez aplikację.
  • Plik rozszerzenia patch jest opcjonalny i jest przeznaczony do obsługi niewielkich aktualizacji głównego pliku rozszerzenia.

Możesz korzystać z obu plików rozszerzeń w dowolny sposób, ale zalecamy, aby główny plik rozszerzenia dostarczał zasoby główne. Rzadko (jeśli nigdy nie jest) aktualizowany. Plik rozszerzenia poprawki powinien być mniejszy i służyć jako „operator poprawek” i być aktualizowany wraz z każdą główną wersją lub w razie potrzeby.

Nawet jeśli aktualizacja aplikacji wymaga tylko nowego pliku rozszerzenia poprawki, musisz przesłać nowy pakiet APK ze zaktualizowanym plikiem versionCode w pliku manifestu. Konsola Play nie pozwala na przesyłanie plików rozszerzających do istniejących pakietów APK.

Uwaga: plik rozszerzenia poprawki jest pod względem semantycznym taki sam jak główny plik rozszerzenia – każdego pliku możesz używać w dowolny sposób.

Format nazwy pliku

Każdy przesyłany plik rozszerzenia może mieć dowolny wybrany format (ZIP, PDF, MP4 itp.). Możesz też użyć narzędzia JOBB, aby hermetyzować i zaszyfrować zestaw plików zasobów oraz kolejne poprawki dla tego zbioru. Niezależnie od typu pliku Google Play uznaje je za nieprzezroczyste binarne bloby i zmienia ich nazwy zgodnie z tym schematem:

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

Ten schemat składa się z 3 elementów:

main lub patch
Określa, czy plik jest plikiem rozszerzenia głównego czy poprawki. Każdy plik APK może zawierać tylko 1 plik główny i 1 plik z poprawką.
<expansion-version>
To liczba całkowita odpowiadająca kodowi wersji pliku APK, z którym najpierw jest powiązane rozszerzenie (jest to wartość odpowiadająca wartości android:versionCode aplikacji).

„Pierwsza” jest podkreślana, bo chociaż Konsola Play pozwala na ponowne wykorzystanie przesłanego pliku rozszerzenia z nowym plikiem APK, jego nazwa nie ulegnie zmianie – zachowa wersję zastosowaną w momencie przesłania pliku.

<package-name>
Nazwa pakietu Twojej aplikacji w stylu Java.

Załóżmy, że wersja APK to 314159, a nazwa pakietu to com.example.app. Jeśli prześlesz główny plik rozszerzenia, zmieni się on na:

main.314159.com.example.app.obb

Lokalizacja pamięci

Gdy Google Play pobiera pliki rozszerzeń na urządzenie, zapisuje je w pamięci współdzielonej systemu. Aby zapewnić prawidłowe działanie plików, nie możesz ich usuwać, przenosić ani zmieniać ich nazw. Jeśli aplikacja musi sama pobierać pliki z Google Play, musisz zapisać pliki w dokładnie tej samej lokalizacji.

Metoda getObbDir() zwraca konkretną lokalizację na potrzeby plików rozszerzeń w takiej postaci:

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

W przypadku każdej aplikacji w tym katalogu mogą znajdować się maksymalnie 2 pliki rozszerzające. Jeden to główny plik rozszerzający, a drugi to plik rozszerzający z poprawką (jeśli jest potrzebny). Poprzednie wersje zostaną zastąpione, gdy zaktualizujesz aplikację za pomocą nowych plików rozszerzeń. Od Androida 4.4 (poziom interfejsu API 19) aplikacje mogą odczytywać pliki rozszerzeń OBB bez uprawnień do pamięci zewnętrznej. Jednak niektóre implementacje Androida 6.0 (poziom interfejsu API 23) i nowszych nadal wymagają uprawnień, więc musisz zadeklarować w manifeście aplikacji uprawnienie READ_EXTERNAL_STORAGE i poprosić o niego uprawnienia w czasie działania:

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

W przypadku Androida w wersji 6 i nowszych w czasie działania należy prosić o dostęp do pamięci zewnętrznej. Jednak niektóre implementacje Androida nie wymagają uprawnień do odczytu plików OBB. Ten fragment kodu pokazuje, jak sprawdzić uprawnienia do odczytu, zanim poprosisz o przyznanie uprawnień do pamięci zewnętrznej:

Kotlin

val obb = File(obb_filename)
var open_failed = false

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

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

Java

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

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

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

Jeśli musisz rozpakować zawartość plików rozszerzeń, nie usuwaj plików rozszerzeń OBB później ani nie zapisuj rozpakowanych danych w tym samym katalogu. Zapisz rozpakowane pliki w katalogu określonym przez getExternalFilesDir(). Jeśli to możliwe, najlepiej użyć formatu pliku rozszerzenia, który umożliwia odczyt bezpośrednio z pliku, zamiast wymagać rozpakowywania danych. Przygotowaliśmy np. projekt biblioteki o nazwie Plik ZIP z rozszerzeniem APK, który odczytuje dane bezpośrednio z pliku ZIP.

Uwaga: w przeciwieństwie do plików APK wszystkie pliki zapisane w pamięci współdzielonej mogą zostać odczytane przez użytkownika i inne aplikacje.

Wskazówka: jeśli pakujesz pliki multimedialne w pliku ZIP, możesz używać wywołań odtwarzania multimediów z ustawieniami przesunięcia i długości (np. MediaPlayer.setDataSource() i SoundPool.load()) bez konieczności rozpakowywania pliku ZIP. Aby to zadziałało, nie musisz dodatkowo kompresować plików multimedialnych podczas tworzenia pakietów ZIP. Na przykład w narzędziu zip użyj opcji -n, aby określić sufiksy plików, które nie powinny być kompresowane:
zip -n .mp4;.ogg main_expansion media_files

Proces pobierania

W większości przypadków Google Play pobiera i zapisuje pliki rozszerzeń w czasie, gdy pobiera pakiet APK na urządzenie. Czasami jednak Google Play nie może pobrać plików rozszerzeń lub użytkownik usunął wcześniej pobrane pliki rozszerzeń. W takiej sytuacji aplikacja musi mieć możliwość pobierania plików przy użyciu adresu URL podanego przez Google Play po rozpoczęciu głównej aktywności.

Ogólnie proces pobierania wygląda tak:

  1. Użytkownik decyduje się zainstalować Twoją aplikację z Google Play.
  2. Jeśli Google Play może pobrać pliki rozszerzeń (tak się dzieje na większości urządzeń), pobiera je razem z plikiem APK.

    Jeśli Google Play nie może pobrać plików rozszerzeń, pobiera tylko plik APK.

  3. Gdy użytkownik uruchomi Twoją aplikację, musi ona sprawdzić, czy pliki rozszerzeń są już zapisane na urządzeniu.
    1. Jeśli tak, aplikacja jest gotowa do użycia.
    2. Jeśli nie, aplikacja musi pobrać pliki rozszerzeń przez HTTP z Google Play. Aplikacja musi wysłać żądanie do klienta Google Play za pomocą usługi licencjonowania aplikacji w Google Play, która odpowiada nazwie, rozmiarze pliku i adresowi URL każdego pliku rozszerzenia. Po uzyskaniu tych informacji możesz pobrać pliki i zapisać je w odpowiednim miejscu na dane.

Uwaga: jeśli podczas uruchamiania aplikacji nie ma ich jeszcze na urządzeniu, musisz dołączyć kod niezbędny do pobrania z Google Play plików rozszerzeń. Jak już wspomnieliśmy w sekcji dotyczącej pobierania plików rozszerzeń, udostępniliśmy bibliotekę, która znacznie upraszcza ten proces i umożliwia pobieranie plików z usługi przy minimalnej ilości kodu.

Lista kontrolna programowania

Oto podsumowanie czynności, które musisz wykonać, by używać plików rozszerzeń w aplikacji:

  1. Najpierw ustal, czy rozmiar skompresowanego pliku do pobrania aplikacji musi być większy niż 100 MB. Ilość miejsca to nieoceniona wartość, więc należy zadbać o jak najmniejsze pliki do pobrania. Jeśli Twoja aplikacja zajmuje ponad 100 MB, aby można było przesyłać różne wersje zasobów graficznych przeznaczonych dla różnych gęstości ekranu, rozważ opublikowanie wielu plików APK, w których każdy z nich zawiera tylko zasoby wymagane na ekranach, na które jest kierowana. Aby uzyskać najlepsze rezultaty w przypadku publikowania w Google Play, prześlij pakiet Android App Bundle, który zawiera cały skompilowany kod i zasoby aplikacji, ale opóźnia generowanie plików APK i podpisywanie ich w Google Play.
  2. Określ, które zasoby aplikacji mają być oddzielone od pliku APK i spakuj je do pliku, który będzie używany jako główny plik rozszerzający.

    Zwykle drugiego pliku rozszerzenia z poprawką należy używać tylko podczas aktualizowania głównego pliku rozszerzającego. Jeśli jednak Twoje zasoby przekraczają limit 2 GB na główny plik rozszerzenia, możesz użyć pliku z poprawką dla pozostałych zasobów.

  3. Zaprojektuj aplikację tak, aby korzystała z zasobów z plików rozszerzających w lokalizacji pamięci współdzielonej urządzenia.

    Pamiętaj, że nie możesz usuwać, przenosić ani zmieniać nazw plików rozszerzeń.

    Jeśli Twoja aplikacja nie wymaga określonego formatu, zalecamy utworzenie plików ZIP z plikami rozszerzającymi, a następnie odczytywanie ich za pomocą biblioteki ZIP rozszerzeń APK.

  4. Dodaj do głównego działania w aplikacji funkcję logiczną, która sprawdza, czy pliki rozszerzeń znajdują się na urządzeniu po uruchomieniu. Jeśli plików nie ma na urządzeniu, skorzystaj z usługi licencjonowania aplikacji w Google Play, aby poprosić o adresy URL plików rozszerzeń, a następnie je pobrać i zapisać.

    Aby znacznie ograniczyć ilość kodu, który musisz napisać i zapewnić użytkownikom dobre wrażenia podczas pobierania, zalecamy wdrożenie sposobu pobierania za pomocą Biblioteki pobierania.

    Jeśli zamiast korzystać z biblioteki, tworzysz własną usługę pobierania, pamiętaj, że nie możesz zmieniać nazw plików rozszerzających i musisz je zapisać w odpowiedniej lokalizacji na dane.

Po zakończeniu tworzenia aplikacji postępuj zgodnie z instrukcjami w artykule o testowaniu plików rozszerzeń.

Zasady i ograniczenia

Dodawanie plików rozszerzeń APK to funkcja dostępna podczas przesyłania aplikacji przy użyciu Konsoli Play. Gdy przesyłasz swoją aplikację po raz pierwszy lub aktualizujesz aplikację, która używa plików rozszerzeń, musisz pamiętać o tych regułach i ograniczeniach:

  1. Żaden z tych plików nie może być większy niż 2 GB.
  2. Aby można było pobrać pliki rozszerzeń z Google Play, użytkownik musi pobrać aplikację z Google Play. Google Play nie podaje adresów URL plików rozszerzeń, jeśli aplikacja została zainstalowana w inny sposób.
  3. Podczas pobierania z aplikacji adres URL podany przez Google Play dla każdego pliku jest unikalny dla każdego pobrania i każdy wygasa wkrótce po przekazaniu aplikacji.
  4. Jeśli zaktualizujesz aplikację za pomocą nowego pliku APK lub prześlesz wiele plików APK dla tej samej aplikacji, możesz wybrać pliki rozszerzeń przesłane do poprzedniego pliku APK. Nazwa pliku rozszerzenia się nie zmienia – zachowuje wersję otrzymaną przez plik APK, z którym plik był pierwotnie powiązany.
  5. Jeśli używasz plików rozszerzeń w połączeniu z wieloma plikami APK, by udostępnić różne pliki rozszerzeń dla różnych urządzeń, i tak musisz przesłać osobne pakiety APK dla każdego urządzenia, by uzyskać unikalną wartość versionCode i zadeklarować różne filtry dla każdego pliku APK.
  6. Nie można zaktualizować aplikacji przez zmianę plików rozszerzeń – musisz przesłać nowy plik APK, by zaktualizować aplikację. Jeśli zmiany dotyczą tylko zasobów w plikach rozszerzeń, możesz zaktualizować pakiet APK, zmieniając versionCode (i być może również versionName).

  7. Nie zapisuj innych danych w katalogu obb/. Jeśli musisz rozpakować niektóre dane, zapisz je w lokalizacji określonej przez getExternalFilesDir().
  8. Nie usuwaj pliku rozszerzenia .obb ani nie zmieniaj jego nazwy (chyba że przeprowadzasz aktualizację). Jeśli to zrobisz, Google Play (lub sama aplikacja) będzie wielokrotnie pobierać plik rozszerzenia.
  9. W przypadku ręcznej aktualizacji pliku rozszerzenia trzeba usunąć poprzedni plik.

Pobieranie plików rozszerzeń

W większości przypadków Google Play pobiera i zapisuje pliki rozszerzeń na urządzeniu podczas instalacji lub aktualizacji pliku APK. Dzięki temu pliki rozszerzeń będą dostępne przy pierwszym uruchomieniu aplikacji. W niektórych przypadkach aplikacja musi jednak pobrać same pliki rozszerzeń, wysyłając żądanie do nich z adresu URL podanego w odpowiedzi z usługi licencjonowania aplikacji w Google Play.

Oto podstawowe zasady przy pobieraniu plików rozszerzeń:

  1. Po uruchomieniu aplikacji poszukaj plików rozszerzeń w lokalizacji pamięci współdzielonej (w katalogu Android/obb/<package-name>/).
    1. Jeśli znajdują się tam pliki rozszerzeń, nie musisz nic robić, a aplikacja będzie działać dalej.
    2. Jeśli nie ma plików rozszerzających:
      1. Wyślij żądanie, korzystając z licencjonowania aplikacji w Google Play, aby uzyskać nazwy, rozmiary i adresy URL plików rozszerzeń aplikacji.
      2. Użyj adresów URL podanych przez Google Play, aby pobrać pliki rozszerzeń i zapisać je. Musisz zapisać pliki w lokalizacji pamięci współdzielonej (Android/obb/<package-name>/) i używać dokładnej nazwy pliku podanej w odpowiedzi z Google Play.

        Uwaga: adres URL plików rozszerzających podany w Google Play jest unikalny dla każdego pobrania i wygasa wkrótce po przekazaniu aplikacji.

Jeśli Twoja aplikacja jest bezpłatna (nie jest płatna), prawdopodobnie nie korzystasz z usługi licencjonowania aplikacji. Jego głównym celem jest egzekwowanie zasad dotyczących licencjonowania aplikacji i dbanie o to, aby użytkownik miał prawo do korzystania z aplikacji (słusznie zapłacił za nią w Google Play). Aby ułatwić działanie pliku rozszerzania, ulepszyliśmy usługę licencjonowania w taki sposób, aby wysyłała do aplikacji odpowiedź zawierającą adres URL jej plików rozszerzających przechowywanych w Google Play. Dlatego nawet jeśli Twoja aplikacja jest bezpłatna dla użytkowników, musisz dołączyć Bibliotekę weryfikacji licencji (LVL), aby używać plików rozszerzeń APK. Oczywiście jeśli Twoja aplikacja jest bezpłatna, nie musisz wymuszać weryfikacji licencji – potrzebujesz biblioteki do wykonania żądania zwracającego adresy URL plików rozszerzeń.

Uwaga: niezależnie od tego, czy aplikacja jest bezpłatna, Google Play zwraca adresy URL plików rozszerzających tylko wtedy, gdy użytkownik pozyskał ją z Google Play.

Oprócz LVL potrzebny jest zestaw kodu, który pobiera pliki rozszerzające przez połączenie HTTP i zapisuje je w odpowiedniej lokalizacji w pamięci współdzielonej urządzenia. W trakcie wdrażania tej procedury w aplikacji musisz wziąć pod uwagę kilka kwestii:

  • Na urządzeniu może być za mało miejsca na pliki rozszerzające. Sprawdź to przed rozpoczęciem pobierania i wyświetl ostrzeżenie, jeśli będzie za mało miejsca.
  • Pobieranie plików powinno odbywać się w ramach usługi w tle, aby uniknąć zablokowania interakcji użytkownika i opuszczenia aplikacji przez użytkownika do momentu zakończenia pobierania.
  • W trakcie żądania i pobierania mogą wystąpić różne błędy, które musisz uwzględnić w sposób przejrzysty.
  • W trakcie pobierania połączenie sieciowe może się zmienić, dlatego musisz wprowadzić te zmiany, a jeśli zostanie przerwane, wznów pobieranie, gdy tylko będzie to możliwe.
  • Gdy pobieranie odbywa się w tle, należy powiadomić użytkownika o postępie pobierania, o zakończeniu pobierania i z powrotem do aplikacji.

Aby ułatwić Ci to zadanie, stworzyliśmy Bibliotekę pobierania, która wysyła żądania adresów URL plików rozszerzających za pomocą usługi licencjonowania, pobiera pliki rozszerzające i wykonuje wszystkie wymienione powyżej czynności, a nawet umożliwia wstrzymywanie i wznawianie pobierania. Gdy dodasz do aplikacji bibliotekę pobierania i kilka punktów zaczepienia kodu, prawie cała praca związana z pobieraniem plików rozszerzeń jest już zakodowana. Aby zapewnić użytkownikom maksymalną wygodę przy minimalnym wysiłku w Twoim imieniu, zalecamy pobranie plików rozszerzeń za pomocą Biblioteki pobierania. Informacje w sekcjach poniżej wyjaśniają, jak zintegrować bibliotekę z aplikacją.

Jeśli wolisz opracować własne rozwiązanie do pobierania plików rozszerzeń przy użyciu adresów URL Google Play, musisz wykonać żądanie licencji zgodnie z dokumentacją licencjonowania aplikacji, a następnie pobrać nazwy, rozmiary i adresy URL plików rozszerzeń z dodatków w odpowiedzi. Jako zasady licencjonowania należy użyć klasy APKExpansionPolicy (zawartej w bibliotece weryfikacji licencji), która rejestruje nazwy, rozmiary i adresy URL plików rozszerzeń z usługi licencjonowania.

Informacje o bibliotece narzędzia do pobierania

Aby korzystać z plików rozszerzeń APK w swojej aplikacji i zapewnić użytkownikom jak najlepsze wrażenia przy minimalnym nakładzie pracy, zalecamy skorzystanie z biblioteki pobierania plików dołączonej do pakietu Biblioteki rozszerzeń APK w Google Play. Ta biblioteka pobiera pliki rozszerzeń do usługi w tle, wyświetla użytkownikowi powiadomienie o stanie pobierania, obsługuje utratę połączenia sieciowego, wznawia pobieranie, gdy jest to możliwe, itd.

Aby zaimplementować pobieranie plików rozszerzeń za pomocą Biblioteki pobierania, musisz tylko:

  • Rozszerz specjalną podklasę Service i podklasę BroadcastReceiver, które wymagają od Ciebie tylko kilku wierszy kodu.
  • Dodaj do głównego działania część logiki, która sprawdza, czy pliki rozszerzeń zostały już pobrane. W razie potrzeby wywołuje proces pobierania i wyświetla interfejs postępu.
  • Zaimplementuj interfejs wywołania zwrotnego z kilkoma metodami w głównym działaniu, które otrzymują aktualne informacje o postępie pobierania.

W sekcjach poniżej dowiesz się, jak skonfigurować aplikację za pomocą Biblioteki pobierania.

Przygotowywanie do korzystania z Biblioteki pobierania

Aby korzystać z Biblioteki pobierania, musisz pobrać z niego 2 pakiety i dodać do swojej aplikacji odpowiednie biblioteki.

Najpierw otwórz Menedżera pakietów SDK Androida (Narzędzia > Menedżer pakietów SDK) i w sekcji Wygląd i zachowanie > Ustawienia systemu > Android SDK wybierz kartę Narzędzia SDK, a potem kliknij ją i pobierz:

  • Pakiet Biblioteki licencji Google Play
  • Pakiet biblioteki rozszerzeń plików APK Google Play

Utwórz nowy moduł biblioteki dla biblioteki weryfikacji licencji i biblioteki pobierania. Dla każdej biblioteki:

  1. Wybierz File > New > New Module (Plik > Nowy > Nowy moduł).
  2. W oknie Create New Module (Utwórz nowy moduł) wybierz Android Library, a następnie Next (Dalej).
  3. Podaj nazwę aplikacji/biblioteki, np. „Biblioteka licencji Google Play” i „Biblioteka pobierania Google Play”, wybierz Minimalny poziom pakietu SDK i kliknij Zakończ.
  4. Wybierz Plik > Struktura projektu.
  5. Wybierz kartę Właściwości i w polu Library Repository wpisz bibliotekę z katalogu <sdk>/extras/google/ (play_licensing/ oznacza bibliotekę weryfikacji licencji lub play_apk_expansion/downloader_library/ – bibliotekę do pobierania).
  6. Kliknij OK, aby utworzyć nowy moduł.

Uwaga: biblioteka narzędzia do pobierania zależy od biblioteki weryfikacji licencji. Pamiętaj, aby do właściwości projektu w Bibliotece pobierania dodać bibliotekę weryfikacji licencji.

Możesz też zaktualizować projekt z poziomu wiersza poleceń, dodając do niego biblioteki:

  1. Przejdź do katalogu <sdk>/tools/.
  2. Uruchom android update project z opcją --library, aby dodać do projektu zarówno bibliotekę LVL, jak i bibliotekę pobierania. Przykład:
    android update project --path ~/Android/MyApp \
    --library ~/android_sdk/extras/google/market_licensing \
    --library ~/android_sdk/extras/google/market_apk_expansion/downloader_library
    

Po dodaniu do aplikacji zarówno biblioteki weryfikacji licencji, jak i biblioteki pobierania, możesz szybko zintegrować możliwość pobierania plików rozszerzeń z Google Play. Wybrany format plików rozszerzeń i sposób ich odczytywania z pamięci współdzielonej to osobna implementacja, którą musisz rozważyć w zależności od potrzeb aplikacji.

Wskazówka: pakiet rozszerzenia APK zawiera przykładową aplikację, która pokazuje, jak korzystać z Biblioteki pobierania w aplikacji. W przykładzie używana jest trzecia biblioteka, która jest dostępna w pakiecie rozszerzeń APK, o nazwie Biblioteka ZIP rozszerzenia APK. Jeśli jako plików rozszerzeń planujesz używać plików ZIP, zalecamy dodanie do aplikacji biblioteki ZIP rozszerzenia APK. Więcej informacji znajdziesz poniżej w sekcji o korzystaniu z biblioteki plików ZIP rozszerzeń APK.

Deklarowanie uprawnień użytkownika

Aby pobrać pliki rozszerzeń, Biblioteka pobierania wymaga kilku uprawnień, które musisz zadeklarować w pliku manifestu aplikacji. Są to:

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

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

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

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

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

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

Uwaga: domyślnie biblioteka pobierania wymaga interfejsu API na poziomie 4, ale biblioteka ZIP rozszerzenia APK wymaga interfejsu API na poziomie 5.

Wdrażanie usługi pobierania

Jeśli chcesz pobierać pliki w tle, Biblioteka pobierania udostępnia własną podklasę Service o nazwie DownloaderService, którą musisz rozszerzyć. Oprócz pobrania za Ciebie plików rozszerzeń DownloaderService:

  • Rejestruje element BroadcastReceiver, który nasłuchuje zmian w połączeniu sieciowym urządzenia (transmisja CONNECTIVITY_ACTION), aby wstrzymać pobieranie, gdy jest to konieczne (na przykład z powodu utraty połączenia) i wznowić pobieranie, gdy jest to możliwe (nawiązanie połączenia).
  • zaplanuje alarm na RTC_WAKEUP, aby ponowić próbę pobrania aplikacji w przypadkach, w których usługa zostanie zatrzymana.
  • Tworzy niestandardowy obiekt Notification, który wyświetla postęp pobierania i wszelkie błędy lub zmiany stanu.
  • Pozwala aplikacji na ręczne wstrzymywanie i wznawianie pobierania.
  • Sprawdza, czy pamięć współdzielona jest podłączona i dostępna, czy pliki jeszcze nie istnieją oraz czy jest wystarczająco dużo miejsca, a wszystko to przed pobraniem plików rozszerzeń. Następnie powiadamia użytkownika, gdy któreś z tych stwierdzeń nie jest prawdziwe.

Musisz tylko utworzyć w aplikacji klasę, która rozszerza klasę DownloaderService i zastąpić 3 metody, aby podać szczegółowe informacje o aplikacji:

getPublicKey()
Musi on zwrócić ciąg tekstowy w postaci klucza publicznego RSA zakodowanego w standardzie Base64 dostępnego na stronie profilu w Konsoli Play (patrz Konfigurowanie licencji na potrzeby licencjonowania).
getSALT()
Musi on zwrócić tablicę losowych bajtów, których licencja Policy używa do utworzenia Obfuscator. Ciąg zaburzający sprawia, że zaciemniony plik SharedPreferences, w którym zapisane są dane licencji, będzie unikalny i niewykrywalny.
getAlarmReceiverClassName()
Musi ona zwrócić nazwę klasy klasy BroadcastReceiver w aplikacji, w przypadku której powinien pojawić się alarm informujący o ponownym rozpoczęciu pobierania (może się to zdarzyć w przypadku niespodziewanego zatrzymania usługi pobierania).

Oto przykład pełnego wdrożenia funkcji DownloaderService:

Kotlin

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

class SampleDownloaderService : DownloaderService() {

    override fun getPublicKey(): String = BASE64_PUBLIC_KEY

    override fun getSALT(): ByteArray = SALT

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

Java

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

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

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

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

Uwaga: musisz zaktualizować wartość BASE64_PUBLIC_KEY, by była ona kluczem publicznym należącym do Twojego konta wydawcy. Znajdziesz go w Konsoli programisty w informacjach o profilu. Jest to konieczne nawet przy testowaniu pobranych plików.

Pamiętaj, aby zadeklarować usługę w pliku manifestu:

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

Implementowanie odbiornika alarmów

Aby monitorować postęp pobierania plików i w razie potrzeby ponownie je uruchamiać, DownloaderService planuje alarm typu RTC_WAKEUP, który wysyła do BroadcastReceiver w aplikacji zdarzenie Intent. Musisz zdefiniować BroadcastReceiver do wywoływania interfejsu API z Biblioteki pobierania, który sprawdza stan pobierania i w razie potrzeby uruchamia go ponownie.

Wystarczy zastąpić metodę onReceive(), aby wywołać DownloaderClientMarshaller.startDownloadServiceIfRequired().

Na przykład:

Kotlin

class SampleAlarmReceiver : BroadcastReceiver() {

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

Java

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

Zwróć uwagę, że jest to klasa, dla której musisz zwrócić nazwę w metodzie getAlarmReceiverClassName() swojej usługi (patrz poprzednia sekcja).

Pamiętaj, aby zadeklarować odbiorcę w pliku manifestu:

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

Rozpoczynam pobieranie

Główna aktywność w aplikacji (rozpoczynana po kliknięciu ikony programu uruchamiającego) odpowiada za sprawdzenie, czy pliki rozszerzeń znajdują się już na urządzeniu, oraz za zainicjowanie pobierania, jeśli nie są.

Aby rozpocząć pobieranie za pomocą Biblioteki pobierania, należy wykonać te czynności:

  1. Sprawdź, czy pliki zostały pobrane.

    Biblioteka pobierania zawiera w klasie Helper niektóre interfejsy API, które mogą pomóc w tym procesie:

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

    Na przykład przykładowa aplikacja udostępniona w pakiecie rozszerzenia APK wywołuje tę metodę w metodzie onCreate() aktywności, aby sprawdzić, czy pliki rozszerzające już istnieją na urządzeniu:

    Kotlin

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

    Java

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

    W tym przypadku każdy obiekt XAPKFile zawiera numer wersji i rozmiar znanego pliku rozszerzenia oraz wartość logiczną, która określa, czy jest to główny plik rozszerzenia. (Szczegółowe informacje znajdziesz w klasie SampleDownloaderActivity przykładowej aplikacji).

    Jeśli ta metoda zwraca wartość false (fałsz), aplikacja musi rozpocząć pobieranie.

  2. Rozpocznij pobieranie, wywołując metodę statyczną DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass).

    Przyjmuje ona te parametry:

    • context: Context Twojej aplikacji.
    • notificationClient: PendingIntent na początek głównej czynności. Jest ona używana w elemencie Notification, który tworzy DownloaderService, aby pokazywać postęp pobierania. Gdy użytkownik wybierze powiadomienie, system wywoła podany tutaj element PendingIntent i powinno otworzyć działanie, które pokazuje postęp pobierania (zwykle ta sama aktywność, która rozpoczęła pobieranie).
    • serviceClass: obiekt Class do implementacji DownloaderService, wymagany do uruchomienia usługi i rozpoczęcia pobierania, jeśli to konieczne.

    Zwraca ona liczbę całkowitą, która wskazuje, czy pobranie jest wymagane. Możliwe wartości to:

    • NO_DOWNLOAD_REQUIRED: zwracany, jeśli pliki już istnieją lub trwa pobieranie.
    • LVL_CHECK_REQUIRED: zwracany, jeśli do pozyskania adresów URL plików rozszerzeń wymagana jest weryfikacja licencji.
    • DOWNLOAD_REQUIRED: zwracany, jeśli adresy URL plików rozszerzeń są już znane, ale nie zostały pobrane.

    Działanie LVL_CHECK_REQUIRED i DOWNLOAD_REQUIRED jest zasadniczo takie samo i zwykle nie musisz się nimi przejmować. W swojej głównej aktywności, która wywołuje metodę startDownloadServiceIfRequired(), możesz po prostu sprawdzić, czy odpowiedź to NO_DOWNLOAD_REQUIRED. Jeśli odpowiedź to coś innego niż NO_DOWNLOAD_REQUIRED, Biblioteka pobierania rozpocznie pobieranie. W takim przypadku musisz zaktualizować interfejs aktywności, aby wyświetlić postęp pobierania (patrz następny krok). Jeśli odpowiedź to NO_DOWNLOAD_REQUIRED, pliki są dostępne, a aplikacja może się uruchomić.

    Na przykład:

    Kotlin

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

    Java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Check if expansion files are available before going any further
        if (!expansionFilesDelivered()) {
            // Build an Intent to start this activity from the Notification
            Intent notifierIntent = new Intent(this, MainActivity.getClass());
            notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                                    Intent.FLAG_ACTIVITY_CLEAR_TOP);
            ...
            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                    notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
            // Start the download service (if required)
            int startResult =
                DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                            pendingIntent, SampleDownloaderService.class);
            // If download has started, initialize this activity to show
            // download progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // This is where you do set up to display the download
                // progress (next step)
                ...
                return;
            } // If the download wasn't necessary, fall through to start the app
        }
        startApp(); // Expansion files are available, start the app
    }
    
  3. Gdy metoda startDownloadServiceIfRequired() zwraca coś innego niż NO_DOWNLOAD_REQUIRED, utwórz instancję IStub, wywołując DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService). IStub łączy Twoją aktywność z usługą pobierania, dzięki czemu otrzymuje wywołania zwrotne dotyczące postępu pobierania.

    Aby utworzyć instancję IStub przez wywołanie CreateStub(), musisz przekazać jej implementację interfejsu IDownloaderClient i swojej implementacji DownloaderService. W następnej sekcji poświęconej postępowi pobierania omawiamy interfejs IDownloaderClient, który zwykle należy zaimplementować w klasie Activity, aby umożliwić aktualizowanie interfejsu aktywności w przypadku zmiany stanu pobierania.

    Zalecamy wywołanie metody CreateStub(), aby utworzyć instancję IStub podczas metody onCreate() aktywności, gdy startDownloadServiceIfRequired() rozpocznie pobieranie.

    Na przykład w poprzednim przykładowym kodzie dla strony onCreate() możesz odpowiedzieć na wynik startDownloadServiceIfRequired() w ten sposób:

    Kotlin

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

    Java

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

    Po zwróceniu metody onCreate() aktywność otrzyma wywołanie onResume(), które należy wywołać connect() w IStub, przekazując do niego Context aplikacji. I na odwrót: musisz wywołać disconnect() w wywołaniu zwrotnym onStop() aktywności.

    Kotlin

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

    Java

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

    Wywołanie connect() w metodzie IStub wiąże Twoją aktywność z elementem DownloaderService, dzięki czemu otrzymuje wywołania zwrotne dotyczące zmian w stanie pobierania za pomocą interfejsu IDownloaderClient.

Odbieram informacje o postępach pobierania

Aby otrzymywać informacje o postępach pobierania i korzystać z DownloaderService, musisz zaimplementować interfejs IDownloaderClient biblioteki pobierania. Zwykle aktywność używana do rozpoczynania pobierania powinna implementować ten interfejs, aby wyświetlać postęp pobierania i wysyłać żądania do usługi.

Wymagane metody interfejsu dla elementu IDownloaderClient:

onServiceConnected(Messenger m)
Po utworzeniu wystąpienia w aktywności IStub otrzymasz wywołanie tej metody, które przekazuje obiekt Messenger połączony z Twoim wystąpieniem DownloaderService. Aby wysyłać żądania do usługi, na przykład wstrzymywać i wznawiać pobieranie, musisz wywołać metodę DownloaderServiceMarshaller.CreateProxy() w celu otrzymania interfejsu IDownloaderService połączonego z usługą.

Zalecana implementacja wygląda tak:

Kotlin

private var remoteService: IDownloaderService? = null
...

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

Java

private IDownloaderService remoteService;
...

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

Po zainicjowaniu obiektu IDownloaderService możesz wysyłać do usługi pobierania polecenia, takie jak wstrzymanie i wznowienie pobierania (requestPauseDownload() i requestContinueDownload()).

onDownloadStateChanged(int newState)
Usługa pobierania wywołuje ten zdarzenie, gdy zachodzi zmiana stanu pobierania, na przykład rozpoczyna się lub kończy pobieranie.

Wartość newState będzie jedną z kilku możliwych wartości określonych przez jedną ze stałych STATE_* klasy IDownloaderClient.

Aby przekazywać użytkownikom przydatne komunikaty, możesz zażądać odpowiedniego ciągu znaków dla każdego stanu, wywołując funkcję Helpers.getDownloaderStringResourceIDFromState(). Zwraca to identyfikator zasobu jednego z ciągów tekstowych pakietu z Biblioteką pobierania. Na przykład ciąg „Pobieranie zostało wstrzymane, ponieważ korzystasz z roamingu” odpowiada kolumnie STATE_PAUSED_ROAMING.

onDownloadProgress(DownloadProgressInfo progress)
Usługa pobierania wywołuje to, by dostarczyć obiekt DownloadProgressInfo, który opisuje różne informacje o postępie pobierania, w tym szacowany pozostały czas, bieżącą szybkość, ogólny postęp i łączny wynik, dzięki czemu można zaktualizować interfejs postępu pobierania.

Wskazówka: przykłady wywołań zwrotnych, które aktualizują interfejs postępu pobierania, znajdziesz w SampleDownloaderActivity w przykładowej aplikacji dostarczonej z pakietem rozszerzenia APK.

Niektóre publiczne metody interfejsu IDownloaderService, które mogą Ci się przydać, to:

requestPauseDownload()
Wstrzymuje pobieranie.
requestContinueDownload()
Wznawia wstrzymane pobieranie.
setDownloadFlags(int flags)
Określa preferencje użytkownika dotyczące typów sieci, z których można pobierać pliki. Obecna implementacja obsługuje jedną flagę, FLAGS_DOWNLOAD_OVER_CELLULAR, ale możesz dodawać kolejne. Domyślnie ta flaga nie jest włączona, więc użytkownik musi korzystać z Wi-Fi, aby pobrać pliki rozszerzeń. Możesz dostosować ustawienia, tak aby użytkownicy mogli pobierać treści przez sieć komórkową. W takim przypadku możesz zadzwonić pod numer:

Kotlin

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

Java

remoteService
    .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);

Korzystanie z zasady pakietu APKExpansionPolicy

Jeśli zamiast korzystać z Biblioteki pobierania w Google Play, zdecydujesz się utworzyć własną usługę pobierania, nadal musisz korzystać z narzędzia APKExpansionPolicy dostępnego w bibliotece weryfikacji licencji. Klasa APKExpansionPolicy jest prawie identyczna z klasą ServerManagedPolicy (dostępną w bibliotece weryfikacji licencji Google Play), ale obejmuje dodatkową obsługę dodatkowych funkcji odpowiedzi dotyczących pliku rozszerzenia APK.

Uwaga: jeśli używasz Biblioteki pobierania, jak omówiliśmy w poprzedniej sekcji, będzie ona obsługiwać wszystkie interakcje z klasą APKExpansionPolicy, więc nie musisz bezpośrednio używać tej klasy.

Klasa zawiera metody, które pomagają uzyskać niezbędne informacje o dostępnych plikach rozszerzeń:

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

Więcej informacji o korzystaniu z APKExpansionPolicy, gdy nie używasz Biblioteki pobierania, znajdziesz w dokumentacji dotyczącej dodawania licencji do aplikacji, w której wyjaśniamy, jak wdrożyć takie zasady dotyczące licencji.

Odczytywanie pliku rozszerzenia

Po zapisaniu plików rozszerzeń APK na urządzeniu sposób odczytywania plików zależy od typu użytego pliku. Jak wspomnieliśmy w omówieniu, pliki rozszerzeń mogą być dowolnymi plikami, ale ich nazwy są zapisywane za pomocą określonego formatu i zapisywane w <shared-storage>/Android/obb/<package-name>/.

Niezależnie od sposobu odczytywania plików, zawsze najpierw sprawdź, czy dostępna jest pamięć zewnętrzna. Istnieje ryzyko, że użytkownik podłączył pamięć urządzenia do komputera przez USB lub usunął kartę SD.

Uwaga: gdy aplikacja się uruchamia, zawsze sprawdzaj, czy pamięć zewnętrzna jest dostępna i czytelna, wywołując metodę getExternalStorageState(). Zwraca jeden z kilku możliwych ciągów reprezentujących stan pamięci zewnętrznej. Aby aplikacja mogła odczytać tę wartość, zwracana wartość musi wynosić MEDIA_MOUNTED.

Uzyskiwanie nazw plików

Zgodnie z opisem w omówieniu pliki rozszerzeń APK są zapisywane w określonym formacie nazw plików:

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

Aby uzyskać lokalizację i nazwy plików rozszerzeń, musisz utworzyć ścieżkę do swoich plików za pomocą metod getExternalStorageDirectory() i getPackageName().

Oto metoda, której możesz użyć w aplikacji, aby uzyskać tablicę zawierającą pełną ścieżkę do obu plików rozszerzeń:

Kotlin

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

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

Java

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

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

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

Możesz wywołać tę metodę, przekazując do niej aplikację Context i wersję wybranego pliku rozszerzenia.

Numer wersji pliku rozszerzenia można określić na wiele sposobów. Prostym sposobem jest zapisanie wersji w pliku SharedPreferences po rozpoczęciu pobierania przez wysłanie zapytania o nazwę pliku rozszerzenia za pomocą metody getExpansionFileName(int index) klasy APKExpansionPolicy. Kod wersji znajdziesz wtedy w pliku SharedPreferences, gdy chcesz uzyskać dostęp do pliku rozszerzającego.

Więcej informacji o odczytywaniu danych z pamięci współdzielonej znajdziesz w dokumentacji magazynu danych.

Korzystanie z biblioteki plików ZIP rozszerzeń APK

Pakiet rozszerzenia Apk Google Market zawiera bibliotekę o nazwie „<sdk>/extras/google/google_market_apk_expansion/zip_file/” rozszerzenia pliku APK. Jest to opcjonalna biblioteka, która ułatwia odczytywanie plików rozszerzeń, gdy są one zapisane jako pliki ZIP. Biblioteka ta pozwala łatwo odczytywać zasoby z plików rozszerzających ZIP jako wirtualny system plików.

Biblioteka ZIP rozszerzeń APK zawiera te klasy i interfejsy API:

APKExpansionSupport
Udostępnia kilka metod dostępu do nazw plików rozszerzających i plików ZIP:
getAPKExpansionFiles()
Ta sama metoda, którą pokazano powyżej, zwraca pełną ścieżkę do obu plików rozszerzających.
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)
Zwraca element ZipResourceFile, który reprezentuje sumę zarówno pliku głównego, jak i pliku poprawki. Oznacza to, że jeśli podasz zarówno mainVersion, jak i patchVersion, zwróci to wartość ZipResourceFile, która daje dostęp do wszystkich danych z uprawnieniami do odczytu (dane z pliku poprawki zostaną scalone z plikiem głównym).
ZipResourceFile
Reprezentuje plik ZIP w pamięci współdzielonej i wykonuje wszystkie działania, aby udostępnić wirtualny system plików na podstawie plików ZIP. Instancję możesz pobrać, używając polecenia APKExpansionSupport.getAPKExpansionZipFile() lub ZipResourceFile, przekazując jej ścieżkę do pliku rozszerzenia. Ta klasa zawiera wiele przydatnych metod, ale zazwyczaj nie musisz korzystać z większości z nich. Kilka ważnych metod:
getInputStream(String assetPath)
Udostępnia InputStream umożliwiający odczyt pliku w pliku ZIP. Pole assetPath musi być ścieżką do żądanego pliku określoną względem katalogu głównego zawartości pliku ZIP.
getAssetFileDescriptor(String assetPath)
Udostępnia AssetFileDescriptor dla pliku w pliku ZIP. Pole assetPath musi być ścieżką do żądanego pliku określoną względem katalogu głównego zawartości pliku ZIP. Jest to przydatne w przypadku niektórych interfejsów API Androida, które wymagają AssetFileDescriptor, na przykład niektórych interfejsów API MediaPlayer.
APEZProvider
Większość aplikacji nie potrzebuje tych zajęć. Ta klasa definiuje ContentProvider, który przechowuje dane z plików ZIP za pomocą dostawcy treści Uri, aby zapewnić dostęp do plików w niektórych interfejsach API Androida, które wymagają dostępu typu Uri do plików multimedialnych. Jest to przydatne np. wtedy, gdy chcesz odtworzyć film za pomocą VideoView.setVideoURI().

Pomija kompresję ZIP plików multimedialnych

Jeśli używasz plików rozszerzeń do przechowywania plików multimedialnych, plik ZIP nadal pozwala używać wywołań odtwarzania multimediów na Androidzie z ustawieniami przesunięcia i długości (np. MediaPlayer.setDataSource() i SoundPool.load()). Aby to działało, nie możesz dodatkowo kompresować plików multimedialnych podczas tworzenia pakietów ZIP. Na przykład w narzędziu zip użyj opcji -n, aby określić sufiksy plików, które nie powinny być kompresowane:

zip -n .mp4;.ogg main_expansion media_files

Odczytywanie z pliku ZIP

Jeśli używasz biblioteki ZIP rozszerzeń APK, odczytanie pliku z pliku ZIP zwykle wymaga:

Kotlin

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

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

Java

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

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

Powyższy kod umożliwia dostęp do każdego pliku znajdującego się w głównym pliku rozszerzenia lub pliku rozszerzenia poprawki przez odczyt ze scalonej mapy wszystkich plików z obu plików. Wystarczy, że podasz metodę getAPKExpansionFile() swojej aplikacji android.content.Context oraz numer wersji zarówno głównego pliku rozszerzenia, jak i pliku rozszerzenia poprawki.

Jeśli wolisz odczytywać dane z konkretnego pliku rozszerzenia, możesz użyć konstruktora ZipResourceFile ze ścieżką do odpowiedniego pliku rozszerzenia:

Kotlin

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

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

Java

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

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

Więcej informacji o używaniu tej biblioteki na potrzeby plików rozszerzeń znajdziesz w klasie SampleDownloaderActivity przykładowej aplikacji, która zawiera dodatkowy kod do weryfikowania pobranych plików przy użyciu CRC. Pamiętaj, że jeśli użyjesz tego przykładu jako podstawy do własnej implementacji, musisz w tablicy xAPKS zadeklarować rozmiar w bajtach plików rozszerzeń.

Testowanie plików rozszerzeń

Przed opublikowaniem aplikacji musisz przetestować 2 rzeczy: odczytać pliki rozszerzeń i je pobrać.

Testowanie odczytów plików

Zanim prześlesz aplikację do Google Play, sprawdź jej zdolność do odczytu plików z pamięci współdzielonej. Wystarczy, że dodasz pliki do odpowiedniej lokalizacji w pamięci współdzielonej urządzenia i uruchomisz aplikację:

  1. W pamięci współdzielonej utwórz na urządzeniu odpowiedni katalog, w którym Google Play będzie zapisywać pliki.

    Jeśli na przykład nazwa pakietu to com.example.android, musisz utworzyć katalog Android/obb/com.example.android/ w pamięci współdzielonej. (podłącz urządzenie testowe do komputera, aby podłączyć pamięć udostępnioną, i ręcznie utwórz ten katalog).

  2. Dodaj do tego katalogu ręcznie pliki rozszerzeń. Pamiętaj, by nazwy plików zmieniać tak, by były zgodne z formatem nazw plików używanym w Google Play.

    Na przykład bez względu na typ pliku głównym plikiem rozszerzenia aplikacji com.example.android powinien być main.0300110.com.example.android.obb. Kod wersji może mieć dowolną wartość. Pamiętaj:

    • Główny plik rozszerzenia zawsze zaczyna się od main, a plik poprawki – od patch.
    • Nazwa pakietu jest zawsze taka sama jak nazwa pliku APK, do którego dołączony jest plik w Google Play.
  3. Gdy pliki rozszerzeń znajdują się już na urządzeniu, możesz zainstalować i uruchomić aplikację, aby przetestować pliki rozszerzeń.

Oto kilka przypomnień o obsłudze plików rozszerzeń:

  • Nie usuwaj plików rozszerzeń .obb ani nie zmieniaj ich nazw (nawet wtedy, gdy rozpakujesz dane w innej lokalizacji). Jeśli to zrobisz, Google Play (lub Twoja aplikacja) będzie wielokrotnie pobierać plik rozszerzenia.
  • Nie zapisuj innych danych w katalogu obb/. Jeśli musisz rozpakować niektóre dane, zapisz je w lokalizacji określonej przez getExternalFilesDir().

Testowanie pobierania plików

Czasami aplikacja musi ręcznie pobrać pliki rozszerzeń przy pierwszym uruchomieniu, dlatego ważne jest, by przetestować ten proces, by mieć pewność, że będzie mogła wykonywać zapytania o adresy URL, pobierać pliki i zapisywać je na urządzeniu.

Aby przetestować wdrożenie w aplikacji procedury pobierania ręcznego, możesz opublikować ją na ścieżce testu wewnętrznego, aby była dostępna tylko dla autoryzowanych testerów. Jeśli wszystko działa zgodnie z oczekiwaniami, aplikacja powinna rozpocząć pobieranie plików rozszerzeń zaraz po rozpoczęciu głównej aktywności.

Uwaga: wcześniej można było testować aplikację, przesyłając nieopublikowaną wersję roboczą. Ta funkcja nie jest już obsługiwana. Zamiast tego musisz ją opublikować na ścieżce testu wewnętrznego, zamkniętego lub otwartego. Więcej informacji znajdziesz w artykule Wersje robocze aplikacji nie są dłużej obsługiwane.

Aktualizowanie aplikacji

Jedną z dużych zalet używania plików rozszerzeń w Google Play jest możliwość aktualizowania aplikacji bez ponownego pobierania wszystkich oryginalnych zasobów. Google Play pozwala na przesłanie 2 plików rozszerzeń do każdego pakietu APK, więc możesz wykorzystać drugi plik jako „poprawkę” zapewniającą aktualizacje i nowe zasoby. Dzięki temu unikniesz ponownego pobierania głównego pliku rozszerzeń, które może być duże i kosztowne dla użytkowników.

Plik rozszerzający z poprawkami jest technicznie taki sam jak główny plik rozszerzenia i ani system Android, ani Google Play nie instalują poprawek między głównym plikiem rozszerzenia a plikami rozszerzającymi. Twój kod aplikacji musi sam wprowadzać wszelkie niezbędne poprawki.

Jeśli jako plików rozszerzeń używasz plików ZIP, plik ZIP rozszerzenia APK dołączony do pakietu rozszerzenia APK umożliwia scalenie pliku poprawki z głównym plikiem rozszerzenia.

Uwaga: nawet jeśli chcesz wprowadzić zmiany tylko w pliku rozszerzenia poprawki, musisz zaktualizować pakiet APK, by umożliwić Google Play przeprowadzenie aktualizacji. Jeśli nie wymagasz zmian w kodzie aplikacji, wystarczy zaktualizować versionCode w pliku manifestu.

Użytkownicy, którzy wcześniej zainstalowali Twoją aplikację, nie pobiorą głównego pliku rozszerzenia powiązanego z pakietem APK w Konsoli Play, o ile nie zmienisz głównego pliku rozszerzenia. Obecni użytkownicy otrzymają tylko zaktualizowany plik APK i nowy plik rozszerzenia poprawki (z zachowaniem poprzedniego głównego pliku rozszerzenia).

Oto kilka kwestii, o których warto pamiętać podczas aktualizowania plików rozszerzeń:

  • Jednocześnie mogą być dostępne tylko 2 pliki rozszerzające aplikację. Jeden główny plik rozszerzający i jeden plik rozszerzenia z poprawką. Podczas aktualizacji pliku Google Play usuwa jego poprzednią wersję (i ta aplikacja musi też być dostępna podczas ręcznych aktualizacji).
  • Gdy dodajesz plik rozszerzenia poprawki, system Android nie instaluje poprawki aplikacji ani głównego pliku rozszerzenia. Musisz zaprojektować aplikację tak, aby obsługiwała dane poprawki. Pakiet rozszerzenia APK zawiera jednak bibliotekę, w której można używać plików ZIP jako plików rozszerzeń. Ta biblioteka scala dane z pliku poprawki z głównym plikiem rozszerzenia, dzięki czemu można łatwo odczytać wszystkie dane z pliku rozszerzenia.