How to Handle Handler Messages When Activity/Fragment Is Paused

How to handle Handler messages when activity/fragment is paused

Although the Android operating system does not appear to have a mechanism that sufficiently addresses your problem I believe this pattern does provide a relatively simple to implement workaround.

The following class is a wrapper around android.os.Handler that buffers up messages when an activity is paused and plays them back on resume.

Ensure any code that you have which asynchronously changes a fragment state (e.g. commit, dismiss) is only called from a message in the handler.

Derive your handler from the PauseHandler class.

Whenever your activity receives an onPause() call PauseHandler.pause() and for onResume() call PauseHandler.resume().

Replace your implementation of the Handler handleMessage() with processMessage().

Provide a simple implementation of storeMessage() which always returns true.

/**
* Message Handler class that supports buffering up of messages when the
* activity is paused i.e. in the background.
*/
public abstract class PauseHandler extends Handler {

/**
* Message Queue Buffer
*/
final Vector<Message> messageQueueBuffer = new Vector<Message>();

/**
* Flag indicating the pause state
*/
private boolean paused;

/**
* Resume the handler
*/
final public void resume() {
paused = false;

while (messageQueueBuffer.size() > 0) {
final Message msg = messageQueueBuffer.elementAt(0);
messageQueueBuffer.removeElementAt(0);
sendMessage(msg);
}
}

/**
* Pause the handler
*/
final public void pause() {
paused = true;
}

/**
* Notification that the message is about to be stored as the activity is
* paused. If not handled the message will be saved and replayed when the
* activity resumes.
*
* @param message
* the message which optional can be handled
* @return true if the message is to be stored
*/
protected abstract boolean storeMessage(Message message);

/**
* Notification message to be processed. This will either be directly from
* handleMessage or played back from a saved message when the activity was
* paused.
*
* @param message
* the message to be handled
*/
protected abstract void processMessage(Message message);

/** {@inheritDoc} */
@Override
final public void handleMessage(Message msg) {
if (paused) {
if (storeMessage(msg)) {
Message msgCopy = new Message();
msgCopy.copyFrom(msg);
messageQueueBuffer.add(msgCopy);
}
} else {
processMessage(msg);
}
}
}

Below is a simple example of how the PausedHandler class can be used.

On the click of a button a delayed message is sent to the handler.

When the handler receives the message (on the UI thread) it displays a DialogFragment.

If the PausedHandler class was not being used an IllegalStateException would be shown if the home button was pressed after pressing the test button to launch the dialog.

public class FragmentTestActivity extends Activity {

/**
* Used for "what" parameter to handler messages
*/
final static int MSG_WHAT = ('F' << 16) + ('T' << 8) + 'A';
final static int MSG_SHOW_DIALOG = 1;

int value = 1;

final static class State extends Fragment {

static final String TAG = "State";
/**
* Handler for this activity
*/
public ConcreteTestHandler handler = new ConcreteTestHandler();

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}

@Override
public void onResume() {
super.onResume();

handler.setActivity(getActivity());
handler.resume();
}

@Override
public void onPause() {
super.onPause();

handler.pause();
}

public void onDestroy() {
super.onDestroy();
handler.setActivity(null);
}
}

/**
* 2 second delay
*/
final static int DELAY = 2000;

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

if (savedInstanceState == null) {
final Fragment state = new State();
final FragmentManager fm = getFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
ft.add(state, State.TAG);
ft.commit();
}

final Button button = (Button) findViewById(R.id.popup);

button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

final FragmentManager fm = getFragmentManager();
State fragment = (State) fm.findFragmentByTag(State.TAG);
if (fragment != null) {
// Send a message with a delay onto the message looper
fragment.handler.sendMessageDelayed(
fragment.handler.obtainMessage(MSG_WHAT, MSG_SHOW_DIALOG, value++),
DELAY);
}
}
});
}

public void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
}

/**
* Simple test dialog fragment
*/
public static class TestDialog extends DialogFragment {

int value;

/**
* Fragment Tag
*/
final static String TAG = "TestDialog";

public TestDialog() {
}

public TestDialog(int value) {
this.value = value;
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View inflatedView = inflater.inflate(R.layout.dialog, container, false);
TextView text = (TextView) inflatedView.findViewById(R.id.count);
text.setText(getString(R.string.count, value));
return inflatedView;
}
}

/**
* Message Handler class that supports buffering up of messages when the
* activity is paused i.e. in the background.
*/
static class ConcreteTestHandler extends PauseHandler {

/**
* Activity instance
*/
protected Activity activity;

/**
* Set the activity associated with the handler
*
* @param activity
* the activity to set
*/
final void setActivity(Activity activity) {
this.activity = activity;
}

@Override
final protected boolean storeMessage(Message message) {
// All messages are stored by default
return true;
};

@Override
final protected void processMessage(Message msg) {

final Activity activity = this.activity;
if (activity != null) {
switch (msg.what) {

case MSG_WHAT:
switch (msg.arg1) {
case MSG_SHOW_DIALOG:
final FragmentManager fm = activity.getFragmentManager();
final TestDialog dialog = new TestDialog(msg.arg2);

// We are on the UI thread so display the dialog
// fragment
dialog.show(fm, TestDialog.TAG);
break;
}
break;
}
}
}
}
}

I've added a storeMessage() method to the PausedHandler class in case any messages should be processed immediately even when the activity is paused. If a message is handled then false should be returned and the message will be discarded.

How to handle AsyncTask onPostExecute when paused to avoid IllegalStateException

If you need to synchronize your task with the activity lifecycle, I believe that Loaders are exactly what you need. More specifically, you should use AsyncTaskLoader to do the job. So now instead of running an AsyncTask, you launch your loader, then wait for response in a listener. If the activity is paused, you won't get a callback, this part will be managed for you.

There is another way to handle this task: using a fragment which retains its instance. The general idea is that you create a fragment without UI and call setRetainInstance(true). It has a task which is being notified about the activity being available or not. If not, the task's thread suspends until an activity becomes available.

Stop Handler when Fragment is destroyed

I don't think it has to do with your fragment lifecycle. You need to replace

handler.removeCallbacksAndMessages(runnableCode);
// this does not seem to work

with

handler.removeCallbacks(runnableCode);

Just an alternative create a method in FeedsFragment

public void removeHandler()
{
Log.i("Stop Handler ","Yes");
handler.removeCallbacks(runnableCode);
}

In activity when you want to replace fragment

if(feedsFragment.isAdded())
{
feedsFragment.removeHandler();
}
...// rest of the code to replace the fragment with new one

Edit:

viewPager.addOnPageChangeListener(pageChangeListener);
viewPager.post(new Runnable() {
@Override
public void run() {
pageChangeListener.onPageSelected(0);
}
});

Then

    ViewPager.OnPageChangeListener pageChangeListener = new ViewPager.OnPageChangeListener() {

int currentPosition = 0;

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {


}

@Override
public void onPageSelected(int newPosition) {


FragmentLifeCycle fragmentToShow = (FragmentLifeCycle) adapter.getItem(newPosition);
fragmentToShow.onResumeFragment();

if(newPosition==0)
{
currentPosition = newPosition;
}else {
FragmentLifeCycle fragmentToHide = (FragmentLifeCycle) adapter.getItem(currentPosition);
fragmentToHide.onPauseFragment();

currentPosition = newPosition;
}
}

Have a interface

 public interface FragmentLifeCycle {

void onPauseFragment();
void onResumeFragment();

}

Implement the interface in all fragments. Assuming the handler is used in Fragment at position 0.

@Override
public void onPauseFragment() {

if(handler!=null)
handler.removeCallbacks(runnableCode);
}

@Override
public void onResumeFragment() {
handler = new Handler();

// Define the task to be run here
runnableCode = new Runnable() {
@Override
public void run() {

Log.i("Do ","Something");
handler.postDelayed(runnableCode, 3000);
}
};

handler.post(runnableCode);
}

In other fragment just implement the interface and do nothing.

Recyclerview in fragment. Not updating after Activity has been closed

Ok I found the problem. It had something to do with my runnables and handlers not being declared within onCreate. Somehow that messed it up. I made a major reconstruction of my code to solve it so its hard to tell which line exactly was the problem but if you are facing a similar problem check that your runOnUi and handler declarations happen in the right places.

DialogFrag#show() from a Fragment throwing IllegalStateException: Can not perform this action after onSaveInstanceState

OK, as far as I can tell this is a bug in the AOSP as I have also seen an instance of this from pure Android stack (ie nothing of my code at all).

So it looks like there is a threading issue in the Activity/Fragment lifecycle in which a UI thread can get priority to respond to a button click AFTER the Activity/Fragment has already saved it's state.

My work around which has been 100% successful so far is to catch the IllegalStateException and schedule the dialog show for the next time the Activity/Fragment becomes active using a PauseHandler https://stackoverflow.com/a/25322330/493682



Related Topics



Leave a reply



Submit