180 minút s Androidom: 9. stretnutie [Online prezenčka]

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

Koncepty, ktoré zvládneme

  • IntentService využívajúce notifikácie
  • notifikácie používateľa
  • PendingIntenty 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

IntentService a notifikácie

Plán práce

  1. vytvoríme vlastný IntentService
  2. v jeho onHandleIntent() kontaktujeme server cez URL a naparsujeme používateľov do zoznamu používateľov
  3. 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ódy

    private 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 Handlery

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štantu ALARM_SERVICE
  • 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

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() na AlarmManageri 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

  1. vytvorme definičný súbor s layoutom
  2. vytvorme aktivitu preferencií oddedením od PreferencesActivity
  3. 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 v res/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 layout preferences.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í.

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *