java - How do you Manage a CountdownTimer from within an IntentService? -
i implementing pomodoro timer using intentservice , seem struggling managing timer within intentservice . have @ moment following:
the code:
package com.nursson.pomodorotimer.service; import android.app.intentservice; import android.content.intent; import android.os.countdowntimer; import android.os.looper; import android.support.v4.content.localbroadcastmanager; import android.util.log; import com.nursson.pomodorotimer.model.pomodoro; /** * {@link intentservice} subclass handling asynchronous task requests in * service on separate handler thread. * <p/> * helper methods. */ public class pomodoroservice extends intentservice { public static final string action_cancelled = "com.nursson.pomodorotimer.service.pomodoro.action.cancelled"; public static final string action_complete = "com.nursson.pomodorotimer.service.pomodoro.action.complete"; public static final string action_start = "com.nursson.pomodorotimer.service.pomodoro.action.start"; public static final string action_stop = "com.nursson.pomodorotimer.service.pomodoro.action.stop"; public static final string action_tick = "com.nursson.pomodorotimer.service.pomodoro.action.tick"; public static final string extra_time_remaining = "com.nursson.pomodorotimer.service.pomodoro.param.time_remaining"; private boolean started; private countdowntimer countdowntimer = new countdowntimer(10000, 1000) { @override public void ontick(long millisuntilfinished) { intent tickintent = new intent(action_tick); tickintent.putextra(extra_time_remaining, millisuntilfinished); localbroadcastmanager.getinstance(pomodoroservice.this).sendbroadcast(tickintent); log.i("timer", "ticking..."); } @override public void onfinish() { started = false; intent tickintent = new intent(action_complete); localbroadcastmanager.getinstance(pomodoroservice.this).sendbroadcast(tickintent); log.i("timer", "countdown finished()"); looper.mylooper().quit(); } }; public pomodoroservice() { super("pomodoroservice"); } @override public void oncreate() { super.oncreate(); log.i("timer", "oncreate()"); } @override public void ondestroy() { super.ondestroy(); countdowntimer.cancel(); log.i("timer", "ondestroy()"); } @override public void onhandleintent(intent intent) { if (intent == null) { return; } switch (intent.getaction()) { case action_start: handlestartaction(); break; case action_stop: handlestopaction(); break; } looper.loop(); } private void handlestopaction() { log.i("timer", "handlestopaction(): " + started); if (started) { log.i("timer", "handlestopaction() - cancel timer"); started = false; countdowntimer.cancel(); intent tickintent = new intent(action_cancelled); localbroadcastmanager.getinstance(pomodoroservice.this).sendbroadcast(tickintent); } } private void handlestartaction() { log.i("timer", "handlestartaction()" + started); if (!started) { log.i("timer", "handlestartaction() - starting timer"); started = true; countdowntimer.start(); } } boolean isstarted() { return started; } countdowntimer getcountdowntimer() { return countdowntimer; } }
what happening in code
so intentservice contains countdowntimer broadcast messages activities. uses looper.loop();
, looper.mylooper().quit();
try , manage lifecycle. if not include ondestroy()
gets called , countdowntimer runs until end. want ondestroy()
called after timer finishes
the exception getting when code reaches looper.mylooper().quit();
, stacktrace below:
08-29 02:27:38.855 3337-3337/com.nursson.pomodorotimer e/androidruntime: fatal exception: main process: com.nursson.pomodorotimer, pid: 3337 java.lang.illegalstateexception: main thread not allowed quit. @ android.os.messagequeue.quit(messagequeue.java:415) @ android.os.looper.quit(looper.java:228) @ com.nursson.pomodorotimer.service.pomodoroservice$1$override.onfinish(pomodoroservice.java:44) @ com.nursson.pomodorotimer.service.pomodoroservice$1$override.access$dispatch(pomodoroservice.java) @ com.nursson.pomodorotimer.service.pomodoroservice$1.onfinish(pomodoroservice.java:0) @ android.os.countdowntimer$1.handlemessage(countdowntimer.java:127) @ android.os.handler.dispatchmessage(handler.java:102) @ android.os.looper.loop(looper.java:148) @ android.app.activitythread.main(activitythread.java:5417) @ java.lang.reflect.method.invoke(native method) @ com.android.internal.os.zygoteinit$methodandargscaller.run(zygoteinit.java:726) @ com.android.internal.os.zygoteinit.main(zygoteinit.java:616)
what want happen
i want able start , stop countdowntimer using intentservice , "good citizen" when doing this.
the end goal have timer can last through orientation change , can continue running when activity terminated.
what tried far:
- removing
looper.mylooper().quit();
.ondestroy()
never gets called when countdowntimer done. - removing
looper
references.ondestroy()
gets called early. timer works cannot reference countdowntimer cancel it.
what thought about:
- there alternative countdowntimer better suited want do? alarmmanager maybe?
- should using service instead of intentservice might make easier handle countdowntimer?
all examples of looper
have seen use pattern
looper.prepare(); // create handler looper.loop();
it creates , runs message loop threads not have one, handler being used interact loop.
now in code not see neither prepare
nor handler
, first not follow typical pattern , besides misuse blocking character of looper.loop()
prevent onhandleintent
termination.
calling looper.mylooper().quit()
in onfinish
not working since countdowntimer
methods seem run on main thread, trying quit main thread's message loop. looks doesn't it.
if need block onhandleindent
until onfinish
gets executed, why not use 1 of plain java threading patterns? like
private final lock lock = new reentrantlock(); private final condition waitforfinish = lock.newcondition(); @override public void onhandleintent(intent intent) { if (intent == null) { return; } switch (intent.getaction()) { case action_start: handlestartaction(); break; case action_stop: handlestopaction(); break; } lock.lock(); try { while (started) { waitforfinish.await(); } } catch (interruptedexception ie) { // log exceptions } { lock.unlock(); } }
and
@override public void onfinish() { started = false; intent tickintent = new intent(action_complete); localbroadcastmanager.getinstance(pomodoroservice.this).sendbroadcast(tickintent); log.i("timer", "countdown finished()"); lock.lock(); try { waitforfinish.signal(); } { lock.unlock(); } }
the disadvantage – intentservice handles requests in single worker thread, long blocks in onhandleintent
won’t accept new requests, request stop timer (better check believe me - not have android sdk here proove it).
an alternative solution put blocking call ondestroy
- free onhandleintent
new requests , prevent service finishing until timer done.
or use type of service – bound or unbound, won’t destroy after being out of work. have downside though – both not meant work off main thread, intentservice for.
Comments
Post a Comment