Google Play wymaga, aby skompresowany plik APK, który użytkownicy pobierają, nie przekraczał 100 MB. W przypadku większości aplikacji jest to wystarczająco dużo miejsca na kod i zasoby. Niektóre aplikacje potrzebują jednak więcej miejsca na wysokiej jakości grafiki, pliki multimedialne lub inne duże zasoby. Wcześniej, jeśli skompresowany rozmiar pliku do pobrania aplikacji przekraczał 100 MB, dodatkowe zasoby trzeba było hostować i pobierać samodzielnie, gdy użytkownik otwierał aplikację. Hostowanie i przesyłanie dodatkowych plików może być kosztowne, a użytkownicy często nie są zadowoleni z jakości. Aby ułatwić Ci ten proces i uczynić go przyjemniejszym dla użytkowników, Google Play umożliwia dołączenie 2 dużych plików rozszerzeń, które uzupełniają plik APK.
Google Play hostuje pliki rozszerzeń Twojej aplikacji i przesyła je na urządzenie bez żadnych opłat. Pliki rozszerzeń są zapisywane w pamięci współdzielonej urządzenia (na karcie SD lub partycji na dysku USB, zwanej też „pamięci zewnętrznej”), do której aplikacja ma dostęp. Na większości urządzeń Google Play pobiera pliki rozszerzeń w tym samym czasie, co plik APK, dzięki czemu aplikacja ma wszystko, czego potrzebuje, gdy użytkownik otworzy ją po raz pierwszy. W niektórych przypadkach aplikacja musi jednak pobrać pliki z Google Play podczas uruchamiania.
Jeśli nie chcesz używać plików rozszerzających, a skompresowany rozmiar pliku do pobrania aplikacji przekracza 100 MB, prześlij aplikację za pomocą pakietu aplikacji na Androida, który umożliwia pobranie skompresowanego pliku o rozmiarze do 200 MB. Dodatkowo, ponieważ korzystanie z pakietów aplikacji odkłada generowanie i podpisywanie plików APK na czas korzystania z Google Play, użytkownicy pobierają zoptymalizowane pliki APK zawierające tylko kod i zasoby potrzebne do uruchamiania aplikacji. Nie musisz kompilować, podpisywać ani zarządzać wieloma plikami APK ani plikami rozszerzenia, a użytkownicy otrzymują mniejsze, bardziej zoptymalizowane pliki do pobrania.
Omówienie
Za każdym razem, gdy przesyłasz plik APK za pomocą Konsoli Google Play, możesz dodać do niego 1 lub 2 pliki rozszerzeń. Każdy plik może mieć rozmiar do 2 GB i dowolny format, ale zalecamy użycie skompresowanego pliku, aby zaoszczędzić przepustowość podczas pobierania. Każdy plik rozszerzający pełni inną rolę:
- Główny plik rozszerzający to główny plik rozszerzający dodatkowych zasobów wymaganych przez aplikację.
- Plik rozszerzający poprawka jest opcjonalny i przeznaczony do wprowadzania niewielkich zmian w głównym pliku rozszerzonym.
Możesz używać obu plików rozszerzeń w dowolny sposób, ale zalecamy, aby główny plik rozszerzeń zawierał podstawowe zasoby i rzadko był aktualizowany. Plik rozszerzenia z łatkami powinien być mniejszy i służyć jako „nośnik łatek”. Powinien być aktualizowany z każdą większą wersją lub w razie potrzeby.
Nawet jeśli aktualizacja aplikacji wymaga tylko nowego pliku rozszerzenia, musisz przesłać nowy plik APK z aktualizowanym parametrem versionCode
w pliku manifestu. (Konsola Play nie pozwala przesłać pliku rozszerzenia do istniejącego pakietu APK).
Uwaga: plik rozszerzający z poprawką ma taką samą semantykę co główny plik rozszerzający. Możesz używać każdego z nich w dowolny sposób.
Format nazwy pliku
Każdy przesyłany plik rozszerzający może mieć dowolny format (ZIP, PDF, MP4 itp.). Możesz też użyć narzędzia JOBB do zakapsułkowania i zaszyfrowania zbioru plików zasobów oraz kolejnych poprawek dla tego zbioru. Niezależnie od typu pliku, Google Play traktuje je jako nieprzezroczyste pliki binarne i zmienia ich nazwy według tego schematu:
[main|patch].<expansion-version>.<package-name>.obb
Schemat składa się z 3 elementów:
main
lubpatch
- Określa, czy plik jest głównym plikiem rozszerzonym czy plikiem rozszerzonym z łatką. W przypadku każdego pliku APK może być tylko 1 plik główny i 1 plik poprawek.
<expansion-version>
- Jest to liczba całkowita odpowiadająca kodom wersji plików APK, z którymi powiązano rozszerzenie po raz pierwszy (jest zgodna z wartością
android:versionCode
aplikacji).Podkreślamy słowo „pierwszy”, ponieważ chociaż Konsola Play umożliwia ponowne użycie przesłanego pliku rozszerzającego w nowym pliku APK, nazwa tego pliku nie zmienia się – zachowuje wersję zastosowaną podczas pierwszego przesłania.
<package-name>
- Nazwa pakietu aplikacji w stylu Java.
Załóżmy na przykład, że wersja pliku APK to 314159, a nazwa pakietu to com.example.app. Jeśli prześlesz główny plik rozszerzenia, jego nazwa zostanie zmieniona na:
main.314159.com.example.app.obb
Lokalizacja pamięci
Gdy Google Play pobierze pliki rozszerzeń na urządzenie, zapisze je w miejscu udostępnionego magazynu. Aby zapewnić prawidłowe działanie, nie wolno usuwać, przenosić ani zmieniać nazw plików rozszerzeń. Jeśli aplikacja musi pobrać pliki bezpośrednio z Google Play, musisz zapisać je w tej samej lokalizacji.
Metoda getObbDir()
zwraca określoną lokalizację plików rozszerzeń w takim formacie:
<shared-storage>/Android/obb/<package-name>/
<shared-storage>
to ścieżka do współdzielonego miejsca na dane, dostępnego zgetExternalStorageDirectory()
.<package-name>
to nazwa pakietu aplikacji w stylu Java, dostępna wgetPackageName()
.
W tym katalogu nigdy nie ma więcej niż 2 plików rozszerzeń dla danej aplikacji.
Jeden to główny plik rozszerzający, a drugi to plik rozszerzający z poprawką (jeśli jest potrzebny). W przypadku aktualizacji aplikacji za pomocą nowych plików rozszerzenia poprzednie wersje zostaną zastąpione. Od Androida 4.4 (poziom API 19) aplikacje mogą odczytywać pliki rozszerzeń OBB
bez uprawnień do pamięci zewnętrznej. Niektóre implementacje Androida 6.0 (poziom interfejsu API 23) i nowszych wymagają jednak uprawnienia READ_EXTERNAL_STORAGE
, więc musisz zadeklarować je w manifeście aplikacji i poprosić o nie w czasie działania aplikacji w ten sposób:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
W przypadku Androida w wersji 6 lub nowszej uprawnienia do pamięci zewnętrznej muszą być wymagane w czasie działania. Niektóre implementacje Androida nie wymagają jednak uprawnień do odczytu plików OBB. Ten fragment kodu pokazuje, jak sprawdzić dostęp do odczytu przed poproszeniem o dostęp 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
, a także nie zapisuj rozpakowanych danych w tym samym katalogu. Rozpakowane pliki należy zapisać w katalogu określonym przez getExternalFilesDir()
. Jeśli to możliwe, najlepiej użyć formatu pliku rozszerzonego, który umożliwia odczytywanie bezpośrednio z pliku, zamiast rozpakowywania danych. Udostępniliśmy na przykład projekt biblioteki APK Expansion Zip Library, który odczytuje Twoje dane bezpośrednio z pliku ZIP.
Uwaga: w przeciwieństwie do plików APK wszystkie pliki zapisane w pamięci współdzielonej mogą być odczytywane 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 w plikach z kontrolą przesunięcia i długości (takich jak MediaPlayer.setDataSource()
i SoundPool.load()
) bez konieczności rozpakowywania pliku ZIP. Aby to zadziałało, podczas tworzenia pakietów ZIP nie należy dodatkowo kompresować plików multimedialnych. Jeśli na przykład używasz narzędzia 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 tym samym czasie, w którym pobiera plik APK na urządzenie. W niektórych przypadkach Google Play może nie pobrać plików rozszerzających lub użytkownik może usunąć wcześniej pobrane pliki rozszerzające. Aby obsłużyć te sytuacje, aplikacja musi mieć możliwość samodzielnego pobierania plików podczas uruchamiania głównej czynności za pomocą adresu URL udostępnionego przez Google Play.
Ogólnie proces pobierania wygląda tak:
- Użytkownik decyduje się zainstalować Twoją aplikację z Google Play.
- Jeśli Google Play może pobrać pliki rozszerzeń (co dotyczy 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.
- Gdy użytkownik uruchomi aplikację, musi ona sprawdzić, czy pliki rozszerzające są już zapisane na urządzeniu.
- Jeśli tak, aplikacja jest gotowa do użycia.
- Jeśli nie, aplikacja musi pobrać pliki rozszerzające z Google Play przez HTTP. Aplikacja musi wysłać żądanie do klienta Google Play za pomocą usługi licencjonowania aplikacji w Google Play, która odpowie nazwą, rozmiarem i adresem URL każdego pliku rozszerzenia. Następnie pobierasz te pliki i zapisujesz je w odpowiednim miejscu.
Uwaga: musisz uwzględnić kod niezbędny do pobrania plików rozszerzeń z Google Play, jeśli w momencie uruchamiania aplikacji nie są one jeszcze na urządzeniu. W sekcji Pobieranie plików rozszerzeń opisaliśmy bibliotekę, która znacznie upraszcza ten proces i pozwala pobrać pliki z usługi z minimalną ilością kodu.
Lista kontrolna rozwoju
Oto podsumowanie zadań, które należy wykonać, aby używać plików rozszerzeń w aplikacji:
- Najpierw określ, czy skompresowany rozmiar pliku do pobrania aplikacji musi przekraczać 100 MB. Pamięć jest cenna, dlatego należy dążyć do tego, aby łączny rozmiar pobierania był jak najmniejszy. Jeśli Twoja aplikacja zajmuje ponad 100 MB, ponieważ zawiera wiele wersji zasobów graficznych dla różnych gęstości ekranu, rozważ opublikowanie kilku plików APK, z których każdy będzie zawierać tylko zasoby wymagane na ekranach docelowych. Aby uzyskać najlepsze wyniki podczas publikowania w Google Play, prześlij pakiet aplikacji na Androida, który zawiera cały skompilowany kod i zasoby aplikacji, ale odkłada generowanie i podpisywanie pliku APK na później.
- Określ, które zasoby aplikacji oddzielić od pliku APK, i zapakuj je w pliku, który będzie używany jako główny plik rozszerzający.
Zwykle podczas wprowadzania zmian w głównym pliku rozszerzonym należy używać tylko drugiego pliku rozszerzonego. Jeśli jednak Twoje zasoby przekraczają limit 2 GB dla głównego pliku rozszerzenia, możesz użyć pliku poprawki dla pozostałych zasobów.
- Stwórz aplikację, która będzie używać zasobów z plików rozszerzeń w pamięci współdzielonej urządzenia.
Pamiętaj, że nie wolno usuwać, przenosić ani zmieniać nazw plików rozszerzeń.
Jeśli Twoja aplikacja nie wymaga określonego formatu, zalecamy utworzenie plików ZIP dla plików rozszerzeń, a następnie odczytanie ich za pomocą biblioteki plików ZIP z rozszerzeniem APK.
- Dodaj do głównej czynności aplikacji logikę, która sprawdza, czy pliki rozszerzeń znajdują się na urządzeniu podczas uruchamiania. Jeśli pliki nie znajdują się na urządzeniu, użyj usługi Licencjonowanie aplikacji w Google Play, aby poprosić o adresy URL plików rozszerzeń, a potem pobierz i zapisz je.
Aby znacznie zmniejszyć ilość kodu, który musisz napisać, i zapewnić użytkownikom dobre wrażenia podczas pobierania, zalecamy użycie biblioteki Downloader do implementacji zachowania pobierania.
Jeśli zamiast biblioteki tworzysz własną usługę pobierania, pamiętaj, że nie możesz zmieniać nazw plików rozszerzeń i musisz zapisywać je we właściwym miejscu przechowywania.
Po zakończeniu tworzenia aplikacji postępuj zgodnie z instrukcjami dotyczącymi testowania plików rozszerzających.
Zasady i ograniczenia
Dodawanie plików rozszerzeń APK to funkcja dostępna podczas przesyłania aplikacji w Konsoli Play. Podczas pierwszego przesyłania aplikacji lub jej aktualizowania (jeśli używa ona plików rozszerzeń) musisz pamiętać o tych zasadach i ograniczeniach:
- Rozmiar każdego pliku rozszerzenia nie może przekraczać 2 GB.
- Aby pobrać pliki rozszerzeń z Google Play, użytkownik musi mieć Twoją aplikację z Google Play. Google Play nie podaje adresów URL plików rozszerzeń, jeśli aplikacja została zainstalowana w inny sposób.
- Podczas pobierania z aplikacji adres URL udostępniany przez Google Play dla każdego pliku jest unikalny dla każdego pobierania i wygasa wkrótce po przekazaniu go aplikacji.
- Jeśli zaktualizujesz aplikację za pomocą nowego pliku APK lub prześlesz kilka plików APK z tą samą aplikacją, możesz wybrać pliki rozszerzające przesłane wcześniej dla poprzedniego pliku APK. Nazwa pliku rozszerzenia się nie zmienia – pozostaje wersja otrzymana przez plik APK, z którym był on pierwotnie powiązany.
- Jeśli używasz plików rozszerzeń w połączeniu z wieloma plikami APK, aby udostępniać różne pliki rozszerzeń na różnych urządzeniach, musisz nadal przesyłać osobne pliki APK dla każdego urządzenia, aby podać unikalną wartość
versionCode
i zadeklarować różne filtry dla każdego pliku APK. - Nie możesz wprowadzić aktualizacji aplikacji, zmieniając tylko pliki rozszerzające. Aby zaktualizować aplikację, musisz przesłać nowy plik APK. Jeśli zmiany dotyczą tylko zasobów w plikach rozszerzających, możesz zaktualizować plik APK, zmieniając tylko plik
versionCode
(i być może także plikversionName
). - Nie zapisuj innych danych w katalogu
obb/
. Jeśli musisz rozpakować niektóre dane, zapisz je w lokalizacji określonej przezgetExternalFilesDir()
. - Nie usuwaj ani nie zmieniaj nazwy pliku rozszerzenia
.obb
(chyba że wykonujesz aktualizację). W przeciwnym razie Google Play (lub sama aplikacja) będzie wielokrotnie pobierać plik rozszerzenia. - Podczas ręcznego aktualizowania pliku rozszerzenia musisz usunąć poprzedni plik rozszerzenia.
Pobieranie plików rozszerzenia
W większości przypadków Google Play pobiera i zapisuje pliki rozszerzeń na urządzeniu w tym samym czasie, gdy instaluje lub aktualizuje plik APK. Dzięki temu pliki rozszerzeń są dostępne, gdy aplikacja uruchamia się po raz pierwszy. W niektórych przypadkach jednak aplikacja musi sama pobrać pliki rozszerzające, wysyłając żądanie do adresu URL otrzymanego w odpowiedzi od usługi licencjonowania aplikacji w Google Play.
Oto podstawowa logika potrzebna do pobrania plików rozszerzających:
- Po uruchomieniu aplikacji poszukaj plików rozszerzających w pamięci współdzielonej (w katalogu
Android/obb/<package-name>/
).- Jeśli pliki rozszerzenia są dostępne, wszystko jest w porządku i aplikacja może kontynuować.
- Jeśli pliki rozszerzeń nie tam są:
- Prześlij żądanie za pomocą licencjonowania aplikacji w Google Play, aby uzyskać nazwy, rozmiary i adresy URL plików rozszerzeń aplikacji.
- Pobierz pliki rozszerzeń, korzystając z adresów URL udostępnionych przez Google Play, i je zapisz. Musisz zapisać pliki w wspólnym miejscu na dane (
Android/obb/<package-name>/
) i użyć dokładnej nazwy pliku podanej w odpowiedzi Google Play.Uwaga: adres URL, który Google Play udostępnia dla Twoich plików rozszerzających, jest unikalny dla każdego pobierania i wygasa wkrótce po przekazaniu do aplikacji.
Jeśli aplikacja jest bezpłatna, prawdopodobnie nie korzystasz z usługi licencjonowania aplikacji. Jest ona przeznaczona głównie do egzekwowania zasad licencjonowania aplikacji i zapewnienia, że użytkownik ma prawo do korzystania z aplikacji (o którą zapłacił w Google Play). Aby ułatwić korzystanie z funkcji plików rozszerzeń, usługa licencjonowania została ulepszona, aby zapewniać odpowiedź dla Twojej aplikacji, która zawiera adres URL plików rozszerzeń aplikacji hostowanych w Google Play. Dlatego nawet jeśli Twoja aplikacja jest bezpłatna, 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 – wystarczy, że biblioteka wykona żądanie, które zwróci adres URL plików rozszerzenia.
Uwaga: niezależnie od tego, czy Twoja aplikacja jest bezpłatna, Google Play zwraca adresy URL plików rozszerzeń tylko wtedy, gdy użytkownik kupił ją w Google Play.
Oprócz LVL potrzebujesz zestawu kodu, który pobiera pliki rozszerzeń przez połączenie HTTP i zapisuje je w odpowiednim miejscu w wspólnej pamięci urządzenia. Podczas implementowania tej procedury w aplikacji należy wziąć pod uwagę kilka kwestii:
- Na urządzeniu może nie być wystarczająco dużo miejsca na pliki rozszerzeń, dlatego przed rozpoczęciem pobierania należy sprawdzić, czy jest wystarczająco dużo miejsca, i ostrzec użytkownika, jeśli nie ma wystarczająco dużo miejsca.
- Pobieranie plików powinno odbywać się w usłudze działającej w tle, aby nie blokować interakcji z użytkownikiem i umożliwić mu opuszczenie aplikacji podczas pobierania.
- Podczas wysyłania żądania i pobierania może wystąpić wiele błędów, które musisz odpowiednio obsłużyć.
- Podczas pobierania połączenie z siecią może się zmieniać, dlatego należy uwzględniać takie zmiany i w razie przerwania pobierania wznowić je, jeśli to możliwe.
- Podczas pobierania w tle należy wyświetlić powiadomienie, które wskazuje postęp pobierania, informuje o jego zakończeniu i przenosi użytkownika z powrotem do aplikacji.
Aby ułatwić Ci to zadanie, opracowaliśmy bibliotekę pobierania, która wysyła żądanie adresów URL plików rozszerzeń za pomocą usługi licencjonowania, pobiera pliki rozszerzeń, wykonuje wszystkie wymienione powyżej czynności i nawet pozwala wstrzymać i wznowić pobieranie. Dodanie do aplikacji biblioteki Downloader i kilku elementów kodu oznacza, że prawie cała praca związana z pobieraniem plików rozszerzeń została już wykonana. Aby zapewnić użytkownikom jak najlepsze wrażenia przy minimalnym wysiłku z Twojej strony, zalecamy pobranie plików rozszerzeń za pomocą Biblioteki Downloadera. Informacje w następnych sekcjach wyjaśniają, jak zintegrować bibliotekę z aplikacją.
Jeśli wolisz opracować własne rozwiązanie do pobierania plików rozszerzeń za pomocą adresów URL Google Play, musisz wykonać żądanie licencji zgodnie z dokumentacją dotyczącą licencjonowania aplikacji, a następnie pobrać nazwy, rozmiary i adresy URL plików rozszerzeń z elementów dodatkowych odpowiedzi. Jako polityki licencyjnej należy użyć klasy APKExpansionPolicy
(znajdującej się w bibliotece weryfikacji licencji), która zawiera nazwy, rozmiary i adresy URL plików rozszerzeń z usługi licencyjnej.
Informacje o bibliotece Downloader
Aby używać plików rozszerzeń APK w swojej aplikacji i zapewniać użytkownikom jak najlepszą obsługę przy minimalnym wysiłku z Twojej strony, zalecamy użycie biblioteki pobierania zawartej w pakiecie Biblioteka rozszerzeń APK Google Play. Ta biblioteka pobiera pliki rozszerzeń w usłudze działającej w tle, wyświetla powiadomienie o stanie pobierania, obsługuje utratę połączenia z internetem, wznawia pobieranie, gdy jest to możliwe, i wykonuje inne czynności.
Aby wdrożyć pobieranie plików rozszerzeń za pomocą biblioteki Downloader:
- Rozszerz specjalną podklasę
Service
i podklasęBroadcastReceiver
, które wymagają tylko kilku linii kodu. - Dodaj do głównej aktywności logikę, która sprawdza, czy pliki rozszerzeń zostały już pobrane. Jeśli nie, uruchamia proces pobierania i wyświetla interfejs postępu.
- W głównej aktywności zaimplementuj interfejs wywołania zwrotnego z kilkoma metodami, który będzie otrzymywać informacje o postępie pobierania.
W poniższych sekcjach znajdziesz instrukcje konfigurowania aplikacji za pomocą biblioteki Downloadera.
Przygotowanie do korzystania z biblioteki Downloadera
Aby korzystać z biblioteki Downloader, musisz pobrać 2 pakiety z Menedżera pakietu SDK i dodać odpowiednie biblioteki do aplikacji.
Najpierw otwórz Menedżer pakietu Android SDK (Narzędzia > Menedżer pakietu SDK), a następnie w sekcji Wygląd i zachowanie > Ustawienia systemowe > Pakiet Android SDK kliknij kartę Narzędzia SDK, aby wybrać i pobrać:
- Pakiet biblioteki licencjonowania Google Play
- Pakiet APK Expansion Library w Google Play
Utwórz nowy moduł biblioteki dla biblioteki weryfikacji licencji i biblioteki pobierania. W przypadku każdej biblioteki:
- Kliknij Plik > Nowy > Nowy moduł.
- W oknie Utwórz nowy moduł kliknij kolejno Biblioteka Androida i Dalej.
- Podaj nazwę aplikacji lub biblioteki, np. „Google Play License Library” i „Google Play Downloader Library”, wybierz Minimalny poziom pakietu SDK, a następnie kliknij Zakończ.
- Wybierz Plik > Struktura projektu.
- Wybierz kartę Właściwości i w polu Biblioteka
Repozytorium wpisz bibliotekę z katalogu
<sdk>/extras/google/
(play_licensing/
dla biblioteki weryfikującej licencję lubplay_apk_expansion/downloader_library/
dla biblioteki pobierania). - Kliknij OK, aby utworzyć nowy moduł.
Uwaga: biblioteka pobierania zależy od biblioteki weryfikacji licencji. Pamiętaj, aby dodać bibliotekę weryfikacji licencji do właściwości projektu biblioteki pobierania.
Możesz też zaktualizować projekt w wierszu poleceń, aby uwzględnić biblioteki:
- Przejdź do katalogu
<sdk>/tools/
. - Wykonaj
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 Biblioteki weryfikacji licencji i Biblioteki pobierania możesz szybko zintegrować możliwość pobierania plików rozszerzeń z Google Play. Format plików rozszerzeń i sposób ich odczytu z pamięci współdzielonej to osobne implementacje, które należy wziąć pod uwagę w zależności od potrzeb aplikacji.
Wskazówka: pakiet Apk Expansion zawiera przykładową aplikację, która pokazuje, jak używać w aplikacji biblioteki Downloader. Przykład korzysta z biblioteki zewnętrznej dostępnej w pakiecie Apk Expansion o nazwie APK Expansion Zip Library. Jeśli planujesz używać plików ZIP na pliki rozszerzeń, zalecamy też dodanie do aplikacji biblioteki ZIP do rozszerzeń APK. Więcej informacji znajdziesz w sekcji poniżej o używaniu biblioteki ZIP do rozszerzeń APK.
Deklarowanie uprawnień użytkowników
Aby pobrać pliki rozszerzające, biblioteka Downloader 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 Downloader wymaga poziomu interfejsu API 4, ale biblioteka APK Expansion Zip wymaga poziomu interfejsu API 5.
Implementacja usługi pobierania
Aby umożliwić pobieranie w tle, biblioteka Downloader udostępnia własną podklasę Service
o nazwie DownloaderService
, którą należy rozszerzyć. Oprócz pobierania plików rozszerzeń za Ciebie DownloaderService
:
- Rejestruje
BroadcastReceiver
, który wykrywa zmiany w połączeniu z siecią urządzenia (wysyłane w ramachCONNECTIVITY_ACTION
), aby w razie potrzeby wstrzymać pobieranie (np. z powodu utraty połączenia) i wznowić je, gdy to możliwe (po przywróceniu połączenia). - W przypadku usług, które zostały przerwane, harmonogram
RTC_WAKEUP
alarmu, aby ponownie spróbować pobrać plik. - Tworzy niestandardowy element
Notification
, który wyświetla postęp pobierania oraz wszelkie błędy lub zmiany stanu. - Umożliwia aplikacji ręczne wstrzymywanie i wznawianie pobierania.
- Przed pobraniem plików rozszerzeń weryfikuje, czy udostępnione miejsce na dane jest zamontowane i dostępne, czy pliki nie istnieją już i czy jest wystarczająco dużo miejsca. Następnie powiadamia użytkownika, jeśli którekolwiek z tych kryteriów nie jest spełnione.
Wystarczy, że utworzysz w aplikacji klasę rozszerzającą klasę DownloaderService
i zastąpisz 3 metody, aby podać konkretne informacje o aplikacji:
getPublicKey()
- Ta funkcja musi zwracać ciąg znaków, który jest kluczem publicznym RSA zakodowanym w formacie Base64 dla konta wydawcy. Można go znaleźć na stronie profilu w Konsoli Play (patrz Konfigurowanie licencjonowania).
getSALT()
- Ta funkcja musi zwracać tablicę losowych bajtów, której licencjonowanie
Policy
używa do tworzeniaObfuscator
. Sól zapewnia, że zaszyfrowany plikSharedPreferences
, w którym są zapisane dane dotyczące licencjonowania, będzie unikalny i niewykrywalny. getAlarmReceiverClassName()
- Ta funkcja musi zwracać nazwę klasy
BroadcastReceiver
w aplikacji, która powinna otrzymać alarm informujący, że pobieranie musi zostać wznowione (może się tak zdarzyć, jeśli usługa pobierania nieoczekiwanie się zatrzyma).
Oto na przykład pełna implementacja 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
, aby była to wartość klucza publicznego należącego do Twojego konta wydawcy. Klucz znajdziesz w Developer Console w sekcji z informacjami o profilu. Jest to konieczne nawet wtedy, gdy testujesz swoje pliki do pobrania.
Pamiętaj, aby zadeklarować usługę w pliku manifestu:
<app ...> <service android:name=".SampleDownloaderService" /> ... </app>
Implementacja odbiornika alarmu
Aby monitorować postępy pobierania plików i w razie potrzeby ponownie uruchomić pobieranie, DownloaderService
planuje alarm RTC_WAKEUP
, który wysyła Intent
do BroadcastReceiver
w aplikacji. Musisz zdefiniować BroadcastReceiver
, aby wywołać interfejs API z biblioteki Downloader, który sprawdza stan pobierania i w razie potrzeby ponownie uruchamia pobieranie.
Aby wywołać metodę DownloaderClientMarshaller.startDownloadServiceIfRequired()
, musisz zastąpić metodę onReceive()
.
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 to jest klasa, której nazwę musisz zwrócić w metodzie getAlarmReceiverClassName()
usługi (patrz poprzednia sekcja).
Pamiętaj, aby zadeklarować odbiornik w pliku manifestu:
<app ...> <receiver android:name=".SampleAlarmReceiver" /> ... </app>
Rozpoczynanie pobierania
Główna aktywność w aplikacji (ta, która jest uruchamiana przez ikonę w menu) odpowiada za sprawdzanie, czy pliki rozszerzeń są już na urządzeniu, i inicjowanie ich pobierania, jeśli nie.
Aby rozpocząć pobieranie za pomocą biblioteki Downloadera, wykonaj te czynności:
- Sprawdź, czy pliki zostały pobrane.
Biblioteka Downloader zawiera interfejsy API klasy
Helper
, które ułatwiają ten proces:getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)
doesFileExist(Context c, String fileName, long fileSize)
Na przykład przykładowa aplikacja udostępniona w pakiecie Apk Expansion wywołuje metodę
onCreate()
w metodzie aktywnościonCreate()
, aby sprawdzić, czy pliki rozszerzające istnieją już 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ą wskazującą, czy jest to główny plik rozszerzenia. (szczegóły znajdziesz w klasieSampleDownloaderActivity
w próbnej aplikacji).Jeśli zwracana wartość to false, aplikacja musi rozpocząć pobieranie.
- Rozpocznij pobieranie, wywołując stałą metodę
DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass)
.Metoda przyjmuje te parametry:
context
:Context
Twojej aplikacji.notificationClient
:PendingIntent
, aby rozpocząć główną aktywność. Jest on używany w komponencieNotification
utworzonym przez komponentDownloaderService
do wyświetlania postępu pobierania. Gdy użytkownik kliknie powiadomienie, system wywoła podany przez Ciebie pakietPendingIntent
i powinien otworzyć aktywność pokazującą postęp pobierania (zwykle tę samą aktywność, która rozpoczęła pobieranie).serviceClass
: obiektClass
dla implementacjiDownloaderService
, wymagany do uruchomienia usługi i rozpoczęcia pobierania w razie potrzeby.
Metoda zwraca liczbę całkowitą, która wskazuje, czy pobieranie jest wymagane. Możliwe wartości to:
NO_DOWNLOAD_REQUIRED
: zwracany, jeśli pliki istnieją lub pobieranie jest już w toku.LVL_CHECK_REQUIRED
: zwracany, jeśli do uzyskania adresów URL plików rozszerzeń wymagana jest weryfikacja licencji.DOWNLOAD_REQUIRED
: zwracany, jeśli adresy URL plików rozszerzających są już znane, ale nie zostały pobrane.
Działania atrybutów
LVL_CHECK_REQUIRED
iDOWNLOAD_REQUIRED
są w podstawie takie same i zwykle nie musisz się nimi przejmować. W głównej metodzie wywołaniastartDownloadServiceIfRequired()
możesz sprawdzić, czy odpowiedź toNO_DOWNLOAD_REQUIRED
. Jeśli odpowiedź jest inna niżNO_DOWNLOAD_REQUIRED
, biblioteka Downloader rozpoczyna pobieranie i należy zaktualizować interfejs aktywności, aby wyświetlić postęp pobierania (patrz następny krok). Jeśli odpowiedź toNO_DOWNLOAD_REQUIRED
, pliki są dostępne i aplikacja może się uruchomić.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 }
- Jeśli metoda
startDownloadServiceIfRequired()
zwraca cokolwiek inne niżNO_DOWNLOAD_REQUIRED
, utwórz instancjęIStub
, wywołując metodęDownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService)
.IStub
zapewnia powiązanie Twojej aktywności z usługą pobierania, dzięki czemu Twoja aktywność otrzymuje wywołania zwrotne dotyczące postępu pobierania.Aby utworzyć instancję klasy
IStub
, wywołując metodęCreateStub()
, musisz jej przekazać implementację interfejsuIDownloaderClient
i implementację klasyDownloaderService
. W następnej sekcji Otrzymywanie informacji o postępie pobierania omawiamy interfejsIDownloaderClient
, który zwykle należy zaimplementować w klasieActivity
, aby można było aktualizować interfejs aktywności po zmianie stanu pobierania.Zalecamy wywołanie metody
CreateStub()
, aby utworzyć instancję obiektuIStub
w ramach metodyonCreate()
aktywności, gdystartDownloadServiceIfRequired()
rozpocznie pobieranie.Na przykład w poprzednim przykładowym kodzie
onCreate()
możesz na wynikstartDownloadServiceIfRequired()
odpowiedzieć 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; }
Gdy metoda
onCreate()
zwróci wartość, Twoja aktywność otrzyma wywołanie metodyonResume()
, w której wywołasz metodęconnect()
obiektuIStub
, przekazując do niej obiektContext
z Twojej aplikacji. Z kolei w wywołaniu zwrotnymonStop()
w aktywności należy wywołać funkcjędisconnect()
.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 funkcji
connect()
w komponencieIStub
wiąże Twoją aktywność z komponentemDownloaderService
, dzięki czemu Twoja aktywność otrzymuje wywołania zwrotne dotyczące zmian stanu pobierania przez interfejsIDownloaderClient
.
Postęp pobierania
Aby otrzymywać informacje o postępie pobierania i interagować z DownloaderService
, musisz zaimplementować interfejs IDownloaderClient
biblioteki Downloader.
Zazwyczaj działanie, które służy do uruchamiania pobierania, powinno implementować ten interfejs, aby wyświetlać postęp pobierania i wysyłać żądania do usługi.
Wymagane metody interfejsu dla IDownloaderClient
to:
onServiceConnected(Messenger m)
- Po uruchomieniu instancji
IStub
w swojej aktywności otrzymasz wywołanie tej metody, która przekazuje obiektMessenger
połączony z Twoją instancjąDownloaderService
. Aby wysyłać żądania do usługi, np. wstrzymywać i wznawiać pobieranie, musisz wywołaćDownloaderServiceMarshaller.CreateProxy()
, aby otrzymać interfejsIDownloaderService
połączony z tą 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()
irequestContinueDownload()
). onDownloadStateChanged(int newState)
- Usługa pobierania wywołuje tę funkcję, gdy nastąpi zmiana stanu pobierania, np. rozpoczęcie lub zakończenie pobierania.
Wartość
newState
będzie jedną z kilku możliwych wartości określonych przez jedną z konstantSTATE_*
klasyIDownloaderClient
.Aby wyświetlać użytkownikom przydatne komunikaty, możesz poprosić o odpowiednie ciągi znaków dla każdego stanu, wywołując funkcję
Helpers.getDownloaderStringResourceIDFromState()
. Zwraca identyfikator zasobu dla jednej z ciągów znaków wbudowanych w bibliotece pobierania. Na przykład ciąg „Pobieranie zostało wstrzymane, ponieważ jesteś w roamingu” odpowiada wartościSTATE_PAUSED_ROAMING
. onDownloadProgress(DownloadProgressInfo progress)
- Usługa pobierania wywołuje tę metodę, aby przekazać obiekt
DownloadProgressInfo
, który zawiera różne informacje o postępie pobierania, w tym szacowany czas pozostały do zakończenia, bieżącą prędkość, ogólny postęp i łączny czas, dzięki czemu można zaktualizować interfejs użytkownika pokazujący postęp pobierania.
Wskazówka: przykłady tych funkcji zwracających wywołanie, które aktualizują interfejs użytkownika pokazujący postęp pobierania, znajdziesz w funkcji SampleDownloaderActivity
w przykładowej aplikacji dołączonej do pakietu rozszerzonego pliku APK.
Oto kilka publicznych metod interfejsu IDownloaderService
, które mogą Ci się przydać:
requestPauseDownload()
- Wstrzymuje pobieranie.
requestContinueDownload()
- Wznawia wstrzymane pobieranie.
setDownloadFlags(int flags)
- Ustawia ustawienia użytkownika dotyczące typów sieci, w których można pobierać pliki. Obecna implementacja obsługuje 1 flagę,
FLAGS_DOWNLOAD_OVER_CELLULAR
, ale możesz dodać inne. Domyślnie ten parametr jest wyłączony, więc użytkownik musi być połączony z siecią Wi-Fi, aby pobrać pliki rozszerzeń. Możesz udostępnić użytkownikom ustawienie, które umożliwi pobieranie przez sieć komórkową. W takim przypadku możesz zadzwonić:Kotlin
remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply { ... setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR) }
Java
remoteService .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
Używanie APKExpansionPolicy
Jeśli zamiast korzystać z biblioteki pobierania w Google Play zdecydujesz się na stworzenie własnej usługi pobierania, nadal musisz używać APKExpansionPolicy
udostępnionego w bibliotece weryfikacji licencji. Klasa APKExpansionPolicy
jest prawie identyczna jak klasa ServerManagedPolicy
(dostępna w bibliotece Google Play do weryfikacji licencji), ale zawiera dodatkowe informacje dotyczące dodatkowych danych odpowiedzi pliku APK.
Uwaga: jeśli używasz biblioteki pobierania, jak opisano w poprzedniej sekcji, biblioteka wykonuje wszystkie interakcje z APKExpansionPolicy
, więc nie musisz używać tej klasy bezpośrednio.
Klasa zawiera metody, które pomogą Ci uzyskać niezbędne informacje o dostępnych plikach rozszerzeń:
getExpansionURLCount()
getExpansionURL(int index)
getExpansionFileName(int index)
getExpansionFileSize(int index)
Więcej informacji o używaniu APKExpansionPolicy
, gdy nie korzystasz z Biblioteki Downloadera, znajdziesz w dokumentacji Dodawanie licencji do aplikacji, która wyjaśnia, jak zaimplementować zasady licencjonowania takie jak te.
Czytanie pliku rozszerzenia
Gdy pliki rozszerzeń APK zostaną zapisane na urządzeniu, sposób ich odczytu zależy od typu pliku. Jak wspomniano w omówieniu, pliki rozszerzeń mogą być dowolnego typu, ale ich nazwy są zmieniane za pomocą określonego formatu nazwy pliku i zapisywane w <shared-storage>/Android/obb/<package-name>/
.
Niezależnie od tego, jak odczytujesz pliki, zawsze najpierw sprawdź, czy pamięć zewnętrzna jest dostępna do odczytu. Możliwe, że użytkownik podłączył pamięć do komputera przez USB lub wyjął kartę SD.
Uwaga: po uruchomieniu aplikacji zawsze sprawdzaj, czy przestrzeń zewnętrznego magazynu danych jest dostępna i czytelna, wywołując funkcję getExternalStorageState()
. Zwraca jedną z kilku możliwych wartości reprezentujących stan pamięci zewnętrznej. Aby można było go odczytać w aplikacji, wartość zwracana musi być typu MEDIA_MOUNTED
.
Pobieranie nazw plików
Jak opisano w omówieniu, pliki rozszerzające APK są zapisywane w określonym formacie nazwy pliku:
[main|patch].<expansion-version>.<package-name>.obb
Aby uzyskać lokalizację i nazwy plików rozszerzeń, użyj metod getExternalStorageDirectory()
i getPackageName()
do utworzenia ścieżki do plików.
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 jej swoją aplikację Context
oraz wersję żądanego pliku rozszerzenia.
Numer wersji pliku rozszerzenia można określić na wiele sposobów. Jednym z prostych sposobów jest zapisanie wersji w pliku SharedPreferences
, gdy rozpocznie się pobieranie, przez zapytanie o nazwę pliku rozszerzenia za pomocą metody APKExpansionPolicy
klasy APKExpansionPolicy
.getExpansionFileName(int index)
Gdy chcesz uzyskać dostęp do pliku rozszerzonego, możesz odczytać kod wersji, odczytując plik SharedPreferences
.
Więcej informacji o odczytywaniu z pamięci współdzielonej znajdziesz w dokumentacji Pamięci danych.
Korzystanie z biblioteki plików ZIP z rozszerzeniem APK
Pakiet Google Market Apk Expansion zawiera bibliotekę o nazwie APK Expansion Zip Library (znajdującą się w folderze <sdk>/extras/google/google_market_apk_expansion/zip_file/
). Jest to opcjonalna biblioteka, która ułatwia odczytywanie plików rozszerzenia, gdy są one zapisane jako pliki ZIP. Dzięki tej bibliotece możesz łatwo odczytywać zasoby z plików ZIP jako wirtualnego systemu plików.
Biblioteka ZIP rozszerzeń APK zawiera te klasy i interfejsy API:
APKExpansionSupport
- Przedstawia kilka metod uzyskiwania dostępu do nazw plików rozszerzających i plików ZIP:
getAPKExpansionFiles()
- Ta sama metoda, która zwraca pełną ścieżkę do obu plików rozszerzenia.
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)
- Zwraca
ZipResourceFile
reprezentujący sumę głównego pliku i pliku poprawki. Oznacza to, że jeśli określisz zarównomainVersion
, jak ipatchVersion
, zwróci toZipResourceFile
, który zapewnia dostęp do odczytu wszystkich danych, z danymi pliku poprawki scalonymi na wierzchu pliku głównego.
ZipResourceFile
- Reprezentuje plik ZIP w wspólnym miejscu przechowywania i wykonuje wszystkie czynności związane z tworzeniem wirtualnego systemu plików na podstawie plików ZIP. Możesz uzyskać instancję za pomocą funkcji
APKExpansionSupport.getAPKExpansionZipFile()
lub za pomocą funkcjiZipResourceFile
, przekazując ścieżkę do pliku rozszerzenia. Ta klasa zawiera wiele przydatnych metod, ale zazwyczaj nie musisz korzystać z większości z nich. Oto kilka ważnych metod:getInputStream(String assetPath)
- Dostarcza
InputStream
do odczytania pliku w pliku ZIP.assetPath
musi być ścieżką do wybranego pliku względem katalogu głównego zawartości pliku ZIP. getAssetFileDescriptor(String assetPath)
- Podaje
AssetFileDescriptor
dla pliku w pliku ZIP. WartośćassetPath
musi być ścieżką do wybranego pliku w powiązaniu z katalogiem głównym zawartości pliku ZIP. Jest to przydatne w przypadku niektórych interfejsów Android API, które wymagająAssetFileDescriptor
, np. niektórych interfejsówMediaPlayer
.
APEZProvider
- Większość aplikacji nie musi korzystać z tej klasy. Ta klasa definiuje
ContentProvider
, który porządkuje dane z plików ZIP za pomocą dostawcy treściUri
, aby zapewnić dostęp do plików dla niektórych interfejsów API Androida, które oczekują dostępuUri
do plików multimedialnych.Uri
Jest to przydatne, jeśli chcesz odtworzyć film za pomocąVideoView.setVideoURI()
.
Pomijanie kompresji ZIP plików multimedialnych
Jeśli pliki rozszerzeń służą do przechowywania plików multimedialnych, plik ZIP umożliwia korzystanie z wywołań odtwarzania multimediów w Androidzie, które zapewniają kontrolę przesunięcia i długości (takich jak MediaPlayer.setDataSource()
i SoundPool.load()
). Aby to działało, podczas tworzenia pakietów ZIP nie należy dodatkowo kompresować plików multimedialnych. Jeśli na przykład używasz narzędzia 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
Gdy używasz biblioteki ZIP z rozszerzeniem APK, odczyt 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 zapewnia dostęp do dowolnego pliku, który istnieje w głównym pliku rozszerzenia lub pliku rozszerzenia poprawki, poprzez odczyt z połączonej mapy wszystkich plików z obu plików. Aby przekazać metodę getAPKExpansionFile()
, musisz podać tylko android.content.Context
aplikacji oraz numer wersji głównego pliku rozszerzenia i pliku rozszerzenia poprawki.
Jeśli wolisz odczytywać z określonego pliku rozszerzenia, możesz użyć konstruktora ZipResourceFile
z ś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 do plików rozszerzeń znajdziesz w klasie SampleDownloaderActivity
przykładowej aplikacji, która zawiera dodatkowy kod do weryfikowania pobranych plików za pomocą CRC. Pamiętaj, że jeśli użyjesz tego przykładu jako podstawy do własnej implementacji, musisz zadeklarować rozmiar bajtów plików rozszerzeń w tablicy xAPKS
.
Testowanie plików rozszerzeń
Zanim opublikujesz aplikację, musisz przetestować 2 rzeczy: odczyt plików rozszerzających i pobieranie plików.
Testowanie odczytu pliku
Zanim prześlesz aplikację do Google Play, przetestuj, czy potrafi ona odczytać pliki z pamięci współdzielonej. Wystarczy, że dodasz pliki w odpowiednim miejscu w pamięci współdzielonej urządzenia i uruchomisz aplikację:
- Na urządzeniu utwórz odpowiedni katalog w wspólnym magazynie, w którym Google Play będzie zapisywać pliki.
Jeśli na przykład nazwa pakietu to
com.example.android
, musisz utworzyć katalogAndroid/obb/com.example.android/
w wspólnym miejscu na dane. (podłącz urządzenie testowe do komputera, aby zamontować współdzieloną pamięć masową i ręcznie utworzyć ten katalog). - Ręcznie dodaj do tego katalogu pliki rozszerzeń. Pamiętaj, aby zmienić nazwy plików tak, aby pasowały do formatu nazwy pliku używanego przez Google Play.
Na przykład niezależnie od typu pliku główny plik rozszerzający aplikacji
com.example.android
powinien mieć nazwęmain.0300110.com.example.android.obb
. Kod wersji może mieć dowolną wartość. Pamiętaj:- Główny plik rozszerzający zawsze zaczyna się od
main
, a plik poprawki odpatch
. - Nazwa pakietu zawsze odpowiada nazwie pliku APK, do którego jest on dołączony w Google Play.
- Główny plik rozszerzający zawsze zaczyna się od
- Teraz, gdy pliki rozszerzeń znajdują się na urządzeniu, możesz zainstalować i uruchomić aplikację, aby przetestować pliki rozszerzeń.
Oto kilka przypomnień dotyczących obsługi plików rozszerzeń:
- Nie usuwaj ani nie zmieniaj nazwy plików rozszerzających
.obb
(nawet jeśli rozpakujesz dane w innej lokalizacji). W przeciwnym razie Google Play (lub sama 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 przezgetExternalFilesDir()
.
Testowanie pobierania plików
Ponieważ aplikacja musi czasami ręcznie pobierać pliki rozszerzające po pierwszym otwarciu, ważne jest, aby przetestować ten proces i upewnić się, że aplikacja może pobrać adresy URL, pobrać pliki i zapisz je na urządzeniu.
Aby przetestować implementację procedury ręcznego pobierania w aplikacji, 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 rozszerzających, gdy tylko rozpocznie się główna aktywność.
Uwaga: wcześniej można było testować aplikację, przesyłając nieopublikowaną wersję „w postaci roboczej”. Ta funkcja nie jest już obsługiwana. Zamiast tego musisz opublikować ją na ścieżce testów wewnętrznych, zamkniętych lub otwartych. Więcej informacji znajdziesz w artykule Aplikacje w wersji roboczej nie są już obsługiwane.
Aktualizowanie aplikacji
Jedną z wielkich zalet korzystania z plików rozszerzeń w Google Play jest możliwość aktualizowania aplikacji bez ponownego pobierania wszystkich oryginalnych zasobów. Google Play umożliwia dołączenie do każdego pliku APK 2 plików rozszerzenia, więc możesz użyć drugiego pliku jako „łatki”, która zawiera aktualizacje i nowe zasoby. Dzięki temu nie trzeba będzie ponownie pobierać głównego pliku rozszerzenia, który może być duży i kosztowny dla użytkowników.
Plik rozszerzający z poprawką jest technicznie taki sam jak główny plik rozszerzający, a system Android ani Google Play nie wykonują faktycznego procesu łatania między głównym plikiem a plikiem rozszerzającym z poprawką. Kod aplikacji musi samodzielnie wprowadzać niezbędne poprawki.
Jeśli jako pliki rozszerzeń używasz plików ZIP, biblioteka ZIP z rozszerzeniem APK, która jest dołączona do pakietu z rozszerzeniem APK, umożliwia połączenie pliku poprawek z głównym plikiem rozszerzenia.
Uwaga: nawet jeśli musisz wprowadzić zmiany tylko w pliku rozszerzenia, musisz zaktualizować plik APK, aby Google Play mogło przeprowadzić aktualizację.
Jeśli nie musisz wprowadzać zmian w kodzie aplikacji, po prostu zaktualizuj element versionCode
w pliku manifestu.
Dopóki nie zmienisz głównego pliku rozszerzenia powiązanego z pakietem APK w Konsoli Play, użytkownicy, którzy wcześniej zainstalowali Twoją aplikację, nie będą pobierać głównego pliku rozszerzenia. Dotychczasowi użytkownicy otrzymają tylko zaktualizowany plik APK i nowy plik rozszerzenia (z zachowaniem poprzedniego głównego pliku rozszerzenia).
Oto kilka kwestii, o których warto pamiętać w związku z aktualizacją plików rozszerzeń:
- Do aplikacji można dodać tylko 2 pliki rozszerzenia naraz. 1 główny plik rozszerzający i 1 plik rozszerzający z poprawką. Podczas aktualizowania pliku Google Play usuwa jego poprzednią wersję (co musisz też zrobić w przypadku ręcznego aktualizowania aplikacji).
- Podczas dodawania pliku rozszerzenia poprawki system Android nie aktualizuje aplikacji ani głównego pliku rozszerzenia. Aplikacja musi obsługiwać dane poprawki. Pakiet Apk Expansion zawiera jednak bibliotekę do korzystania z plików ZIP jako plików rozszerzeń, która scala dane z pliku poprawek z głównym plikiem rozszerzenia, aby można było łatwo odczytać wszystkie dane z pliku rozszerzenia.