Androide. El fragmento getActivity () a veces devuelve nulo

194

En los informes de errores de la consola del desarrollador, a veces veo informes con problemas de NPE. No entiendo lo que está mal con mi código. En el emulador y la aplicación de mi dispositivo funciona bien sin cierres forzados, sin embargo, algunos usuarios obtienen NullPointerException en la clase de fragmento cuando se llama al método getActivity ().

Actividad

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

Clase AsyncTask

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

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

Clase de fragmento

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

Tal vez este error ocurre cuando las aplicaciones se reanudaron desde el fondo. En este caso, ¿cómo debo manejar esta situación correctamente?

Georgy Gobozov
fuente
Resolví un problema, pero no una solución. No sé por qué, pero el fragmento reanudará la actividad anterior. Y esto solo sucede cuando mi aplicación en la última posición en la lista de aplicaciones recientes, parece que el sistema destruye mi aplicación.
Georgy Gobozov
1
Cuando reanudo mi aplicación desde el fondo fragmetn onCreate un onResume llamado antes del método de actividad onCreate / onResume. Parece un fragmento separado aún vivo e intentando reanudar.
Georgy Gobozov
1
en esta cadena firstTask.setTaskListener ((TaskListener) adapter.getItem (0)); adapter.getItem (0) devuelve fragmento antiguo, el adaptador no elimina fragmentos correctamente
Georgy Gobozov
9
Gran actividad por cierto :) pregunta formulada, comentarios restantes y respuesta dada: ¡todo lo hace una sola persona! +1 para estos.
Prizoff
guarde el contexto (getActivity ()) en onCreateView () ya que esto se llama cuando la vista se recrea en el caso de fondo.
sha

Respuestas:

123

Parece que encontré una solución a mi problema. Muy buenas explicaciones se dan aquí y aquí . Aquí está mi ejemplo:

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

La idea principal de este código es que, mientras ejecuta su aplicación normalmente, crea nuevos fragmentos y los pasa al adaptador. Cuando reanude su administrador de fragmentos de aplicaciones ya tiene la instancia de este fragmento y necesita obtenerlo del administrador de fragmentos y pasarlo al adaptador.

ACTUALIZAR

Además, es una buena práctica usar fragmentos para verificar isAdded antes de llamar a getActivity (). Esto ayuda a evitar una excepción de puntero nulo cuando el fragmento se separa de la actividad. Por ejemplo, una actividad podría contener un fragmento que empuja una tarea asíncrona. Cuando finaliza la tarea, se llama al oyente 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();
    }

}

Si abrimos el fragmento, empujamos una tarea y luego presionamos rápidamente para regresar a una actividad anterior, cuando la tarea haya finalizado, intentará acceder a la actividad en onPostExecute () llamando al método getActivity (). Si la actividad ya está separada y esta verificación no está allí:

if (isAdded()) 

entonces la aplicación se bloquea.

Georgy Gobozov
fuente
56
Sin embargo, esto es molesto, tener que llamar isAdded()antes de cada acceso ... hace que el código sea feo.
Ixx
25
No parece haber mucha diferencia entre tener if(isAdded())oif(getActivity() != null)
StackOverflowed
19

Ok, sé que esta pregunta está realmente resuelta, pero decidí compartir mi solución para esto. He creado una clase padre abstracta para mi 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;
        }
    }
}

Como puede ver, he agregado un oyente, así que cada vez que necesite obtener en Fragments Activitylugar de estándar getActivity(), necesitaré llamar

 getAvailableActivity(new IActivityEnabledListener() {
        @Override
        public void onActivityEnabled(FragmentActivity activity) {
            // Do manipulations with your activity
        }
    });
Paul Freez
fuente
¡Gran respuesta! debe marcarse como la correcta ya que resuelve el problema real: en mi caso, no es suficiente comprobar que getActivity () no es nulo porque debo completar mi tarea sin importar qué. Estoy usando esto y funciona perfectamente.
Hadas Kaminsky
18

Lo mejor para deshacerse de esto es mantener la referencia de actividad cuando onAttachse llama y usar la referencia de actividad donde sea necesario, por ejemplo

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

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

Editado, ya que onAttach(Activity)se deprecia y ahora onAttach(Context)se está utilizando

Pawan Maheshwari
fuente
9
Fragments siempre mantiene la referencia de su actividad principal y lo pone a su disposición con el método getActivity (), aquí mantenemos la misma referencia.
Pawan Maheshwari
8
Google realmente recomienda esto si necesita su fragmento para compartir eventos con la actividad. developer.android.com/guide/components/fragments.html (busque "Crear devoluciones de llamada de eventos a la actividad")
Vering
66
es posible que desee agregar el método onDetach, que anula la referencia de actividad
medianoche
2
sí, inicialice mActivity = null en el método onDetach para anular esa referencia de actividad.
Pawan Maheshwari
19
Nunca hagas eso. está filtrando su actividad completa (y con ella todo el árbol de diseño, con elementos dibujables y demás). Si getActivity()devuelve nulo, es porque ya no estás en una actividad. Esta es una solución sucia.
njzk2
10

No llame a métodos dentro del Fragmento que requieran getActivity () hasta que se inicie en la Actividad principal.

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
fuente
En realidad, tuve un problema similar porque estaba comenzando la tarea en el constructor de fragmentos. Muchas gracias.
Supreme Dolphin
4

He estado luchando contra este tipo de problema. por un tiempo, y creo que he encontrado una solución confiable.

Es bastante difícil saber con certeza que this.getActivity()no va a volver nullpor un Fragment, especialmente si está lidiando con algún tipo de comportamiento de red que le da a su código suficiente tiempo para retirarseActivity referencias.

En la solución a continuación, declaro una pequeña clase de administración llamada ActivityBuffer. Esencialmente, esto classtrata de mantener una referencia confiable a un propietario Activityy promete ejecutar Runnables dentro de un Activitycontexto válido siempre que haya una referencia válida disponible. Los Runnables están programados para su ejecución en el subproceso de la interfaz de usuario inmediatamente si Contextestá disponible; de ​​lo contrario, la ejecución se aplaza hasta que Contextesté lista.

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

}

En términos de su implementación, debemos tener cuidado de aplicar los métodos del ciclo de vida para que coincidan con el comportamiento descrito anteriormente por 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;
    }

}

Finalmente, en cualquier área dentro de su área Fragmentque se extienda y BaseFragmentque no sea confiable para una llamada getActivity(), simplemente haga una llamada this.getActivityBuffer().safely(...)y declare unActivityBuffer.IRunnable para la tarea!

El contenido de su void run(final Activity pActivity)se garantiza que se ejecutará a lo largo del hilo de la interfaz de usuario.

La ActivityBuffercontinuación, se puede utilizar como sigue:

this.getActivityBuffer().safely(
  new ActivityBuffer.IRunnable() {
    @Override public final void run(final Activity pActivity) {
       // Do something with guaranteed Context.
    }
  }
);
Mapsy
fuente
¿Puede agregar un ejemplo del uso de este método get.getActivityBuffer (). Safe (...)?
fahad_sust
3
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}
Mohanraj Balasubramaniam
fuente
¿Podría por favor elaborar más su respuesta agregando un poco más de descripción sobre la solución que proporciona?
abarisone
1

Sé que esta es una pregunta antigua, pero creo que debo proporcionar mi respuesta porque otros no resolvieron mi problema.

en primer lugar: estaba agregando fragmentos dinámicamente usando fragmentTransactions. Segundo: mis fragmentos fueron modificados usando AsyncTasks (consultas DB en un servidor). Tercero: mi fragmento no fue instanciado al inicio de la actividad Cuarto: utilicé una instanciación de fragmento personalizada "crear o cargar" para obtener la variable del fragmento. Cuarto: la actividad se recreó debido al cambio de orientación

El problema era que quería "eliminar" el fragmento debido a la respuesta de la consulta, pero el fragmento se creó incorrectamente justo antes. No sé por qué, probablemente debido a la "confirmación" que se realizará más tarde, el fragmento aún no se agregó cuando llegó el momento de eliminarlo. Por lo tanto, getActivity () estaba devolviendo nulo.

Solución: 1) Tuve que verificar que estaba tratando de encontrar correctamente la primera instancia del fragmento antes de crear uno nuevo 2) Tuve que poner serRetainInstance (true) en ese fragmento para mantenerlo a través del cambio de orientación (sin apilamiento) por lo tanto, no es necesario ningún problema) 3) En lugar de "recrear o recuperar un fragmento viejo" justo antes de "eliminarlo", puse directamente el fragmento al inicio de la actividad. Crear una instancia al inicio de la actividad en lugar de "cargar" (o crear instancias) la variable de fragmento antes de eliminarla evitó problemas de getActivity.

Feuby
fuente
0

En Kotlin puede intentar de esta manera manejar la condición nula getActivity ().

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

        //your code here

   }

Verificará que la actividad sea nula o no y, si no es nula, ejecutará el código interno.

Sachin
fuente