24

I use an AsyncTask to perform a long process.

I don't want to place my long process code directly inside doInBackground. Instead my long process code is located in another class, that I call in doInBackground.

I would like to be able to call publishProgress from inside the longProcess function. In C++ I would pass a callback pointer to publishProgress to my longProcess function.

How do I do that in java ?

EDIT:

My long process code:

public class MyLongProcessClass
    {
    public static void mylongProcess(File filetoRead)
        {
        // some code...
        // here I would like to call publishProgress
        // some code...
        }
    }

My AsyncTask code:

private class ReadFileTask extends AsyncTask<File, Void, Boolean>
    {
    ProgressDialog  taskProgress;

    @Override
    protected Boolean doInBackground(File... configFile)
        {
        MyLongProcessClass.mylongProcess(configFile[0]);
        return true;
        }
    }

EDIT #2 The long process method could also be non-static and called like this:

MyLongProcessClass fileReader = new MyLongProcessClass();
fileReader.mylongProcess(configFile[0]);

But that does not change my problem.

Narayan Soni
  • 114
  • 8
Regis St-Gelais
  • 3,156
  • 5
  • 27
  • 40

5 Answers5

35

The difficulty is that publishProgress is protected final so even if you pass this into your static method call you still can't call publishProgress directly.

I've not tried this myself, but how about:

public class LongOperation extends AsyncTask<String, Integer, String> {
    ...

    @Override
    protected String doInBackground(String... params) {
        SomeClass.doStuff(this);
        return null;
    }
    ...

    public void doProgress(int value){
        publishProgress(value);
    }
}
...
public class SomeClass {
    public static void doStuff(LongOperation task){
        // do stuff
        task.doProgress(1);
        // more stuff etc
    }
}

If this works please let me know! Note that calling doProgress from anywhere other than a method that has been invoked from doInBackground will almost certainly cause an error.

Feels pretty dirty to me, anyone else have a better way?

dave.c
  • 10,910
  • 5
  • 39
  • 62
  • it could work if my LongOperation class did not call xmlReader.parse (where the time consuming code is located). Looks like I will be stuck with a simple rotating progress dialog. – Regis St-Gelais Apr 01 '11 at 19:43
  • @Regis St-Gelais if you are trying to monitor the progress of the parse you could try [this approach](http://stackoverflow.com/questions/5015856/android-sax-parser-progress-monitoring) or [this one](http://stackoverflow.com/questions/3100013/java-sax-parser-progress-monitoring) – dave.c Apr 01 '11 at 21:49
  • Incredible the fact that i waste a lot of time to find a solution and now, thx to you, i found a so simple solution!! Thx much ;) – kinghomer Sep 17 '12 at 16:05
  • exactly, Feels pretty dirty 2. – thecr0w Sep 25 '12 at 10:51
5

A solution could be placing a simple public class inside the AsyncTask (make sure the task you define is also public) which has a public method that calls publishProgress(val). Passing that class should be available from any other package or class.

public abstract class MyClass {

    public MyClass() {
        // code...
    }

    // more code from your class...

    public class Task extends AsyncTask<String, Integer, Integer> {
        private Progress progress;

        protected Task() {
            this.progress = new Progress(this);
        }

        // ...

        @Override
        protected Integer doInBackground(String... params) {
            // ...
            SomeClass.doStuff(progress);
            // ...
        }

        // ...

        @Override
        protected void onProgressUpdate(Integer... progress) {
            // your code to update progress
        }

        public class Progress {
            private Task task;

            public Progress(Task task) {
                this.task = task;
            }

            public void publish(int val) {
                task.publishProgress(val);
            }
        }
    }
}

and then in the other class:

public class SomeClass {
    public static void doStuff(Progress progress){
        // do stuff
        progress.publish(20);
        // more stuff etc
    }
}

This worked for me.

jalv1039
  • 1,663
  • 3
  • 17
  • 21
2

Split up the longProcess() function into smaller functions.

Sample code:

@Override
protected Boolean doInBackground(Void... params) {
    YourClass.yourStaticMethodOne();
    publishProgress(1);
    YourClass.yourStaticMethodTwo();
    publishProgress(2);
    YourClass.yourStaticMethodThree();
    publishProgress(3);

    // And so on...
    return true;
}
Wroclai
  • 26,835
  • 7
  • 76
  • 67
  • @Regis St-Gelais: What's complicated? If you're getting a value back from one of these static methods you can just catch it up and pass it into the next function. AFAIK, there's no other solution available. – Wroclai Apr 01 '11 at 19:26
  • @Regis St-Gelais: Elaborate the problem with using this method. – Wroclai Apr 01 '11 at 20:05
1

If this works please let me know! Note that calling doProgress from anywhere other than a method that has been invoked from doInBackground will almost certainly cause an error.

Yes, it works. I extended it so that you don't need to pass the AsyncTask as a parameter to your method. This is particularly useful if (like me) you've already written all your methods before deciding that actually you do need to publish some progress, or in my case, update the UI from an AsyncTask:

public abstract class ModifiedAsyncTask<A,B,C> extends AsyncTask<A,B,C>{

    private static final HashMap<Thread,ModifiedAsyncTask<?,?,?>> threads 
             = new HashMap<Thread,ModifiedAsyncTask<?,?,?>>();

    @Override
    protected C doInBackground(A... params) {
        threads.put(Thread.currentThread(), this);
        return null;        
    }

    public static <T> void publishProgressCustom(T... t) throws ClassCastException{
        ModifiedAsyncTask<?, T, ?> task = null;
        try{
            task = (ModifiedAsyncTask<?, T, ?>) threads.get(Thread.currentThread());
        }catch(ClassCastException e){
            throw e;
        }
        if(task!=null)
            task.publishProgress(t);
    }
}

public class testThreadsActivity extends Activity {

/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);        
    }

    public void Button1Clicked(View v){
        MyThread mthread = new MyThread();
        mthread.execute((Void[])null);      
    }

    private class MyThread extends ModifiedAsyncTask<Void, Long, Void>{

        @Override
        protected Void doInBackground(Void... params) {
            super.doInBackground(params);

            while(true){
                myMethod(System.currentTimeMillis());               
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {                  
                    return null;
                }
            }           
        }

        protected void onProgressUpdate(Long... progress) {
            //Update UI
            ((TextView) findViewById(R.id.textView2)).setText("The Time is:" + progress[0]);
        }


    }

    private void myMethod(long l){

        // do something

        // request UI update
        ModifiedAsyncTask.publishProgressCustom(new Long[]{l});
    }

}

Feels pretty dirty to me, anyone else have a better way?

My way is probably worse. I'm calling a static method for doProgress (which I called publishProgressCustom). It can be called from anywhere without producing an error (as if the thread has no corresponding AsyncTask in the hashMap, it won't call publishProgress). The down side is that you have to add the Thread-AsyncTask mapping yourself after the thread has started. (You can't override AsyncTask.execute() sadly as this is final). I've done it here by overriding doInBackground() in the super class, so that anyone extending it just has to put super.doInBackground() as the first line in their own doInBackground().

I don't know enough about Threads and AsyncTask to know what happens to the HashMap references once the Thread and/or AsyncTask comes to an end. I suspect bad things happen, so I wouldn't suggest anyone try my solution as part of their own unless they know better

James Coote
  • 1,975
  • 19
  • 29
  • Very bad idea to do like this. If calling thread is different, it wont work. Plus you are putting threads in HashMap but not removing them. – xmen Dec 01 '14 at 05:34
0

When you say "my long process code is located in another class that I call in doInBackground", do you mean "located in another method that I call in doInBackground"?

If so, you could make that method a private method of your AsynTask class. Then you could call publishProgress inside the method whenever needed.

OceanBlue
  • 9,142
  • 21
  • 62
  • 84