0

I am trying to update my app to handle configuration changes (especially screen turning) manually.

I have some questions about what happens when changes happen during a Thread execution.

I have created an abstract class I call ThreadTask which uses Threads and Handlers to the main thread's looper to send updates to the main thread. This is my implementation of AsyncTask but with threads, I prefer this to using AsyncTask because I have more control over it.

It also has two methods to register an observer to the above events, it uses this interface:

public interface TaskObserver {
    void pre_execute();
    void on_progress(ProgressData progress);
    void finished(Object result);
    void cancelled(Object result);
}

The abstract members that the subclass must implement are :

    abstract Object do_in_background();

and some concrete members are:

synchronized void add_observer(TaskObserver observer){
    _observers.add(observer);
}

synchronized void remove_observer(TaskObserver observer){
    _observers.remove(observer);
}

synchronized void post_execute(Object result) {
    int observers = _observers.size();
    for (int idx = 0; idx < observers; idx++) {
         _observers.get(idx).finished(result);
    }
}
///plus all the other methods of the interface

So when I implement a concrete class it would go something like this:

public class MyThreadTask extends ThreadTask{

    @Override
    Object do_in_background() {
        progress.primary_max = 5;
        for (int cur = 0 ; cur < 5 ; cur++) {
            progress.primary = cur;
            publish();
            Thread.Sleep(3000);
        }
    }

}

and I updated the activity that calls this like so:

static final string TAG ="my_main_activity";

MyDataFragment _data; //this is a datafragment, a fragment with retaininstancestate , and a public `MyThreadTask task` variable

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (_data == null) {
        _data = (MyDataFragment)getFragmentManager().findFragmentByTag(TAG + "_data");
        if (_data == null) {
            _data = new MyDataFragment();
            getFragmentManager().beginTransaction().add(_data, TAG + "_data").commit();
        }
    }
    if (_data.task != null) {
       _data.task.register(this);
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (_data.task != null) {
       _data.task.remove(this);
    }
}

this makes sure that I always have a reference to the correct thread

When I wish to start the task I do it like so:

button.setOnClickListener((v) -> {
    if (_data.task != null) return; //to make sure only one thread starts at a time
    _data.task = new MyThreadTask();
    _data.task.register(this);
    _data.task.start(); //this is not thread's start, it is mine, it does other things too
})

and when the thread finishes it calls void finished(Object result) which I handle like so:

void finished(Object result) {
    try {
        //get result;
    } finally {
        _data.task.remove(this);
        _data.task = null;
    }
}

here are my questions:

a) is declaring my observer methods as synchronized necessary? I did it just to make sure , but when the activity is destroyed and then recreated, does it happen on the same thread? is there a chance for example that a progress_update may happen while an observer is being removed during onDestroy?

b) what will happen if a thread finishes and calls post_execute(which is important) during a configuration change? will the update be lost?

c) If indeed the update is lost because it currently has no observers, is there a way, either in my implementation or a different one, to handle the above?

Thanks in advance for any help you can provide

Cruces
  • 3,029
  • 1
  • 26
  • 55

2 Answers2

0

The preferred way to keep a background task alive through a configuration change is by hosting it in a retained fragment. The same instance of the fragment will persist through the configuration change. When the fragment is paused, check the activity's isChangingConfigurations and cancel the task only if it's false.

I don't know if this is documented anywhere, but it seems that the entire configuration change is posted to the main thread so that nothing else can run between pausing the old activity and resuming the new one. If you were using an AsyncTask in a retained fragment, you would be assured that its onPostExecute could not run during the configuration change. With your approach, the task could easily complete when there is no observer.

Kevin Krumwiede
  • 9,868
  • 4
  • 34
  • 82
  • I am hosting it in a retained fragment, MyDataFragment _data is a retained fragment. Thanks for the isChangingConfigurations method, it will probably help. Also Thanks for the information about AsyncTask by the way, I'll take a look at AsyncTask's code and see if I can replicate that behaviour in my code too – Cruces Sep 14 '17 at 12:27
  • Well I checked AsyncTask's code, and I don't see anything special about it preventing postexecute being run during a configuration change. It seems to use a simple handler to post the finish data to the task which in turn calls onpostexecute, maybe I'm missing something? – Cruces Sep 14 '17 at 12:34
  • @Cruces It's not in the code of `AsyncTask`. It's a consequence of the framework posting all the steps of the config change to the main thread en bloc. – Kevin Krumwiede Sep 14 '17 at 20:19
0

Asynctask does not handle configuration changes that well. I think, instead of Asynctask you should use AsynctaskLoader which can handle the config changes easily and it behaves within the life cycle of activities/fragments.

When you run AsyncTask and if the android system kills your activity/fragment(during config changes or memory conservation) then your doInBackground() method still keeps on running in the background and this can lead to undesirable results.

Therefore, instead of using AsyncTask you can use AsynctaskLoader or if you are populating data from SQLite then you can use CursorLoader.

References:

  1. Guideline to choose among AsyncTaskLoader and AsyncTask to be used in Fragment

  2. https://developer.android.com/reference/android/content/AsyncTaskLoader.html

Kaveesh Kanwal
  • 1,753
  • 17
  • 16
  • Sorry I may have misspoke, when I said my implementation of AsyncTask, I meant that I have the same methods as AsyncTask (pre_execute, do_in_background etc) but I use Thread to run background tasks and Handler to send messages to the main thread. – Cruces Sep 14 '17 at 12:23
  • Also I my implementation works, it does update the data correctly, I am just wondering what happens when the lifecycle methods run when the thread finishes – Cruces Sep 14 '17 at 12:24
  • If you want to properly handle config changes then i recommend that you use AsyncTaskLoader, it will handle config changes for you. Android docs also recommend the same. – Kaveesh Kanwal Sep 14 '17 at 13:08