I'd like to report the progress of an Android-independant calculation function doing uninterruptible calculations.
By Android-independant, I mean the code of the package must not depend on any Android package.
Ideally, I want to be able to monitor the progress of any function that takes time to complete. I use an AsyncTask to report to the UI thread periodically. The AsyncTask starts a Thread that runs the function. So, every second, the AsyncTask asks the Thread for its progress and publishes it to the UI Thread.
So I've got three threads :
- the UI thread responsible for user interactions and display
- the computing Thread that runs calculations and sets its progress quickly
- the AsyncTask responsible for asking regularly to the computing thread if it's done and how much work has been done if it's not yet finished, and then publishing the answer to the UI thread
It almost works but I'm not satisfied with the results and I think my implementation is cumbersome and broken.
I observe two problems :
- When I start the Thread, it may not start immediately. (More precisely, it may take several seconds before I see its output, that's not the same.)
- If the computing thread lasts longer than a predefined duration, I cannot kill it without instrumenting the function.
1- is seen with this log trace (executed on a connected device) :
04-07 15:34:31.416 18706-18834/com.example.android.elevationdrag D/yo? loop(99)0.0 0.0 0.0 0
04-07 15:34:32.416 18706-18834/com.example.android.elevationdrag D/yo? loop(98)0.0 0.0 0.0 0
04-07 15:34:33.416 18706-18834/com.example.android.elevationdrag D/yo? loop(97)0.0 0.0 0.0 0
04-07 15:34:34.416 18706-18834/com.example.android.elevationdrag D/yo? loop(96)0.0 0.0 0.0 0
04-07 15:34:34.826 18706-18835/com.example.android.elevationdrag D/yoSVC? 0.0
04-07 15:34:35.006 18706-18835/com.example.android.elevationdrag D/dalvikvm? GC_FOR_ALLOC freed 5547K, 50% free 5614K/11228K, paused 29ms, total 30ms
04-07 15:34:35.416 18706-18834/com.example.android.elevationdrag D/yo? loop(95)0.16882964936278524 0.0 0.0 0
04-07 15:34:35.456 18706-18835/com.example.android.elevationdrag D/dalvikvm? GC_FOR_ALLOC freed 1341K, 44% free 6321K/11228K, paused 24ms, total 24ms
04-07 15:34:35.916 18706-18835/com.example.android.elevationdrag D/dalvikvm? GC_FOR_ALLOC freed 812K, 33% free 7556K/11228K, paused 31ms, total 31ms
04-07 15:34:36.356 18706-18835/com.example.android.elevationdrag D/dalvikvm? GC_FOR_ALLOC freed 835K, 22% free 8769K/11228K, paused 40ms, total 40ms
04-07 15:34:36.426 18706-18834/com.example.android.elevationdrag D/yo? loop(94)0.461804305565497 0.16957664338683295 1696.0 1696
04-07 15:34:36.776 18706-18835/com.example.android.elevationdrag D/dalvikvm? GC_FOR_ALLOC freed 870K, 12% free 9946K/11228K, paused 47ms, total 47ms
04-07 15:34:37.286 18706-18835/com.example.android.elevationdrag D/dalvikvm? GC_FOR_ALLOC freed 854K, 8% free 11370K/12340K, paused 61ms, total 61ms
04-07 15:34:37.426 18706-18834/com.example.android.elevationdrag D/yo? loop(93)0.7725448196430245 0.461804305565497 4618.0 4618
04-07 15:34:37.796 18706-18835/com.example.android.elevationdrag D/dalvikvm? GC_FOR_ALLOC freed 1149K, 10% free 12877K/14236K, paused 68ms, total 68ms
04-07 15:34:38.126 18706-18835/com.example.android.elevationdrag D/yo? Released from DoCalculatePlan.run()
04-07 15:34:38.436 18706-18834/com.example.android.elevationdrag D/yo? loop(92)100.0 0.7728058175550427 7728.0 7728
yo
lines are from the AsyncTask, where I put the following line :
Log.d("yo", "loop("+loops+")" +String.valueOf(cpt_plan.getPercentProgress())+" "+String.valueOf(progressr)+" "+String.valueOf(progressd)+" "+String.valueOf(progress));
yoSVC
lines are from the function itself, where I put the following line :
Log.d("yoSVC", String.valueOf(this.percentProgress));
More logging from the service class. One can observe the context swith occuring at 15:53:45.096, exactly one second after calling Thread.sleep().
04-07 15:53:43.096 24348-24421/com.example.android.elevationdrag D/yo? loop(99)0.0 0.0 0.0 0
04-07 15:53:44.096 24348-24421/com.example.android.elevationdrag D/yo? loop(98)0.0 0.0 0.0 0
04-07 15:53:44.486 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.011000550888307417
04-07 15:53:44.506 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.022001101776615132
04-07 15:53:44.546 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.04500068861038525
04-07 15:53:44.566 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.05600123949869297
04-07 15:53:44.606 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.0790008263324631
04-07 15:53:44.626 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.09000137722077081
04-07 15:53:44.666 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.11300096405454094
04-07 15:53:44.706 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.13600055088830218
04-07 15:53:44.726 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.14700110177660103
04-07 15:53:44.756 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.1700006886103526
04-07 15:53:44.776 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.18100123949865146
04-07 15:53:44.816 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.20400082633240305
04-07 15:53:44.836 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.2150013772207019
04-07 15:53:44.866 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.23800096405445348
04-07 15:53:44.906 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.26100055088822277
04-07 15:53:44.926 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.27200110177653936
04-07 15:53:44.956 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.295000688610328
04-07 15:53:44.976 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.3060012394986446
04-07 15:53:45.016 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.32900082633243327
04-07 15:53:45.026 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.34000137722074986
04-07 15:53:45.066 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.3630009640545385
04-07 15:53:45.096 24348-24421/com.example.android.elevationdrag D/yo? loop(97)0.38391750447598555 0.0 0.0 0
04-07 15:53:45.106 24348-24422/com.example.android.elevationdrag D/yoSVC? 0.3860005508883272
The "service" class is instrumented with a double in order to publish it progress :
class Plan
{
//...
// In order to inform users of the calculation progress, the service itself has
// to publish its progress.
private volatile double percentProgress;
//...
}
Android application code extracted from the Activity :
private class CalculatePlanTask extends AsyncTask<BigDecimal, Integer, Plan>
{
volatile boolean released = false;
volatile Plan cpt_plan;
CalculatePlanTask()
{
cpt_plan = new Plan();
}
private class DoCalculatePlan implements Runnable
{
final BigDecimal period;
final BigDecimal principal;
final BigDecimal rate;
private DoCalculatePlan(BigDecimal period, BigDecimal principal, BigDecimal rate)
{
this.period = period;
this.principal = principal;
this.rate = rate;
}
/**
* Starts executing the active part of the class' code. This method is
* called when a thread is started that has been created with a class which
* implements {@code Runnable}.
*/
@Override
public void run()
{
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
Situation situation = new Situation(principal, period.intValue());
cpt_plan.setSituationDeDepart(situation);
cpt_plan.setUniqueEvent(new RateChange(0, rate.movePointLeft(2), RateChange.RateChangeMode.KEEP_DURATION));
cpt_plan.computeSituation();
released = true;
//Log.d("yo", "Released from DoCalculatePlan.run()");
}
}
/** The system calls this to perform work in a worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Plan doInBackground(BigDecimal... bds) {
publishProgress(0);
BigDecimal bdPeriods = bds[0];
BigDecimal bdPrincipal = bds[1];
BigDecimal bdRate = bds[2];
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
Thread calculator = new Thread(new DoCalculatePlan(bdPeriods, bdPrincipal, bdRate));
calculator.start();
final int timeoutMilli = 100 * 1000;
final int sleeptime = 1000;
final int maxLoops = timeoutMilli / sleeptime;
int loops = maxLoops;
int progress = 0;
boolean thrown = false;
while (!released && !thrown && --loops >= 0)
{
//DO NOT use this variable : progress = (int) Math.round(cpt_plan.getPercentProgress()*100.0);
//double progressr = cpt_plan.getPercentProgress(); //cpt_plan.percentProgress;
//double progressd = Math.round(cpt_plan.percentProgress*10000);
progress = (int) Math.round(cpt_plan.percentProgress*10000);
//DONE:
try
{
Thread.sleep(sleeptime);
publishProgress((int) Math.round(cpt_plan.getPercentProgress()*10000)); // Use directly the volatile variable which value is copied.
//Log.d("yo", "loop("+loops+")" +String.valueOf(cpt_plan.getPercentProgress())+" "+String.valueOf(progressr)+" "+String.valueOf(progressd)+" "+String.valueOf(progress));
}
catch (InterruptedException e)
{
released = true;
thrown = true;
}
}
//DONE : put the thread in an interrupted state.
// we cannot kill it :(
calculator.interrupt();
if (0 == loops || thrown)
return null;
return cpt_plan;
}
protected void onProgressUpdate(Integer... progress)
{
int p = progress[0];
EditText principal = (EditText) findViewById(R.id.editPrincipal);
principal.setText(progress[0].toString());
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Plan p_plan)
{
if (null == p_plan)
plan = new Plan();
else
plan = p_plan;
postCalculateImpl();
}
}
The code has been modified a lot of times to experiment, so may look a bit odd, nevertheless, constructive comments are appreciated.
Before going further, I wonder if I misunderstand something, and most of the questions or blogs entries I've found so far present an implementation in the Task, or with Android code.
I wonder why my computing Thread does not start working immediately after having called sleep in the AsyncTask (maybe it is and the logging is error-prone).
I also wonder how to interrupt my Thread without putting ugly Threading code inside the computing function. Calling Thread.isInterrupted() each time percentProgress is set would be the most acceptable to me.
I must note that the documentation here : http://developer.android.com/reference/android/os/AsyncTask.html
says
When first introduced, AsyncTasks were executed serially on a single background thread. Starting with DONUT, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting with HONEYCOMB, tasks are executed on a single thread to avoid common application errors caused by parallel execution.
If you truly want parallel execution, you can invoke executeOnExecutor(java.util.concurrent.Executor, Object[]) with THREAD_POOL_EXECUTOR
So when I tested executeOnExecutor() I expected concurrent executions, but saw none but sequential ones.
This answer Running multiple AsyncTasks at the same time -- not possible? might explain this behaviour, as my device runs under Android 4.4. But a mistake on my side is more probable.