Android: Zobrazovanie histórie volaní: content providery, kurzory a SimpleListAdapter

Dobre navrhnutá aplikácia obvykle pozostáva z troch vrstiev: používateľ obdivuje vrstvu používateľského rozhrania, to je prepojené s vrstvou biznis logiky, kde prebiehajú výpočty a zložité žonglovanie s dátami, ktoré prichádzajú z databázovej vrstvy. Samozrejme, v prípade jednoduchých aplikácií sa vrstvy miešajú, ale kvôli prehľadnosti a udržateľnosti je užitočné mať vyťahovanie a ukladanie dát v samostatných objektoch.

Content Providers

V Androide to veľmi ľahko možno dosiahnuť pomocou content providerov (doslova: poskytovateľov obsahu). Content Provider je objekt, ktorý v sebe zapúzdruje štruktúrované dáta (najčastejšie dáta v tabuľkách), a umožňuje k nim pristupovať pomocou prehľadnej a jednotnej filozofie.

Klasickým príkladom sú dáta v telefóne: kontakty, história volaní, obrázky, videá, zvuky: všetky tieto dáta sú sprístupnené pomocou samostatných content providerov.

Providery, ktoré sú zabudované v platforme, možno nájsť v balíčku android.provider, ale popravde, zoznam pôsobí bez znalosti celej filozofie pomerne chaoticky.

Content Provider pre históriu volaní

Nahliadnutím do zoznamu providerov (prípadne googlením či pátraním na stackoverflow.com) zistíme, že históriu volaní rieši provider CallLog.

Tabuľky

Každý provider sprístupňuje viacero tabuliek, ktoré sú reprezentované vnútornými triedami providera. V našom prípade máme k dispozícii len jednu, zodpovedajúcu triede CallLog.Calls. Ak si však pozriete providera k médiám, zistíte, že máte takmer tucet tabuliek, zodpovedajúcich rozličným typom multimediálnych súborov.

Content URI

Každá tabuľka má jednoznačný identifikátor v podobe URI adresy, tzv. Content URI, ktorý možno považovať za jej unikátne meno v systéme. Podľa konvencií je Content URI k dispozícii ako konštanta v triede tabuľky, čo je vidieť aj v našom prípade, kde máme konštantu CallLog.Calls.CONTENT_URI

Content Resolver a Cursor

Teraz, keď vieme, z ktorej tabuľky budú pochádzať dáta a poznáme jej identifikátor, môžeme pristúpiť k samotnému vyťahovaniu.

Hoci sme celý čas spomínali, že budeme pracovať s content providermi, v skutočnosti budeme musieť narábať s jeho prostredníkmi.

Content Resolver (pozor, nie Content Provider!) je objekt, ktorý na základe Content URI (teda na základe identifikátora tabuľky) a ďalších kritérii vráti tzv. kurzor. Pomocou neho sa môžeme posúvať po tabuľkových riadkoch a vyťahovať hodnoty z jednotlivých stĺpcov. Ak ste pracovali s klasickým JDBC, teda štandardným Java API pre prístup k databázam, môžete Cursor považovať za analógiu ResultSetu.

Ukážka kódu v aktivite:

Uri contentProviderUri = CallLog.Calls.CONTENT_URI; 
Cursor cursor = getContentResolver().query(contentProviderUri, null, null, null, null);

Každá aktivita má metódu getContentResolver() na získanie inštancie Content Resolvera. Metóda query() slúži na dopytovanie nad dátami a potrebuje päť parametrov: prvý predstavuje Content URI (= identifikátor) tabuľky a ďalšie štyri uvádzajú filtrovacie podmienky pre dáta, ktorým sa teraz nebudeme venovať. Ak namiesto nich uvedieme null, filtrovacie podmienky sa budú ignorovať.

Manažment kurzorov

Ak poznáme životný cyklus aktivity, a nuansy okolo odchádzania na pozadie a návratu na popredie, vieme, že udržiavať jej stav môže byť komplikované. To sa týka aj kurzora, ktorý tiež nesie stav. Našťastie, samotná aktivita sa môže sama postarať o všetku špinavú robotu: stačí zavolať metódu:

startManagingCursor(cursor);

a je to vybavené.

Priame získanie manažovaného kurzora

Namiesto dvojice getContentRquery() / startManagingCursor() môžeme volať managedQuery(...), ktorá má rovnaké parametre.

Pozor! API je zastaralé!

Dôležitá poznámka: celé API okolo manažmentu kurzorov je zastaralé: súvisí s ním množstvo problémov s výkonnosťou (volania tabuliek by mali bežať v separátnom vlákne atď). Vylepšené API dostupné v novších verziách Androidu spomenieme neskôr, pretože v tejto chvíli potrebujeme pochopiť základnú filozofiu fungovania, ktorá je i tak pomerne zložitá.

Zoznamové adaptéry pracujúce nad kurzormi

Kurzory sú natoľko častá technika dát, že ním bola venovaná špeciálna trieda pre adaptéry (teda modely) zoznamu. Používajú sa totiž nielen pri práci s content providermi, ale tiež pri SQL databázach.

Nazýva sa SimpleCursorAdapter a pri vytváraní potrebuje päť (resp. šesť parametrov):

ListAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, cursor, from, to);
  1. this predstavuje kontext, v tomto prípade vlastniacu aktivitu
  2. druhý parameter indikuje layout pre jednotlivé položky v zozname. V tomto prípade použijeme zabudovaný layout, kde položku tvorí jeden reťazec.
  3. kurzor pochádza z content resolvera
  4. pole názvov stĺpcov z tabuľky získanej z content resolvera, ktoré sa zobrazia v podpoložkách položky zoznamu (pozri nižšie)
  5. pole identifikátorov widgetov, na ktoré sa namapujú hodnoty zo stĺpcov tabuľky.

Štvrtý a piaty parameter si ukážme na príklade:

Z tabuľky histórie volaní sa rozhodneme zobrazovať len posledné telefónne číslo, čomu zodpovedá stĺpec CallLog.Calls.NUMBER (zistili sme to z dokumentácie k tabuľke Calls v content provideri CallLog). Pole from teda bude obsahovať len jeden prvok s názvom stĺpca:

String[] from = { CallLog.Calls.NUMBER };

Layout pre celú položku definovaný v druhom parametri, ktorý je v tomto prípade zabudovaný androidovský layout android.R.layout.simple_list_item_1, obsahuje len jeden widget, a to s identifikátorom android.R.id.text1. Pole to bude teda vyzerať:

int[] to = { android.R.id.text1 };

Na prvý prvok z poľa from sa teda bude mapovať widget z prvého prvku poľa to. Ak by widgetov v to bolo viac, tak i-ty prvok z poľa from sa bude mapovať na i-ty widget z poľa to.

Ak máme hotový adaptér pre zoznamy, stačí ho asociovať so zoznamovou aktivitou cez známe:

setListAdapter(adapter);

Celý kód tak vyzerá:

public class MainActivity extends ListActivity {
    private static final String[] NO_PROJECTION = null;

    private static final String NO_SELECTION = null;

    private static final String[] NO_SELECTION_ARGS = null;

    private static final String NO_SORT_ORDER = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String[] from = { CallLog.Calls.NUMBER };
        int[] to = { android.R.id.text1 };

        Uri contentProviderUri = CallLog.Calls.CONTENT_URI;

        Cursor cursor = getContentResolver().query(contentProviderUri, NO_PROJECTION, NO_SELECTION, NO_SELECTION_ARGS, NO_SORT_ORDER);
        startManagingCursor(cursor);

        ListAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, cursor, from, to);
        setListAdapter(adapter);
    }

}

Spustenie aktivity

Spustenie aplikácie ešte nebude úspešné. V LogCate sa totiž zjaví chyba:

java.lang.SecurityException: Permission Denial: opening provider com.company.contentprovider.AplicacaoContentProvider requires READ_CONTACTS or WRITE_CONTACTS.

Čítanie histórie volaní je totiž citlivá záležitosť (určite nechcete, aby aplikácia zistila zoznam čísiel a odoslala ho na pochybná server) a preto treba explicitne nastaviť oprávnenia, ktoré ho umožnia.

Otvorte AndroidManifest.xml a na karte Permissions pomocou tlačidla Add… pridajte nové oprávnenie. Zvoľte Uses Permission a pridajte oprávnenie android.permission.READ_CONTACTS.

Ak náhodou uvidíte prázdny zoznam, je to preto, že ste vo vašom emulátore neuskutočnili žiadne volania. Nebojte sa použiť zabudovanú aplikáciu Dialer a vytočiť na skúšku ľubovoľné čísla. Ak sa vrátite do vašej aplikácie, uvidíte za každé odchádzajúce (ale aj prichádzajúce) číslo jednu položku v zozname.

Výsledný výzor

Výsledný projekt

Stiahnite si celý projekt: CallLogBrowser

Pridaj komentár

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