Cách xử lý thông báo của Trình xử lý khi tạm dừng hoạt động / phân đoạn


98

Một chút khác biệt về bài đăng khác của tôi

Về cơ bản, tôi có một tin nhắn Handlertrong Fragmentđó nhận được một loạt các tin nhắn có thể dẫn đến hộp thoại bị loại bỏ hoặc hiển thị.

Khi ứng dụng được đưa vào nền, tôi nhận được onPausenhưng sau đó vẫn nhận được thông báo của tôi như người ta mong đợi. Tuy nhiên, bởi vì tôi đang sử dụng các phân đoạn, tôi không thể loại bỏ và hiển thị các hộp thoại vì điều đó sẽ dẫn đến IllegalStateException.

Tôi không thể loại bỏ hoặc hủy bỏ việc cho phép mất trạng thái.

Cho rằng tôi có một, Handlertôi đang tự hỏi liệu có một cách tiếp cận được đề xuất về cách tôi nên xử lý thư khi ở trạng thái tạm dừng.

Một giải pháp khả thi mà tôi đang xem xét là ghi lại các tin nhắn đến trong khi tạm dừng và phát lại chúng trên một onResume. Điều này hơi không thỏa đáng và tôi nghĩ rằng phải có một cái gì đó trong khuôn khổ để xử lý điều này một cách thanh lịch hơn.


1
bạn có thể xóa tất cả các thư trong trình xử lý trong phương thức onPause () của phân mảnh, nhưng có vấn đề khi khôi phục các thư mà tôi nghĩ là không thể.
Yashwanth Kumar 10/11/11

Câu trả lời:


167

Mặc dù hệ điều hành Android dường như không có cơ chế giải quyết đủ vấn đề của bạn, tôi tin rằng mẫu này cung cấp một cách giải quyết tương đối đơn giản.

Lớp sau là một lớp bao bọc xung quanh android.os.Handlerbộ đệm thông báo khi một hoạt động bị tạm dừng và phát lại chúng khi tiếp tục.

Đảm bảo bất kỳ mã nào bạn có mà thay đổi không đồng bộ trạng thái phân mảnh (ví dụ: cam kết, loại bỏ) chỉ được gọi từ một thông báo trong trình xử lý.

Truy xuất trình xử lý của bạn từ PauseHandlerlớp.

Bất cứ khi nào hoạt động của bạn nhận được onPause()cuộc gọi PauseHandler.pause()onResume()cuộc gọi PauseHandler.resume().

Thay thế việc triển khai Trình xử lý của bạn handleMessage()bằng processMessage().

Cung cấp một triển khai đơn giản storeMessage()luôn trả về 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);
        }
    }
}

Dưới đây là một ví dụ đơn giản về cách PausedHandlerlớp có thể được sử dụng.

Khi nhấp vào một nút, một thông báo trì hoãn sẽ được gửi đến trình xử lý.

Khi trình xử lý nhận được thông báo (trên chuỗi giao diện người dùng), nó sẽ hiển thị a DialogFragment.

Nếu PausedHandlerlớp không được sử dụng, IllegalStateException sẽ được hiển thị nếu nút trang chủ được nhấn sau khi nhấn nút kiểm tra để khởi chạy hộp thoại.

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;
                }
            }
        }
    }
}

Tôi đã thêm một storeMessage()phương thức vào PausedHandlerlớp trong trường hợp bất kỳ thông báo nào sẽ được xử lý ngay lập tức ngay cả khi hoạt động bị tạm dừng. Nếu một thông báo được xử lý thì false sẽ được trả về và thông báo đó sẽ bị loại bỏ.


26
Giải pháp tốt, hoạt động tốt. Không thể không nghĩ rằng khung công tác nên xử lý điều này.
PJL

1
làm cách nào để chuyển lệnh gọi lại tới DialogFragment?
Malachiasz

Tôi không chắc rằng tôi hiểu câu hỏi Malachiasz, vui lòng giải thích thêm.
rút nhanh mcgraw

Đây là một giải pháp rất thanh lịch! Trừ khi tôi sai, bởi vì resumephương pháp sử dụng sendMessage(msg)về mặt kỹ thuật có thể có các chuỗi khác xếp hàng thông báo ngay trước (hoặc giữa các lần lặp lại của vòng lặp), có nghĩa là các thông báo được lưu trữ có thể được xen kẽ với các thông báo mới đến. Không chắc chắn nếu nó là một vấn đề lớn. Có thể sử dụng sendMessageAtFrontOfQueue(và tất nhiên là lặp lại) sẽ giải quyết được vấn đề này?
yan

4
Tôi nghĩ rằng cách tiếp cận này có thể không phải lúc nào cũng hoạt động - nếu hoạt động bị phá hủy bởi Hệ điều hành, danh sách các thông báo đang chờ xử lý quy trình sẽ trống sau khi tiếp tục.
GaRRaPeTa

10

Một phiên bản đơn giản hơn một chút của PauseHandler tuyệt vời của quickdraw là

/**
 * 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);

}

Nó giả định rằng bạn luôn muốn lưu trữ các tin nhắn ngoại tuyến để phát lại. Và cung cấp Activity làm đầu vào để #processMessagesbạn không cần quản lý nó trong lớp con.


Tại sao là của bạn resume()pause(), và handleMessage synchronized?
Maksim Dmitriev

5
Bởi vì bạn không muốn #pause được gọi trong #handleMessage và đột nhiên thấy rằng hoạt động đó không có giá trị khi bạn đang sử dụng nó trong #handleMessage. Đó là đồng bộ hóa trên trạng thái được chia sẻ.
William

@William Bạn có thể giải thích cho tôi biết thêm chi tiết tại sao bạn cần đồng bộ hóa trong lớp PauseHandler không? Có vẻ như lớp này chỉ hoạt động trong một luồng, luồng giao diện người dùng. Tôi đoán rằng không thể gọi #pause trong #handleMessage vì cả hai đều hoạt động trong chuỗi giao diện người dùng.
Samik

@William bạn có chắc không? HandlerThread handlerThread = new HandlerThread ("mHandlerNonMainThread"); handlerThread.start (); Looper looperNonMainThread = handlerThread.getLooper (); Handler handlerNonMainThread = new Handler (looperNonMainThread, new Callback () {public boolean handleMessage (Message msg) {return false;}});
swooby

Xin lỗi @swooby, tôi không làm theo. Tôi chắc chắn về điều gì? Và mục đích của đoạn mã bạn đăng là gì?
William

2

Đây là một cách hơi khác để tiếp cận vấn đề thực hiện các cam kết Fragment trong một hàm gọi lại và tránh sự cố IllegalStateException.

Đầu tiên, hãy tạo một giao diện có thể chạy tùy chỉnh.

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

Tiếp theo, tạo một phân mảnh để xử lý các đối tượng MyRunnable. Nếu đối tượng MyRunnable được tạo sau khi tạm dừng Hoạt động, ví dụ: nếu màn hình được xoay hoặc người dùng nhấn nút trang chủ, nó sẽ được đưa vào hàng đợi để xử lý sau với ngữ cảnh mới. Hàng đợi vẫn tồn tại bất kỳ thay đổi cấu hình nào vì cá thể setRetain được đặt thành true. Phương thức runProtected chạy trên luồng giao diện người dùng để tránh tình trạng chạy đua với cờ 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);
                }
            }
        });
    }
}

Cuối cùng, phân đoạn có thể được sử dụng trong một ứng dụng chính như sau:

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();
                ...
            }
         });
    }
}

0

Trong các dự án của mình, tôi sử dụng mẫu thiết kế người quan sát để giải quyết vấn đề này. Trong Android, bộ thu phát sóng và ý định là một sự bắt đầu của mô hình này.

Những gì tôi làm là tạo một BroadcastReceiver mà tôi đăng ký trong onResume của phân mảnh / hoạt động và hủy đăng ký trong onPause của phân mảnh / hoạt động . Trong BroadcastReceiver phương pháp 's onReceive tôi đặt tất cả các mã mà cần phải chạy như kết quả of - the BroadcastReceiver - nhận Intent (message) được gửi tới ứng dụng của bạn nói chung. Để tăng tính chọn lọc về loại ý định mà phân đoạn của bạn có thể nhận được, bạn có thể sử dụng bộ lọc ý định như trong ví dụ bên dưới.

Một ưu điểm của cách tiếp cận này là Intent (thông báo) có thể được gửi từ mọi nơi trong ứng dụng của bạn (hộp thoại mở trên đầu phân đoạn của bạn, tác vụ không đồng bộ, phân đoạn khác, v.v.). Các tham số thậm chí có thể được chuyển dưới dạng phần bổ sung ý định.

Một ưu điểm khác là cách tiếp cận này tương thích với bất kỳ phiên bản API Android nào, vì BroadcastReceivers và Intents đã được giới thiệu trên API cấp 1.

Bạn không bắt buộc phải thiết lập bất kỳ quyền đặc biệt nào trên tệp kê khai của ứng dụng trừ khi bạn định sử dụng sendStickyBroadcast (nơi bạn cần thêm 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);
    }

}

3
Nếu sendBroadcast () trong InformFragment () được gọi trong trạng thái Tạm dừng, unregisterReceiver () sẽ đã được gọi và do đó sẽ không có người nhận nào ở xung quanh để nắm bắt ý định đó. Sau đó hệ thống Android sẽ loại bỏ ý định nếu không có mã nào để xử lý ngay lập tức?
Steve B

Tôi nghĩ rằng các bài đăng dính eventbus của robot màu xanh lá cây là như thế này, thật tuyệt.
j2emanue
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.