soporte FragmentPagerAdapter contiene referencia a fragmentos antiguos

ÚLTIMA INFORMACIÓN:

reduje mi problema a ser un problema con fragmentManager conservando instancias de fragmentos antiguos y mi viewpager no está sincronizado con mi FragmentManager. Consulte este problema … http://code.google.com/p/android/issues/detail?id=19211#makechanges . Todavía no tengo ni idea de cómo resolver esto. Alguna sugerencia…

Intenté depurar esto durante mucho tiempo y cualquier ayuda sería muy apreciada. Estoy usando un FragmentPagerAdapter que acepta una lista de fragmentos como esta:

List fragments = new Vector(); fragments.add(Fragment.instantiate(this, Fragment1.class.getName())); ... new PagerAdapter(getSupportFragmentManager(), fragments); 

La implementación es estándar. Estoy utilizando ActionBarSherlock y v4 computability library para Fragments.

Mi problema es que después de salir de la aplicación, abrir varias aplicaciones y regresar, los fragmentos pierden su referencia a FragmentActivity (es decir, getActivity() == null ). No puedo entender por qué está pasando esto. Traté de establecer manualmente setRetainInstance(true); pero esto no ayuda. Pensé que esto sucedería cuando mi FragmentActivity se destruyera; sin embargo, esto todavía ocurre si abro la aplicación antes de recibir el mensaje de registro. ¿Hay alguna idea?

 @Override protected void onDestroy(){ Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY"); super.onDestroy(); } 

El adaptador:

 public class PagerAdapter extends FragmentPagerAdapter { private List fragments; public PagerAdapter(FragmentManager fm, List fragments) { super(fm); this.fragments = fragments; } @Override public Fragment getItem(int position) { return this.fragments.get(position); } @Override public int getCount() { return this.fragments.size(); } } 

Uno de mis Fragmentos se quitó, pero cometned todo lo que se quitó y todavía no funciona …

 public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener { ... @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); handler = new Handler(); setHasOptionsMenu(true); } @Override public void onAttach(Activity activity) { super.onAttach(activity); Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH"); context = activity; if(context== null){ Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL"); }else{ Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT"); } } @Override public void onActivityCreated(Bundle savedState) { super.onActivityCreated(savedState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.my_fragment,container, false); return v; } @Override public void onResume(){ super.onResume(); } private void callService(){ // do not call another service is already running if(startLoad || !canSet) return; // set flag startLoad = true; canSet = false; // show the bottom spinner addFooter(); Intent intent = new Intent(context, MyService.class); intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver); context.startService(intent); } private ResultReceiver resultReceiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, final Bundle resultData) { boolean isSet = false; if(resultData!=null) if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){ if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){ removeFooter(); startLoad = false; isSet = true; } } switch(resultCode){ case MyService.STATUS_FINISHED: stopSpinning(); break; case SyncService.STATUS_RUNNING: break; case SyncService.STATUS_ERROR: break; } } }; public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { menu.clear(); inflater.inflate(R.menu.activity, menu); } @Override public void onPause(){ super.onPause(); } public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) { boolean loadMore = /* maybe add a padding */ firstVisible + visibleCount >= totalCount; boolean away = firstVisible+ visibleCount <= totalCount - visibleCount; if(away){ // startLoad can now be set again canSet = true; } if(loadMore) } public void onScrollStateChanged(AbsListView arg0, int state) { switch(state){ case OnScrollListener.SCROLL_STATE_FLING: adapter.setLoad(false); lastState = OnScrollListener.SCROLL_STATE_FLING; break; case OnScrollListener.SCROLL_STATE_IDLE: adapter.setLoad(true); if(lastState == SCROLL_STATE_FLING){ // load the images on screen } lastState = OnScrollListener.SCROLL_STATE_IDLE; break; case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL: adapter.setLoad(true); if(lastState == SCROLL_STATE_FLING){ // load the images on screen } lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL; break; } } @Override public void onDetach(){ super.onDetach(); if(this.adapter!=null) this.adapter.clearContext(); Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED"); } public void update(final int id, String name) { if(name!=null){ getActivity().getSupportActionBar().setTitle(name); } } 

}

El método de actualización se invoca cuando un usuario interactúa con un fragmento diferente y getActivity devuelve nulo. Este es el método que el otro fragmento llama …

 ((MyFragment) pagerAdapter.getItem(1)).update(id, name); 

Creo que cuando la aplicación se destruye, se crea nuevamente en lugar de simplemente iniciar la aplicación hasta el fragmento predeterminado, la aplicación se inicia y luego el visor de navegación navega a la última página conocida. Esto parece extraño, ¿no debería la aplicación cargar solo el fragmento predeterminado?

Se encuentra con un problema porque está instanciando y guardando referencias a sus fragmentos fuera de PagerAdapter.getItem , y está tratando de usar esas referencias independientemente de ViewPager. Como dice Seraph, tiene garantías de que se ha creado una instancia / agregado de un fragmento en un ViewPager en un momento determinado; esto se debe considerar como un detalle de implementación. Un ViewPager hace una carga lenta de sus páginas; de forma predeterminada, solo carga la página actual, y la de la izquierda y la derecha.

Si coloca su aplicación en segundo plano, los fragmentos que se han agregado al administrador de fragmentos se guardan automáticamente. Incluso si tu aplicación es eliminada, esta información se restaura cuando reinicias tu aplicación.

Ahora considere que ha visto algunas páginas, Fragmentos A, B y C. Usted sabe que estos han sido agregados al administrador de fragmentos. Debido a que está utilizando FragmentPagerAdapter y no FragmentStatePagerAdapter , estos fragmentos seguirán agregándose (pero potencialmente separados) cuando se desplace a otras páginas.

Considere que luego fondo su aplicación, y luego se mata. Cuando regrese, Android recordará que solía tener los fragmentos A, B y C en el administrador de fragmentos, por lo que los recrea para usted y luego los agrega. Sin embargo, los que se agregan al administrador de fragmentos ahora NO son los que tienes en tu lista de fragmentos en tu Actividad.

El FragmentPagerAdapter no intentará llamar a getPosition si ya hay un fragmento agregado para esa posición de página en particular. De hecho, dado que el fragmento creado por Android nunca se eliminará, no tiene ninguna esperanza de reemplazarlo con una llamada a getPosition . Manejarlo también es bastante difícil obtener una referencia porque se agregó con una etiqueta que usted desconoce. Esto es por diseño; se le desaconseja jugar con los fragmentos que está gestionando el buscapersonas. Deberías realizar todas tus acciones dentro de un fragmento, comunicarte con la actividad y solicitar cambiar a una página en particular, si es necesario.

Ahora, de vuelta a tu problema con la actividad que falta. Llamar a pagerAdapter.getItem(1)).update(id, name) después de que todo esto haya sucedido, le devuelve el fragmento en su lista, que aún no se ha agregado al administrador de fragmentos , por lo que no tendrá una referencia de actividad. Quisiera sugerir que su método de actualización debe modificar alguna estructura de datos compartidos (posiblemente administrada por la actividad), y luego cuando se mueve a una página en particular, puede dibujarse basándose en esta información actualizada.

Encontré una solución simple que funcionó para mí.

Haga que su fragmento de adaptador amplíe FragmentStatePagerAdapter en lugar de FragmentPagerAdapter y anule el método onSave para devolver null

 @Override public Parcelable saveState() { return null; } 

Esto evita que Android reproduzca fragmentos


Un día después encontré otra solución mejor.

Llame a setRetainInstance(true) para todos sus fragmentos y guarde referencias a ellos en alguna parte. Lo hice en variable estática en mi actividad, porque está declarado como singleTask y los fragmentos pueden permanecer igual todo el tiempo.

De esta forma, Android no recrea fragmentos, sino que usa las mismas instancias.

Resolví este problema accediendo a mis fragmentos directamente a través del FragmentManager en lugar de hacerlo a través de FragmentPagerAdapter. Primero necesito descubrir la etiqueta del fragmento generado automáticamente por FragmentPagerAdapter …

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

Entonces simplemente obtengo una referencia a ese fragmento y hago lo que necesito así que …

 Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1)); ((MyFragmentInterface) f).update(id, name); viewPager.setCurrentItem(1, true); 

Dentro de mis fragmentos, establecí setRetainInstance(false); para que pueda agregar valores manualmente al paquete savedInstanceState.

 @Override public void onSaveInstanceState(Bundle outState) { if(this.my !=null) outState.putInt("myId", this.my.getId()); super.onSaveInstanceState(outState); } 

y luego en OnCreate, tomo esa tecla y restablezco el estado del fragmento según sea necesario. Una solución fácil que fue difícil (al menos para mí) resolver.

Solución probada de trabajo global.

getSupportFragmentManager() mantiene la referencia nula algunas veces y View pager no crea nuevas, ya que encuentra referencia al mismo fragmento. Así que para superar este uso getChildFragmentManager() resuelve el problema de una manera simple.

No hagas esto:

new PagerAdapter(getSupportFragmentManager(), fragments);

Hacer esto:

new PagerAdapter(getChildFragmentManager() , fragments);

No intente interactuar entre fragmentos en ViewPager. No puedes garantizar que otro fragmento esté adjunto o que exista. En lugar de cambiar el título de la barra de acciones de un fragmento, puedes hacerlo desde tu actividad. Use un patrón de interfaz estándar para esto:

 public interface UpdateCallback { void update(String name); } public class MyActivity extends FragmentActivity implements UpdateCallback { @Override public void update(String name) { getSupportActionBar().setTitle(name); } } public class MyFragment extends Fragment { private UpdateCallback callback; @Override public void onAttach(SupportActivity activity) { super.onAttach(activity); callback = (UpdateCallback) activity; } @Override public void onDetach() { super.onDetach(); callback = null; } public void updateActionbar(String name) { if(callback != null) callback.update(name); } } 

Después de unas horas de buscar un problema similar, creo que tengo otra solución. Al menos me funcionó y solo tengo que cambiar un par de líneas.

Este es el problema que tuve, tengo una actividad con un buscapersonas que usa un FragmentStatePagerAdapter con dos Fragmentos. Todo funciona bien hasta que obligo a la actividad a ser destruida (opciones de desarrollador) o giro la pantalla. Guardo una referencia a los dos fragmentos después de que se crean dentro del método getItem.

En ese momento, la actividad se creará de nuevo y todo funciona bien en este punto, pero he perdido la referencia a mis fragmentos ya que getItem no se vuelve a llamar.

Así es como solucioné ese problema, dentro de FragmentStatePagerAdapter:

  @Override public Object instantiateItem(ViewGroup container, int position) { Object aux = super.instantiateItem(container, position); //Update the references to the Fragments we have on the view pager if(position==0){ fragTabOne = (FragOffersList)aux; } else{ fragTabTwo = (FragOffersList) aux; } return aux; } 

No recibirá una llamada en getItem nuevamente si el adaptador ya tiene una referencia interna, y no debe cambiar eso. En su lugar, puede obtener el fragmento que está utilizando al mirar este otro método, instanciatearItem (), que será invocado para cada uno de sus fragmentos.

Espero que ayude a cualquiera.

Puedes eliminar los fragmentos cuando destruyes el viewpager, en mi caso, los onDestroyView() en onDestroyView() de mi fragmento:

 @Override public void onDestroyView() { if (getChildFragmentManager().getFragments() != null) { for (Fragment fragment : getChildFragmentManager().getFragments()) { getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss(); } } super.onDestroyView(); } 

Dado que FragmentManager se encargará de restaurar tus Fragmentos tan pronto como se llame al método onResume () tengo el fragmento llamado a la actividad y se agrega a una lista. En mi caso, estoy almacenando todo esto en mi implementación de PagerAdapter. Cada fragmento conoce su posición porque se agrega a los argumentos del fragmento en la creación. Ahora, cada vez que necesito manipular un fragmento en un índice específico, todo lo que tengo que hacer es usar la lista de mi adaptador.

El siguiente es un ejemplo de un Adaptador para una ViewPager personalizada que hará crecer el fragmento a medida que se mueve al foco, y lo reducirá a medida que se desenfoque. Además de las clases de Adaptador y Fragmento que tengo aquí, todo lo que necesita es que la actividad principal pueda hacer referencia a la variable del adaptador y que esté configurado.

Adaptador

 public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener { public final String TAG = this.getClass().getSimpleName(); private final int COUNT = 4; public static final float BASE_SIZE = 0.8f; public static final float BASE_ALPHA = 0.8f; private int mCurrentPage = 0; private boolean mScrollingLeft; private List mFragments; public int getCurrentPage() { return mCurrentPage; } public void addFragment(SummaryTabletFragment fragment) { mFragments.add(fragment.getPosition(), fragment); } public GrowPagerAdapter(FragmentManager fm) { super(fm); mFragments = new ArrayList(); } @Override public int getCount() { return COUNT; } @Override public Fragment getItem(int position) { return SummaryTabletFragment.newInstance(position); } @Override public void onPageScrollStateChanged(int state) {} @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { adjustSize(position, positionOffset); } @Override public void onPageSelected(int position) { mCurrentPage = position; } /** * Used to adjust the size of each view in the viewpager as the user * scrolls. This provides the effect of children scaling down as they * are moved out and back to full size as they come into focus. * * @param position * @param percent */ private void adjustSize(int position, float percent) { position += (mScrollingLeft ? 1 : 0); int secondary = position + (mScrollingLeft ? -1 : 1); int tertiary = position + (mScrollingLeft ? 1 : -1); float scaleUp = mScrollingLeft ? percent : 1.0f - percent; float scaleDown = mScrollingLeft ? 1.0f - percent : percent; float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp; float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown; if (scaleUp < BASE_SIZE) scaleUp = BASE_SIZE; if (scaleDown < BASE_SIZE) scaleDown = BASE_SIZE; // Adjust the fragments that are, or will be, on screen SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null; SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null; SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null; if (current != null && next != null) { // Apply the adjustments to each fragment current.transitionFragment(percentIn, scaleUp); next.transitionFragment(percentOut, scaleDown); if (afterNext != null) { afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE); } } } @Override public void onScrollChanged(int l, int t, int oldl, int oldt) { // Keep track of which direction we are scrolling mScrollingLeft = (oldl - l) < 0; } } 

Fragmento

 public class SummaryTabletFragment extends BaseTabletFragment { public final String TAG = this.getClass().getSimpleName(); private final float SCALE_SIZE = 0.8f; private RelativeLayout mBackground, mCover; private TextView mTitle; private VerticalTextView mLeft, mRight; private String mTitleText; private Integer mColor; private boolean mInit = false; private Float mScale, mPercent; private GrowPagerAdapter mAdapter; private int mCurrentPosition = 0; public String getTitleText() { return mTitleText; } public void setTitleText(String titleText) { this.mTitleText = titleText; } public static SummaryTabletFragment newInstance(int position) { SummaryTabletFragment fragment = new SummaryTabletFragment(); fragment.setRetainInstance(true); Bundle args = new Bundle(); args.putInt("position", position); fragment.setArguments(args); return fragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); mRoot = inflater.inflate(R.layout.tablet_dummy_view, null); setupViews(); configureView(); return mRoot; } @Override public void onViewStateRestored(Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState); if (savedInstanceState != null) { mColor = savedInstanceState.getInt("color", Color.BLACK); } configureView(); } @Override public void onSaveInstanceState(Bundle outState) { outState.putInt("color", mColor); super.onSaveInstanceState(outState); } @Override public int getPosition() { return getArguments().getInt("position", -1); } @Override public void setPosition(int position) { getArguments().putInt("position", position); } public void onResume() { super.onResume(); mAdapter = mActivity.getPagerAdapter(); mAdapter.addFragment(this); mCurrentPosition = mAdapter.getCurrentPage(); if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) { mInit = true; transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE); return; } if (getPosition() == mCurrentPosition && !mInit) { mInit = true; transitionFragment(0.00f, 1.0f); } } private void setupViews() { mCover = (RelativeLayout) mRoot.findViewById(R.id.cover); mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left); mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right); mBackground = (RelativeLayout) mRoot.findViewById(R.id.root); mTitle = (TextView) mRoot.findViewById(R.id.title); } private void configureView() { Fonts.applyPrimaryBoldFont(mLeft, 15); Fonts.applyPrimaryBoldFont(mRight, 15); float[] size = UiUtils.getScreenMeasurements(mActivity); int width = (int) (size[0] * SCALE_SIZE); int height = (int) (size[1] * SCALE_SIZE); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height); mBackground.setLayoutParams(params); if (mScale != null) transitionFragment(mPercent, mScale); setRandomBackground(); setTitleText("Fragment " + getPosition()); mTitle.setText(getTitleText().toUpperCase()); mLeft.setText(getTitleText().toUpperCase()); mRight.setText(getTitleText().toUpperCase()); mLeft.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mActivity.showNextPage(); } }); mRight.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mActivity.showPrevPage(); } }); } private void setRandomBackground() { if (mColor == null) { Random r = new Random(); mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255)); } mBackground.setBackgroundColor(mColor); } public void transitionFragment(float percent, float scale) { this.mScale = scale; this.mPercent = percent; if (getView() != null && mCover != null) { getView().setScaleX(scale); getView().setScaleY(scale); mCover.setAlpha(percent); mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE); } } @Override public String getFragmentTitle() { return null; } } 

Mi solución: configuré casi todas las vistas como static . Ahora mi aplicación interactúa perfecta. Ser capaz de llamar a los métodos estáticos de todas partes tal vez no sea un buen estilo, pero ¿por qué jugar con un código que no funciona? Leí muchas preguntas y sus respuestas aquí en SO y ninguna solución me trajo éxito (para mí).

Sé que puede filtrar la memoria y desperdiciar montones, y mi código no encajará en otros proyectos, pero no tengo miedo de esto. Probé la aplicación en diferentes dispositivos y condiciones, sin problemas, el Android La plataforma parece ser capaz de manejar esto. La interfaz de usuario se actualiza cada segundo e, incluso en un dispositivo S2 ICS (4.0.3), la aplicación puede gestionar miles de marcadores geográficos.

Me enfrenté al mismo problema, pero mi ViewPager estaba dentro de un TopFragment que creó y configuró un adaptador usando setAdapter(new FragmentPagerAdapter(getChildFragmentManager())) .

onAttachFragment(Fragment childFragment) este problema anulando onAttachFragment(Fragment childFragment) en TopFragment de esta manera:

 @Override public void onAttachFragment(Fragment childFragment) { if (childFragment instanceof OnboardingDiamondsFragment) { mChildFragment = (ChildFragment) childFragment; } super.onAttachFragment(childFragment); } 

Como ya se sabe (ver respuestas arriba), cuando childFragmentManager se recrea a sí mismo, también crea los fragmentos que estaban dentro de viewPager.
La parte importante es que después de eso, llama a AttachFragment y ahora tenemos una referencia al nuevo fragmento recreado.

Espero que esto ayude a cualquiera a obtener esta vieja Q como yo 🙂

Resolví el problema guardando los fragmentos en SparceArray:

 public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter { SparseArray fragments = new SparseArray<>(); public SaveFragmentsPagerAdapter(FragmentManager fm) { super(fm); } @Override public Object instantiateItem(ViewGroup container, int position) { Fragment fragment = (Fragment) super.instantiateItem(container, position); fragments.append(position, fragment); return fragment; } @Nullable public Fragment getFragmentByPosition(int position){ return fragments.get(position); } } 

Solo para que sepas…

Añadiendo a la letanía de problemas con estas clases, hay un error bastante interesante que vale la pena compartir.

Estoy usando una ViewPager para navegar por un árbol de elementos (seleccione un elemento y el paginador de vistas anima a desplazarse hacia la derecha, y aparece la siguiente twig, navegue hacia atrás, y la ViewPager se desplaza en la dirección opuesta para regresar al nodo anterior) .

El problema surge cuando presiono y saco fragmentos del final de FragmentStatePagerAdapter. Es lo suficientemente inteligente como para notar que los elementos cambian, y lo suficientemente inteligente como para crear y reemplazar un fragmento cuando el elemento ha cambiado. Pero no lo suficientemente inteligente como para descartar el estado del fragmento, o lo suficientemente inteligente como para recortar los estados de fragmentos guardados internamente cuando cambia el tamaño del adaptador. Entonces, cuando saca un elemento y lo inserta uno nuevo, el fragmento del nuevo elemento obtiene el estado guardado del fragmento del artículo anterior, lo que causó esgulps en mi código. Mis fragmentos contienen datos que pueden requerir mucho trabajo para recuperarlos de Internet, por lo que no guardar estado realmente no era una opción.

No tengo una solución limpia. Usé algo como esto:

  public void onSaveInstanceState(Bundle outState) { IFragmentListener listener = (IFragmentListener)getActivity(); if (listener!= null) { if (!listener.isStillInTheAdapter(this.getAdapterItem())) { return; // return empty state. } } super.onSaveInstanceState(outState); // normal saving of state for flips and // paging out of the activity follows .... } 

Una solución imperfecta porque la nueva instancia de fragmento todavía obtiene un paquete de estado guardado, pero al menos no contiene datos obsoletos.