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

问题描述:

我已将问题缩小为 FragmentManager 保留旧片段实例的问题,以及我的 viewpager 与 FragmentManager 不同步的问题.请参阅此问题:http://code.google.com/p/android/issues/detail?id=19211#makechanges.我仍然不知道如何解决这个问题.有什么建议吗?

I have narrowed my problem down to being a problem with the FragmentManager retaining instances of old fragments and my viewpager being out of sync with my FragmentManager. See this issue: http://code.google.com/p/android/issues/detail?id=19211#makechanges. I still have no clue how to solve this. Any suggestions?

我已经尝试调试了很长时间,任何帮助将不胜感激.我正在使用 FragmentPagerAdapter,它接受像这样的片段列表:

I have tried to debug this for a long time and any help would be greatly appreciated. I am using a FragmentPagerAdapter which accepts a list of fragments like so:

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

实施是标准的.我正在为 Fragment 使用 ActionBarSherlock 和 v4 可计算性库.

The implementation is standard. I am using ActionBarSherlock and v4 computability library for Fragments.

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

My problem is that after leaving the app and opening several other applications and coming back, the fragments lose their reference back to the FragmentActivity (ie. getActivity() == null). I can not figure out why this is happening. I tried to manually set setRetainInstance(true); but this does not help. I figured that this happens when my FragmentActivity gets destroyed, however this still happens if I open the app before I get the log message. Are there any ideas?

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

    }

}

我的一个 Fragments 被剥离了,但我将所有被剥离的片段都注释掉了,但它仍然不起作用.

One of my Fragments stripped but I commented everything out that's stripped and it still doesn't work.

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 时,将调用更新方法.这是另一个片段调用的方法:

The update method is called when a user interacts with a different fragment and the getActivity is returning null. Here is the method the other fragment is calling:

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

我相信当应用程序被销毁然后再次创建时,而不是仅仅将应用程序启动到默认片段,应用程序会启动,然后 viewpager 导航到最后一个已知页面.这看起来很奇怪,应用程序不应该只是加载到默认片段吗?

I believe that when the app is destroyed then created again instead of just starting the app up to the default fragment the app starts up and then viewpager navigates to the last known page. This seems strange, shouldn't the app just load to the default fragment?

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

You are running into a problem because you are instantiating and keeping references to your fragments outside of PagerAdapter.getItem, and are trying to use those references independently of the ViewPager. As Seraph says, you do have guarantees that a fragment has been instantiated/added in a ViewPager at a particular time - this should be considered an implementation detail. A ViewPager does lazy loading of its pages; by default it only loads the current page, and the one to the left and right.

如果您将您的应用程序置于后台,则已添加到片段管理器中的片段会自动保存.即使您的应用被终止,这些信息也会在您重新启动应用时恢复.

If you put your app into the background, the fragments that have been added to the fragment manager are saved automatically. Even if your app is killed, this information is restored when you relaunch your app.

现在考虑您已经查看了几个页面,片段 A、B 和 C.您知道这些已添加到片段管理器中.因为您使用的是 FragmentPagerAdapter 而不是 FragmentStatePagerAdapter,所以当您滚动到其他页面时,这些片段仍会被添加(但可能会分离).

Now consider that you have viewed a few pages, Fragments A, B and C. You know that these have been added to the fragment manager. Because you are using FragmentPagerAdapter and not FragmentStatePagerAdapter, these fragments will still be added (but potentially detached) when you scroll to other pages.

考虑到您随后将您的应用程序作为后台应用程序,然后它就被杀死了.当你回来时,Android 会记住你曾经在片段管理器中有片段 A、B 和 C,因此它会为你重新创建它们,然后添加它们.但是,现在添加到片段管理器的那些不是您在 Activity 的片段列表中拥有的那些.

Consider that you then background your application, and then it gets killed. When you come back, Android will remember that you used to have Fragments A, B and C in the fragment manager and so it recreates them for you and then adds them. However, the ones that are added to the fragment manager now are NOT the ones you have in your fragments list in your Activity.

如果已经为该特定页面位置添加了片段,则 FragmentPagerAdapter 将不会尝试调用 getPosition.实际上,由于 Android 重新创建的片段永远不会被删除,因此您无法通过调用 getPosition 来替换它.处理它也很难获得对它的引用,因为它添加了一个你不知道的标签.这是设计使然;不鼓励您弄乱视图寻呼机正在管理的片段.您应该在一个片段中执行所有操作,与 Activity 进行通信,并在必要时请求切换到特定页面.

The FragmentPagerAdapter will not try to call getPosition if there is already a fragment added for that particular page position. In fact, since the fragment recreated by Android will never be removed, you have no hope of replacing it with a call to getPosition. Getting a handle on it is also pretty difficult to obtain a reference to it because it was added with a tag that is unknown to you. This is by design; you are discouraged from messing with the fragments that the view pager is managing. You should be performing all your actions within a fragment, communicating with the activity, and requesting to switch to a particular page, if necessary.

现在,回到缺少活动的问题.在所有这些发生后调用 pagerAdapter.getItem(1)).update(id, name) 会返回列表中的片段,该片段尚未添加到片段管理器,所以它不会有活动引用.我建议您的更新方法应该修改一些共享数据结构(可能由活动管理),然后当您移动到特定页面时,它可以根据更新后的数据绘制自己.

Now, back to your problem with the missing activity. Calling pagerAdapter.getItem(1)).update(id, name) after all of this has happened returns you the fragment in your list, which has yet to be added to the fragment manager, and so it will not have an Activity reference. I would that suggest your update method should modify some shared data structure (possibly managed by the activity), and then when you move to a particular page it can draw itself based on this updated data.