-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathTaskService.java
More file actions
447 lines (365 loc) · 8.81 KB
/
Copy pathTaskService.java
File metadata and controls
447 lines (365 loc) · 8.81 KB
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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
package propoid.util.service;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.util.Log;
/**
* A service mediating between {@link Task}s and {@link TaskObserver}s.
*
* @see #resolveTask(Class, Intent)
*/
public abstract class TaskService<L extends TaskObserver> extends Service {
private ObserverBinder binder;
private List<L> observers = new ArrayList<L>();
private Map<Class<? extends Task>, Constructor<?>> constructors = new HashMap<Class<? extends Task>, Constructor<?>>();
/**
* Currently executed tasks.
*/
private List<Execution> executions = new ArrayList<Execution>();
private ExecutorService executor;
private Handler handler;
protected TaskService() {
this(new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>()));
}
protected TaskService(ExecutorService executor) {
this.executor = executor;
}
@Override
public void onCreate() {
super.onCreate();
handler = new Handler(getMainLooper());
binder = new ObserverBinder();
}
@Override
public void onDestroy() {
executor.shutdown();
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
schedule(intent);
return START_NOT_STICKY;
}
@Override
public final IBinder onBind(Intent intent) {
return binder;
}
/**
* Schedule an intent.
*
* @param intent
* intent to schedule
*/
protected void schedule(Intent intent) {
if (intent != null && intent.getAction() != null) {
Task task = resolveTask(intent);
if (task != null) {
schedule(task);
}
}
}
/**
* Schedule a new {@link Task}.
*
* @param task
* task to schedule
*/
protected synchronized final void schedule(Task task) {
for (int w = 0; w < executions.size(); w++) {
Execution candidate = executions.get(w);
if (candidate.task.onScheduling(task)) {
// handled by task
return;
}
}
Execution execution = new Execution(task);
executions.add(execution);
executor.submit((Callable<Void>) execution);
}
synchronized void deschedule(Execution execution) {
executions.remove(execution);
}
/**
* Resolve a task for the given intent.
*
* @param intent
* the initiating intent
* @return
*
* @see #schedule(Task)
*/
@SuppressWarnings("unchecked")
protected Task resolveTask(Intent intent) {
Task task;
try {
Class<? extends Task> clazz = (Class<? extends Task>) Class
.forName(intent.getAction());
Constructor<?> constructor = getConstructor(clazz);
if (constructor.getParameterTypes().length == 1) {
task = (Task) constructor.newInstance(this);
} else {
task = (Task) constructor.newInstance(this, intent);
}
onTaskResolved(intent, task);
} catch (Throwable ex) {
task = null;
onTaskUnresolved(intent, ex);
}
return task;
}
/**
* Hook method for handling of a resolved task.
*
* @param intent
* intent that triggered the task
* @param task
* resolved task
*/
protected void onTaskResolved(Intent intent, Task task) {
}
/**
* Hook method for handling tasks which could not be resolved.
*
* @param intent
* intent that triggered the task
* @param throwable
*/
protected void onTaskUnresolved(Intent intent, Throwable throwable) {
Log.d("propoid-util", "unresolved task '" + intent.getAction() + "'",
throwable);
}
/**
* Hook method for handling task failures.
*
* @param task
* @param throwable
* @return should delayed tasks still be scheduled
*
* @see Task#onExecute()
*/
protected boolean onTaskFailed(Task task, Throwable throwable) {
Log.e("propoid-util", "failed task '" + throwable.getMessage() + "'",
throwable);
return false;
}
/**
* Hook method to be notified of a newly subscribed {@link TaskObserver}s.
*
* @param observer
*/
protected void onSubscribed(L observer) {
}
/**
* Hook method to be notified of a newly unsubscribed {@link TaskObserver}s.
*
* @param observer
*/
protected void onUnsubscribed(L observer) {
}
private Constructor<?> getConstructor(Class<? extends Task> clazz) {
Constructor<?> constructor = constructors.get(clazz);
if (constructor == null) {
try {
constructor = clazz.getDeclaredConstructor(getClass(),
Intent.class);
constructors.put(clazz, constructor);
} catch (Exception ex) {
}
}
if (constructor == null) {
try {
constructor = clazz.getDeclaredConstructor(getClass());
constructors.put(clazz, constructor);
} catch (Exception ex) {
}
}
if (constructor == null) {
throw new IllegalArgumentException("no valid constructor");
}
return constructor;
}
/**
* An execution of a {@link Task}.
*/
class Execution implements Callable<Void>, Runnable {
Task task;
boolean publishing;
/**
* Optional successive task.
*
* @see #delay(Task)
*/
List<Task> delayed;
public Execution(Task task) {
this.task = task;
task.execution = this;
}
/**
* Called by the {@link ExecutorService}.
*/
@Override
public Void call() throws Exception {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
try {
task.onExecute();
} catch (Throwable ex) {
if (!onTaskFailed(task, ex)) {
delayed = null;
}
}
deschedule(this);
if (delayed != null) {
for (Task task : delayed) {
schedule(task);
}
}
return null;
}
/**
* Called by the {@link Task}.
*/
public synchronized void publish() {
if (publishing) {
throw new IllegalStateException("already publishing");
}
publishing = true;
handler.post(this);
while (publishing) {
try {
wait();
} catch (InterruptedException interrupted) {
}
}
}
/**
* Called by the {@link Handler}.
*/
@Override
public synchronized void run() {
task.onPublish();
publishing = false;
notifyAll();
}
public void delay(Task task) {
if (delayed == null) {
delayed = new ArrayList<Task>();
}
delayed.add(task);
}
}
class ObserverBinder extends Binder {
void subscribe(L observer) {
synchronized (observers) {
observers.add(observer);
}
onSubscribed(observer);
}
void unsubscribe(L observer) {
synchronized (observers) {
observers.remove(observer);
}
onUnsubscribed(observer);
}
}
/**
* A task.
*/
public abstract class Task {
Execution execution;
/**
* Execute the actual task.
*/
protected void onExecute() throws Exception {
}
/**
* Called by the service to allow this task to handle another task to be
* scheduled.
* <p>
* Overriden methods may
* <ul>
* <li>return {@code false} for unrelated tasks to be scheduled
* normally, thus running in parallel</li>
* <li>return {@code true} while dropping the task silently, e.g. if its
* purpose is already served by this task</li>
* <li>return {@code true} and delay the task to let it be scheduled
* after this task has finished</li>
* </ul>
*
* @param other
*
* @see #delay(Task)
*/
public boolean onScheduling(Task other) {
return false;
}
/**
* Delay another task to be scheduled after this one.
*
* @param other
* task to delay
*/
public final void delay(Task other) {
if (execution == null) {
throw new IllegalStateException("not executing");
}
execution.delay(other);
}
/**
* Initiate publishing.
*
* @see #onPublish()
*/
public final void publish() {
if (execution == null) {
throw new IllegalStateException("not executing");
}
execution.publish();
}
/**
* Publish, forwards to {@link #onPublish(TaskObserver)} for each
* subscribed observer.
*/
protected void onPublish() {
for (L observer : observers) {
onPublish(observer);
}
}
/**
* Publish to the given observer.
*
* @param observer
* observer
*/
protected void onPublish(L observer) {
}
}
/**
* Utility method to create an {@link Intent} for a service's {@link Task}.
*
* @param context
* context of intent
* @param task
* action of intent
*/
public static <L extends TaskObserver> Intent createIntent(Context context,
Class<? extends TaskService<L>.Task> action) {
Intent intent = new Intent(context, action.getEnclosingClass());
intent.setAction(action.getName());
return intent;
}
}