android - How to handle Handler messages when activity/fragment is paused

android - How to handle Handler messages when activity/fragment is paused

Stefana Dominik Author: Stefana Dominik Date: 2022-08-16
android - How to handle Handler messages when activity/fragment is paused

All you need to know about android - How to handle Handler messages when activity/fragment is paused , in addintion to android - How to delete delayed messages before they arrive at a Handler? , push notification - How to handle Android FCM Messages when application is in Background with data payload? , android - How to handle AsyncTask onPostExecute when paused to avoid IllegalStateException , How to cancel a handler before time in Android code?

  1. android - How to handle Handler messages when activity/fragment is paused
  2. Question:

    Slight variation on my other posting

    Basically I have a message Handler in my Fragment which receives a bunch of messages that can result in dialogs being dismissed or shown.

    When the app is put into the background I get an onPause but then still get my messages coming through as one would expect. However, because I'm using fragments I can't just dismiss and show dialogs as that will result in an IllegalStateException.

    I can't just dismiss or cancel allowing state loss.

    Given that I have a Handler I'm wondering whether there is a recommended approach as to how I should handle messages while in a paused state.

    One possible solution I'm considering is to record the messages coming through while paused and play them back on an onResume. This is somewhat unsatisfactory and I'm thinking that there must be something in the framework to handle this more elegantly.


    Solution 1:

    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.

    Solution 2:

    A slightly simpler version of quickdraw's excellent PauseHandler is

    /**
     * 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
         */
        private final List<Message> messageQueueBuffer = Collections.synchronizedList(new ArrayList<Message>());
    
        /**
         * Flag indicating the pause state
         */
        private Activity activity;
    
        /**
         * Resume the handler.
         */
        public final synchronized void resume(Activity activity) {
            this.activity = activity;
    
            while (messageQueueBuffer.size() > 0) {
                final Message msg = messageQueueBuffer.get(0);
                messageQueueBuffer.remove(0);
                sendMessage(msg);
            }
        }
    
        /**
         * Pause the handler.
         */
        public final synchronized void pause() {
            activity = null;
        }
    
        /**
         * Store the message if we have been paused, otherwise handle it now.
         *
         * @param msg   Message to handle.
         */
        @Override
        public final synchronized void handleMessage(Message msg) {
            if (activity == null) {
                final Message msgCopy = new Message();
                msgCopy.copyFrom(msg);
                messageQueueBuffer.add(msgCopy);
            } else {
                processMessage(activity, msg);
            }
        }
    
        /**
         * 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 activity  Activity owning this Handler that isn't currently paused.
         * @param message   Message to be handled
         */
        protected abstract void processMessage(Activity activity, Message message);
    
    }
    

    It does assume that you always want to store offline messages for replay. And provides the Activity as input to #processMessages so you don't need to manage it in the sub class.

    Solution 3:

    Here is a slightly different way to approach the problem of doing Fragment commits in a callback function and avoiding the IllegalStateException issue.

    First create a custom runnable interface.

    public interface MyRunnable {
        void run(AppCompatActivity context);
    }
    

    Next, create a fragment for processing the MyRunnable objects. If the MyRunnable object was created after the Activity was paused, for e.g. if the screen is rotated, or the user presses the home button, it is put in a queue for later processing with a new context. The queue survives any configuration changes because setRetain instance is set to true. The method runProtected runs on UI thread to avoid a race condition with the isPaused flag.

    public class PauseHandlerFragment extends Fragment {
    
        private AppCompatActivity context;
        private boolean isPaused = true;
        private Vector<MyRunnable> buffer = new Vector<>();
    
        @Override
        public void onAttach(Context context) {
            super.onAttach(context);
            this.context = (AppCompatActivity)context;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);
        }
    
        @Override
        public void onPause() {
            isPaused = true;
            super.onPause();
        }
    
        @Override
        public void onResume() {
            isPaused = false;
            playback();
            super.onResume();
        }
    
        private void playback() {
            while (buffer.size() > 0) {
                final MyRunnable runnable = buffer.elementAt(0);
                buffer.removeElementAt(0);
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        //execute run block, providing new context, incase 
                        //Android re-creates the parent activity
                        runnable.run(context);
                    }
                });
            }
        }
        public final void runProtected(final MyRunnable runnable) {
            context.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if(isPaused) {
                        buffer.add(runnable);
                    } else {
                        runnable.run(context);
                    }
                }
            });
        }
    }
    

    Finally, the fragment may be used in a main application as follows:

    public class SomeActivity extends AppCompatActivity implements SomeListener {
        PauseHandlerFragment mPauseHandlerFragment;
    
        static class Storyboard {
            public static String PAUSE_HANDLER_FRAGMENT_TAG = "phft";
        }
    
        protected void onCreate(Bundle savedInstanceState) {
    
            super.onCreate(savedInstanceState);
    
            ...
    
            //register pause handler 
            FragmentManager fm = getSupportFragmentManager();
            mPauseHandlerFragment = (PauseHandlerFragment) fm.
                findFragmentByTag(Storyboard.PAUSE_HANDLER_FRAGMENT_TAG);
            if(mPauseHandlerFragment == null) {
                mPauseHandlerFragment = new PauseHandlerFragment();
                fm.beginTransaction()
                    .add(mPauseHandlerFragment, Storyboard.PAUSE_HANDLER_FRAGMENT_TAG)
                    .commit();
            }
    
        }
    
        // part of SomeListener interface
        public void OnCallback(final String data) {
            mPauseHandlerFragment.runProtected(new MyRunnable() {
                @Override
                public void run(AppCompatActivity context) {
                    //this block of code should be protected from IllegalStateException
                    FragmentManager fm = context.getSupportFragmentManager();
                    ...
                }
             });
        }
    }
    

  3. android - How to delete delayed messages before they arrive at a Handler?
  4. Question:

    My Problem is that I need to send messages with a delay of 1 second. The handler then initiates some action, you're getting the picture.

    There are nevertheless some conditions in which the already sent message should be deleted ( before the second elapsed ) to prevent the handler from doing anything. I couldn't figure out how to do this ( or if it's even possible ), so If anyone of you has a clue, please let me know..


    Solution 1:

    There is nothing scary about the removeMessages() methods; they are perfectly safe. The framework relies heavily on these methods and they are used in many many places, especially in the default widgets (View, ListView, etc.) It's much much better than building a Handler that ignores specific messages. This is programming, don't go with your feelings :p

    Solution 2:

    Many developers and much of the source code you'll find will show people passing anonymous functions to a handler, so I think in some cases you may be unsure how to remove these. A simple solution is to declare your runnable as you would any other object, and keep a pointer to it which can be used to clear any instance of it from the Handler queue.

    private Runnable lastMyRunnablePtr = null;
    

    ...

    private class MyRunnable implements Runnable
    {}
    

    ....

    lastMyRunnablePtr = new MyRunnable();
    mHandler.postDelayed(lastMyRunnablePtr ,30000);
    

    ....

    protected void onDestroy() {
      mHandler.removeCallbacks(lastMyRunnablePtr);
    }
    

    Solution 3:

    Actually, you should consider the implementation of handler.removeMessages(int, obj). If the obj is an object related to autoboxing, you will meet a problem of the implementation of android MessageQueue.

    For following code snippet, the removeMessages won't work as a result of auto-boxing, boxing-conversion and the implemetation of MessageQueue using p.obj == object to compare object.

    Message msg = handler.obtainMessage(what, 256);
    handler.sendMessageDelayed(message, delayMillis);
    handler.removeMessages(what, 256);
    

    Ref this post.

  5. push notification - How to handle Android FCM Messages when application is in Background with data payload?
  6. Question:

    Android push notification using FCM is unable to handle when application in background. Default message is displayed in notification tray.

    Can Anyone help me how to handle the messages when application in background.

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);
    

    }

    This method is not called when application is in background. Any help will be great help for me.


    Solution 1:

    The onMessageReceived() will be always called if you receive the payload as "data" always from server.

    Example : Payload from server should be like following :

    {
     "data":{
     "id": 1,
     "missedRequests": 5
     "addAnyDataHere": 123
     },
     "to":"fhiT7evmZk8:APA91bFJq7Tkly4BtLRXdYvqHno2vHCRkzpJT8QZy0TlIGs......"
    }
    

    for more about this issue. Please refer following URL : Firebase onMessageReceived not called when app in background

  7. android - How to handle AsyncTask onPostExecute when paused to avoid IllegalStateException
  8. Question:

    I appreciate the numerous postings regarding AsyncTask on a rotation change. I have the following problem when using the compatability lib and trying to dismiss a DialogFragment in onPostExecute.

    I have a fragment which fires of an AsyncTask which displays a progress DialogFragment, then in onPostExecute dismisses the dialog and then potentially throws up another DialogFragment.

    If when the progress dialog is being displayed I put the application into the background I get the following for my fragment:

    1) onPause

    2) onSaveInstanceState

    3) onPostExecute in which I try to dismiss and invoke a dialog.

    I get an IllegalStateException because I'm trying to effectively commit a transaction when the activity has saved its state and I understand this.

    On a rotation I've assumed (perhaps incorrectly) that I wouldn't get an onPostExecute until the activity has been recreated. However, when putting the application into the background I assumed (definitely incorrectly) that the onPostExectute wouldn't get called while the fragment/activity was paused.

    My question is, is my solution to simply detect in onPostExecute that the fragment/activity is paused and simply perform what I need to do in onResume instead? Seems somewhat ugly to me.

    Thanks in advance, peter.

    Edit 1

    Need to support 2.1 and above

    Edit 2

    I have considered showing the dialog using FragmentTransaction:add and FragmentTransaction:commitAllowingStateLosshowever this isn't without its problems.


    Solution 1:

    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.

    Solution 2:

    Another way of achieving what you require is to implement the PauseHandler class that I documented in this post.

    Then in your onPostExecute method call sendMessage() to post your message into the handler.

    When your application resumes the action will be handled.

    Solution 3:

    Rather then using BroadcastReceiver, I prefer using bus libraries like guava, otto or eventbus. Their performance is much better then the broadcast receiver implementation.

  9. How to cancel a handler before time in Android code?
  10. Question:

    I create 1 minute delayed timer to shutdown service if it's not completed. Looks like this:

    private Handler timeoutHandler = new Handler();
    

    inside onCreate()

    timeoutHandler.postDelayed(new Runnable()
            {
                public void run()
                {
                    Log.d(LOG_TAG, "timeoutHandler:run");
    
                    DBLog.InsertMessage(getApplicationContext(), "Unable to get fix in 1 minute");
                    finalizeService();
                }
            }, 60 * 1000);
    

    If I get job accomplished before this 1 minute - I would like to get this delayed thing cancelled but not sure how.


    Solution 1:

    You can't really do it with an anonymous Runnable. How about saving the Runnable to a named variable?

    Runnable finalizer = new Runnable()
        {
            public void run()
            {
                Log.d(LOG_TAG, "timeoutHandler:run");
    
                DBLog.InsertMessage(getApplicationContext(), "Unable to get fix in 1 minute");
                finalizeService();
            }
        };
    timeoutHandler.postDelayed(finalizer, 60 * 1000);
    
    ...
    
    // Cancel the runnable
    timeoutHandler.removeCallbacks(finalizer);
    

    Solution 2:

    If you don't want to keep a reference of the runnable, you could simply call:

    timeoutHandler.removeCallbacksAndMessages(null);
    

    The official documentation says:

    ... If token is null, all callbacks and messages will be removed.

    Solution 3:

    You might want to replace use of postDelayed with use of sendMessageDelayed like so:

    private Handler timeoutHandler = new Handler(){
        @Override
        public void handleMessage(Message msg)
        {
                switch (msg.what){
            case 1:
                ((Runnable)msg.obj).run();
                break;
            }
        }
    };
    

    Then post a Message:

    Message m = Message.obtain();
    m.what = 1;
    m.obj = new Runnable(){
                public void run()
                {
                    Log.d(LOG_TAG, "timeoutHandler:run");
    
                    DBLog.InsertMessage(getApplicationContext(), "Unable to get fix in 1 minute");
                    finalizeService();
                }
            };
    timeoutHandler.sendMessageDelayed(m, 60 * 1000);
    

    and then cancel:

    timeoutHandler.removeMessages(1);
    

    No tracking of the runnable necessary.