Android. Fragment getActivity () czasami zwraca null

194

W raportach błędów konsoli programisty czasami widzę raporty z problemem NPE. Nie rozumiem, co jest nie tak z moim kodem. Na emulatorze i mojej aplikacji urządzenia działa dobrze bez forcecloses, jednak niektórzy użytkownicy otrzymują NullPointerException w klasie fragmentów, gdy wywoływana jest metoda getActivity ().

Czynność

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

Klasa AsyncTask

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

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

Klasa fragmentów

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

Być może ten błąd występuje, gdy aplikacje są wznawiane z tła. W takim przypadku, jak powinienem właściwie poradzić sobie z tą sytuacją?

Georgy Gobozov
źródło
Znalazłem problem, ale nie rozwiązanie. Nie wiem dlaczego, ale fragment wznawia wcześniejszą aktywność. Dzieje się tak tylko wtedy, gdy moja aplikacja na ostatniej pozycji na liście ostatnio używanych aplikacji wydaje się, że system niszczy moją aplikację.
Georgy Gobozov
1
Po wznowieniu aplikacji z fragmentu tła onCreate wywołuje onResume wywołany przed aktywnością metody onCreate / onResume. Wydaje się, że jakiś oderwany fragment wciąż żyje i próbuje wznowić.
Georgy Gobozov,
1
w tym ciągu firstTask.setTaskListener ((TaskListener) adapter.getItem (0)); adapter.getItem (0) zwraca stary fragment, adapter nie usuwa fragmentów poprawnie
Georgy Gobozov
9
Nawiasem mówiąc, świetna aktywność :) zadane pytanie, pozostawione komentarze i udzielona odpowiedź - wszystkie są wykonywane przez jedną osobę! +1 za te.
Prizoff,
zapisz kontekst (getActivity ()) w onCreateView (), ponieważ jest to wywoływane, gdy widok jest odtwarzany w tle.
sha

Odpowiedzi:

123

Wygląda na to, że znalazłem rozwiązanie mojego problemu. Tu i tutaj podano bardzo dobre wyjaśnienia . Oto mój przykład:

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

Główną ideą tego kodu jest to, że podczas normalnego działania aplikacji tworzysz nowe fragmenty i przekazujesz je do adaptera. Gdy wznawiasz, menedżer fragmentów aplikacji ma już instancję tego fragmentu i musisz go pobrać z menedżera fragmentów i przekazać go do adaptera.

AKTUALIZACJA

Dobrą praktyką jest także używanie fragmentów do sprawdzania isAdded przed wywołaniem getActivity (). Pomaga to uniknąć wyjątku wskaźnika zerowego, gdy fragment jest odłączony od działania. Na przykład działanie może zawierać fragment, który wypycha zadanie asynchroniczne. Po zakończeniu zadania wywoływany jest detektor onTaskComplete.

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

}

Jeśli otworzymy fragment, wypchniemy zadanie, a następnie szybko wrócimy do poprzedniego działania, po zakończeniu zadania spróbuje uzyskać dostęp do działania w onPostExecute (), wywołując metodę getActivity (). Jeśli działanie jest już odłączone, a tego sprawdzenia nie ma:

if (isAdded()) 

następnie aplikacja ulega awarii.

Georgy Gobozov
źródło
56
Jest to denerwujące, ponieważ trzeba dzwonić isAdded()przed każdym dostępem ... sprawia, że ​​kod jest brzydki.
Ixx
25
Wydaje się, że nie ma dużej różnicy między posiadaniem if(isAdded())lubif(getActivity() != null)
StackOverflow Flow
19

Ok, wiem, że to pytanie zostało rozwiązane, ale postanowiłem podzielić się z tym moim rozwiązaniem. Stworzyłem abstrakcyjną klasę nadrzędną dla mojego 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;
        }
    }
}

Jak widzisz, dodałem detektora, więc za każdym razem, gdy będę musiał uzyskać Fragments Activityzamiast standardowego getActivity(), będę musiał zadzwonić

 getAvailableActivity(new IActivityEnabledListener() {
        @Override
        public void onActivityEnabled(FragmentActivity activity) {
            // Do manipulations with your activity
        }
    });
Paul Freez
źródło
Świetna odpowiedź! powinien być oznaczony jako poprawny, ponieważ rozwiązuje prawdziwy problem: W moim przypadku nie wystarczy sprawdzić, czy getActivity () nie ma wartości zerowej, ponieważ muszę wykonać zadanie bez względu na wszystko. Używam tego i działa idealnie.
Hadas Kaminsky
18

Najlepszym sposobem na pozbycie się tego jest zachowanie odwołania do aktywności, gdy onAttachjest wywoływane, i używanie odwołania do aktywności tam, gdzie jest to potrzebne, na przykład

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

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

Edytowane, ponieważ onAttach(Activity)jest amortyzowane i teraz onAttach(Context)jest używane

Pawan Maheshwari
źródło
9
Fragmenty zawsze zachowują referencję swojej aktywności nadrzędnej i udostępniają metodę getActivity (), tutaj zachowujemy tę samą referencję.
Pawan Maheshwari,
8
Google faktycznie zaleca to, jeśli potrzebujesz fragmentu, aby udostępnić wydarzenia aktywności. developer.android.com/guide/components/fragments.html (poszukaj „Tworzenie wywołań zwrotnych zdarzeń dotyczących działania”)
Vering
6
możesz dodać metodę onDetach, która unieważnia odwołanie do aktywności
północ
2
tak, zainicjuj mActivity = null w metodzie onDetach, aby zerować to odwołanie do działania.
Pawan Maheshwari
19
nigdy tego nie rób. przeciekasz całą swoją aktywność (a wraz z nią całe drzewo układu, z elementami do rysowania itp.). Jeśli getActivity()zwraca null, oznacza to, że nie jesteś już w aktywności. To jest brudne obejście.
njzk2
10

Nie należy wywoływać metod w ramach fragmentu, które wymagają getActivity (), dopóki onStart w działaniu nadrzędnym.

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
źródło
Właściwie miałem podobny problem, ponieważ zaczynałem zadanie w konstruktorze fragmentów. Wielkie dzięki.
Supreme Dolphin
4

Od pewnego czasu zmagam się z tego rodzaju problemami i wydaje mi się, że wpadłem na niezawodne rozwiązanie.

Jest to dość trudne wiedzieć na pewno, że this.getActivity()nie ma powrotu nulldla Fragment, zwłaszcza jeśli mamy do czynienia z wszelkiego rodzaju zachowania sieci, która daje kod wystarczająco dużo czasu, aby wycofać Activityreferencje.

W poniższym rozwiązaniu deklaruję małą klasę zarządzania o nazwie ActivityBuffer. Zasadniczo classdotyczy to zachowania wiarygodnego odwołania do własności Activityi obiecania wykonania Runnables we właściwym Activitykontekście, gdy tylko dostępne jest prawidłowe odwołanie. Do Runnables są zaplanowane do wykonania na wątków UI natychmiast, jeśli Contextsą dostępne, w przeciwnym razie wykonanie jest odroczone do czasu, że Contextjest gotowy.

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

}

Jeśli chodzi o jego realizacji, musimy zadbać, aby zastosować cyklu życia metod w czasie z zachowaniem opisanym powyżej Pawan M :

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

}

Wreszcie, we wszystkich obszarach, Fragmentktóre rozciągają BaseFragmentsię na niewiarygodne połączenie getActivity(), po prostu zadzwoń this.getActivityBuffer().safely(...)i zadeklaruj ActivityBuffer.IRunnablezadanie!

void run(final Activity pActivity)Gwarantuje to, że zawartość Twojego pliku zostanie wykonana wzdłuż wątku interfejsu użytkownika.

ActivityBufferMoże być wykorzystywane w następujący sposób:

this.getActivityBuffer().safely(
  new ActivityBuffer.IRunnable() {
    @Override public final void run(final Activity pActivity) {
       // Do something with guaranteed Context.
    }
  }
);
Mapsy
źródło
Czy możesz dodać przykład użycia metody this.getActivityBuffer (). Bezpiecznie (...).
fahad_sust
3
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}
Mohanraj Balasubramaniam
źródło
Czy mógłby Pan uściślić swoją odpowiedź, dodając nieco więcej opisu oferowanego rozwiązania?
abarisone
1

Wiem, że to stare pytanie, ale myślę, że muszę udzielić odpowiedzi na to pytanie, ponieważ inni nie rozwiązali mojego problemu.

po pierwsze: dynamicznie dodawałem fragmenty za pomocą fragmentTransactions. Po drugie: moje fragmenty zostały zmodyfikowane za pomocą AsyncTasks (zapytania DB na serwerze). Po trzecie: mój fragment nie został utworzony w momencie rozpoczęcia działania Po czwarte: użyłem niestandardowej instancji fragmentu „utwórz lub załaduj”, aby uzyskać zmienną fragmentu. Po czwarte: aktywność została odtworzona z powodu zmiany orientacji

Problem polegał na tym, że chciałem „usunąć” fragment z powodu odpowiedzi na zapytanie, ale fragment został wcześniej nieprawidłowo utworzony. Nie wiem, dlaczego, prawdopodobnie z powodu „zatwierdzenia”, które zostało wykonane później, fragment nie został jeszcze dodany, kiedy nadszedł czas, aby go usunąć. Dlatego getActivity () zwracał null.

Rozwiązanie: 1) Musiałem sprawdzić, czy poprawnie próbowałem znaleźć pierwszą instancję fragmentu, zanim utworzyłem nowy 2) Musiałem umieścić serRetainInstance (true) na tym fragmencie, aby zachować go poprzez zmianę orientacji (bez backstacka dlatego nie ma problemu) 3) Zamiast „odtworzenia lub odzyskania starego fragmentu” tuż przed „usunięciem”, bezpośrednio włączam fragment na początku aktywności. Tworzenie instancji na początku działania zamiast „ładowania” (lub tworzenia instancji) zmiennej fragmentu przed jej usunięciem zapobiegło problemom z getActivity.

Feuby
źródło
0

W Kotlin możesz wypróbować ten sposób do obsługi warunku getActivity () null.

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

        //your code here

   }

Sprawdzi, czy aktywność jest zerowa, czy nie, a jeśli nie, uruchom kod wewnętrzny.

Sachin
źródło