Search This Blog

Sunday, December 20, 2009

Watch out your background thread!

The other day i came across something that i hadn't known before , do you
know that when you flip your android phone and go from portrait mode to landscape or vice versa, whatever application is running will be killed by android and recreated again?
It wouldn't be a problem as long as lifeCycle methods of your activity are implemented well, but what if you have a seperate thread which is working behind the scene and interact with your activity through a Handler, like what I have used in both WhitePage and YahooSearch applications. what happens? if you switch the screen mode while our Thread is working the activity is killed, but our Thread is abviously not notified about what has happend, so it will send the message through the old handler to the old activity which might be waiting for Garbage Collection or whatever else but it is not the activity which you wanted to send your message to.
so I've come up with this solution, I override onSaveInstanceState() method of activity class, it checks whether there is any unfinished task running, if so it locks the thread and save it, we also need to check if there is any saved thread in onCreate() method and unblock it with a new Handler class so that Messages will be delivered through the right Handler.(NOTE : we can also use onRetainNonConfigurationInstance() and getLastNonConfigurationInstance() methods which is apparently a better approach to solve this particular problem. ***Please see comments on this Post*** )




@Override
public void onCreate(Bundle savedInstanceState) {
.
.
.

if(savedInstanceState != null){
this.thread = (FetcherThread)savedInstanceState.getSerializable("thread");
this.thread.unlockIt(handler);
}
else{
this.thread = new FetcherThread(handler);
this.thread.start();
}

.
.
.

}

@Override
public void onSaveInstanceState(Bundle bundle){
super.onSaveInstanceState(bundle);
if(!this.thread.lastRequest_finished)
this.thread.lockIt();

bundle.putSerializable("thread", this.thread);
}





On the other side, in our Thread I added a new flag which is checked just before the thread wants to send a message back, if everything is alright it carries on and send a message, otherwise it will wait until it's notified that it is safe to send a message.




public void run(){

.
.
.

Bundle content = new Bundle();
content.putSerializable("result",results);
Message msg = new Message();
msg.setData(content);


while(!this.launcherReady){

try{
synchronized (this) {
this.wait();
}
}catch(InterruptedException exp){
////Just Nothing
}
}

this.callback.sendMessage(msg);

.
.
.

}



public synchronized void lockIt(){
this.launcherReady = false;
}

public synchronized void unlockIt(Handler newOne){
this.launcherReady = true;
if(newOne != null)
this.callback = newOne;
this.notifyAll();
}





you can also use Android AsyncTask class when you need a background task, which is a neat way to do it (if you will) , but be aware of the fact that it does not support the issue we discussed here by default. so you will still need to somehow handle it by yourself.

8 comments:

Anonymous said...

Hi. We had a similar problem once. It is quite common i think.

Your solution sounds valid although it does have some problems. In your scenario you have the inner loop in the run which polls and also your activity has to actively know your thread.

A more elegant way would be to use some kind of MessageDispatcher, which acts as a broker to your async messages. The dispatcher would pipe the messages to the currently active activity or wait until notified of a newly created activity to start dispatching (beware of activty leckage).

Unknown said...

There is a method specifically for this type of thing called onRetainNonConfigurationInstance. It is used to restore arbitrary objects across configuration change events, recoverable in onCreate. For a complete working example of how to use this API, see this code currently in use in my project:

http://github.com/jasta/five-android/blob/master/src/org/devtcg/five/activity/SourceCheckSettings.java

More examples of this technique can be found in the Android source tree. This approach should be used for any complex objects that either require significant initialization or are inherently not serializable (like Threads).

G said...

Couldn't you just override OnConfigurationChanged (and pickup the config changes in your manifest) so your app doesn't get restarted from scratch?

Unknown said...

@G, Overriding onConfigurationChanged has a different impact. You would need to declare android:configChanges="orientation" in your manifest, which would mean that some functionality won't be available to you. In particular, if you had taken advantage of res/*-land or port folders, those resources would not be automatically reflected. So you'd have to call setContentView yourself and manage reinitialization manually, adding a lot of complexity to your activity.

In some special circumstances, this is the right thing to do for performance reasons, but is rarely a good idea if you're just trying to avoid handling the onDestroy/onCreate events happening during configuration changes.

See my earlier comment regarding onRetainNonConfigurationInstance for a more elegant solution than the one described in this blog.

Moritz Post said...

You could also set android:configChanges="orientation|keyboardHidden" on your activity in your manifest. That would simply not create a new activity when changing orientation.

@ Josh

Interesting approach to use onRetainNonConfigurationInstance. In the javadocs it states that it can't be relied upon to be called. So there is some overhead when designing a general reload mechanism of the resources you want to retain in case you would need to reload them. Of course such mechanism should be in place for general app suspension like low memory.

G said...

I didn't think about the landscape/portrait specific resources cause I never use them, so good call there.

However, if you're using universal layouts and resources theres something I've been wanting to try but haven't had the time to implement yet (but in theory would result in a very nice performance boost). If anyone wants to try this, It'd be great if you could report back if it works...

So if your manifest has android:configChanges="orientation|keyboardHidden" and you override onConfigurationChanges, then global variables are not lost when switching orientations, so my idea would go as follows...

public class MyActivity extends Activity {
private LinearLayout mRootLayout;

@Overrides
public void onCreate(Bundle bundle) {
super.onCreate(bundle);

setContentView(R.layout.my_view);

mRootLayout = (LinearLayout)findViewById(R.id.root_layout_of_my_view);

//Do more stuff here
}

@Overrides
public void onConfigurationChanged(Configuration config) {
setContentView(mRootLayout);

super.onConfigurationChanged(config);
}
}

I've posted an easier-to-read version of this at pastebin
http://pastebin.com/f8f3f7f3

With this method, the xml of your layout should only be inflated once when the activity is created. Then the view itself should be stored in the mRootLayout object and set again from there.

In theory, this should mean that and other global views you have declared (along with their assigned listeners) "should" still work without having to be re-initiated (since you're not inflating the layout again).

But again, I have NOT tested this, it's just an idea i thought of yesterday.

Unknown said...

@Moritz,
onRetainNonConfigurationInstance is specifically designed for passing state to the next instance of yourself only on configuration change (orientation is being discussed here). It is the only officially supported way to pass complex state like Threads through to the next instance of your activity after the config change is complete. The documentation is saying that it cannot be relied upon because any number of events can cause the onRetain.../onDestroy/onCreate sequence of events to be broken: for instance, if the config change event occurred while the activity was paused and then later the process is killed before resuming. In this case, there's no hope of saving the thread anyway.

It is not used as a way to save persistent across instances of the application, for instance if the user pressing Back and then later returns. For that, the standard model of saving state in onPause or onStop is ideal, but of course you can't save threads as state in this way, that's absurd.

P.S. I'm hoping in light of this, the author of this blog significantly revises his post, or retracts it. His approach has serious and fundamental flaws and does not represent Android best practices.

Amir said...

Hi everyone
There are some points about this post that i want to illustrate:

1- The main purpose of this post (and this whole weblog) is to share some ideas about how things
can be done in android and not to provide best possible codes(and personally I think even talking about best approach in programming is ridiculous, you might talk about a better way but talking about the best way come from either lack of experience or some unreasonable bias).

2- I admit that the provided piece of code here is poor and I should have spent more time to make it more professional and i might do that later. but I still believe suggested idea is a simple, easy and fast way to deal with the problem in some similar circumstances.

3- I'd love to hear about my flaws and weaknesses and actually that's why I've decided to start this weblog, to learn more about android and find out how things can be done in mobile phones environment, but please give your reasons why you think something is wrong or has some "Fundamental Flaws". It will help both me and others to improve our knowledge.

4- I quickly went over Activity class documentation this morning and I found this piece of document interesting, it has the answer to some of the questions that have come up here so far:

Configuration Changes
If the configuration of the device (as defined by the Resources.Configuration class) changes, then anything displaying a user interface will need to update to match that configuration. Because Activity is the primary mechanism for interacting with the user, it includes special support for handling configuration changes.
Unless you specify otherwise, a configuration change (such as a change in screen orientation, language, input devices, etc) will cause your current activity to be destroyed, going through the normal activity lifecycle process of onPause(), onStop(), and onDestroy() as appropriate. If the activity had been in the foreground or visible to the user, once onDestroy() is called in that instance then a new instance of the activity will be created, with whatever savedInstanceState the previous instance had generated from onSaveInstanceState(Bundle).
This is done because any application resource, including layout files, can change based on any configuration value. Thus the only safe way to handle a configuration change is to re-retrieve all resources, including layouts, drawables, and strings. Because activities must already know how to save their state and re-create themselves from that state, this is a convenient way to have an activity restart itself with a new configuration.
In some special cases, you may want to bypass restarting of your activity based on one or more types of configuration changes. This is done with the android:configChanges attribute in its manifest. For any types of configuration changes you say that you handle there, you will receive a call to your current activity's onConfigurationChanged(Configuration) method instead of being restarted. If a configuration change involves any that you do not handle, however, the activity will still be restarted and onConfigurationChanged(Configuration) will not be called.