4. lecke Rezgő motor és az alkalmazás életciklusa

Online Android szakkör (DKRMG)


Ebben a leckében használni fogjuk a telefon rezgő motorját, illetve egy kicsit mélyebben megismerkedünk az androidos alkalmazások működésével.

Projekt importálása

Az előzőekhez hasonlóan ebben a leckében is egy előkészített projektben fogunk dolgozni. Ezen a linken megtaláljátok az L04-Razor.zip fájlt, ezt töltsétek le, csomagoljátok ki az eddigi projektek mellé.

Majd importáljátok a projektet az Android Studioba (File -> Close Project, majd Import Project). Ha véletlenül elakadnátok, akkor követhetitek az előző lecke (L03) projekt importálásáról szóló részét.

Ha mindent jól csináltál, akkor a programot le is tudod futtatni (). Az alkalmazásunk layout-ja azonban egyelőre csak egy általunk odacsempészett képet tartalmaz.

1. feladat: rezgés bekapcsolása

Első lépésként készítünk egy egyszerű alkalmazást, ami egy gomb lenyomásakor bekapcsolja a telefon rezgő motorját (avagy Vibrator angolul).

Ehhez:

  1. Létre kell hoznunk egy gombot a layout szerkesztőben
  2. Meg kell írnunk egy onClick függvény vázát (legyen a neve: rezegjOnClick), ami a gomb lenyomására fog lefutni
  3. Be kell állítanunk a gombunk onClick tulajdonságát, hogy az új függvényünket hívja meg
  4. Létre kell hozunk egy Vibrator típusú változót (legyen a neve: rezgoMotor)
  5. Hozzá kell rendelnünk a rezgoMotor változót a telefonunk rezgő motorjához
  6. Be kell fejeznünk a rezegjOnClick függvényt, hogy aktiválja a rezgést

1.1 Gomb lerakása

Mint bizonyára emlékezhettek az előző leckéből, az alkalmazásunk kinézetét a Layout szerkesztőben tudjuk megszabni. A layout szerkesztőt az app -> res -> layout -> activity_main.xml megnyitásával éritek el. Az új gombot húzzátok mondjuk a képernyő közepére, a kép alá. A felirata legyen: rezegj!. A layout:width tulajdonsága pedig match_parent, hogy a képernyő teljes szélességét kihasználja. Nyugodtan átállíthatjátok a szöveg méretét/színét stb.

1.2 rezegjOnClick váza

Remélhetőleg ismerős az alábbi függvény, amely rezegjOnClick névre hallgat. A függvény törzse a komment lesz, ahol perjelek mögött a komment emlékeztet minket: a függvényünk egyelőre üres.


public void rezegjOnClick(View v) {
    // TODO 1.5 Ezt itt még be kell fejezni
}

A függvényt a TODO 1.2 helyére írd be! Figyeld meg, hogy egy szintre került a többi, már meglévő függvénnyel (azaz a sor ugyan annyival kezdődik beljebb, mint a függvények). Ezt nyugodtan meg is jegyezhetjük: a Java nyelvben a függvények mindig egy szinten helyezkednek el.

1.3 Gomb onClick tulajdonsága

Ahhoz, hogy a gomb lenyomásakor a függvényünk lefusson (meghívásra kerüljön), át kell állítanunk a gomb "onClick" tulajdonságát a layout editorban az új függvényünkre.

1.4 Változó létrehozása, rezgő motor hozzárendelése

A vibrátor motor eléréséhez létre kell hoznunk egy változót. Előfordulhat, hogy csodálkoztok: Miért is van szükség egy változó létrehozására, amikor mi csak meg szeretnénk kérni a telefonunkat, hogy rezegjen egy picit? Gondoljatok így a helyzetre:
A telefon rezgő motorja egy picit olyan, mint egy gomb, amit elhelyeztünk a layout szerkesztővel, csak ezt a telefon gyártója rakta bele a telefonba. Mi tudjuk, hogy létezik, de a Java kódunknak ennél több kell: Létre kell hozunk egy Vibrator típusú változót, majd hozzá kell rendelnünk a telefonunkban elhelyezkedő hardverhez. (Emlékezz, a Button típusú változónkhoz is hozzárendeltünk egy-egy gombot, megadott id azonosítóval).

Tehát hozzunk létre egy Vibrator típusú változót TODO 1.4a

Vibrator rezgoMotor;

A hozzárendelést az onCreate függvény utolsó sorában TODO 1.4b az alábbi paranccsal tehetjük meg:

rezgoMotor = (Vibrator) getSystemService(VIBRATOR_SERVICE);

Ugye hogy ez mennyire hasonlít ahhoz, amit a gomboknál csináltunk az előző lecke alkalmával?

gomb = (Button) findViewById(R.id.gomb1); // ez a sor csak minta, ne írd be

1.5 Rezgés aktiválása

Ezen a ponton rendelkezünk egy Vibrator osztályú változóval. A Vibrator osztályú változóknak pedig több hasznos belső függvényük is van.

A jövőben az olyan összetett típusokat, amik saját belső függvényekkel rendelkeznek, sokszor osztályoknak fogjuk hívni. Így különböztetjük meg a primitív típusokat (egész számok: int, karakterek: char, bool változók: boolean, lebegőpontos számok: float vagy double) az olyan típusoktól, mint pl. Button, TextView, Vibrator.

Emlékeztetőül: a gombjaink belső függvényeit (mint pl. a setText) a következő módon hívhatjuk meg:

<változó>.<függvényNeve>(<paraméterek>);

pl.:

gomb1.setText("új szöveg");

A Vibrator osztály rendelkezik egy igen hasznos függvénnyel:

vibrate(milliszekundum)

A zárójelben lévő paraméter szabja meg, hogy hány ezredmásodpercig rezegjen a telefonunk.

Ezek alapján tudnál írni egy parancsot, ami meghívja/elindítja a rezgoMotor vibrate eljárását 1 másodperces paraméterrel (1000ms)? Írd a parancsot a rezegjOnClick függvényünk törzsébe, hogy a gomb megnyomásakor induljon el TODO 1.5! Ha nagyon nincs ötleted, akkor kattints a sor végén lévő gombra.

Ugye nem csak lustaságból nézed meg a megoldást?!


        

Ha minden jól sikerült, akkor most elindíthatod az alkalmazást! A telefonodon ha megnyomod a gombot, a készülék egy másodpercig rezegni fog.

2. feladat: rezgés megszakítása

Próbáljuk megnövelni a rezgés idejét! Írd át a kódot, hogy 1000 ms helyett a készülék mondjuk 10000 ms-ig rezegjen!

Itt érdemes egy kicsit elgondolkoznunk. Tíz másodperc sok idő, így előfordulhat, hogy a rezgést időnként még az idő lejárta előtt le akarjuk állítani. Gondolj például arra, amikor a telefonod csörgés közben szokott rezegni. A hívás fogadásakor a rezgés mindig azonnal leáll.

Szerencsére a Vibrator osztály rendelkezik egy hasznos belső függvénnyel a jelenlegi rezgés leállítására is. Ez pedig a cancel() függvény. Figyeld meg, hogy itt nincs a függvénynek paramétere, hiszen rezgés leállításához nincs szükség több információra.

2.1 rezgés leállítása

Az előző feladatok alapján hozz létre egy új gombot az előző alá, és érd el, hogy a megnyomásakor lefusson a rezgoMotor változó cancel() függvénye!

Ehhez:

  1. Létre kell hoznunk egy gombot a layout szerkesztőben (mondjuk állj! felirattal)
  2. Meg kell írnunk egy onClick függvény vázát (legyen a neve: alljOnClick)
  3. Be kell állítanunk az új gombunk onClick tulajdonságát, hogy az alljOnClick függvényünket hívja meg
  4. Be kell fejeznünk az alljOnClick függvényt, hogy állítsa le a rezgést.

        

Ha mindent jól csináltál, akkor a programod futtatásakor most egy gombnyomásra elindíthatod vagy megállíthatod a telefonod rezgését. Ha a 10 másodperces időt túl rövidnek találod, nyugodtan átállíthatod mondjuk 5 percre (hány ezredmásodperc van 5 percben?). Végtelenített rezgést is lehet csinálni, de az egy picit bonyolultabb, ezért most nem foglalkozunk vele.

Az Activity életciklusa

Bevezetés

Ezen a ponton kénytelenek vagyunk kicsit jobban belemászni az alkalmazásunk és az activitynk életciklusába.

Miért van erre szükségünk? Erre láthatsz egy egyszerű demonstrációt:

Mindennek, ami változik, van életciklusa. Például nézzünk meg egy tanévet: Szeptemberben elkezdődik a tanítás, hétvégente egy picit pihenhetünk, a nagyobb szünetekben (ősz, tél, tavasz) még a tankönyveket is bavágjuk a sarokba, június végén pedig végleg és visszavonhatatlanul lezárjuk a tanévet.

A fenti probléma megértéséhez meg kell néznünk, hogy mi történik a programunk indításakor, bezárásakor stb.

Mi is az az Activity?

Az Androidos alkalmazásaink alapvető építőköve az Activity. Az Activity biztosítja a kapcsolatot az Android rendszer és a felhasználók között. Kicsit olyan, mint egy Windows ablak, vagy egy Lazarus Form.

Mi is dolgoztunk már Activitykkel, amikor az alkalmazásunk képernyőjének kinézetét szerkesztettük a Layout szerkesztővel, illetve amikor a mögötte rejlő Java kódot írtuk (pl. az onClick függvények). Az activitynk neve Main volt, ezért is hívták a Java fájlunkat MainActivity.java-nak, a layoutot pedig activity_main.xml-nek. Fontos megérteni, hogy ez a kettő szorosan összefügg: layout nélkül nincs mi megjelenjen, és a Java kód nélkül nem csinál semmit az alkalmazásunk.

Ez a kettő együtt alkotja a mi MainActivitynket!

Azt is fontos tudnunk, hogy a mi MainActivity ablakunk nem működne rendesen, ha nem venne kölcsön néhány sornyi programot a Google által írt általános, unalmas, üres Activitytől. Egészen pontosan a projektünk létrehozásakor a MainActivity épp úgy viselkedik, mint egy áltagos Activity, de nekünk lehetőségünk van a működését (java) és kinézetét (layout) egy kicsit kibővíteni.

Erre szép példa az onCreate függvényünk, ami eddig mágikus módon minden projektünkben benne volt.

Tehát a lényeg: van pár nagyon fontos függvényünk, amit felhasználhatunk az Android programozás folyamán. Az onCreate az egyik ezek közül, ami mindig a program indításakor fut le. Van azonban néhány hasonló függvény is, ezekkel fogunk nemsokára megismerkedni

Vessünk most egy pillantást a Java kódra (MainActivity.java), és ismerkedjünk meg a kód különböző részeivel.

Kattints a zölddel bekeretezett részekre, hogy megtudd: melyik sor mire való. Baloldalt egy általános magyarázatot találsz, jobboldalon pedig egy program kezdeményt láthatsz az előző leckéből

Kattints rá lent a zöld keretes részekre, hogy megtudd, mire valók!

A package segít a programunk fájljainak rendszerezésében. Ez főként akkor szükséges, ha nagyobb programokat írunk, egyelőre nem kell vele foglalkozni.

Az Android Studio ide sorolja fel, hogy hol találhatók a különböző osztályok, amiket használunk az alkalmazásunkban. Többnyire ezzel sem kell foglalkoznunk.

Ez az activitynk neve. Meg kell, hogy egyezzen a fájl nevével.

Az "extends Activity" rész garantálja, hogy megörököljük a Google által megírt általános Activity osztály összes kódját

A komponenseinket (Button - gombok, TextView - feliratok, stb) érdemes rögön (az osztály elején) deklarálni, bemutatni. Így később könnyebben találjuk meg őket. Ne feledd, ezt Lazarusban nem kellett megtenned., itt viszont szükséges.

Az összes többi változó jöhet ide. Például az előző leckében a gondolt számot, vagy épp a véletlen számot készítő generátort mutattuk itt be.

Ezek a sorok elengedhetetlenek a programunk megfelelő működéséhez. Az első parancsról (super) nemsokára beszélünk majd. A második parancs (setContentView) teljesen lényegtelen, ha véletlenül mégis érdekelne: ez a sor határozza meg, hogy az activity_main.xml layout jelenjen meg az Activity elindulásakor, ha úgy tetszik, ez kapcsolja össze a Java kódot (mit csinál) és a layoutot (hogy néz ki).

Érdemes a változóinkat még az OnCreate függvényben társítani, mert így a program indulásátától kezdve nem kell miattuk többé aggódnunk.

Ha van olyan változónk, aminek a program elején még értéket kell adnunk, azt itt tehetjük meg.

Itt sorakozhatnak egymás alatt a különböző függvényeink.


                

                

3. feladat: Életciklus függvények

Az eredeti Activity osztálynak van pár érdekes függvénye, amivel eddig nem is találkoztunk, vagy csak nagyon érintőlegesen használtunk. Ezek olyan függvények, amit az Android rendszer automatikusan indít el adott pillanatban, ezzel jelezve az alkalmazásunknak (vagy épp az Activitynknek), hogy bekövetkezett az adott életciklus eseménye ( → ezért életciklus függvények).

Például nézzük meg a fenti interaktív példán az onCreate függvényt.


protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);   // a super sor minden örökölt függvénynél kell!

    // kiegészítés 
    // ide jön a saját kódunk
}

Az első sor: super.<függvény neve>(<esetleges paraméterek>) biztosítja, hogy az a függvény is lefut, amit a Google-nél írtak meg. Ez szükséges ahhoz, hogy egyáltalán működni tudjon az alkalmazásunk.

Az Activitynk onCreate függvényét akkor futtatja a rendszer, amikor frissen elindítjuk az alkalmazást. Ez a legkorábbi pont, ahol bármit tudunk csinálni az alkalmazás futása során. Érdemes ide rakni a változóink értékadását (pl.: layout komponensek: gombok, TextView-k, stb. illetve egyéb rendszerkomponensek: rezgő motor).

Az elindulás (születés) nagyon fontos esemény, de koránt sem az egyetlen az Activitynk futása (élete) során. Nézzünk meg még kettőt, és rögtön használjuk is fel őket!

3.1 onPause

Az onPause függvényt akkor hívja a rendszer, amikor az alkalmazásunk futása valamiért felfüggesztésre kerül. Ez történik például a programunk bezárásakor (vissza gombbal, vagy a home gombbal).

A TODO 3.1 helyre írd be az alábbi pár sort!


@Override
protected void onPause() {
    super.onPause();
    // ide írhatjuk a saját kódunkat...
}

Az @Override sort nem muszáj beírnod. Ez csak egy komment-szerű jelzés, hogy az Activity egyik függvényét készülünk bővíteni; ezért nem is találkoztál még vele a saját (pl. gomb kattintás kezelő) függvényeidnél, hiszen azok a Google által írt "ős-Activityben" nem szerepelnek.

Például írjuk be saját kódként azt a parancsot, ami leállítja a telefon rezgését!

Ha most elindítjuk a programot, mit tapasztalunk a program bezárásakor? Abbamarad a rezgés?

3.2 onResume

Az onResume függvényt többnyire akkor hívja a rendszer, amikor az alkalmazás visszatér a felfüggesztett állapotból, de például lefut az első indulásnál is, az onCreate után (ld. később).


@Override
protected void onResume() {
    super.onResume();
    // ide írhatjuk a saját kódunkat...
}

Például írjuk be saját kódként azt a parancsot, ami elindítja a rezgést 5 percig!

Ha most elindítjuk a programot, mit tapasztalunk? Mi történik az első indításkor? És a program menüből történő újraindításánál?

3.3 feladat

Az alábbi ábra azt mutatja meg, hogy mi minden történik az alkalmazásunkkal élete folyamán. A színes buborékok az Activity lehetséges állapotait (Fut, Felfüggesztve), a nyilak pedig a változásokkor futó függvényeket mutatják.

Egyes események következtében (pl. képernyő lezárása) az alkalmazás állapotot válthat, ilyenkor pedig a megfelelő nyílon lévő függvény(ek) elindul(nak). (Az OnDestroy függvénnyel egyelőre nem kell foglalkoznod, de találkozol vele, ha/amikor megcsinálod az első szorgalmi feladatot. )

Próbáld ki, hogy mi történik / melyik függvény hívódik meg, ha …

  1. …elindítod a programot?
  2. …lezárod a képernyőt (oldalsó gombbal)?
  3. …újra kinyitod a képernyőt (felső/oldalsó gombbal)?
  4. …bezárod a programot a vissza gombbal?
  5. …bezárod a programot a home gombbal?
  6. …újraindítod a programot a menüből?
  7. …elforgatod a képernyőt?
  8. …érkezik egy bejövő hívás (Ne vedd fel, és ne kísérletezz ezzel, ha nem vagy benne biztos, hogy nem fog pénzbe kerülni ;) )?

A fenti kérdésekre a válaszokat küldd el nekünk emailben (dkrmg.android@gmail.com)!

Szorgalmi feladatok

1*. Log, avagy naplózás

A fentiek alapján nagyjából rá lehet érezni, hogy mikor melyik életciklus-függvény fut le, de azért ez egy kicsit bizonytalan. Jó lenne pontosan látni, hogy melyik, mikor és milyen sorrendben fut le, nem? A rezgés elindulása és megállítása sok mindent elárult nekünk, de az Android rendszer ennél egy sokkal jobb eszközt is tartalmaz: A LogCat elnevezésű "rendszert", amivel rövid szöveges üzeneteket, értesítéseket tudunk kiíratni úgy, hogy azokat a gépen is láthassuk.

Az ilyen log-üzenetek kiíratása pofon egyszerű. Annyi a dolgunk, hogy a kérdéses helyekre a java kódban beírjuk a következő parancsot:

Log.e("MainActivity", "...ide jöhet egy tetszőleges szöveg, ami meg fog jelenni...");

A fenti utasítás a következőképp épül fel:
Meghívjuk a Log osztálynak e nevű belső függvényét. Az első paraméter ("MainActivity") egy azonosító, hogy könnyebben megtaláljuk, hogy mi írta ki az üzenetet, a második pedig maga az üzenet ("...ide jöhet egy tetszőleges szöveg, ami meg fog jelenni...").

Próbáljuk is ki!

2*. feladat: Jelenlegi működés logolása, naplózása

A már meglévő életciklus-függvényekbe helyezzünk el egy-egy logoló utasítást, és ellenőrizzük, hogy mikor futnak!

2*.1 feladat

  1. Keressük meg az onCreate függvényünk záró kapcsos zárójelét, és elé szúrjunk be egy új sort TODO 4.1.
  2. Ebbe a sorba írjuk be a következőt:
    Log.e("MainActivity", "onCreate fut");
  3. Ugyan ezt végezzük el az onPause és az onResume függvényeinkben is, de figyeljünk oda, hogy mindegyikben a megfelelő üzenetet írassuk ki!

Elvileg most már szépen naplózza az alkalmazásunk a saját működését. De hol tudjuk mi ezt a naplót megnézni?

2*.2 feladat

  1. Ha még nem tettük volna meg, futtassuk az alkalmazásunkat!
  2. Figyeljük meg, hogy miután elindult a telefonon az app, az Android Studio ablakában alul megjelenik egy kisebb ablak (panelnek is hívhatnánk), amiben eleinte semmi nincsen, majd egy idő után megjelenik benne pár sor.
    Ez az ablak a fentebb már említett LogCat rendszer megjelenítője, itt tudjuk visszanézni, hogy milyen üzeneteket naplózott az alkalmazásunk.
  3. Figyeljünk rá, hogy a fenti legördülő listában ez szerepeljen: app: hu.dkrmg.android.razor! Ha nem ez van ott, akkor próbáljuk meg lenyitni a legördülő listát, és onnan válasszuk ki ezt a sort, vagy futtassuk újra az alkalmazást!

Értelmezzük, hogy mit is látunk!

Ha megnézzük, hogy milyen sorrendben jelennek meg az üzenetek, láthatjuk, hogy milyen sorrendben futnak le a különböző életciklus-függvények.

A Logcat ablak emlékszik a korábbi futtatásokra is, ezeket az üzeneteket futtatás előtt érdemes az üzenetek mellett oldalt található kis kuka gombbal törölni.

2*.3 onDestroy naplózása

A MainActivity fájlban már létezik néhány életciklus függvény: onCreate, onResume, onPause. Az ábránkon volt még ezen kívül egy onDestroy nevű függvény is, ami a kilépéskor fut le.

Honnan tudhatjuk meg, hogy pontosan hogyan kell kinézne (azaz mit kell írni a függvény neve elé, és utána, még a nyitó kapcsos zárójel előtt)?

Erre több lehetőségünk is van, most nézzük meg a legkényelmesebbet.

  1. A már meglévő onResume függvényünk záró kapcsos zárójele után hagyjunk ki egy sort, majd kezdjük el beírni a következő megírandó függvényünk nevét: onDestroy.
  2. Ha az első 3-4 karakter leütése után felnézünk a képernyőre, láthatjuk, hogy megjelent egy kis ablak, benne egy csomó olyan függvénnyel, aminek a nevében szerepel valahol az onDe karaktersorozat. Ezek közül már a mi feladatunk kiválasztani a nekünk szükségest, de ez nem okozhat gondot, hiszen csak egy olyan van, aminek a neve onDestroy.
  3. Győződjünk meg róla, hogy az van kijelölve (ha nem, a fel-le nyilakkal jelöljük ki!), majd nyomjunk ENTER-t! Ekkor az Android Studio megírja helyettünk, és még arra is figyel, hogy a kötelező super.onDestroy(); sor is szerepeljen benne.
  4. Az előző feladat alapján helyezzünk el ebbe a függvénybe is egy Log-sort, a megfelelő üzenettel
  5. Futtassuk a programot, és figyeljük a LogCat ablakot!
  6. Most ellenőrizd a 3.3 feladatra adott válaszaidat, és küldd el a válaszokat/változtatásokat egy screenshottal együtt emailben (dkrmg.android@gmail.com)

3*. Morzekód Program

Ezen a héten a szorgalmi feladat során egy rezgő Morze készüléket készíthettek a telefonotokból.

A Morzekódban a betűket rövid és hosszú jelekké alakítjuk. Ha nem hallottál még a Morzekódról, akkor érdemes elolvasni a Wikipédia cikket.

A feladat: EditText mezőbe beírt szöveget gombnyomásra Morze jellé alakítunk, majd lejátsszuk a telefon rezgő motorjával

Ehhez:

Haladó

A MorseCode osztály kódját megtalálod a MainActivity.java mellett. Nyisd meg a fájlt, tanulmányozd egy kicsit, majd próbáld elmagyarázni, hogy miként is működik a MorseCode osztály.