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

Mau*_*ycy 107 android android-fragments

最新信息:

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

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

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

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

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

@Override
protected void onDestroy(){
    Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
    super.onDestroy();
}
Run Code Online (Sandbox Code Playgroud)

适配器:

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

    }

}
Run Code Online (Sandbox Code Playgroud)

我的一个碎片被剥夺了但是我很满意每一个被剥离但仍然无法工作的东西......

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

}
Run Code Online (Sandbox Code Playgroud)

}

当用户与不同的片段交互并且getActivity返回null时,将调用update方法.这是另一个片段调用的方法...

((MyFragment) pagerAdapter.getItem(1)).update(id, name);
Run Code Online (Sandbox Code Playgroud)

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

ant*_*nyt 120

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

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

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

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

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

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

  • 简短:永远不要在适配器外部保留对片段的引用 (28认同)
  • 那么如果Activity实例没有实例化FragmentPagerAdapter,它如何访问FragmentPagerAdapter实例呢?将来的实例不会只重新实例化FragmentPagerAdapter及其所有片段实例吗?FragmentPagerAdapter应该实现所有片段接口来管理片段之间的通信吗? (2认同)

mc.*_*dev 106

我找到了适合我的简单解决方案.

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

@Override
public Parcelable saveState()
{
    return null;
}
Run Code Online (Sandbox Code Playgroud)

这可以防止android重新创建片段


一天后,我找到了另一个更好的解决方案.

调用setRetainInstance(true)所有片段并在某处保存对它们的引用.我在我的活动中使用静态变量做了那个,因为它被声明为singleTask并且片段可以一直保持不变.

这样android不会重新创建片段但使用相同的实例.

  • 对片段和/或活动进行静态引用是一件非常危险的事情,因为它很容易导致内存泄漏.当然,如果你小心,你可以通过在不再需要时将它们设置为null来轻松处理它. (6认同)
  • 谢谢,谢谢,谢谢这么多,我真的没有话要感谢你,我在过去10天里追逐这个问题并尝试了很多方法.但是这四条神奇的线条救了我一生:) (4认同)

Mau*_*ycy 28

我通过FragmentManager直接访问我的片段而不是像FragmentPagerAdapter那样解决了这个问题.首先,我需要弄清楚FragmentPagerAdapter自动生成的片段的标签......

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

然后我只是得到一个对该片段的引用并做我需要的事情......

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

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

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

    super.onSaveInstanceState(outState);
}
Run Code Online (Sandbox Code Playgroud)

然后在OnCreate中我抓住该密钥并根据需要恢复片段的状态.一个简单的解决方案,对我来说很难(至少对我而言).

  • 片段引用中你的"我的"是什么? (3认同)

Vin*_*yak 25

全球工作测试解决方案

getSupportFragmentManager()保持null引用一些时间,View寻呼机不会创建新的,因为它找到对同一片段的引用.因此,这种用法getChildFragmentManager()以简单的方式解决了问题.

不要这样做:

new PagerAdapter(getSupportFragmentManager(), fragments);

做这个:

new PagerAdapter(getChildFragmentManager() , fragments);

  • 如果您在Fragment中托管(并实例化)pagerAdapter而不在您的活动中,这将是可能的.在这种情况下,你是对的,你应该默认使用childFragmentManager.默认情况下,使用SupportFragmentManager会出错 (6认同)

Ser*_*aph 7

不要尝试在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);
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

您可以在销毁viewpager时删除片段,以我为例,我将其从onDestroyView()片段中删除了:

@Override
public void onDestroyView() {

    if (getChildFragmentManager().getFragments() != null) {
        for (Fragment fragment : getChildFragmentManager().getFragments()) {
            getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
        }
    }

    super.onDestroyView();
}
Run Code Online (Sandbox Code Playgroud)