Apka w Google Play – przygotowanie

Po długich tygodniach (czy miesiącach) projektowania, kodowania i testowania nadchodzi taki moment, kiedy nasza aplikacja z bezużytecznej, paskudnej larwy przekształca się w pięknego motyla, szykującego się do pokazania światu. A nam zaczyna coraz częściej chodzić po głowie udostępnienie aplikacji szerszemu gronu odbiorców. Najsensowniejszą opcją jest opublikowanie jej w sklepie Google Play (dawnym Android Market – bardzo mi się podobała ta nazwa). Można również założyć wątek na jakimś forum androida np. forum.android.com.pl , szczególnie jeśli nasza aplikacja nie może zostać opublikowana w sklepie z powodu niezgodności z regulaminem. Zakładamy, że naszej aplikacji ten problem nie dotyczy, więc…

Konto developerskie

googleDev

Aby opublikować apkę musimy założyć konto developerskie na stronie Google Developers, które zostanie powiązane z naszym zwykłym kontem Google. Założenie go wymaga jednorazowej opłaty 25$ wniesionej za pomocą karty kredytowej. Nie ma opcji przelewu czy PayPal, więc jeśli nie posiadamy odpowiedniej karty, to dobrym rozwiązaniem jest wirtualna karta kredytowa (większość kont bankowych ma taką opcję). Ostatnim punktem jest uzupełnienie naszych danych o:

  • nazwę programisty (widoczna dla klientów sklepu)
  • mail kontaktowy
  • nr telefonu (do użytku tylko dla firmy Google na wypadek problemów)opcje google dev

Od tego momentu można już rozpocząć dodawanie pierwszej apki.

Samo konto oferuje dostęp do bardzo dużej ilości usług i platform. Naturalnie obiektem naszego zainteresowania jest zakładka Android, a dokładniej Google Play Console, z której możemy dodawać aplikacje. Sama konsola ma sporo dodatkowych funkcjonalności jak np. połączenie z usługami Firebase, AdWords, DoubleClick czy ustawienie konta sprzedawcy. Ja poprzestałem na ustawieniach podstawowych.

Dodanie aplikacji – przygotowanie

Konto developerskie już śmiga, możemy rozpocząć dodawanie pierwszej aplikacji. Rozpoczynamy od określenia domyślnej wersji językowej oraz tytułu aplikacji (do 30 znaków). Kolejnym krokiem jest przygotowanie elementów:

  • krótkiego opisu do 80 znaków
  • rozszerzonego opisu do 4000 znaków
  • min. 2 screenów z aplikacji (JPG lub PNG)
  • ikony apki 512x512px (PNG z kanałem alfa)
  • grafiki nagłówka do sekcji Polecane 1024x500px (JPG lub PNG bez kanału alfa)

Jest to lista minimum. Poza tym możemy dodać link do filmu promocyjnego na YouTube, grafiki promocyjne itp. Dla każdej wybranej przez nas obsługiwanej wersji językowej możemy również dodać osobne tłumaczenie opisów.

Po uzbrojeniu się w w/w treści wrzucamy je w odpowiednie pola zakładki Informacje o aplikacji. Kolejnym krokiem jest wybranie typu aplikacji (Gra lub Aplikacja), jej kategorii oraz dokonanie oceny treści. google play consoleDzwonnik trafił do typu Aplikacje w kategorii Narzędzia. Oceny można dokonać dopiero po uploadzie pliku .apk z samą aplikacją, a polega to na wypełnieniu kwestionariusza na temat występowania w apce przemocy, golizny, nazistowskich symboli, narkotyków itp. Na jej podstawie nadawane są kategorie wiekowe różnych organizacji. Dzwonnik dostał m.in. PEGI 3 😉

Ostatnimi dwom krokami jest uzupełnienie informacji kontaktowych widocznych dla klientów sklepu: strona internetowa, mail i numer telefonu. Wymagany bezwzględnie jest tylko mail. Pozostaje jeszcze dołączyć link do polityki prywatności, jednak można tą opcję pominąć (z czego skorzystałem). Voila!

aplikacja bez apk

W następnym wpisie poruszę kwestię podpisywania aplikacji,  gdyż uploadować można tylko podpisane apki 🙂

Dzwonnik 1.0 w Google Play!

Kamień milowy został osiągnięty!

Dzwonnik google play phonezagościł wśród aplikacji dostępnych w sklepie Google Play 🙂

Samo wykupienie dostępu do konta developerskiego (25$ za dożywotni dostęp) jest zaledwie preludium do publikacji aplikacji. Po uszczupleniu kiesy czeka nas przygotowanie różnej maści wymaganych opisów, grafik, podpisanie aplikacji i wypełnienie tony wymaganych przez Google formularzy zanim będziemy mogli nacisnąć finalny przycisk, za którym kryje się udostępnienie publiczne aplikacji. A nawet po jego naciśnięciu należy uzbroić się w cierpliwość, gdyż czeka nas oglądanie poniższego widoku oznajmiającego mielenie apki w trybach sklepu. Teoretycznie może to trwać kilka godzin, w przypadku mojej aplikacji było to około godziny.

przetwarzenie Google play

Sam Dzwonnik udostępnia obecnie swoje podstawowe funkcjonalności, bez żadnych dodatkowych bajerów i jest dostępny całkowicie za darmo i bez reklam, dla telefonów od wersji systemu Android 5.0 (API 21). W miarę możliwości postaram się dodać w kolejnych wersjach kilka opcjonalnych funkcjonalności:

  • regulacja głośności dla każdego z kanałów osobno (dzwonek, wiadomości, media, system)
  • parę skórek do wyboru
  • wsparcie dla starszych wersji Androida

Ponadto  w kilku krótkich postach przybliżę cały proces publikacji, ze szczególnym uwzględnieniem momentów problematycznych i… nieco dziwnych.

Tymczasem jeśli masz pomysł na jakieś rozwinięcie możliwości Dzwonnika zapraszam do komentowania 😉

Wersje językowe

Nie wiem jakie jest Wasze zdanie, ale osobiście uważam, że przed opublikowaniem apki w Google Play dobrym pomysłem jest zaopatrzenie jej w angielską wersję językową. W końcu w dzisiejszych czasach jest to lingua franca, współczesna łacina. Ewentualnie spolszczenie, jeśli całość jest od początku po angielsku 😉

Środowisko Android udostępnia bardzo przyjemny mechanizm ułatwiający tłumaczenie wszystkich tekstów występujących w aplikacji dzięki możliwości przechowywania ciągów znaków jako resources  typu String w pliku strings.xml.

strings

W ten sposób zamiast zabitego na sztywno tekstu gdzieś w kodzie, wstawiane jest odniesienie do konkretnej wartości tekstowej za pomocą jej nazwy, w pliku ze stringami.

notHard

notHardR

Daje to programiście dwie zalety:

  • cały tekst występujący w aplikacji znajduje się w jednym miejscu
  • możliwość wykorzystania tego samego Stringa w wielu miejscach

Android Studio umożliwia prosty mechanizm stworzenia analogicznych plików, w różnych wersjach językowych. Wystarczy stworzyć nowy plik i wybrać język, który chcemy użyć, a odpowiednio nazwane pliki i foldery zostaną utworzone automatycznie. Pozostaje jeszcze skopiować wartości z pliku strings.xml i wiersz po wierszu przetłumaczyć. Wszystko elegancko znajduje się w jednym miejscu.

1

W zależności od języka ustawionego w systemie telefonu aplikacja wybiera odpowiednią wersję językową, a w przypadku jej braku,  jako domyślna jest traktowana wersji z pliku strings.xml. Trzeba jedynie pamiętać aby żaden fragment tekstu przeznaczony do przetłumaczenia nie schował się gdzieś wewnątrz kodu, wpisany na sztywno.

extract string

Na szczęście IDE przypomina nam o tym i umożliwia bardzo prostą ekstrakcję Stringa. Również po wykonaniu inspekcji kodu (Analyze -> Inspect Code) jedną z informacji, które otrzymamy są miejsca z występującym hardcodowanym tekstem.

international

W analogiczny sposób można regionalizować również inne zasoby np. obrazy. Nie znam rozwiązań z innych środowisk, ale ta metoda bardzo przypadła mi do gustu.

Czas na testy

etsty

Dzwonnik jest już gotowy w swojej wersji zawierającej minimalne funkcjonalności. Można stworzyć zdarzenie zmiany głośności dzwonka lub trybu na dowolny dzień tygodnia oraz godzinę. Wraz ze zmianą głośności dzwonka zmieniają się również głośności powiadomień, multimediów oraz alarmów. Rejestracja zdarzeń na dany dzień następuje raz na dobę dzięki DailyReceiver. W przypadku restartu telefonu DailyReceiver również zostanie uruchomiony dzięki zdarzeniu sterowanym rebootem – OnBootReceiver. Niestety ze względu na trudności w testowaniu zdarzeń wywoływanych czasem muszę ograniczyć się do testów manualnych. Jedynie klasa obsługi bazy danych ma obecnie napisane testy jednostkowe z wykorzystaniem frameworka Robolectric.

W dalszych planach pozostaje:

  • przetestowanie działania aplikacji w praktyce
  • dodanie kilku funkcjonalności (np. usunięcie całej bazy, wczytanie aktualnych ustawień dzwonków przy tworzeniu nowego zdarzenia)
  • poprawienie interfejsu graficznego
  • wrzucenie aplikacji do sklepu Google Play (naturalnie darmowej)

Ponieważ nigdy nie korzystałem se sklepu Google jako developer, jak również nie korzystałem z Google Analytics jestem bardzo ciekawy tej części projektu 🙂

Dzyń Dzyń

Doszedłem do momentu, w którym aplikacja otrzymała swoją najbardziej rzucającą się w oczy funkcjonalność, czyli możliwość modyfikowania głośności dzwonka. Alternatywnie przejścia w tryb wibracji lub ciszy. Dostęp do modyfikacji tych parametrów zapewnia klasa AudioManager, której instancję otrzymujemy z kontekstu aplikacji.

AudioManager audioManager = (AudioManager)
context.getSystemService(Context.AUDIO_SERVICE);

Za pomocą AudioManagera możemy ustawić wybrany profil dźwiękowy:

audioManager.setRingerMode(profileType);

Dzwonnik umożliwia wybór trzech podstawowych profili:

AudioManager.RINGER_MODE_SILENT

AudioManager.RINGER_MODE_VIBRATE

AudioManager.RINGER_MODE_NORMAL

Ponieważ XX w. skończył się jakiś czas temu i obecnie telefon potrafi wydawać z siebie wiele różnych dźwięków ponad samym poinformowaniem o przychodzącym połączeniu. Dźwięki są  skategoryzowane w ramach strumieni. Przykładami są m.in. strumienie odpowiadające za dzwonek, powiadomienia, alarmy, dźwięk muzyki/filmów:

AudioManager.STREAM_RING

AudioManager.STREAM_NOTIFICATION

AudioManager.STREAM_ALARM

AudioManager.STREAM_MUSIC

Aby zmienić wartość głośności środowisko Android udostępnia metody oparte o podanie typu strumienia audio oraz operacji jaką chcemy na nim wykonać:

audioManager.adjustStreamVolume(streamType, operationType, flag); 

Mamy kilka operacji do wyboru. Podstawowe służą do zwiększenia/zmniejszenia głośności o jedną jednostkę:

AudioManager.ADJUST_RAISE

AudioManager.ADJUST_LOWER  

Drugą opcją jest podanie liczbowej wartość głośności jaką chcemy ustawić:

audioManager.setStreamVolume(streamType, volumeValue, flag); 

Strumienie mogą mieć różne zakresy wartości. Przykładowo dla głośności dzwonka jest to zakres 0 do 7.  Flagi definiują dodatkowe zachowanie podczas regulacji głośności. Dla przykładu dwie poniższe flagi powodują odpowiednio odtworzenie dźwięku przy zmianie głośności oraz umożliwiają przejście w tryb cichy/wibracji w przypadku zmniejszenia głośności poniżej zera.

AudioManager.FLAG_PLAY_SOUND

AudioManager.FLAG_ALLOW_RINGER_MODES 

Naturalnie powyższe przykłady nie wyczerpują wszystkich możliwości jakie daje nam AudioManager. Ostatnią istotną kwestią są uprawnienia aplikacji do modyfikacji dźwięku. Sama regulacja głośności oraz ustawienie telefonu w tryb cichy/wibracji nie wymaga niczego. Natomiast wyjście z tego trybu wymaga aby aplikacja posiadała dodatkowe uprawnienia do przerywania trybu „Nie przeszkadzać”. Dla systemów do API 23 należy umieścić wpis w pliku manifestu (nad ):

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

W przypadku Androida od wersji API 24, poza wpisem aplikacja musi poprosić użytkownika o dostęp do tych samych ustawień. Do sprawdzenia czy aplikacja posiada uprawnienia służy metoda isNotificationPolicyAccessGranted() klasy NotificationManager.

private void grantDoNotDisturbAccess() {
   NotificationManager notificationManager =
     (NotificationManager) getBaseContext().getSystemService(Context.NOTIFICATION_SERVICE);
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
     && !notificationManager.isNotificationPolicyAccessGranted()){
       Intent intent = new
Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
       startActivity(intent);
   }
}

Zdarzenia planowane i cykliczne

Zdarzają się sytuacje, kiedy potrzebujemy aby w aplikacji metoda została uruchomiona o ściśle określonym czasie. Być może powinna być również powtarzana co jakiś okres czasu, niezależnie od tego, czy np. nasza aplikacja jest w danym momencie włączona czy nie. Takie zdarzenia mogą służyć np. do aktualizacji pogody czy wywołania przypomnienia dla użytkownika o jakiejś czynności.

W przypadku systemu, w którym wywołanie zdarzenia odbywa się na skutek pojawienia się nowych danych na serwerze, zalecanym rozwiązaniem jest zastosowanie systemu Firebase Cloud Messaging (jeśli jesteśmy właścicielem serwera) połączonego z Sync Adapterem po stronie aplikacji w telefonie.

W przypadku kiedy wywołanie zdarzenia ma się odbyć w ustalonym wcześniej momencie w czasie z pomocą przychodzi nam klasa AlarmManager, która umożliwia zarejestrowanie intencji do wywołania. Zobaczmy dokładniej jak możemy skorzystać z jej możliwości.
Pierwszym etapem jest stworzenie klasy do wywołania, która musi dziedziczyć po klasie BroadcastReceiver. Implementuje ona jedną metodę – onReceive().

public class RingtoneSwitcher extends BroadcastReceiver {
private final String EXTRA_VOLUME = "com.wordpress.gatarblog.dzwonnik.VOLUME";

@Override
public void onReceive(Context context, Intent intent) {
int volume = intent.getIntExtra(EXTRA_VOLUME,-1);
}
}

Następnym krokiem jest deklaracja naszej nowej klasy w pliku manifestu. Jeśli ten krok zostanie pominięty, to zdarzenie nie zadziała. Co gorsza nie dostaniemy nawet żadnej informacji o błędzie, bądź konieczności rejestracji klasy w manifeście!

<receiver android:name=".RingtoneSwitcher"></receiver>;

Ostatnim etapem jest zarejestrowanie naszej nowej klasy z użyciem AlarmManager’a.

public void setTimeAndAlarm(){
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);

alarmManager = (AlarmManager)getBaseContext().getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context,RingtoneSwitcher.class);
intent.putExtra("EXTRA_VOLUME",volumeValue);
PendingIntent alarmIntent = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
}

Rozbierzmy całość na poszczególne elementy i przeanalizujmy.

public void setTimeAndAlarm(){
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);

Zaczynając od góry metody wpierw pobieramy instancję obiektu typu Calendar za pomocą metody getInstance(). Następnie ustawiamy aktualny czas w milisekundach korzystając z zegara systemowego. Ostatnim etapem pracy nad kalendarzem jest ustawienie konkretnego momentu w czasie. W tym wypadku jest to godzina i minuta najbliższa obecnej chwili, jednak można ustawić również dzień tygodnia, miesiąc etc.

alarmManager = (AlarmManager)getBaseContext().getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context,RingtoneSwitcher.class); intent.putExtra("EXTRA_VOLUME",volumeValue);

Kolejnym etapem jest pobranie instancji klasy AlarmManager z kontekstu aplikacji. Następnie tworzymy nową intencję Intent z obecnego kontekstu, do klasy rozszerzającej BroadcastReceiver. Jak w przypadku każdej intencji można w niej zawrzeć dodatkowe dane do przekazania, na zasadzie pary klucz-wartość. W tym przypadku jest to wartość głośności dzwonka. Można również przekazywać obiekty implementujące interfejs Serializable.

PendingIntent alarmIntent = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);

Ostatnim i najistotniejszym fragmentem jest stworzenie obiektu oczekującej intencji PendingIntent . Warto zwrócić uwagę na dwa spośród parametrów metody tworzącej nowy obiekt:

  • id – jest to zmienna typu int przechowująca unikalny numer oczekującej intencji. W przypadku podania istniejącego już numeru otrzymujemy dostęp do istniejącej intencji.
  • PendingIntent.FLAG… – ustawienie szczegółów zachowania np. stworzenie obiektu typu immutable (FLAG_IMMUTABLE) lub nie tworzenie nowego obiektu jeśli już jeden istnieje, a jedynie aktualizacja jego parametrów (FLAG_UPDATE_CURRENT). W dokumentacji są całkiem ładnie opisane pozostałe typy.

alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);

Oraz jego zarejestrowanie za pomocą AlarmManager’a. W tym momencie mamy kilka metod:

  • setExact() – tworzenie pojedynczego zdarzenia rozpoczynającego się w dokładnie określonym czasie
  • setRepeating() – tworzenie zdarzenia powtarzalnego okresowo, rozpoczynającego się w dokładnie określonym czasie. Długość okresu nie może być niższa niż 60 sekund.
  • setInexactRepeating() – tworzenie zdarzenia powtarzalnego okresowo, rozpoczynającego się w przybliżonym stopniu, w określonym czasie. Ta opcja ma na celu oszczędność baterii, ponieważ system stara się zebrać kilka zdarzeń i uruchomić je w tym samym momencie. Od API w wersji 19 wszystkie zdarzenia powtarzalne są typu Inexact.

Przykład rejestracji zdarzenia powtarzalnego:


alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),AlarmManager.INTERVAL_DAY, alarmIntent);

Parametrami przekazywanymi do metody są:

  • AlarmManager.RTC_WAKEUP – typ alarmu powodujący uruchomienie intencji nawet jeśli telefon jest wygaszony (jest to opcja bardziej bateriożerna), bazujący na czasie systemowym UTC. Drugim głównym typem jest typ bazujący na czasie jaki upłynął od włączenia telefonu oraz możliwość nie wybudzania telefonu z powodu alarmu.
  • calendar.getTimeInMillis() – czas rozpoczęcia działania wyrażony w milisekunach
  • AlarmManager.INTERVAL_DAY – okres pomiędzy wywołaniem wyrażony w milisekundach. Kilka naturalnych okresów (dzień, godzina etc) jest z góry zdefiniowanych jako stałe.
  • alarmIntent – obiekt oczekującej intencji

Po uruchomieniu metody setTimeAndAlarm() zdarzenie powinno zostać zarejestrowane i będzie oczekiwało na ustawiony z góry moment wywołania. Mam nadzieje, że udało mi się nieco przybliżyć temat zdarzeń sterowanych czasem w środowisku Androida 🙂