Top Banner
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íček Studijní obor: Aplikovaná informatika Vedoucí práce: doc. Ing. Filip Malý, Ph.D. Hradec Králové duben 2015
65

Univerzita Hradec Králové Fakulta informatiky a ...

Oct 15, 2021

Download

Documents

dariahiddleston
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

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: Univerzita Hradec Králové Fakulta informatiky a ...

[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: Univerzita Hradec Králové Fakulta informatiky a ...

[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: Univerzita Hradec Králové Fakulta informatiky a ...

[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: Univerzita Hradec Králové Fakulta informatiky a ...

[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