Jelikož Android se vyvíjí a existuje v mnoha verzích, po spuštění studia je zapotřebí doinstalovat API pro konkrétní verzi (či verze pro které míníme vývoj provádět). K údržbě těchto součástí studia najdeme v menu Tools položku SDK Manager.
Další věcí, kterou budeme potřebovat a kterou musíme instalovat zvlášť, je emulátor mobilního telefonu. O toto se stará AVD Manager, který je hned vedlejší položkou v menu Tools.
V AVD Manageru je třeba nastavit, na jakém z dostupných emulátorů
telefonů chcete kód ladit a v jaké verzi Androidu. Při výběru (po
kliknutí na Create Virtual Device) myslete na to, že celé
studio je výpočetně velmi náročné a je tedy třeba konfiguraci
přizpůsobit schopnostem hardwaru, na kterém budeme pracovat. Android běží na procesorech intelské architektury (x86), nebo na procesorech ARM. Pro oboje jsou k dispozici emulatory.
Pro našince je přirozenější na x86 spustit emulator x86. Tyto procesory totiž mají již jistou dobu podporu virtualizace, takže se emulace dá touto virtualizací výrazně urychlit (kód běží přímo na našem hardwaru jen poněkud omezeně). Ovšem ke zprovoznění emulatoru x86 na x86 může být potřeba ve Windows v nastavení Turn Windows features on and off zakliknout Virtual Machine Platform, nebo naopak vykliknout Hyper-V (které ovšem našinec na Windows Home Edition nemá, mají jej jen zbohatlíci na Windows Professional edition). Bez tohoto nastavení nemusí jít zprovoznit HAXM (virtualizátor, který výrazně urychluje emulaci). Bez HAXM nelze spustit emulator x86 a je nutno spoléhat na emulator ARMu, který sice HAXM nepotřebuje, nemůže však využít virtualizaci. Napoprvé startuje kolem půl hodiny a při dalších spuštěních to není o mnoho lepší.
Vidíme, že jde o emulator telefonu s Androidem se vším všudy (ale bez SIM-karty).
Pokud se při běhu Studia ozývá soustředěné chrastění disku a nepřichází delší dobu žádná reakce ani na Alt+Ctrl+Del, došla paměť (RAM) a systém tráví většinu času swapováním paměti na disk. Zprovozňovat toto vývojové prostředí může mít smysl s alespoň 4 GB RAM, kde je ovšem již za pomezím praktické použitelnosti, na 8GB systému již prostředí je použitelné. Došla-li paměť a systém swapuje, je vhodné pozavírat zbytečně běžící procesy a případně prostředí trochu přenastavit. Je možné povypínat integraci s verzovacími systémy (File -> Settings -> Plugins a tam tabu Installed odklikat položky Git Integration, GitHub, Google Cloud Tools For Android Studio, Firebase Testing, Google Cloud Tools Core a Subversion. Dále je možné nastavit si úspornější telefon (třeba Nexus One s pamětí 0,5 GB namísto Pixelu 2 s 1 nebo 2 GB). Dále je možné zkusit ještě v této konfiguraci ušetřit (v AVD Manageru klikneme na ikonu tužtičky, tam si necháme zobrazit Advanced Settings, zrušíme SD-kartu, zmenšíme interní storage a především zmenšíme paměť RAM). Při tomto šetření je však třeba myslet na to, že se nám do emulovaného prostředí musí vejít to, co chceme ladit, a to včetně operačního systému. Android je operační systém stojící na Linuxovém kernelu a dobře víme, co tento kernel udělá s procesy, kterým dojde paměť!
Otravné je, že emulace běží i na rychlých strojích tak pomalu, že watchdog občas zpozoruje, že některý důležitý proces (jako třeba uživatelský interface) během 15 sekund nereaguje. Na to upozorní a ptá se, jestli má neodpovídající proces zabít. Někdy (v případě našich vlastních zaseknutých procesů) je to užitečné. Mordovat uživatelský interface však úplně chytré není.
Nahrajeme-li nějakou aplikaci do emulatoru, reaguje emulator jako obyčejný telefon, tedy aplikaci instaluje do persistentní paměti a aplikace tam pak zůstávají. Zde například vidíte na prvním obrázku aplikaci My Application 3 (nahranou z Android Studia), ke které na dalším obrázku přibyla ještě aplikace vytvořená ve Visual Studiu (jmenující se AndroidApp1):
Již od nepaměti je toto studio slušně děravé. Dříve nefungoval designer formulářů a jednotlivé formuláře bylo nutné psát ručně. Ostatně při výpočetním výkonu, který tento designer požaduje, bývá dosud lepší na něj nespoléhat a formuláře si připravovat ručně v XML. Poslední dobou Studio zase má dojem, že i nejnovější verze emulátoru je příliš zastaralá (toto hlášení je zase potřeba ignorovat). Od chvíle, kdy si necháte vygenerovat první projekt, do chvíle, kdy vám začne být fungování studia přiměřeně jasné, prosím, nic neupdatujte (i když vás k tomu Studio bude v jednom kuse vybízet). Po updatu se totiž obvykle změní struktura projektu, a dokud vám nebude fungování dostatečně jasné, starý projekt v nové verzi Studia nezprovozníte.
Samotný vývoj probíhá poměrně standardním způsobem známým například z Mircosoft Visual Studia .NET. Tedy naklikáme si nastavení projektu. Tímto procesem necháme vygenerovat množství XML souborů projekt popisujících a také obvykle alespoň jeden soubor zdrojového textu. Jako programovací jazyky lze použít Kotlin nebo Javu. Při výběru projektu se pokuste vybrat "Basic Activity" (alespoň do chvíle, kdy tato bude přejmenována). My se soustředíme na Javu (tedy při výběru nevybírejte projekt v Kotlinu), ačkoliv dostupné údaje naznačují, že společnost Google chce nadále výrazně více protlačovat Kotlin. Ten je jejich vlastním jazykem, kdežto o Javu se s nimi průběžně soudí společnost Oracle (která Javu získala anexí společnosti SUN Microsystems). Java má však pro nás výhodu, že se syntaxí velmi podobná jazyku C#, který již znáte a bylo tedy možno probrat ji v minulé kapitole velmi rychle, kdežto Kotlin by vyžadoval důkladnější výklad.
Naklikání projektu probíhá způsobem velmi podobným tomu, který znáte z Visual Studia. Předpokládáme tedy, že si s tím poradíte (při využití pokynů z minulého odstavce).
main
. Toto nyní není
pravda. Aplikace pro Android je reprezentována třídou, která dědí od
(alespoň dříve) abstraktní třídy Activity. S postupujícím vývojem
vzniklo množství dalších tříd (dědících od Activity), například
TabActivity, která má usnadnit implementaci programu zobrazujícího
několik tabů.
Svět komunikuje s třídou Activity
základním způsobem pomocí 6 metod (zvaných callbacky, jelikož tyto metody jsou vyvolávány příslušnými událostmi). Jde o funkce onCreate, onStart, onResume, onPause, onStop
a onDestroy.
Jednotlivé funkce se vyvolají při příchodu dotyčné události.
Nás zde bude především zajímat funkce onCreate
, která se
vyvolá po startu dotyčné aktivity. V té je třeba zajistit zobrazení
vhodného formuláře a vůbec provést celkovou inicializaci programu, tedy
například ponastavovat ovladače událostí týkajících se zobrazovaného
formuláře (především pokud jsme k návrhu formuláře nepoužili klikací
editor - ostatně v tom případě právě v této funkci provedeme osazení
formuláře zobrazovaného při startu aplikace. Až definujete funkci onCreate
a vytvoříte jednoduchý program komunikující s uživatelem, povšimněte
si, že se nám budou porůznu ztrácet údaje při různých událostech
(například při obyčejném otočení displaye). To je proto, že jsme
nedefinovali funkce OnPause
a onResume
. Ačkoliv my se zde soustředíme jen na funkci onCreate
(protože ostatní zde jmenované funkce pracují podobně), myslete na to,
že i ostatní funkce jsou důležité (má-li se Vaše aplikace používat
příjemně).
Funkce onCreate
přijímá jako parametr uložený stav instance jako (datový) typ Bundle
. Jako první krok v této funkci musíme zavolat metodu onCreate
v naší rodičovské třídě (Activity
). Ze C# pro tyto účely znáte klíčové slovo base
. V Javě se však místo něj používá klíčové slovo super
.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }
Takto přibližně vypadá implicitně vygenerovaná funkce onCreate
(necháme-li si vygenerovat prázdnou aplikaci).
Nahoře vidíme anotaci, která říká, že overridujeme (virtuální) funkci v rodiči.
Za hlavičkou funkce voláme rodičovskou funkci onCreate
.
Na posledním řádku nastavujeme, co se má zobrazit na displayi při startu.
Při experimentování s uvedenými příklady dávejte určitý pozor (alespoň do doby, kdy vám začne být fungování prostředí jasné). Konkrétně z vygenerovaných zdrojových textů neodstraňujte žádné funkce, i když v našem příkladu tyto funkce nejsou a to ani když ve zdrojovém kódu nevidíte žádné volání těchto funkcí. Nejspíše jsou nastavené jako ovladače nějaké události nastavené v XML popisu formuláře. Pokud byste takovou funkci smazali, bude se po ní překladač při nejbližší kompilaci shánět. A ještě přesněji, při své odladěnosti může Android Studio dokonce nějakou chvíli fungovat i bez dotyčné funkce (než zpozoruje, že se zdrojáky změnily a než je rekompiluje).
Povšimněte si, že k prvkům formuláře zde přistupujeme úplně jinak, nežli
v Microsoft Visual Studiu. Tam existovaly proměnné s příslušným jménem.
Společnost Google však jde na věc úplně jinak. Údaje o prvcích
formulářů, které jsme naklikali v builderu, jsou dostupné v resourcech
pod jménem R
a tam konkrétně v prvku layout
. Prvek activity_main
odkazuje k souboru activity_main.xml
, kde je XML-ský popis zobrazovaného formuláře. A hned si začneme všímat rozdílů oproti tomu, co jsme dosud znali.
Formulář zobrazovaný Androidem je navržený jednoduše. Sestává z layoutu, na kterém jsou jednotlivé prvky (které již znáte). Tento layout, přesněji tyto layouty, si nyní trochu objasníme. Základním layoutem je lineární layout. Ten je význačný tím, že prvky na něm obsažené skládá jeden za druhý (v tom pořadí, v jakém je obsahuje). Lineární layout může být vertikální (pak skládáme prvky nad sebe) nebo horizontální (vedle sebe). Další layouty nám umožňují například zobrazovat taby či scrollovat. Dalším layoutem, u kterého se zastavíme, je scrollovací layout. Ten je zvláštní tím, že smí obsahovat pouze jeden prvek. Proto se do scrollovacího layoutu dává layout lineární (který umí obsáhnout více prvků, což je obvykle důvodem, proč potřebujeme s layoutem scrollovat). Pro začátek tedy vystačíte s lineárním layoutem. Další layouty především posilují možnosti rozmisťování prvků po formuláři. Nastudujete je, až budete potřebovat napsat reprezentativněji vyhlížející aplikaci.
Příklad:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".MainActivity" tools:showIn="@layout/activity_main"> <TextView android:id="@+id/textovepole" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>Všimněte si, že používáme právě to XML, o kterém jste se nedávno učili. V příkladu máme constraintový layout, který je nyní obzvlášť v módě. Tam prvky rozmísťujeme s využitím omezení, tedy řekneme, které prvky k sobě mají přibližně přiléhat a celkový vzhled vypadá, jako by se dotyčná místa pospojovala pružinkami, které se pokoušejí mít správnou délku, a tedy za prvky na layoutu tahají, až jim přidělí místo v pozici, která odpovídá minimálnímu pnutí v systému.
Společnost Google má trochu jinou politiku správy zdrojových textů nežli Mircosoft. Namísto importu všeho užitečného hned na začátku je člověk průběžně dotazován při použití konkrétních tříd (EditText, Calendar, TextView
). Zpravidla stačí stisknout
Alt+Enter
k naimportování správného balíčku.
Patrně jste si již vyzkoušeli, že i takto poměrně prázdný projekt lze zkompilovat a spustit. K tomu je třeba nastavit, na čem se má aplikace spustit (tedy musíte vybrat ten emulator, který jste si v AVD-manageru připravili (v horní části okna). Hned vedle tohoto nastavení se nachází zelená šipka známá z magnetofonů jako piktogram pro funkci hraj. Tím program spustíte (a případně předtím zkompilujete). Nedebugujeme však ikonou play, ale ikonou brouka (až nám program nebude fungovat). V prostředí si všimněte množství poschovávaných tabů reprezentovaných nápisy po okraji. Až budete ladit, debugger se vám rozhodně sám nezobrazí. Ale na dolní liště uvidíte nápis Debug a že už se debuguje, vás upozorní zelený puntík u tohot nápisu. Při ladění počítejte s tím, že do hlavy a do mobilu je dost špatně vidět, takže je vhodné používat debugovací výstupy a ladit inkrementálně, tedy omezit množství prováděného kódu na možné minimum (kdy aplikace přestane padat) a kód postupně přidávat (a tak zjistíte, kde padá).
Start je tu opravdu pomalý, protože kromě kompilace a spuštění aplikace je třeba spustit celý emulator androidu (a do něj aplikaci nahrát). Start tohoto nelidsky pomalého prostředí je implicitně podle možností urychlen. Implicitně zkusí emulator napříště nastartovat do stavu uloženého současném ukončení emulace. Tak celý Android nestartuje pokaždé znovu, zato pokaždé při vypínání stav ukládá (a při startu načítá). Toto nastavení sice není úplně pitomé, ale ve chvíli, kdy se emulace zřítí (tedy emulator vytuhne), je třeba aktivní zásah k obnovení funkčnosti. Tento zásah vypadá tak, že v AVD-Manageru klikneme na další akce a řekneme Wipe User Data. Tím zlikvidujeme všechna data, co si Android dosud spočítal a napříště začneme studeným startem (tedy tím, co sice trvá, ale častěji se povede). Taktéž v takovém případě můžeme v dalších akcích objednat napříště studený start.
Všeobecné vlastnosti jazyku Java již znáte (tak víte, jak se pracuje s proměnnými, definují a volají funkce, jaké jsou základní řídicí struktury,...). Zbývá tedy vstup a výstup. Tedy jak načíst data, vypsat data (což je obvykle obdobné) a především jak vyvolat metody, které mají příslušnou akci provést. Těmito metodami se budeme zabývat napřed, protože musíme začít drobným krokem stranou.
Při tvorbě aplikací pro Android se používá událostmi řízené
programování. To vypadá tak, že aplikace se z hlediska programátora jeví
jako nečinná, dokud nepřijde nějaká událost, o kterou jsme si řekli, že
chceme obsloužit. Že chceme dotyčnou událost obsloužit, jsme sdělili
překladači tak, že jsme nějakou funkci nastavili jako ovladač dotyčné
události, případně jsme o nějaké třídě řekli, že dotyčnou událost umí
zpracovat. Klasickým přístupem v Javě je ten druhý (protože v Javě
nejsou pointery). Povšimněte si, že událostmi řízené programování jsme
již viděli v úvodu, protože metody onCreate, onStart,... onDestroy
jsou přesně představitelé
ovladačů událostí. Aby bylo možno obsloužit událost, musíme typicky pro
prvek, kterého se událost týká, stanovit, která třída tuto událost umí
obsloužit. Pro konkrétní události jsou stanovena jména metod, které se
při této události vyvolají. Podobný přístup již znáte z Tvorby webu, kde
stránka také nic nedělala do chvíle, kdy přišla událost, na kterou jsme
pověsili Javascriptový ovladač. Způsob fungování je tedy stejný, jiné
je jen technické provedení.
Chceme-li definovat ovladač nějaké události klasickým Javovým způsobem,
musíme definovat třídu, která implementuje stanovený interface.
Interfacy jsou náhražkou násobné dědičnosti (která v Javě neexistuje).
Interface říká, jaké funkce musí třída implementovat, aby ji bylo možno
použít k žádanému účelu. V Javě se interfacy implementují, tedy za název
třídy (a případný údaj o předkovi) přidáme klíčové slovo implements
a
jméno interfacu, o kterém chceme slíbit, že jej třída implementuje.
Tedy například: public class MainActivity extends AppCompatActivity implements View.OnClickListener
. Následuje definice třídy (ve složených závorkách). Třída implementující OnClickListener
dovede zpracovávat události týkající se kliknutí (například na
tlačítko). Tato třída pak musí overridovat metodu onClick
asi takto:
@Override public void onClick(View v) {...}
Metoda onClick
je vyvolána, kdykoliv klikneme myší na prvek, pro
který je jako tento ovladač příslušná třída definována. Abychom dotyčnou
třídu (přesněji jednotlivé její instance) nastavili jako ovladač
dotyčné události, nastavíme dotyčný listener. Tedy máme-li například v
proměnné B
odkaz na tlačítko (o kterém si v dalších
odstavcích řekneme, jak jej získat) a jsme-li funkce ve třídě dotyčný
listener implementující, jen tlačítku zavoláme metodu setOnClickListener
asi takto:
B.setOnClickListener(this);
Kdybychom chtěli nastavit jinou třídu (nežli sebe), museli bychom si vytvořit instanci dotyčné třídy asi takto:
B.setOnClickListener(new ovladaci_trida());
Alternativou tomuto postupu je, pokud prvky klikáme do formuláře v XML, rovnou popsat, která funkce je ovladačem které události. Tento popis píšeme rovnou do XML-souboru, ve kterém je popis dotyčného formuláře:
<Button android:id="@+id/cudlik" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Jen si zaklikej!" android:onClick="ovladac_udalosti" />Funkci
ovladac_udalosti
přijímající jeden argument typu View
pak definujeme v hlavní třídě naší aktivity.
K těmto ovladačům chybí už jen drobnost: Co znamená argument typu View
? Jde o údaj umožňující nám zjistit, kdo událost vyvolal. Třída View
je abstraktní třída, která je společným rodičem všech nakreslitelných
(a tedy vyjevitelných) prvků (textové políčko, tlačítko,...). Tato
rodičovská třída vynucuje, aby šlo o prvky, které lze nakreslit. Různé
prvky se tedy kreslí různě (stromeček se kreslí jinak nežli domeček,
tlačítko se kreslí jinak nežli textové políčko), ale kliká se na ně
stejně.
V našem rychlokurzu zbývá už opravdu poslední (leč velmi důležitá) věc:
Vstup a výstup. Jak již bylo naznačeno, k prvkům naklikaným na formuláři
přistupujeme jinak nežli jsme byli zvyklí z Visual Studia. Zde se k
prvkům dostaneme přes tzv. ID, které najdeme v R.id...
. Toto ID jsme již definovali v příkladu tlačítka. Tam si povšimněte atributu android:id="@+id/cudlik"
. Tento atribut jsme takto divně nenastavovali z dlouhé chvíle, ale proto, abychom se k dotyčnému tlačítku dostali pomocí ID R.id.cudlik
.
Tedy platí, že chceme-li k prvku naklikanému na formulář přistoupit,
musíme mu nastavit ID na @+id/
a za lomítko napsat ID, pod kterým
budeme k prvku přistupovat.
Předposlední položkou, která nám chybí, je, jak s těmito ID pracovat. To
je tentokrát nečekaně snadné. K nalezení dotyčných prvků slouží funkce findViewById
, které dotyčné ID (tedy ve formátu R.id.samotne_id
předáme). Výsledek volání následně zkonvertujeme na požadovaný typ
(jakého předmětné políčko je). Přiřadíme-li výsledek této operace,
dostáváme se do podobného stavu, v jakém jsme ve Visual Studiu hned na
začátku. Tedy například:
Button button1=(Button)findViewById(R.id.cudlik);nám umožní nadále odkazovat k dotyčnému tlačítku pod jménem
button1
.
Jelikož samotný vstup a výstup je zpravidla řešen týmž atributem, či intuitivně se jmenujícím párem funkcí, stačí už snad nyní říci, jak se některé tyto atributy či funkce jmenují (a už budeme schopni tvořit aplikace pro Android bez omezení).
Prvek | Datový typ | Hodnota |
---|---|---|
Textové políčko | EditText | getText()/setText(String)* |
Vypsaný text (label) | TextView | getText()/setText(String)* |
Zaklikávátko (checkbox) | CheckBox | isChecked()/setChecked(Boolean) |
Výběr z možností (radiobutton) | RadioGroup | getCheckedRadioButtonId()/setChecked()** |
Závěrem je možno snad už jen podotknout, že na konkrétní
specializované úkoly jsou knihovny, ve kterých jsou třídy i s metodami.
Tyto metody budeme volat, abychom se dobrali konkrétních
specializovaných úkolů. S pomocí těchto knihoven tedy můžeme komunikovat
po síti (HttpURLConnection
), pracuje se SQLite databází
(SQLiteDatabase
) a podobně.
Chceme-li udělat téměř cokoliv netriviálního, potřebujeme k tomu práva (jinak příslušná operace spadne s výjimkou). Práva potřebujeme i k úkonům na pohled banálním (otevření souboru na microSD-kartě, stažení dat z webu). Ono to ale dává smysl. Mobilní telefony bývají z podstaty věci připojené k Internetu a data v nich bývají zpravidla citlivá. Poslat si HTTP-request je také na pohled neškodné - dokud do URI jako parametry skriptu třeba nepošleme současné GPS-souřadnice. Že budeme tato práva používat, musíme deklarovat v Manifestu (soubor AndroidManifest.xml
), kde je projekt popsán (co se z něj bude spouštět, jak se to bude jmenovat, jaká práva potřebujeme,...). Tam například přidáme:
<uses-permission android:name="android.permission.INTERNET"/>
chceme-li provést HTTP-request (syntax je tedy jasná, prvek se jmenuje uses-permission,
atribut name
ve jmenném prostoru Android
říká, jaká práva chceme získat). Chceme-li HTTP konekci (ne HTTPS), musíme ještě do manifestu v tagu application
nastavit atribut android:usesCleartextTraffic="true"
.
Ačkoliv o vývoji aplikací pro Android v Android Studiu by bylo možno napsat ještě plno dalších informací, tohle je v tuto chvíli všechno. Následují příklady a ukázky.
Příklad 1:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); }Takto vypadá implicitně vygenerovaná funkce
onCreate
v Basic Activity. Na začátku vidíme zavolání funkce onCreate
v rodiči. Na dalším řádku nastavujeme, co se má zobrazit. Na třetím řádku uchopíme Toolbar. Nás ovšem bude zajímat především situace o 3 řádky dále. Povšimněme si, že tam napřed (jen o dva neprázdné řádky dále) uchopíme FloatingActionButton
, což je stisknutelný prvek v pravém dolním rohu displaye. Na dalším řádku tomuto tlačítku nastavujeme ovladač události. Jak vidíte, vytvoříme třídu s overridovanou metodou onClick
. Snackbar je ten prvek, který se na chvíli objeví po stisknutí dotyčného tlačítka.
Příklad 2: Tentokrát si nastavíme pro TextView, které máme na layoutu, ID textovepole
a takto do toho TextView
zapíšeme:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView t=(TextView)findViewById(R.id.textovepole); t.setText("Nazdar"); }Povšimněte si, že tentokrát namísto řetězce
Hello world
vidíme český pozdrav. Zapsali jsme tedy úspěšně text do textového políčka.
Příklad 3: Tentokrát si uděláme příklad, který založí databázi, pokud tato neexistuje a založí v ní tabulku. Pokud již databáze a námi vytvořená tabulka existuje, zapíšeme do ní (a pro kontrolu jeden ze zapsaných údajů opět zobrazíme v TextView).
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView t=(TextView)findViewById(R.id.textovepole); SQLiteDatabase mydb=openOrCreateDatabase("Lol",MODE_PRIVATE,null); Cursor c=mydb.rawQuery("select DISTINCT tbl_name from sqlite_master where name='studenti' and type='table';",null); if(c.getCount()==0) { mydb.execSQL("create table studenti(jmeno varchar(30),prijmeni varchar(60),ID int);"); t.setText("Nazdar");} else { String result=""; mydb.execSQL("insert into studenti values('Jan','Novak',123);"); c=mydb.rawQuery("select * from studenti;",null); c.moveToFirst(); while(!c.isAfterLast()) { result+=c.getString(0)+"\n"; c.moveToNext(); } t.setText(result); } }Po prvním spuštění se objeví hlášení Nazdar, od druhého dál (kdy už je databáze založena) přibývá počet Janů.
Cursor
, ve kterém nám přijde výsledek funkce rawQuery,
funguje podobně, jak jsme zvyklí z PHP, kde jsme dostávali jeden řádek po druhém. Tentokrát nás metoda moveToFirst
nastaví na začátek odpovědi a my přistupujeme k prvnímu řádku (té odpovědi). Metoda getString(i)
vrátí údaj z i
-tého sloupce jako string (existují i podobné metody vracející údaje jako jiné datové typy). Metoda moveToNext
nás posune na další řádek. Metoda isAfterLast
oznamuje, zda jsme už došli na konec. U objektů třídy SQLiteDatabase
je snad rozdíl mezi metodami rawQuery
a execSQL
je snad patrný. Ta první vrací objekt třídy Cursor
(ze kterého získáme údaje), ta druhá nevrací nic.
Příklad 4: Tentokrát si předvedeme přečtení dat z webu. Příklad bude o něco delší, protože načítání údajů z webu musí probíhat asynchronně. Abychom se dostali k výsledkům, použijeme ovladač pro FloatingActionButton,
který známe již z prvního příkladu. Údaje, které jsme přečetli, se nám tak (jako obvykle v TextView) objeví po stisknutí kulatého tlačítka v pravém dolním rohu:
public class MainActivity extends AppCompatActivity { String httpres; class updater extends AsyncTaskTentokrát si všimněte, že vláknu běžícímu v pozadí (zbytečně) předáváme stringové parametry. My sice nic předat nepotřebujeme, ale takto bychom mohli poslat parametry celkem libovolného typu. Kdybychom chtěli, aby aplikace zareagovala ke konci běhu vlákna na pozadí, museli bychom se pustit do posílání zpráv (vlákno v pozadí by poslalo zprávu vláknu v popředí, které by tato zpráva probudila). Metoda{ @Override protected String doInBackground(String... params) { int i; String vytvoreno = "", httpresr; try { byte[] rawres = new byte[1000000]; URL u = new URL("http://jikos.cz/~perm/"); HttpURLConnection urlConnection = (HttpURLConnection) u.openConnection(); try { InputStream in = new BufferedInputStream(urlConnection.getInputStream()); in.read(rawres); httpres = new String(rawres); } finally { urlConnection.disconnect(); } } catch (Exception e) { httpres = "Bouchlo to! "+e.toString(); } return "Nazdarek!"; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); httpres="Jeste nic..."; new updater().execute("Nazdarek!"); FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { TextView t=(TextView)findViewById(R.id.textovepole); t.setText(httpres); } }); } }
onCreate
vytvoří instanci třídy updater,
která dědí od abstraktní generické třídy AsyncTask.
Tímto manévrem vyvoláme pozaďové vlákno, ve kterém proběhne metoda doInBackground.
K té nyní přesuneme svou pozornost, jelikož provede celou síťovou komunikaci. Tato komunikace se odehrává v try
-blocích. Ve finalizátoru komunikaci ukončíme (ať výjimka přijde nebo ne).Cvičení: Modernizujte kafomat na mobilní aplikaci. :-)
I Visual Studio má AVD Managera. Ten se pokusí založit si vlastní hromádku emulatorů. Nechceme-li emulatory udržovat dvakrát, můžeme si otevřít ten AVD Manager, kde již emulatory nastavené máme (v mém případě v Android Studiu). Odtud emulator pustíme a Visual Studio je schopno komunikovat s již takto běžícícm emulatorem (samo si je ale patrně schopno spustit jen své emulatory). V současné konfiguraci jsem zpozoroval, že zatímco Android Studio zabírá něco málo přes 1 GB RAM, Visual Studio je přibližně poloviční. Pokud tedy máte potíže s velikostí paměti, může být Visual Studio jistou výhodou.
Nový projekt (pro Android) vytvoříme výběrem Android App (Xamarin). Následně se objeví prostředí velmi podobné Android Studiu. Kód se ovšem tentokrát píše v C#. Jelikož Visual Studio a jazyk C# už znáte, opět stačí popsat formátu vstupu a výstupu. Třídy, objekty, metody a atributy k tomu používané však téměř splývají s tím, co jsme používali v Javě v Android Studiu, takže téměř není co vykládat. Chceme-li se dostat k prvku formuláře, tentokrát nejde o třídu R
, ale o třídu Resource
. Její podtřída, kde máme hledat ID, se ale již jmenuje (správně) Id
. Funkce, která podle ID hledá, se jmenuje findViewById
, ovšem je generická a při zavolání ji parametrizujeme typem dotyčného prvku. Její výsledek tak narozdíl od Android Studia nemusíme přetypovávat. Obsah textového okénka se nastaví metodou setText
. Ke čtení ovšem nemáme metodu getText
, nýbrž
wrapper Text
, který můžeme jak číst tak nastavovat (a můžeme jej tedy použít i k nastavení textu), což jsou typické rozdíly mezi Javou a C#. Tedy k textovému políčku se dostaneme takto: TextView t = FindViewById<TextView>(Resource.Id.textovepole);
a jeho obsah nastavíme t.Text="Nazdarek!";
I Visual Studio má klikátko pro návrh formulářů:
Jelikož toto prostředí znáte výrazně lépe, přidáme jen jeden souhrnný příklad demonstrující stažení souboru z webu a práci s databází najednou (jako v posledních dvou příkladech minulé sekce dohromady).
Příklad:
public class MainActivity : AppCompatActivity { class updater : AsyncTask{ protected override string RunInBackground(params string[] p) { byte[] rawres = new byte[10000];//Zde opet delame tridu pracujici na pozadi char[] cres = new char[10000];//Pole charu lze zkonvertovat do stringu, pole bytu ne URL u = new URL("http://jikos.cz/~perm/");//temer stejne jako v Jave HttpURLConnection urlConnection = (HttpURLConnection)u.OpenConnection(); urlConnection.Connect();//Navaz spojeni System.IO.Stream j= urlConnection.InputStream; j.Read((byte[])rawres);//Precti odpoved jako stream for (int i = 0; i < rawres.Length; i++) cres[i] = (char)rawres[i];//zkonvertuj do stringu async_res+= new string(cres);//a pridej k vysledne odpovedi MainActivity.uz = true; return "nazdarek"; } } public static string async_res = "";//sem vlakno z pozadi zapise vysledek public static bool uz = false;//nase synchronizacni primitivum protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); //v C# rikame base, ne super Xamarin.Essentials.Platform.Init(this, savedInstanceState); SetContentView(Resource.Layout.activity_main);//Opet nastavime, co se ma zobrazit TextView t = FindViewById (Resource.Id.textovepole); new updater().Execute("nazdarek"); SQLiteDatabase mydb = OpenOrCreateDatabase("pokus", Android.Content.FileCreationMode.Private, null); ICursor res = mydb.RawQuery("select DISTINCT tbl_name from sqlite_master where name='studenti' and type='table';", null); res.MoveToFirst(); if (res.IsAfterLast) { mydb.ExecSQL("create table studenti (jmeno varchar(30),prijmeni varchar(50),narozeni date);"); async_res += "\nZakladam databazi\n"; }else{ mydb.ExecSQL("insert into studenti values('Jan','Novak','2000/03/22');"); res = mydb.RawQuery("select * from studenti;", null); res.MoveToFirst(); async_res += res.GetString(0) + "\n"; } FloatingActionButton fab = FindViewById (Resource.Id.fab); fab.Click += FabOnClick; } private void FabOnClick(object sender, EventArgs eventArgs) { TextView t = FindViewById (Resource.Id.textovepole); t.Text = async_res; } }
Pozor, sice jsme na to už snad upozornili, ale vlákno běžící v pozadí nesmí zasahovat do zobrazování. Proto nemůže přímo vypsat výsledek svého snažení (a my toto omezení workaroundujeme tím, že se ovladač nějakého tlačítka vždycky podívá, jestli už výpočet doběhl, a pokud ano, výsledek zobrazí). Že program dělá, co má, v tomto případě poznáme podle toho, že pokaždé vypíše obsah staženého souboru a poprvé k tomu přidá poznámku, že zakládá databázi, kdežto od druhého spuštění dále přidá obsah prvního políčka, tedy jméno "Jan". Statický boolský atribut uz
slouží jako zámek. Až má vlákno běžící v pozadí všechno spočítané, nastaví ji na true
. Nicméně při implementaci funkce FabOnClick
, která výsledky zobrazí, se ukázalo, že přečtením částečných výsledků žádná škoda nevznikne, proto tato funkce ani nekontroluje stav zámku. Kdybychom program neměli jen pro vlastní potřebu, bylo by potřeba zamykání dodělat.
Cvičení: Přepište kafomat do C# ve Visual Studiu (a všimněte si, že kód vlastně zůstává téměř stejný).
Vyvíjejí se podobně, a to buďto opět x využitím Xamarinu ve Visual Studiu, nebo je třeba si obstarat další vývojové studio. Vývoj probíhá buďto v Objective C nebo Swift (verze 2) a jde o další prostředí na způsob toho, co jsme už viděli. Jelikož se společnost Apple průběžně pokouší svým uživatelům vývoj vlastních aplikací na vlastním Macu pro vlastní iPhone znepříjemňovat (až znemožňovat), alespoň letos bude vývoj aplikací pro iPhone pouze dobrovolný a podléhá samostudiu.