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

다른 게시물의 약간의 변형

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

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

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

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

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



답변

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


답변

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하므로 하위 클래스에서 관리 할 필요가 없습니다.


답변

콜백 함수에서 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();
                ...
            }
         });
    }
}


답변

내 프로젝트에서는 관찰자 디자인 패턴을 사용하여이 문제를 해결합니다. 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);
    }

}


답변