Anda di halaman 1dari 15

MIT CD!

www.android360.de

iOS unter Druck Android auf dem Vormarsch

Vol.1

Fakten fr Android-Entwickler

MOBILE COMMANDER

SONDER

HEFT

Die Android-Plattform als mobile Steuerzentrale

ONUS INEB ONL


Android Remoting
Clientseitige Implementierung der Serverkommunikation

Caching fr Pros
Caching mit den Content-Providern

EDITORIAL

VOLUTION IST DA, DIE MOBILE RE EHT KEIN ZWEIFEL DARAN BEST
Android Remoting
von Gregor Roth

ENDE FOLGENDE SPANN AUF SIE: ARTIKEL WARTEN


besDie zunehmend mit sere Versorgung ndigen mobilen, breitba sowie Datendiensten leistungsfdie immer schafhigeren Endgerte bessere Vorfen immer chwertiger, die Entwicklung ho ssetzungen fr au endungssis verteilter Anw obiler Apps auf Ba m munizieren pischerweise kom architekturen. Ty P-Ansatz den RESTful-HTT solche Apps ber mme Use sti Diensten, um be chtet mit serverseitigen ieser Artikel beleu . D Cases umzusetzen clientseitige Anstze fr die unterschiedliche ikation. der Serverkommun Implementierung

Liebe Leserinnen

und Leser,

angeknder Print-Ausgabe wie ich bereits in mehr zu bieten, droid sehr viel digt habe, hat An knnten. Daziges Heft packen s wir in ein ein al viel Wissen n darin schon so bei haben wir Ihne n in Andron die Neuerunge geboten: Sie habe hren, wie nt, Sie haben erfa id 2.2 kennengeler nur in der tt n-Source-OS nich sich Googles Ope z sichert s einen festen Plat ne Welt der Smartpho vider von Android erheitskonzept aching-Content-Pro und wie das Sich ben Sie einen C ber hinaus ha hwarz funktioniert. Dar nsentwicklung von Ronan Sc in die Applikatio Anlten sten Einblick er R erha Jeder, der mobile h-Technologie AI alityen entwickelt, auf Basis der Flas d-Re wendung rch die Augmente r oder aber und einen Blick du wird sich frhe fen. Viel Wissen e Welt gewor g belBrille auf di spter mit Cachin t. Deswegen erha g aus unserer Sich n. Eine eitere nicht genu schftigen msse eausgabe zwei w mit dieser Onlin e Daten ten Sie Applikation, di er bespannende Artikel. bezogene Sonvon einem Serv ste themen VPN, prosehen also: Das er den Sie scheinen ternet oder einem vierteljhrlich er zieht, sei es vom In em Cache. derheft unseres das sich ber Anlen Fllen von ein chnology, tiert in nahezu al mit ContentwichHefts Mobile Te etet eite d-Framework bi der vollen Bandbr gien Das Androi stungsfhige droid hinaus mit gien und Strate einfache und lei iler Technolo ovidern eine tiger mob angekom- Pr Problem zu lsen. ch nicht am Ende Mglichkeit, das beschftigt, ist no men

viel Spa mit dem In diesem Sinne Android 360 Onlinebonus von
Thomas Wieeck Redakteur el

ANDROID360

www.android360.de

REST

Implementierungsanstze fr eine REST-basierte Serverkommunikation

Android Remoting
Die zunehmend bessere Versorgung mit mobilen, breitbandigen Datendiensten sowie die immer leistungsfhigeren Endgerte schaffen immer bessere Voraussetzungen fr die Entwicklung hochwertiger, mobiler Apps auf Basis verteilter Anwendungsarchitekturen. Typischerweise kommunizieren solche Apps ber den RESTful-HTTP-Ansatz mit serverseitigen Diensten, um bestimme Use Cases umzusetzen. Dieser Artikel beleuchtet unterschiedliche Anstze fr die clientseitige Implementierung der Serverkommunikation.

von Gregor Roth


Typischerweise verfgten die meisten Android-Applikationen ber ein User Interface. Ein User Interface wird bei Android mithilfe von Activity-Komponenten implementiert, wobei eine Activity in der Regel eine einzelne Bildschirmseite reprsentiert. Die Oberflche einer Bildschirmseite setzt sich aus den sichtbaren Oberflchenelementen, den Views zusammen. Beispielsweise wird ein Textfeld mithilfe einer TextView abgebildet. Typischerweise werden die Oberflchen in einer XML-Datei definiert. Um innerhalb des Java-Programmcodes mittels der findViewById()-Methode auf eine View zugreifen zu knnen, ist bei der Deklaration der View-Komponente eine andorid:id anzugeben. Wie in der UserInfoActivity-Klasse zu sehen ist, wird typischerweise innerhalb der Activity-Initialisierung bzw. der Lifecycle-Methode onCreate() zunchst der Bildschirm ber die Methode setContentView(..) gesetzt. Hierbei wird der Name der

entsprechenden XML-Layoutdatei bergeben. Danach werden die dynamischen Oberflchenelemente mithilfe der Methode findViewById() ermittelt und mit den entsprechenden Werten gesetzt. Bei verteilten Anwendungen sind die relevanten Daten hufig auf einem Server gespeichert und werden dort verwaltet. Das heit, um die bentigten Daten fr die Darstellung zu erhalten, muss zunchst eine Remote-Abfrage beim Server erfolgen (Listing 1).

RESTful HTTP
Bei Android-Applikationen stellt in der Regel der RESTful-HTTP-Ansatz die prferierte Architektur fr die Serverkommunikation dar. Die RESTful-HTTP-Architektur besagt im Wesentlichen, dass die Methoden des HTTP-Protokolls wie GET, POST, DELETE oder PUT protokollkonform angewendet werden, um mit dem Server Informationen auszutauschen. GET dient dazu, Daten von der Serverseite abzuholen, POST bzw. PUT

www.android360.de

ANDROID360

REST

dazu, um Daten an die Serverseite zu bertragen bzw. zu aktualisieren. Eine detailliertere Betrachtung von REST bzw. RESTfull HTTP ist unter [1] zu finden. Im Gegensatz zum SOAP-Protokoll wird REST durch Android direkt untersttzt. Die Android-Plattform beinhaltet alle Bibliotheken, die typischerweise fr eine REST-basierte Kommunikation bentigt werden. So sind beispielsweise der Apache Commons HTTPClient und ein JSON-Parser auf Basis des org.json-APIs Teil der Android-Distribution. Artefakte fr das eher schwergewichtigere SOAP-Protokoll sind in der aktuellen Implementierung von Android nicht zu finden. In diesem Fall

Listing 1: Activity-Klasse
public class UserInfoActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.user_info); UserInfo userInfo = TextView nameView = (TextView) findViewById(R.id.name); nameView.setText(userInfo.getName()); // } }

Listing 2: REST-Aufruf
public class UserRESTClient { private final AndroidHttpClient httpClient = AndroidHttpClient.newInstance("MyUserAgent"); public UserInfo getUserInfo(String userURI) { HttpUriRequest request = new HttpGet(userURI); request.setHeader("Accept", "application/json"); request.setHeader("Accept-Encoding", "GZIP"); HttpResponse response = httpClient.execute(request); int status = response.getStatusLine().getStatusCode(); if (status == HttpStatus.SC_OK) { InputStream instream = AndroidHttpClient.getUngzippedContent(response.getEntity()); String charset = EntityUtils.getContentCharSet(response.getEntity()); String jsonString = IOUtils.toString(instream, charset); JSONObject jsonObject = new JSONObject(userInfoString); UserInfo userInfo = new UserInfo(); userInfo.setUri(jsonObject.getString("uri")); // return userInfo; } // } }

muss dann auf Bibliotheken wie kSOAP2 zurckgegriffen werden. Da die Android-Plattform jedoch nicht das komplette Java-5.0-API bereitstellt, sind einige JavaBibliotheken nicht ohne Weiteres einsetzbar. Um auf die Besonderheiten der Android-Plattform einzugehen, sind von manchen Bibliotheken spezielle Versionen fr Android entstanden. Dies trifft auch fr kSOAP2 zu. Aufgrund der Inaktivitt des kSOAP2-Projekts sind die Patches jedoch auerhalb des Projekts entstanden. Im Gegensatz zu stationren PCs, die hufig mit einer hohen Bandbreite an das Internet angeschlossen sind, spielen bei mobilen Endgerten die Netzabdeckung, die verfgbare Bandbreite und die limitierten Hardwareressourcen eine wichtige Rolle. Oft verfgen die mobilen Endgerte nur ber eine geringe Bandbreite, was zu langen Ladezeiten fhren kann. Des Weiteren kann ein erhhter Bandbreitenbedarf, je nach Vertragstyp des Nutzers, schnell zu hohen Kosten fhren. Eine Reduzierung des bentigten Datenstroms spart aufgrund der verkrzten Kommunikationszeit wichtige Batterielaufzeit. Daher ist bei der Remote-Kommunikation darauf zu achten, dass mglichst nur die wirklich bentigten Daten bertragen werden sowie dass die Hufigkeit der Remote-Aufrufe mglichst niedrig gehalten wird. Bei der Wahl der Encodierung der zu bertragenden Daten ist ein kompaktes Format zu whlen. Flexible REST-Services erlauben es dem Client, den gewnschten MIME-Typ der Antwortstruktur im Request mitzuteilen. HTTP definiert hierfr den Header Accept, der den gewnschten MIME-Typ deklariert. Hufig untersttzen REST-Services die MIME-Typen application/json bzw. application/xml. Manche RESTServices bieten des Weiteren binre MIME-Typen an. Aus dem Gesichtspunkt der Bandbreitenbetrachtung gilt folgende Tendenz: Binrdaten vor JSON vor XML. Jedoch fhrt die Nutzung binrer Formate hufig zu erhhten Aufwendungen und Komplexitt bei der Implementierung des (De-)Marshallings. Im Gegensatz zu XML oder JSON kann beim Umgang mit den binren Typen in der Regel nicht auf hherwertige Bibliotheken in der Android-Plattform zurckgegriffen werden. Zudem existieren nur wenige, nicht proprietre binre Datenformate mit einer nennenswerten Verbreitung. Daher wird oft auf JSON zurckgegriffen. Um trotzdem ein gutes Bandbreitenverhltnis zu erreichen, sollten die zu bertragenden Daten komprimiert werden. In der Regel knnen bei application/json und application/xml sehr gute Kompressionsraten erzielt werden. Ein Vergleich der bentigten Bandbreite und der Performance fr unterschiedliche MIME-Typen ist unter [2] zu finden. Im Codebeispiel wird der Request mit dem Header Accept-Encoding: GZIP angereichert, um dem Server mitzuteilen, dass dieser mglichst die Antwortdaten komprimieren soll. Fr den Aufruf des Servers wird im Beispiel der AndroidHttpClient verwendet, der seit dem Release 2.2 von Android zu Verfgung steht. Der AndroidHttpClient basiert auf dem Apache Commons HTTPClient und setzt bei diesem typische Kon-

ANDROID360

www.android360.de

REST

figurationswerte, passend fr die Android-Umgebung. Beispielsweise wird durch den AndroidHttpClient der connectionTimeout und der soTimeout des zugrunde liegenden HttpClient auf 20 Sekunden gesetzt. Die Securityarchitektur von Android erfordert fr den Zugriff auf bestimmte Systemressourcen spezielle Privilegien. Um die bentigten Berechtigungen fr den Zugriff auf das Internet zu erhalten, ist in der AndroidManifest. xml die Permission android.permission.INTERNET zu setzen. Die der Applikation zugeordneten Permissions aus AndroidManifest.xml werden dem Anwender bei der Installation der Applikation angezeigt. Der Benutzer hat dann die Mglichkeit, die Installation abzubrechen, falls ihm die Permissions zu weitreichend sind.

Prozessierung langlaufender Operationen


Der naheliegende Ansatz, die getUserInfo()-Methode des RESTServiceClient direkt innerhalb der Activity aufzurufen, erweist sich als eine Falle. Potenziell langlaufende Aktivitten, z. B. Serveraufrufe, drfen nie innerhalb einer Activity direkt ausgefhrt werden. Aktivitten laufen im Kontext des Main Threads. Das heit, die Lifecycle-Methoden der Activity, beispielsweise die onCreate()-Methode, werden durch den Main Thread behandelt. Das Gleiche gilt auch fr die Behandlung von Oberflchenereignissen wie ein touch event. Ist beispielsweise ein OnClickListener bei einem Aktualisierungsbutton registriert, so wird die entsprechende Listener-Methode innerhalb des Main Threads aufgerufen. Findet nun in der onCreate()-Methode oder in der Listener-Implementierung ein Serveraufruf statt, so besteht die Gefahr, dass der Main Thread zu lange blockiert. In der Regel sollte der Main Thread nicht lnger als 100 bis 200 ms durch eine einzelne Aktion blockiert werden, um auf weitere eingehende Ereignisse zeitnah reagieren zu knnen. Da die Oberflchenelemente von Android nicht thread-safe sind, mssen auch alle Oberflchenaktualisierungen innerhalb des Main Threads durchgefhrt werden. Beim Starten einer Applikation wird standardmig ein Linux-Prozess mit einer dedizierten Dalvik-VM-Instanz gestartet, innerhalb derer die Applikation ausgefhrt wird. Per Default wird bei der Initialisierung ein einziger Applikations-Thread, der Main Thread gestartet. Dem Main Thread ist wiederum eine MessageQueue zugeordnet. Diese MessageQueue liest der Main Thread nach dem Starten in einer Endlosschleife aus und ruft fr jede Message den entsprechenden Handler zur Behandlung auf. Treten beispielsweise an der Oberflche Userevents auf, so werden diese von dem Android UI Toolkit in die MessageQueue eingestellt, um dann in der Endlosschleife durch den Main Thread behandelt zu werden. Mithilfe dieses Ansatzes werden die Events der Applikation serialisiert und durch den Main Thread synchronisiert behandelt. Blockiert ein Handler, beispielsweise der oben erwhnte OnClickListener den Main Thread fr eine lngere Zeit, so ist die gesamte Applikation eingefroren. Um dies zu verhindern, berwacht

das Android-Laufzeitsystem das Antwortverhalten von Applikationen. Erfolgt beispielsweise innerhalb einer gewissen Zeit keine Reaktion auf den Userevent der Oberflche, wird ein Application Not Responding (ANR) ausgelst. Der ANR ffnet einen Dialog, der den Benutzer auf eine nicht mehr reagierende Applikation hinweist und ihm die Mglichkeit gibt, die Applikation zu schlieen, d. h. das Terminieren des zugrunde liegenden Prozesses zu erzwingen. Potenziell langlaufende Aktivitten wie Serveraufrufe sollten daher immer innerhalb dedizierter Java Threads ausgefhrt werden. Typischerweise wird der Serveraufruf als ein eigenstndiger Aktionscode implementiert. Fr die Prozessierung des Serveraufrufs innerhalb eines Worker Threads bietet sich das Executor-Framework aus dem java.util.concurrent Package an. Das ExecutorFramework stellt verschiedene Thread-Pools zur Verwaltung und Ausfhrung von Threads bereit. Auf Basis der Ergebnisdaten des Serveraufrufs ist hufig die Benutzeroberflche zu aktualisieren. Diese Aktualisierung darf jedoch nur im Rahmen des Main Threads erfolgen. Analog zum Serveraufruf wird die Aktion zur Oberflchenaktualisierung typischerweise als ein eigenstndiges Codefragment implementiert. Da die Oberflchenaktualisierung durch den Main Thread stattfinden muss, ist der Oberflchenaktualisierungscode in die MessageQueue des Main Threads einzustellen. Hierzu kann auf die Handler-Klasse des Android-APIs zurckgegriffen werden. Die Handler-Klasse stellt unter anderem die post()-Methode zu Verfgung. Beim Aufruf dieser Methode wird ein Runnable Object bergeben, das in eine Message verpackt und anschlieend in die Queue eingestellt wird. Innerhalb der Endlosschleife des

Listing 3: Threaded Serveraufruf innerhalb der Activity


public void onCreate(Bundle savedInstanceState) { // final Handler handler = new Handler(); Runnable serverCallAction = new Runnable() { public void run() { final UserInfo userInfo = restClient.getUserInfo("http://myserrver/srv/UserInfo/34"); Runnable uiUpdateAction = new Runnable() { public void run() { TextView nameView = (TextView) findViewById(R.id.name); nameView.setText(userInfo.getName()); // } }; handler.post(uiUpdateAction); } }; executor.execute(serverCallAction); } }

www.android360.de

ANDROID360

REST

Main Threads wird dann die Message bzw. das Runnable-Objekt aus der MessageQueue gelesen und ber den Aufruf deren run()-Methode innerhalb des Main Threads prozessiert. Im Rahmen der Oberflchenaktualisierungsaktion kann es auch passieren, dass die Activity zwischenzeitlich zerstrt wurde. Beispielsweise lst ein Drehen des Endgerts ein zerstren der Activity aus. Die Verbindung zwischen dem Handler-Objekt und der MessageQueue des Main Threads erfolgt im Konstruktor der Handler-Klasse. Innerhalb des Konstruktors greift das Handler-Objekt auf eine Threadlocal-Variable des aktuellen Main Threads zu und speichert dessen MessageQueue als member-Variable. Die MessageQueue wird im Rahmen der Initialisierung des Main Threads als Threadlocal-Variable an den Main Thread gebunden. Aus diesem Grund ist der Handler immer innerhalb des Main Threads zu instanziieren. Aufgrund des Android-Prozess-/Thread-Modells besteht ein typischer Serveraufruf aus zwei Aktionen. Dem eigentlichen Serveraufruf, der in einem dedizierten Worker Thread stattfinden sollte, sowie aus einer Aktualisierungsaktion, die wiederum im Kontext des Main Threads ausgefhrt werden muss. Alternativ zum obigen Codebeispiel kann statt der Handler-Klasse auch die ConvenienceMethode runOnUiThread() der Activity verwendet werden. Diese Methode nutzt intern analog dem Codebeispiel, einen Handler, der das Activity-Objekt implizit bei deren Initialisierung erstellt. Bei der Nutzung der Convenience-Methode muss im Applikationscode kein Handler-Objekt mehr fr die Synchronisation erzeugt werden. Mit der Version 1.5 wurde Android zudem um die Klasse AsyncTask erweitert. Diese Klasse ist auf das Szenario Hintergrundaktion mit anschlieender Modifikation der Oberflche zugeschnitten. Zur Verwendung dieser Klasse ist von ihr abzuleiten

und die entsprechenden Callback-Methoden sind zu implementieren.

Lebenszyklus von Komponenten


Die Android-Laufzeitumgebung kann jederzeit den der Applikation zugrunde liegenden Prozess beenden. In diesem Fall werden implizit alle innerhalb des Prozesses gestarteten Threads beendet und die innerhalb der Applikation laufenden Komponenten, beispielsweise eine Activity oder ein Service, zerstrt. In der Regel versucht Android so lange wie mglich, Prozesse nicht eigenstndig zu beenden. Steht jedoch nur noch wenig Speicher zur Verfgung, der von konkurrierenden Applikationen bentigt wird, geht Android dazu ber, weniger wichtige Prozesse zu beenden. Die Prioritt eines Prozesses hngt davon ab, welche Komponententypen innerhalb des Prozesses ausgefhrt werden und in welchem Status sich die Komponenten befinden. Die hchste Prioritt haben Prozesse, die beispielsweise eine auf der Oberflche des Endgerts sichtbare Activity darstellen. Relativ hohe Prioritt haben auch Prozesse, die einen Service prozessieren. Prozesse, die nur Activity-Komponenten ausfhren, die auf der Oberflche nicht mehr sichtbar sind bzw. bei denen die Lifecycle-Methode onStop() aufgerufen wurde, haben nur noch eine relativ niedrige Prioritt. Beispielsweise treten solche Prozesse auf, wenn der Nutzer die Home-Taste drckt, um in eine andere Applikation zu springen. In der Regel existieren recht viele solcher Prozesse. Ist die Activity des obigen Beispiels nicht mehr sichtbar, so ist die Wahrscheinlichkeit hoch, dass deren Prozess im Fall von Speichermangel beendet wird. Bei der Zerstrung des Prozesses werden auch alle eventuell offenen Serveraufrufe terminiert. Dieses Verhalten kann strend wirken, wenn zum Beispiel im Hintergrund Threads fr die Verwaltung eines Response Caches ausgefhrt werden oder wenn langlaufende HTTPVerbindungen gehalten werden sollen. Beispielsweise nutzen Chatapplikationen oft einen Server-Push-Ansatz [3], der auf langlaufenden HTTP-Verbindungen basiert. Um zu verhindern, dass Remote-Aufrufe bzw. offene HTTP-Verbindungen aufgrund der niedrigen Prozessprioritt zerstrt werden, knnen die Serveraufrufe in einen Service ausgelagert werden. Neben der Activity sind der Service, der ContentProvider und der BroadcastReceiver die vier Komponententypen der Android-Plattform. Ein Service dient dazu, langlaufende Operationen im Hintergrund auszufhren. Wie die anderen Komponententypen auch, muss ein Service in der AndroidManifest.xml-Datei deklariert werden. Der Service wird im Main Thread der Applikation prozessiert. Alle in einer Applikation vorhandenen Komponenten wie die Activity-Komponenten oder eine Servicekomponente werden im gleichen Main Thread ausgefhrt. Ein Service ist weder ein eigener Thread noch ein eigener Prozess. Er befindet sich im gleichen Prozessraum wie eine Activity-Komponente. Aufgrund der Priorittsregel hat der Prozess auch dann noch eine relativ

Listing 4: Local-Service-Implementierung
public class MyService extends Service { public int onStartCommand(Intent intent, int flags, int startId) { // } public IBinder onBind(Intent intent) { return binder; } private final MyLocalServiceBinder binder = new MyLocalServiceBinder(); public class MyLocalServiceBinder extends Binder implements IMyService { public UserInfo getUserInfo(String userURI) throws IOException { return restClient.getUserInfo(userURI); } } }

ANDROID360

www.android360.de

REST

hohe Prioritt, wenn die Activity nicht mehr sichtbar ist, der enthaltene Service aber noch luft. Um die Fairness zwischen den Applikationen aufrechtzuerhalten, sollten Services nur dann gestartet werden, wenn diese auch wirklich bentigt werden. Ebenso sollten Services mglichst zeitnah wieder beendet werden, wenn sie nicht mehr bentigt werden.

Android Local Services


Android unterscheidet zwischen local- und remote-Services. Im Gegensatz zum einem remote-Service kann ein local-Service nur von Komponenten der gleichen Applikation aufgerufen werden. Der Aufrufer, beispielsweise eine Activity, luft im gleichen Prozess wie der aufgerufene Service. Ein Service kann auf zwei Arten gestartet werden. Der Aufruf der startService()-Methode am Context-Objekt startet den Service und gibt den ComponentName zurck. Sollte der Service bereits laufen, wird er nicht ein zweites Mal gestartet. Innerhalb der startService()-Methode ist ein Intent-Objekt zu bergeben, das den Zielservice adressiert. Typischerweise wird hierbei ein Intent-Objekt erzeugt, dem im Konstruktor die Klasse des betroffenen Service bergeben wird. Auf der Seite des Service werden innerhalb der Startprozedur die Lifecycle-Methoden onCreate() und onStartCommand() aufgerufen. Der Aufruf der Lifecycle-Methoden findet durch den Main Thread der Applikation statt. Die onBind()-Methode des Service wird beim Starten des Service durch startService() nicht aufgerufen. Diese Methode kommt erst dann ins Spiel, wenn auf der Clientseite eine Kommunikationsverbindung mit dem Service ber die Methode bindService() hergestellt wird. ber diese Kommunikationsverbindung knnen die Clients den Service auf Basis eines RPCPatterns aufrufen. Anhand eines flag-Parameters wird zudem beim Aufruf der bind-Methode gesteuert, ob der Service gestartet werden soll, falls er noch nicht luft. Dies erspart einen vorherigen startService()-Aufruf. Die onBind()-Methode des Service gibt ein applikationsspezifisches Binder-Objekt zurck, das die Clients verwenden, um den Service aufzurufen. Hierbei ist zu beachten, dass die Aufrufe des Services, analog normaler Methodenaufrufe, synchron erfolgen. Der Aufruf wartet bzw. blockiert so lange, bis die Antwort vom Service zurckgegeben wird. Um langzeitig blockierende Methodenaufrufe innerhalb des Main Threads zu vermeiden, kann von der AsyncTask-Klasse abgeleitet werden. Die clientseitige Implementierung des Serveraufrufs findet dann in der Methode doInBackground() statt, die Aktualisierung der Oberflche in der onPostExecute()-Callback-Methode. Leider untersttzt die AsyncTask-Klasse keine unmittelbare Behandlung von Exceptions. Alternativ knnte hier auch ein Callback Pattern [4] verwendet werden. In diesem Fall wird statt der Businessmethode UserInfo getUserInfo(String userURI) throws IOException eine Methode wie void getUserInfo(String

userURI, IResultCallback<UserInfo> callback) verwendet. Der Main Thread blockiert nur fr eine sehr kurze Zeit, da der Service den Request lediglich zur Prozessierung annimmt, um ihn dann asynchron ber einen dedizierten Thread zu verarbeiten. Die Verwendung eines AsyncTask auf der Clientseite wird berflssig. Lediglich die Aktualisierung der Oberflche auf Basis des Ergebnisses muss wieder im Rahmen des Main Threads stattfinden. Zur bermittlung des Ergebnisses ruft der Service die entsprechende CallbackMethode des IResultCallback<UserInfo>-Objekts auf, beispielsweise onResult(UserInfo userInfo) oder onError(IOException ioe). Ein Callback Pattern wird ebenfalls beim Aufruf der bindService()-Methode an der Context-Klasse verwendet. Fr den Aufbau einer Kommunikationsverbindung zum Service bergibt der Client neben dem Intent und dem start Flag auch ein applikationsspezifisches ServiceConnection-Objekt. Die ServiceConnection-Klasse definiert Callback-Methoden, um Statusnderungen der Kommunikationsverbindung zu erkennen. Die bindService(...)-Methode gibt also weder ein Binder- noch ein ServiceConnectionObjekt direkt zurck. Das notwendige Binder-Objekt zur Kommunikation mit dem Service wird erst in einem nachfolgenden Loopzyklus des Main Threads durch den Aufruf der Callback-Methode onServiceConnected() des ServiceConnection-Objekts bergeben.

Listing 5: Serviceaufruf innerhalb der Activity


public class UserInfoActivity extends Activity { private IMyService myService; public void onCreate(Bundle savedInstanceState) { // Intent intent = new Intent(this, MyService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } private ServiceConnection connection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { myService = (IMyService) service; new AsyncTask<String, Integer, UserInfo>() { protected UserInfo doInBackground(String... params) { return myService.getUserInfo(params[0]); } protected void onPostExecute(UserInfo userInfo) { TextView nameView = (TextView) findViewById(R.id.name); nameView.setText(userInfo.getName()); // } }.execute("http://myserrver/srv/UserInfo/34"); } // }; }

www.android360.de

ANDROID360

REST

Android Remote Services

Die Implementierungen von local-Services und remote-Services sind sich sehr hnlich. Der wesentliche Unterschied ist die Binder-Implementierung, die vom Service in der onBind()-Methode bereitgestellt wird. Im Gegensatz zum Binder des local-Service untersttzt der Binder des remote-Service eine Inter Process Communication (IPC). Der Begriff Binder steht bei Android nicht nur fr einen Interface- bzw. Klassenamen, sondern definiert auch eine Architektur fr Inter Process Communication. Die Binder-Architektur von Android hat ihren Ursprung in Open Binder [5] und findet hufige Anwendung innerhalb des Laufzeitsystems von Android. Im Beispiel des local-Service gibt die onBind(...)-Methode ein MyLocalServiceBinder-Objekt an die Android-Laufzeitumgebung zurck. Genau diese Objektinstanz wird dann dem Client mittels der Call-

back-Methode onServiceConnected() bereitgestellt. Im Fall des remote-Service gibt die onBind()-Methode stattdessen den MyRemoteServiceBinder zurck. Diese Klasse implementiert sowohl das fachliche Interface als auch die schnittstellenspezifische Logik zur Untersttzung des Remote-Aufrufs. Leider untersttzt die IPCArchitektur keine anwendungsspezifischen Exceptions. Fr die Exception-Behandlung knnte beispielsweise die Antwort in ein Result-Objekt eingebettet werden, das ein zustzliches Feld fr die Fehlermeldung beinhaltet. Alternativ kann auch ein Callback Pattern implementiert werden:
private final MyRemoteServiceBinder binder = new MyRemoteServiceBinder(); public class MyRemoteServiceBinder extends IMyService.Stub { public UserInfo getUserInfo(String userURI) throws RemoteException { return restClient.getUserInfo(userURI); } }

Listing 6: ContentProvider-Aufruf innerhalb der Activity


public class UserInfoActivity extends Activity { public void onCreate(Bundle savedInstanceState) { // AsyncQueryHandler handler = new AsyncQueryHandler(getContentResolver()) { protected void onQueryComplete(int token, Object cookie, Cursor cursor) { if (cursor.moveToFirst()) { TextView nameView = (TextView) findViewById(R.id.name); nameView.setText(cursor.getString(cursor.getColumnIndex("name"))); // } } }; handler.startQuery(1, null, Uri.parse("content://org.example.myprovider/UserInfo/ http%3A%2F%2Fmyserrver%2Fsrv%2FUserInfo%2F34"), null, null, null, null); } }

Listing 7: Content-Provider-Implementierung
public class MyContentProvider extends ContentProvider { // public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { switch (uriMatcher.match(uri)) { case USER_INFO: String userURI = uri.getLastPathSegment(); UserInfo userInfo = restClient.getUserInfo(userURI); MatrixCursor cursor = new MatrixCursor(new String[] { "uri", "name", "city", "zip" }); cursor.addRow(new Object[] { userInfo.getUri(), userInfo.getName(), userInfo.getCity(), userInfo.getZip() } ); return cursor; // } // }

Der IPC-relevante Code wird im Beispiel von der generierten Klasse IMyService.Stub geerbt.Typischerweise wird der IPC-relevante Code nicht per Hand implementiert, sondern es wird auf die Generierung der entsprechenden Klassen zurckgegriffen. Die AndroidPlattform stellt hierfr eine eigene Schnittstellenbeschreibungssprache aidl zur Verfgung. Mithilfe der aidl kann die Remote-Schnittstelle definiert werden. Die Syntax ist stark an Java angelehnt, jedoch existieren einige Einschrnkungen. Werden applikationsspezifische Datenstrukturen bertragen, so sind diese ebenfalls in einer aidl-Datei zu deklarieren. Des Weiteren muss die Implementierung der Datenstruktur wie die UserInfo-Bean-Hilfskonstrukte fr die Serialisierung und Deserialisierung bereitstellen. Die IPC von Android verwendet aus Effizienzgrnden nicht den Java-Serializable-Ansatz, sondern definiert mit dem Parcelable einen eigenen Serialisierungsmechanismus. Das ADT-EclipsePlug-in generiert automatisch die entsprechenden JavaKlassen und -Interfaces im /gen/-Verzeichnis. Im Fall des Remote-Service kann in der onServiceConnected()-Methode das bergebene Binder-Objekt nicht mehr unmittelbar verwendet werden. Im Gegensatz zum local-Service ist immer die statische Methode asInterface() der generierten Binder-Klasse zu verwenden, um das Proxy-Objekt fr den Serviceaufruf zu erhalten. Diese Methode erkennt, ob der RemoteService im gleichen Prozess wie auch der Aufrufer luft. Falls dies nicht zutrifft, wird ein Remote-Proxy-Objekt zurckgegeben, das die Serialisierung der Aufrufdaten vornimmt, den remote-Service aufruft und die Deserialisierung der Antwort durchfhrt. Ansonsten wird, wie auch beim local-Service, das konkrete Binder-Objekt zurckgegeben.

Android Content Provider


Neben dem serviceorientierten Ansatz knnen RemoteAbfragen des Servers auch mithilfe eines ContentProvi-

ANDROID360

www.android360.de

REST

der implementiert werden. Ein Content-Provider wird typischerweise verwendet, um auf Daten der lokalen Datenbank zuzugreifen. Die Android-Umgebung beinhaltet beispielweise eine SQLite-Datenbank. Obwohl die Schnittstelle des Content-Providers datenbankorientiert ist, werden Content-Provider nicht nur fr Datenbankzugriffe verwendet. Ein Content-Provider kann auch genutzt werden, um Remote-Aufrufe zu einem nachgelagerten Server zu kapseln. Wie auch die REST-Architektur ist der Content-Provider ressourcenorientiert. Die Entitten werden wie beim RESTful-HTTP-Ansatz ber URIs referenziert. Eine solche Content-URI besteht aus drei Teilen: dem Standardprefix content://, der Authority wie org.example.mycontentprovider, die den Content-Provider identifiziert, sowie dem verbleibenden Teil, der die Entitt identifiziert. Analog zur AsncTask-Klasse stellt Android eine ConvenienceKlasse AsyncQueryHandler bereit, die das Threading bzw. die Thread-Synchronisierung bernimmt. In der startQuery()-Methode des AsyncQueryHandler knnen dann neben dem Content-URI auch die Parameter projection, selection und orderBy bergeben werden. Das Ergebnis der Abfrage wird mithilfe eines Cursors bereitgestellt. Die applikationsspezifische Implementierung eines Content-Providers muss von der Klasse ContentProvider ableiten. Im Wesentlichen sind hierbei die Methoden query(), insert(), delete(), update() und getType() zu implementieren. Zur Auflsung des bergebenen URIs auf die betroffene Entitt wird in der Regel auf die UriMatcher-Klasse zurckgegriffen. Das Ergebnis der Remote-Abfrage des nachgelagerten Servers muss ber einen Cursor bereitgestellt werden. Im Codebeispiel wird hierfr die Android-Klasse MatrixCursor verwendet. Gibt der Server komplexe, verschachtelte Datenstrukturen zurck, so sind diese in der Regel nur schwer in einer effizienten Art und Weise auf den Cursor-Ansatz abbildbar. Hufig sind weitere Subabfragen am Content-Provider notwendig, um das komplette Ergebnis des Serveraufrufs zu erhalten.

eingebunden, geht die Tendenz dazu, die Aufrufe ber einen Android-Service oder einen Content-Provider zu kapseln. Dies gilt insbesondere, wenn ein applikationsspezifisches Caching der Serveraufrufe implementiert werden soll oder Server-Push-Architekturen umgesetzt werden mssen. Obwohl der Content-Provider ressourcenorientiert ist, darf er nicht mit der RESTful-HTTPArchitektur gleichgesetzt werden. Bei der Abbildung einer RESTful-HTTP-Schnittstelle auf die ContentProvider-Schnittstelle knnen insbesondere komplexe Datenstrukturen Probleme bereiten. Des Weiteren ist die Abbildung eines HTTP-Service-URIs auf einen Content-Provider-URI nicht trivial. Beim RESTful-HTTP-Ansatz ist es ein schlechter Stil, auf der Clientseite HTTP-URI-Strukturen zu interpretieren. Der zunchst naheliegende Ansatz, die Struktur eines URIs des HTTP-Service wie http://myserrver/srv/UserInfo/455 auf eine Content URI wie content://org.example.myprovider/UserInfo/4554 abzubilden, verstt gegen das Hypermedia-Prinzip von REST [7], da Teilstrukturen des HTTP URIs clientseitig interpretiert werden. Die Kapselung von HTTP-Services ber einen Android-Service lsst gegenber dem Content-Provider mehr Spielraum, stellt dafr aber keine (teil-)standardisierte Schnittstelle bereit. Wird ein Service nur innerhalb einer Applikation verwendet, so ist es in der Regel einfacher, einen localService zu nutzen bzw. zu implementieren.

Gregor Roth arbeitet als Softwarearchitekt bei der 1&1 Internet AG. Er verfgt ber langjhrige Erfahrungen im Bau verteilter, hochskalierbarer Anwendungssysteme und betreut architektonisch die Entwicklung der webbasierten Applikationen der Marken WEB.DE, GMX und 1&1. Zuvor war er knapp zehn Jahre als Softwarearchitekt im Finanzdienstleistungsbereich ttig.

Fazit
Die hier vorgestellen Anstze zur Implementierung von Remote-Abfragen nachgelagerter Server stellen nur einen Ausschnitt der mglichen Lsungen dar. Des Weiteren wurde in diesem Artikel das Caching von Applikationsdaten nicht betrachtet. Insbesondere bei hochverteilten Systemarchitekturen entwickelt sich das Caching auf der Applikationsebene schnell zu einem wichtigen Thema. Eine gute Betrachtung der Implementierungsanstze auf Basis von Android Services und Content-Proivder ist auch im Video Podcast [6] zu finden. Fr einfache, vereinzelte Serveraufrufe ist es oft ausreichend, auf ein richtiges Threading bzw. auf die Thread-Synchronisierung zu achten und sich der Prozesspriorisierung und deren Folge bewusst zu sein. Werden jedoch umfangreichere HTTP-Schnittstellen wie ein HTTP-basierter Mail-Service oder ein Contact-Service

Links & Literatur


[1] RESTful HTTP in Practice: http://www.infoq.com/articles/designingrestful-http-apps-roth [2] Using Internet Data in Android Applications: http://www.ibm.com/ developerworks/xml/library/x-dataAndroid/ [3] HTML5 Server-Push Technologies: http://today.java.net/ article/2010/04/26/html5-server-push-technologies-part-2 [4] SOA Patterns Service Callback: http://www.soapatterns.org/ service_callback.php [5] Open Binder: http://en.wikipedia.org/wiki/OpenBinder [6] Developing Android REST Client Applications: http://code.google.com/ intl/de-DE/events/io/2010/sessions/developing-RESTful-android-apps. html [7] REST APIs must be Hypertext-driven: http://roy.gbiv.com/ untangled/2008/rest-apis-must-be-hypertext-driven

www.android360.de

ANDROID360

CaChing

Automatische Aktualisierung der GUI mit einem Content-Provider als Cache

Caching-ContentProvider

Jeder, der mobile Anwendungen entwickelt, wird sich frher oder spter mit Caching beschftigen mssen. Eine Applikation, die Daten von einem Server bezieht, sei es vom Internet oder einem VPN, profitiert in nahezu allen Fllen von einem Cache. Denn anders als bei einer Desktopanwendung sind im Mobile-Bereich Verbindungsabbrche oder gar der Totalausfall des Netzwerks an der Tagesordnung. Das betreten einer U-Bahn-Station mag gengen. Die Daten fr einen gewissen Zeitraum, selbst wenn dieser nur wenige Minuten umfasst, auf dem Gert lokal zu speichern, wird den Workflow fr den Anwender deutlich erleichtern. Das Android-Framework bietet mit Content-Providern eine einfache und leistungsfhige Mglichkeit, das Problem zu lsen.

von Ronan Schwarz


Fr Caching gibt es bei Android drei Hauptkategorien oder Vorgehensweisen: Dateien, Datenbanken oder Content-Provider. Allen ist gemeinsam, das die physikalische Speicherung entweder auf dem Gert oder dem externen Speichermedium (SD-Card oder Festplatte bei einigen MIDs) geschehen kann. Das mag verblffen, da dies im Zusammenhang mit Content-Providern selten geschieht und nicht dokumentiert ist trotzdem ist es ohne Weiteres mglich, wie wir noch sehen werden. Eine IO-Operation auf eine Datei hat von allen Methoden sicherlich den geringsten Programmieraufwand,

die Sicherstellung einer sinnvollen Struktur und der entsprechenden Datenintegritt kann sich jedoch als kompliziert erweisen. Datenbanken haben den Vorteil der klaren Struktur und sind lange erprobte Tools, mit denen sich wohl jeder Programmierer auskennt. Allerdings muss die Frontseite entweder einiges an Wissen ber die Struktur und den Zugriff auf die Daten mitbringen (und man hat SQL-Statements im GUI-Code), oder man muss zustzlich das Datenmodell in Klassen abbilden (was fr eine mobile Plattform ein nicht unbedingt wnschenswerter Overhead ist). ContentProvider bieten ein klares, URL-basiertes Interface, die Struktur und Mchtigkeit einer Datenbank und

10

ANDROID360

www.android360.de

CaChing

schirmen das GUI von der physikalischen Speicherung ab. Content-Provider bieten von vornherein auch die Mglichkeit, Daten mit anderen Applikationen zu teilen. Mchte man das nicht oder nur in Teilen, so kann man im Manifest File die entsprechenden Permissions setzen. Einen berblick ber Content-Provider anderer Apps, die zur Verfgung stehen, bietet die OpenIntents Registry [1].

Caching-Content-Provider
Das sich ein Content Provider als Cache verwenden lsst, liegt auf der Hand. Der groe Vorteil von Content-Providern ist jedoch nicht so offensichtlich: die Content Observer. Ein Content Observer ist eine Android-spezifische Implementierung des Observer-Patterns fr Datenbanken. Wann auch immer sich ein bestimmtes Datum in der Datenbank ndert, werden alle vorher registrierten Observer darber informiert. Dies gilt gleichermaen fr Content-Provider. Mit dieser im Grunde sehr einfachen Idee lassen sich GUI, Content-Provider und Backend so verbinden, dass sich die angezeigten Daten automatisch aktualisieren. Als GUI-Komponente bietet sich eine ListActivity an. Eine ListActivity kann bereits mit wenigen Zeilen Code die in einem Cursor enthaltenen Daten anzeigen. Sie bietet zudem eine einfache Mglichkeit, einen Content Observer zu implementieren (zu lassen). Der sorgt dann dafr, dass die in der Liste angezeigten Eintrge jeder-

Fr einen Caching-ContentProvider werden drei Komponenten bentigt.

zeit synchron zu den Daten des Content-Providers sind. Die Klasse CursorAdapter und ihre abgeleiteten Klassen stellen diese Funktionalitt bereits zur Verfgung. Es reicht also aus, in der ListActivity einen passenden Adapter zu bergeben. Nun muss noch der Cache mit den Daten aus dem Backend befllt werden. Dafr benutzen wir einen Service. Ein Service in Android ist ein Hintergrundprozess, hnlich einem Daemon in Unix. Der entscheidende Unterschied ist, dass ein Service nicht konstant luft, sondern hchstens in Intervallen vom System gestartet werden kann. Wie alle Prozesse kann auch ein Service vom Garbage Collector gestoppt werden, falls das System mehr Leistung fr den momentanen Vordergrundprozess braucht. Das ermglicht es Daten, im Hintergrund zu synchronisieren und schont gleichzeitig die Batterie. Fr mehr Informationen zu Android-Prozessen und dem Garbage Collector empfiehlt sich das exzellente Posting Multitasking the Android Way von Diane Hackborn im Android-Developer-Blog [2].

Implementierung
Fr einen Caching-Content-Provider werden also drei Komponenten bentigt: eine ListActivity zur Anzeige, ein Service, um die Daten vom Web zu lesen, und ein Content-Provider fr die Speicherung. Diese werden nun anhand eines Beispielprojekts exemplarisch implementiert und besprochen. Wir versuchen, eine Reihe

Listing 1: Die Klasse CCPListActivity


public class CCPListActivity extends ListActivity { Cursor mCursor; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mCursor = this.getContentResolver().query(Customers.CONTENT_URI, null, null, null, null); startManagingCursor(mCursor); ListAdapter adapter = new SimpleCursorAdapter( this, // Context. R.layout.three_row_layout, mCursor, new String[] {Customers.NAME,Customers.EMAIL,Customers._ID}, new int[] {R.id.text1, R.id.text2,R.id.text3}); setListAdapter(adapter); } }

Listing 2: Die Klasse Customers


public class Customers implements BaseColumns { // This class cannot be instantiated private Customers() {} public static final Uri CONTENT_URI = Uri.parse("content://org.openintents.ccp/ customers"); public static final String CONTENT_TYPE = "vnd.android.cursor.dir/ vnd.openintents.examples.ccp.customers"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/ vnd.openintents.examples.ccp.customer"; public static final String NAME = "name"; public static final String EMAIL = "note"; public static final String[] DEFAULT_PROJECTION_MAP={_ID,NAME,EMAIL}; public static final String DEFAULT_SORT_ORDER=_ID+" asc"; }

www.android360.de

ANDROID360

11

CaChing

von Kunden mit Name und E-Mail aus dem Backend zu laden und anzuzeigen. Da es in Wirklichkeit keinen Server gibt, von dem die Daten kommen knnten (und die Anbindung an einen realen den Umfang dieses Artikels sprengen wrde), wird der Service dies simulieren. Aber der Reihe nach.

Das Frontend: ListActivity


Die ListActivity kommt hier tatschlich mit einer einzigen Methode aus, nmlich onCreate(..). Das XMLLayout ist ein ListActivity-Standard und enthlt eine ListView mit der ID android:list und eine TextView mit der ID android:empty. Sind in der Liste keine Daten ent-

halten, zeigt die ListActivity die TextView an; sobald sich die Liste fllt, verschwindet diese wieder. All das wird bereits fr uns erledigt und bedarf keines weiteren Codes. Wir erzeugen einen Cursor mittels eines Querys auf den Default-URL des Content-Providers, der alle Customer-Eintrge liefert. Die Funktion startMana gingCursor(mCursor) sorgt dafr, dass automatisch ein Requery ausgefhrt wird, wenn sich am Zustand des Cursors etwas ndert z. B. auch dann, wenn die Activity in den Hintergrund geht. Der SimpleCursor Adapter verwendet das angegebene Layout fr die Anzeige und ordnet den Spalten die jeweiligen TextViews

Listing 3: Die Klasse CCProvider


public class CCProvider extends ContentProvider { @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { //we start the service that retrieves data from the backend Intent intent=new Intent(); intent.setClass(getContext(), DataService.class); getContext().startService(intent); SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); switch (sUriMatcher.match(uri)) { case CUSTOMERS: qb.setTables(CUSTOMERS_TABLE_NAME); qb.setProjectionMap(sCustomersProjectionMap); break; case CUSTOMER_ID: qb.setTables(CUSTOMERS_TABLE_NAME); qb.setProjectionMap(sCustomersProjectionMap); qb.appendWhere(Customers._ID + "=" + uri.getPathSegments().get(1)); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } // If no sort order is specified use the default String orderBy; if (TextUtils.isEmpty(sortOrder)) { orderBy = Customers.DEFAULT_SORT_ORDER; } else { orderBy = sortOrder; } SQLiteDatabase db = mOpenHelper.getReadableDatabase(); Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy); c.setNotificationUri(getContext().getContentResolver(), uri); return c; }

@Override public Uri insert(Uri uri, ContentValues initialValues) { if (sUriMatcher.match(uri) != CUSTOMERS) { throw new IllegalArgumentException("Unknown URI " + uri); } ContentValues values; if (initialValues != null) { values = new ContentValues(initialValues); } else { values = new ContentValues(); } // Make sure that the fields are all set if (values.containsKey(Customers.NAME) == false) { Resources r = Resources.getSystem(); values.put(Customers.NAME, r.getString(android.R.string.untitled)); } if (values.containsKey(Customers.EMAIL) == false) { values.put(Customers.EMAIL, ""); } SQLiteDatabase db = mOpenHelper.getWritableDatabase(); //the name field will be assigned a null value if the row is empty. long rowId = db.insert(CUSTOMERS_TABLE_NAME, Customers.NAME, values); if (rowId > 0) { Uri rowUri = ContentUris.withAppendedId(Customers.CONTENT_URI, rowId); getContext().getContentResolver().notifyChange(rowUri, null); //return the uri of the newly created dataset return rowUri; } throw new SQLException("Failed to insert row into " + uri); } }

12

ANDROID360

www.android360.de

CaChing

zu. Fr komplexere Layouts, z. B. mit Bildern, sollte eine eigene Adapterklasse implementiert werden. Wir weisen der ListActivity den Adapter zu und erzeugen somit die Liste und ihre enthaltenen Elemente.

Der Content-Provider
Der Content-Provider enthlt eine Menge an zustzlichem Code, der sich mit Aufbau und Versionierung der darunter liegenden SQLite-Datenbank sowie dem berprfen der aufgerufenen URLs beschftigt. Diese sind immer hnlich, weshalb wir uns auf die wichtigen Teile konzentrieren. Es sei allerdings noch darauf hingewiesen, dass sich die Datenbank keineswegs auf dem internen Speicher befinden muss; der Pfad kann durchaus auch auf der SD-Karte liegen. Man kann auch ganz auf eine Datenbank verzichten solange ein valider Cursor zurckgegeben wird, sind der Phantasie fast keine Grenzen gesetzt. Die entscheidenden Methoden des Content-Providers sind query(), insert() und update(). In query starten wir den Service, nachdem wir einen Cursor zurckgeben, der entweder leer ist oder veraltete Daten enthlt. Mit c.setNotificationUri(getContext(). getContentResolver(), uri); sorgen wir dafr, dass der Cursor aktualisiert wird, wenn sich die Daten im Content-Provider ndern. Das erreichen wir, wenn wir in in sert und update die Content Observer benachrichtigen.
int r3=(int)Math.floor((Math.random() *4)); String name=FIRST_NAMES[r1]+" "+LAST_NAMES[2]; String email=name+DOMAINS[r3]; cv.put(Customers.NAME,name); cv.put(Customers.EMAIL,email); return cv; } // Set an alarm that will restart out service private void scheduleNextRun(){ Intent intent=new Intent(); intent.setClass(getApplicationContext(), DataService.class); PendingIntent pendingIntent= PendingIntent.getService(getApplicationContext(), 1, intent, PendingIntent.FLAG_ONE_SHOT); AlarmManager alarmManager= (AlarmManager)getSystemService(ALARM_SERVICE); long now= System.currentTimeMillis(); //start after 1.5 minutes/ 90 seconds alarmManager.set(AlarmManager.RTC_WAKEUP, (now+ 90000), pendingIntent); } }

Die Datenstruktur anlegen mit BaseColumns


Die Datenstruktur des Content-Providers wird mit Klassen definiert, die das Interface BaseColumns implementieren. Wir halten es simpel und definieren eine Klasse Customers (Listing 2), die auer den in BaseColumns gegebenen Feldern nur noch Name und E-Mail enthlt. Content-Provider verwenden URLs, um die gewnschten Daten zu adressieren. Diese Content Uris folgen dem simplen Schema content:com.mypackage/dataType/ fr alle Daten eines Typs bzw. content:com.mypackage/dataType/ID fr ein spezielles Datum. Wir setzen die passende Con tentUrl als statisches Feld in die Klasse Customer. Ein weiteres statisches Feld ist die Default Projection Map, um die Daten im Cursor zu selektieren. Wenn wir mehr bentigen, lsst sich das jederzeit nachholen. Hier ist es so einfacher, da es nur drei Felder von Interesse gibt.

Die Datenbank muss sich keineswegs auf dem internen Speicher befinden.

Listing 4: Proof-of-Concept-Code
public class DataService extends Service{ @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); Log.d(TAG,"started"); //normally all of this should be loaded off to another thread //a ~30% chance of deleteing all data in the cursor if((int)Math.floor((Math.random() *100))>66) { getContentResolver().delete(Customers.CONTENT_URI, null,null); } //write some random data for (int i=0;i<10;i++) { ContentValues cv=getImportantDataFromTheInternet(); getContentResolver().insert(Customers.CONTENT_URI, cv); } //make sure we run again. scheduleNextRun(); //done. stopSelf(); } private ContentValues getImportantDataFromTheInternet(){ ContentValues cv= new ContentValues(); //get us three random numbers to create a customer int r1=(int)Math.floor((Math.random() *8)); int r2=(int)Math.floor((Math.random() *4));

www.android360.de

ANDROID360

13

CaChing

Ob man das auch in delete() macht, hngt davon ab, ob dieser Event wirklich bentigt wird. In unserem Fall folgt auf jedes delete ein insert, um neue Daten einzufgen; die Aktualisierung des GUI mit einem leeren Cursor zwischen beiden Operationen wird eher verwirren, als ntzen. Damit der Service auch wei, welche Daten gefragt sind, bergibt man einen passenden Parameter als Extra in den Intent. Der URL bietet sich hier besonders an. Der Content-Provider verwendete bereits eine Hilfsklasse vom Typ UriMatcher, um einem URL eine eindeutige ID zuzuordnen, auf die man entsprechend reagiert. Der Code kann einfach in den Service kopiert werden. Wenn man obendrein noch eine REST-Schnittstelle anspricht, kann man eigentlich auch die REST-URLs als Content Urls verwenden und somit direkt an den Service weitergeben. Die entscheidende Zeile, die unsere Cursor von den nderungen in Kenntnis setzt, ist getContext().getContent Resolver().notifyChange(uri, null);. Die null-Variable ist der Content Observer, der die nderungen ausgelst hat. In diesem Fall nicht existent und damit null. uri ist die ContentUrl des Datensatzes, der sich gendert hat. Bei einem insert muss dieser URL erzeugt werden. Die insert-Funktion der Datenbank liefert uns die ID der genderten Zeile, sie wird einfach mit ContentUris.with AppendedId(Customers.CONTENT_URI, rowId); an die passende ContentUri angehngt. In query oder up date nehmen wir dieselbe URI, die als Parameter beim Aufruf bergeben wurde.

knnten vom Garbage Collector beendet werden, wenn das System mehr Ressourcen braucht. Dessen Instanz holen wir uns vom Context (also der Serviceklasse) mit getSystemService(ALARM_SERVICE), eine direkte Instanzierung ist nicht mglich. Wir erzeugen einen Intent und wandeln diesen in einen PendingIntent. Pending Intent.getService(..) sorgt dafr, dass der Service vom System gestartet wird. Wir setzen den nchsten Start mit AlarmManager.set(..) und benutzen RTC_WAKE UP, um sicherzustellen, dass der Intent ausgelst wird auch wenn das Device im Sleep Mode ist (z. B. bei ausgeschaltetem Bildschirm). Die Funktion Set setzt nur einen einzigen Alarm, stattdessen knnte man auch set Repeating(..) verwenden, um eine Serie zu setzten. Fr dieses Beispiel gengt es, den gesamten Code in onStart auszufhren. In der Realitt ist das keine so gute Idee, es empfiehlt sich stattdessen, z. B. Worker Threads zu verwenden.

Fazit
Damit sind nun alle Komponenten zusammen. Wir haben eine effiziente und einfache Struktur, um Caching in unsere Anwendung zu integrieren, das eine saubere Trennung von Backend, Speicher und Darstellung gewhrleistet. Die Anzeige aktualisiert sich von alleine, wenn neue Daten bereit stehen. Der Cache kann im Hintergrund mit neuen Daten gefllt werden, wann immer die Mglichkeit und/oder Notwendigkeit besteht. Das Beispielprojekt setzt auf Android 1.6 auf, sollte aber problemlos auf ltere Versionen angepasst werden knnen. Die vorgestellte Methodik ist auf nahezu alle Projekte anwendbar. Der gesamte Beispielcode steht unter der Apache License 2 und kann mit folgendem Befehl heruntergeladen werden:
svn checkout http://openintents.googlecode.com/svn/trunk/samples/ CachingContentProvider openintents-read-only

Daten erzeugen mit einem Service


Die letzte Komponente ist der Backgroundservice. Der Proof-of-Concept-Code fllt recht simpel aus (Listing 4). Wir bentigen fr unser Beispiel kein echtes Backend, deshalb implementiert der Service eine Funktion getIm portantDataFromTheInternet(), die ein mit zuflligen Werten geflltes ContentValues-Objekt zurckgibt. Ob man in einer echten Anwendung den Zwischenschritt des Parsings in POJOs1 geht, ist Geschmacksache da sich ContenValues direkt in den Content-Provider schreiben lassen, kann man darauf eigentlich auch verzichten. Der Service startet alle 90 Sekunden, um neue Daten zu erzeugen. Fr den echten Anwendungsfall sollte man besser ein Intervall grer als 10 Minuten whlen, um die Batterie nicht unntig zu belasten. Da der Service in jedem Query auf den Content-Provider gestartet wird, ist die Aktualitt der Daten immer noch garantiert. Mchte man nicht alle Daten neu Laden, kann man z. B. im Content-Provider eine zustzliche Tabelle mit Timestamps anlegen und nur die Ressourcen aktualisieren, deren Mindesthaltbarkeitsdatum berschritten ist. Die Funktion scheduleNextRun() verwendet den AlarmManager, um den nchsten Start vorzubereiten. Der AlarmManager ist die sicherste Methode, in Intervallen einen Service ausfhren zu lassen. Threads

Ronan Schwarz wuchs mit der Digitalisierung auf. heute ist er ein Teil davon und arbeitet als mobile-Softwareentwickler bei TiC-mobile. Ronan ist ein Experte fr android und einer der ersten Entwickler fr das neue Samsung-OS Bada.

Links & Literatur


[1] Openintents: http://www.openintents.org [2] Multitasking the android Way: http://bit.ly/a360_CC_2 [3] Caching Content Provider (Beispiel): http://bit.ly/a360_CC_3

14

ANDROID360

www.android360.de

NEU
Das neue Magazin fr
Jetzt abonnieren

Mobile Development, Marketing, Business


sichern! (Limitiertes Angebot) und Sonderpreis

iPhone, iPad Android Mobile Web Windows Phone 7 Tools & Best Practices App Stores Mobile Commerce & Marketing Business & Strategien

go: mobiletechnologymag.de

Anda mungkin juga menyukai