支持FragmentPagerAdapter保存对旧片段的引用

最新信息:

我已经缩小了我的问题,因为fragmentManager保留旧片段的实例,而我的viewpager与我的FragmentManager不同步。 看到这个问题… http://code.google.com/p/android/issues/detail?id=19211#makechanges 。 我仍然不知道如何解决这个问题。 有什么build议么…

我试图debugging很长一段时间,任何帮助将不胜感激。 我正在使用FragmentPagerAdapter,它接受如下所示的片段列表:

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

执行是标准的。 我正在使用ActionBarSherlock和v4可用性碎片库。

我的问题是,离开应用程序并打开其他几个应用程序并返回后,片段失去其参考回到FragmentActivity(即getActivity() == null )。 我无法弄清楚为什么会发生这种情况。 我试图手动设置setRetainInstance(true); 但是这并没有帮助。 我认为这发生在我的FragmentActivity被破坏时,但是如果我在得到日志消息之前打开应用程序,仍然会发生这种情况。 有什么想法吗?

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

适配器:

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

我的一个碎片被剥离了,但是我发现这些东西被剥离了,仍然不起作用。

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

}

更新方法在用户与其他片段交互并且getActivity返回null时被调用。 这是另一个片段正在调用的方法…

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

我相信,当应用程序被销毁,然后再次创build,而不是启动应用程序,直到应用程序启动的默认片段,然后viewpager导航到最后一个已知的页面。 这似乎很奇怪,不应该只是加载到默认片段的应用程序?

你遇到了一个问题,因为你正在实例化和保持对PagerAdapter.getItem之外的片段的PagerAdapter.getItem ,并试图独立于ViewPager使用这些引用。 正如Seraph所说,你确实保证片段在特定的时间被ViewPager实例化/添加 – 这应该被视为一个实现细节。 ViewPager会延迟加载其页面; 默认情况下,它只加载当前页面,左侧和右侧加载。

如果将应用程序置于后台,则添加到片段pipe理器的片段将自动保存。 即使您的应用程序被杀害,当您重新启动您的应用程序时,这些信息也会被恢复。

现在考虑你已经看过几页,碎片A,B和C.你知道这些已经被添加到片段pipe理器。 因为您正在使用FragmentPagerAdapter而不是FragmentStatePagerAdapter ,所以当您滚动到其他页面时,这些片段仍将被添加(但可能会分离)。

考虑你然后背景你的应用程序,然后它被杀害。 当你回来的时候,Android会记住你以前在碎片pipe理器中有碎片A,B和C,所以它会为你重新创build它们,然后添加它们。 但是,现在添加到片段pipe理器中的那些不是您在活动中的片段列表中的那些。

如果已经为该特定页面位置添加了片段,FragmentPagerAdapter将不会尝试调用getPosition 。 事实上,由于Android重新创build的片段永远不会被移除,因此您无法用getPosition调用replace它。 处理它也很难获得对它的引用,因为它被添加了一个你不知道的标签。 这是devise的; 你不鼓励查看传呼机pipe理的碎片。 您应该在片段中执行所有操作,与活动进行通信,并在必要时请求切换到特定页面。

现在,回到你的问题与失踪的活动。 调用pagerAdapter.getItem(1)).update(id, name)在所有这一切发生之后返回你的列表中的片段 ,它还没有被添加到片段pipe理器 ,所以它不会有一个Activity引用。 我会build议你的更新方法应该修改一些共享的数据结构(可能由活动pipe理),然后当你移动到一个特定的页面,它可以绘制自己基于这个更新的数据。

我find了简单的解决scheme,为我工作。

使您的片段适配器扩展FragmentStatePagerAdapter而不是FragmentPagerAdapter并覆盖方法onSave返回null

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

这阻止了android重新创build片段


一天后,我find了另一个更好的解决scheme。

为所有片段调用setRetainInstance(true)并将其引用保存到某处。 我在我的活动的静态variables中做了这个,因为它被声明为singleTask和片段可以一直保持不变。

这样,android不会重新创build片段,但使用相同的实例。

我通过直接通过FragmentManager而不是像这样通过FragmentPagerAdapter来访问我的碎片来解决这个问题。 首先,我需要找出由FragmentPagerAdapter生成的片段的标签…

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

然后,我只是得到一个参考该片段,做我需要的东西如此…

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

在我的片段中,我设置了setRetainInstance(false); 这样我可以手动将值添加到savedInstanceState包中。

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

然后在OnCreate中,我抓住该键,并根据需要恢复片段的状态。 一个简单的解决scheme,很难(至less对我来说)弄清楚。

全球工作testing解决scheme

getSupportFragmentManager()保留空引用一些时间和查看pager不会创build新的,因为它find对同一个片段的引用。 所以为了解决这个问题,使用getChildFragmentManager()以简单的方式解决问题。

不要这样做:

new PagerAdapter(getSupportFragmentManager(), fragments);

做这个:

new PagerAdapter(getChildFragmentManager() , fragments);

不要试图在ViewPager中的片段之间进行交互。 你不能保证附加的其他片段,甚至存在。 除了从片段中更改操作栏标题,您可以从您的活动中完成。 使用标准的界面模式:

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

经过几个小时寻找类似的问题,我认为有另一种解决scheme。 这至less对我有用,我只需要改变几行。

这是我有的问题,我有一个活动与查看传呼机使用FragmentStatePagerAdapter与两个片段。 一切工作正常,直到我强制活动被破坏(开发人员选项),或者我旋转屏幕。 在getItem方法中创build两个片段后,我会保留这两个片段的引用。

在这一点上,活动将被重新创build,一切工作正常,但我已经失去了我的fragmetns的参考,因为getItem不会被再次调用。

这就是我在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; } 

如果适配器已经在内部引用了它,则不会再次调用getItem,并且不应该改变它。 相反,您可以通过查看其他方法instantiateItem()来获取正在使用的片段,该方法将针对每个片段调用。

希望能帮助任何人。

你可以在销毁viewpager的时候删除这些片段,在我的情况下,我在片段的onDestroyView()上删除了它们:

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

由于在调用onResume()方法后,FragmentManager将负责为你恢复你的碎片,我把这个碎片调出来并添加到列表中。 在我的例子中,我将所有这些存储在我的PagerAdapter实现中。 每个片段知道它的位置,因为它被添加到创build片段参数。 现在,无论何时我需要操作特定索引处的片段,我所要做的就是使用我的适配器中的列表。

以下是自定义ViewPager的一个适配器的示例,该对象将移动到焦点时扩展该片段,并在移出焦点时将其缩小。 除了适配器和片段类我在这里所有你需要的是父活动能够引用适配器variables,你设置。

适配器

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

分段

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

我的解决scheme:我几乎将每个视图设置为static 。 现在我的应用程序交互完美。 能够从任何地方调用静态方法可能不是一个好的风格,但是为什么要使用不起作用的代码呢? 我在这里阅读了很多问题和答案,没有任何解决scheme带来成功(对我来说)。

我知道它可以泄漏内存,并浪费堆,我的代码将不适合其他项目,但我不觉得害怕这一点 – 我在不同的设备和条件下testing应用程序,没有任何问题,Android平台似乎能够处理这个问题。 UI每秒刷新一次,甚至在S2 ICS(4.0.3)设备上,该应用程序可以处理数千个地理标记。

我面临同样的问题,但我的ViewPager是在一个TopFragment,它使用setAdapter(new FragmentPagerAdapter(getChildFragmentManager()))创build和设置一个适配器。

我解决了这个问题,通过覆盖onAttachFragment(Fragment childFragment)如下所示:

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

如已知的(参见上面的答案),当childFragmentManager重新创build时,它也会创buildviewPager中的片段。
最重要的部分就是在那之后,他打电话给AttackFragment ,现在我们有了一个新的重新创build的片段的参考!

希望这会帮助任何人得到像我这样的老Q

我通过在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); } }