【问题标题】:LifeCycle of Fragment in FragmentStatePagerAdapter is unexpectedFragmentStatePagerAdapter 中 Fragment 的 LifeCycle 出乎意料
【发布时间】:2017-07-08 09:36:06
【问题描述】:

我想使用 ViewPager 创建一个视频列表。我必须知道寻呼机项目何时可见,何时不可见。 我使用 VideoListAdapter 为 ViewPager 扩展 FragmentStatePagerAdapter。 我使用 Fragment 方法 setUserVisibleHint 来触发视频开始或暂停。 但是有一个问题,ViewPager的位置0的Fragment抛出了NullPointerException。然后我为 Fragment 的相关方法打印日志。
我进入 VideoListActivity 的日志: 07-08 17:06:50.264 E/lemon: startUpdate 07-08 17:06:50.264 E/lemon: instantiateItem 0 07-08 17:06:50.264 E/lemon: getItem 0 07-08 17:06:50.264 E/lemon: setUserVisibleHint 0 isVisibleToUser false 07-08 17:06:50.264 E/lemon: instantiateItem 1 07-08 17:06:50.264 E/lemon: getItem 1 07-08 17:06:50.264 E/lemon: setUserVisibleHint 0 isVisibleToUser false 07-08 17:06:50.264 E/lemon: setPrimaryItem 0 07-08 17:06:50.264 E/lemon: setUserVisibleHint 0 isVisibleToUser true 07-08 17:06:50.264 E/lemon: finishUpdate 07-08 17:06:50.265 E/lemon: onAttach 0 07-08 17:06:50.265 E/lemon: onAttach 1 07-08 17:06:50.265 E/lemon: onCreateView 0 07-08 17:06:50.267 E/lemon: onstart 0 07-08 17:06:50.267 E/lemon: onCreateView 1 07-08 17:06:50.269 E/lemon: onstart 1 07-08 17:06:50.270 E/lemon: startUpdate 07-08 17:06:50.270 E/lemon: setPrimaryItem 0 07-08 17:06:50.270 E/lemon: finishUpdate 07-08 17:06:50.297 E/lemon: startUpdate 07-08 17:06:50.297 E/lemon: setPrimaryItem 0 07-08 17:06:50.297 E/lemon: finishUpdate 07-08 17:06:50.297 E/lemon: startUpdate 07-08 17:06:50.297 E/lemon: setPrimaryItem 0 07-08 17:06:50.297 E/lemon: finishUpdate 07-08 17:06:50.703 E/lemon: startUpdate 07-08 17:06:50.703 E/lemon: setPrimaryItem 0 07-08 17:06:50.703 E/lemon: finishUpdate 07-08 17:06:50.704 E/lemon: startUpdate 07-08 17:06:50.704 E/lemon: setPrimaryItem 0 07-08 17:06:50.704 E/lemon: finishUpdate

我滚动到位置 1 的日志: 07-08 17:09:41.154 E/lemon: startUpdate 07-08 17:09:41.154 E/lemon: setPrimaryItem 0 07-08 17:09:41.154 E/lemon: finishUpdate 07-08 17:09:41.966 E/lemon: startUpdate 07-08 17:09:41.966 E/lemon: instantiateItem 2 07-08 17:09:41.967 E/lemon: getItem 2 07-08 17:09:41.967 E/lemon: setUserVisibleHint 0 isVisibleToUser false 07-08 17:09:41.967 E/lemon: setPrimaryItem 1 07-08 17:09:41.967 E/lemon: setUserVisibleHint 0 isVisibleToUser false 07-08 17:09:41.967 E/lemon: setUserVisibleHint 1 isVisibleToUser true 07-08 17:09:41.967 E/lemon: finishUpdate 07-08 17:09:41.968 E/lemon: onAttach 2 07-08 17:09:41.968 E/lemon: onCreateView 2 07-08 17:09:41.971 E/lemon: onstart 2 07-08 17:09:41.971 E/lemon: startUpdate 07-08 17:09:41.971 E/lemon: setPrimaryItem 1 07-08 17:09:41.971 E/lemon: finishUpdate 07-08 17:09:41.972 E/lemon: startUpdate 07-08 17:09:41.972 E/lemon: setPrimaryItem 1 07-08 17:09:41.972 E/lemon: finishUpdate
我分析了这些日志,发现位置0的fragment先调用setUserVisibleHint(true)然后调用onAttach(),但是位置1的fragment先调用onAttach(),后调用setUserVisibleHint(true)。

所以我在 Fragment 中编写了一个 onTrigger() 方法,同时在 onAttach() 和 setUserVisibleHint(true) 中调用,但是失败了。然后我调试我的代码,它表明在 onAttach() 中调用 onTrigger 时 onTrigger() 中的 isAdded() 返回 false。

所以这里有任何建议让我知道何时触发我的视频开始。非常感谢。

public class FullScreenVideoFragment extends Fragment {

    private FragmentFullScreenVideoBinding binding;
    int colorRes;
    int position;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.e("lemon", "onCreateView " + position);
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_full_screen_video, container, false);
        setView();
        return binding.getRoot();
    }

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

    public void setBgAndPosition(int position, int colorRes) {
        this.position = position;
        this.colorRes = colorRes;
    }

    @Override
    public void onAttach(Context context) {
        Log.e("lemon", "onAttach " + position);
        super.onAttach(context);
        onTriger();
    }

    @Override
    public void onDetach() {
        Log.e("lemon", "onDetach " + position);
        super.onDetach();
    }

    @Override
    public void onAttachFragment(Fragment childFragment) {
        Log.e("lemon", "onAttachFragment " + position);
        super.onAttachFragment(childFragment);
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        Log.e("lemon", "setUserVisibleHint " + position + " isVisibleToUser " + isVisibleToUser);
        super.setUserVisibleHint(isVisibleToUser);
        onTriger();
    }

    private void setView() {
        binding.getRoot().setBackgroundResource(colorRes);
        binding.position.setText(String.valueOf(position));
    }

    private void onTriger() {
        if (!isVisible()) return;
        binding.position.setText(position + " start");
    }
}

public class VideoListAdapter extends FragmentStatePagerAdapter {
    private LinkedList<FullScreenVideoFragment> fragmentCaches;
    private int[] colors = new int[]{android.graphics.Color.RED, android.graphics.Color.BLUE, android.graphics.Color.GREEN};

    public VideoListAdapter(FragmentManager fm) {
        super(fm);
        fragmentCaches = new LinkedList<>();
    }

    @Override
    public Fragment getItem(int position) {
        Log.e("lemon", "getItem " + position);
        FullScreenVideoFragment fragment = generateItem();
        return fragment;
    }

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

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        Log.e("lemon", "destroyItem " + position);
        super.destroyItem(container, position, object);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Log.e("lemon", "setPrimaryItem " + position);
        super.setPrimaryItem(container, position, object);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Log.e("lemon", "instantiateItem " + position);
        FullScreenVideoFragment fragment = (FullScreenVideoFragment) super.instantiateItem(container, position);
        fragment.setBgAndPosition(position, colors[position % 3]);
        return fragment;
    }

    @Override
    public void startUpdate(ViewGroup container) {
        Log.e("lemon", "startUpdate");
        super.startUpdate(container);
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        Log.e("lemon", "finishUpdate");
        super.finishUpdate(container);
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
        Log.e("lemon", "restoreState");
        super.restoreState(state, loader);
    }

    @Override
    public Parcelable saveState() {
        Log.e("lemon", "saveState");
        return super.saveState();
    }

    private FullScreenVideoFragment generateItem() {
        FullScreenVideoFragment neededFragment = null;
        if (!fragmentCaches.isEmpty()) {
            neededFragment = fragmentCaches.get(0);
            fragmentCaches.remove(0);
            return neededFragment;
        }
        neededFragment = new FullScreenVideoFragment();
        return neededFragment;
    }
}

片段xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data></data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/position"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textColor="@color/account_name_color"/>
    </RelativeLayout>
</layout>

【问题讨论】:

    标签: android lifecycle fragmentstatepageradapter


    【解决方案1】:

    我发现Fragments经常使用这三种方式来切换:

    • 显示/隐藏

    • 附加/分离(替换)

    • ViewPager

    这三种方式导致Fragment可见状态存在一些差异,所以我定义了以下类来区分这三种方式。如果一组Fragment使用相同的方式切换,我觉得应该是可靠的,如果切换方式不同,那我就不知道了,不仔细测试。

    import android.support.annotation.IntDef;
    import android.support.v4.app.Fragment;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    /**
     * Created by Kilnn on 2017/7/12.
     * A smart fragment know itself's visible state.
     */
    public abstract class SmartFragment extends Fragment {
    
        private boolean isFragmentVisible;
    
        @Override
        public void onResume() {
            super.onResume();
            int switchType = getSwitchType();
            if (switchType == ATTACH_DETACH) {
                notifyOnFragmentVisible();
            } else if (switchType == SHOW_HIDE) {
                if (!isHidden()) {
                    notifyOnFragmentVisible();
                }
            } else if (switchType == VIEW_PAGER) {
                //If the parent fragment exist and hidden when activity destroy,
                //when the activity restore, The parent Fragment  will be restore to hidden state.
                //And the sub Fragment which in ViewPager is also be restored, and the onResumed() method will callback.
                //And The sub Fragment's getUserVisibleHint() method will return true  if it is in active position.
                //So we need to judge the parent Fragment visible state.
                if (getUserVisibleHint() && isParentFragmentVisible()) {
                    notifyOnFragmentVisible();
                }
            }
        }
    
        @Override
        public void setUserVisibleHint(boolean isVisibleToUser) {
            super.setUserVisibleHint(isVisibleToUser);
            int switchType = getSwitchType();
            if (switchType == VIEW_PAGER) {
                if (isVisibleToUser) {
                    notifyOnFragmentVisible();
                } else {
                    notifyOnFragmentInvisible();
                }
            }
        }
    
        @Override
        public void onHiddenChanged(boolean hidden) {
            super.onHiddenChanged(hidden);
            int switchType = getSwitchType();
            if (switchType == SHOW_HIDE) {
                if (hidden) {
                    notifyOnFragmentInvisible();
                } else {
                    notifyOnFragmentVisible();
                }
            }
        }
    
        @Override
        public void onPause() {
            super.onPause();
            notifyOnFragmentInvisible();
        }
    
        private boolean isParentFragmentVisible() {
            Fragment parent = getParentFragment();
            if (parent == null) return true;
            if (parent instanceof SmartFragment) {
                return ((SmartFragment) parent).isFragmentVisible();
            } else {
                //TODO May be can't get the correct visible state if parent Fragment is not SmartFragment
                return parent.isVisible();
            }
        }
    
        public boolean isFragmentVisible() {
            // Don't judge the state of the parent fragment,
            // because if the parent fragment visible state changes,
            // you must take the initiative to change the state of the sub fragment
    //        return isFragmentVisible && isParentFragmentVisible();
            return isFragmentVisible;
        }
    
        public void notifyOnFragmentVisible() {
            if (!isFragmentVisible) {
                onFragmentVisible();
                isFragmentVisible = true;
            }
        }
    
        public void notifyOnFragmentInvisible() {
            if (isFragmentVisible) {
                onFragmentInvisible();
                isFragmentVisible = false;
            }
        }
    
        /**
         * If this method callback, the Fragment must be resumed.
         */
        public void onFragmentVisible() {
    
        }
    
        /**
         * If this method callback, the Fragment maybe is resumed or in onPause().
         */
        public void onFragmentInvisible() {
    
        }
    
        /**
         * Fragments switch with attach/detach(replace)
         */
        public static final int ATTACH_DETACH = 0;
    
        /**
         * Fragments switch with show/hide
         */
        public static final int SHOW_HIDE = 1;
    
        /**
         * Fragments manage by view pager
         */
        public static final int VIEW_PAGER = 2;
    
        @Retention(RetentionPolicy.SOURCE)
        @IntDef({ATTACH_DETACH, SHOW_HIDE, VIEW_PAGER})
        @interface SwitchType {
        }
    
        @SwitchType
        public abstract int getSwitchType();
    
    }
    

    所以你可以在 onFragmentVisible() 中做 onTrigger()。

    【讨论】:

    猜你喜欢
    • 2014-09-11
    • 2013-07-15
    • 1970-01-01
    • 1970-01-01
    • 2017-12-01
    • 2014-09-19
    • 2018-07-14
    • 2015-03-02
    相关资源
    最近更新 更多