-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpersistenz_hintergrunddienste.tex
299 lines (295 loc) · 19.5 KB
/
persistenz_hintergrunddienste.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
\section{Persistenz}
Die Apps werden vom System beendet, ohne dass dies dem Benutzer bewusst ist. Das Sichern der Daten ist also Aufgabe der App. Es gibt zwei unterschiedliche Arten von Daten: Zustandsdaten der Views und Anwendungsdaten der Domain-Klassen.
\paragraph{View-Daten Persistieren} \code{onCreate} und \code{onSaveInstanceState} erhalten je ein Bundle Objekt, dies ist wie eine Map/Property List mit assoziierten String-Key Values.
\begin{lstlisting}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
}
\end{lstlisting}
\textbf{Wichtig:} Der \code{super} Aufruf speichert alle Views die eine ID haben.\\
Die AppDaten sollten in \code{onPause} gesichert werden, da \code{onSaveInstanceState} nicht immer ausgeführt wird. Es gibt verschiedene Möglichkeiten um Daten zu sichern:
\begin{itemize}
\item \textbf{Shared Preferences:} Key-Value Paare für wenige Daten
\item \textbf{Files:} Für private Daten die mit anderen Programmen geteilt werden
\item \textbf{SQLite:} Für strukturierte Daten, die in einer relationalen Datenbank abgelegt werden können
\item \textbf{Cloud:} Nicht immer verfügbar, lokaler Zwischenspeicher nötig
\end{itemize}
\paragraph{Shared Preferences} Erlaubt sind Key-Value Paare mit Value vom Typ: \code{boolean | float | int | long| String | Set<String>}.
\begin{lstlisting}
SharedPreferences settings = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("disabled", false);
boolean isDisabled = settings.getBoolean("disabled", false);
editor.commit();
\end{lstlisting}
Eine Art Observer für diese Settings ist der \code{settings. registerOnSharedPreferenceChangeListener}.
\paragraph{File Storage} Es gibt zwei Speicher-Typen in Android. Den internen (privaten) und den externen Speicher (extern muss nicht zwingend auf einem externen Medium liegen). In den internen Speicher schreibt man fast ganz normal, wie das in Java üblich ist.
\begin{lstlisting}
FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write("File content".getBytes());
fos.close();
\end{lstlisting}
Möchte man in den externen Speicher schreiben, um beispielsweise Daten anderen Apps zur verfügung zu stellen, braucht man zuerst die Permissions im Manifest zu setzten.
\begin{lstlisting}
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File file = new File(path, "HSR_Cat.png");
\end{lstlisting}
Es gibt Konstanten für verschiedene vordefinierte Verzeichnisse.
\paragraph{SQLite Storage} Der SQLite Storage eignet sich für relationale Daten wie Domain Objekte.
\begin{lstlisting}
public class DBHelpter extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 2;
DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE ....;");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion {}
}
// Irgendwo anders
DBHelper helper = new DBHelpber(this);
SQLiteDatabase db = helper.getReadableDatabase();
db.execSQL("SELECT * FROM ...";);
\end{lstlisting}
\section{Hintergrunddienste}
%TODO: Fix references
Da der Main-Thread für das GUI zuständig ist, und die Ereignisse in einer Queue abgearbeitet werden, sollte man keine lange andauernden Aufträge in diesem laufen lassen (Siehe \autoref{grundlagen:eventhandling} EventHandling).\\
Eine simple und schnelle Lösung ist die Verwendung von Java Threads. Dies ist in Ordnung für kleinere Aufgaben, welche das GUI nicht manipulieren. Möchte man das UI manipulieren muss man mit Hilfsmethoden wie \code{post()} arbeiten.
\begin{lstlisting}
public void onClick(View v) {
Runnable runnable = new Runnable() {
@Override
public void run() {
final Bitmap bitmap = download("something");
Runnable command = new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
};
imageView.post(command);
}
};
Thread thread = new Thread(runnable);
thread.start();
}
\end{lstlisting}
Mit \code{post()} übergibt man die \code{Runnable} Instanz dem UI-Thread und führt diesen aus. Das \code{command} wird also im UI-Thread ausgeführt.
\paragraph{AsyncTask} Typischerweise startet man eine Aufgabe die in einem eigenen Thread ablaufen soll, um am Ende wieder etwas auf den Main-Thread zu tun. Der AsyncTask nimmt Parameter als generische Typen entgegen, für die Aufgabe die man Durchführen will. Der \textbf{erste Parameter} ist der Typ des Inputs, der \textbf{zweite} der Typ des Feedbacks über den Fortschritt und der \textbf{dritte} der Typ des Outputs. Pre = Vor, Post = Nach
\begin{lstlisting}
public class DownloadBitmapTask extends AsyncTask<String, Integer, Bitmap> {
@Override
protected void onPreExecute() {
// UI-Thread
super.onPreExecute();
}
@Override
protected Bitmap doInBackground(String ... params) {
// Eigener Thread
publishProgress(10);
return download(params[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap){
//UI-Thread
imageView.setImageBitmap(bitmap);
}
@Override
protected void onProgressUpdate(Integer... values){
//UI-Thread
progressBar.setProgress(values[0]);
}
}
\end{lstlisting}
Möchte man keine Information über den Fortschritt, setzt man den zweiten generischen Parameter auf \code{Void}.
\subsection{Komponenten einer Android App}
\includegraphics[scale=0.25]{komponentenEinerApp.png}
\paragraph{Context Klasse} Mit einer Context-Instanz kann man:
\begin{itemize}
\item Neue View erstellen (Kontext muss als Parameter mitgegeben werden)
\item Auf System-Service zugreifen (\code{context. getSystemService( LAYOUT\_INFLATER\_SERVICE)})
\item Die Applikationsinstanz erhalten
\item Neue Acativities starten (mittels Intent)
\item Preferences lesen und schreiben
\item Services starten
\end{itemize}
\subsection{Services}
Für Aufgaben die im Hintergrund ablaufen sollen oder deren Abarbeitung das UI zu lange blockieren würde, verwendet man Services. Diese haben folgende Eigenschaften:
\begin{itemize}
\item Kein UI
\item Können gestarted oder gebunden werden
\item Arbeiten im UI-Thread der Applikation, sind keine eigenen Threads
\item Müssen in der Manifest Datei deklariert werden
\end{itemize}
\paragraph{Deklaration} Wie Activities müssen auch Service-Komponenten im Manifest deklariert werden
\begin{lstlisting}
<application>
<service
android:name=".ExampleService"
android:exported="false" />
</application>
\end{lstlisting}
Es gibt zwei verschiedene Arten von Services. Den \textbf{gebundenen} Service (Client-Server ähnliche Kommunikation über eine längere Zeitdauer) und den \textbf{gestarteten} Service (erledigen einmaliger Aufgaben).
\paragraph{Started Services} Ein Service wird mit \code{context.startService(intent)} gestartet. Der Service läuft im Hintergrund und wird nicht gestoppt, auch wenn der Anwender die App wechselt oder der startende Context zerstört wird (im Hintergrund meint nicht in einem eigenen Thread). Der Service soll sich selbst beenden, wenn der User die App wechselt. Grundsätzlich kann man sagen der Service entkoppelt Aufgaben vom Context, und der AsyncTask entkoppelt Aufgaben vom Main-Thread.
\paragraph{Bound Services} Ein Service wird mit \code{context.bindService(intent, ...)} gebunden. Der Service liefert ein Interface, das dem Client erlaubt, Methoden des Services aufzurufen. Es ist möglich, dass mehrere Clients gebunden sind. Wenn alle Clients \code{context.unbindService(...)} aufgerufen haben, wird der Service zerstört.
\paragraph{Lifecycle} Die Callbacks eines Services sind ähnlich wie die der Activitiy
\includegraphics[scale=0.4]{service_lifecycle.png}
\paragraph{Started Service Starten} Der Service wird aus einer Activity gestartet.
\begin{lstlisting}
Intent intent = new Intent(this, SimpleService.class);
this.startService(intent);
\end{lstlisting}
Dies ruft ggf \code{onCreate()} und anschliessen \code{onStartCommand()} im Service auf. Ohne Einstellung, findet keine Kommunikation zwischen Service und Acitivity statt.\\
Der Service kann mit \code{stopSelf()} gestoppt werden und sollte auch getan werden. Man kann den Service auch in der Acitivity stoppen, mittels \code{stopService()}.
\paragraph{Started Service IntentService} Der IntentService stellt einen Worker-Thread zur Verfügung. Er verwendet eine Queue, um Intents zwischenzuspeichern, welche sequentiell in \code{onHandleIntent()} abgearbeitet werden. Der Service wird gestoppt, nachdem alle Intents abgearbeitet wurden.
\begin{lstlisting}
public class HelloIntentService extends IntentService {
public HelloIntentService() {
super("HelloIntentService");
}
@Override
protected void onHandleIntent(Intent intent) { }
}
\end{lstlisting}
Der Service enthält keine Referenz an die aufrufende Activity. Um nun ein Resultat vom Service zu bekommen, kann man entweder einen Intent mit Resultat als Broadcast versenden, wo dann wiederum die Activity einen Broadcast-Receiver zur Verfügung stellen muss um den Intent zu empfangen. Oder man verwendet einen. \code{PendingIntent}.
\paragraph{Pending Intent} ist ein Objekt das einen Intent umhüllt (wrapped). Genauer ist es ein Token das man einer "{}Fremden App"{} übergiebt (e.g. \code{NotificationManager, AlarmManager} Home Screen \code{AppWidgetManager} oder 3rd Party Apps), welch die "Fremde App" berechtigt bestimmten Code auszuführen. \\
Wenn man der "{}fremden App"{} einen Intent übergibt, und diese App diesen Intent sendet/broadcastet, dann wird dieser Intent mit seinen eigenen Berechtigungen ausgeführt. Wenn man aber der "fremden App" einen PendingIntent übergibt werden die Berechtigungen der übergebenden App verwendet. Wird die Klasse, welche den PendingIntent erzeugt terminiert, bleibt der PendingIntent bestehen und kann von anderen Apps weiterverwendet werden. Intents behandelen spezifische Komponenten der App, genauso PendingIntents und nutzen dafür z.B. (\code{PendingIntent.getActivity() PendingIntent.getBroadcast() PendingIntent.getService()} \\
\textbf{Bsp. 1}: Intent erzeugen und wrappen, ausführen der Operation zugehörend zum PendingIntent (\code{send()})
\begin{lstlisting}
// Explicit intent to wrap
Intent intent = new Intent(this, LoginActivity.class);
// Create pending intent and wrap our intent
PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, intent, PendingIntent.FLAG_CANCEL_CURRENT);
try {
// Perform the operation associated with our pendingIntent
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
\end{lstlisting}
\textbf{Besseres Bsp. 2}: Erzeugen, wrappen, starten mit AlarmManager nach 3 sec.
\begin{lstlisting}
int seconds = 2;
// Create an intent that will be wrapped in PendingIntent
Intent intent = new Intent(this, MyReceiver.class);
// Create the pending intent and wrap our intent
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0);
// Get the alarm manager service and schedule it to go off after 3s
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (seconds * 1000), pendingIntent);
Toast.makeText(this, "Alarm set in " + seconds + " seconds", Toast.LENGTH_LONG).show();
\end{lstlisting}
Wenn man noch eine BroadcastReceiver onReceive() Methode hinzufügt, vibriert das Gerät für 2 sec. sobald ein Broadcast Event gesendet wurde.
\begin{lstlisting}
@Override
public void onReceive(Context context, Intent intent) {
// Vibrate for 2 seconds
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(2000);
}
\end{lstlisting}
\paragraph{Bound Service} Der Bound Service funktioniert nach einem Client-Server Modell. Clients können Methoden eines Service aufrufen und die App kann die Funktionalität des Services andern Apps zur verfügugn stellen. Beim Aufruf von \code{BindService()} erhält der Client ein Interface um mit dem Service zu kommunizieren. Es ist möglich, dass mehrere Clients gleichzeitig gebunden sind. Nachdem alle Clients \code{unbindService()} aufgerufen haben, wird der Service beendet.
\paragraph{Bound Service: Server} Der Server muss von der Service Klasse und ein Binder implementieren.
\begin{lstlisting}
public class LocalService extends Service {
private final IBinder binder = new LocalBinder();
public class LocalBinder extends Binder {
LocalService getService() {
return LoaclService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public int getRandomNumber() {
return new Random.nextInt(100);
}
}
\end{lstlisting}
\paragraph{Bound Service: Client} Ein Bound-Service liefert bei \code{onBind()} ein Interface vom Typ \code{IBinder}. Wenn der Service nur innerhalb der App und im gleichen Prozess arbeitet, sollte ein Binder als Interface geliefert werden. Clients rufen so die Methoden des Binders oder des Services auf.
\begin{lstlisting}
Intent intent = new Intent(this, LocalService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
\end{lstlisting}
Der Aufruf von \code{bindService()} liefert nicht den \code{onBind()} Binder, sondern der Callback \code{onServiceConnected()} wird aufgerufen.
\begin{lstlisting}
private ServiceConnection c = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder binder) {
LocalService.LocalBinder myBinder = (...)binder;
LocalService myService = myBinder.getService();
int random = myService.getRandomNumer();
}
@Override
public void onServiceDisconeccted(ComponentName name) {}
};
\end{lstlisting}
\paragraph{Bound Service: Messenger} Ein Messenger kann eingesetzt werden um prozessübergreifend Informationen auszutauschen. Der Service stellt dabei einen Handler zur Verfügung um Meldungen zu erhalten, welche er sequentiell abarbeitet. Für Rückgabewerte des Servers muss der Client seinerseits einen Messenger zur Verfügung stellen.
\subsection{Broadcast Receiver} Broadcasts sind Meldungen die als Intent verschickt werden. Die Action bestimmt den Ereignistyp. Eine Meldung enthält Informationen über ein bestimmtes Ereignis (SMS empfangen, System booted, Akku schwach etc.). Ein Broadcast Receiver kann registriert werden, um bestimmte Meldungen zu erhalten. Standartmeldungen des Systems umfassen unter anderem: \code{ACTION\_BOOT\_COMPLETED | ACTION\_POWER\_CONNECTED | ACTION\_POWER\_DISCONNECTED | ACTION\_BATTERY\_LOW | ACTION\_BATTERY\_OKAY}.
\paragraph{Broadcast Receiver: Registrieren} Man kann den Receiver entweder statisch in der Manifest Datei, dann ist der Receiver global in der App verfügbar (an keine Activity gebunden) und wird bei jeder Meldung neu instanziert. Dynamisch macht man das mit \code{context.registerReceiver()}. Dann wird der Receiver an die Activity gebunden und muss abgemeldet werden, wenn er nicht mehr benötigt wird.
\begin{lstlisting}[language=xml, caption="{}Statische Registration"]
<receiver android:name=".TimeChangedReceiver" >
<intent-filter>
<action android:name="android.intent.action.TIME_SET" />
</intent-filter>
</receiver>
\end{lstlisting}
\begin{lstlisting}[caption="Dynamische Registration"]
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getApplicationContext());
IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
MyBroadcastReceiver receiver = new MyBroadcastReceiver(this);
lbm.registerReceiver(receiver, filter);
\end{lstlisting}
\begin{lstlisting}[caption="Receiver abmelden"]
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getApplicationContext());
lbm.unregisterReceiver(receiver);
\end{lstlisting}
\paragraph{Broadcast Receiver: Implementation} Die Implementation eines Receivers muss von der Klasse \code{BroadcastReceiver} ableiten. Die Methode \code{onReceive()} muss überschrieben werden um Meldungen zu erhalten und verarbeiten.
\begin{lstlisting}
private class MyBroadcastReceiver extends BroadcastReceiver {
public MyBroadcastReceiver(MainActivity activity) { }
@Override
public void onReceive(Context context, Intent intent){ }
}
\end{lstlisting}
\paragraph{Broadcast Receiver: Versenden} Der Broadcast Receiver kann auch Meldungen versenden (semantik?). Dies kann man mit der Methode \code{context.sendBroadcast(intent)}. Die Action im angegeben Intent definiert den Ereignistyp (Namensgebend und in der Action ist global, bei eigenen Actions, Name mit eigenem Namespace verwenden). Beim Intent können unter Extras zusätzliche Daten hinterlegt werden. Wenn man Meldungen nur im eigenen selben Prozess versenden möchte, sollte man den \code{LocalBroadcastManger verwenden}. So verlassen die Meldungen den Prozess nicht und andere Prozesse können unserer App keine Meldungen senden.
\subsection{Content Provider}
Der Content Provider stellt Daten prozessübergreifend zur Verfügung. Es ist ein Client-Server Modell. Der Provider Client und Provider kommunizieren über eine standartisierte Schnittstelle um Daten auszutauschen. Android stellt beispielsweise Kalender oder Kontakte über einen Content-Provider zur Verfügung.
\paragraph{Schnittstelle} eines Content Provider stellt in der Regel Daten zur Verfügung die in einer Tabelle in einer Datenbank abgelegt sind. Deshalb ähnelt die Schnittstelle stark der SQL Syntax.\\
\paragraph{Security} Der Content Provider kann Permissions setzte um den Zugriff auf die Daten einzuschränken. Die Clients müssen die erforderlichen Permissions im Manifest angeben.
\begin{lstlisting}[language=xml]
<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
\end{lstlisting}
\paragraph{Content Provider: Client} Der ContentResolver stellt die Schnittstelle zur Verfügung. Die Tabelle des Content-Provider wird als URI angegeben: \code{content://user\_dictionary/words}.
\begin{lstlisting}
Cursor cursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI,
mProjection,
mSelectionClause,
mSelectionArgs,
mSortOrder);
int index = cursor.getColumnIndex(UserDictionary.Words.WORD);
while(cursor.moveToNext()){
String word = cursor.getString(index);
}
\end{lstlisting}
\paragraph{Content Provider: Server} Der Einsatz eines eigenen Servers ist erforderlich wenn andere Apps strukturierte Daten oder Dateien zur Verfügung stehen sollen, oder wenn andere Apps die Möglichkeit haben sollen, Daten zu durchsuchen. Ein Provider kann Daten als Dateien oder strukturiert anbieten. Dateien werden im Internal Storage abgelegt, der Client erhält einen Handle um auf eine Datei zuzugreifen. Strukturierte Daten werden in der Regel in einer Datenbank abgelegt. Der ContentResolver hat gezeigt, dass das Prinzip von Tabellen mit Spalten und Reihen verwendet werden sollte. \textbf{Ein Content-Provider muss Thread-Save programmiert werden}.\\
Bei der Implementation muss man sich folgendes überlegen:
\begin{itemize}
\item Wie sollen Daten persistent gespeichert werden
\item Die Content-URI muss festgelegt werden
\item Die Klasse \code{ContentProvider} muss implementiert werden.
\item Eine Contract Klasse muss implementiert werden. Die Klasse enthält alle Bezeichner (Konstanten) die ein Client benötigt. Die Klasse muss zudem in den Applikationscode des Clients kopiert werden.
\item Permissions in der Manifest Datei müssen festgelegt werden
\end{itemize}