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 ResultSet
u.
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);
this
predstavuje kontext, v tomto prípade vlastniacu aktivitu- 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.
- kurzor pochádza z content resolvera
- 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)
- 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