Etkinlik / parça duraklatıldığında İşleyici iletileri nasıl işlenir


98

Diğer gönderimde hafif bir değişiklik var

Temelde Bir mesajım var Handlerbenim de Fragmentdiyaloglarda neden olabilir bir sürü mesaj aldığında görevden veya gösterilen.

Uygulama arka plana yerleştirildiğinde bir alıyorum onPauseama yine de mesajlarım beklendiği gibi geliyor. Ancak, fragmanları kullandığım için diyalogları kapatıp gösteremem, çünkü bu bir IllegalStateException.

Devlet kaybına izin vermeyi reddedemem veya iptal edemem.

Bir var olduğu göz önüne alındığında Handlerben ise duraklatılmış durumda iletileri nasıl ele aldığını şekilde önerilen bir yaklaşım olup olmadığını merak ediyorum.

Düşündüğüm olası bir çözüm, duraklatılmış durumdayken gelen mesajları kaydetmek ve bunları bir onResume. Bu biraz tatmin edici değil ve çerçevede bunu daha zarif bir şekilde ele almak için bir şeyler olması gerektiğini düşünüyorum.


1
parçanın onPause () yönteminde işleyicideki tüm mesajları kaldırabilirsiniz, ancak mesajların geri yüklenmesinde mümkün olmadığını düşündüğüm bir sorun var.
Yashwanth Kumar

Yanıtlar:


167

Android işletim sistemi sorununuzu yeterince çözen bir mekanizmaya sahip gibi görünmese de, bu modelin uygulanması nispeten basit bir geçici çözüm sağladığına inanıyorum.

Aşağıdaki sınıf, android.os.Handlerbir etkinlik duraklatıldığında mesajları arabelleğe alan ve devam ettirildiğinde yeniden oynatan bir sarmalayıcıdır .

Bir parça durumunu eşzamansız olarak değiştiren (ör. Commit, dismiss) sahip olduğunuz herhangi bir kodun yalnızca işleyicideki bir mesajdan çağrıldığından emin olun.

İşleyicinizi PauseHandlersınıftan türetin .

Her ne zaman etkinlik bir alan onPause()çağrıyı PauseHandler.pause()ve için onResume()çağrı PauseHandler.resume().

Handler uygulamanızı değiştirin handleMessage()ile processMessage().

storeMessage()Her zaman geri dönen basit bir uygulama sağlayın 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);
        }
    }
}

Aşağıda, PausedHandlersınıfın nasıl kullanılabileceğine dair basit bir örnek verilmiştir .

Bir düğmenin tıklanmasıyla, işleyiciye gecikmiş bir mesaj gönderilir.

İşleyici mesajı aldığında (UI iş parçacığında) bir DialogFragment.

Eğer PausedHandlersınıf kullanılmamış ev düğmesi iletişim kutusu açmak için test düğmesine bastıktan sonra basıldığında ise bir IllegalStateException gösterilecekti.

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

Etkinlik duraklatıldığında bile herhangi bir iletinin hemen işlenmesi gerekmesi ihtimaline storeMessage()karşı PausedHandlersınıfa bir yöntem ekledim . Bir mesaj işlenirse, yanlış döndürülmeli ve mesaj silinmelidir.


26
Güzel çözüm, harika çalışıyor. Çerçevenin bunu ele alması gerektiğini düşünmeden edemiyorum.
PJL

1
Geri arama DialogFragment'a nasıl iletilir?
Malachiasz

Malachiasz sorusunu anladığımdan emin değilim, lütfen açıklar mısınız?
quickdraw mcgraw

Bu çok zarif bir çözüm! Yanılıyorsam, resumeyöntem sendMessage(msg)teknik olarak kullandığından , mesajı hemen önce (veya döngünün yinelemeleri arasında) sıraya alan başka iş parçacıkları olabilir, bu da depolanan mesajların gelen yeni mesajlarla araya eklenebileceği anlamına gelir. Önemli olup olmadığından emin değilim. Belki kullanmak sendMessageAtFrontOfQueue(ve elbette geriye doğru yinelemek) bu sorunu çözer mi?
yan

4
Bence bu yaklaşım her zaman işe yaramayabilir - eğer etkinlik işletim sistemi tarafından yok edilirse, işlem olarak bekleyen mesajların listesi devam ettirildikten sonra boş olacaktır.
GaRRaPeTa

10

Quickdraw'ın mükemmel PauseHandler'ın biraz daha basit bir versiyonu

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

}

Tekrar oynatmak için her zaman çevrimdışı mesajları saklamak istediğinizi varsayar. Ve Aktiviteyi girdi olarak sağlar, #processMessagesböylece onu alt sınıfta yönetmenize gerek kalmaz.


Neden senin resume()ve pause(), ve handleMessage synchronized?
Maksim Dmitriev

5
Çünkü #handleMessage sırasında #pause'un çağrılmasını istemiyorsunuz ve #handleMessage'da kullanırken aniden aktivitenin boş olduğunu fark ediyorsunuz. Paylaşılan durumda bir senkronizasyondur.
William

@William Bana daha fazla ayrıntı verebilir misiniz lütfen bir PauseHandler sınıfında neden senkronizasyona ihtiyacınız var? Görünüşe göre bu sınıf sadece bir iş parçacığında, UI iş parçacığında çalışıyor. Sanırım #handleMessage sırasında #pause çağrılamadı çünkü ikisi de UI iş parçacığında çalışıyor.
Samik

@William emin misin? 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

Üzgünüm @swooby Takip etmiyorum. Neyden emin miyim? Ve gönderdiğiniz kod parçacığının amacı nedir?
William

2

Bir geri arama işlevinde Fragment commit'leri yapma ve IllegalStateException sorunundan kaçınma sorununa yaklaşmanın biraz farklı bir yolu.

Önce özel bir çalıştırılabilir arayüz oluşturun.

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

Ardından, MyRunnable nesnelerini işlemek için bir parça oluşturun. MyRunnable nesnesi Etkinlik duraklatıldıktan sonra oluşturulmuşsa, örneğin ekran döndürülürse veya kullanıcı ana sayfa düğmesine basarsa, daha sonra yeni bir bağlamla işlenmek üzere kuyruğa alınır. SetRetain örneği true olarak ayarlandığından kuyruk, herhangi bir yapılandırma değişikliğinden kurtulur. RunProtected yöntemi, isPaused bayrağıyla bir yarış koşulundan kaçınmak için UI iş parçacığında çalışır.

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

Son olarak, fragman aşağıdaki gibi bir ana uygulamada kullanılabilir:

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

Projelerimde bunu çözmek için gözlemci tasarım modelini kullanıyorum. Android'de yayın alıcıları ve amaçları bu modelin bir uygulamasıdır.

Ne yapmak bir oluşturmaktır BroadcastReceiver ben veridiliminin / etkinliğin kayıt onResume veridiliminin / Faaliyetin ve unregister OnPause . In BroadcastReceiver 'ın yöntemine OnReceive BroadcastReceiver - - Bir Niyet (mesaj) alan genel olarak uygulamanıza gönderildi ı ihtiyaçlar sonucunda çalıştırmak için tüm kodu koydu. Parçanızın hangi tür niyetleri alabileceği konusundaki seçiciliği artırmak için aşağıdaki örnekte olduğu gibi bir amaç filtresi kullanabilirsiniz .

Bu yaklaşımın bir avantajı, Niyetin (mesaj) uygulamanızın her yerinden gönderilebilmesidir (parçanızın üstünde açılan bir iletişim kutusu, zaman uyumsuz bir görev, başka bir parça vb.). Parametreler, amaç ekstraları olarak bile aktarılabilir.

Diğer bir avantaj, bu yaklaşımın herhangi bir Android API sürümüyle uyumlu olmasıdır, çünkü BroadcastReceivers and Intents, API seviyesi 1'de kullanıma sunulmuştur.

SendStickyBroadcast kullanmayı planlıyorsanız (BROADCAST_STICKY eklemeniz gereken yer) dışında, uygulamanızın manifest dosyasında herhangi bir özel izin ayarlamanıza gerek yoktur.

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
NotifyFragment () içindeki sendBroadcast () Duraklatma durumu sırasında çağrılırsa, unregisterReceiver () zaten çağrılmış olacak ve dolayısıyla bu amacı yakalamak için etrafta alıcı olmayacak. Hemen işleyecek bir kod yoksa, Android sistemi niyeti iptal etmez mi?
Steve B

yeşil robotlar eventbus yapışkan gönderileri böyle, harika.
j2emanue
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.