活动/片段暂停时如何处理Handler消息
我的其他post略有变化
基本上我有一个消息Handler
在我的Fragment
,收到一堆消息,可能会导致对话被解散或显示。
当应用程序被放到后台时,我得到一个onPause
但仍然可以让我的消息像预期的那样通过。 但是,因为我正在使用片段,所以不能忽略并显示对话框,因为这会导致IllegalStateException
。
我不能只是解雇或取消允许国家损失。
鉴于我有一个Handler
我想知道是否有一个build议的方法,我应该如何处理处于暂停状态的消息。
我正在考虑的一种可能的解决scheme是logging暂停时发送的消息,并在onResume
上播放。 这有点令人不满意,我想在框架中必须有一些东西来处理这个更优雅。
提前致谢。 彼得。
尽pipeAndroid操作系统似乎没有足够解决您的问题的机制,但我相信这种模式确实提供了相对简单的实施解决方法。
下面这个类是android.os.Handler
一个包装,当一个活动暂停时缓冲消息,并在简历上播放它们。
确保你有任何asynchronous改变片段状态的代码(例如commit,dismiss)只能从处理器的消息中调用。
从PauseHandler
类派生你的处理程序。
每当您的活动收到一个onPause()
调用PauseHandler.pause()
和onResume()
调用PauseHandler.resume()
。
用processMessage()
replaceHandler handleMessage()
实现。
提供一个总是返回true
的storeMessage()
的简单实现。
/** * Message Handler class that supports buffering up of messages when the * activity is paused ie 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
类的一个简单例子。
在点击一个button时,延迟的消息被发送到处理程序。
当处理程序收到消息(在UI线程上)时,它显示一个DialogFragment
。
如果没有使用PausedHandler
类,如果在按下testingbutton启动对话框之后按下主页button,则会显示IllegalStateException PausedHandler
。
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 ie 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,并且消息将被丢弃。
quickdraw的优秀PauseHandler版本稍微简单一点
/** * Message Handler class that supports buffering up of messages when the activity is paused ie 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); }
它确实假设你总是希望存储离线消息以进行重放。 并提供Activity作为#processMessages
input,所以你不需要在子类中进行pipe理。
这里有一个稍微不同的方法来处理callback函数中的Fragment提交问题,并避免IllegalStateException问题。
首先创build一个自定义的可运行界面。
public interface MyRunnable { void run(AppCompatActivity context); }
接下来,创build一个用于处理MyRunnable对象的片段。 如果MyRunnable对象是在Activity暂停后创build的,例如,如果屏幕旋转了,或者用户按下Homebutton,它将被放入一个队列,以便以后用新的上下文进行处理。 由于setRetain实例设置为true,因此队列可以保留任何configuration更改。 runProtected方法在UI线程上运行以避免使用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(); ... } }); } }
在我的项目中,我使用观察者devise模式来解决这个问题。 在Android中,广播接收器和意图是这种模式的实现。
我所做的是创build一个BroadcastReceiver ,我在片段/活动的onResume中注册并注销片段/活动的onPause 。 在BroadcastReceiver的onReceive方法中,我将所有需要运行的代码放在BroadcastReceiver的结果中 – 接收一般发送到您的应用程序的Intent(消息)。 为了提高您的片段可以接收的意向types的select性,您可以使用意向filter ,如下例所示。
这种方法的一个优点是Intent (消息)可以从你的应用程序(在你的片段顶部打开一个对话框,一个asynchronous任务,另一个片段等)的任何地方发送。 参数甚至可以作为意图额外传递。
另一个优点是,这种方法与任何Android API版本兼容,因为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); } }