Kommunikation zwischen Background Service und Activities

  • Antworten:5
Benjamin Stelter
  • Forum-Beiträge: 16

16.07.2010, 00:24:28 via Website

Hallo Ihr lieben,

ich bin zur Zeit dabei, meine erste App zu schreiben. Sie ist zwar nur für private Zwecke, soll aber dennoch mit Funktionen nicht geizen (man will ja dabei auch etwas lernen :grin:).

Dabei soll ein Background Service immer wieder, z. B. über einen Timer, bestimmte Daten einer Webseite (viel mehr meine Nachrichten aus einem Browserspiel :grin:) auslesen und in eine SQLite-DB schreiben. Diese wird dann wiederum bei Bedarf von entsprechenden Activities ausgelesen.

Nun möchte ich aber auch, dass die Activities mir in irgendeiner Form (z. B. durch eine Animation) visuell signalisieren, wenn der Service gerade ein Update macht, während ich mich "in der App befinde".

Zudem möchte ich über die App auch "manuell" ein Update anstoßen, sowie den Service starten und beenden können.

Um das Auslesen (parsen) sowie die DB-Geschichten mache ich mir keine Sorgen, der Teil läuft schon super ;-)

Aber ich habe ziemliche Probleme mit dem Timer des Service und der Kommunikation zwischen Service und Activities, bzw. dem User.

Ich lasse mir testweise aus der run()-Methode des Timers (über einen vom Service erstellten Handler) Toast-Messages anzeigen, damit ich sehe, wie zuverlässig die Updates laufen. Während ich an meinem Handy herumspiele, ist die Welt in Ordnung. Wenn es nun aber für eine Weile in den (Tief-)Schlaf fällt, und ich es dann wieder "wecke", kommen oft mehrere Toast-Meldungen hintereinander. Werden hier nur die Toast-Messages über Handler.post() in eine Art Queue gesteckt, die dann erst wieder abgearbeitet wird, wenn ich das Handy "wecke", oder verzögert hier tatsächlich der Timer seine Aufrufe? Auf jeden Fall mache ich hier wohl etwas falsch :blink:

Die nächste Fragen sind also:
Wie kommuniziere ich richtig zwischen Service und Activities, respektive GUI?
Sollte das über IPC gehen, oder geht das auch wirklich ohne (zumal der Service ja auch wirklich NUR etwas mit dieser einen App zu tun hat)?
Ich habe zu dem Thema (ohne IPC) schon das eine oder andere Tutorial gesehen und auch danach gearbeitet, aber das scheint ja nicht so wirklich zu funktionieren.

Apropos Tutorials:
Kennt Ihr so richtig gute Tutorials oder Lektüren, am Besten mit praxisnahen Beispielen, zu diesem Thema?
Habe glaube in den letzten Tagen das halbe Netz durchkämmt und nichts brauchbares gefunden :blink:
Wäre schön, hier bei AndroidPIT im Wiki oder Forum etwas entsprechendes auffinden zu können :D

Mir geht erstmal hauptsächlich darum, wie man - wenn man schon den Service an die Activities binden kann - wie ich dann dem Service sage, dass er an eine Activity gebunden ist und daher der Activity eine Art "Update-Event" schicken soll, wenn er gerade updated, bzw. einen neuen Update-Vorgang anstößt. Oder andersrum: Kann ich den Service von Activity-Seite her "pollen", ob er gerade beim Updaten ist?

Und wie kann ich den Service von den Activities aus zum erneuten Updaten bewegen?
Laut Doku soll es ja ok sein, einfach nochmal startService() oder bindService() mit einem entsprechenden Intent aufzurufen, währen der Service läuft...?


Uiii... das war jetzt viel! :*)
Ich hoffe daher, dass es an Details nicht mangelt :grin:

Vielen Dank schon mal für's Durchlesen :grin:


Liebe Grüße

Benjamin Stelter

Antworten
Mac Systems
  • Forum-Beiträge: 1.727

16.07.2010, 11:30:27 via Website

Ich lasse mir testweise aus der run()-Methode des Timers (über einen vom Service erstellten Handler) Toast-Messages anzeigen, damit ich sehe, wie zuverlässig die Updates laufen. Während ich an meinem Handy herumspiele, ist die Welt in Ordnung. Wenn es nun aber für eine Weile in den (Tief-)Schlaf fällt, und ich es dann wieder "wecke", kommen oft mehrere Toast-Meldungen hintereinander. Werden hier nur die Toast-Messages über Handler.post() in eine Art Queue gesteckt, die dann erst wieder abgearbeitet wird, wenn ich das Handy "wecke", oder verzögert hier tatsächlich der Timer seine Aufrufe? Auf jeden Fall mache ich hier wohl etwas falsch :blink:

An diese Frage schliesse Ich mich an! Ich stellte so was mit Notifications fest die nur anzeigen wenn etwas gerade läuft FLAG_ONGOING_EVENT.


Dabei soll ein Background Service immer wieder, z. B. über einen Timer, bestimmte Daten einer Webseite (viel mehr meine Nachrichten aus einem Browserspiel :grin:) auslesen und in eine SQLite-DB schreiben. Diese wird dann wiederum bei Bedarf von entsprechenden Activities

Timer ? Sicher das du nicht einen Thread(Pool) meinst ?



Mir geht erstmal hauptsächlich darum, wie man - wenn man schon den Service an die Activities binden kann - wie ich dann dem Service sage, dass er an eine Activity gebunden ist und daher der Activity eine Art "Update-Event" schicken soll, wenn er gerade updated, bzw. einen neuen Update-Vorgang anstößt. Oder andersrum: Kann ich den Service von Activity-Seite her "pollen", ob er gerade beim Updaten ist?

Meist musst du ihn gar nicht binden, außer du brauchst ihn wirklich oft. Sonst könntest du ihn einfach starten und den Intent in der Service#onStart auswerten ....da führen viele wege nach Rom. Übersichtlicher ist allerdings das Binden des Service. On gerade ein Update läuft oder nicht ist einzig dein Problem das kann ein Service nicht leisten du musst den status irgendwie auswerten.

Ein Service kann deine Activity zwar kennen sollte es aber nicht. Dafür gibt es in Java Interfaces, damit legst du dich nicht auf die Implementierung fest! Daher definiere ein AIDL Callback Interface wie z.b:


/**
* @author mac
* @version $Id: IServiceCallbackListener.aidl 357 2010-05-01 13:29:34Z mac $
*/
oneway interface IServiceCallbackListener
{
void onTaskComplete();

void onTaskFailed();
}

Einzelne Task (wie ein Update) kannst du mittels AIDL/Stub des Services starten:


@Override
public void update(final int _selectedID, final IServiceCallbackListener _listener) <-- Hier übergibt man den Callback Listener
{
if (_listener == null)
{
throw new NullPointerException("IServiceCallbackListener is null");
}

if (Logging.isEnabled())
{
Log.d(LOG_TAG, "Insert Task to update spot with selectedID:" + _selectedID + " into scheduler");
}

final UpdateSpotForecastTask task = new UpdateSpotForecastTask(_selectedID, SpotService.this);
addTask(task, _listener);
}


Die Methode addTask die den Task in einen ThreadPool mit Priorität stellt:

/**
*
* @param _task
*/
private void addTask(final Callable<Void> _task, final IServiceCallbackListener _listener)
{
try
{
final PriorizedFutureTask task = new PriorizedFutureTask(PRIORITY.NORMAL, _task, _listener);
threadPool.execute(task);
}
catch (final Exception e)
{
Log.e(LOG_TAG, "Failed to queue task", e);
}
}


Der PriorizedFutureTask hält eine private RemoteCallbackList<IServiceCallbackListener> welche sich um die Verwaltung der entfernten Callback Interfaces kümmert -> Java Doc dazu lesen! Dieser erbt von FutureTask:

final class PriorizedFutureTask extends FutureTask<Void>

Ein Service ist ein anderer Prozess so das du mit AIDL arbeiten musst um das ganze hinzugekommen. Methoden der Klasse FutureTask musst du entsprechend überschreiben damit du den CallbackListenern später Rückmeldung geben kannst.




try
{
final PriorizedFutureTask task = new PriorizedFutureTask(PRIORITY.NORMAL, _task, _listener);
threadPool.execute(task);
}
catch (final Exception e)
{
Log.e(LOG_TAG, "Failed to queue task", e);
}

Ich habe mit diesem Ansatz gut Erfahrungen gemacht,

hth,
Mac

Windmate HD, See you @ IO 14 , Worked on Wundercar, Glass V3, LG G Watch, Moto 360, Android TV

Benjamin Stelter

Antworten
Benjamin Stelter
  • Forum-Beiträge: 16

16.07.2010, 17:42:10 via Website

Hey, danke für die ausführliche Antwort!

Timer ? Sicher das du nicht einen Thread(Pool) meinst ?
Doch, ich meine java.util.Timer!
Und davon verwende ich
1scheduleAtFixedRate(TimerTask task, long delay, long period)
um den Update-Task alle 10 Minuten laufen zu lassen.
Aber das scheint ja dann wohl nicht so wirklich der richtige Weg zu sein ^^

Ein Service kann deine Activity zwar kennen sollte es aber nicht. Dafür gibt es in Java Interfaces, damit legst du dich nicht auf die Implementierung fest!
Das war mir soweit schon bewusst, daher habe ich ein eigenes Interface deklariert, dass ich dann in meine Activities implementiert habe. Allerdings ist das kein AIDL-Interface.

Ich glaube die Geschichte mit dem AIDL hab ich schon halbwegs kapiert, als ich gerade etwas in der Doku herumgestöbert habe. Werde mir das nachher daheim nochmal genauer anschauen.

Und auch die Geschichte mit der RemoteCallbackList und den FutureTasks.

Das mit dem ThreadPool habe ich allerdings noch nicht ganz verstanden. :(
Meinst Du einen java.util.concurrent.ThreadPoolExecutor (bzw. ScheduledThreadPoolExecutor)?


Liebe Grüße

Benny

— geändert am 16.07.2010, 17:42:52

Antworten
Mac Systems
  • Forum-Beiträge: 1.727

16.07.2010, 18:14:34 via Website

Du kannst verschiedene ThreadPools verwenden, welchen ist an sich egal..

1public class AService extends Service
2{
3
4
5 private ExecutorService threadPool;
6
7
8 /**
9 * Checks if ThreadPool already created, if not it creates it
10 */
11 private synchronized void createThreadPool()
12 {
13 if (threadPool == null)
14 {
15 final int poolSize = getResources().getInteger(R.integer.schedule_threadpool_size);
16 final BlockingQueue<? super Runnable> queue = new PriorityBlockingQueue<Runnable>(poolSize,
17 new PriorizedFutureTaskComparator());
18 //
19 final MyThreadFactory factory = new MyThreadFactory("a Serivce Thread", Thread.NORM_PRIORITY);
20 threadPool = new ThreadPoolExecutor(1, 1, 1L, TimeUnit.SECONDS, (BlockingQueue<Runnable>) queue, factory);
21 }
22 }


Diese Methode wird in der onCreate bzw. onStart Methode bei mir aufgerufen und ist daher auch synchronized. Dieser verwendet sogar eine eigende ThreadFactory so das ich maximalen Einfluss auf diese habe. Wie viele Threads ich benutzte ist in den Ressourcen definiert!


Den Service kannst du z.b auch so beenden:


1@Override
2 public void onDestroy()
3 {
4 try
5 {
6 if (Logging.isEnabled())
7 {
8 Log.d(LOG_TAG, "Service#onDestroy");
9 }
10 //
11 if (threadPool != null)
12 {
13 // First we try a 'soft' shutdown
14 threadPool.shutdown();
15 // waiting for termination.
16 final boolean isTerminated = threadPool.awaitTermination(4L, TimeUnit.SECONDS);
17 if (!isTerminated)
18 {
19 if (Logging.isEnabled())
20 {
21 Log.d(LOG_TAG, "soft shutdown failed, trying hard shutdown.");
22 }
23 // Do a hard termination.
24 final List<Runnable> uncompletedTasks = threadPool.shutdownNow();
25 logUncompletedTask(uncompletedTasks);
26 }
27 if (Logging.isEnabled())
28 {
29 Log.d(LOG_TAG, "shutdown completed.");
30 }
31 }
32 }
33 catch (final SecurityException e)
34 {
35 Log.e(LOG_TAG, "shutdown failed", e);
36 }
37 catch (final InterruptedException e)
38 {
39 Log.e(LOG_TAG, "Failed to shutdown threadpool");
40 }
41 finally
42 {
43 super.onDestroy();
44 }
45 }

Falls der Service beendet wird sollte man die Task in der Queue sich merken momentan werden diese bei mir nur ausgegeben mittels : logUncompletedTask(...)

— geändert am 16.07.2010, 18:23:15

Windmate HD, See you @ IO 14 , Worked on Wundercar, Glass V3, LG G Watch, Moto 360, Android TV

Antworten
Benjamin Stelter
  • Forum-Beiträge: 16

29.07.2010, 20:53:55 via Website

Vielen Dank für Deine Hilfe, Mac! :)

Läuft viel "sauberer" jetzt. Vor allem die Kommunikation über AIDL! Super! :)

Nur die Updates laufen immer noch nicht so zuverlässig, wie ich das gerne hätte (alle 10 Minuten!). Wenn das Telefon aktiv ist, dann schon, aber wenn es mal für 1+ Stunden inaktiv (Display aus usw.) ist, dann werden die Updates nur noch willkürlich ausgeführt. Und meistens noch ein paar mal hintereinander, nachdem man das Telefon wieder aktiviert hat.

So in etwa sieht mein Code meines Service aus, um die Updates im 10-Minuten-Intervall zu starten:

1public final class MyService extends Service
2{
3 private final static int UPDATE_PERIOD = (1000 * 60 * 10);
4
5 private ScheduledExecutorService mThreadPool = null;
6
7
8 @Override
9 public void onCreate()
10 {
11 super.onCreate();
12
13 /*
14 * some service initialization stuff
15 */
16
17 if (mThreadPool == null)
18 mThreadPool = new ScheduledThreadPoolExecutor(3);
19 }
20
21
22 @Override
23 public int onStartCommand(Intent intent, int flags, int startId)
24 {
25 if (mThreadPool != null)
26 {
27 final MyUpdateTask updateTask = new MyUpdateTask();
28 /*
29 * some updateTask setup stuff
30 */
31 mThreadPool.scheduleAtFixedRate(updateTask, 0, UPDATE_PERIOD, TimeUnit.MILLISECONDS);
32 }
33
34 // We want this service to continue running until it is explicitly
35 // stopped, so return sticky.
36 return START_STICKY;
37 }
38}
(Das Code-"Fenster", bzw. die Einrückungen darin sind haarsträubend! ^^)

Liebe Grüße

Benny

— geändert am 29.07.2010, 20:55:03

Antworten
Mac Systems
  • Forum-Beiträge: 1.727

30.07.2010, 00:45:52 via Website

Schön das es läuft!

Das ein ThreadPool nicht das beste ist an dieser Stelle ist normal, inmmerhin versucht Android zu schlafen wenn es kann. Da wird ein Thread auch nicht unbedingt wach.
Hier hilft nur ein Alarm, dieser wird zuverlässig aufgerufen.

Grob lässt sich das Zenario so umschreiben:

Alarm erzeugen -> BroadcastReciever wird aufgerufen -> Broadcast Reciever startet Service -> Broadcast Reciever stellt eine Aufgabe in einen ThreadPool. Telefon geht wieder schlafen.

Hierfür habe Ich mir einen AlarmBroadcastReciever geschrieben:
1public final class AlarmBroadcastReciever extends BroadcastReceiver
2{
3 private final static String LOG_TAG = AlarmBroadcastReciever.class.getSimpleName();
4
5 /*
6 * (non-Javadoc)
7 *
8 * @see android.content.BroadcastReceiver#onReceive(android.content.Context,
9 * android.content.Intent)
10 */
11 @Override
12 public void onReceive(final Context _context, final Intent _intent)
13 {
14 if (Logging.isEnabled)
15 {
16 Log.d(LOG_TAG, "AlarmBroadcastReciever::onReceive");
17 }
18 if (Alert.isAlertIntent(_intent))
19 {
20 final Alert alert = Alert.read(_intent);
21 handleAlert(_context, alert);
22 }
23 else
24 {
25 Log.e(LOG_TAG, "Expected intent which contains alert, skipping!");
26 }
27 }
28
29 /**
30 * Passing the alert to the {@link SpotService}.
31 *
32 * @param _context
33 * @param _alert
34 * @see SpotService
35 */
36 private static void handleAlert(final Context _context, final Alert _alert)
37 {
38 final Intent startServiceIntent = new Intent();
39 Alert.write(_alert, startServiceIntent);
40 startServiceIntent.setAction(NAME_DER_ACTION);
41 final ComponentName name = _context.startService(startServiceIntent);
42 if (name == null)
43 {
44 Log.e(LOG_TAG, "Failed to start SpotService.");
45 }
46 }
47}


Weiterhin habe Ich auf das benutzen von Parcelable verzichtet und serialisiere die Alarme einfach mittels eines Mappings. Die Klasse Alert ist aussagekräftig genug dafür:
1/**
2 * Represents an alarm queued in AlarmManager. This Class is designed to be
3 * serialized into an {@link Intent}.
4 *
5 * @author mac
6 * @version $Id: Alert.java 552 2010-07-19 01:16:36Z mac $
7 */
8public final class Alert
9{
10 /**
11 * Lookup value
12 */
13 public final static String RETRYS = "alert.retrys";
14 /**
15 * Lookup value
16 */
17
18 public final static String REPEAT_ID = "alert.repeatid";
19
20 /**
21 * Lookup value
22 */
23 public final static String SELECTED_ID = "alert.selectedid";
24 /**
25 * Lookup value
26 */
27
28 public final static String TIME = "alert.time";
29 /**
30 * Lookup value
31 */
32
33 public final static String WEEKDAY = "alert.weekday";
34 /**
35 * Lookup value
36 */
37
38 public final static String SPOTNAME = "alert.spotname";
39 /**
40 * Constant which describes how often the alert will be enqueued using
41 * {@link AlarmManager#enqueueRetryAlarm(Alert, android.content.Context)}
42 * before marked as failed/expired.
43 */
44 private final static int MAX_RETRYS = 3;
45 /**
46 * @see #REPEAT_ID
47 */
48 private final int alarmID;
49 /**
50 * @see #SELECTED_ID
51 */
52 private final int selectedID;
53 /**
54 * @see #RETRYS
55 */
56 private volatile int retryCounter = 0;
57 /**
58 * @see #TIME
59 */
60 private final long time;
61 /**
62 * @see #WEEKDAY
63 */
64 private final int dayOfWeek;
65 /**
66 * @see #SPOTNAME
67 */
68 private final String spotName;
69
70 /**
71 *
72 * @param _spotName
73 * @param _selectedID
74 * @param _repeatId
75 * @param _retryCounter
76 * @param _time
77 * @param _dayOfWeek
78 * @see Calendar#DAY_OF_WEEK
79 * @see Repeat#getId()
80 * @see Station#getId()
81 */
82 Alert(final String _spotName, final int _selectedID, final int _repeatId, final int _retryCounter,
83 final long _time, final int _dayOfWeek)
84 {
85 selectedID = _selectedID;
86 alarmID = _repeatId;
87 retryCounter = _retryCounter;
88 time = _time;
89 dayOfWeek = _dayOfWeek;
90 spotName = _spotName;
91 }
92
93 /**
94 * ID which is used to create or cancel Alerts using {@link AlarmManager}
95 *
96 * @return
97 * @see Repeat#getId()
98 */
99 public int getAlertID()
100 {
101 return alarmID;
102 }
103
104 /**
105 * Retrieve selected id
106 *
107 * @return
108 * @see Station#getId()
109 */
110 public int getSelectedID()
111 {
112 return selectedID;
113 }
114
115 /**
116 * retry counter for this alarm.
117 *
118 * @return
119 */
120 public int getRetryCounter()
121 {
122 return retryCounter;
123 }
124
125 /**
126 * returns daytime this alert happen first in mills.
127 *
128 * @return
129 */
130 public long getTime()
131 {
132 return time;
133 }
134
135 /**
136 * Returns day of week this alert happen
137 *
138 * @return
139 * @see Calendar#DAY_OF_WEEK
140 */
141 public int getDayOfWeek()
142 {
143 return dayOfWeek;
144 }
145
146 /**
147 * Call this method if you want to re-queue this alert as a retry.
148 *
149 * @see #isRetry()
150 */
151 void incrementRetryCounter()
152 {
153 retryCounter++;
154 }
155
156 /**
157 * Returns <code>true</code> if this alert represents a retry.
158 *
159 * @return
160 * @see #incrementRetryCounter()
161 */
162 public boolean isRetry()
163 {
164 return retryCounter > 0;
165 }
166
167 /**
168 * Returns <code>true</code> if alert is expired (too many retrys).
169 *
170 * @return
171 */
172 public boolean isExpired()
173 {
174 return retryCounter >= MAX_RETRYS;
175 }
176
177 /**
178 * Returns <code>true</code> if no retry are made for this alert.
179 *
180 * @return
181 */
182 public boolean isNormalAlert()
183 {
184 return retryCounter == 0;
185 }
186
187 /**
188 * @return the spotName
189 */
190 public String getSpotName()
191 {
192 return spotName;
193 }
194
195 @Override
196 public String toString()
197 {
198 return "Alert [dayOfWeek=" + dayOfWeek + ", repeatID=" + alarmID + ", retryCounter=" + retryCounter
199 + ", selectedID=" + selectedID + ", spotName=" + spotName + ", time=" + time + "]";
200 }
201
202 /**
203 * Writes all info needed to recreate an alert from an intent (Serialize).
204 *
205 * @param _alert
206 * @param _intent
207 * @throws NullPointerException
208 * @see {@link #read(Intent)}
209 */
210 public static void write(final Alert _alert, final Intent _intent) throws NullPointerException
211 {
212 if (_intent == null)
213 {
214 throw new NullPointerException("intent");
215 }
216 if (_alert == null)
217 {
218 throw new NullPointerException("alert");
219 }
220 _intent.putExtra(SPOTNAME, _alert.getSpotName());
221 _intent.putExtra(REPEAT_ID, _alert.getAlertID());
222 _intent.putExtra(RETRYS, _alert.getRetryCounter());
223 _intent.putExtra(SELECTED_ID, _alert.getSelectedID());
224 _intent.putExtra(TIME, _alert.getTime());
225 _intent.putExtra(WEEKDAY, _alert.getDayOfWeek());
226 }
227
228 /**
229 * Returns <code>true</code> if <code>Intent</code> represents an
230 * <code>Alert</code>.
231 *
232 * @param _intent
233 * @return
234 * @throws NullPointerException
235 * @see {@link #read(Intent)}
236 * @see {@link #write(Alert, Intent)}
237 */
238 public static boolean isAlertIntent(final Intent _intent) throws NullPointerException
239 {
240 if (_intent == null)
241 {
242 throw new NullPointerException("Intent");
243 }
244 try
245 {
246 final Bundle bundle = _intent.getExtras();
247 checkForExtraOrThrow(SPOTNAME, bundle);
248 checkForExtraOrThrow(SELECTED_ID, bundle);
249 checkForExtraOrThrow(REPEAT_ID, bundle);
250 checkForExtraOrThrow(RETRYS, bundle);
251 checkForExtraOrThrow(TIME, bundle);
252 checkForExtraOrThrow(WEEKDAY, bundle);
253 }
254 catch (final IllegalArgumentException e)
255 {
256 return false;
257 }
258 return true;
259 }
260
261 /**
262 * Recreate an <code>Alert</code> from an Intent. (Deserialize)
263 *
264 * @param _intent
265 * @return
266 * @throws NullPointerException
267 * @throws IllegalArgumentException
268 * @see Alert#write(Alert, Intent)
269 * @see Alert#isAlertIntent(Intent)
270 */
271 public static Alert read(final Intent _intent) throws NullPointerException, IllegalArgumentException
272 {
273 if (_intent == null)
274 {
275 throw new NullPointerException("Intent");
276 }
277 final int NOT_FOUND = -1;
278 final int NOT_FOUND_LONG = -1;
279 //
280
281 final Bundle bundle = _intent.getExtras();
282 checkForExtraOrThrow(SPOTNAME, bundle);
283 checkForExtraOrThrow(SELECTED_ID, bundle);
284 checkForExtraOrThrow(REPEAT_ID, bundle);
285 checkForExtraOrThrow(RETRYS, bundle);
286 checkForExtraOrThrow(TIME, bundle);
287 checkForExtraOrThrow(WEEKDAY, bundle);
288
289 final String stationName = _intent.getStringExtra(SPOTNAME);
290 final int selectedID = _intent.getIntExtra(SELECTED_ID, NOT_FOUND);
291 final int repeatID = _intent.getIntExtra(REPEAT_ID, NOT_FOUND);
292 final int retryCounter = _intent.getIntExtra(RETRYS, NOT_FOUND);
293 final long time = _intent.getLongExtra(TIME, NOT_FOUND_LONG);
294 final int weekday = _intent.getIntExtra(WEEKDAY, NOT_FOUND);
295
296 if (stationName == null || selectedID == NOT_FOUND || repeatID == NOT_FOUND || retryCounter == NOT_FOUND
297 || weekday == NOT_FOUND || time == NOT_FOUND_LONG)
298 {
299 throw new IllegalArgumentException("Intent missing alert entrys! Entrys : stationName=" + stationName
300 + " selectedID=" + selectedID + " repeatID=" + repeatID + " retryCounter=" + retryCounter
301 + " time=" + time + " weekday=" + weekday);
302 }
303
304 final Alert alert = new Alert(stationName, selectedID, repeatID, retryCounter, time, weekday);
305 return alert;
306 }
307
308 private static void checkForExtraOrThrow(final String _key, final Bundle _bundle) throws IllegalArgumentException
309 {
310 if (_bundle == null)
311 {
312 throw new IllegalArgumentException("Bundle null.");
313 }
314 if (!_bundle.containsKey(_key))
315 {
316 throw new IllegalArgumentException("Missing key:" + _key);
317 }
318 }
319}

Die Methoden read & write solltest du dir anschauen. Somit spart man sich den Einsatz der Parcleable und es funktioniert dennoch wunderbar.
Du musst also nur deinen Service ein wenig umschreiben und du hast eine solide Lösung! Den ScheduledThreadpoolExecuter brauchst du also nicht sondern vielmehr einen einfachen ThreadPool der aufgaben sofort erledigt. Die Lösung skaliert recht gut und ist Ressourcen sparend. Wichtig ist das der AlertBroadcastReciever schnell durchlaufen wird da sonst ein ANR droht.

hth,
Mac

Windmate HD, See you @ IO 14 , Worked on Wundercar, Glass V3, LG G Watch, Moto 360, Android TV

Antworten