2013-09-03 13 views
11

Ich habe ein Problem, wo ListFragment.onListItemClick nach onDestroyView aufgerufen wird. Ich erhalte viele Fehlerberichte in dem Feld (10-20 pro Tag von ~ 1000 aktiven Benutzern), aber die einzige Art, die ich gefunden habe, um sie zu reproduzieren, besteht darin, den Zurück-Knopf zu drücken, während man auf den ganzen Bildschirm klickt. Machen Hunderte von Benutzern das wirklich? Dies ist die Spur:Fragment OnClickListener aufgerufen nach onDestroyView

java.lang.IllegalStateException: Content view not yet created 
at au.com.example.activity.ListFragment.ensureList(ListFragment.java:860) 
at au.com.example.activity.ListFragment.getListView(ListFragment.java:695) 
at au.com.example.activity.MyFragment.onListItemClick(MyFragment.java:1290) 
at au.com.example.activity.ListFragment$2.onItemClick(ListFragment.java:90) 
at android.widget.AdapterView.performItemClick(AdapterView.java:301) 
at android.widget.AbsListView.performItemClick(AbsListView.java:1519) 
at android.widget.AbsListView$PerformClick.run(AbsListView.java:3278) 
at android.widget.AbsListView$1.run(AbsListView.java:4327) 
at android.os.Handler.handleCallback(Handler.java:725) 
at android.os.Handler.dispatchMessage(Handler.java:92) 
at android.os.Looper.loop(Looper.java:137) 
at android.app.ActivityThread.main(ActivityThread.java:5293) 
at java.lang.reflect.Method.invokeNative(Native Method) 
at java.lang.reflect.Method.invoke(Method.java:511) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869) 
at dalvik.system.NativeStart.main(Native Method) 

verursacht von getListView().getItemAtPosition in MyFragment.onListItemClick (MyFragment: 1290) aufrufen. Wie kann getView während eines Click-Handler-Callbacks null zurückgeben? Ich stellte auch fest, dass das Fragment zu diesem Zeitpunkt abgetrennt war, isAdded() falsch war und getActivity null war.

Eine Abhilfe wäre getListView zu ersetzen mit den listView übergeben von dem Rückruf public void onListItemClick(ListView listView, View v, int position, long id), aber auch andere Funktionen müssen noch andere Teile der Benutzeroberfläche aktualisieren, so würde dies nur das Problem woanders bewegen. Stattdessen nulled ich den Rückruf in onDestroyView:

public void onDestroyView() {   
     mHandler.removeCallbacks(mRequestFocus); 
     if(mList!=null){ 
      mList.setOnItemClickListener(null); 
     } 
     mList = null; 
     mListShown = false; 
     mEmptyView = mProgressContainer = mListContainer = null; 
     mStandardEmptyView = null; 
     super.onDestroyView(); 
    } 

Aber ich habe immer noch dieses onClick Problem in anderen (nicht-Liste) zu Fragmenten. Wie genau unterdrückt das Framework diese Rückrufe normalerweise, wenn das Fragment entfernt wird (z. B. in onBackPressed -> popBackStackImmediate())? In onDestroyView, ich null zusätzliche Ansichten, die ich in onCreateView erstellt habe. Muss ich jeden Listener manuell löschen, den ich so eingestellt habe?

Dies ist ein ähnliches Problem auf die unbeantwortet q: Fragment's getView() returning null in a OnClickListener callback

Ich setOnRetainInstance(true) in meine Fragmente verwenden, btw.

+0

können Sie den Fragmentcode geben? –

+1

Können Sie Ihren Onclick-Code posten? Machst du irgendwelche Hintergrundarbeit dort? Überprüfst du, ob getView() == null im onclick ist? – user1634451

+0

unterstützt Ihre App Rotationen? Wenn sich das Telefon dreht, wird die aktuelle Aktivität zerstört und eine neue erstellt. Dies bedeutet, wenn der Benutzer auf ein Objekt kurz vor der Rotation geklickt hat, wird der Klick wieder angezeigt, aber der Aktivitätskontext und die Ansicht sind null. Wie vorgeschlagen, überprüfen Sie, ob die Ansicht nicht null ist, stellen Sie sicher, dass Sie nicht versuchen, während einer Rotation auf den Kontext zuzugreifen (weil er zerstört ist). – toidiu

Antwort

1

Sie haben wirklich nicht sehr viele Informationen gegeben, aber basierend auf dem, was Sie gegeben haben, klingt es wie Fragment ausstehende Transaktionen könnte Ihr Problem sein.

In Android, wenn Sie ändern oder Fragmente instantiieren, wird alles durch ausstehende Transaktionen durchgeführt, es sei denn, es wird anders gesagt. Es ist im Wesentlichen eine Wettlaufbedingung.

getSupportFragmentManager().beginTransaction() 
    .replace(R.id.container, new ExampleFragment() 
    .commit(); 

Der UI-Thread hat eine Warteschlange von Arbeit, die es zu jeder beliebigen Zeit ausführen muss. Obwohl Sie die FragmentTransaction nach dem Ausführen des obigen Codes festgeschrieben haben, wurde sie tatsächlich am Ende der Warteschlange in den UI-Thread eingereiht, nachdem alle bereits ausstehenden Vorgänge beendet wurden. Dies bedeutet, dass, wenn Klickereignisse während der ausstehenden Transaktion auftreten (was leicht passieren kann, dh wenn Sie Klicks auf den Bildschirm spammen oder mit mehreren Fingern klicken), werden diese Klickereignisse in die UI-Thread-Warteschlange nach platziert FragmentTransaktion.

Das Endergebnis ist, dass die Fragmenttransaktion verarbeitet wird, Ihre Fragmentansicht zerstört wird und Sie getView() aufrufen, und es gibt null zurück.

Sie könnten ein paar Dinge versuchen:

  1. getSupportFragmentManager().executePendingTransactions() Dadurch werden alle ausstehenden Transaktionen ausführen rechts dann und entfernt die anstehenden Aspekt

  2. Überprüfen Sie, ob das Fragment isVisible() oder isAdded() oder eines anderen zu sehen Fragment 'ist' Methode, die es ermöglicht, Laufzeitinformationen über den aktuellen Status des Fragments im Lebenszyklus zu erhalten, bevor Sie Code ausführen, der möglicherweise ausgeführt wird, nachdem die Fragmentansicht zerstört wurde (dh auf Listener klicken)

  3. Nehmen wir an, Sie haben einen Klick-Handler, wo, wenn der Benutzer auf etwas klickt, Sie zu einem anderen Fragment animieren. Sie könnten etwas wie den folgenden Code verwenden, den Sie vor dem FragmentTransaction auf Ihrer äußersten Ansicht ausführen (in einem Fragment wäre es das, was von getView() zurückkehrt), und das würde Klicks auf eine Ansicht dauerhaft deaktivieren, wenn es dazu kommen würde zerstört werden oder temporär Klicks für einen bestimmten Zeitraum deaktivieren, wenn Sie die Ansicht erneut verwenden möchten.

Hoffe, das hilft.


public class ClickUtil { 

    /** 
    * Disables any clicks inside the given given view. 
    * 
    * @param view The view to iterate over and disable all clicks. 
    */ 
    public static void disable(View view) { 
    disable(view, null); 
    } 

    /** 
    * Disables any clicks inside the given given view for a certain amount of time. 
    * 
    * @param view The view to iterate over and disable all clicks. 
    * @param millis The number of millis to disable clicks for. 
    */ 
    public static void disable(View view, Long millis) { 
    final List<View> clickableViews = (millis == null) ? null : new ArrayList<View>(); 
    disableRecursive(view, clickableViews); 

    if (millis != null) { 
     MainThread.handler().postDelayed(new Runnable() { 
     @Override public void run() { 

      for (View v : clickableViews) { 
      v.setClickable(true); 
      } 

     } 
     }, millis); 
    } 
    } 

    private static void disableRecursive(View view, List<View> clickableViews) { 
    if (view.isClickable()) { 
     view.setClickable(false); 

     if (clickableViews != null) 
     clickableViews.add(view); 
    } 

    if (view instanceof ViewGroup) { 
     ViewGroup vg = (ViewGroup) view; 
     for (int i = 0; i < vg.getChildCount(); i++) { 
     disableRecursive(vg.getChildAt(i), clickableViews); 
     } 
    } 
    } 

} 
+0

Dieses Problem hat eigentlich nichts mit 'Fragment'-Transaktionen zu tun - es wird durch einen Bug in Vor-ICS (/ Honeycomb?) Implementierungen verursacht Der Callback zum Ausführen von Klicks wurde in der Nachrichtenwarteschlange beibehalten, wenn die "Ansicht" von dem "Fenster" getrennt wurde. Das Klicken der 'View'-Taste beim Loslösen kann nicht behoben werden, da alle Callbacks, die sich bereits in der Warteschlange befinden, weiterhin ausgeführt werden. Sie müssen alle Klicklistener entfernen, um dieses Problem zu beheben. Ich habe eine Antwort auf die andere verknüpfte [Frage] hinzugefügt (http://stackoverflow.com/q/17785388/3153792). – corsair992

+0

@ corsair992 Haben Sie einen Link zum Issue Tracker für diesen Defekt? Ich untersuche die Möglichkeit, dass so etwas auf neueren Geräten passiert. Danke! – FearTheCron

+0

@FearTheCron: Wie ich in meiner [Antwort] (https://Stackoverflow.com/a/27497711) auf die verknüpfte Frage erwähnt habe, sind mir keine alten Fehlerberichte zu diesem Problem bekannt. Dies war nur etwas, das ich beim Durchsehen des Android-Quellcodes entdeckt habe, um eine mögliche Ursache für die gemeldeten Probleme zu finden. – corsair992

-1

meinen Arm Wett es durch extra staatenlos Fragmente irgendwo innerhalb der App zu leben. Ich würde persönlich davon abraten, die Instanz zu behalten und Android das zu lassen, was er damit tun kann, während Sie den Standardmechanismus verwenden, um Ihren Status beizubehalten (saveInstanceState, Datenbank, High-Level-Klassen/-Muster, SharedPreferences usw.).

Persönlich hatte ich viele Probleme beim Beibehalten einer Fragmentinstanz (normalerweise beim erneuten Erstellen oder Reaktivieren von Fragmenten durch Konfigurationsänderungen oder Verlassen und erneutes Eingeben der Anwendung), was im Allgemeinen zu zwei Fragmenten führt, eines davon mit Ansichten verbunden, staatenlos, also nutzlos; und der "echte" hält den vorherigen Zustand ohne irgendeine Verbindung zu Ansichten und endet mit Ausnahmen und allem, was man nicht haben will.

Beginnen Sie damit, die Instanz nicht zu behalten und zu sehen, was sich ergibt.

-1

Sie könnten mHandler.removeCallbacksAndMessages (null) in vielen Situationen für mich arbeiten.

Verwandte Themen