활동 / 조각이 일시 중지되었을 때 핸들러 메시지를 처리하는 방법


98

다른 게시물의 약간의 변형

기본적으로 나는 메시지를 가지고 Handler내에서 Fragment대화 상자가 발생할 수있는 메시지의 무리를 수신 기각 또는 표시되는.

앱이 백그라운드로 들어가면 나는을 얻었 onPause지만 예상대로 내 메시지가 여전히 전달됩니다. 그러나 조각을 사용하고 있기 때문에 대화 상자를 닫고 표시 할 수는 없습니다 IllegalStateException.

상태 손실 허용을 취소하거나 취소 할 수 없습니다.

나는이 점을 감안 Handler나는 동안 일시 중지 상태에서 메시지를 처리하는 방법에 관해서는 권장되는 방법이 있는지 궁금하네요.

내가 고려하고있는 한 가지 가능한 해결책은 일시 중지 된 동안 들어오는 메시지를 기록하고 onResume. 이것은 다소 불만족스럽고 프레임 워크에 더 우아하게 처리 할 수있는 무언가가 있어야한다고 생각합니다.


1
조각의 onPause () 메서드에서 처리기의 모든 메시지를 제거 할 수 있지만 불가능하다고 생각되는 메시지를 복원하는 데 문제가 있습니다.
Yashwanth Kumar

답변:


167

Android 운영 체제에는 문제를 충분히 해결할 수있는 메커니즘이없는 것 같지만이 패턴이 비교적 간단한 해결 방법을 제공한다고 생각합니다.

다음 클래스는 android.os.Handler활동이 일시 중지 될 때 메시지를 버퍼링하고 다시 시작할 때 메시지를 재생 하는 래퍼 입니다.

조각 상태 (예 : commit, dismiss)를 비동기 적으로 변경하는 코드가 핸들러의 메시지에서만 호출되는지 확인하십시오.

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클래스 사용 방법에 대한 간단한 예입니다 .

버튼을 클릭하면 지연된 메시지가 핸들러로 전송됩니다.

핸들러가 메시지를 수신하면 (UI 스레드에서) DialogFragment.

경우 PausedHandler클래스가 사용되지 않은 홈 버튼은 대화를 시작하려면 테스트 버튼을 누르면 누를 경우 (자), IllegalStateException가 표시됩니다.

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가 반환되고 메시지가 삭제됩니다.


26
좋은 솔루션, 치료 효과가 있습니다. 프레임 워크가 이것을 처리해야한다고 생각하는 것을 도울 수 없습니다.
PJL 2011

1
DialogFragment에 콜백을 전달하는 방법?
Malachiasz

Malachiasz라는 질문을 이해했는지 잘 모르겠습니다. 자세히 설명해 주시겠습니까?
quickdraw mcgraw 2014 년

이것은 매우 우아한 솔루션입니다! 내가 틀리지 않는 한, resume방법이 sendMessage(msg)기술적으로 사용 하기 때문에 메시지를 대기열에 넣는 다른 스레드가 바로 직전 (또는 루프의 반복 사이)에있을 수 있으며, 이는 저장된 메시지가 도착하는 새 메시지와 인터리브 될 수 있음을 의미합니다. 그것이 큰 문제인지 확실하지 않습니다. 아마도 사용 sendMessageAtFrontOfQueue(물론 뒤로 반복)하면이 문제를 해결할 수 있습니까?
yan

4
이 접근 방식은 항상 작동하지 않을 수 있다고 생각합니다. 활동이 OS에 의해 파괴되면 프로세스 대기중인 메시지 목록이 재개 후 비어있게됩니다.
GaRRaPeTa 2015 년

10

quickdraw의 우수한 PauseHandler의 약간 더 간단한 버전은 다음과 같습니다.

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

}

재생을 위해 항상 오프라인 메시지를 저장한다고 가정합니다. 그리고 Activity를 입력으로 제공 #processMessages하므로 하위 클래스에서 관리 할 필요가 없습니다.


귀하의 resume()pause(), 및 이유는 무엇 handleMessage synchronized입니까?
Maksim Dmitriev 2014 년

5
#handleMessage 중에 #pause가 호출되는 것을 원하지 않고 #handleMessage에서 사용하는 동안 갑자기 활동이 null임을 발견하기 때문입니다. 공유 상태에 걸친 동기화입니다.
William

@William PauseHandler 클래스에서 동기화가 필요한 이유를 자세히 설명해 주시겠습니까? 이 클래스는 하나의 스레드, UI 스레드에서만 작동하는 것 같습니다. 둘 다 UI 스레드에서 작동하기 때문에 #handleMessage 중에 #pause를 호출 할 수 없다고 생각합니다.
Samik

@William 확실합니까? HandlerThread handlerThread = new HandlerThread ( "mHandlerNonMainThread"); handlerThread.start (); 루퍼 looperNonMainThread = handlerThread.getLooper (); 핸들러 handlerNonMainThread = new Handler (looperNonMainThread, new Callback () {public boolean handleMessage (Message msg) {return false;}});
swooby

@swooby 죄송합니다. 나는 무엇을 확신합니까? 그리고 게시 한 코드 조각의 목적은 무엇입니까?
William

2

콜백 함수에서 Fragment 커밋을 수행하고 IllegalStateException 문제를 피하는 문제에 접근하는 약간 다른 방법이 있습니다.

먼저 사용자 지정 실행 가능한 인터페이스를 만듭니다.

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

다음으로 MyRunnable 개체를 처리하기위한 조각을 만듭니다. 활동이 일시 중지 된 후 MyRunnable 개체가 생성 된 경우 (예 : 화면이 회전하거나 사용자가 홈 버튼을 누를 경우) 나중에 새 컨텍스트로 처리 할 수 ​​있도록 대기열에 넣습니다. setRetain 인스턴스가 true로 설정되어 있으므로 큐는 구성 변경 사항을 유지합니다. runProtected 메소드는 isPaused 플래그가있는 경쟁 조건을 피하기 위해 UI 스레드에서 실행됩니다.

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

0

내 프로젝트에서는 관찰자 디자인 패턴을 사용하여이 문제를 해결합니다. Android에서 브로드 캐스트 수신기와 인 텐트는이 패턴의 구현입니다.

내가하는 일은 프래그먼트 / 액티비티의 onResume 에 등록하고 프래그먼트 / 액티비티의 onPause에 등록 해제 하는 BroadcastReceiver 를 만드는 것입니다 . 에서 브로드 캐스트 리시버 의 방법 onReceive 브로드 캐스트 리시버 - - 텐트 (메시지) 수신 일반적으로 앱에 전송 된 나는 요구의 결과로 실행하는 모든 코드를 삽입. 프래그먼트가 수신 할 수있는 인 텐트 유형에 대한 선택성을 높이려면 아래 예제와 같이 인 텐트 필터사용할있습니다 .

이 접근 방식의 장점은 앱 (프래그먼트 위에서 열린 대화, 비동기 작업, 다른 프래그먼트 등)이있는 모든 곳에서 인 텐트 (메시지)를 보낼 수 있다는 것입니다. 매개 변수는 인 텐트 엑스트라로 전달할 수도 있습니다.

또 다른 장점은 BroadcastReceivers 및 Intents가 API 레벨 1에 도입 되었기 때문에이 접근 방식이 모든 Android API 버전과 호환된다는 것입니다.

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

}

3
Pause 상태에서 notifyFragment ()의 sendBroadcast ()가 호출되면 unregisterReceiver ()가 이미 호출되어 해당 인 텐트를 포착 할 수신자가 주변에 없습니다. Android 시스템은 즉시 처리 할 코드가 없으면 인 텐트를 버리지 않습니까?
Steve B

녹색 로봇 이벤트 버스 스티커 포스트가 이렇게 멋지다고 생각합니다.
j2emanue aug
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.