ViewPager Fragments getting destroyed over time?

2019-01-31 04:37发布

问题:

I am using Android's support.v4 package to develop a ViewPager containing multiple Fragments.

I am trying to hide the Views when the user changes page. So, I implemented an OnPageChangeListener to listen to my adapter, and call an update() method in my Fragments when when a page change occurs. But I'm getting a NullPointerException, and I cannot figure out why.

Here is the code:

public class MyActivity extends FragmentActivity implements
        OnPageChangeListener {

    private static final int NUM_ITEMS = 2;
    private ViewPager mPager;
    private static SpaceAdapter mAdapter;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_pager);

        mAdapter = new SpaceAdapter(getSupportFragmentManager());
        mPager = (ViewPager) findViewById(R.id.pager);
        mPager.setAdapter(mAdapter);

        mPager.setOnPageChangeListener(this);
}

    public static class SpaceAdapter extends FragmentPagerAdapter {

        public SpaceAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public int getCount() {
            return NUM_ITEMS;
        }

        @Override
        public Fragment getItem(int position) {
            switch (position) {
            case 0:
            return new MyFragment();
            case 1:
            return new MyFragment();
            default:
            return new MyFragment();
            }
        }
    }

    @Override
    public void onPageScrollStateChanged(int arg0) {
        Log.i("pagination", "scroll state changed: " + arg0);
}

    @Override
    public void onPageScrolled(int arg0, float arg1, int arg2) {
        // Log.i("pagination", "page scroll: " + arg0 + " with: " + arg1 + ", " + arg2);
        if (mAdapter.getItem(arg0) instanceof IUpdateOnPage) {
            ((IUpdateOnPage) mAdapter.getItem(arg0)).updateOnPage();
        }
    }

    @Override
    public void onPageSelected(int arg0) {
        Log.i("pagination", "page selected: " + arg0);
    }
}

and

public class MyFragment extends Fragment implements
        SurfaceHolder.Callback, IUpdateOnPage {

    private SurfaceView charts;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.crew, null);
        bindViews(v);
        return v;
    }

    private void bindViews(View v) {
        charts = (SurfaceView) v.findViewById(R.id.civ_charts);

        charts.getHolder().addCallback(this);
        Log.i("wtf", "huh " + String.valueOf(charts == null)); // logs false
    }

    @Override
    public void updateOnPage() {
        if (charts != null) {
            Log.i("crew surface", "hidden");
            charts.setVisibility(View.GONE);
        } else {
            Log.i("crew surface", "charts is null");  //THIS HERE gets logged
        }
    }
}

wWhen I copy/pasted I removed the SurfaceHolder.Callback methods, but they are there. The bindViews(v) method logs if charts == null, and that always returns false. When I page, updateOnPage gets called, and charts == null returns true. Why does this happen and how do I get around it?

and this is line 108:

charts.setVisibility(View.GONE);

回答1:

Add the following when you initialize your ViewPager,

mPager.setOffscreenPageLimit(NUM_ITEMS-1);

In your case, this will set the number of off-screen pages to 1 (i.e. it will keep the additional off-screen page in memory when the other page is currently in focus). This will force your ViewPager to keep all of the Fragments even when they are not in focus.



回答2:

I examined your code and I think the issue is that in onPageScrolled, you're calling getItem. This implementation (which is actually like most implementations of it on web) returns a new instance each time. However, most examples I've seen don't call getItem, so this is usually alright.

In your case, I would hold lazily create the Fragment in getItem: if it doesn't exist for that position, then create it, and then hang onto it in an ArrayList or some other collection, but if it already exists, just return it from that collection.

Otherwise, you create a fragment instance A, then scroll to another fragment instance B. You try to tell fragment instance A to clean up some views, but you wind up telling a new fragment instance C to do so. It hasn't had a chance to set up its own views yet, so you're getting the null pointer to charts.