android android-fragments android-activity android-asynctask nullpointerexception

refresh fragment android



Androide. Fragmento getActivity() a veces devuelve nulo (7)

En los informes de error de la consola del desarrollador, a veces veo informes con el problema 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 forzar el cierre, 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 ocurra cuando las aplicaciones se reanudaron desde el fondo. En este caso, ¿cómo debo manejar esta situación correctamente?


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 devolverá null para un Fragment , especialmente si se trata de algún tipo de comportamiento de red que le da a su código tiempo suficiente para retirar las referencias de Activity .

En la siguiente solución, declaro una pequeña clase de gestión llamada ActivityBuffer . Básicamente, esta class trata sobre el mantenimiento de una referencia confiable a una Activity propietaria y la promesa de ejecutar Runnable dentro de un contexto de Activity válido siempre que haya una referencia válida disponible. Los Runnable s están programados para su ejecución en el subproceso UI inmediatamente si el Context está disponible; de ​​lo contrario, la ejecución se aplaza hasta que el Context esté listo.

/** 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 :

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 Fragment que extienda BaseFragment cual usted no sea confiable acerca de una llamada a getActivity() , simplemente realice una llamada a this.getActivityBuffer().safely(...) y declare ActivityBuffer.IRunnable para la tarea !

Luego se garantiza que los contenidos de su void run(final Activity pActivity) se ejecuten a lo largo del subproceso UI.


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

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

Editado, ya que onAttach (Activity) está depreciado y ahora se usa onAttach (Context)


No invoque métodos dentro del Fragmento que requieran getActivity () hasta onStart en la Actividad primaria.

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


Ok, sé que esta pregunta está realmente resuelta, pero decidí compartir mi solución para esto. Creé una clase primaria 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, agregué un oyente así que, siempre que necesite obtener Activity Fragments lugar de getActivity() estándar, tendré que llamar

getAvailableActivity(new IActivityEnabledListener() { @Override public void onActivityEnabled(FragmentActivity activity) { // Do manipulations with your activity } });


Parece que encontré una solución a mi problema. Muy buenas explicaciones se dan here y here . 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 en este código es que, mientras ejecuta su aplicación normalmente, crea nuevos fragmentos y los pasa al adaptador. Cuando está reanudando su aplicación, el administrador de fragmentos 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 cuando se usan fragmentos comprobar que se ha agregado 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 la tarea finaliza, 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, pulsamos una tarea y luego presionamos rápidamente hacia atrás para volver a una actividad anterior, cuando la tarea finalice, intentará acceder a la actividad en onPostExecute () llamando al método getActivity (). Si la actividad ya está separada y esta comprobación no está allí:

if (isAdded())

entonces la aplicación falla.


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

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 en el inicio de la actividad. Cuarto: utilicé una creación de fragmentos personalizada "crear o cargarlo" para obtener la variable del fragmento. Cuarto: la actividad fue recreada 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 que el "compromiso" se hizo más tarde, el fragmento no se agregó aún cuando llegó el momento de eliminarlo. Por lo tanto, getActivity () devolvía nulo.

Solución: 1) Tuve que comprobar que estaba tratando de encontrar la primera instancia del fragmento antes de crear uno nuevo 2) Tuve que poner serRetainInstance (verdadero) en ese fragmento para mantenerlo en el cambio de orientación (sin backstack) es necesario, por lo tanto, no hay problema) 3) En lugar de "volver a crear o obtener fragmentos viejos" justo antes de "eliminarlo", puse directamente el fragmento en el inicio de la actividad. Al crear una instancia al inicio de la actividad en lugar de "cargar" (o instanciar) la variable de fragmento antes de eliminarlo, se impidieron los problemas de getActivity.


@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // run the code making use of getActivity() from here }