• No results found

Skapa en egen View

4.3 Implementation

8.1.4 Skapa en egen View

För att få mer kontroll över utseendet och funktionaliteten i en View är det fördelaktigt att ärva ifrån en View och anpassa den efter eget önskemål. För att skapa egna View-komponenter behöver följande tre steg göras:

1. Skapa en egen klass och ärv ifrån en View-klass

2. Överskugga några av metoderna ifrån superklassen. Metoderna att överskugga har prexet on.

3. Använd den nya ärvda klassen precis som den View den är baserad på. En egen View kan sedan inkluderas i en xml-layout-l precis som en View ifrån Androids API. Det kan se ut på följande sätt:

<view

c l a s s=" se . your . package . MyView"

id="@+id / note "

android : layout_width=" f i l l _ p a r e n t "

android : layout_height=" f i l l _ p a r e n t "

android : background="@android : drawable /empty"

android : padding="10 dip "

android : s c r o l l b a r s=" v e r t i c a l "

android : fadingEdge=" v e r t i c a l " />

Listing 8.3: En egen View i en layout

Ovan ses att namnet på paketet View-klassens namn måste sättas explicit. [13]

8.2 Designval

Androidplattformen och Android Quicklauncher har funktionaliteten att blädd- ra mellan olika fönster men saknar implementation för att stödja element som scrollas vertikalt i dem. HorizontalPagern tillhandahåller denna funk- tionalitet och är kodmässigt väldigt lik Android Quicklauncher vilket ger en trygghet i att det är välskriven kod. Därför väljs HorizontalPagern för att implementera swipe och ing.

mellan så skapas en egen menywidget som ärver ifrån en View. Detta för att göra menywidgeten mer dynamisk då antalet menyknappar kan variera och den kan komma att behöva animeras.

För att hålla ihop både menywidget och HorizontalPagern kan ytterliggare en anpassad View göras, kallad SwipeHolder. I denna View placeras både meny- widgeten och HorizontalPagern vilket gör att endast Swipeholdern behöver inkluderas i xml-layout-len för att få en menywidget och HorizontalPager.

8.3 Implementation

Här beskrivs implementationen för alla delar separat.

8.3.1 Horizontal Pager

I sitt grundutförande visar sig dock inte HorizontalPagern fungera tillfred- ställande då scrollningen låser sig i vertikal scrollning efter att en sådan har påbörjats i till exempel en ListView. Detta beror på att HorizontalPagern inte nollställer sitt TouchState efter avslutad interaktion. HorizontalPagern tror således att det fortfarande är vertikal scrollning som gäller. För att nollställa TouchState modieras den tidigare beskrivna metoden onInter- ceptTouchEvent som avgör om det är en vertikal eller horisontell scrollning. När MotionEvent ger ett ACTION_DOWN sätts variabeln mTouchState till TOUCH_STATE_REST vilket innebär att TouchState nollställs. Nedan visas koden för detta:

Override

p u b l i c boolean onInterceptTouchEvent ( MotionEvent ev ) { . . .

switch ( a c t i o n ) {

case MotionEvent .ACTION_MOVE: . . .

. . .

case MotionEvent .ACTION_DOWN:

/∗

∗ No motion yet , but r e g i s t e r the c o o r d i n a t e s so we can check f o r i n t e r c e p t

∗ at the next MOVE event .

∗/

mLastMotionX = ev . getX ( ) ;

// N o l l s t ä l l e r TOUCH_STATE vid ny b e r ö r i n g .

mTouchState = TOUCH_STATE_REST; . . . .

}

return mTouchState == TOUCH_STATE_SCROLLING; }

Listing 8.4: Modikation av HorizontalPager. Ny kod markerad i gult.

8.3.2 Menywidget

För att skapa en egen menywidget skapas en klass som heter SwipeMenu som ärver av LinearLayout. LinearLayout är passande då den kan ordna sina element i kolumner och låta dem fördela sig jämt över ytans bredd eller låta dem följa efter varandra ifrån vänster eller höger.

I koden nedan ses hur klassen SwipeMenu lägger till knappar genom att skapa Views av typen Button och lägger till dem genom metoden addView(View) som ärvs ifrån LinearLayout. Vidare ses hur det för varje knapp registreras en OnClickListener som byter fönster i HorizontalPager när ett klick sker på menyknappen.

p r i v a t e void addMenuItem ( S t r i n g menuText , i n t screenIndex ) {

Button newButton = new Button ( getContext ( ) ) ; newButton . setText ( menuText ) ;

newButton . s e t I d ( screenIndex ) ;

newButton . setBackgroundColor ( backgroundColor ) ; newButton . setOnTouchListener (t h i s) ;

newButton . s et O nCl ickL is ten er (new OnClickListener ( ) { p u b l i c void onClick ( View v ) {

h o r i zo n t a l P a g e r . setCurrentScreen ( v . getId ( ) , true) ; }

}) ;

addView ( newButton ) ; }

Listing 8.5: Lägga till en menyknapp

enSwitchListener. Utöver att kunna lyssna till när ett swipe eller ing har genomförts behöver SwipeMenu lysa upp en knapp när den markeras och implementerar därför också OnTouchListener. Nedan visas klassens huvud samt implementationen för OnTouchListener och OnScreenSwutchListener:

p u b l i c c l a s s SwipeMenu extends LinearLayout implements OnScreenSwitchListener , OnTouchListener {

. . .

/∗∗

∗ När användaren nuddar vid en knapp anropas denna metod .

∗/

p u b l i c boolean onTouch ( View v , MotionEvent event ) { switch ( event . getAction ( ) ) {

case MotionEvent .ACTION_DOWN:

v . setBackgroundColor ( h i g h l i g h t C o l o r ) ; break;

case MotionEvent .ACTION_UP: . . . break; d e f a u l t: break; } return f a l s e; } . . . /∗∗

∗ När användare har swipat e l l e r f l i n g a t anropas denna metod och knappens bakgrund

∗ uppdateras ∗/ p u b l i c void onScreenSwitched (i n t s c r e e n ) { currentButton = s c r e e n ; updateButtonBackgroundColor ( ) ; } . . . }

Listing 8.6: För att highlighta menyknapparna

Ovan ses att i onTouch-metoden ges knappen en highlightfärg då an- vändaren har markerat eller pressat ned ngret på en knapp (i kod Motio- nEvent.ACTION_DOWN). När användaren släpper ngret sätts den åter till den färg den hade tidigare. onScreenSwitched-metoden ovan anropas då fönster har bytts genom swipe eller ing och metoden byter då färg på den aktuella menyknappen för fönstret.

8.3.3 SwipeHolder

För att få menywidgeten och HorizontalPagern att fungera som ett eget View- objekt, likt menywidgeten, kan en egen View kallad SwipeHolder skapas. Då menywidgeten och HorizontalPagern alltid ska ligga efter varandra på rad(se gur 8.1) kan SwipeHoldern ärva ifrån Linearlayout. SwipeHoldern har ansvaret för att lägga till menyknappar i menywidgeten och fönster i HorizontalPagern. Nedan visas hur menywidgeten och HorizontalPager upp- dateras när ett nytt fönster i form av en vy läggs till i SwipeHoldern:

/∗

∗ När en View l ä g g s t i l l i SwipeHoldern skapas e t t nytt f ö n s t e r i HorizontalPager och

' en menyknapp l ä g g s t i l l i menywidgeten ∗/

@Override

p u b l i c void addView ( View c h i l d ) { i f( belongsInPager ( c h i l d ) ) { h or i zo n t al P ag e r . addView ( c h i l d ) ; swipeMenu . update ( ) ; } e l s e { super. addView ( c h i l d ) ; } }

Listing 8.7: Koden för att lägga till en View till HorizontalPagern

SwipeHoldern läggs enkelt till i en xml-layout-l genom följande rader.

<se . market . android . gui . h e l p e r s . SwipeHolder

android : id="@+id / activity_main_swipe_holder " android : layout_width=" f i l l _ p a r e n t "

android : layout_height=" f i l l _ p a r e n t " market :

menuBackgroundColor=" @color /main_menu_background_color"

market : menuSelectColor=" @color /main_menu_select_color"

market : distributeMenuHorizontal=" true "> <ListView android : id="@+id /

a c t i v i t y _ m a i n _ l i s t _ c a t e g o r i e s "

android : layout_width=" f i l l _ p a r e n t " android : layout_height=" f i l l _ p a r e n t " />

android : layout_width=" f i l l _ p a r e n t " android : layout_height=" f i l l _ p a r e n t " />

<ListView android : id="@+id / activity_main_list_topchart "

android : layout_width=" f i l l _ p a r e n t " android : layout_height=" f i l l _ p a r e n t " />

</se . market . android . gui . h e l p e r s . SwipeHolder>

Listing 8.8: SwipeHoldern i en layout

I koden ses att inuti SwipeHoldern läggs tre stycken ListView:s. För dessa kommer automatiskt tre stycken menyknappar att genereras.

Kapitel 9

Responsivt användargränssnitt

Många Androidapplikationer lider av ryckighet och dålig respons. Enligt Android Developers forum, upplever användaren lagg vid utebliven respons inom 100 till 200 ms [5]. Om inte en applikationen svarar det underliggande systemet inom 5 sekunder, visas en dialogruta med valet att stänga ap- plikationen. Att skapa en applikation som ger en laggfri upplevelse är en icke-trivial uppgift. Det gäller att identiera och hantera exekveringen av tidskrävande operationer, för att hindra att applikationen ger dålig respons och låser sig.

En Androidapplikation får som standard endast en tråd som delas av appli- kationens alla objekt så som Activity, Service, BroadcastReciever med era. Dålig responsivitet uppstår då Activity-objekt, som utgör användar- gränssnittet, inte får tillräckliga resurser. Om användaren klickar på en knapp som startar hämtning av en stor mängd data från internet måste användaren fortfarande kunna interagera med användargränsnittet, exempelvis klicka på en annan knapp för att avbryta nedladdningen.

Tidskrävande operationer som nätverkskommunikation initeras inte bara från användargränsnittet. Exempelvis ska APP köra synkroniseringar med WEB för att hitta nya uppdateringar. Dessa synkroniseringar startas inte från an- vändargränsittet utan från yttre intents som hanteras av AppStatusHandler som är av typen BroadcastReciever. Om ingen särskild åtgärd har vidtagits exekverar AppStatusHandler i huvudtråden, samma tråd som Activity- objekt. Det resulterar i att AppStatusHandler kan låsa användargränssnittet under långa perioder och i värsta fall orsaka stängning av programmet.

Problemet som behöver lösas är att undvika att applikationens huvudtråd, även kallad ui-tråden, blir låst av tunga tidskrävande operationer. I detta kapitel beskrivs hur avlastningen av användargränsnittets huvudtråd ska uppnås.

9.1 Angreppsätt

En lösning är att identiera tidskrävande uppgifter och lägga ut dessa på annan tråd, istället för att försöka optimera dem. Många metoder går inte att optimera och kommer alltid att ta tid (som hämtning av data från WEB). Det nns två metoder i android för att hantera trådar: Antingen genom javas trådning java.lang.Thread eller androidklassen AsyncTask.

9.1.1 Java Thread

Android SDK inkluderar stöd för manuell trådning med java.lang.Thread. Att placera ett tidskrävande arbete på en egen java.lang.Thread imple- menteras genom att utföra det tidskrävande arbetet i metoden run(), se kodexemplet nedan. new Thread ( ) { @Override p u b l i c void run ( ) { writeThesisReport ( ) ; } } . run ( ) ;

När ett tidskrävande arbete i en separat tråd är färdigt är det ett van- ligt scenario att resultatet ska publiceras för användaren, exempelvis ge- nom att fylla en textruta i användargränsnittet. I Android representeras en textruta av ett objekt av typen View. View-objekt skapas alltid på ui- tråden och kan inte manipuleras från en annan tråd. Om en annan tråd än ui-tråden försöker maniplera View-objekt kastas ett undantag av typen CalledFromWrongThreadException. För att publicera ett resultat på an- vändargränsittets krävs en speciell synkronisering mellan ui-tråden och den separata jobbtråden. Det kan genomföras genom att anropa metoden

utför själva uppdateringen av View-objekten. I exemplet nedan exekveras PublishThesisReportRunnable på ui-tråden, som exempelvis kan fylla en textruta med text.

new Thread ( ) { @Override

p u b l i c void run ( ) { writeThesisReport ( ) ; }

a c t i v i t y . runOnUiThread (new PublishThesisReportRunnable ( ) ) ; } . run ( ) ;

9.1.2 Android AsyncTask

Android tillhandahåller en klass AsyncTask för att underlätta skapandet av bakgrundstrådar och synkroniseringen med ui-tråden. För att använda AsyncTask behöver tre metoder implemeteras: doInBackground,

onProgressUpdate och onPostExecute. Metoden doInBackground körs på en egen tråd och ska därför innehålla tunga operationer. För att visa re- sultatet för användaren kan användargränssnittet uppdateras i metoderna onProgressUpdate och onPostExecute, vilka körs på ui-tråden, vilket und- viker att CalledFromWrongThreadException kastas.

För att starta en AsyncTask anropas dess metod execute, vilket måste ske från ui-tråden. Då era instanser av AsyncTask anropas kommer de antingen köras i serie eller parallellt beroende på plattformsversion. Före Android 1.6 och efter 3.0 körs de seriellt, versioner där emellen körs de parallellt. Anledningen att samtidiga AsyncTask, efter Android 3.0, återigen exekveras seriellt beror på att undvika onödig komplexitet med parallella processer.

9.2 Designval

Genom att utnyttja den abstrakta klassen AsyncTask, istället för att di- rekt bygga på java.lang.Thread, uttnyttjas en färdiga implementation för trådning anpassad för android. Klassen AsyncTask hanterar problem med för många trådar genom en pool, samt att uppdatera objekt på ui-tråden. Därför väljs AsyncTask framför Java Thread. Med AsyncTask som grund, utökas den i klassen GuiWorker, en klass som kör objekt av typen GuiTask. Klassen

GuiWorker kan hantera era GuiTask-objekt för att köra dem sedan sekventi- ellt, se gur 9.1. Detta säkerhetsställer att de asynkrona operationerna kom- mer att köras på samma sätt på alla Android-versioner, det vill säga sekven- tiellt. GuiWorker har även en referens till ett ProgressViewHolder-objekt, som visar en progressbar när arbetet börjar och döljs när alla GuiTask- objekten är klara.

Tidskrävande operationer utförs i GuiTask metod execute, för att undvi- ka att belasta ui-tråden. De tidskrävande operationerna åternns främst i kommunikation med WEB, som att hämta och skicka appinformation, använ- darkommentarer och bilder.

ProgressView Holder ChildThread GuiTask GuiTask GuiTask GuiTask GuiTask GuiTask GuiTask GuiTask ProgressView Holder GuiWorker show execute onPostExecute hide

Figur 9.1: GuiWorker exekverar GuiTask:s på en separat tråd. En Progress- ViewHolder visas under hela arbetet

9.3 Implementation

Klassen GuiWorker utökar den abstrakta klassen AsyncTask och parametrise- rar den med GuiTask enligt AsyncTask<GuiTask[], GuiTask, GuiTask[]>. GuiTask exponerar två publika metoder, execute och onPostExecute. Me- toderna i GuiTask anropas från motsvarande metoder i GuiWorker, se ko- dexemplet nedan.

p u b l i c c l a s s GuiWorker extends AsyncTask<GuiTask , GuiTask , GuiTask [] >{

. . .

@Override

protected GuiTask [ ] doInBackground ( GuiTask . . . t a s k s ) { f o r (i n t i = 0 ; i < t a s k s . length ; i++) { t a s k s [ i ] . execute ( ) ; } return t a s k s ; } @Override

protected void onPostExecute ( GuiTask [ ] t a s k s ) { super. onPostExecute ( t a s k s ) ; f o r ( GuiTask task : t a s k s ) { task . onPostExecute ( ) ; } . . . } }

I koden ovan ses att GuiWorker i metoden doInBackground kör varje GuiTask:s execute-metod som har blivit inladdad i den. Det gör att de tunga operationerna kommer att köras på en annan tråd än ui-tråden. När alla GuiTask är körda anropas onPostExecute och där körs också varje GuiTask:s onPostExecute-metod. Denna metod körs på ui-tråden och uppdaterar de ui-element som har fått förändrade värden när de kördes i metoden doIn- Background i GuiTask.

För att använda GuiWorker för att exempelvis fylla en lista i användargräns- snittet med appar från WEB, behövs en GuiTask som hämtar apparna och sedan fyller listan. Hämtningen av data från WEB sker då i GuiTask-objektets metod execute för att undvika att låsa ui-tråden, och uppdateringen av den synliga listan i användargränssnittet sker i metoden onPostExecute.

Genom att köra tunga operationer i en separat tråd från ui-tråden (huvud- tråden) uppnås eektivt ett responsivt användargränssnitt. Skillnaden kan ses i avsnitt 11.1 där resursåtgången jämförs vid hämtning av en applista på huvutråden med hämtning på en separat tråd.

Kapitel 10

Byggverktyg

För att automatisera processen att bygga källkoden används ofta ett bygg- verktyg. Byggverktygen hjälper till att bygga källkoden på samma sätt oav- sett vilken dator som används. Processen går också betydligt snabbare och blir mycket enklare att hantera.

Ifrån det beställande företaget nns kravet att byggverktyget dels ska vara open-source och ska ha stöd för enhetstester och kodtäckning samt vara inter- grerarbar med Eclipse och Android. Marknadsplatsapplikationen utvecklas till Android-plattformen vilket medför att alla utvecklingsverktyg måste vara kompatibla med de bibliotek och verktyg som utgör Android SDK:t.

För att skriva enhetstester och utföra kodtäckningsanalyser av Android- applikationer, utesluts alla verktyg förutom JUnit och EMMA då Android SDK:et endast har inbyggt stöd för JUnit och EMMA. JUnit är ett verktyg som utför enhetstester och EMMA ger kodtäckning på de utförda enhets- testerna. På grund av att Android enbart stödjer dessa två kommer enbart byggverktyg som stödjer JUnit och EMMA att beskrivas i följande avsnitt.

10.1 Angreppssätt

Ett byggverktyg kan hjälpa till att automatisera processen att kompilera, testa, signera och sjösätta projekt. Byggverktygen använder en instruktionsl som beskriver hur byggverktyget ska genomföra olika byggsteg. Hur mycket instruktioner som behöver skrivas, beror på hur mycket av standardbeteendet i byggverktyget som kan användas. Syftet med att använda ett byggverktyg är att garantera att alla bygger samma projekt på samma sätt samt för att

automatisera processen.

I avsnittet beskrivs de två största open-source-verktygen för Android: Maven och Ant.

10.1.1 Maven

Maven är inte bara ett byggverktyg utan kan fungera som ett komplement i att leda projekt i mjukvarutveckling. Kärnan i Maven är en project object model, kallad pom-l. Modellen innefattar en uppsättning standarder, en projektcykel, en hantering av beroenden och logik för att denera mål vid olika tillfällen i livscykeln. Trots Mavens omfattande funktionalitet kan den också användas som ett rent byggverktyg.

I Maven behöver man man inte explicit denera exempelvis vad för kompi- lator som ska användas, vilken l som ska kompileras och så vidare. Maven vet med hjälp av konventioner hur den hittar källkoden och hur den ska behandlas.

Maven har stöd för hantering av externa och interna bibliotek, även kallat dependency management. Maven har även kontroll över transitiva beroenden, alltså kontroll över bibliotekens bibliotek. Det är möjligt att kongurera ett Maven-projekt till att vara beroende av en viss version av ett plugin, för att sedan låta Maven automatiskt hämta rätt ler från internet. Maven cachar även nerladdade ler, vilket gör att utvecklingen inte är beroende av internetanslutning, så länge inte ett beroende av en annan version uppstår. [14]

Integration med Android och Eclipse

För Android nns ett plugin som heter Maven Android Plugin som används vid bygge i kommandoprompten. För hanteringen av Android-projekt i Eclip- se används ett plugin som heter Maven Integration for Android Development Tools som kräver att pluginet m2Eclipse är installerat. Detta plugin gör det möjligt att låta Maven sköta bygget även inne i Eclipse.

JUnit och EMMA integration

Maven har i princip plugins för allting. Det gäller också JUnit och EMMA. Genom att i POM-len denera de två olika pluginen hämtar Maven själv hem pluginen och kör dem på källkoden.

10.1.2 Ant

Ant är en förbättrad version av make och syftar till att förenkla proccessen att skriva buildscript. Ant är i förhållande till Maven ett mer utpräglat byggverktyg. Ant använder en uppgiftsbaserad process där man explicit anger exempelvis vilken typ av kompilator som ska användas, vart man placerar resultaten och så vidare.

Det är möjligt att få stöd för Dependency Management i Ant. Det nns ett verktyg som heter Apache Ivy som är tätt integrerat med Ant och erbjuder kraftfulla Ant tasks.

Integration med Android

Android SDK:et ger ett stöd för att bygga och testa med Ant. När man skapar ett nytt Android-projekt i kommandoprompten med hjälp av andro- idverktygen, generas även en Ant-l, build.xml. Den kan sedan användas för att kompilera och deploya projektet. Allt som krävs är att ha Ant installerat på datorn. Det är enkelt att både köra och generera kodtäckningsrapporter för testprojekt [15].

JUnit och EMMA integration

Androids testverktyg är JUnit och kan köras direkt ifrån Eclipse med Andro- ids verktyg ADT för Eclipse eller köras via kommandoprompten med Ant run-tests. Android har också direkt stöd för EMMA som kan köras med hjälp av kommandot ant coverage i kommandoprompten.

10.2 Designval

Både Maven och Ant uppfyller kraven att ha stöd för enhetstester och kod- täckning. Maven ger ytterliggare funktionalitet iform av Dependency Ma- nagement i standardinstallationen medan för Ant måste verktyget Ant Ivy installeras. I initiala fasen för APP där beroenden av andra bibliotek inte är aktuellt så är det lättviktiga Ant att föredra. Android stödjer dessutom Ant och i deras dokumentation på developer.android.com är det Ant som är rekommenderat som byggverktyg. Mavens fördelar i form av att vara mer deklarativt och inte så explicit vägs inte upp av att behöva simma mot strömmen.

vara intressant om Ant Ivy inte håller måttet. Detta anses dock inte vara aktuellt inom överskådlig framtid.

10.3 Implementation

För att skapa ett Androidprojekt med tester med hjälp av Ant behöver endas två kommandon skrivas i kommandoprompten. För att få kodtäckning skrivs ännu en rad. Dessa tre steg visas nedan:

1. Skapa huvudprojektet: Öppna kommandoprompten och gå till katalogen där du vill skapa projekten. Skriv följande rad:

android create project --package com.example.helloandroid --activity HelloAndroid --target 7 --path <projekt-namn> 2. Skapa testprojektet: I samma mapp skriv följande rad:

android create test-project -m ../<projekt-namn> -p <projekt-test-namn>

3. Kör EMMA: I kommandoprompten gå till testmappen och skriv ant coverage. I mappen coverage nns nu en htmll med coveragesta- tistiken.

Kapitel 11

Funktionella och icke-funktionella

tester

I detta kapitel redovisas de tester som har gjorts på olika delar av systemet. Avsnitt 11.1 beskriver tester av responsiviteten för användargränssnittet, avsnitt 11.2 beskriver MockWEB och i avsnitt 11.3 sammanfattas testerna.

11.1 Responsivt användargrässnitt

I detta avsnitt studeras skillnaden mellan att exekvera tunga operation på en applikations huvudtråd jämfört med en separat tråd. Här åskådligörs även påverkan detta får på responiviteten i användargränssnittet.

För att undersöka belastning av trådar användes proleringsverktyget trace- view som ingår i Android SDK:et. Traceview läser in trace-ler som generas under exekvering av en applikation. Generering kan styras genom att lägga till rader i källkoden som styr start och stopp för skrivning till trace-len. I Traceview kan man se en tidslinje för beläggningen av trådar samt en trädstruktur över alla funktionsanrop med cpu-belastning.

I gur 11.1 syns den resulterande trace-len som genererades då en applista hämtades på huvudtråden. Majoriteten av exekveringen tog plats i tåden

Related documents