How to get the current savedInstanceState in an AsyncTask callback? - android

I ran into an interesting problem, and I'm not sure how to go about fixing it. Consider the following code:
public class MainActivity extends AppCompatActivity {
private Bundle savedState;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
savedState = savedInstanceState;
Log.d("ON CREATE", "savedState is null: "+(savedState==null));
new CustomTask().execute();
}
public class CustomTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... voids) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(Void v) {
Log.d("POST EXECUTE", "savedState is null: "+(savedState==null));
}
}
}
This code saves a reference to the savedInstanceState then runs an AsyncTask, which tries to access that variable 5 seconds later. If the user changes the orientation of the device before the AsyncTask finishes its work, the following output is produced:
ON CREATE: savedState is null: true //initial run
ON CREATE: savedState is null: false //re-created after orientation change
POST EXECUTE: savedState is null: true //first instance
POST EXECUTE: savedState is null: false //second instance
Both of the onPostExecute() methods fire after the orientation change, but they are seemingly accessing the same savedState variable, which I expected to be non-null in both cases since it was being accessed after the orientation change.
Apparently the first AsyncTask, which was started before the orientation change still references the savedState variable from before the change as well. So my questions are:
Why does it do this? After the app state is restored, I would expect the AsyncTask to simply access all class members in their current states, which means savedState would be non-null.
And how can I access the current savedInstanceState variable from the callback of an AsyncTask that was started before that variable was changed?

After the orientation change, there are two instances of MainActivity. The old one has gone through the tear-down lifecycle events (onStop(), onDestroy(), etc.) but has not been garbage collected because the inner-class CustomTask thread is still running and holds a reference to it. That instance of CustomTask sees the null savedState. It knows nothing of the new instance of MainActivity, created after the restart, and its non-null savedState.
Do you really want the original instance of CustomTask to continue running after restart? Maybe you should cancel it when the activity is destroyed. If you really need it to continue running and have access to state data that you now declare in the activity, you will need to move that state data out of the activity to somewhere else, such as a singleton object, subclass of Application or persistent storage.
Using a retained fragment might be another option for retaining state and background processing across restarts.

After a month of dealing with this issue, I finally found the solution to this. The key concept I was struggling with was how to update an (already running) AsyncTask with a reference to the current instance of the Activity it's working with. Here's how to do it.
The first step is to separate the AsyncTask into its own class file, and pass a reference to the Activity it's working with in through a constructor or a setter. So to use the same example I used in my original question, it would look something like this:
public class CustomTask extends AsyncTask<Void, Void, Void> {
//This could also be a reference to a callback interface
//implemented in an Activity
private Activity activity;
public CustomTask(Activity activity) {
this.activity = activity;
}
protected Void doInBackground(Void... voids) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(Void v) {
Log.d("POST EXECUTE", "savedState is null: "+(savedState==null));
}
//newly added method
public void setActivity(Activity activity) {
this.activity = activity;
}
//newly added method
public void detachFromActivity() {
activity = null;
}
}
The next step is to save a reference to the running AsyncTask as a data member of the Activity. That's just a matter of creating a private variable private CustomTask customTask; in the Activity.
Next, and this is the important part, you need to override onRetainCustomNonConfigurationInstance() in your Activity, and in this method, detach the AsyncTask from its old Activity (which is destroyed on a screen rotation) and retain the reference to the task so we can work with it in the new instance of the Activity that will be re-created when the screen finishes rotating. So this method would look like this:
#Override
public Object onRetainCustomNonConfigurationInstance() {
if(customTask != null) {
customTask.detachFromActivity();
}
return customTask;
}
Now, after the screen rotation has completed and the Activity is re-created, we need to get the reference to our task that was passed through. So we call getLastCustomNonConfigurationInstance() and cast the Object it returns to our specific AsyncTask class type. We can do a null check here to see if we have a task that was passed through. If there is one, we set its listener to our CURRENT Activity reference, so that the callbacks come to the right Activity instance (and hence avoided NullPointerExceptions, IllegalStateExceptions, and other nasties). This bit should look like this:
customTask = (CustomTask) getLastCustomNonConfigurationInstance();
if(customTask != null) {
customTask.setListener(this);
}
Now our running AsyncTask has the correct reference to the current instance of our Activity, and it will properly deliver its callbacks to an up-to-date Activity.
For more information on the uses and limitations of this solution, please see the Android documentation here: http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance()

Related

What is the Correct way to update callback references when Android Activity's callbacks go null after an orientation change

I have an Activity which implements a listener. My concern is that the
activity can get re-created and the callback will then have a reference
to an object that is null.
This means we must update the controller with a new reference that references
the newly created activity.
What pattern is best to use even if the callbacks are async?
Is there perhaps a safe way to update the controllers reference in a thread > safe way.
OR
Should one rather use a Headless fragment and use the onAttach method get the
updated reference.
OR
Should one rather not use these patterns and use a Handler for
all your callbacks?
I suspect that my updateListener method will not work in all cases e.g.
1) init is busy and is just about to call the callback, line marked with
*10*
2) the activity gets recreated and updates the controller with
a new reference but the updateListener method is blocked because the callback is about to take place.
3) the callback executes and fails as the listener reference variable is stale.
public class Controller {
UserActionListener listener
static Controller instance;
public static synchronized Controller getInstance(UserActionListener listener) {
if (instance == null) {
instance = new Controller();
}
this.listener = listener;
return instance;
}
private Controller() {
//empty, enforce getInstance
}
private init() {
// do some very long running operation in a separate thread.
//.... on completion we update the UI
synchronized(Controller.class) {
/*10*/ listener.handle("SHOW DIALOG");
}
}
public void updateListener(UserActionListener listener) {
synchronized(Controller.class) {
this.listener = listener;
}
}
public class MainActivity extends AppCompatActivity implements UserActionListener {
static Controller controller;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
controller = Controller.getInstance(this);
if (savedInstanceState == null) {
//do not run on re-create
controller.init();
}
}
#Override
protected void onPostResume() {
super.onPostResume();
controller.updateListener(this);
}
#Override
public void handleAction(String userAction) {
switch (userAction) {
case "SHOW DIALOG" :
Toast.makeText(getActivity(),"Hello",Toast.LENGTH_LONG).show();
}
the direct answer to your question is a simple subscription pattern.
on the activity you call:
#Override
public void onStart(){
controller.updateListener(this);
}
#Override
public void onStop(){
controller.updateListener(null);
}
and inside the controller check for null before calling anything on the listener.
But there's a fundamental flaw on the logic.
With the following code:
static Controller controller;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
controller = new Controller(this);
}
the static controller having a reference to the activity is leaking the activity, avoiding it to be garbage collected.
also, even thou the controller is static, you're creating a new one every time the activity is created, also inside the controller init() you have the following:
// do some very long running operation
//....
that means this very long running operation is:
running in the UI thread. This will block your app initialisation, the user will think it's broken and the system will probably show a message to the user asking to close it.
there's nothing to guarantee that your process won't be killed either by the user or by the system before the "very long operation" finishes. If you want to run a long operation you MUST user a Service instead.
Very sample, Use WeakReference to activity

Android check if this activity is not null/destroyed

There are a lot of answered questions in Stackoverflow about checking if activity is null from a fragment, using getActivity()==null
How do I check if activity is not null in the activity itself?
My specific case is this:
activity starts an asynctask, then activity is destroyed, then asynctask returns in onPostExecute, it invokes a method in the activity (which is registered as a listener for that task) and this method uses a reference to THIS to pass a context in a method. The context is null, though.
EDIT: Here is some code.
public interface OnGetStuffFromServerListener {
void onGetStuffSuccess();
}
public class SomeActivity implements OnGetStuffFromServerListener {
#Override
public whatever onCreate() {
new GetStuffFromServer(this).execute();
}
#Override
public void onGetStuffFromServerSuccess() {
deleteSomeFiles(this); // NPE -> How do I check if activity still exists here?
}
private void deleteSomeFiles(Context context) {
...
context.getExternalFilesDir(null).toString(); // NPE toString on a null object reference
}
}
public class GetSomeStuffFromServer extends AsyncTask<Void, Void, Void> {
private OnGetSomeStuffFromServerListener listener;
public GetSomeStuffFromServer (OnGetSomeStuffFromServerListener listener) {
this.listener = listener;
}
...doInBackground
onPostExecute() {
if(listener!=null) {
listener.onGetSomeStuffFromServerSuccess();
}
}
}
Actually, if I am using getApplicationContext() instead of this, maybe I will not have a problem at all?
I'm not sure why your Activity is being destroyed. Although you may be able to recreate the Activity using a Bundle. Google's documentation on Activities gives the following sample for saving and restoring an instance of your activity.
The following will save the state of your Activity:
static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
...
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
The following will be called to restore your Activity's previous state. Note that the logic is contained in onCreate(), so it sounds like you will have initialize your Activity again.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
} else {
// Probably initialize members with default values for a new instance
}
...
}
Let me know if that helps!
edit:
Try canceling the operation in onDestroy(). If the Activity has called onDestroy() its memory has been released by the device. Make sure you aren't disposing of your Activity anywhere else in your code.
#Override
protected void onDestroy() {
asynctask.cancel(true);
super.onDestroy();
}
Use myActivity.isDestroyed()
reference to doc

ProgressDialog, activity's onCreate() and AsyncTask

This question has to do with an AsyncTask that needs to kill a ProgressDialog. While the task is running, the activity gets destroyed and recreated (phone rotation, for example). A new dialog gets made with the help of onSaveInstanceState() but the AsyncTask, spawned by the previously destroyed activity, can't see it.
Picture, if you will... (quick mockup code for example's sake).
public class Bob extends Activity {
private ProgressDialog m_d;
public void onCreate(Bundle b) {
m_d = new ProgressDialog(this);
// ...
if (b != null) {
if (b.getBoolean("dialog") == true)
m_d.show();
}
// ...
}
public void onSaveInstanceState(Bundle b) {
super.onSaveInstanceState(b);
b.putBoolean("dialog", (m_d.isShowing()));
}
public void onDestroy() {
if (m_d.isShowing()) {
m_d.dismiss();
m_d = null;
}
//...
}
}
The AsyncTask onPreExecute() does m_d.show() and onPostExecute() does m_d.hide().
The problem is when my activity gets recreated (say, on phone rotation), the AsyncTask seems to have the old m_d. It is null, because that got killed in onDestroy().
It doesn't have the new m_d, created when the activity got recreated. So now I have a ProgressDialog and the guy who was supposed to kill it in onPostExecute() can't see it.
Now what? I need the old AsyncTask to somehow signal the new ProgressDialog to go away.
private OnItemSelectedListener onSpinnerSelection = new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
// ...
new Switcharoo().execute();
}
}
private class Switcharoo extends AsyncTask<Void, Void, Void> {
protected void onPreExecute() {
m_d.show();
// ...
}
protected void onPostExecute() {
if (m_d != null) m_d.hide();
// ...
}
protected Void doInBackground(Void... arg0) {
// ...
}
}
If Android doesn't kill my activity while the task is running, it's fine. The ProgressDialog pops up and goes away like I expect.
If Android decides to restart my activity for any reason while the task is running, m_d will be null in onPostExecute() even though I recreated it in onCreate() - it has the old m_d, not the new one.
There are two problems: the first is that the AsyncTask is bound to the activity that has created it. I mean, the instance of the activity.
If you want it to survive between activities rotations you have to store it somewhere else (check this for example). In any case, you need to have a reference to it from the new activity (another thing you could do is to use a model fragment, i.e. a fragment with no UI that you set as setRetainInstance(true)).
Once you have the reference to the async task from the newly created activity, nothing prevents you to have m_d local to the async task and a setter method that updates it with the new dialog.
Note also that it would be a good practice to have weak references pointing to the activity and the dialog itself in order to allow garbage collection. Otherwise the dialog (and probably the activity itself) would not be freed until the execution of the task itself ended.

How to force main Acivity to wait for subactivity in Android?

I am calling a subactivity from main activity. This subactivity should take few numbers from user (i'm using Edit text control to achieve this), save them to static variable in another class and terminate. I want main activity to wait for subactivity but both are just running simultaneously. Even doing sth like that doesn't help:
Thread t = new Thread(new Runnable(){
public void run(){
Log.v("==================", "run "+new Date());
startActivityForResult(new Intent(ctx,myCustomSubactivity.class),1);
} });
Log.v("==================", "calling run "+new Date());
t.start();
try {
t.join();
} catch (InterruptedException e) {Log.v("==================", "can't join");}
Log.v("==================", "back from activity "+new Date());
do you know how to force main activity to wait? Thread.wait() method is not supported in Android(program throws error).
May be I'm missing something but why don't just use startActivityForResult and onActivityResult mechanism? You could get result from you subactivity from intent it was resulted with.
Edit: BTW as far as I understand, if you will run Object.wait() from Activity code if will hold UI tread whitch can result in Application not responding error.
I agree with Nikolay this is definitely the android way to do this.
Start the subactivity with startActivityForResult in the sub activity use setResult to add an result code and an intent with all the numbers you need in the data bundle.
In your first activity overwrite onActivityResult and retrieve the numbers from the Intent.
If you use the static variable this seems easier in the first moment but it is very insecure and there are some cases this may not work. If your program is send to the background your activities will be saved but if the phone runs low on memory the system will close your program and after the user resumes it everything looks like the moment the user left it but the static variables will be recreated to their initialization value.
Try to get used to the way the android activity lifecycle works. Using this approach will result in fewer used memory and a much better user experience.
Check out the Notepad example, it covers exactly this situation. And as others have said, the Android way is to have your first activity start up your second activity (not sub-activity!) and asynchronously listen for a response (not pause or wait, no need for joining, etc.).
Well... you can do it like this (btw, there's not straight forward way):
Have a singleton class, let's call it Monitor:
public class Singleton
{
private Singleton() { }
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
public class ParentActivity extends Activity
{
private void startAndWait()
{
Intent i = new Intent();
// initialize i
startActivityForResult(i);
Singleton si = Singleton.getInstance();
synchronized(si)
{
si.wait();
}
//do remaining work
}
}
public class ChildActivity extends Activity
{
protected void onCreate(Bundle savedInstance)
{
//do all the work
Singleton si = Singleton.getInstance();
synchronized(si)
{
si.notify();
}
}
}
I'm not here to judge if it's a good pattern or not but if you really need an activity to wait for a sub-activity, you can try this approach:
define an object (lock) over which the two activities get synchronized; this can (should) also work as the object to exchange data between those two activities and thus should be defined as static
in parent activity, start an async task (as the UI main thread cannot be in waiting state)
in the async task, start your sub-activity
the async task waits on the lock till it gets notified
the sub-activity does whatever it needs and notifies the waiting thread when it finishes
I did a similar thing in my app and IMHO had a good reason for this (not to bother a user with login screen upon app start or resume, the app tries to re-use credentials stored in a secured place and only in case it fails, it shows this login screen. So yes, basically any activity in my app can get "paused" and waits till the user provides correct credentials in the login activity upon which the login screen finishes and the app continues exactly where it got paused (in the parent activity).
In the code it would be something like this:
ParentActivity:
public class ParentActivity extends Activity {
private static final String TAG = ParentActivity.class.getSimpleName();
public static class Lock {
private boolean condition;
public boolean conditionMet() {
return condition;
}
public void setCondition(boolean condition) {
this.condition = condition;
}
}
public static final Lock LOCK = new Lock();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.parent_layout);
// do whatever logic you need and anytime you need to stat sub-activity
new ParentAsyncTask().execute(false);
}
private class ParentAsyncTask extends AsyncTask<Boolean, Void, Boolean> {
#Override
protected Boolean doInBackground(Boolean... params) {
// do what you need and if you decide to stop this activity and wait for the sub-activity, do this
Intent i = new Intent(ParentActivity.this, ChildActivity.class);
startActivity(i);
synchronized (LOCK) {
while (!LOCK.conditionMet()) {
try {
LOCK.wait();
} catch (InterruptedException e) {
Log.e(TAG, "Exception when waiting for condition", e);
return false;
}
}
}
return true;
}
}
}
ChildActivity:
public class ChildActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.child_layout);
// do whatever you need in child activity, but once you want to finish, do this and continue in parent activity
synchronized (ParentActivity.LOCK) {
ParentActivity.LOCK.setCondition(true);
ParentActivity.LOCK.notifyAll();
}
finish();
// if you need the stuff to run in background, use AsyncTask again, just please note that you need to
// start the async task using executeOnExecutor method as you need more executors (one is already occupied), like this:
// new ChildAsyncTask().executeOnExecutor(ChildAsyncTask.THREAD_POOL_EXECUTOR, false);
}
}

Android AsyncTask context behavior

I've been working with AsyncTasks in Android and I am dealing with an issue.
Take a simple example, an Activity with one AsyncTask. The task on the background does not do anything spectacular, it just sleeps for 8 seconds.
At the end of the AsyncTask in the onPostExecute() method I am just setting a button visibility status to View.VISIBLE, only to verify my results.
Now, this works great until the user decides to change his phones orientation while the AsyncTask is working (within the 8 second sleep window).
I understand the Android activity life cycle and I know the activity gets destroyed and recreated.
This is where the problem comes in. The AsyncTask is referring to a button and apparently holds a reference to the context that started the AsyncTask in the first place.
I would expect, that this old context (since the user caused an orientation change) to either become null and the AsyncTask to throw an NPE for the reference to the button it is trying to make visible.
Instead, no NPE is thrown, the AsyncTask thinks that the button reference is not null, sets it to visible. The result? Nothing is happening on the screen!
Update: I have tackled this by keeping a WeakReference to the activity and switching when a configuration change happens. This is cumbersome.
Here's the code:
public class Main extends Activity {
private Button mButton = null;
private Button mTestButton = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mButton = (Button) findViewById(R.id.btnStart);
mButton.setOnClickListener(new OnClickListener () {
#Override
public void onClick(View v) {
new taskDoSomething().execute(0l);
}
});
mTestButton = (Button) findViewById(R.id.btnTest);
}
private class TaskDoSomething extends AsyncTask<Long, Integer, Integer>
{
#Override
protected Integer doInBackground(Long... params) {
Log.i("LOGGER", "Starting...");
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 0;
}
#Override
protected void onPostExecute(Integer result) {
Log.i("LOGGER", "...Done");
mTestButton.setVisibility(View.VISIBLE);
}
}
}
Try executing it and while the AsyncTask is working change your phones orientation.
AsyncTask is not designed to be reused once an Activity has been torn down and restarted. The internal Handler object becomes stale, just like you stated. In the Shelves example by Romain Guy, he simple cancels any currently running AsyncTask's and then restarts new ones post-orientation change.
It is possible to hand off your Thread to the new Activity, but it adds a lot of plumbing. There is no generally agreed on way to do this, but you can read about my method here : http://foo.jasonhudgins.com/2010/03/simple-progressbar-tutorial.html
If you only need a context and won't use it for ui stuff you can simply pass the ApplicationContext to your AsyncTask.You often need the context for system resources, for example.
Don't try to update the UI from an AsyncTask and try to avoid handling configuration changes yourself as it can get messy. In order to update the UI you could register a Broadcast receiver and send a Broadcast.
You should also have the AsyncTask as a separate public class from the activity as mentioned above, it makes testing a lot easier. Unfortunately Android programming often reinforces bad practices and the official examples are not helping.
This is the type of thing that leads me to always prevent my Activity from being destroyed/recreated on orientation change.
To do so add this to your <Activity> tag in your manifest file:
android:configChanges="orientation|keyboardHidden"
And override onConfigurationChanged in your Activity class:
#Override
public void onConfigurationChanged(final Configuration newConfig)
{
// Ignore orientation change to keep activity from restarting
super.onConfigurationChanged(newConfig);
}
To avoid this you can use the answer givin here: https://stackoverflow.com/a/2124731/327011
But if you need to destroy the activity (different layouts for portrait and landscape) you can make the AsyncTask a public class (Read here why it shouldn't be private Android: AsyncTask recommendations: private class or public class?) and then create a method setActivity to set the reference to the current activity whenever it is destroyed/created.
You can see an example here: Android AsyncTask in external class

Categories

Resources