Android. Fragment getActivity () gibt manchmal null zurück

194

In Entwicklerkonsolen-Fehlerberichten werden manchmal Berichte mit NPE-Problemen angezeigt. Ich verstehe nicht, was mit meinem Code falsch ist. Auf dem Emulator und meiner Geräteanwendung funktioniert es gut ohne Forcecloses. Einige Benutzer erhalten jedoch NullPointerException in der Fragmentklasse, wenn die Methode getActivity () aufgerufen wird.

Aktivität

pulic class MyActivity extends FragmentActivity{

    private ViewPager pager; 
    private TitlePageIndicator indicator;
    private TabsAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        pager = (ViewPager) findViewById(R.id.pager);
        indicator = (TitlePageIndicator) findViewById(R.id.indicator);
        adapter = new TabsAdapter(getSupportFragmentManager(), false);

        adapter.addFragment(new FirstFragment());
        adapter.addFragment(new SecondFragment());
        indicator.notifyDataSetChanged();
        adapter.notifyDataSetChanged();

        // push first task
        FirstTask firstTask = new FirstTask(MyActivity.this);
        // set first fragment as listener
        firstTask.setTaskListener((TaskListener) adapter.getItem(0));
        firstTask.execute();
    }

    indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener()  {
        @Override
        public void onPageSelected(int position) {
            Fragment currentFragment = adapter.getItem(position);
            ((Taskable) currentFragment).executeTask();
        }

        @Override
        public void onPageScrolled(int i, float v, int i1) {}

        @Override
        public void onPageScrollStateChanged(int i) {}
    });
}

AsyncTask-Klasse

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

    @Override
    protected void onPostExecute(T result) {
        ... 
        taskListener.onTaskComplete(result);
    }   
}

Fragmentklasse

public class FirstFragment extends Fragment immplements Taskable, TaskListener{

    public FirstFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.first_view, container, false);
    }

    @Override
    public void executeTask() {
        FirstTask firstTask = new FirstTask(MyActivity.this);
        firstTask.setTaskListener(this);
        firstTask.execute();
    }

    @Override
    public void onTaskComplete(T result) {
        // NPE is here 
        Resources res = getActivity().getResources();
        ...
    }
}

Möglicherweise tritt dieser Fehler auf, wenn Anwendungen aus dem Hintergrund fortgesetzt werden. Wie soll ich in diesem Fall richtig mit dieser Situation umgehen?

Georgy Gobozov
quelle
Ich habe ein Problem herausgefunden, aber keine Lösung. Ich weiß nicht warum, aber Fragment nimmt frühere Aktivitäten wieder auf. Und dies passiert nur, wenn meine App an letzter Stelle in der Liste der letzten Apps steht und das System meine Anwendung zerstört.
Georgy Gobozov
1
Wenn ich meine Anwendung vom Hintergrund fragmetn onCreate fortsetze, wird ein onResume aufgerufen, der vor der Aktivität onCreate / onResume-Methode aufgerufen wird. Es scheint, als ob ein freistehendes Fragment noch lebt und versucht, es wieder aufzunehmen.
Georgy Gobozov
1
in dieser Zeichenfolge firstTask.setTaskListener ((TaskListener) adapter.getItem (0)); adapter.getItem (0) altes Fragment zurückgeben, Adapter Fragmente nicht korrekt entfernen
Georgy Gobozov
9
Tolle Aktivität übrigens :) Frage gestellt, Kommentare hinterlassen und Antwort gegeben - alle werden von einer einzigen Person erledigt! +1 für diese.
Prizoff
Speichern Sie den Kontext (getActivity ()) in onCreateView (), da dies aufgerufen wird, wenn die Ansicht im Hintergrund neu erstellt wird.
sha

Antworten:

123

Es scheint, dass ich eine Lösung für mein Problem gefunden habe. Hier und hier werden sehr gute Erklärungen gegeben . Hier ist mein Beispiel:

pulic class MyActivity extends FragmentActivity{

private ViewPager pager; 
private TitlePageIndicator indicator;
private TabsAdapter adapter;
private Bundle savedInstanceState;

 @Override
public void onCreate(Bundle savedInstanceState) {

    .... 
    this.savedInstanceState = savedInstanceState;
    pager = (ViewPager) findViewById(R.id.pager);;
    indicator = (TitlePageIndicator) findViewById(R.id.indicator);
    adapter = new TabsAdapter(getSupportFragmentManager(), false);

    if (savedInstanceState == null){    
        adapter.addFragment(new FirstFragment());
        adapter.addFragment(new SecondFragment());
    }else{
        Integer  count  = savedInstanceState.getInt("tabsCount");
        String[] titles = savedInstanceState.getStringArray("titles");
        for (int i = 0; i < count; i++){
            adapter.addFragment(getFragment(i), titles[i]);
        }
    }


    indicator.notifyDataSetChanged();
    adapter.notifyDataSetChanged();

    // push first task
    FirstTask firstTask = new FirstTask(MyActivity.this);
    // set first fragment as listener
    firstTask.setTaskListener((TaskListener) getFragment(0));
    firstTask.execute();

}

private Fragment getFragment(int position){
     return savedInstanceState == null ? adapter.getItem(position) : getSupportFragmentManager().findFragmentByTag(getFragmentTag(position));
}

private String getFragmentTag(int position) {
    return "android:switcher:" + R.id.pager + ":" + position;
}

 @Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("tabsCount",      adapter.getCount());
    outState.putStringArray("titles", adapter.getTitles().toArray(new String[0]));
}

 indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            Fragment currentFragment = adapter.getItem(position);
            ((Taskable) currentFragment).executeTask();
        }

        @Override
        public void onPageScrolled(int i, float v, int i1) {}

        @Override
        public void onPageScrollStateChanged(int i) {}
 });

Die Hauptidee in diesem Code ist, dass Sie während der normalen Ausführung Ihrer Anwendung neue Fragmente erstellen und diese an den Adapter übergeben. Wenn Sie fortfahren, verfügt Ihr Anwendungsfragmentmanager bereits über die Instanz dieses Fragments, und Sie müssen es vom Fragmentmanager abrufen und an den Adapter übergeben.

AKTUALISIEREN

Es ist auch eine gute Vorgehensweise, wenn Sie Fragmente verwenden, um isAdded zu überprüfen, bevor getActivity () aufgerufen wird. Dies hilft, eine Nullzeigerausnahme zu vermeiden, wenn das Fragment von der Aktivität getrennt wird. Eine Aktivität kann beispielsweise ein Fragment enthalten, das eine asynchrone Aufgabe überträgt. Wenn die Aufgabe abgeschlossen ist, wird der Listener onTaskComplete aufgerufen.

@Override
public void onTaskComplete(List<Feed> result) {

    progress.setVisibility(View.GONE);
    progress.setIndeterminate(false);
    list.setVisibility(View.VISIBLE);

    if (isAdded()) {

        adapter = new FeedAdapter(getActivity(), R.layout.feed_item, result);
        list.setAdapter(adapter);
        adapter.notifyDataSetChanged();
    }

}

Wenn wir das Fragment öffnen, eine Aufgabe verschieben und dann schnell zurück drücken, um zu einer vorherigen Aktivität zurückzukehren, wird nach Abschluss der Aufgabe versucht, auf die Aktivität in onPostExecute () zuzugreifen, indem die Methode getActivity () aufgerufen wird. Wenn die Aktivität bereits getrennt ist und diese Prüfung nicht vorhanden ist:

if (isAdded()) 

dann stürzt die Anwendung ab.

Georgy Gobozov
quelle
56
Das ist allerdings ärgerlich, isAdded()vor jedem Zugriff anrufen zu müssen ... macht den Code hässlich.
Ixx
25
Es scheint keinen großen Unterschied zwischen if(isAdded())oderif(getActivity() != null)
StackOverflowed
19

Ok, ich weiß, dass diese Frage tatsächlich gelöst ist, aber ich habe beschlossen, meine Lösung dafür zu teilen. Ich habe eine abstrakte Elternklasse für Folgendes erstellt Fragment:

public abstract class ABaseFragment extends Fragment{

    protected IActivityEnabledListener aeListener;

    protected interface IActivityEnabledListener{
        void onActivityEnabled(FragmentActivity activity);
    }

    protected void getAvailableActivity(IActivityEnabledListener listener){
        if (getActivity() == null){
            aeListener = listener;

        } else {
            listener.onActivityEnabled(getActivity());
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        if (aeListener != null){
            aeListener.onActivityEnabled((FragmentActivity) activity);
            aeListener = null;
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (aeListener != null){
            aeListener.onActivityEnabled((FragmentActivity) context);
            aeListener = null;
        }
    }
}

Wie Sie sehen können, habe ich einen Listener hinzugefügt, sodass ich immer dann anrufen muss , wenn ich Fragments Activityanstelle von Standard einen bekommen getActivity()muss

 getAvailableActivity(new IActivityEnabledListener() {
        @Override
        public void onActivityEnabled(FragmentActivity activity) {
            // Do manipulations with your activity
        }
    });
Paul Freez
quelle
Gute Antwort! sollte als das richtige markiert werden, da es das eigentliche Problem löst: In meinem Fall reicht es nicht aus zu überprüfen, ob getActivity () nicht null ist, da ich meine Aufgabe auf jeden Fall erledigen muss. Ich benutze dies und es funktioniert perfekt.
Hadas Kaminsky
18

Das Beste, um dies zu beseitigen, besteht darin, die Aktivitätsreferenz zu behalten, wenn sie onAttachaufgerufen wird, und die Aktivitätsreferenz zu verwenden, wo immer dies erforderlich ist, z

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    mContext = context;
}

@Override
public void onDetach() {
    super.onDetach();
    mContext = null;
}

Bearbeitet, da onAttach(Activity)abgeschrieben wird & jetzt onAttach(Context)verwendet wird

Pawan Maheshwari
quelle
9
Fragmente behalten immer die Referenz der übergeordneten Aktivität bei und stellen Sie mit der Methode getActivity () zur Verfügung. Hier behalten wir dieselbe Referenz bei.
Pawan Maheshwari
8
Google empfiehlt dies tatsächlich, wenn Sie Ihr Fragment benötigen, um Ereignisse für die Aktivität freizugeben. developer.android.com/guide/components/fragments.html (suchen Sie nach "Erstellen von Ereignisrückrufen für die Aktivität")
Vering
6
Möglicherweise möchten Sie die Methode onDetach hinzufügen, mit der die Aktivitätsreferenz ungültig wird
Mitternacht,
2
yeah initialisiere mActivity = null bei der onDetach-Methode, um diese Aktivitätsreferenz aufzuheben.
Pawan Maheshwari
19
Mach das niemals. Sie verlieren Ihre gesamte Aktivität (und damit den gesamten Layoutbaum mit Drawables und dergleichen). Wenn getActivity()null zurückgegeben wird, liegt dies daran, dass Sie sich nicht mehr in einer Aktivität befinden. Dies ist eine schmutzige Problemumgehung.
NJZK2
10

Rufen Sie keine Methoden innerhalb des Fragments auf, für die getActivity () erforderlich ist, bis onStart in der übergeordneten Aktivität.

private MyFragment myFragment;


public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    myFragment = new MyFragment();

    ft.add(android.R.id.content, youtubeListFragment).commit();

    //Other init calls
    //...
}


@Override
public void onStart()
{
    super.onStart();

    //Call your Fragment functions that uses getActivity()
    myFragment.onPageSelected();
}
bvmobileapps
quelle
Eigentlich hatte ich ein ähnliches Problem, weil ich die Aufgabe im Fragmentkonstruktor gestartet habe. Vielen Dank.
Supreme Dolphin
4

Ich habe eine Weile mit dieser Art von Problem gekämpft und ich denke, ich habe eine zuverlässige Lösung gefunden.

Es ist ziemlich schwierig, sicher zu wissen , dass this.getActivity()nicht zurückkehren wird nullfür ein Fragment, vor allem , wenn Sie mit jeder Art von Netzwerkverhalten zu tun haben , die Ihrem Code genügend Zeit gibt zurückzuziehen ActivityReferenzen.

In der folgenden Lösung deklariere ich eine kleine Verwaltungsklasse namens ActivityBuffer. Im Wesentlichen geht es hier classdarum, eine zuverlässige Referenz auf einen Eigentümer zu erhalten Activityund zu versprechen, Runnables in einem gültigen ActivityKontext auszuführen, wenn eine gültige Referenz verfügbar ist. Die Runnables werden sofort auf dem UI-Thread ausgeführt, wenn sie Contextverfügbar sind. Andernfalls wird die Ausführung verschoben, bis sie Contextfertig sind.

/** A class which maintains a list of transactions to occur when Context becomes available. */
public final class ActivityBuffer {

    /** A class which defines operations to execute once there's an available Context. */
    public interface IRunnable {
        /** Executes when there's an available Context. Ideally, will it operate immediately. */
        void run(final Activity pActivity);
    }

    /* Member Variables. */
    private       Activity        mActivity;
    private final List<IRunnable> mRunnables;

    /** Constructor. */
    public ActivityBuffer() {
        // Initialize Member Variables.
        this.mActivity  = null;
        this.mRunnables = new ArrayList<IRunnable>();
    }

    /** Executes the Runnable if there's an available Context. Otherwise, defers execution until it becomes available. */
    public final void safely(final IRunnable pRunnable) {
        // Synchronize along the current instance.
        synchronized(this) {
            // Do we have a context available?
            if(this.isContextAvailable()) {
                // Fetch the Activity.
                final Activity lActivity = this.getActivity();
                // Execute the Runnable along the Activity.
                lActivity.runOnUiThread(new Runnable() { @Override public final void run() { pRunnable.run(lActivity); } });
            }
            else {
                // Buffer the Runnable so that it's ready to receive a valid reference.
                this.getRunnables().add(pRunnable);
            }
        }
    }

    /** Called to inform the ActivityBuffer that there's an available Activity reference. */
    public final void onContextGained(final Activity pActivity) {
        // Synchronize along ourself.
        synchronized(this) {
            // Update the Activity reference.
            this.setActivity(pActivity);
            // Are there any Runnables awaiting execution?
            if(!this.getRunnables().isEmpty()) {
                // Iterate the Runnables.
                for(final IRunnable lRunnable : this.getRunnables()) {
                    // Execute the Runnable on the UI Thread.
                    pActivity.runOnUiThread(new Runnable() { @Override public final void run() {
                        // Execute the Runnable.
                        lRunnable.run(pActivity);
                    } });
                }
                // Empty the Runnables.
                this.getRunnables().clear();
            }
        }
    }

    /** Called to inform the ActivityBuffer that the Context has been lost. */
    public final void onContextLost() {
        // Synchronize along ourself.
        synchronized(this) {
            // Remove the Context reference.
            this.setActivity(null);
        }
    }

    /** Defines whether there's a safe Context available for the ActivityBuffer. */
    public final boolean isContextAvailable() {
        // Synchronize upon ourself.
        synchronized(this) {
            // Return the state of the Activity reference.
            return (this.getActivity() != null);
        }
    }

    /* Getters and Setters. */
    private final void setActivity(final Activity pActivity) {
        this.mActivity = pActivity;
    }

    private final Activity getActivity() {
        return this.mActivity;
    }

    private final List<IRunnable> getRunnables() {
        return this.mRunnables;
    }

}

In Bezug auf die Implementierung müssen wir darauf achten, dass die Lebenszyklusmethoden so angewendet werden, dass sie mit dem oben von Pawan M beschriebenen Verhalten übereinstimmen :

public class BaseFragment extends Fragment {

    /* Member Variables. */
    private ActivityBuffer mActivityBuffer;

    public BaseFragment() {
        // Implement the Parent.
        super();
        // Allocate the ActivityBuffer.
        this.mActivityBuffer = new ActivityBuffer();
    }

    @Override
    public final void onAttach(final Context pContext) {
        // Handle as usual.
        super.onAttach(pContext);
        // Is the Context an Activity?
        if(pContext instanceof Activity) {
            // Cast Accordingly.
            final Activity lActivity = (Activity)pContext;
            // Inform the ActivityBuffer.
            this.getActivityBuffer().onContextGained(lActivity);
        }
    }

    @Deprecated @Override
    public final void onAttach(final Activity pActivity) {
        // Handle as usual.
        super.onAttach(pActivity);
        // Inform the ActivityBuffer.
        this.getActivityBuffer().onContextGained(pActivity);
    }

    @Override
    public final void onDetach() {
        // Handle as usual.
        super.onDetach();
        // Inform the ActivityBuffer.
        this.getActivityBuffer().onContextLost();
    }

    /* Getters. */
    public final ActivityBuffer getActivityBuffer() {
        return this.mActivityBuffer;
    }

}

Schließlich können Sie in allen Bereichen in Ihrem Bereich Fragment, in BaseFragmentdenen Sie in Bezug auf einen Anruf nicht vertrauenswürdig sind getActivity(), einfach einen Anruf tätigen this.getActivityBuffer().safely(...)und einen ActivityBuffer.IRunnablefür die Aufgabe deklarieren !

Der Inhalt von void run(final Activity pActivity)wird dann garantiert entlang des UI-Threads ausgeführt.

Das ActivityBufferkann dann wie folgt verwendet werden:

this.getActivityBuffer().safely(
  new ActivityBuffer.IRunnable() {
    @Override public final void run(final Activity pActivity) {
       // Do something with guaranteed Context.
    }
  }
);
Mapsy
quelle
Können Sie ein Beispiel für die Verwendung der Methode this.getActivityBuffer (). Safe (...) hinzufügen?
fahad_sust
3
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}
Mohanraj Balasubramaniam
quelle
Könnten Sie bitte Ihre Antwort näher erläutern und etwas mehr Beschreibung der von Ihnen bereitgestellten Lösung hinzufügen?
Abarisone
1

Ich weiß, dass dies eine alte Frage ist, aber ich denke, ich muss meine Antwort darauf geben, weil mein Problem nicht von anderen gelöst wurde.

Zunächst einmal: Ich habe Fragmente mithilfe von fragmentTransactions dynamisch hinzugefügt. Zweitens: Meine Fragmente wurden mithilfe von AsyncTasks (DB-Abfragen auf einem Server) geändert. Drittens: Mein Fragment wurde beim Aktivitätsstart nicht instanziiert. Viertens: Ich habe eine benutzerdefinierte Fragmentinstanziierung "Erstellen oder Laden" verwendet, um die Fragmentvariable abzurufen. Viertens: Die Aktivität wurde aufgrund einer Orientierungsänderung neu erstellt

Das Problem war, dass ich das Fragment wegen der Abfrageantwort "entfernen" wollte, aber das Fragment wurde kurz zuvor falsch erstellt. Ich weiß nicht, warum das Fragment, wahrscheinlich weil das "Commit" später durchgeführt wird, noch nicht hinzugefügt wurde, als es Zeit war, es zu entfernen. Daher gab getActivity () null zurück.

Lösung: 1) Ich musste überprüfen, ob ich richtig versucht habe, die erste Instanz des Fragments zu finden, bevor ich eine neue erstellte. 2) Ich musste serRetainInstance (true) auf dieses Fragment setzen, um es durch Orientierungsänderungen zu erhalten (kein Backstack) benötigt daher kein Problem) 3) Anstatt "altes Fragment neu zu erstellen oder abzurufen" kurz vor "entfernen", setze ich das Fragment direkt auf Aktivitätsstart. Das Instanziieren beim Aktivitätsstart anstelle des "Ladens" (oder Instanziierens) der Fragmentvariablen vor dem Entfernen verhinderte Probleme mit getActivity.

Feuby
quelle
0

In Kotlin können Sie auf diese Weise versuchen, die Nullbedingung getActivity () zu behandeln.

   activity.let { // activity == getActivity() in java

        //your code here

   }

Es wird überprüft, ob die Aktivität null ist oder nicht. Wenn nicht null, wird der innere Code ausgeführt.

Sachin
quelle