FragmentStatePagerAdapter 中片段的生命周期是意外的

LifeCycle of Fragment in FragmentStatePagerAdapter is unexpected

我想使用 ViewPager 创建视频列表。我必须知道寻呼机项何时可见和不可见。 我使用 VideoListAdapter 为 ViewPager 扩展 FragmentStatePagerAdapter。 我使用 Fragment 方法 setUserVisibleHint 来触发视频开始或暂停。 但是有个问题,ViewPager的0号位置的Fragment抛出NullPointerException。然后我为片段的相关方法打印日志。
我进入 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
我分析这些日志,我发现positino 0中的片段先调用setUserVisibleHint(true),然后调用onAttach(),但是位置1中的片段先调用onAttach(),然后调用setUserVisibleHint(true)。

所以我在 Fragment 中写了一个 onTrigger() 方法,在 onAttach() 和 setUserVisibleHint(true) 中调用,但失败了。然后我调试我的代码,它显示 onTrigger() 中的 isAdded() returns 在 onAttach().

中调用 onTrigger 时为 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>

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

  • show/hide

  • attach/detach(替换)

  • ViewPager

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

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