1

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 :

  1. the UI thread responsible for user interactions and display
  2. the computing Thread that runs calculations and sets its progress quickly
  3. 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 :

  1. 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.)
  2. 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));

yoSVClines 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.

Community
  • 1
  • 1
SR_
  • 874
  • 13
  • 31

0 Answers0