Soporte FragmentPagerAdapter tiene referencia a fragmentos antiguos

ULTIMO INFORME:

He reducido mi problema a ser un problema con el fragmentManager retener instancias de fragmentos antiguos y mi viewpager estar fuera de sincronización con mi FragmentManager. Consulte este número … http://code.google.com/p/android/issues/detail?id=19211#makechanges . Todavía no tengo ni idea de cómo resolver esto. Alguna sugerencia…

He tratado de depurar esto durante mucho tiempo y cualquier ayuda sería muy apreciada. Estoy utilizando un FragmentPagerAdapter que acepta una lista de fragmentos así:

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

La implementación es estándar. Estoy utilizando ActionBarSherlock y v4 computability biblioteca de fragmentos.

Mi problema es que después de dejar la aplicación y abrir varias otras aplicaciones y volver, los fragmentos pierden su referencia de nuevo a la FragmentActivity (es decir, getActivity() == null ). No puedo entender por qué está sucediendo esto. Traté de establecer manualmente setRetainInstance(true); Pero esto no ayuda. Pensé que esto sucede cuando mi FragmentActivity consigue destruido, sin embargo esto todavía sucede si abro la app antes de que consiga el mensaje del registro. ¿Hay alguna idea?

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

El adaptador:

 public class PagerAdapter extends FragmentPagerAdapter { private List<Fragment> fragments; public PagerAdapter(FragmentManager fm, List<Fragment> 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 despojado, pero me commetned everyhting que está despojado 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 llama cuando un usuario interactúa con un fragmento diferente y getActivity está devolviendo null. Aquí está el método que el otro fragmento está llamando …

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

Creo que cuando se destruye la aplicación se crea de nuevo en lugar de sólo iniciar la aplicación hasta el fragmento predeterminado la aplicación se inicia y, a continuación, viewpager navega a la última página conocida. Esto parece extraño, no debería la aplicación sólo carga en el fragmento por defecto?

Se está PagerAdapter.getItem un problema porque está instanciando y manteniendo referencias a sus fragmentos fuera de PagerAdapter.getItem y está intentando utilizar esas referencias independientemente de ViewPager. Como dice Seraph, usted tiene garantías de que un fragmento ha sido instanciado / agregado en un ViewPager en un momento determinado – esto debe ser considerado un detalle de implementación. Un ViewPager hace la carga perezosa de sus páginas; Por defecto sólo carga la página actual y la que está a 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 se cancela la aplicación, esta información se restaura al relanzar la aplicación.

Ahora considera que has visto unas pocas páginas, Fragmentos A, B y C. Sabes que estos han sido agregados al gestor de fragmentos. Debido a que está utilizando FragmentPagerAdapter y no FragmentStatePagerAdapter , estos fragmentos seguirán agregándose (pero potencialmente separados) al desplazarse a otras páginas.

Considere que a continuación, el fondo de su aplicación, y luego se mata. Cuando regreses, Android recordará que solías tener Fragmentos A, B y C en el gestor de fragmentos y así los recrea para ti y luego los agrega. Sin embargo, los que se agregan al gestor 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 añadido para esa posición de página en particular. De hecho, dado que el fragmento recreado por Android nunca se eliminará, no tienes ninguna esperanza de reemplazarlo por una llamada a getPosition . Conseguir una manija en ella es también bastante difícil de obtener una referencia a ella porque fue agregada con una etiqueta que sea desconocida para usted. Esto es por diseño; Se desaconseja jugar con los fragmentos que el paginador de vista está administrando. Debe realizar todas sus acciones dentro de un fragmento, comunicarse con la actividad y solicitar cambiar a una página en particular, si es necesario.

Ahora, vuelva a su problema con la actividad que falta. Después de todo esto ha vuelto a devolver el fragmento en su lista, que todavía no se ha agregado al gestor de fragmentos , y por lo tanto no tendrá una referencia de actividad. Yo sugeriría que su método de actualización debería modificar alguna estructura de datos compartida (posiblemente gestionada por la actividad) y, a continuación, cuando se mueva a una página en particular puede dibujar a partir de estos datos actualizados.

Encontré solución simple que trabajó para mí.

Haga que su adaptador de fragmento extienda FragmentStatePagerAdapter en lugar de FragmentPagerAdapter y el método override onSave para devolver null

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

Esto evita que el android recupere el fragmento


Un día más tarde encontré otra y mejor solución.

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 se declara como singleTask y los fragmentos pueden permanecer iguales todo el tiempo.

De esta manera android no recrea fragmentos pero usa las mismas instancias.

He resuelto este problema mediante el acceso a mis fragmentos directamente a través de la FragmentManager en lugar de a través de la FragmentPagerAdapter así. Primero tengo que averiguar la etiqueta del fragmento generado automáticamente por el 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í …

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

Dentro de mis fragmentos establezco el setRetainInstance(false); De modo que pueda agregar manualmente valores 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 el OnCreate i agarrar esa clave y restaurar el estado del fragmento, según sea necesario. Una solución fácil que era difícil (para mí por lo menos) de averiguar.

Solución probada de trabajo global.

getSupportFragmentManager() mantiene la referencia nula algunas veces y View pager no crea nuevo ya que encuentra referencia al mismo fragmento. Así que para llegar a este uso getChildFragmentManager() resuelve el problema de manera sencilla.

No haga esto:

new PagerAdapter(getSupportFragmentManager(), fragments);

Hacer esto:

new PagerAdapter(getChildFragmentManager() , fragments);

No trate de interactuar entre fragmentos en ViewPager. Usted no puede garantizar que otro fragmento adjunto o incluso existe. En lugar de cambiar el título de la barra de acción del fragmento, puedes hacerlo desde tu actividad. Utilice el 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 tiene otra solución. Este al menos funcionó para mí y sólo tengo que cambiar un par de líneas.

Este es el problema que tuve, tengo una actividad con un paginador de vista que utiliza un FragmentStatePagerAdapter con dos fragmentos. Todo funciona bien hasta que obligue a la actividad a ser destruido (opciones de desarrollador) o girar la pantalla. Sigo una referencia a los dos fragmentos después de que se creó 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 mi fragmetns como getItem no 'volver a llamar.

Así es como arreglé ese problema, dentro del 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; } 

Usted no recibirá una llamada en getItem de nuevo si el adaptador ya tiene una referencia a ella internamente, y no debe cambiar eso. En su lugar, puede obtener el fragmento que está siendo utilizado mirando a este otro método instantiateItem () que se llamará para cada uno de sus fragmentos.

Espero que ayude a cualquiera.

Usted puede quitar los fragmentos cuando destruya el viewpager, en mi caso, los quité 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(); } 

Puesto que el FragmentManager se encargará de restaurar tus Fragmentos para ti tan pronto como se llama al método onResume (), el fragmento llama a la actividad y se agrega a una lista. En mi ejemplo estoy almacenando todo esto en mi implementación de PagerAdapter. Cada fragmento sabe su posición porque se añade a los argumentos de 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 utilizar la lista de mi adaptador.

A continuación, se muestra un ejemplo de un adaptador para un ViewPager personalizado que crecerá el fragmento a medida que se enfoca, y lo escalará hacia abajo a medida que se mueve fuera de foco. Además de las clases de adaptador y fragmento que tengo aquí todo lo que necesitas es que la actividad de padre sea capaz de hacer referencia a la variable de adaptador y que está establecido.

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<SummaryTabletFragment> 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<SummaryTabletFragment>(); } @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: establezco casi todas las vistas como static . Ahora mi aplicación interactúa perfecto. Ser capaz de llamar a los métodos estáticos de todas partes es quizás no un buen estilo, pero ¿por qué jugar con código que no funciona? He leído un montón de preguntas y sus respuestas aquí en SO y ninguna solución trajo el éxito (para mí).

Sé que puede perder la memoria, y el montón de residuos, y mi código no caben en otros proyectos, pero no me siento asustado acerca de esto – He probado la aplicación en diferentes dispositivos y condiciones, sin problemas en absoluto, el Android 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 es capaz de manejar miles de geo-marcadores.

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

He arreglado este problema por sobreescribir onAttachFragment(Fragment childFragment) en el TopFragment como este:

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

Como ya se sabe (vea las respuestas anteriores), cuando el childFragmentManager se recrea, también crea los fragmentos que estaban dentro de la viewPager.
La parte importante es que después de eso, llama aAttachFragment y ahora tenemos una referencia al nuevo fragmento recreado!

Espero que esto ayude a alguien a obtener este viejo Q como yo 🙂

Resolví el problema guardando los fragmentos en SparceArray:

 public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter { SparseArray<Fragment> 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); } } 
  • Activity and Fragment Lifecycle, ¿el fuego onResume dispara uno tras otro?
  • Android TabLayout dentro de Fragmento
  • ¿Por qué onResume método de un fragmento nunca se disparó después de despedir un DialogFragment que se inició desde el fragmento?
  • ¿Cómo llamo a un PreferenceFragment específico de una PreferenceActivity?
  • Maneje adecuadamente la animación de DrawerLayout mientras navega a través del fragmento
  • Fragmentos que desaparecen después de la aplicación no se ha utilizado durante pocas horas
  • Icono de menú que no se muestra en la barra de acción
  • Cómo establecer ViewPager dentro de un fragmento
  • Android Reorder Fragment Backstack
  • Forzar cierre cuando los cambios de orientación con fecha abierta DialogueFragment
  • Fragmento retenido no retenido
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.