Page 1
Univerzita Hradec Králové
Fakulta informatiky a managementu
Katedra informatiky a kvantitativních metod
Android aplikace pro základní konfiguraci
a monitoring routerů MikroTik
Bakalářská práce
Autor: Matěj DaníčekStudijní obor: Aplikovaná informatika
Vedoucí práce: doc. Ing. Filip Malý, Ph.D.
Hradec Králové duben 2015
Page 2
Prohlášení:
Prohlašuji, že jsem bakalářskou práci zpracoval samostatně a s použitím
uvedené literatury.
V Hradci Králové dne 29. 4. 2015 Matěj Daníček
Page 3
Poděkování:
Děkuji vedoucímu bakalářské práce doc. Ing. Filipu Malému, Ph.D za metodické
vedení, rady a vstřícnost při zpracování této práce. Dále také rodině za trpělivost a
podporu, bez níž by práce nemohla vzniknout.
Page 4
Anotace
Obsahem této bakalářské práce je popis tvorby a součástí Android aplikace pro
základní konfiguraci a monitoring routerů MikroTik pomocí RouterOS API, jehož
účelem je poskytovat rozhraní právě pro aplikace takovéhoto typu. Ve své teoretické
části se práce zabývá jak popisem základních komponent a způsobů vývoje aplikací na
platformě Android, tak samotným RouterOS, strukturou konfigurace, API a způsobem,
jakým lze pomocí něj komunikovat se zařízením. Vytvářená aplikace není zamýšlena
jako plnohodnotné nahrazení oficiálních nástrojů pro konfiguraci (jako např. aplikace
Winbox), avšak má být nástrojem pro rychlé kontroly stavu zařízení či menší změny
konfigurace a případným základem pro budoucí vývoj a rozšiřování funkcionality.
Klíčová slova: Android, Java, MikroTik, RouterOS, MikroTik API, Winbox
Annotation
Title: Android Application for Monitoring and Basic Configuration
of MikroTik Routers
This Bachelor Thesis focuses on the components and development process of an
Android application that would provide users with tools for basic monitoring and
configuring of MikroTik routers using the RouterOS API which is intended to provide
an interface for applications similar to this one. In its theoretical part, the thesis
describes core components and methods that are used in Android application
development as well as RouterOS itself, its configuration, API and ways of its usage.
The application itself is not intended to be a full substitute for official configuration
tools (such as the Winbox application). The goal is to provide a tool for performing
quick status checks and minor configuration changes as well as a foundation for
possible future development and adding other functions.
Key words: Android, Java, MikroTik, RouterOS, MikroTik API, Winbox
Page 5
Obsah
1. Úvod............................................................................................................................................................. 1
2. Android API............................................................................................................................................... 2
2.1. Aktivita............................................................................................................................................... 2
2.1.1. Životní cyklus......................................................................................................................... 2
2.2. Fragment........................................................................................................................................... 4
2.3. Manifest aplikace........................................................................................................................... 5
2.4. Perzistentní uložení dat.............................................................................................................. 5
2.5. Resources.......................................................................................................................................... 6
2.6. Sestavování prvků uživatelského rozhraní..........................................................................7
3. MikroTik API............................................................................................................................................. 8
3.1. Balíčky a struktura příkazového stromu.............................................................................. 8
3.1.1. Příkazový strom.................................................................................................................... 9
3.1.2. Funkční balíčky..................................................................................................................... 9
3.2. API slova a věty............................................................................................................................ 10
3.2.1. Struktura API slova........................................................................................................... 10
3.2.2. Příkazové slovo................................................................................................................... 12
3.2.3. Atributové slovo................................................................................................................. 12
3.2.4. API atributové slovo (tag).............................................................................................. 12
3.2.5. Filtrovací slovo (query word)....................................................................................... 13
3.2.6. Slovo odpovědi.................................................................................................................... 15
3.3. API příkazy.................................................................................................................................... 16
3.3.1. Příkaz „login“....................................................................................................................... 16
3.3.2. Příkaz „getall“ („print“)................................................................................................... 17
3.3.3. Příkaz „listen“...................................................................................................................... 18
3.3.4. Ukončení příkazů („cancel“).......................................................................................... 19
4. Vývoj aplikace........................................................................................................................................ 20
4.1. Komunikační modul................................................................................................................... 20
4.1.1. Připojení k routeru........................................................................................................... 20
4.1.2. Odesílání příkazů a příjem odpovědí.........................................................................22
4.1.3. Odesílání příkazů z nevolajícího vlákna...................................................................22
4.1.4. TLS připojení....................................................................................................................... 23
Page 6
4.2. Správce spojení............................................................................................................................ 23
4.3. Obnovování spojení.................................................................................................................... 24
4.3.1. Automatická obnova při chybě odesílání.................................................................25
4.3.2. BroadcastReceiver pro zjištění změn sítě................................................................25
4.3.3. Ruční obnova spojení....................................................................................................... 26
4.4. XML znázornění příkazového stromu................................................................................27
4.4.1. Elementy <branch> a <tabsBranch>.........................................................................27
4.4.2. Element <list>..................................................................................................................... 28
4.4.3. Element <propertyTab>.................................................................................................. 28
4.4.4. Element <property>......................................................................................................... 29
4.4.5. Element <scan>.................................................................................................................. 30
4.5. Model příkazového stromu..................................................................................................... 31
4.5.1. Třídy příkazového stromu............................................................................................. 32
4.5.2. Rozhraní Parcelable.......................................................................................................... 33
4.6. Třídy kontrolerů a zobrazovací rozhraní..........................................................................34
4.6.1. Třída ListController.......................................................................................................... 36
4.6.2. Třída AddController......................................................................................................... 37
4.6.3. Třída DetailController..................................................................................................... 38
4.6.4. Třída ScanController........................................................................................................ 38
4.6.5. Zobrazovací rozhraní....................................................................................................... 38
4.6.6. Třídy ListItem a PropertyItem..................................................................................... 39
4.7. Aktivita MainActivity................................................................................................................. 39
4.7.1. ListView a RouterArrayAdapter..................................................................................40
4.7.2. Kontextové menu............................................................................................................... 41
4.7.3. Asynchronní úlohy AsyncTask.....................................................................................41
4.7.4. Třída DBManager.............................................................................................................. 42
4.7.5. Import z .wbx souboru.................................................................................................... 43
4.8. Aktivita ListTabsActivity.......................................................................................................... 45
4.8.1. Výsuvné menu (Drawer)................................................................................................. 45
4.8.2. ViewPager, fragmenty a lišta záložek........................................................................45
4.8.3. Obnovení do posledního stavu..................................................................................... 46
4.9. Fragment ListFragment............................................................................................................ 47
4.9.1. Zobrazení výsledků........................................................................................................... 47
Page 7
4.9.2. Kontextové a hlavní menu.............................................................................................. 47
4.9.3. Zobrazování chyb............................................................................................................... 48
4.9.4. Zobrazovaní „loading“ animace...................................................................................49
4.10. Aktivita AddTabsActivity....................................................................................................... 49
4.10.1. Fragment............................................................................................................................ 49
4.10.2. Zobrazování odpovědí a chybových zpráv............................................................50
4.10.3. Obnovení do posledního stavu..................................................................................50
4.10.4. Aktivita DetailTabsActivity.......................................................................................... 51
4.11. Aktivita ScanActivity............................................................................................................... 51
5. Shrnutí výsledků................................................................................................................................... 53
6. Závěry a doporučení........................................................................................................................... 54
7. Seznam použitých zdrojů.................................................................................................................. 55
8. Přílohy...................................................................................................................................................... 59
Page 8
1. Úvod
Cílem této práce je vytvořit aplikaci, která by umožňovala základní konfiguraci
a monitoring routerů se systémem RouterOS (tedy routerů od společnosti MikroTik)
z mobilních zařízení či tabletů se systémem Android a popsat vzniklé problémy
a postupy, které budou při vývoji využity.
Zařízení MikroTik poskytují levné, snadno konfigurovatelné, avšak dostatečně
robustní řešení pro síťové použití. Jejich konfigurace z osobních počítačů je prováděna
např. pomocí oficiální aplikace Winbox s přehledným grafickým uživatelským
rozhraním. Pokud však správce sítě nemá k osobnímu počítači přístup a je nutné co
nejrychleji zjistit a popř. odstranit závadu na síťově infrastruktuře, aplikace pro
mobilní zařízení je vhodným řešením.
Vzhledem ke své dominanci na trhu mobilních zařízení (podle serveru Business
Insider [1] více než 80 % světových mobilních uživatelů používá zařízení s některou
z verzí operačního systému Android) byla vybrána právě tato platforma.
Pro správce sítě či síťového technika je také nutná evidence zařízení (tak, aby nemusel
pokaždé zadávat adresu a přihlašovací údaje), rychlé zjištění zda je zařízení „online“
a možnost zařízení jednoduše restartovat.
1
Page 9
2. Android API
V této kapitole jsou popsány základní obecné principy vývoje Android aplikací, jejich
základní struktura a způsob vytváření uživatelského rozhraní. Konkrétnější postupy
využité při vývoji aplikace budou popsány v příslušné části kapitoly 4 – Vývoj
aplikace.
2.1. Aktivita
„Aktivita je komponenta aplikace umožňující zobrazení obrazovky, se kterou může
uživatel interagovat za účelem provedení požadovaných akcí.“[2] Aplikace zpravidla
obsahuje více aktivit, mezi kterými je možno přecházet a posílat výsledky. Z jedné
aktivity je možné spustit aktivitu jinou, „(...) v ten moment je původní aktivita
zastavena, avšak systém si ji a její stav uchová v zásobníku. Při otevření nové aktivity je
tato vložena do zásobníku, zobrazena a je na ní umístěno zaostření (focus). Zásobník
využívá princip „last in, first out“ (tedy „poslední dovnitř, první ven“) tzn., že pokud
uživatel skončil svou práci v aktivitě a stiskl tlačítko „zpět“, je tato ze zásobníku
odstraněna a zničena (destroy) a předchozí aktivita je obnovena (resume).“ [2]
2.1.1. Životní cyklus
Jak uvádí dokumentace na serveru developer.android.com [2], aktivita může být ve 3
hlavních stavech:
Resumed (pokračující) nebo také running (běžící) – stav, ve kterém je aktivita
v popředí a je na ní zaostřeno.
Paused (přerušená) – jiná aktivita je v popředí, ale je částečně průhledná nebo
nezabírá celou obrazovku. Přerušená aktivita není ukončená, avšak v případě
akutního nedostatku paměti může být tato aktivita systémem ukončena.
Stopped (zastavená) – aktivita je celá překryta jinou aktivitou a pro uživatele tedy
není viditelná. Může být systémem ukončena pokud je nutné uvolnit paměť.
„Pokud je aktivita přerušena či zastavena, systém může uvolnit paměť jejím
odstraněním. A to buď zavoláním metody finish() nebo jednoduše ukončením jejího
2
Page 10
procesu.“[2] Proto také není doporučováno používání aplikací (tzv. „task killers“),
které automaticky ukončují aplikace na pozadí. Jak poznamenává např. Whitson [3],
automatické ukončování je nejenom neúčinné pro zrychlení zařízení a prodloužení
výdrže baterie, ale dokonce kontraproduktivní, neboť aplikace a jejich aktivity musí
být vytvořené znovu, což je oproti udržování v paměti daleko náročnější a pomalejší.
Rizikem je také nestabilita systému, případně nedostupnosti některých funkcí.
O přechodu mezi stavy je aktivita informována pomocí volání metod uvedených na
obr. 1. Tyto metody lze přepsat tak, aby prováděly konkrétně to, co je pro danou
aktivitu v dané chvíli potřeba. Pro správnou funkčnost je však nutné vždy zavolat
metodu základní třídy (pomocí volání super.jmenoMetody). Mezi nejdůležitější
z těchto metod patří mj. onCreate a onPause. Typickými činnostmi v těchto metodách
jsou podle developer.android.com [2] např.:
onCreate – Tato metoda je volána při vytváření aktivity. Jejím obsahem by mělo být
definování prvků, které nebudou během životního cyklu aktivity měněny (tedy např.
prvky grafického uživatelského rozhraní, navázání dat na seznamy atp.).
onPause – Volána předtím, než je aktivita překryta jiným objektem. V této metodě by
mělo docházet mj. k uložení či aktualizaci perzistentních dat, zastavení animací či
jiných operací náročných na CPU a uvolnění prostředků, které mohou být využívány
jinou aktivitou či aplikací (tedy např. fotoaparát [4]). Pro plynulý a rychlý přechod
mezi aktivitami by tyto činnosti neměly být časově náročné, neboť následná aktivita je
zobrazena až po dokončení této metody.
3
Page 11
2.2. Fragment
„Fragment představuje činnost či část uživatelského rozhraní v aktivitě. V jedné aktivitě
můžete kombinovat více fragmentů a vytvořit tak více-panelové uživatelské rozhraní
a znovu využívat fragmenty ve více aktivitách.“[5]
4
Obr. 1: Životní cyklus aktivity. Zdroj: [2]
Page 12
Fragmenty tedy lze využívat jako určité podčásti aktivit. Mají vlastní životní cyklus,
který je sice vázán na životní cyklus zastřešující aktivity (pokud je například aktivita
uváděna do stavu paused, jsou všechny fragmenty v ní uvedeny do stejného stavu), ale
umožňuje pracovat i s jednotlivými fragmenty (přidávat, odebírat apod.).[5]
2.3. Manifest aplikace
„Každá aplikace musí mít ve svém kořenovém adresáři umístěn soubor
AndroidManifest.xml (pojmenování souboru je striktní a neměnné). Manifest poskytuje
systému Android informace nutné pro spuštění aplikačního kódu.“[6] Mezi tyto
informace patří mj.: [6]
Jméno JAVA balíčku aplikace, které slouží jako jednoznačný identifikátor dané
aplikace. Pro správné identifikování verzí musí zůstat stejné po celou dobu života
aplikace. Obvykle bývá voleno jako reverzní jméno domény, tedy např.
com.company.project
Komponenty aplikace jako např. aktivity, služby atp. Vyjmenovává jednotlivé třídy,
které implementují dané komponenty.
„Povolení, která aplikaci musí být udělena, aby mohla přistupovat k chráněným částem
API a mohla spolupracovat s ostatními aplikacemi.“[6]
Minimální verze API, kterou aplikace vyžaduje.
Knihovny, se kterými aplikace vyžaduje propojení.
Verze aplikace (celé číslo) a jméno verze (volný řetězec znaků).
2.4. Perzistentní uložení dat
„Android poskytuje několik možností pro uložení trvalých aplikačních dat.“[7] Výběr
možnosti závisí na konkrétních potřebách dané aplikace. Mezi kritéria výběru patří
mj. „privátnost“ dat (zda data mají být přístupná pouze aplikaci či zda k nim mají mít
přístup i jiné aplikace a uživatel), velikost dat, struktura dat (zda budou ukládány
např. pouze seznamy hodnot nebo je zapotřebí uchovávat složitější relace mezi daty).
Možnosti uložení jsou následující: [7]
5
Page 13
Shared Preferences (sdílené hodnoty) – Tato možnost umožňuje trvale uložit páry
klíč–data, kde data mohou být primitivní datové typy jako boolean – pravdivostní
hodnota, float – číslo s plovoucí desetinou čárkou, integer – celé číslo, long či řetězec
znaků. Sdílených hodnot ve spojení s třídou PreferenceActivity se využívá pro uložení
uživatelského nastavení.[8]
Interní a externí úložiště – Nabízí možnost pro uložení souborů. V interním úložišti
(tedy v interní paměti telefonu) je možné zvolit, zda bude soubor přístupný všem
aplikacím či pouze té, která ho vytvořila. Použití úložiště externího (tedy přídavné
paměťové karty či pevné paměti telefonu) znamená umožnění přístupu k souboru
uživateli i ostatním aplikacím. Pokud ukládaná data budou patřit uživateli (např.
fotografie, videa, nahrané zvuky atp.), je vhodné je umístit do některé ze sdílených
složek v kořenovém adresáři externího úložiště (tedy např. /Music, /Pictures,
/Ringtones atp.).
SQL databáze – „Android poskytuje plnou podporu databází SQLite. Jakákoliv
vytvořená databáze je přístupná všem třídám dané aplikace, ale ne aplikacím jiným.“[7]
Pro uchovávání složitěji strukturovaných dat je nevhodnější SQLite databáze, která se
vyznačuje svojí jednoduchostí a malou velikostí.[9]
2.5. Resources
V Android aplikacích je využíváno konceptu Resources, tedy externalizace zdrojů jako
jsou obrázky, řetězce, zvuky a rozvržení zobrazení. Toto umožňuje spravovat pomocná
data odděleně od zdrojového kódu a také načítat jejich různé verze podle parametrů
zařízení, na kterém je aktuálně spouštěna. Lze tak jednoduše provádět jazykovou
lokalizaci či modifikovat rozložení zobrazení pro zařízení s rozdílným rozlišením
a velikostí displaye. Tato data jsou v projektu umístěna v adresáři /res a jeho
podadresářích, rozdělených a pojmenovaných podle dat v nich umístěných
a parametrů zařízení, při kterých mají být použity.[10]
Mezi nejdůležitější součásti tohoto adresáře patří definice zobrazovaných řetězců (v
souboru strings.xml) a definice prvků zobrazování (v adresáři /res/layout).
6
Page 14
Přístup k obsahu adresáře /res je v kódu prováděn pomocí volání některé z množství
metod, které API nabízí. Těmto metodám je v parametru předáván celočíselný
identifikátor daného Resource objektu, který je získán pomocí třídy R resp. jejich
statických vnitřních tříd a konstant. Tato třída je dynamicky vytvářena při sestavování
aplikace.[11] Ve výsledku je např. získání hodnoty řetězce s chybovou zprávou
prováděno pomocí následující metody:
String errorString = MainActivity.this.getString(R.string.error);
2.6. Sestavování prvků uživatelského rozhraní
Strukturu uživatelského rozhraní lze vytvářet dvěma způsoby. Prvním je nadefinování
prvků v XML souboru umístěném v podadresáři /res/layout a využití metody
LayoutInflater.inflate, která na základě celočíselného identifikátoru XML souboru
vytváří a vrací instanci třídy View.[12]
Druhou možností je vytváření prvků programově přímo ze zdrojového kódu. K tomu
je využito třídy ViewGroup, tedy zobrazovacího prvku, který může hierarchicky
zastřešovat další, dceřinné prvky (instance třídy View či jejích potomků). Tato třída
poskytuje množství metod pro práci se zastřešovanými prvky, mezi ně patří např.
mnohonásobně přetížená metoda addView, dále pak getChildAt či removeView.[13]
Tento způsob je vhodné použít v případě, kdy prvky nejsou statické (tedy předem
známé a neměnné), ale jsou vytvářeny dynamicky na základě aktuálního stavu
aplikace.
7
Page 15
3. MikroTik API
Konfiguraci routerů MikroTik je možné provádět několika způsoby. Asi
nejpoužívanější a nejpohodlnější je využití oficiální aplikace Winbox, která má
poměrně přehledné a intuitivní grafické rozhraní. Další možností je využití SSH či
Telnet klienta a komunikaci prostřednictvím konzole, tedy bez grafického rozhraní.
Počínaje verzí 3.0 je uživatelům umožněno vytvářet vlastní software pro konfiguraci
a správu zařízení s RouterOS pomocí API, tedy rozhraní pro komunikaci aplikací se
zařízením.[14] Tohoto API využívají mj. různé dohledové či „provisioningové“ systémy
jako např. ISPadmin.[15]
3.1. Balíčky a struktura příkazového stromu
„RouterOS nabízí velké množství funkcí a vzhledem k tomu, že každá instalace vyžaduje
specifickou množinu těchto funkcí, je možné přidávat a odebírat určité skupiny těchto
funkcí pomocí systému balíčků. Ve výsledku tedy uživatel může ovlivňovat podporované
8
Obr. 2: Grafické uživatelské rozhraní aplikace Winbox. Zdroj: [16]
Page 16
funkce a velikost instalace.“[17] Každé zařízení tak může podporovat jinou
funkcionalitu a mít jinou strukturu příkazového stromu.
3.1.1. Příkazový strom
Pro snadnější orientaci jsou příkazy umístěny ve stromové struktuře. Jméno dané
úrovně odpovídá konfiguračním informacím v ní dostupným.[18] Tedy například „/ip
route“ pro přístup k IPv4 směrovacím cestám (routes) či „/interface vlan“ pro virtuální
LAN rozhraní (VLAN).
Většina úrovní menu představuje tabulku (seznam) položek a jejich atributů (jejich
výpis či navrácení se provádí příkazem „print“). Nad těmito tabulkami je možné
provádět příkazy jako „add“ (přidání položky), „remove“ (odebrání položky), „set“
(nastavení hodnoty daných atributů položky).
Některé úrovně neumožňují položky přidávat či odebírat, ale pouze měnit parametry.
Obecně to jsou např. fyzicky nepřidatelné položky (mj. ethernetová rozhraní) či různé
seznamy klientů (např. aktivní uživatelé či bezdrátově připojená zařízení).
Úrovně, ve kterých je uchováváno globální nastavení platné pro více položek či pro
celé zařízení mají v tabulce pouze jednu neodebratelnou položku, které je však možno
příkazem „set“ nastavovat hodnoty atributů. Příkladem může být úroveň „/ip dhcp-
server config“ a atribut „store-leases-disk“, který udává, jak často budou zapůjčení IP
adres ukládány na disk, resp. flash paměť.[19]
3.1.2. Funkční balíčky
Balíčky funkcí je možné získat z webových stránek MikroTik, poté je nahrát do
zařízení a aktivovat, deaktivovat, odstranit, aktualizovat apod. Všechny tyto příkazy
jsou dostupné z úrovně „/system packages“. Dostupný je také příkaz „print“ pro výpis
balíčků, podle kterého je možné určit strukturu příkazového stromu.[17]
V tabulce 1 jsou uvedeny nejdůležitější dostupné balíčky a jejich funkce.
9
Page 17
Jméno balíčku Funkce
dhcp DHCP klient a server
ipv6 podpora IPv6 adresování
ntp NTP klient a server
routing dynamické směrovací protokoly
security IPSEC, SSH, Secure WinBox
system základní směrování, IPv4, telnet, API, firewall, SNMP, VLAN, aj.
wireless podpora bezdrátových rozhraní
advanced-tools rozšířené ping nástroje, netwatch (sledování zařízení v síti), IPsken, wake-on-LAN
Tab. 1: Přehled nejdůležitějších funkčních balíčků. Zdroj: [17] (upraveno)
3.2. API slova a věty
„Komunikace s routerem probíhá pomocí odesílání vět routeru a přijímání jedné či více
vět jako odpovědí. Věta je sekvence slov ukončená slovem s nulovou délkou. Slovo je část
věty zakódovaná specifickým způsobem – zakódovaná délka a data(…) Každá věta
odeslaná routeru pomocí API by měla obsahovat příkaz jako první slovo, následovaný
dalšími slovy v libovolném pořadí, konec věty je tvořen slovem s nulovou délkou. Po
obdržení celé věty (příkazové slovo, případná další slova a slovo s nulovou délkou)
routerem je tato zpracována, poté je vytvořena a vrácena odpověď.“[14]
3.2.1. Struktura API slova
Každé slovo ve větě musí být určitým způsobem zakódováno. V první části je uvedena
délka slova, v druhé pak samotný jeho obsah. Délka slova značí počet bytů obsahu,
které budou odeslány.[14]
Délka slova Počet bytů Kódování
méně než 0x80 1 délka
0x80 až 0x3FFF 2 délka | 0x8000
0x4000 až 0x1FFFFF 3 délka | 0xC00000
0x200000 až 0xFFFFFFF 4 délka | 0xE0000000
více než 0xFFFFFFF 5 0xF0 a délka ve 4 bytech
Tab. 2: Kódování délky slova. Zdroj: [14] (upraveno)
10
Page 18
V tabulce 2 je uveden způsob, kterým jsou kódovány jednotlivé délky slov. V případě,
že je délka menší než 0x80 tedy maximálně 0x7F (127 v desítkovém zápisu) bytů je
tato délka zakódována v jednom bytu, kde nejvýznamnější bit má hodnotu 0 a ostatní
určují samotnou hodnotu v rozmezí 0-127. Právě nulový nejvýznamnější byt určuje,
že délka slova je zakódována právě v jednom bytu.
Pokud je délka slova v rozmezí 0x80 až 0xFFFFFFF, je k ní za pomoci bitového součtu
(bitového OR) připočtena příslušná maska. Díky této operaci určují nejvýznamnější
bity nejvýznamnějšího bytu celkový počet bytů, ve kterých je délka slova zakódována.
Například pokud mají dva nejvýznamnější bity nejvýznamnějšího bytu hodnoty 1 a 0,
znamená to, že celá délka slova je zakódována ve 2 bytech. Analogicky hodnoty 1,1,0
značí zakódování délky ve 3 bytech a hodnoty 1,1,1,0 ve 4.
Pokud je délka slova větší než 0xFFFFFFF, je hodnota nejvýznamnějšího bytu
nastavena na 0xF0 a další 4 byty (tedy 32bitů) nesou informaci o samotné hodnotě.
Nejvýznamnější byte obsahující 0xF0 tedy značí, že následující 4 byty obsahují
velikost slova.
Tento způsob kódování umožňuje zasílat informaci o délce slova v minimálním
nutném počtu bytů. Není tedy zapotřebí, aby první 4 byty byly pevně vyhrazeny pro
délku slova v případě, že tato délka má malou hodnotu a několik z těchto 4 bytů by
mělo nulovou hodnotu.
Za zakódovanou délkou slova následuje slovo jako takové. Slovo může být několika
typů: příkazové, atributové, API atributové, filtrovací (dotazovací neboli „query
word“) či slovo odpovědi.[14]
3.2.2. Příkazové slovo
Příkazové slovo je zpravidla první slovo v odesílané větě a začíná lomítkem. Příkazy
mají velice podobnou strukturu jako ty, používané v příkazovém řádku (CLI), avšak
jednotlivé úrovně příkazového stromu nejsou odděleny mezerou, ale lomítkem. Také
některé CLI příkazy či parametry API nepodporuje, avšak může podporovat jiné
s podobnou funkcionalitou. Příkladem může být filtrovací parametr „where“ pro
příkaz „print“ a jeho nahrazení filtrovacími slovy (query words).[14]
11
Page 19
Mezi příklady příkazových slov patří:
/ip/address/getall
/user/active/listen
/interface/vlan/remove
3.2.3. Atributové slovo
Příkazová slova za sebou mohou mít seznam atributových slov. Atributové slovo se
skládá z následujících částí: [14]
1. Zakódovaná délka slova (tak jako v každém typu slova)
2. Znak „=“ jakožto prefix obsahu
3. Jméno atributu
4. Znak „=“
5. Hodnota atributu (může být i prázdná)
Atributová slova pak mohou vypadat např. následovně:
=address=10.0.0.1
=interface=wlan1
3.2.4. API atributové slovo (tag)
API atributové slovo je strukturou podobné běžnému atributovému slovu s rozdílem
ve znaku prefixu obsahu. U API atributů se nejedná o rovnítko, ale o tečku. API
atributové slovo tedy vždy začíná tečkou. V současnosti je jediným API atributem
„tag“, který umožňuje při posílání věty routeru tuto větu označit řetězcem znaků.
Tento řetězec je poté vrácen v každé odpovědi, kterou router odesílá na základě
přijaté věty.[14]
V následující ukázce [14] je vypnuto rozhraní „ether1“ pomocí věty s tagem 3.
/interface/set=disabled=yes=.id=ether1.tag=3
12
Page 20
Načež router odpovídá opět větou s tagem 3:
!done.tag=3
Toto „tagování“ umožňuje mít v aplikaci přesný přehled k jakému příkazu patří
obdržená odpověď a které části aplikace má být předána ke zpracování.
3.2.5. Filtrovací slovo (query word)
V kombinaci s příkazem „print“ (tedy dotaz na všechny položky) je možné seznam
výsledků k navrácení filtrovat pomocí tzv. „query slov“. Tato funkce byla přidána ve
verzi 3.21. Filtrovací slovo začíná otazníkem a jeho použití má svá jasně daná pravidla.
Pořadí takovýchto slov je podstatné, neboť jsou vyhodnocovány postupně. Dotaz jako
celek je vyhodnocen pro každou položku, která má být vrácena v odpovědi. Pokud
položka odpovídá kritériím, je zpracována a vrácena, pokud ne (v zásobníku
pravdivostních hodnot pro výsledky jednotlivých výrazů se vyskytuje alespoň jedna
nepravda) je ignorována.[14]
Možnosti skládání filtrovacích dotazů uvádí tabulka 3.
Filtrovací výraz (slovo) Popis
?atribut vloží „pravda“ pokud položka má atribut s daným názvem,jinak „nepravda“
?-atribut vloží „pravda“ pokud položka nemá atribut s danýmnázvem, jinak „nepravda“
?=atribut=hodnota?atribut=hodnota
vloží „pravda“ pokud má položka daný atribut roven danéhodnotě, jinak „nepravda“
?<atribut=hodnota vloží „pravda“ pokud hodnota daného atributu menší nežzadaná hodnota, jinak „nepravda“
?>atribut=hodnota vloží „pravda“ pokud hodnota daného atributu větší nežzadaná hodnota, jinak „nepravda“
?#operace provádí operace s hodnotami v zásobníku
Tab. 3: Přehled filtrovacích výrazů. Zdroj: [14] (upraveno)
S hodnotami v zásobníku lze pracovat pomocí operací, které se řadí do řetězce
a následně jsou s prefixem „?#“ vloženy do slova a odeslány. Tyto operace jsou
v řetězci vyhodnocovány postupně zleva doprava.
13
Page 21
Pokud se v řetězci nachází číslo následované znakem (kromě „.“), je hodnota s tímto
zásobníkovým indexem zkopírována a vložena na vrchol zásobníku. Pokud je číslo
následované „.“, je toto číslo ignorováno, neboť „.“ vkládá kopii hodnoty na vrcholu
zásobníku. V případě, že je číslo na konci řetězce, jsou veškeré hodnoty v zásobníku
nahrazeny hodnotou na tomto indexu.[14]
Znak „!“ odstraní vrchní hodnotu ze zásobníku a vloží její negaci. Znaky „&“ a „|“
odeberou dvě hodnoty z vrcholu zásobníku a vloží jejich logický součin (AND) resp.
logický součet (OR).[14]
Následující věta vrátí všechna rozhraní, která nejsou typu „wlan“ či „bridge“.
/interface/print?type=wlan?#!?type=bridge?#!?#&
Stejného výsledku je možno dosáhnout kompaktnější větou:
/interface/print?type=wlan?type=bridge?#|!
Tento způsob filtrování pro příkaz „print“ nahrazuje konzolový parametr „where“,
který není v API dostupný.
3.2.6. Slovo odpovědi
Odpovědní slova jsou posílána routerem v odpovědi na celé věty poslané klientem.
První slovo v odpovědi vždy začíná znakem „!“. V případě, že není spojení ukončeno
generuje každá odeslaná věta minimálně jednu odpověď.[14]
První slovo udává o jaký typ odpovědi se jedná. V tabulce 4 je uveden jejich přehled.
14
Page 22
První slovo odpovědi Typ odpovědi
!done poslední (ukončovací) odpověď na danou větu
!re odpověď obsahující data
!final odpověď při uzavírání API připojení
!trap odpověď při chybových a výjimečných stavech
Tab. 4: Typy odpovědí. Zdroj dat: [14]
V případě, že při vyhodnocování přijaté věty nastane chyba, je vrácena chybová
odpověď (začínající slovem „!trap“) jejímž obsahem je atribut „message“ (obsahující
zprávu o chybě) a případně také atribut „category“ (tedy druh chyby, ke které došlo).
Přehled chybových kategorií je uveden v tabulce 5.
Kód Kategorie
0 chybějící položka či příkaz
1 chyba hodnoty argumentu
2 provádění příkazu přerušeno
3 chyba v souvislosti se skriptováním
4 obecná chyba
5 API chyba
6 TTY chyba
7 hodnota generovaná :return příkazem
Tab. 5: Chybové kategorie.[14]
Pokud je tedy routeru odeslána např. věta přidávající IP adresu neexistujícímu
rozhraní, router vrací následující chybovou větu: [14]
!trap=category=1=message=input does not match any value of interface
3.3. API příkazy
Příkazy používané v API úzce korespondují s příkazy dostupnými v konzoli, avšak
mají svá specifika. Tato kapitola se zabývá nejdůležitějšími příkazy a jejich použitím.
15
Page 23
3.3.1. Příkaz „login“
Pro komunikaci pomocí API je nutné nejdřív provést přihlášení. To je iniciováno
klientem pomocí odeslání věty, jejímž obsahem je pouze příkazové slovo „/login“, na
které router běžně odpovídá „!done“ větou s atributem „ret“, tedy například: [14]
!done=ret=ebddd18303a54111e2dea05a92ab46b4
Hodnotou atributu „ret“ je řetězec hexadecimálních znaků. Tento řetězec je společně
s přihlašovacím heslem uživatele „zahashován“ a v následné větě, která obsahuje
i uživatelské jméno, odeslán. Tato věta pak může vypadat následovně: [14]
/login=name=admin=response=001ea726ed53ae38520c8334f82d44c9f2
Oficiální manuálové stránky neobsahují informaci, jakým způsobem má být heslo
a přijatý řetězec zpracován a odeslán, avšak uvedené příklady API klientů (např. v Javě
[20] či C# [21]) sestavují hodnotu atributu „response“ následujícím způsobem: na
začátek jsou vloženy dvě nuly a zbývající část je tvořena MD5 hashí uživatelského
hesla a řetězce obdrženého od routeru v předchozím kroku. Přesněji je hash tvořena
ze třech částí v tomto pořadí:
1. Nulový znak (tedy ve výsledku nulový byte)
2. Uživatelské heslo
3. Řetězec atributu „ret“ obdržený od routeru resp. jeho úprava, kde je každá
dvojice obdržených znaků převedena na jeden byte. Tedy např. dvojice 4A je
převedena na byte s hodnotou 74, dvojice 6E na byte s hodnotou 110 atp.
Router si po odeslání odpovědi na první „/login“ příkaz sám vytvoří MD5 hash
z přihlašovacích údajů a odeslaného řetězce. Tento řetězec porovná s řetězcem
přijatým od uživatele a pokud jsou totožné, vrátí jednoslovnou větu obsahující „!done“.
V případě že hashe totožné nejsou, router ještě před dokončovací „!done“ větou vrátí
větu chybovou, která obsahuje zprávu, že přihlášení nebylo možné.[22]
Vzhledem k tomu, že heslo není nikdy přenášeno v prosté textové podobě, zamezuje
tento způsob přihlašování jeho odposlechnutí během přenosu.
16
Page 24
3.3.2. Příkaz „getall“ („print“)
Od RouterOS verze 3.21 je příkaz „getall“ aliasem pro příkaz „print“, jsou tedy
navzájem zaměnitelné. Tyto API příkazy vrací seznam položek v daném kontextu
příkazového stromu a jsou použitelné všude tam, kde je dostupný konzolový příkaz
„print“. Na rozdíl od konzolové verze příkazu se položky, které mají být vráceny
nemohou filtrovat pomocí parametru „where“, ale za pomoci filtrovacích (query) slov.
Dalším rozdílem je vracení vnitřního identifikátoru položky seznamu pomocí atributu
„.id“. Tento identifikátor je používán při práci s danou položkou, kterou jednoznačně
v zařízení identifikuje. Zároveň je v API možné filtrovat, které atributy budou u každé
položky vráceny pomocí příkazového atributu „.proplist“, jehož hodnotou je řetězec
čárkou oddělených jmen atributů, které mají být v odpovědi vráceny. Používání těchto
atributových seznamů je doporučováno z výkonnostních důvodů, protože není-li tento
příkazový atribut uveden, je přistupováno ke všem atributům dané položky, a to
i k těm, které mají dlouhou přístupovou dobu (jako například obsahy souborů či
výkonnostní počítadla).[14]
Příkladem použití příkazu „print“ může být vypsání všech IPv4 adres a hodnot jejich
atributů „.id“ (vnitřní identifikátor položky), „address“ (IPv4 adresa a délka prefixu),
„network“ (adresa sítě) a „interface“ (jméno rozhraní, ke kterému je daná IP adresa
přiřazena) pomocí následující věty:
/ip/adress/print=.proplist=.id,address,network,interface
Odpověď routeru se skládá z „!re“ věty pro každou vrácenou položku a poslední
ukončovací „!done“ věty:
!re.tag=1=.id=*1=address=192.168.2.2/24=network=192.168.2.0=interface=bridge1...další !re odpovědi pro všechny zbývající IPv4 adresy
!done
17
Page 25
3.3.3. Příkaz „listen“
„Příkaz „listen“ je dostupný tam, kde je dostupný konzolový příkaz „print“, ale ne všude
má očekávaný efekt (nemusí tedy fungovat). Věta „!re“ je vrácena pokaždé, když se
v dané položce něco změní. Pokud je položka odstraněna nebo zmizí jakýmkoliv jiným
způsobem, vrácená věta obsahuje hodnotu „.dead=yes“. Tento příkaz se neukončuje
automaticky. K jeho ukončení je využíván „/cancel“ příkaz.“[14]
Jedná se tedy o monitorovací příkaz, který vrací odpověď pokaždé, když se v dané
položce změní hodnota některého z atributů. Pokud je daná položka odstraněna nebo
zmizí (např. dojde k odpojení bezdrátového klienta), odeslaná odpověď obsahuje
atribut „.dead“ s hodnotou „yes“. Toto alespoň uvádí manuál, avšak např. pro IPv4
adresy ve verzi systému 6.23 bylo zjištěno, že je pro tento atribut vracena hodnota
„true“. S touto nekonzistencí je nutno při vývoji počítat a ověřovat obě hodnoty.
Na verzi 6.23 bylo také odzkoušeno, že příkaz „/listen“ lze kombinovat s atributem
„.proplist“. Poté jsou v odpovědi vráceny pouze atributy uvedené v tomto seznamu,
mezi kterými musí být i atribut „.dead“ pro případ odstranění položky. V dokumentaci
není tato možnost uvedena a nelze zaručit její funkčnost ve všech verzích systému.
3.3.4. Ukončení příkazů („cancel“)
Ukončit zpracovávané příkazy je možné pomocí věty s příkazovým slovem „/cancel“.
Nepovinným příkazovým atributem je v takovéto větě „tag“ s hodnotou tagu příkazu,
jehož zpracovávání má být ukončeno. V případě, že tento atribut není uveden, jsou
ukončeny všechny právě probíhající příkazy. Ukončení příkazu obvykle znamená
navrácení chybové „!trap“ a dokončovací „!done“ odpovědi.[14]
Při používání ukončovacích příkazů je nutné správně rozlišovat atribut „=tag“ (tag
příkazu k ukončení) a API atribut „.tag“ (tag samotného ukončovacího příkazu).
V následující ukázce je ukončen příkaz s tagem 2, přičemž ukončovací příkaz jako
takový je odeslán s tagem 4.
/cancel=tag=2.tag=4
18
Page 26
4. Vývoj aplikace
V této kapitole bude popsán vývoj samotné aplikace, její struktura a nejdůležitější
součásti. Při vývoji byl kladen důraz na modularitu, tedy odstínění jednotlivých
součástí aplikace podle jejich funkcí tak, aby byla zajištěna co možná největší
znovupoužitelnost a upravitelnost. Mezi tyto moduly patří např. komunikační modul
pro kódování a dekódování komunikace se zařízením, modul pro zpracovávání
přijatých odpovědí (jejich převod na modelové třídy) a předání zobrazovacímu
modulu, který zajišťuje zobrazení informací uživateli pomocí aktivit, fragmentů
a pohledů (views). Za další (pomocné) moduly lze považovat modul pro převod XML
souboru s příkazovým stromem na modelové třídy či modul pro import seznamu
routerů z .wbx souboru (vytvářený aplikací Winbox).
4.1. Komunikační modul
V prvotních fázích vývoje aplikace byly pro komunikaci s routerem využity třídy
zveřejněné na stránkách MikroTik dokumentace.[20] Tyto se ovšem ukázaly jako
nevhodné kvůli své málo objektově-orientované struktuře a celkově neuspořádanému
kódu. Gideon LeGrange zveřejnil na serveru github.com svojí implementaci
komunikačních tříd.[23] Zveřejněny byly pod licencí Apache 2.0, která umožňuje
volné používání, úpravu a šíření díla (za předpokladu, že je k dílu přiložena kopie této
licence).[24] Tyto třídy poskytují snadno použitelné metody pro vytvoření spojení
k routeru, přihlášení uživatele a jak synchronní, tak asynchronní vykonávání příkazů.
Proto byly původní třídy komunikačního modulu nahrazeny právě těmito od Gideona
LeGrange.
4.1.1. Připojení k routeru
Hlavní třídou zajišťující komunikaci se zařízením je ApiConnectionImpl, která je
potomkem abstraktní třídy ApiConnection. Ta předepisuje metody, které její potomek
implementuje. Mezi ně patří i statická metoda connect s parametry: String host (pro
adresu routeru) a celočíselnou hodnotou portu, na kterém má být spojení navázáno.
Tato metoda navrací novou instanci třídy ApiConnection (či některého z potomků).
Ve třídě ApiConnectionImpl je v těle této metody volána nad vracenou instancí metoda
19
Page 27
open, která zajišťuje samotné navázání spojení, inicializaci vláken pro odesílání
příkazů, čtení obdržených odpovědí a celkovou připravenost třídy pro komunikaci, jak
se naznačeno v následující ukázce:
private Socket sock = null;private DataOutputStream out = null;private DataInputStream in = null;private boolean connected = false;private BlockingQueue<Command> outQueue;private Writer writer;private Reader reader;private Processor processor;/** * Start the API. Connect to the MikroTik * @author Gideon LeGrange */private void open(String host, int port, boolean secure) throws
ApiConnectionException {try {
InetAddress ia = InetAddress.getByName(host);if (secure) {
sock = openSSLSocket(ia, port);} else {
sock = new Socket();sock.connect(new InetSocketAddress(ia, port), SOCKET_CONNECT_TIMEOUT);
}in = new DataInputStream(sock.getInputStream());out = new DataOutputStream(sock.getOutputStream());connected = true;outQueue = new LinkedBlockingQueue<Command>();writer = new Writer();writer.setDaemon(true);writer.start();reader = new Reader();reader.setDaemon(true);reader.start();processor = new Processor();processor.setDaemon(true);processor.start();
} catch (UnknownHostException ex) {connected = false;throw new ApiConnectionException(String.format("Unknown host '%s'", host),
ex);} catch (IOException ex) {
connected = false;throw new ApiConnectionException(String.format("Error connecting to %s:
%d : %s", host, port, ex.getMessage()), ex);}
}
Vnitřní třídy Reader a Processor umožňují vícevláknové zpracovávání obdržených
odpovědí resp. jejich převod na třídy Response a její potomky. Třída Reader zajišťuje
převod přijatých odpovědí na řetězce znaků za pomocí statické metody decode
20
Page 28
pomocné třídy Util a tyto poté vkládá do fronty LinkedBlockingQueue, odkud jsou
vyzvedány třídou Processor voláním metody take a zpracovávány do modelových tříd
odpovědí (obsahujících odpovídají obdržené hodnoty).
4.1.2. Odesílání příkazů a příjem odpovědí
Třída ApiConnectionImpl využívá značkování (tagging) příkazů pro správné určení,
které části aplikace (resp. instanci třídy implementující rozhraní ResultListener) má
být vrácena odpověď na daný dotaz. Pro získání korektní hodnoty tagu je použita
privátní synchronizovaná metoda nextTag, která navrací hodnotu globálního
celočíselného počítadla ve formě hexadecimálního řetězce.
V paměti je také udržována mapa (přesněji instance třídy ConcurrentHashMap), ve
které jsou klíči řetězcové hodnoty tagů a hodnotou je reference na rozhraní
ResultListener. Do této mapy je při odesílání příkazu vložen záznam s daným tagem
a příslušnou instancí třídy implementující dané rozhraní, tak aby po obdržení
odpovědi bylo známo, pro který objekt je určena.
V této implementaci je umožněno odesílat příkazy jak synchronně tak asynchronně.
Tedy pozastavit vlákno odesílající příkaz dokud není obdržena celá odpověď nebo
pouze příkaz odeslat, vlákno nepozastavovat a určit instanci třídy implementující
rozhraní definující metody, které budou volány při obdržení odpovědi.
4.1.3. Odesílání příkazů z nevolajícího vlákna
V původní implementaci Gideona LeGrange byly příkazy odesílány (resp. byty
zapisovány do odchozího proudu) ve vlákně, které volalo metodu execute. V případě
této aplikace se jedná o její hlavní vlákno. V situaci, kdy je k dispozici dostatečně
rychlé internetové připojení nepředstavuje tento přístup větší problém. Pokud je ale
připojení pomalé, může docházet k zablokování volajícího vlákna (a tedy blokování
uživatelského rozhraní), neboť potvrzení o přijetí paketů nejsou přijímána dostatečně
rychle.
K odstranění tohoto problému byla do třídy ApiConnectionImpl přidána vnitřní třída
Writer, která je potomkem třídy Thread. Ve své metodě run se cyklicky snaží získat
příkaz k odeslání z fronty outQueue (do které je objekt příkazu vložen v metodě
21
Page 29
execute) a ten poté za pomoci metody Util.write zapsat do proudu. Tato úprava také
přinesla nutnost změny propagace výjimek, které nastaly při odesílání. Místo, aby byly
výjimky vyhazovány přímo metodou execute, je nyní nalezen ResultListener pro
příslušný příkaz a na něm volána metoda error, ve které je předána odpovídající
výjimka.
4.1.4. TLS připojení
Třídy od Gideona LeGrange také poskytují metody metody pro připojení k routeru
pomocí zabezpečeného TLS spojení za využití Diffieho-Hellmanovy výměny klíčů.
Toto spojení se ovšem nedaří vytvořit, neboť MikroTik zařízení spojení uzavírá
v počáteční fázi. Podle autora tříd připojení se jedná o problém nekompatibility
implementace TLS připojení v Javě a MikroTiku. Jediný případ, kdy se mu podařilo
spojení vytvořit bylo za užití Javy verze 8 a vytvoření spojení bez certifikátů za
pomoci DH výměny klíčů.[23] Aplikace tedy podporuje pouze standardní připojení
bez využití TLS.
4.2. Správce spojení
Všechny části aplikace které komunikují s routerem musí mít přístup k instanci třídy
ApiConnection, která představuje spojení se zařízením. Není vhodné, aby každá část
aplikace vytvářela spojení nové, a je tedy nutné zajistit předávání jedné instance.
V systému Android probíhá komunikace mezi jednotlivými aktivitami (resp. jejich
spouštění) pomocí Intent objektů, ke kterým lze připojit objekty pomocí tzv. „extras
Bundle“ – mapování objektů na řetězcové klíče.[25] Tyto připojované objekty ovšem
mohou být pouze některého ze základních typů (String, int, long, boolean atp.) nebo
musí implementovat rozhraní Serializable či Parcelable.[26] Tento přístup tedy není
pro tuto situaci použitelný a byla místo něj zvolena jedna z doporučovaných alternativ
[27], a to konkrétně návrhový vzor singleton.
Tímto návrhovým vzorem je docíleno, že v celé aplikaci existuje pouze jedna instance
dané třídy a odkaz na ní je možné získat pomocí veřejné, synchronizované statické
metody, při jejímž prvním volání je tato instance vytvořena za pomoci privátního
konstruktoru. Tento princip znázorňuje následující ukázka.
22
Page 30
public class ConnectionManager {private static ConnectionManager instance;
private ConnectionManager() {}
public static synchronized ConnectionManager getInstance() {if (instance == null) {
instance = new ConnectionManager();}return instance;
}//další metody třídy ConnectionManager
}
Třída ConnectionManager si uchovává odkaz na instanci připojení, který je iniciován
při volání metody createConnection, otevírající nové spojení. Odkaz na aktivní spojení
je možno získat pomocí volání metody getConnection. Tato metoda ověří, zda
uchovávaná reference není prázdná (null) a v případě, že je, metoda vyhazuje výjimku
ConnectionNulledException, jinak vrací právě instanci spojení.
Tato třída si také udržuje referenci na Router, ke kterému bylo jako poslední
vytvořeno připojení. Této reference je využito v poskytované metodě reconnect, která
se pokusí o obnovení spojení k tomuto routeru.
4.3. Obnovování spojení
Problémem mobilních zařízení je velká náchylnost ke ztrátě internetového připojení,
ať už při přijmutí hovoru (pokud uživatel využívá mobilní datové připojení), přesunu
do dosahu veřejné Wi-Fi sítě (pokud má uživatel zapnuto automatické přepínání
z mobilních dat na Wi-Fi v dosahu) či prostém oslabení síly signálu a změny režimu
sítě.
V případě, že je připojení (byť i na malou chvíli) ztraceno, nelze spojení žádným
způsobem obnovit. Musí být k routeru vytvořeno spojení nové a znovu provedeno
přihlášení. V následujících podkapitolách jsou uvedena některá možná řešení tohoto
problému.
23
Page 31
4.3.1. Automatická obnova při chybě odesílání
V případě, že při odesílání příkazu dojde k IOException „broken pipe“ (tedy ztrátě
spojení), je podle tagu příslušného příkazu nalezen ResponseReceiver, který očekává
jeho odpovědi a je na něm zavolána metoda error, kde je jako argument předána nová
ApiConnectionException (výjimka značící chybu spojení). Instancí ResponseReceiver je
zpravidla anonymní vnitřní třída v metodě některého z kontrolerů. V kontroleru by
při obdržení této výjimky došlo k zavolaní metody disconnected na příslušné
zobrazovací instanci (tuto metodu by bylo nutno přidat do definice ViewInterface
a následně i implementovat v příslušných třídách). V implementaci této metody by
poté byl spuštěn AsyncTask, který by se pokusil o vytvoření nového připojení.
V případě úspěchu by byl vytvořen nový kontroler, který by už disponoval aktivním
spojením, se kterým by mohl pracovat.
Problémem tohoto přístupu je případ, kdy při pokusu o obnovu spojení stále ještě
není internetové připojení dostupné a tento pokus selže. Řešením by mohlo být pokus
několikrát s časovým odstupem opakovat, to by ovšem mohlo trvat poměrně dlouho
a pro uživatele být nevhodné. Další nevýhodou by byla poměrně vysoká
implementační složitost.
4.3.2. BroadcastReceiver pro zjištění změn sítě
Bylo zvažováno předchozí řešení rozšířit popř. nahradit pomocí třídy
BroadcastReceiver, která umožňuje odchytávat zasílané Intent objekty a pracovat
s nimi. V manifestu aplikace je možné registrovat určitý BroadcastReceiver jako
příjemce určitých typů zpráv. V tomto případě byly odchytávány zprávy o změně stavu
sítě pomocí následující definice v manifestu aplikace: [28]
<application<!-- vynechané elementy --><receiver
android:name=".helpers.NetworkChangeReceiver"android:label="NetworkChangeReceiver" ><intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" /><action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
</intent-filter></receiver><!-- vynechané elementy --> />
24
Page 32
Ve třídě NetworkChangeReceiver (potomek třídy BroadcastReceiver) v balíčku .helpers
byla přepsána metoda onReceive a v ní vždy ověřen stav spojení. Při testování na
emulovaném zařízení vše probíhalo dle očekávání a zprávy o změně stavu sítě byly
přijímány korektně a téměř okamžitě po té, co ke změně došlo. Problém nastal při
testování na fyzickém zařízení Huawei Ascend G300 s Android verze 4.4.4
(CyanogenMod v11), kde byly totožné zprávy o změně stavu sítě zasílány několikrát.
Podle diskuze na serveru stackoverflow.com [29] se jedná o problém specifický pro
některá zařízení. Řešením tohoto neočekávaného chování je udržování poslední
přijaté změny a její porovnání s právě přijímanou. V případě, že hodnoty stavu jsou
totožné, jedná se o vícenásobné odeslání a změna ve stavu sítě ve skutečnosti
nenastala.
Největším problémem se ale ukázala prodleva mezi změnou stavu sítě a přijetím
zprávy. V některých případech se jednalo až o desítky sekund. Za tuto dobu se mohl
stav aplikace změnit a obnovení spojení již není nutné, či je dokonce nežádoucí, neboť
uživatel se k routeru připojil ručně. Z těchto důvodů bylo toto řešení zamítnuto.
4.3.3. Ruční obnova spojení
Jako nejvhodnější řešení se ukázala ruční obnova spojení iniciovaná uživatelem.
V případě, že dojde ke ztrátě spojení při vykonávání příkazu, je o tom uživatel
informován chybovou zprávou a v menu všech patřičných aktivit je mu snadno
dostupná položka pro znovupřipojení, to je vykonáno v pozadí pomocí AsyncTask
a o výsledku je uživatel informován pomocí Toast zprávy. V případě úspěchu je
případně vykonána akce pro znovu načtení zobrazovaných hodnot. Pokus o obnovu
spojení může kromě absence připojení selhat z důvodu, že proces aplikace byl
ukončen (neboť byl např. dlouho v pozadí a systém potřeboval uvolnit prostředky)
a vytvořen znovu. To znamená, že byla vytvořena i nová instance singleton správce
připojení a tím ztracen odkaz na Router, ke kterému má být vytvořeno spojení. To lze
vyřešit perzistentním ukládáním informací o routeru například ve sdílených
preferencích (SharedPreferences), avšak není to nezbytně nutné, neboť pokud byla
aplikace tak dlouho v pozadí, že její proces byl ukončen, je pravděpodobné, že uživatel
nebude chtít pokračovat tam, kde skončil, když z aplikace odcházel a postačí
zobrazení hlavní aktivity se seznamem routerů.
25
Page 33
Tento přístup byl nakonec zvolen, jelikož umožňuje uživateli mít přesně pod
kontrolou vytváření spojení k routeru. Nedochází ke snahám o připojení na pozadí
a k zbytečnému využívání prostředků. Zároveň pro uživatele není obnova spojení
nijak výrazně obtěžující.
4.4. XML znázornění příkazového stromu
Na rozdíl od konzole, API neposkytuje příkazovou nápovědu pomocí příkazu „?“, která
by vracela dostupné příkazy v dané úrovni popř. jména atributů dané položky. Ačkoliv
na tuto API funkcionalitu byl vznesen požadavek [30], podle reakce zaměstnance
podpory MikroTik má tato funkcionalita nízkou prioritu.[31]
Je tedy nutné dopředu znát strukturu příkazového stromu, atributy jednotlivých
položek a jejich typ (zda se jedná např. o volný řetězec, celočíselnou hodnotu, výběr
z předem známých hodnot, odkaz na jinou položku či pravdivostní hodnotu). Pro
účely perzistentního uložení této struktury se nabízely dvě možnosti: relační databáze
či značkovací soubor XML. Z důvodu snadnější čitelnosti a upravitelnosti byl zvolen
přístup využívající XML, ovšem není vyloučeno, že v budoucnu se relační databáze
ukáže jako vhodnější díky snadnější práci s vazbami mezi jednotlivými prvky.
4.4.1. Elementy <branch> a <tabsBranch>
Tyto elementy znázorňují větvě příkazového stromu a umožňují navigaci v menu. Mají
dva atributy a to sice name (zobrazované, uživatelsky přívětivé jméno) a command
(jak bude daná větev reprezentována v samotném příkazu). Hodnota atributu
command je zpravidla uvozena lomítkem neboť konečný příkaz (resp. jeho část
značící kontext, tedy v jaké větvi má být vykonán) je skládán prostým spojením
hodnot command všech rodičovských elementů.
Rozdíl mezi těmito elementy spočívá ve způsobu jejich zobrazování. Zatímco
u <branch> mohou být potomky elementy <list> i <scan> (v libovolném počtu
a kombinaci), kde každý z nich je zobrazován jako samostatná položka menu, tak
v případě elementu <tabsBranch> mohou být potomky pouze elementy <list>, které
ovšem nejsou zobrazovány jako položky menu, ale jako jednotlivé záložky (tabs)
26
Page 34
v zobrazovacím okně (aktivitě). Tento způsob se snaží co nejvěrněji imitovat
uživatelské rozhraní aplikace Winbox tak, jak na něj jsou uživatelé zvyklí.
4.4.2. Element <list>
Tento element reprezentuje úroveň stromu, ve které je dostupný příkaz „print“. Jedná
se tedy o seznam položek, nad kterými je možno volat další příkazy. Stejně jako
elementy <branch> a <tabsBranch> disponuje atributy name a command. K nim ještě
přibyly pravdivostní argumenty reprezentující dostupné příkazy nad jednotlivými
položkami. Jsou jimi:
• addable – zda má uživatel možnost přidat novou položku
• commentable – zda jsou u položek evidovány komentáře a lze je upravovat
• disablable – zda je možné položku aktivovat/deaktivovat aniž by musela být
přidána/odebrána
• removable – zda lze položku odebrat
V tomto elementu mohou být potomky elementy <property> (reprezentace atributu
pro položku daného typu), <propertyTab> (reprezentace záložky atributů) či <scan>
(reprezentace dostupnosti příkazu skenovacího typu).
4.4.3. Element <propertyTab>
V případě, že položka daného kontextu má mnoho atributů, je pro přehlednost
vhodné tyto rozdělit do záložek (tabů). Je tomu stejně i u desktopové aplikace Winbox.
K tomu slouží tento element a jeho atribut name, jehož hodnota je poté přiřazena
objektu Property (resp. jeho poli propertyTabName) a identifikuje, ke které záložce má
být daný atribut přiřazen, musí být tedy v daném kontextu jedinečná. V případě, že je
element <property> přímým potomkem elementu <list>, je tato hodnota při parsování
nastavena na atribut name nadřazeného listu. Potomky tohoto elementu mohou být
pouze elementy <property>.
4.4.4. Element <property>
Tento element reprezentuje atribut položky daného kontextu. Např. v kontextu
/ip/address to jsou mj. address, interface, netmask, network. Opět disponuje atributy
name a command (který v tomto případě reprezentuje jméno daného atributu tak, jak
27
Page 35
ho očekává a odesílá MikroTik zařízení). Dalším atributem je type, pro označení typu
atributu, jehož přípustné hodnoty jsou uvedeny v tabulce 6.
Hodnota Význam
string volný řetězec znaků
int celočíselná hodnota
bool pravdivostní hodnota (true/false)
listDynamic výběr jedné hodnoty z proměnného seznamu, který obsahuje
hodnoty získané z aktuální konfigurace zařízení (tedy např.
jména rozhraní pro atribut interface u IP adresy)
listStatic výběr jedné hodnoty ze statického seznamu předem známých
hodnot
multipleBool čárkami oddělený seznam hodnot, ve kterém jsou možné
hodnoty předem známy a uživatel vybere, které z nich mají
být součástí výsledného řetězce
multipleString čárkami oddělený seznam uživatelem určeného počtu volných
řetězcových hodnot
multipleInt čárkami oddělený seznam uživatelem určeného počtu
celočíselných hodnot
multipleListDynamic čárkami oddělený seznam uživatelem určeného počtu hodnot
ze seznamu získaného ze zařízení (obdobně jako listDynamic)
multipleListStatic čárkami oddělený seznam uživatelem určeného počtu hodnot
ze statického, předem známého seznamu
Tab. 6: Přehled typů atributů a jejich význam
Pro všechny typy kromě string, multipleString, int, multipleInt a bool je také nutný
atribut listItems, který definuje z jakých položek lze vybírat. Pro typy s dynamickým
seznamem (tedy u kterých, je potřeba nejdříve zjistit aktuální hodnoty konfigurace) je
hodnota ve formátu: [statickáPoložka1,statickáPoložka2,...]kontext.jmenoAtributu
(např.: „all,none,/interface/wireless.name“ pro seznam jmen všech bezdrátových
rozhraní a statické možnosti „all“ a „none“). V ostatních případech se jedná o řetězec
čárkami oddělených hodnot.
28
Page 36
Dalšími atributy elementu <property> jsou pravdivostní atributy showInTable (ten
určuje, zda má být hodnota zobrazena v přehledové tabulce všech položek daného
kontextu), showInDetail (určující, zda má být v detailu položky zobrazeno pole
s hodnotou daného atributu) a editable (zda má být hodnota tohoto atributu
uživatelem upravitelná).
V případě typů string a multipleString lze také nadefinovat atributy regex
a validationError pro informování uživatele o chybně zadané hodnotě. Regex vyjadřuje
regulární výraz odpovídající validní hodnotě a validationError chybu, která má být
uživateli zobrazena v případě, že zadaná hodnota neodpovídá danému regulárnímu
výrazu. Vzhledem k častému opakování stejných validačních kritérií (např. IP či MAC
adresa) byl pro zmenšení objemu dat i celkové zjednodušení a zpřehlednění XML
souboru zaveden element <validationPair>, který zastřešuje validační kritéria a na
který se lze odkazovat pomocí hodnoty atributu id. Není tedy nutné pokaždé opakovat
(v některých případech značně dlouhý) regulární výraz. Příklad užití tohoto elementu
je uveden v následující ukázce:
<root><validationPairs>
<validationPairid="time"regex="^((\d+)(w|h|m|s))+$|^((\d+:){2}\d+)$"validationError="expected e.g.: 1w2d3h4s or 1:20:30"/>
</validationPairs><!-- vynechané elementy -->
<propertyname="Lease Time" command="lease-time" type="string" validationID="time"/>
</root>
4.4.5. Element <scan>
Element <scan> reprezentuje příkaz skenovacího typu. Tedy takový, ve kterém je
odeslán příkaz a router vrací odpovědi dokud není jeho vykonávání klientskou
aplikací (uživatelem) ukončeno popř. dokud nevyprší počítadlo. Příkladem může být
příkaz ping či frekvenční skenování u bezdrátových rozhraní.
Tento element může být umístěn buďto do elementu <branch> (v takovém případě
bude zobrazen jako položka menu a není svázán s žádnou existující položkou
konfigurace) nebo do elementu <list> (pokud je příkaz svázán s některou z položek
29
Page 37
daného kontextu). V případě, že je příkaz potomkem elementu <list>, je nutné
specifikovat jméno atributu položky, podle kterého je identifikována konkrétní
položka, nad kterou je příkaz vyvolán (zpravidla je to vnitřní identifikátor „.id“). Tato
specifikace je provedena pomocí XML atributu itemIdPropertyName.
Dalším XML atributem je pravdivostní addOnly, který určuje, zda se vracené položky
pouze přidávají (jako je tomu u příkazu ping) nebo zda může být navrácena položka,
odpovídající některé dříve přijaté s novými hodnotami, které mají nahradit ty staré
(např. skenování bezdrátových sítí). Vzhledem k tomu, že u takto vracených položek
není přidáván jednoznačný identifikátor (jako „.id“ u položek konfigurace) je nutné
specifikovat XML atribut returnItemIdPropertyName, označující, který navracený
atribut má být jako takovýto identifikátor použit.
Potomky tohoto elementu mohou být elementy <property> (pro reprezentaci
vstupních parametrů daného příkazu) a <returnProperty> (tedy navracený atribut),
u kterého není nutné sledovat typ, neboť uživatel toto pole nemá možnost měnit
a jedná se vždy o řetězec znaků. Proto má pouze dva XML atributy, a to sice name
a command se stejným významem jako element <property>.
4.5. Model příkazového stromu
Elementy XML jsou pomocnou třídou XMLParser parsovány (převáděny) na instance
modelových tříd. Struktura, hierarchie a vazby těchto tříd jsou znázorněny na obr. 3,
který obsahuje zjednodušený diagram, ve kterém byly pro přehlednost vynechány get
a set metody.
30
Page 38
Obr. 3: Zjednodušený diagram tříd příkazových větví
4.5.1. Třídy příkazového stromu
Třída Branch představuje předka pro jednotlivé typy příkazových větví, kterým
poskytuje pole name a command (včetně jejich veřejných get a set metod).
Třída MenuBranch znázorňuje navigační větev, tedy takovou, která pouze tvoří
strukturu příkazového stromu. Tato třída si udržuje seznam dceřiných větví a také
odkaz na větev, která je jí nadřazená. Této třídě odpovídá XML element <branch>.
Dceřiná třída TabsBranch odpovídá svému XML protějšku, tedy elementu
<tabsBranch>. Jedná se o třídu zastřešující seznam ListBranch tříd (tedy tříd
představujících větve, ve kterých je dostupný příkaz print), kde pro každou z nich má
být vytvořena záložka (tab) s tabulkou.
31
Page 39
Třída ListBranch si uchovává mj. seznam atributů (tříd Property), které jsou dostupné
pro položky daného kontextu. Zároveň disponuje seznamem názvů tabů, do kterých
mají být při zobrazení detailu rozřazena pole s hodnotami jednotlivých atributů.
Dalším atributem je seznam větví s příkazy skenovacího typu, které jsou nad
položkami dostupné.
Třída ScanBranch si uchovává seznam vstupních atributů properties, seznam
návratových atributů returnProperties a také příkazové názvy atributů, které
identifikují položku, nad kterou je příkaz vykonán a navrácenou položku. Posledním
atributem je boolean addOnly, který značí, zda mají být přijaté položky pouze
přidávány či případně i upravovány jejich dříve přijaté hodnoty.
Třída Property představuje atribut položky daného kontextu. Mezi její atributy patří:
• String name – uživatelsky přívětivé jméno atributu
• String command – příkazové jméno atributu, tedy takové, se kterým pracuje
MikroTik zařízení
• boolean showInTable, showInDetail – pole určující, zda má být hodnota
zobrazena v přehledové tabulce resp. detailu dané položky
• boolean editable – zda má být daný atribut uživatelem upravitelný
• String propertyTabName – jméno záložky detailu, do které má být tento atribut
zařazen
• PropertyType type – enumerace označující typ parametru (možné hodnoty
odpovídají typům uvedeným v kapitole 4.4.4 Element <property>)
• String listItems – řetězec vyjadřující možné hodnoty v případě výběrového
typu, jehož formát je přiblížen ve výše zmíněné kapitole 4.4.4 Element
<property>
• String regex, validationError – regulární výraz značící validní hodnotu
a případná chybová zpráva pro uživatele
4.5.2. Rozhraní Parcelable
Většina z těchto tříd musí být předávána mezi aktivitami či ukládána při jejich
ukončování (pro případ nutnosti jejich obnovení). K tomu je využíván objekt Bundle
a jeho mapování objektů pomocí řetězcových klíčů. Tyto objekty ovšem musí být
32
Page 40
primitivního typu nebo musí implementovat rozhraní Serializable či Parcelable. Tyto
rozhraní ve výsledku umožňují „zploštění“ objektu (tedy jeho převod na proud bytů)
a následné vytvoření kopie na základě jeho zploštělé formy. Rozhraní Parcelable je
součástí Android API a oproti Serializable nevyužívá reflexi pro správné zploštění a je
tedy méně výkonnostně náročné. Jeho nevýhodou je nutnost implementovat metodu
writeToParcel (která objekt převádí na instanci třídy Parcel, do které jsou vkládány
hodnoty jednotlivých atributů třídy) a konstruktor s jediným parametrem typu Parcel,
který zajišťuje vytvoření instance na základě hodnot v něm obsažených.[32]
Obě rozhraní vyžadují, aby všechny atributy dané třídy byly opět primitivního typu, či
typu, který implementuje dané rozhraní. Jednotlivé třídy jsou zpracovávány postupně,
nezávisle na sobě. V případě, že jednotlivé třídy na sebe udržují kruhové reference,
nelze tento přístup použít, neboť dojde k zacyklení volání metod. Mj. z tohoto důvodu
třídy MenuBranch a Branch žádné z těchto rozhraní neimplementují.
Při výběru mezi Parcelable a Serializable byla dána přednost výkonnosti na úkor
znovupoužitelnosti. V případě, že by tyto modelové třídy měly být použity v jiné
aplikaci, která by nebyla určena pro platformu Android, odstranit z těchto tříd
implementaci rozhraní popř. jí nahradit jinou nepředstavuje významnou zátěž.
4.6. Třídy kontrolerů a zobrazovací rozhraní
Pro odstínění zobrazovací vrstvy od vrstvy komunikační byla zavedena mezivrstva
„kontrolerů“, tedy tříd, které poskytují metody pro odesílání příkazů routeru
a zpracovávají přijaté odpovědi na modelové třídy. Tato architektura ve spojení
s využitím rozhraní zvyšuje přehlednost kódu, odstínění tříd a umožňuje snadnější
úpravu či nahrazení tříd zajišťujících tvorbu uživatelského rozhraní. Další výhodou je
možnost znovupoužití těchto tříd i v jiné aplikaci a na jiné platformě s pouhou
úpravou tříd implementujících zobrazovací rozhraní. Diagram na obr. 4 znázorňuje
strukturu a metody rozhraní zobrazovací vrstvy a tříd kontrolerů.
33
Page 41
Třídy kontrolerů a zobrazovací rozhraní si navzájem odpovídají. Třídy kontrolerů si
udržují referenci na třídu implementující příslušné zobrazovací rozhraní. Jejich
metody nemají návratovou hodnotu, protože jsou vykonávány asynchronně, resp. na
základě volání metody kontroleru je routeru odeslán příkaz, po jehož vykonání je
volána příslušná metoda zobrazovacího rozhraní.
34
Obr. 4: Diagram rozhraní zobrazovací vrstvy a tříd kontrolerů
Page 42
Ačkoliv komunikační modul poskytuje možnost odesílání příkazů synchronně, obecně
není vhodné (a v Android verze Honeycomb a vyšší dokonce dovolené [33]), aby
aplikace vytvářela či pracovala (čekala na odpověď) se síťovými spojeními ve svém
hlavním (UI) vlákně, neboť tyto operace mohou být časově náročné a tedy způsobit
„zamrznutí“ uživatelského rozhraní (Android po 5 vteřinách, kdy aplikace neodpovídá
zobrazuje „Application not responding“ – ANR dialog, ve kterém nabízí uživateli její
ukončení [34]). Z tohoto důvodu není využíváno synchronních dotazů, neboť ty by
stejně musely být volány z nových vláken popř. pomocných AsyncTask tříd.
Třídy kontrolerů získávají v konstruktoru přístup k instanci připojení pomocí
singleton třídy ConnectionManager a její metody getConnection. V této fázi už musí být
spojení vytvořeno, neboť kontroler není asociován s třídou Router a nemá tedy
přehled, nad jakým routerem jsou příkazy vykonávány. Dále je v konstruktoru vždy
předávána instance implementující příslušné zobrazovací rozhraní (nad kterou budou
volány metody pro zobrazování výsledku), odpovídající typ příkazové větve a popř.
hodnota identifikátoru položky.
4.6.1. Třída ListController
Třída ListController poskytuje jeden konstruktor s parametry ListViewInterface (tedy
instance implementující zobrazovací rozhraní, nad kterou budou volány příslušné
zobrazovací metody) a ListBranch (tedy příkazovou větví, nad kterou mají být
vykonávány API příkazy). Ve svém konstruktoru si třída připraví na základě jmen
atributů dané větve řetězec proplist (určující, které atributy mají být vráceny) a také
spojovou mapu propertyCommandToValue (ve které jsou klíči příkazová jména
atributů a všechny hodnoty jsou nastaveny na „null“), jejíž klon je nastaven všem
parsovaným instancím třídy ListItem a následně jsou jednotlivé hodnoty nastaveny
podle přijaté odpovědi.
Třída dále poskytuje metody pro odeslání dotazů spojených s přehledovým
seznamem (tabulkou) položek. Jsou jimi:
• getAll() – Pro odeslání dotazu na všechny položky větve. Obdržené výsledky
jsou pomocí metody parseToListItem převáděny na instance třídy ListItem,
35
Page 43
které jsou po přijetí dokončovací věty odeslány v seznamu jako argument
metody showTable zobrazovací třídě.
• listen() – Pro odeslání „listen“ příkazu pro sledování změn v položkách. Při
každém přijetí výsledku je opět využita metoda parseToListItem a výsledek
předán jako parametr metody updateRow zobrazovací třídě.
• stopListening() – Pro odeslání příkazu „cancel“ s odpovídajícím tagem, který
ruší sledování změn.
• remove(String itemID) – Pro odeslání příkazu „remove“ odstraňujícího položku
s daným identifikátorem. Výsledek tohoto příkazu (stejně jako následujících
dvou) není nutné explicitně zobrazovat, neboť tuto změnu zachytí a zobrazí
příkaz „listen“.
• setComment(String itemID, String comment) – Pro odeslání příkazu „set“, který
nastavuje hodnotu atributu comment u položky s daným identifikátorem.
• toggleEnabled(String itemID, boolean enabled) – Pro odeslání příkazu „set“,
který nastavuje hodnotu atributu enabled (tedy zda je položka aktivní či ne)
u položky s daným identifikátorem.
4.6.2. Třída AddController
Další kontrolerovou třídou je AddController, která poskytuje metodu pro získání
dostupných hodnot atributů getProperties. Kód této metody je vykonáván v novém
vlákně, neboť dostupné hodnoty jsou pro jednoduchost získávány pomocí
synchronních příkazů a není vhodné, aby na odpovědi čekalo volající vlákno. Metoda
využívá pomocnou metodu buildPropertyItems, která na základě seznamu tříd
Property vytváří seznam obsahující instance PropertyItem a v případě, že daná
Property je typu dynamického seznamu, získává dostupné hodnoty pomocí metody
getAvailableValues. Po zpracování všech atributů dané větve je volána metoda show na
zobrazující třídě.
Dále také poskytuje metodu update pro odeslání příkazu „add“, který přidává položku
s určenými hodnotami. Tyto hodnoty jsou obsaženy v předávaném seznamu
s instancemi PropertyItem a jejich atributu newValue. Po obdržení výsledku či chyby je
volána metoda updateResult pro zobrazení výsledku s patřičnou pravdivostní
hodnotou značící úspěch a popř. chybovou zprávou.
36
Page 44
4.6.3. Třída DetailController
Potomkem třídy AddController je třída DetailController, která je svou funkcí podobná
svému předkovi. Rozdíl je v metodě getProperties, která nejenže zjišťuje možné
hodnoty, ale zároveň zjišťuje hodnoty aktuálně nastavené pro danou položku.
Přepsaná je také metoda buildUpdateCommand (vytvářející počáteční část příkazu
ukládajícího změny), ve které vkládá místo řetězce „add“ řetězec „set“ pro nastavení
nových hodnot dané položce.
4.6.4. Třída ScanController
Poslední kontrolerovou třídou je ScanController, která poskytuje metodu pro zahájení
„skenování“ startScan s parametrem typu List<PropertyItem> (který obsahuje
parametry skenovacího příkazu a jejich hodnoty) a bezparametrový příkaz stopScan
pro odeslání „cancel“ příkazu zastavující skenování. Při obdržení odpovědi je tato
převedena na instanci třídy ListItem a předána k zobrazení metodou showOneResult.
4.6.5. Zobrazovací rozhraní
Obdobně jako třídy kontrolerů jsou i zobrazovací rozhraní rozdělena podle typů
metod, které nabízejí. Jejich společný předek ViewInterface poskytuje metody
showError(String) a showDisconnectError() pro zobrazení případných chyb.
Rozhraní ListViewInterface poskytuje metodu pro zobrazení přehledu položek
a hodnot jejich základních atributů showTable, ve které je parametrem seznam jmen
sloupců tabulky (tedy jmen atributů) a seznam instancí třídy ListItem, která
představuje jednu navrácenou položku. Dále také poskytuje metodu pro úpravu již
zobrazené hodnoty update s parametrem ListItem, který představuje položku
s novými hodnotami.
DetailViewInterface je rozhraním pro zobrazení detailu položky. K tomu poskytuje
metodu show, jejímž parametrem je seznam instancí třídy PropertyItem. Dále také
definuje metodu updateResult, která slouží k zobrazení výsledku příkazu, který ukládá
uživatelem změněné hodnoty. Tato metoda má parametry boolean (zda uložení
proběhlo úspěšně) a String (obsahující případnou chybovou zprávu, proč uložení
selhalo).
37
Page 45
ScanViewInterface je rozhraním pro zobrazení výsledků příkazu skenovacího typu.
Poskytuje metodu showOneResult, jejímž parametrem je (vzhledem k podobnosti
se zobrazováním seznamu položek) třída ListItem. Druhou metodou je setRunning
s parametrem boolean, která nastavuje popř. zobrazuje, zda příkaz právě probíhá. Tato
metoda je nutná, neboť je vhodné mít v zobrazovací části přehled o stavu příkazu
a nelze spoléhat na to, že odeslání příkazu skutečně znamená, že je vykonáván.
4.6.6. Třídy ListItem a PropertyItem
Pro komunikaci mezi třídami kontrolerů a zobrazovacími rozhraními byly zavedeny
třídy ListItem a PropertyItem, které představují navrácené odpovědi od routeru
s patřičnými hodnotami.
Třída ListItem představuje jednu „!re“ odpověď routeru. A poskytuje pomocné
atributy pro identifikátor položky, pravdivostní atributy dead a disabled a také mapu,
ve které jsou klíči příkazové názvy atributů položky a hodnotami jsou řetězce hodnot
přijaté od zařízení.
Třída PropertyItem představuje jeden atribut u vrácené položky. Je jí využíváno při
zobrazování detailu položky, neboť jejími atributy jsou: třída Property (ke které se
váže), hodnota atributu (tak, jak byla obdržena od routeru), nová hodnota zadaná
uživatelem a případně seznam dostupných hodnot.
4.7. Aktivita MainActivity
MainActivity je hlavní aktivitou aplikace a při jejím startu je jako první spuštěna
a zobrazena. Hlavním prvkem aktivity je seznam routerů získaných z SQLite databáze.
Při dlouhém stisku položky je zobrazeno kontextové menu, které nabízí operace se
zvolenou položkou. Těmito operacemi jsou: ověření stavu routeru pomocí příkazu
„ping“, odstranění routeru, jeho úprava a restartování. Při krátkém stisku položky je
vytvořeno spojení ke zvolenému routeru a zobrazena ListTabsActivity, která
zpřístupňuje konfiguraci routeru. Hlavní menu aktivity poskytuje volbu přidání
položky, obnovení (znovu-načtení) seznamu routerů z databáze a import seznamu
routerů z .wbx souboru.
38
Page 46
4.7.1. ListView a RouterArrayAdapter
Seznamy položek jsou zobrazovány pomocí elementu ListView, který vyžaduje třídu
ArrayAdapter<> (či jejího potomka) pro správu položek k zobrazení. Třída
ArrayAdapter je generická, pracuje tedy s libovolným typem objektů a v seznamu
zobrazuje řetězec vracený metodou toString nad daným objektem. V případě, že je
vyžadováno složitější rozvržení prvků a zobrazování hodnot v položce seznamu, je
nutné implementovat vlastní třídu (která je potomkem třídy ArrayAdapter) a přepsat
její metodu getView, která navrací instanci třídy View, která odpovídá jedné položce
seznamu.[35]
Pro optimalizaci výkonu (omezení počtu vytváření nových View) je parametrem
metody getView i convertView, který představuje instanci zobrazovací položky
seznamu, která již nadále nemá být zobrazována (je např. „odscrollována“ mimo
obrazovku). Tato položka potom může být znovu použita (zobrazena) s novými
hodnotami.[35] Přepsaná metoda getView vnitřní třídy RouterArrayAdapter je ve
zjednodušené formě uvedena v následující ukázce:
static final int resource = R.layout.routerlist;
@Overridepublic View getView(int position, View convertView, ViewGroup parent) {
RelativeLayout alertView;Router router = getItem(position);if (convertView == null) {
// Pokud se nejedná o upravování již existující položky, musí být // vytvořena nová s patřičným rozvržením prvků.alertView = new RelativeLayout(getContext());String inflater = Context.LAYOUT_INFLATER_SERVICE;LayoutInflater vi;vi = (LayoutInflater) getContext().getSystemService(inflater);vi.inflate(resource, alertView, true);
} else {alertView = (RelativeLayout) convertView;
}// Nastavení příslušné hodnoty pro zobrazovací pole.TextView txtName = (TextView) alertView.findViewById(R.id.txtName);txtName.setText(router.getName());// Vynechaná nastavování dalších zobrazovacích polí.return alertView;
}
39
Page 47
4.7.2. Kontextové menu
Kontextové menu (stejně jako hlavní menu aktivity) může být vytvořeno (resp.
naplněno zobrazovacími položkami) na základě XML souboru. Metoda
onCreateContextMenu je volána pokaždé, když má být zobrazeno kontextové menu, je
to tedy vhodné místo pro vytváření menu. Parametrem metody je mj. i zobrazovací
prvek (View), nad kterým je kontextové menu otevíráno. Je tedy možné zobrazovat
různá kontextová menu pro různé položky.[36]
Rozhodnutí, jaká akce má být provedena po zvolení položky kontextového menu, je
prováděno v metodě onContextItemSelected za pomoci jejího argumentu item
(vybraná položka menu) a jeho identifikátoru. Pomocí třídy ContextMenuInfo (jejíž
instanci lze také získat z argumentu item) lze zjistit index zvoleného prvku v seznamu
a tedy odpovídající instanci třídy Router. Návratová hodnota této metody označuje,
zda bylo volání zpracováno a nemá být propagováno dále (nadřazeným třídám či
fragmentům v aktivitě).[36] Způsob zpracování výběru položky kontextového menu je
znázorněno v následující ukázce:
@Overridepublic boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo acmi;acmi = (AdapterContextMenuInfo) item.getMenuInfo();final Router selectedRouter = (Router)
listView.getAdapter().getItem(acmi.position);int id = item.getItemId();switch (id) {case R.id.action_open:
new RouterOpener().execute(selectedRouter);return true;
// Vynechané "case" možnosti.default:
return super.onContextItemSelected(item);}
}
4.7.3. Asynchronní úlohy AsyncTask
Potencionálně časové náročné operace (práce s databází, vytváření síťového spojení,
import z .wbx souboru atp.) není vhodné vykonávat v hlavním vlákně. Proto
MainActivity disponuje řadou vnitřních tříd, potomků třídy AsyncTask, která poskytuje
40
Page 48
metody pro poměrně snadné vykonávání činností v novém vlákně a synchronizaci
výsledků s hlavním UI vláknem.
Hlavní poskytované metody jsou doInBackground (vykonávána v separátním vlákně),
onPreExecute (vykonávána na hlavním vlákně před voláním doInBackground),
onPostExecute (vykonávána na hlavním vlákně po dokončení metody
doInBackground) a onProgressUpdate (vykonávána na hlavním vlákně po volání
publishProgress z metody doInBackground).[37]
Třída AsyncTask využívá variabilní argumenty (varargs), které umožňují volat metodu
s libovolným počtem argumentů daného typu.[38] Dále je také generická, neboť
umožňuje definovat typy parametrů jednotlivých metod. V následujícím příkladě
definice třídy jsou atributy metody doInBackground typu Router, metody
publishProcess typu Integer stejně jako metody onPostExecute.
private class RoutersSaver extends AsyncTask<Router, Integer, Integer> {...}
Pro informování uživatele o probíhající akci na pozadí je využíváno třídy
ProgressDialog (dialog s animací značící načítání), který je s patřičnými informacemi
zobrazován v metodě onPreExecute a schováván v metodě onPostExecute popř.
onCancelled (v případě, že je AsyncTask zastaven).
4.7.4. Třída DBManager
Pro čtení a zapisování dat do SQLite databáze je využito třídy DBManager, která dědí
po třídě SQLiteOpenHelper a přepisuje její metody onCreate a onUpdate. Tyto metody
zajišťují vytvoření databáze (popř. její úpravu po zvýšení čísla verze) pomocí instance
třídy SQLiteDatabase (která je předávána jako argument těchto metod) a její metody
execSQL, která vykonává SQL příkazy. Ve třídě je nadefinována řada statických
finálních řetězců obsahující jména tabulek, sloupců a příkazů pro jejich vytvoření.
Toto není nezbytně nutné, ale usnadňuje to práci s databází a provádění případných
změn, neboť jména a příkazy jsou měněny na jednom místě.
Ve svých metodách pro získávání a zapisování modelových tříd třída DBManager
využívá některé z celé řady metod pro získávání, přidávání, úpravu a mazání dat,
poskytovaných třídou SQLiteDatabase. Za zmínku stojí rozhraní Cursor, které je
41
Page 49
návratovým typem mnohonásobně přetížených query metod, které provádí dotazy na
data z databáze. Nad objektem Cursor lze postupně procházet výsledky a dotazovat se
na hodnoty jednotlivých sloupců.[39] Tento způsob získávání dat je znázorněn
v následující ukázce dotazu na všechny routery:
SQLiteDatabase db = this.getReadableDatabase();List<Router> routers = new ArrayList<Router>();Cursor c;c = db.query(TABLE_ROUTERS, null, null, null, null, null, COLUMN_NAME_ROUTER);if (c.moveToFirst()) {
do {Router router = new Router();router.setId(c.getInt(c.getColumnIndex(COLUMN_ID_ROUTER)));router.setName(c.getString(c.getColumnIndex(COLUMN_NAME_ROUTER)));// Vynechány další analogické sloupce.routers.add(router);
} while (c.moveToNext());}
Metody pro zápis do databáze mohou jako jeden z argumentů přijímat instanci třídy
ContentValues, do které jsou pomocí metody put vkládány klíče (jména sloupců)
a hodnoty.[40] Přidání routeru do databáze tedy může vypadat následovně:
public int addRouter(Router router) {SQLiteDatabase db = this.getWritableDatabase();return (int) db.insert(TABLE_ROUTERS, null, createRouterValues(router));
}
private ContentValues createRouterValues(Router router){ContentValues values = new ContentValues();values.put(COLUMN_NAME_ROUTER, router.getName());values.put(COLUMN_USERNAME, router.getUserName());// Vynechány další analogická mapování.return values;
}
4.7.5. Import z .wbx souboru
Aby uživatelé nemuseli všechna zařízení vkládat do aplikace ručně, bylo
implementováno parsování z .wbx souboru (tedy souboru, do kterého aplikace
Winbox umožňuje seznam routerů exportovat).
Import je spuštěn z hlavního menu MainActivity a v prvotní fázi je uživateli zobrazen
dialog pro výběr souboru, ze kterého mají být routery naimportovány. Na rozdíl od
javovské knihovny Swing, neposkytuje Android API žádný nativní dialog pro výběr
souboru. Za tímto účelem byla vytvořena třída FileChooserDialog jako potomek třídy
42
Page 50
Dialog, která zobrazuje seznam adresářů a souborů pomocí ListView a potomka třídy
ArrayAdapter, který do položek seznamu vkládá jména souborů a adresářů (popř.
řetězec „/..“ na první místo v seznamu pro vstup na vyšší úroveň). Pro korektní pořadí
zobrazených položek (tedy nejprve adresáře seřazené podle jména a poté soubory
seřazené podle jména) jsou při změně adresáře jeho podadresáře a soubory
rozděleny do dvou seznamů, které jsou následně nezávisle na sobě seřazeny
a sloučeny (resp. soubory jsou přidány k podadresářům). Skryté soubory (metoda
isHidden vrací pravdu) a ty, ke kterým nemá aplikace přístup (metoda canRead vrací
nepravdu) jsou ignorovány a nejsou zobrazovány. Do manifestu aplikace také bylo
nutné přidat následují povolení pro čtení z paměťové karty:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
FileChooserDialog také definuje vnitřní rozhraní FileSelectedListner s jedinou metodou
onFileSelected(File file), která je volána v případě, že uživatel vybere soubor. Zobrazení
tohoto dialogu může být z aktivity provedeno následujícího způsobem:
new FileChooserDialog(this, "/", new FileChooserDialog.FileSelectedListener() {
@Overridepublic void onFileSelected(File file) {
// Metody pro zpracování vybraného souboru.}
}).show();
Samotné parsování souboru je prováděno třídou WBXParser, resp. jeho statickou
metodou parseWBX s parametrem InputStream, který určuje jaký proud dat má být
parsován. Samotný formát dat ve .wbx souboru není na první pohled zcela zřejmý, ale
jak vyplynulo z diskuze na serveru localloop.co.za [41] jedná se o páry klíčů a hodnot,
které před sebou mají specifickým způsobem zakódovanou svou délku. Pořadí
zakódovaných hodnot je následující:
• byte, jehož hodnota = délka klíče + délka hodnoty + 1
• byte s nulovou hodnotou
• byte jehož hodnota odpovídá délce klíče
• klíč
• hodnota
43
Page 51
Záznam pro jeden router vždy začíná klíčem „type“ a končí klíčem „pwd“ značícím
uživatelské heslo. Kvůli tomuto kódování byla dána přednost čtení souboru po bytech
před čtením po řádcích či jinou metodou.
4.8. Aktivita ListTabsActivity
Tato aktivita je spouštěna po výběru položky z hlavní aktivity a obsahuje výsuvné
menu pro navigaci v příkazovém stromu a zobrazovací prvek ViewPager, který
umožňuje zobrazování fragmentů. Aby aktivita mohla zobrazovat lištu záložek je
nutné, aby byla potomkem třídy ActionBarActivity, která poskytuje tuto funkcionalitu
a metody s ní spojené (jako např. getSupportActionBar pro získání odkazu na instanci
lišty).
4.8.1. Výsuvné menu (Drawer)
Výsuvné menu je definováno v XML souboru s rozvržením aktivity a vyžaduje, aby
kořenovým elementem tohoto souboru byl element <DrawerLayout>, jehož prvním
potomkem je samotný obsah aktivity při zasunutém panelu následovaný elementem,
který obsahuje obsah panelu.[42] Obsahem panelu je v tomto případě jednoduchý
ListView seznam, pro který je vytvořena vnitřní adaptérová třída zajišťující korektní
zobrazování položek (podobně jako v MainActivity pro seznam routerů). Po zvolení
položky je podle typu zvolené větve buďto patřičně upraven a aktualizován seznam
větví k zobrazení nebo jsou vytvořeny fragmenty s tabulkami položek či je otevřena
ScanActivity.
4.8.2. ViewPager, fragmenty a lišta záložek
Hlavním zobrazovacím prvkem ListTabsActivity je ViewPager umožňující zobrazovat
a přepínat mezi fragmenty, které obsahují každý po jedné tabulce s položkami určité
větve. Uživateli je tak umožněno pohodlně tažením či výběrem v horní liště přepínat
mezi zobrazovanými tabulkami. O korektní vytváření a zobrazování fragmentů se
stará vnitřní třída TabsPagerAdapter (potomek FragmentStatePagerAdapter), které je
v konstruktoru předávána instance třídy TabsBranch. Podle tohoto argumentu jsou
v přepsané metodě getItem vytvářeny nové instance třídy ListFragment. Těmto je za
44
Page 52
pomoci třídy Bundle a metody setArgument předávána patřičná instance třídy
ListBranch, podle které jsou odesílány dotazy routeru.
Každý ListFragment funguje jako autonomní jednotka, disponuje vlastní instancí třídy
ListController pro komunikaci s routerem a má svůj vlastní životní cyklus. Tímto
vzniká problém s rušením fragmentů, neboť ve výchozím nastavení jsou v paměti
uchovávány pouze fragmenty sousedící s aktivním fragmentem (tak, aby mohl být
animován hladký přechod mezi nimi) a ostatní jsou rušeny či nejsou vůbec vytvořeny,
resp. jsou vytvářeny až ve chvíli, kdy se stanou sousedními. To způsobuje nehladké
přechody mezi fragmenty (neboť při vytváření fragmentu dochází k zasílání dotazů na
položky) a také problémy při navracení hodnot, neboť fragment, který je má
zobrazovat, již nemusí existovat. Tento problém je odstraněn nastavením
maximálního limitu uchovávaných fragmentů na potřebný počet pomocí metody
ViewPager.setOffscreenPageLimit(int limit).[43]
Aby bylo možné přepínat mezi jednotlivým fragmenty pomocí lišty záložek v horní
části obrazovky, je nutné implementovat rozhraní TabListener a přepsat jeho metodu
onTabSelected, v jejímž těle je nutné nastavit, jaký fragment má být zobrazen pomocí
metody viewPager.setCurrentItem(index). Pro samotné zobrazení lišty záložek je nutné
nastavit její typ pomocí následující metody:
ActionBar actionBar = getSupportActionBar();actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
4.8.3. Obnovení do posledního stavu
V případě, že je aktivita přesunuta do pozadí, zůstává v paměti a je ji možné obnovit
do původního stavu, ale pouze za předpokladu, že není systémem z důvodu
uvolňování prostředků ukončena. V takovém případě je volána metoda
onSaveInstanceState, která umožňuje uložení hodnot do Bundle objektu a při
obnovování je z něho získat a pracovat s nimi. Jedinou ukládanou hodnotou je v tomto
případě aktuální TabsBranch, podle které jsou zobrazovány fragmenty tak, aby při
návratu byly korektně obnoveny. Vzhledem k cyklickým odkazům nelze takto předávat
aktuální větev v menu, to je vždy parsováno znovu při vytváření této aktivity
a aktuální větev nastavena na kořen stromu.
45
Page 53
4.9. Fragment ListFragment
Třída ListFragment je potomkem třídy Fragment a přepisuje některé jeho metody
životního cyklu. Zároveň také implementuje zobrazovací rozhraní ListView a jeho
metody pro zobrazení tabulky položek a jejich aktualizaci. V přepisované metodě
onCreateView získává fragment z předávaného „bundlu“ instanci ListBranch, na jejímž
základě si vytváří instanci třídy ListController pro komunikaci s routerem. V metodě
onStart se pak pomocí kontrolerové metody getAll dotazuje na všechny položky dané
větve a pomocí metody listen začíná naslouchat změnám v položkách.
Metody zobrazovacích rozhraní zpravidla nejsou volány z hlavního vlákna, ale
z vlákna, které naslouchá přijatým odpovědím. Objekty zobrazení nejsou v systému
Android vláknově bezpečné a lze k nim tedy přistupovat pouze z hlavního vlákna.
K řešení tohoto problému je poskytnuto několik možností a v této práci je využíváno
metody Activity.runOnUiThread(Runnable), která umožňuje vykonat kód v hlavním
vlákně.[44]
4.9.1. Zobrazení výsledků
Zobrazení výsledků je prováděno pomocí tabulkového layoutu TableLayout
(umístěného jak v ScrollView tak v HorizontalScrollView pro zajištění posunu v obou
směrech v případě, že je obsah větší než zobrazovací plocha) a vkládání řádkových
prvků TableRow s textovým polem pro každou buňku tabulky.
Aby bylo možné korektně aktualizovat řádky tabulky a pracovat s položkami, je ke
každému tabulkovému řádku pomocí metody setTag přiřazen odpovídající objekt
ListItem vrácený v odpovědi. Iterací přes řádky lze poté získat všechny tyto objekty,
porovnat hodnoty identifikátorů a získat tak odkaz na řádek, který má být nahrazen.
4.9.2. Kontextové a hlavní menu
Při dlouhém stisku řádku tabulky je zobrazeno kontextové menu, které nabízí
možnosti pro odebrání, vypnutí, zapnutí či změny komentáře dané položky. Pokud
nad položkami existují příkazy skenovacího typu, jsou dostupné právě z tohoto
kontextového menu. Vzhledem k tomu, že ne všechny větve umožňují všechny tyto
46
Page 54
operace, není kontextové menu vytvářeno na základě statického XML souboru, ale
programově v metodě onCreateContextMenu na základě aktuální příkazové větve.
Možnost přidání položky (pro ty větve, kde je to možné) není vhodné nabízet
v kontextovém menu, neboť to se vztahuje ke konkrétní vybrané položce. Vhodnější
místo je hlavní menu aktivity, jehož obsah lze upravovat v metodě
onCreateOptionsMenu. Jelikož se nejedná o aktivitu, ale o fragment, tato metoda
nebude volána, pokud není pomocí volání setHasOptionsMenu(true) při vytváření
fragmentu řečeno, že se fragment bude podílet na podobě tohoto menu.[45]
4.9.3. Zobrazování chyb
Nejjednodušší způsob zobrazení chyby uživatelům je tzv. Toast, tedy zpráva na několik
vteřin zobrazená ve spodní části obrazovky. Pokud je již nějaký Toast zobrazen a má
být zobrazen další, je tento vložen do fronty a zobrazen po skrytí aktuálního. Proto
tento přístup nemůže být pro zobrazování chyb v případě ListTabsActivity využit,
neboť chybu může zobrazit každý fragment, což by mohlo vyústit v několikanásobné
zobrazování a skrývání chybových zpráv, které může trvat poměrně dlouhou dobu
a pro uživatele působit značně rušivě.
Využito tedy bylo textového pole v každém fragmentu, které je odpovídajícím
způsobem zobrazováno či schováváno. Schováváno je v metodě onStart a v metodě
showTable (která značí úspěšné vykonání příkazu), zobrazováno pak je v případě
obdržení chyby v metodě showError a showDisconnectError. Pokud chyba nastane
v důsledku špatného příkazu, není tabulka položek zobrazena vůbec (neboť metoda
showTable není volána) a do textového pole je zobrazena příslušná chybová zpráva.
V případě, že příkaz byl korektní (tabulka tedy je zobrazena) a dojde k přesunu
aktivity do pozadí a ztrátě spojení (uživatel např. obdrží telefonní hovor), je při
návratu do aktivity znovu odeslán dotaz routeru, který v důsledku ztráty spojení
končí chybou. V tu chvíli zůstává zobrazena původní tabulka již načtených hodnot, ale
navíc je zobrazeno textové pole s informací o ztrátě spojení. Tímto je zajištěno, že
uživatel může mít zobrazena dříve přijatá data a zároveň být informován o chybě
spojení.
47
Page 55
4.9.4. Zobrazovaní „loading“ animace
Pro lepší uživatelskou přívětivost je vhodné zobrazovat informaci o odeslání dotazu
routeru a probíhajícím čekání na odpověď pomocí třídy ProgressDialog. Pokud by
každý fragment zobrazoval tento dialog za sebe, došlo by ke stejnému problému jako
v případě Toast zpráv, tedy mnohonásobnému zobrazení dialogu (tyto by se sice
překrývaly a pro uživatele nepředstavovaly takový problém, ale docházelo by ke
zbytečnému vytváření objektů). Proto zastřešující ListTabsActivity poskytuje metody
showLoading a hideLoading, které pracují s počítadlem, které je inkrementováno
v metodě showLoading (volané z každého fragmentu při odesílání dotazu)
a dekrementováno v metodě hideLoading (volené z fragmentů při obdržení výsledku
či chyby). Jedna instance dialogu je tedy zobrazena při prvním volání (počítadlo má
hodnotu 0) a skrývána ve volání posledním (počítadlo sníženo na hodnotu 0).
4.10. Aktivita AddTabsActivity
AddTabsActivity zobrazuje „formulář“ pro vyplnění hodnot nově přidávané položky.
Obsahuje „záložky“ s fragmenty DetailFragment, které obsahují dynamicky vytvářená
pole pro zadávání hodnot atributů. Tyto fragmenty, na rozdíl od ListFragment,
nekomunikují s routerem jako autonomní části, ale slouží pouze pro zobrazení polí.
O komunikaci a přípravu zobrazení se stará samotná aktivita, která disponuje svou
instancí třídy AddController a implementuje metody rozhraní DetailViewInterface,
které jsou z kontroleru volány pro zobrazení příslušných polí.
4.10.1. Fragment
Na rozdíl od výše popsané ListTabsActivity je v této aktivitě nutné udržovat odkazy na
všechny fragmenty, které jsou její součástí, neboť z nich musí být možné získat
hodnoty, které zadal uživatel. K tomuto účelu bylo vytvořena vnitřní třída
SectionsPagerAdapter (jako potomek FragmentStatePagerAdapter). Ta spravuje
fragmenty zobrazované ve ViewPager objektu aktivity. Ve svém konstruktoru přijímá
seznam instancí DetailFragment (který je sestaven při spouštění aktivity na základě
příkazové větve) a v přepsané metodě getItem místo navracení nového fragmentu
vrací odpovídající položku z tohoto seznamu. Odkaz na seznam fragmentů je
48
Page 56
uchováván v globální proměnné aktivity a je tedy kdykoliv možné se dotázat na
aktuální hodnoty polí ve všech fragmentech (za pomoci fragmentové metody
getAllItems).
4.10.2. Zobrazování odpovědí a chybových zpráv
Metoda show (ve které je jako argument předáván seznam PropertyItem objektů)
zajišťuje zobrazování odpovědí, resp. na základě hodnoty atributu PropertyTabName
u Property dané PropertyItem instance vyhledá odpovídající fragment ze seznamu
a jemu pomocí metody addPropertyView předá instanci PropertyItem, podle které si
fragment vytvoří příslušný zobrazovací prvek a naplní ho odpovídající hodnotou či
hodnotami.
Aby bylo možné identifikovat k jaké Property se daný zobrazovací prvek váže, je zde
opět využito metody View.setView a odpovídající instance PropertyItem je tak svázána
s daným pohledem.
Pro zobrazování chybových zpráv může být v tomto případě využito Toast zpráv,
neboť fragmenty s routerem nekomunikují a samotná aktivita využívá v jednom čase
pouze jednu kontrolerovou komunikační metodu. Nehrozí tedy nebezpečí uživatelsky
nepřívětivého mnohonásobného zobrazování chybových zpráv.
4.10.3. Obnovení do posledního stavu
Korektní uložení a obnovení aktivity v případě, že byla systémem ukončena, je
prováděno v metodě onSaveInstanceState, kdy je do Bundle objektu (reprezentujícího
stav aktivity při ukončování) vložen seznam všech PropertyItem položek (které
implementují rozhraní Parcelable), které jsou zobrazeny ve fragmentech. V metodě
onCreate je pak kontrolováno, zda je znám předchozí stav aktivity a v případě že ano,
je místo kontrolerové metody getProperties volána rovnou zobrazovací metoda show,
které je v argumentu předán právě seznam PropertyItem získaný z Bundle objektu.
Toto zajišťuje, že nedochází ke ztrátám nových, uživatelem zadaných hodnot.
49
Page 57
4.10.4. Aktivita DetailTabsActivity
Potomkem třídy AddTabsActivity je aktivita DetailTabsActivity. Tato není používána
pro přidávání položek, ale pro úpravu již existujících. Obě aktivity jsou si velmi
podobné, disponují fragmenty a uživateli umožňují měnit hodnoty v polích v nich
zobrazených. Rozdílem jsou některé metody, které tato dceřiná třída přepisuje.
Například při vytváření instance kontroleru není využíván AddControler, ale jeho
potomek DetailController (který ještě před vrácením položek provede dotaz na
aktuální hodnoty). Druhým podstatným rozdílem je reakce na stisknutí tlačítka
uložení, kde je v tomto případě ještě před voláním kontrolerové metody zobrazen
dialog z přehledem všech změn, které uživatel provedl. Tato třída také definuje vlastní
atribut id, který představuje hodnotu identifikátoru pro upravovanou položku.
Posledním drobným rozdílem je nastavování jiného nadpisu (title) aktivity.
4.11. Aktivita ScanActivity
Aktivita ScanActivity umožňuje vykonávání jednoduchých příkazů skenovacího typu,
neboť implementuje rozhraní ScanViewInterface. Aktivita neobsahuje fragmenty
a zobrazuje pouze několik prvků. Těmi jsou dynamicky vytvářená pole pro zadání
parametrů daného příkazu a tabulka pro zobrazování výsledků. V liště aktivity (popř.
jejím menu) jsou zobrazeny položky pro spuštění/zastavení příkazu a smazání
zobrazených hodnot.
Třída si vytváří instanci třídy ScanController, která poskytuje metody pro odeslání
příkazu a jeho zastavení. Přijaté odpovědi jsou (podobně jako u ListController)
zpracovány, převedeny na instance ListItem a aktivitě předány pomocí metody
showOneResult. V případě, že přijaté položky mohou představovat již dříve přijatou
entitu pouze s rozdílnými hodnotami, aktivita před jejím zobrazením projde všechny
právě zobrazované položky a podle atributu returnItemIdentifierProperty dané
skenovací větve se bude snažit najít položku se shodnou hodnotou. V případě, že je
shodná položka nalezena, je nahrazena novou, v opačném případě je položka přidána.
Pro umožnění tohoto vyhledávání je k jednotlivým řádkům tabulky opět pomocí
metody setTag přiřazován ListItem objekt.
50
Page 58
Pole pro zadání parametrů jsou podobně jako v AddTabsActivity zobrazována na
základě seznamu instancí PropertyItem. Tento seznam je také využíván pro ukládání
a obnovování stavu aktivity.
Chybové zprávy mohou být uživateli zobrazeny pomocí Toast objektů, neboť routeru
je vždy odesílán pouze jeden příkaz.
51
Page 59
5. Shrnutí výsledků
Vytvořená aplikace umožňuje uchovávat informace o zařízení (a to včetně jejich
importu ze souboru ve formátu .wbx) tak, aby uživatel nemusel tyto informace
s každým použitím zadávat znovu. Dále pak umožňuje rychlou kontrolu stavu zařízení
pomocí příkazu ping, čtení či úpravu základních konfiguračních položek (mj. rozhraní,
bezdrátových přístupových seznamů, IP adres či DHCP přidělování adres) a provádění
příkazů skenovacího typu (jako např. skenování dostupných bezdrátových sítí či ping).
Jednotlivé komponenty aplikace jsou koncipovány pro co možná největší
univerzálnost, tedy tak, aby případné provedení změn v XML souboru příkazového
stromu nevyžadovalo žádné nebo pouze minimální zásahy do samotného kódu
aplikace.
Pro vyšší uživatelskou přívětivost, aplikace umožňuje uživateli iniciovat obnovení
spojení v případě ztráty konektivity bez nutnosti zavření aktuální aktivity a také
uživatele informuje o probíhajícím načítání hodnot (ať už načítání seznamu zařízení
z SQLite databáze či čekání na odpověď routeru).
Aplikace není zamýšlena jako plnohodnotná náhrada již existujících konfiguračních
nástrojů, avšak je vhodná pro rychlou kontrolu či změnu konfigurace zařízení
v případě, že tyto nástroje nejsou uživateli k dispozici. Aplikace také může tvořit
základ pro budoucí přidávání funkcionality.
52
Page 60
6. Závěry a doporučení
Vývoj aplikací pro platformu Android je díky rozdělení aplikace pomocí aktivit značně
specifický. Problematické je nejen předávání objektů mezi aktivitami a zajištění, aby
aktivity byly obnovovány do korektního stavu, v případě jejich ukončení systémem,
ale také větší náchylnost ke ztrátě spojení a tedy nutnost uživateli umožnit obnovu
připojení bez ztráty vložených dat (např. při úpravě či přidávání položky konfigurace).
Vytvořenou aplikaci je před případným oficiálním zveřejněním nutné řádně otestovat
na různých verzích systémů RouterOS i Android, prověřit její korektní funkčnost
v praxi skupinou testovacích uživatelů a popř. opravit zjištěné nedostatky. Aplikace ve
své současné podobně poskytuje možnost konfigurace pouze několika základních
položek, avšak díky využití XML a univerzálnosti zobrazovacích prvků je dobrým
základem pro aplikaci umožňující plnohodnotnou konfiguraci. Mapování kompletního
příkazového stromu pro všechny balíčky bude nepochybně časově velmi náročný úkol.
Je zde možnost zveřejnění zdrojového kódu aplikace a vytvoření podpůrného systému
(např. webové aplikace), který by umožnil členům poměrně rozsáhlé komunity
uživatelů MikroTik podílet se na mapování příkazových stromů.
V případě rozšiřování dostupné konfigurace by bylo nutné doimplementovat závislost
dostupnosti atributů položek na hodnotách atributů jiných, tedy případ, kdy jeden či
více atributů je dostupných pouze v případě, že jiný atribut má určitou hodnotu. Zde
je nutné zvážit výhody a nevýhody (v podobě horší upravitelnosti) přechodu na
relační databázi. Mezi další vhodnou funkcionalitu by patřilo např. zobrazování grafů
pro sledování přenášených dat, šifrování uložených údajů, možnost odesílat
hromadně příkazy více zařízením či vytáčení VPN spojení v případě, že je router
umístěn v privátní síti. Prostor pro přidání funkcionality a tedy i zvýšení počtu
a spokojenosti uživatelů je poměrně velký.
53
Page 61
7. Seznam použitých zdrojů
Není-li uvedeno jinak, je pro zdroje v anglickém jazyce využit vlastní (volný) překlad.
[1] EDWARDS, Jim. The iPhone 6 Had Better Be Amazing And Cheap, Because Apple
Is Losing The War To Android. In: businessinsider.com [online]. 31.05.2014 [cit.
13.04.2015]. Dostupné z WWW: http://www.businessinsider.com/iphone-v-
android-market-share-2014-5
[2] Activities. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z WWW:
http://developer.android.com/guide/components/activities.html
[3] WHITSON, Gordon. Android Task Killers Explained: What They Do and Why You
Shouldn't Use Them. In: lifehacker.com [online]. 29.09.2010 [cit. 13.04.2015].
Dostupné z WWW: http://lifehacker.com/5650894/android-task-killers-
explained-what-they-do-and-why-you-shouldnt-use-them
[4] Pausing and Resuming an Activity. In: Android Developers [online]. [cit.
13.04.2015]. Dostupné z WWW:
http://developer.android.com/training/basics/activity-lifecycle/pausing.html
[5] Fragments. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z WWW:
http://developer.android.com/guide/components/fragments.html
[6] App Manifest. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z
WWW: http://developer.android.com/guide/topics/manifest/manifest-
intro.html
[7] Storage Options. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z
WWW: http://developer.android.com/guide/topics/data/data-storage.html
[8] Settings. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z WWW:
http://developer.android.com/guide/topics/ui/settings.html
[9] Distinctive Features Of SQLite. In: sqlite.org [online]. [cit. 13.04.2015]. Dostupné
z WWW: http://www.sqlite.org/different.html
[10]Resources Overview. In: Android Developers [online]. [cit. 13.04.2015].
Dostupné z WWW:
http://developer.android.com/guide/topics/resources/overview.html
54
Page 62
[11]Accessing Resources. In: Android Developers [online]. [cit. 13.04.2015].
Dostupné z WWW:
http://developer.android.com/guide/topics/resources/accessing-resources.html
[12]LayoutInflater. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z
WWW:
http://developer.android.com/reference/android/view/LayoutInflater.html
[13]ViewGroup. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z
WWW: http://developer.android.com/guide/components/fragments.html
[14]Manual:API. In: MikroTik Wiki [online]. poslední úprava: 10.02.2015 [cit.
13.04.2015]. Dostupné z WWW: http://wiki.mikrotik.com/wiki/Manual:API
[15]MikroTik. In: ispadmin.eu [online]. NET service solution, © 2015 [cit.
13.04.2015]. Dostupné z WWW: http://www.ispadmin.eu/cz/router-
management/mikrotik
[16]Manual:Winbox. In: MikroTik Wiki [online]. poslední úprava: 13.03.2014 [cit.
13.04.2015]. Dostupné z WWW: http://wiki.mikrotik.com/wiki/Manual:Winbox
[17]Manual:System/Packages. In: MikroTik Wiki [online]. poslední úprava:
20.06.2011 [cit. 13.04.2015]. Dostupné z WWW:
http://wiki.mikrotik.com/wiki/Manual:System/Packages
[18]Manual:Console. In: MikroTik Wiki [online]. poslední úprava: 12.12.2011 [cit.
13.04.2015]. Dostupné z WWW: http://wiki.mikrotik.com/wiki/Manual:Console
[19]Manual:IP/DHCP Server. In: MikroTik Wiki [online]. poslední úprava: 01.07.2014
[cit. 13.04.2015]. Dostupné z WWW:
http://wiki.mikrotik.com/wiki/Manual:IP/DHCP_Server
[20]API in Java. In: MikroTik Wiki [online]. poslední úprava: 25.04.2012 [cit.
13.04.2015]. Dostupné z WWW: http://wiki.mikrotik.com/wiki/API_in_Java
[21]API in C Sharp. In: MikroTik Wiki [online]. poslední úprava: 14.07.2009 [cit.
13.04.2015]. Dostupné z WWW: http://wiki.mikrotik.com/wiki/API_in_C_Sharp
[22]API in Ruby class. In: MikroTik Wiki [online]. poslední úprava: 27.04.2011 [cit.
13.04.2015]. Dostupné z WWW: http://wiki.mikrotik.com/wiki/API_Ruby_class
[23]LeGRANGE, Gideon. GideonLeGrange/mikrotik-java. In: GitHub [online]. poslední
úprava: 30.03.2015 [cit. 13.04.2015]. Dostupné z WWW:
https://github.com/GideonLeGrange/mikrotik-java
55
Page 63
[24]Apache License, Version 2.0. In: apache.org [online]. leden 2004 [cit. 13.04.2015].
Dostupné z WWW: http://www.apache.org/licenses/LICENSE-2.0
[25]Intents and Intent Filters. In: Android Developers [online]. [cit. 13.04.2015].
Dostupné z WWW: http://developer.android.com/guide/components/intents-
filters.html
[26]Bundle. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z WWW:
http://developer.android.com/reference/android/os/Bundle.html
[27]Android Application Framework FAQ. In: Android Developers [online]. [cit.
13.04.2015]. Dostupné z WWW:
http://developer.android.com/guide/faq/framework.html#3
[28]Determining and Monitoring the Connectivity Status. In: Android Developers
[online]. [cit. 13.04.2015]. Dostupné z WWW:
http://developer.android.com/training/monitoring-device-state/connectivity-
monitoring.html
[29]"brianestey". [Receiving multiple broadcast is a device specific problem...]. In:
stackoverflow.com [online]. 07.12.2011, 10:05 (upraveno uživatelem Sheraz
Ahmad Khilji: 13.05.14, 10:03) [cit. 13.04.2015]. Dostupné z WWW:
http://stackoverflow.com/questions/8412714/broadcastreceiver-receives-
multiple-identical-messages-for-one-event
[30]"Boen robot". Proposal API help. In: MikroTik Wiki [online]. poslední úprava:
22.04.2014 [cit. 13.04.2015]. Dostupné z WWW:
wiki.mikrotik.com/wiki/Proposal_API_help
[31]"janisk". Re: [API proposal] "/help" command. In: forum.mikrotik.com [online].
22.04.2014 3:42pm [cit. 13.04.2015]. Dostupné z WWW:
http://forum.mikrotik.com/viewtopic.php?f=9&t=84191
[32]BREAULT, Philippe. Parcelable vs Serializable. In: developerphil.com [online].
18.04.2013 [cit. 13.04.2015]. Dostupné z WWW:
http://www.developerphil.com/parcelable-vs-serializable/
[33]NetworkOnMainThreadException. In: Android Developers [online]. [cit.
13.04.2015]. Dostupné z WWW:
http://developer.android.com/reference/android/os/NetworkOnMainThreadEx
ception.html
56
Page 64
[34]Keeping Your App Responsive. In: Android Developers [online]. [cit. 13.04.2015].
Dostupné z WWW: http://developer.android.com/training/articles/perf-anr.html
[35]Using an ArrayAdapter with ListView. In: GitHub [online]. poslední úprava:
20.01.2015 [cit. 13.04.2015]. Dostupné z WWW:
https://github.com/codepath/android_guides/wiki/Using-an-ArrayAdapter-
with-ListView
[36]Menus. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z WWW:
http://developer.android.com/guide/topics/ui/menus.html#context-menu
[37]AsyncTask. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z WWW:
http://developer.android.com/reference/android/os/AsyncTask.html
[38]Varargs. In: docs.oracle.com [online]. Oracle, © 2004, 2010 [cit. 13.04.2015].
Dostupné z WWW:
http://docs.oracle.com/javase/1.5.0/docs/guide/language/varargs.html
[39]Cursor. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z WWW:
http://developer.android.com/reference/android/database/Cursor.html
[40]ContentValues. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z
WWW:
http://developer.android.com/reference/android/content/ContentValues.html
[41]VRBA, Jindřich. [Hello, thanks for help,...]. In: localloop.co.za [online]. 02.11.2009,
5:01pm [cit. 13.04.2015]. Dostupné z WWW:
http://localloop.co.za/2008/10/reading-mikrotiks-winbox-addresseswbx-file-
format/#comment-301
[42]Creating a Navigation Drawer. In: Android Developers [online]. [cit. 13.04.2015].
Dostupné z WWW: http://developer.android.com/training/implementing-
navigation/nav-drawer.html
[43]ViewPager. In: developer.android.com [online]. [cit. 13.04.2015]. Dostupné z
WWW:
http://developer.android.com/reference/android/support/v4/view/ViewPager.
html
[44]Processes and Threads. In: Android Developers [online]. [cit. 13.04.2015].
Dostupné z WWW: http://developer.android.com/guide/components/processes-
and-threads.html
57
Page 65
[45]Fragment. In: Android Developers [online]. [cit. 13.04.2015]. Dostupné z WWW:
http://developer.android.com/reference/android/app/Fragment.html
8. Přílohy
Přílohou této práce je CD obsahující zdrojové kódy aplikace (resp. složku obsahující
Eclipse projekt), složku obsahující knihovnu zpětné kompatibility appcompat_v7,
zkompilovanou aplikaci (soubor s příponou .apk) a kopii licence Apache 2.0.
58