Как обрабатывать сообщения обработчика при приостановке активности / фрагмента
небольшое изменение на моей другой публикации
в основном у меня есть сообщение Handler в своем Fragment который получает кучу сообщений, которые могут привести к тому, что диалоги будут отклонены или показаны.
когда приложение помещается в фоновом режиме я получаю onPause но тогда все равно получите мои сообщения, как и следовало ожидать. Однако, поскольку я использую фрагменты, я не могу просто отклонить и показать диалоги, поскольку это приведет к IllegalStateException.
Я не могу просто уволить или отменить разрешение потери состояния.
учитывая, что у меня есть Handler Мне интересно, есть ли рекомендуемый подход к
как я должен обрабатывать сообщения, находясь в приостановленном состоянии.
одним из возможных решений, которые я рассматриваю, является запись сообщений, поступающих во время паузы, и воспроизведение их на onResume. Это несколько неудовлетворительно, и я думаю, что должно быть что-то в рамках, чтобы справиться с этим более элегантно.
4 ответов:
хотя операционная система Android, похоже, не имеет механизма, который достаточно решает вашу проблему, я считаю, что этот шаблон обеспечивает относительно простой в реализации обходной путь.
следующий класс является оболочкой вокруг
android.os.Handlerэто буферизует сообщения, когда действие приостановлено и воспроизводит их при возобновлении.убедитесь, что любой код, который асинхронно изменяет состояние фрагмента (например, commit, dismiss), вызывается только из a сообщение в обработчике.
вывести свой обработчик из тега
PauseHandlerкласса.всякий раз, когда ваша деятельность получает
onPause()вызовPauseHandler.pause()иonResume()вызовPauseHandler.resume().замените вашу реализацию обработчика
handleMessage()СprocessMessage().обеспечить простую реализацию
storeMessage()которая всегда возвращает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); } } }Ниже приведен простой пример того, как
PausedHandlerкласс может быть использован.On при нажатии кнопки обработчику отправляется задержанное сообщение.
когда обработчик получает сообщение (в потоке пользовательского интерфейса) отображается
DialogFragment.если
PausedHandlerкласс не используется исключение IllegalStateException будет показано, если кнопка home была нажата после нажатия кнопки test для запуска диалогового окна.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; } } } } }я добавил
storeMessage()методPausedHandlerкласс в случае, если любые сообщения должны быть обработаны немедленно, даже если деятельность приостановлена. Если сообщение обрабатывается, то false должен быть возвращен, и сообщение будет отброшено.
немного более простая версия превосходного PauseHandler quickdraw-это
/** * 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); }предполагается, что вы всегда хотите хранить автономные сообщения для воспроизведения. И обеспечивает активность в качестве входных данных для
#processMessagesпоэтому вам не нужно управлять им в подклассе.
вот немного другой способ подойти к проблеме выполнения фиксации фрагмента в функции обратного вызова и избежать проблемы IllegalStateException.
сначала создайте пользовательский интерфейс для запуска.
public interface MyRunnable { void run(AppCompatActivity context); }затем создайте фрагмент для обработки MyRunnable объектов. Если объект MyRunnable был создан после того, как действие было приостановлено, например, если экран вращается, или пользователь нажимает кнопку home, он помещается в очередь для последующей обработки с помощью новый контекст. Очередь переживает любые изменения конфигурации, поскольку для экземпляра setRetain установлено значение true. Метод runProtected выполняется в потоке пользовательского интерфейса, чтобы избежать состояния гонки с флагом isPaused.
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); } } }); } }наконец, фрагмент может быть использован в главном приложении следующим образом:
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(); ... } }); } }
в моих проектах я использую шаблон проектирования observer для решения этой проблемы. В Android широковещательные приемники и намерения являются имплементацией этого шаблона.
то, что я делаю, это создать BroadcastReceiver который я регистрирую в фрагменте / activity onResume и отменить регистрацию в фрагмент/деятельности onPause. В BroadcastReceiver's метод onReceive я поставил весь код, который должен выполняться в результате - BroadcastReceiver-получение намерения (сообщения), которое было отправлено в ваше приложение в целом. Увеличить селективность по какому типу намерений может получить ваш фрагмент вы можете использовать фильтр намерений как в примере ниже.
преимуществом такого подхода является то, что намерение(сообщение) может быть отправлено отовсюду в вашем приложении(диалог, который открылся поверх вашего фрагмента, асинхронная задача, другой фрагмент и т. д.). Параметры могут даже передаваться как намерение дополнительный.
еще одним преимуществом является то, что этот подход совместим с любой версией API Android, так как BroadcastReceivers и Intents были введены на уровне API 1.
вам не требуется устанавливать какие-либо специальные разрешения на файл манифеста вашего приложения, за исключением случаев, когда вы планируете использовать sendStickyBroadcast(где вам нужно добавить BROADCAST_STICKY).
public class MyFragment extends Fragment { public static final String INTENT_FILTER = "gr.tasos.myfragment.refresh"; private BroadcastReceiver mReceiver = new BroadcastReceiver() { // this always runs in UI Thread @Override public void onReceive(Context context, Intent intent) { // your UI related code here // you can receiver data login with the intent as below boolean parameter = intent.getExtras().getBoolean("parameter"); } }; public void onResume() { super.onResume(); getActivity().registerReceiver(mReceiver, new IntentFilter(INTENT_FILTER)); }; @Override public void onPause() { getActivity().unregisterReceiver(mReceiver); super.onPause(); } // send a broadcast that will be "caught" once the receiver is up protected void notifyFragment() { Intent intent = new Intent(SelectCategoryFragment.INTENT_FILTER); // you can send data to receiver as intent extras intent.putExtra("parameter", true); getActivity().sendBroadcast(intent); } }
Comments