Vzorová aplikácia
Vytvorme aplikáciu, ktorá ukazuje zoznam ľudí prítomných v miestnosti. Majme pripravené REST API, ktoré umožní oznámiť prítomnosť a ukázať zoznam ľudí. Zoznam ľudí nech sa periodicky aktualizuje.
Výsledok
- androidová časť na Githube
- REST API na Githube implementované v Spring Boot
Koncepty, ktoré zvládneme
IntentService
využívajúce notifikácie- notifikácie používateľa
PendingIntent
y ako nositelia informácií pre úmysel odoslaný niekedy v budúcnosti- periodické spúšťanie služieb cez
AlarmManager
- počúvanie na štart systému
- používateľské nastavenia aplikácie (preferences) a ich úprava
Prípravné práce
- potrebujeme mať zverejnené REST API (v samostatnom projekte)
- vytvoríme triedu pre používateľa
User
- trieda musí implementovať
java.io.Serializable
, aby sme ju mohli posielať v intentoch
- trieda musí implementovať
IntentService
a notifikácie
Plán práce
- vytvoríme vlastný
IntentService
- v jeho
onHandleIntent()
kontaktujeme server cezURL
a naparsujeme používateľov do zoznamu používateľov - vytvoríme notifikáciu
Inšpiračná literatúra
Pripomienky
- pripomienky: nezabudneme získať právo internetu (
uses-permission
)
Kód
public static final String EXTRA_SUPPRESS_TRIGGER_NOTIFICATION = "suppressTriggerNotification";
@Override
protected void onHandleIntent(Intent intent) {
try {
URL url = new URL(ENDPOINT_URL);
InputStream in = url.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line;
List<User> users = new LinkedList<User>();
while((line = reader.readLine()) != null) {
User user = parseUser(line);
if(user != null) {
users.add(user);
}
}
if(! intent.hasExtra(SUPPRESS_EXTRA_TRIGGER_NOTIFICATION)) {
notify(users);
}
} catch (IOException e) {
Log.e(TAG, "Cannot download available users", e);
}
}
- ošetríme špeciálny prípad: ak zavoláme intent s extra parametrom v konštante
EXTRA_SUPPRESS_TRIGGER_NOTIFICATION
, potlačíme vyvolanie notifikácie.- slúži to na oddelenie obsluhy vyvolanej z budúceho periodického spúšťania od ručnej aktualizácie
Deklarácia v manifeste
<service
android:name=".FetchOnlineUsersIntentService"
android:enabled="true" />
Ručné spustenie IntentService
- do kontextového menu aktivity dodáme novú položku Refresh.
aktualizujeme
res/menu/main.xml
<item android:id="@+id/action_refresh" android:title="@string/action_refresh"/>
dodajme obsluhu do
˙onOptionsItemSelected()
a presmerujme to do vlastnej metódyprivate void onRefreshOptionItemClick() { Intent service = new Intent(this, FetchOnlineUsersIntentService.class); startService(service); }
Periodické spúštanie
AlarmManager
- Ak chceme spúšťať periodické operácie i v čase, keď aplikácia nebeží, použijeme
android.app.AlarmManager
- operačný systém optimalizuje spúšťanie
- ak chceme periodicky spúšťať udalosti vo vnútri aplikácie, použime radšej
Handler
y
Implementácia v projekte
V našej službe urobme pomocnú statickú metódu na naplánovanie úlohy.
public static void schedule(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE);
Intent intent = new Intent(context, PresentrIntentService.class);
PendingIntent pendingIntent = PendingIntent.getService(context, SERVICE_REQUEST_CODE, intent, NO_FLAGS);
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime(), 7 * 1000, pendingIntent);
}
AlarmManager
je systémová služba- získame ju z kontextu cez
getSystemService()
a konštantuALARM_SERVICE
- získame ju z kontextu cez
- udalosť, ktorá sa má spustiť, je u nás služba
- vytvoríme teda
PendingIntent
, kde uvedieme:- kontext
- kód požiadavky (request code) — ak ho nepoužívame, pošleme napr. nulu
- intent charakterizujúci objekt, ktorý sa má spustiť: u nás je ním služba
- príznaky (flags), používané pri odstraňovaní či modifikácii pending intentu, môžeme použiť vlastnú konštantu
NO_FLAGS
rovnú nule.
- metódou
set()
nastavíme plánovanie:- prvý parameter uvádza časovú škálu:
ELAPSED_REALTIME
uvádza počet milisekúnd od bootu, vrátane času, keď zariadenie spalo. Ak interval uplynie a mobil spí, alarm sa odignoruje.
- druhý parameter udáva čas prvého spustenia
- tretí parameter udáva periodicitu v milisekudných
- štvrtý parameter udáva pending intent, ktorý sa má spustiť pri tiku
- prvý parameter uvádza časovú škálu:
Bonusovka: rušenie periodicity
- ak chceme zrušiť periodicitu, musíme si zapamätať pending intent, ktorým sme nastavili plánovanie
- metódou
cancel()
naAlarmManager
i zrušíme plánovanie
Počúvanie na štart systému
- Ak chceme reagovať na spustenie systému a naplánovať automatickú aktualizáciu, využijeme vlastný broadcast receiver.
- Budeme reagovať na systémový intent s akciou
android.intent.action.BOOT_COMPLETED
Kód
Vytvoríme vlastnú triedu:
package sk.upjs.ics.android.presentr;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class BootCompletedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
PresentrIntentService.schedule(context);
}
}
V kóde len zavoláme metódu pre naplánovanie periodické spúšťania.
Konfigurácia receivera
Konfigurácia v manifeste
- v predošlom príklade z minula sme konfigurovali receiver v kóde.
- vtedy však bolo potrebné aktualizovať GUI
tuto GUI nepotrebujeme, receiver môžeme deklarovať v manifeste (v rámci elementu
<application>
)<receiver android:name="sk.upjs.ics.android.presentr.BootCompletedReceiver" android:enabled="true" android:exported="true" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver>
Konfigurácia filtra intentov
- nezabudneme nadeklarovať filter pre intenty
- využijeme XML deklaráciu
<intent-filter>a
- deklarujeme filtrovanie pre príslušnú akciu
Obmedzenia pre boot receivery
- aplikácie, ktoré sú umiestnené na externom úložisku, nedostávajú notifikáciu o dokončení štartu
- viď dokumentácia obsahujúca tiež naratívny exkurz smerom k dvom druhom “pevného disku” v Androidoch
Používateľské nastavenia aplikácie (preferences)
Preferencie (preferences) predstavujú používateľské prispôsobenia či nastavenia možností aplikácie. Android poskytuje API, ktoré nás ušetrí ukladania do databázy, registrov, či konfiguračného súboru a zároveň dáva k dispozícii jednoduchý spôsob ich zobrazenia a zmeny v používateľskom rozhraní.
Preferencie si z hľadiska programu môžeme predstaviť ako hashmapu s reťazcovými kľúčmi.
Plán práce
- vytvorme definičný súbor s layoutom
- vytvorme aktivitu preferencií oddedením od
PreferencesActivity
- zavolajme ju z hlavnej aktivity a počkajme na výsledok
Definičný súbor s layoutom
- layout aktivity preferencií sa riadi špeciálnym formátom
- layout vytvorme napr. použitím File | New | Android > Android XML File
- z rozbaľovadla vyberme Resource Type: Preference
- súbor sa obvykle volá
preferences.xml
a objaví sa vres/xml/preferences.xml
Ukážkový kód
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<EditTextPreference
android:title="Meno používateľa"
android:key="user"
/>
<CheckBoxPreference
android:title="Periodicky aktualizovať"
android:key="isPeriodicallyUpdating"
/>
<EditTextPreference
android:title="Adresa servera"
android:key="serverUrl"
/>
</PreferenceScreen>
Vytvorenie aktivity preferencií
- vytvorme novú aktivitu
- oddeďme od
android.preference.PreferenceActivity
- prekryme metódu
onCreate()
- zavolajme metódu
addPreferencesFromResource
, kde sa odkážeme na layoutpreferences.xml
Poznámky
- nasledovný kód platí pre Android 2.x
- novšie Androidy umožňujú využitie fragmentov, ale o tých niekedy v budúcnosti
- zatiaľ budeme používať deprecated API
Kód
package sk.upjs.ics.android.presentr;
import android.os.Bundle;
public class PreferencesActivity extends android.preference.PreferenceActivity {
@SuppressWarnings("deprecation")
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
Zobrazenie preferenčnej aktivity
- preferenčnú aktivitu zavoláme z hlavnej aktivity cez
startActivityForResult()
- v hlavnej aktivite s výhodou využijeme predpripravené menu pre Settings
Čakanie na výsledok
- použijeme
startActivityForResult()
, - a počkáme na výsledok v
onActivityResult()
Kód
private void onSettingsOptionItemClick() {
Intent showPreferencesIntent = new Intent(this, PreferencesActivity.class);
startActivityForResult(showPreferencesIntent, REQUEST_CODE_SHOW_PREFERENCES);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_SHOW_PREFERENCES:
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
Bonusovka
- ak chceme pod názvom položky preferencie zobraziť aj jej hodnotu, použijeme trik
- preferenčná aktivita nech implementuje
OnSharedPreferenceChangeListener
- implementujeme metódu
onSharedPreferenceChanged()
volanej pri zmene hodnoty preferencie - tam vyzdvihneme položku a nastavíme jej sumár
- nezabudneme odregistrovať listenera v
onPause()
a zaregistrovať ho v `onPause()˙ - sumáry musíme inicializovať pri štarte aktivity: metóda
onSharedPreferenceChanged()
sa totiž volá len pri zmene metódu zavoláme ručne pre každý kľúč preferencií zvlášť
private void initializeSummaries() { SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences(); for(String key : sharedPreferences.getAll().keySet()) { onSharedPreferenceChanged(sharedPreferences, key); } }
Bonusovka: správa zoznamu
- hlavná aktivita môže dediť od ListActivity
- service nech broadcastuje intent…
- …ktorý je odchytávaný aktivitou
- v extra intentu posielame zoznam používateľov
- hlavná aktivita môže mať
ArrayAdapter
, ktorému nastavíme tento zoznam
Bonusovka: klik na prázdny zoznam vyvolá refresh
Deklarujeme vlastný layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<ListView android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawSelectorOnTop="false"/>
<TextView android:id="@android:id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:text="Tap to get available users"
android:clickable="true"
android:onClick="onEmptyListViewClick"
/>
</RelativeLayout>
V našej hlavnej aktivite potom klasickým spôsobom získame layout
setContentView(R.layout.activity_main);
Rodičovská implementácia aktivity ListActivity
potom sama použije listview i textview pre prázdny zoznam. Nezabudneme zapnúť klikateľnost a zaviesť metódu po kliknutí.