Android

Keretrendszer

AndroidManifest.xml

Ahogy azt már korábban említettük, minden alkalmazásnak kötelezően tartalmaznia kell egy AndroidManifest.xml nevű fájlt a gyökér könyvtárában, mivel ez szolgáltat olyan információkat a rendszernek, amit tudnia kell bármilyen futás előtt. Nézzük meg mit csinál a fájl.

A manifest fájl nevezi meg a Java csomagok neveit. Ez egyedi azonosítóként szolgál az alkalmazásnak.

Leírja az alkalmazás komponenseit – activityket, service-eket, broadcast receivereket és content providereket. Megnevezi az osztályokat, amik implementálják ezeket a komponenseket, és publikálja a képességeit, használatának módját (például melyik Intent üzeneteket képes kezelni). Ezek alapján a deklarációk alapján tudja az Android rendszer, hogy milyen komponensek léteznek és milyen feltételek mellett lehet őket futtatni.

Meghatározza, melyik folyamathoz tartoznak az alkalmazás komponensei.

Meghatározza, milyen engedélyekre van szüksége az alkalmazásnak az API egyes részeinek eléréséhez, valamint más alkalmazásokkal való együttműködéshez.

Meghatározza azokat az engedélyeket is, amivel más alkalmazásoknak rendelkezni kell, hogy használni tudják a komponenseit.

Meghatározza a minimum API szintet, ami szükséges az alkalmazás futtatásához.

Felsorolja a könyvtárakat, amiket az alkalmazásnak mindenképp linkelnie kell.

A fájl strukturája
<?xml version="1.0" encoding="utf-8"?> <manifest> <uses-permission /> <permission /> <permission-tree /> <permission-group /> <instrumentation /> <uses-sdk /> <uses-configuration /> <uses-feature /> <supports-screens /> <compatible-screens /> <supports-gl-texture /> <application> <activity> <intent-filter> <action /> <category /> <data /> </intent-filter> <meta-data /> </activity> <activity-alias> <intent-filter> . . . </intent-filter> <meta-data /> </activity-alias> <service> <intent-filter> . . . </intent-filter> <meta-data/> </service> <receiver> <intent-filter> . . . </intent-filter> <meta-data /> </receiver> <provider> <grant-uri-permission /> <meta-data /> </provider> <uses-library /> </application> </manifest>

Csak ezeket az elemeket lehet használni a fájlban, a fenti struktúra alapján egybeágyazva. Csak a és az elemek kötelezőek, és csak egyszer használhatók. A többi szerepelhet többször is, vagy akár egyszer se. Persze valaminek kell lenni még a kötelezőkön kívül, különben nincs semmi jelentése a fájlnak, így az alkalmazásnak sincs. A különböző szinteken több elem is előfordulhat. Ezeknek a sorrendje tetszőleges lehet.

Az elemekhez attribútumok rendelhetők, amelyekkel testre szabható a viselkedésük. Az elemeket a későbbiek folyamán lépésről-lépésre mutatjuk be, fejezetenként.

Activity

Egy Activity egy alkalmazás komponens, ami egy képrenyőt biztosít, amelyen keresztül a felhasználó kezelheti az alkalmazás szolgáltatásait. Minden activityhez tartozik egy ablak, amelyben a felhasználói felületét rajzolhatja. Ez tipikusan kitölti a képernyőt, de lehet kisebb is, és ekkor másik ablakok előtt helyezkedhet el.

Egy alkalmazás általában több Activityt tartalmaz. Ezek közül van egy, amelyet main activityként kezel. Ez indul el, mikor a felhasználó először futtatja az alkalmazást. Minden activity indíthat másik activityt. Amikor egy új activity indul, az előző megáll és ezt a rendszer egy verembe teszi. Ez a verem LIFO mechanizmus alapján működik, így ha a felhasználó visszatér az új activityből, akkor az előző fog folytatódni. Az ilyen állapotváltozásokat különböző callback metódusok hívásával jelzi a rendszer az activitynek.

Az Activityk életciklusát, állapotváltozásait, valamint az ezekhez tartozó callback metódusokat mutatja be a következő ábra:

Az ábráról látszi, hogy egy activitynek három állapota lehet:

Amikor egy activity háttérbe kerül, a rendszer leállíthatja, hogy memóriát szabadítson fel más activityk számára. Ekkor, ha újra meghívjuk az activityt, akkor újra létre kell hozni azt.

Activity létrehozása

Egy activity létrehozásához az Activity osztály egy leszármazottját kell létrehozni. Ebben az osztályban implementálni kell a callback metódusokat, amiket a rendszer hív, mikor állapotváltozás történik. A következő váz bemutatja egy Activity implementálásának a módját:

public class ExampleActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // The activity is being created. } @Override protected void onStart() { super.onStart(); // The activity is about to become visible. } @Override protected void onResume() { super.onResume(); // The activity has become visible (it is now "resumed"). } @Override protected void onPause() { super.onPause(); // Another activity is taking focus (this activity is about to be "paused"). } @Override protected void onStop() { super.onStop(); // The activity is no longer visible (it is now "stopped") } @Override protected void onDestroy() { super.onDestroy(); // The activity is about to be destroyed. } }

Látszik, hogy a metódusok implementálásakor először mindig hívni kell az ősosztály megfelelő metódusát, mielőtt bármilyen feladatot cégrehajthatnánk. Nézzük meg, mire szolgálnak ezek a metódusok:

Metódus Leírás Leállítható a metódus után? Következő metódus
onCreate() Ezt a metódust kötelező implementálni. A rendszer akkor hívja meg, amikor létrehozza az activityt. Ebben kell inicializálni a felhasználói felület komponenseit. A legfontosabb metódus, amit meg kell hívni a setContentView(). Ezzel lehet definiálni a felhasználói felülethez tartozó layout-ot. A metódus kap egy Bundle objektumot paraméterül, amely tartalmazza az előző állapotát az activitynek (ha van ilyen). Ezt részletesebben lásd az Activity állapot mentése pontban. Nem onStart()
onRestart() Akkor hívódik mikor egy activity megáll és újra indul. Nem onStart()
onStart() Akkor hívódik, mikor az activity előtérbe kerül. Nem onResume() vagy onStop()
onResume() Akkor hívódik, mikor megkapja a fókuszt az activity. Nem onPause()
onPause() A rendszer ezt hívja először, amikor a felhasználó elhagyja az activityt. Ez még nem jelenti azt, hogy az activity meg fog semmisülni. Általában itt kell elvégezni, menteni a permanens változtatásokat, mivel lehet, hogy a felhasználó nem tér vissza többet az activityre. Igen onResume() vagy onStop()
onStop() Akkor hívódik, mikor az activity nem látható többé, háttérbe kerül. Igen onRestart() vagy onDestroy()
onDestroy() Az activity megszűnése előtt hívódik. Ez az utolsó hívás, amit az activity fogad. Ez lehet azért, mert az activity ténylegesen befejeződik, vagy mert a rendszer átmenetileg leállítja az activityt a memóriatakarékosság miatt. Az isFinishing() metódus segítségével lehet lekérdezni, hogy melyik eset történt. Igen -

Activity deklarálása az AndroidManifest.xml fájlban

Minden activityt deklarálni kell a manifest fájlban, hogy a rendszer számára elérhető legyen. Ehhez egy elemet kell az elemen belül elhelyezni.

<manifest ... > <application ... > <activity android:name=".ExampleActivity" /> ... </application ... > ... </manifest >

Láthatjuk a példában, hogy az activity nevét egy attribútumban adtuk. Ez valójában az activityhez tartozó osztály nevét adja meg. Számos további attribútum is megadható, ezekkel tudjuk definiálni a címkéjét, az ikonját, az UI témáját/stíusát stb. Az android:name attribútum az egyetlen, amit kötelezően meg kell adni. Miután egyszer publikálva lett az alkalmazásunk, ezt a nevet ne változtassuk meg, mert néhány funkcionalitást elronthat, például az alkalmazás parancsikonjait.

Az elem szintaxisa a következőképpen néz ki:

<activity android:allowTaskReparenting=["true" | "false"] android:alwaysRetainTaskState=["true" | "false"] android:clearTaskOnLaunch=["true" | "false"] android:configChanges=["mcc", "mnc", "locale", "touchscreen", "keyboard", "keyboardHidden", "navigation", "screenLayout", "fontScale", "uiMode", "orientation", "screenSize", "smallestScreenSize"] android:enabled=["true" | "false"] android:excludeFromRecents=["true" | "false"] android:exported=["true" | "false"] android:finishOnTaskLaunch=["true" | "false"] android:hardwareAccelerated=["true" | "false"] android:icon="drawable resource" android:label="string resource" android:launchMode=["multiple" | "singleTop" | "singleTask" | "singleInstance"] android:multiprocess=["true" | "false"] android:name="string" android:noHistory=["true" | "false"] android:permission="string" android:process="string" android:screenOrientation=["unspecified" | "user" | "behind" | "landscape" | "portrait" | "reverseLandscape" | "reversePortrait" | "sensorLandscape" | "sensorPortrait" | "sensor" | "fullSensor" | "nosensor"] android:stateNotNeeded=["true" | "false"] android:taskAffinity="string" android:theme="resource or theme" android:uiOptions=["none" | "splitActionBarWhenNarrow"] android:windowSoftInputMode=["stateUnspecified", "stateUnchanged", "stateHidden", "stateAlwaysHidden", "stateVisible", "stateAlwaysVisible", "adjustUnspecified", "adjustResize", "adjustPan"] > . . . </activity>
Intent filters használata

Egy elem több intent filtert adhat meg, az elem használatával. Ezek deklarálják, hogy más alkalmazások miként tudják aktiválni az activityt. Ha létrehozunk egy alkalmazást az Android SDK eszközeivel, a törzs activity, ami automatikusan létrejön, tartalmazni fog egy intent filtert, ami deklarálja az activity viselkedését a „main" akció esetén. Ez a „launcher" kategóriába fog kerülni.

<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>

Az <action> elem adja meg, hogy ez a „main" belépési pont az alkalmazáshoz. A <category> elem pedig azt mondja meg, hogy ezt az activityt kell a rendszer alkalmazás indítójába (application launcher) listázni, ezzel megadva a lehetőséget a felhasználónak, hogy futtassa az activityt.

Ha nem szeretnénk több belépési pontot az alkalmazáshoz, azaz nem szeretnénk, hogy más alkalmazások használhassák bizonyos részeit, ezt az egy intent filtert elég megadni. Fontos, hogy csak egy activityhez rendelhető hozzá a „main" akció és a „launcher" kategória. A többi activity elérését explicit intentekkel lehet biztosítani, ekkor ezeknek nincs szükségük külön intent filter megadására.

Azonban, ha egy activityt elérhetővé szeretnénk tenni más alkalmazások számára is, akkor további intent filtereket kell definiálni, hogy reagáljon az implicit intentekre. Minden intent típus esetéban az <intent-filter> elemnek tartalmazni kell egy elemet, és opcionálisan egy <category> elemet, és/vagy <data> elemet. Ezek az elemek adját meg az intent típusát, amit fogadni tud az activity.

<intent-filter android:icon="drawable resource" android:label="string resource" android:priority="integer" > <action android:name="string" /> <category android:name="string" /> <data android:host="string" android:mimeType="string" android:path="string" android:pathPattern="string" android:pathPrefix="string" android:port="string" android:scheme="string" /> </intent-filter>

Az Intentekről és Intent Filterekről később részletesebben is szó esik.

Activity indítása

Egy activityt a startActivity() metódussal lehet indítani, ami egy Intent-et kap paraméterül, amely leírja az indítandó activityt. Emellett tartalmazhat kis menyiségű adatot is, amit az activity indításánál használhatunk.

Egy alkalmazáson dolgozva gyakran kell ismert activityt futtatni. Ehhez tehát Intentet kell létrehozni, ami definiálja az indítanó activityt, az osztályának nevével. Például egy SignInActivity indítása:

Intent intent = new Intent(this, SignInActivity.class); startActivity(intent);

Azonban néha olyan Activityt kell futtatni (pl. email küldés, SMS küldés stb), ami nem tartozik az alkalmazáshoz, hanem a szükséges akciót más alkalmazás biztosítja. Ebben az esetben különösen fontos szerepe van az Intentnek. Létrehozhatunk egy Intent-et, ami leírja az akciót, amit végre akarunk hajtani és a rendszer futtatja egy másik alkalmazás megfelelő activity-jét. Ha több activity is kezelni tudja az Intentet, akkor a felhasználó választhat ezek közül. Például email küldéséhez a következő intentet lehet létrehozni:

Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_EMAIL, recipientArray); startActivity(intent);

Az EXTRA_EMAIL extra hozzáadása az Intenthez az emailcímek tömbjének megadását jelenti, ahová az emailt küldeni kell. Ezeket az email kliens bemásolja majd a címzett mezőjébe.

Activity indítása eredményért

Néha egy activityt olyan célból futtatunk, hogy az valamilyen eredményt szolgáltasson nekünk. Ilyenkor az activityt a startActivityForResult() metódussal kell indítani a startActivity() helyett. Ekkor az eredmény megkapásához az onActivityResult() callback metódusát kell implementálni az Activity osztálynak. Mikor az indított activity végez, visszaad egy eredmény egy Intent-ben az onActivityResult() metódusnak.

Például ha azt akarjuk, hogy a felhasználó válasszon ki egy elemet a telefonkönyvéből, hogy aztán tudjunk valami végrehajtani vele az activiynkben, azt így tehetjük meg:

private void pickContact() { // Create an intent to "pick" a contact, as defined by the content provider URI Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI); startActivityForResult(intent, PICK_CONTACT_REQUEST); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // If the request went well (OK) and the request was PICK_CONTACT_REQUEST if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) { // Perform a query to the contact's content provider for the contact's name Cursor cursor = getContentResolver().query(data.getData(), new String[] {Contacts.DISPLAY_NAME}, null, null, null); if (cursor.moveToFirst()) { // True if the cursor is not empty int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME); String name = cursor.getString(columnIndex); // Do something with the selected contact's name... } } }

A példán keresztül láthattuk az alap logikáját az onActivityResult() metódus használatának. Ha a kérés sikeres volt, akkor a resultCode attribútum a RESULT_OK eredményt veszi fel, a requestCode pedig megegyezik a startActivityForResult() második paraméterével. Az Intentek használatáról bővebben lesz szó az Intent pontban.

Activity leállítása

Egy activity-t a finish() metódusának meghívásával lehet leállítani, valamint egy másik activityt, amit korábban indítottunk a finishActivity() metódussal.

Megjegyzés: Általában nem kell explicit leállítani egy activityt a fenti metódusokkal. Ahogy láttuk az activity életciklusánál, az Android rendszer kezeli az activityk életét.

Activity állapotának mentése

Kidolgozásra vár.

Konfigurációváltozások kezelése

Kidolgozásra vár.

Services

A Service egy felhasználói felülettel nem rendelkező komponens, melyet elsősorban hosszabb időigényű háttérfolyamatok elvégzésére használhatunk. Más komponensek indíthatják a service-t és a háttérben akkor is tovább futnak, ha közben a felhasználó egy másik alkalmazásra váltott. Például háttérben történő zenelejátszás, hálózati kommunikáció, stb.
Alapvetően 2 féle lehet: started és bound.

Started: egy service "started", ha egy alkalmazás komponens (például egy activity) elindítja a startService() metódussal. Ha egyszer elindult a service, akkor folyamatosan futhat még azután is, hogy az őt meghívő komponens megszűnt. Általában ezek a "started" service-k nem adnak vissza értéket a hívónak.

Bound: egy service "bound", ha egy alkalmazás komponens meghívja a bindService() metódust. Ez egy kliens-szerver interfészt kínál, tehát kéréseket küldhetünk és válaszokat fogadhatunk vele.

Content providers

A content providerek kezelik az adatokhoz való hozzáférést. Ez egy standard interfész, amely összeköti egy folyamat adatait egy másik futó folyamattal. Amikor hozzá szeretnénk férni az adatokhoz a content providerrel, akkor a ContentResolver objektumot használhatjuk az alkalmazásunkban, hogy a providerrel kliensként kommunikálhassunk. A ContentResolver objektum a provider objektummal kommunikál, ami egy példánya annak az osztálynak, ami megvalósítja a ContentProvider interfészt. A provider objektum adatokat fogad a kliensektől, végrehajtja a kívánt tevékenységet, majd visszaadja az eredményt.

Intents, Intent filters

A négy fő komponens közül három (Activity, Service, Broadcast receiver) üzenetek segítségével aktivizálódik, melyeket intenteknek nevezünk. Az intentek futási időben kötik össze a különálló komponenseket.
Példa: egy ActivityA nevű activityből így indíthatunk egy ActivityB activity-t:

public void startActivityB(View v) { Intent intent = new Intent(ActivityA.this, ActivityB.class); startActivity(intent); }

Processes, Threads

Amikor egy alkalmazás komponens elindul és az alkalmazásnak nincs más, futó komponense, az android rendszer indít egy új linux process-t, mely egy szálon indítja az alkalmazást. Alapértelezés szerint egy alkalmazás minden komponense egy szálon, egy folyamatként fut. Ha egy alkalmazás komponens indulásakor már az alkalmazás más komponense fut (tehát az alkalmazáshoz már tartozik egy folyamat), akkor az új komponens is ezt a folyamatot és szálat fogja használni. Természetesen a fejlesztő bármikor indíthat új szálakat, vagy folyamatokat.

Folyamatok életciklusa:
A rendszer megpróbálja fenntartani az alkalmazás folyamatát ameddig csak lehet, de végül a régi folyamatokat meg kell szüntetnie, hogy az újabb, fontosabb folyamatok el tudjanak indulni. A rendszer minden folyamatnak meghatározza a prioritását a folyamatban futó komponensek állapotai alapján. Ha szükség van erőforrásra az újabb folyamatok indulásához, akkor a legkevésbé fontos folyamat törlésével kezdi a rendszer, majd a következő legkevésbé fontos folyamattal folytatja és így tovább.

A fontossági hierarchiában 5 szint van:
1. Előtérben futó folyamat: ez a folyamat végzi azt a tevékenységet, amit a felhasználó éppen használ.
2. Látható folyamat: ennek a folyamatnak egyik komponense sincs előtérben, de befolyásolhatja, hogy mit lát a felhasználó a kijelzőn.
3. Service folyamat: olyan folyamat, ami a startService() metódus által indult és nem tartozik az előbbi két kategóriába. Például service folyamat a háttérben történő zenehallgatás, vagy fájlok letöltése.
4. Háttérban futó folyamat: a felhasználó számára nem látható folyamat (az activity onStop() metódusa meghívódott). Ezek a folyamatok nem befolyásolják a felhasználói élményt és a rendszer bármikor kilőheti őket, ha szükség van további erőforrásra.
5. Üres folyamat: ez a folyamat nem tartalmaz semmilyen aktív alkalmazás komponenst. Ezek a folyamatok a legközelebbi indítás gyorsítására, vagy hasonló caching célok miatt léteznek még.

Szálak

Amikor egy alkalmazás elindul, a rendszer automatikusan létrehoz neki egy végrehajtási szálat, amit "main" szálnak, vagy UI szálnak nevezünk. Ez a szál felelős a felhasználói felületen történő események kezeléséért.
A rendszer magától nem hoz létre külön szálakat a komponensek egyes példányaihoz. Minden komponens, ami egy folyamaton belül fut, példányosítva van a UI szálban és a rendszerhívások a komponensekhez ebből a szálból indulnak. Amikor az alkalmazásunk hosszabb műveleteket hajt végre a háttérben (például hálózati műveletek, adatbázis lekérdezések, stb.), akkor ez blokkolni fogja a UI szálat. Ha a UI szál blokkolva van, akkor nem képes kezelni a felhasználói eseményeket, ami a felhasználó szempontjából rossz megoldás. Ilyenkor célszerű új szálakat létrehozni.

Az android UI toolkit nem szálbiztos, ezért két fontos szabályt be kell tartani a szálkezeléssel kapcsolatban:
1. Ne blokkoljuk a UI szálat
2. A UI szálon kívülről ne manipuláljuk a UI toolkitet

Háttér szálak (worker threads)

Az előbb leírtak alapján sokszor szükség lehet háttérben futó szálak létrehozására, hogy a UI szálat ne blokkoljuk. A következő példa elindít egy új szálat, amely letölt egy képet a webről:

public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start(); }

Láthatjuk, hogy ez a példa nem teljesíti a szálak kezelésére vonatkozó második feltételt, mert a háttérszálból próbálja módosítani a UI toolkitet. Ez nem várt eredményeket okozhat, ezért figyeljünk arra, hogy betartsuk a korábban említett két alapvető szabályt! A probléma megoldására az android több lehetőséget is kínál, ezek közül nézzünk egyet:

public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start(); }

Ez a megoldás szálbiztos, a hálózati folyamatokat egy külön szálban kezeljük és az ImageView-t a UI szálon keresztül érjük el. Szebb és átláthatóbb megoldást érhetünk el az AsyncTask segítsévégel.

Az AsyncTask használata: Az AsyncTask egyszerűsíti azon háttérszálak végrehajtását, amelyek a UI szállal kommunikálnak. Végrehajtja a műveleteket a háttérszálon, majd megjeleníti az eredményt a UI szálon. Az alkalmazás készítőjének nem kell külön Thread-et használni. Nézzük az előző példa megoldását AsyncTask segítségével:

public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png"); } private class DownloadImageTask extends AsyncTask { /** Ezt fogja végrehajtani a háttérben */ protected Bitmap doInBackground(String... urls) { return loadImageFromNetwork(urls[0]); } /** A doInBackground() eredményét megjeleníti a UI szálon */ protected void onPostExecute(Bitmap result) { mImageView.setImageBitmap(result); } }
Ez a megoldás átláthatóbb, mert elkülöníti egymástól a háttérszálon és a UI szálon végrehajtandó tevékenységeket.

User Interface

Kidolgozásra vár.

Resources

Kidolgozásra vár.

Data storage

Kidolgozásra vár.

Permissions, Security

Kidolgozásra vár.