NPE в Fragment.setUserVisibleHint() при использовании ViewPager

Я за это не согласен. Я переключаю вкладки вручную в своем ViewPager. У меня этот код внутри моей деятельности:

@Override
public void onBackPressed()
{
    if (childFragmentStack.empty())
    {
        // Go to the devices screen
        Intent intent = new Intent(this, SelectDeviceActivity.class);
        startActivity(intent);
    }
    else
    {
        Fragment fragment = childFragmentStack.pop();

        if (fragment == null)
        {
            return;
        }

        processingBackStack = true;

        if (fragment instanceof ViewChildFragment)
        {
            viewFragment.activateFragment((ViewChildFragment) fragment);
            mViewPager.setCurrentItem(VIEW_FRAGMENT_INDEX, true);
        }
        else if (fragment instanceof SetupChildFragment)
        {
            setupFragment.activateFragment((SetupChildFragment) fragment);
            mViewPager.setCurrentItem(SETUP_FRAGMENT_INDEX, true); //**
        }
        else if (fragment == homeFragment)
        {
            mViewPager.setCurrentItem(HOME_FRAGMENT_INDEX, true); //**
        }

        processingBackStack = false;
    }
}

Если я просматриваю вкладки, я добавляю их в стек ( "childFragmentStack" ). Я использую FragmentPagerAdapter для обработки фрагментов. Что произойдет, если я сделаю что-то вроде View- > Setup- > View- > Setup, а затем отмените его, он дойдет до Setup- > View- > CRASH. Мне нравится, когда я нажимаю Назад, Фрагмент настройки больше не действует для того, что я делаю, но он никогда не воссоздается! Фрагмент установки создается только в MainActivity.onCreate(), поэтому он все равно должен быть рядом и действителен.

NPE происходит на строках, которые я обозначил **. Вот полная трассировка стека:

    04-18 16:04:57.096: E/AndroidRuntime(13072): FATAL EXCEPTION: main
    04-18 16:04:57.096: E/AndroidRuntime(13072): java.lang.NullPointerException
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.app.Fragment.setUserVisibleHint(Fragment.java:841)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.app.FragmentPagerAdapter.setPrimaryItem(FragmentPagerAdapter.java:130)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.view.ViewPager.populate(ViewPager.java:1066)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:550)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:509)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.support.v4.view.ViewPager.setCurrentItem(ViewPager.java:501)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at com.lochinvar.serf.MainActivity.onBackPressed(MainActivity.java:234)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.app.Activity.onKeyUp(Activity.java:2131)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.view.KeyEvent.dispatch(KeyEvent.java:2633)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.app.Activity.dispatchKeyEvent(Activity.java:2361)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1819)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.view.ViewRootImpl.deliverKeyEventPostIme(ViewRootImpl.java:3577)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.view.ViewRootImpl.handleImeFinishedEvent(ViewRootImpl.java:3547)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:2797)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.os.Handler.dispatchMessage(Handler.java:99)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.os.Looper.loop(Looper.java:137)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at android.app.ActivityThread.main(ActivityThread.java:4745)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at java.lang.reflect.Method.invokeNative(Native Method)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at java.lang.reflect.Method.invoke(Method.java:511)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
    04-18 16:04:57.096: E/AndroidRuntime(13072):    at dalvik.system.NativeStart.main(Native Method)

[EDIT] Я забыл упомянуть, что я перегрузил FragmentPagerAdapter.getPageTitle(), и он никогда не возвращает null (случай по умолчанию относится к строке).

Ответ 1

Наконец-то! Теперь я могу надежно воссоздать эту ошибку!

Другим способом, который я обнаружил, чтобы восстановить ошибку, является закрытие активности/приложения и быстро повторно открыть страницу с помощью фрагмента ViewPager. Возможно, вам придется попробовать несколько раз, потому что в моих тестах мне пришлось снова открыть приложение в течение примерно 30 мс. Это время может быть медленнее или быстрее для разных устройств скорости.

Проблема заключалась в том, что я только однажды создал фрагмент (используя new) один раз и сохранил ссылку на этот экземпляр, чтобы я мог его повторно использовать. Одно простое решение этой проблемы - всегда возвращать экземпляр new фрагмента FragmentPagerAdapter.getItem(...), как показано ниже.

public class ViewPagerAdapter extends FragmentPagerAdapter {
    ...

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0: return mMyFragment; // Error. Has the edge-case crash.
            case 1: return new MyFragment(); // Works.
            default: return new MyDefaultFragment();
        }
    }
}

ps - Корневая проблема, вероятно, имеет какое-то отношение к жизненному циклу фрагмента и пытается использовать его снова при его уничтожении.

pps - это решение устраняет исключение NullPointerException для android.app.Fragment.setUserVisibleHint(Fragment.java:997) и должно также работать для android.support.v4.app.Fragment.setUserVisibleHint.

Ответ 2

Это был настоящий мозговой твистер для меня. Мы удалили весь код, который заменил Fragment, и сохранили те же фрагменты для всего жизненного цикла Activity и все еще имели эту проблему. Только тогда viewPager.setOffscreenPageLimit(TABS);, где TABS - количество вкладок (в нашем случае 4), мы перестали получать ссылку NullPointerException.

FWIW. Я уверен, что проблема в коде Google. Мы не смогли добиться этого, чтобы запустить Nexus 5 на Lollipop, но он не работает на устройствах Samsung, работающих под управлением Kitkat. Когда я проследил эту ошибку, это выглядело как неудача, потому что ссылающийся Fragment уже прошел через внутреннюю функцию Fragment.initState, потому что Fragment id равен -1.

Ответ 3

У меня было плохое время в очень похожем случае (такое же исключение в Fragment.setUserVisibleHint), даже если я использовал FragmentStatePagerAdapter. Был случайный (таким образом, трудно воспроизвести) крах моего приложения, когда базовые данные, которые изменились.

В моем случае я, наконец, смог избавиться от этого сбоя, переопределив следующий способ в адаптере:

@Override
public void restoreState(Parcelable state, ClassLoader loader) {
    // don't super !
}

Я действительно думаю, что в этом классе есть ошибка, учитывая количество вопросов, подобных этому в StackOverflow.

Я надеюсь, что это может быть полезно кому-то.

Изменить: Я опубликовал аналогичный ответ android.support.v4.app.Fragment.setUserVisibleHint нулевого указателя при возобновлении работы, однако, Я не уверен, что эти вопросы повторяются. Тем не менее, я думаю, что этот ответ может помочь некоторым людям найти их для подобного симптома. Как я должен отметить это?