ViewPager2设置Adapter报错IllegalArgumentExceptionyichang

ViewPager2设置Adapter报错IllegalArgumentException

1.问题出现场景

首页是由ViewPager2+Fragment实现,而第二个Fragment中又嵌套了ViewPager2+Fragment,当在首页跳转到其他页面后,再按返回键,则程序抛出异常,位置是第二个Fragment在设置adapter的时候报:

java.lang.IllegalArgumentException
        at androidx.core.util.Preconditions.checkArgument(Preconditions.java:36)
        at androidx.viewpager2.adapter.FragmentStateAdapter.onAttachedToRecyclerView(FragmentStateAdapter.java:132)
        at androidx.recyclerview.widget.RecyclerView.setAdapterInternal(RecyclerView.java:1209)
        at androidx.recyclerview.widget.RecyclerView.setAdapter(RecyclerView.java:1161)
        at androidx.viewpager2.widget.ViewPager2.setAdapter(ViewPager2.java:461)
        at com.lihao.wanandroid.ui.navigation.NavigationFragment.initView(NavigationFragment.kt:63)
        at com.lihao.jetpackcore.base.BaseVmFragment.onViewCreated(BaseVmFragment.kt:51)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:332)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
        at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1434)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1497)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2625)
        at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2577)
        at androidx.fragment.app.Fragment.performActivityCreated(Fragment.java:2722)
        at androidx.fragment.app.FragmentStateManager.activityCreated(FragmentStateManager.java:346)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1188)
        at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2224)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1997)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1953)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1849)
        at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

2.解决方案

  • 原因: 原因是adapter是一个成员变量,在Fragment销毁viewPager时,adapter并没有被释放,依旧持有之前的ViewPager中的RecyclerView对象,当Fragment重新创建时,新的viewPager不能和adapter进行绑定,所以抛出异常。
  • 解决办法:在调用viewpager.setAdapter()之前将adapter重新赋值即可,也可以设置成局部变量。

3. 问题分析

既然问题是在viewPager.setAdapter()时报的错,我们就从setAdapter()函数开始分析,以下是setAdapter()中的代码:

    public void setAdapter(@Nullable @SuppressWarnings("rawtypes") Adapter adapter) {
        final Adapter currentAdapter = mRecyclerView.getAdapter();
        mAccessibilityProvider.onDetachAdapter(currentAdapter);
        unregisterCurrentItemDataSetTracker(currentAdapter);
        mRecyclerView.setAdapter(adapter);
        mCurrentItem = 0;
        restorePendingState();
        mAccessibilityProvider.onAttachAdapter(adapter);
        registerCurrentItemDataSetTracker(adapter);
    }

从异常信息中可以看到报错的位置是RecyclerView.setAdapter(),然后又到了RecyclerView.setAdapterInternal(),这是因为RecyclerView.setAdapter()有调用了RecyclerView.setAdapterInternal()函数,通过注释我们知道这个函数是用来重新设置adapter并添加监听器,然后在调用onAttachedToRecyclerView()时抛出异常,那我们直接看setAdapterInternal()函数:

    private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            removeAndRecycleViews();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
    }

函数中首先判断了adapter是否为空,然后调用onAttachedToRecyclerView(),将recyclerView自身作为参数传递,然后进到onAttachedToRecyclerView中看到是一个空函数,这是因为我们看到的是Adapter基类下的实现,而我们传进来的是子类FragmentStateAdapter,那我们进到FragmentStateAdapter中查看onAttachedToRecyclerView()的实现:

    @CallSuper
    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        checkArgument(mFragmentMaxLifecycleEnforcer == null);
        mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
        mFragmentMaxLifecycleEnforcer.register(recyclerView);
    }

方法只有三行,首先是调用了checkArgument()函数,然后创建FragmentMaxLifecycleEnforcer对象,最后调用register(),而异常信息中最顶端报错的函数就是checkArgument(),也就是这个函数抛出了IllegalArgumentException异常,越来越接近真相了,那我们进去看一看:

    public static void checkArgument(boolean expression) {
        if (!expression) {
            throw new IllegalArgumentException();
        }
    }

,没想到这里面更简单,只是判断了一下expression,如果是false则跑出异常,而这个值是由刚才的方法传进来的mFragmentMaxLifecycleEnforcer == null,也就是说我的mFragmentMaxLifecycleEnforcer对象不为空,那么这个FragmentMaxLifecycleEnforcer类到底是个什么东西呢?然后我找到了对他的注释:

Pauses (STARTED) all Fragments that are attached and not a primary item.
Keeps primary item Fragment RESUMED.
翻译过来为:暂停(启动)所有附加的而不是主要项的片段。保持主项目片段恢复。

看的不是太懂,但是大致可以看出应该是一个管理生命周期的,那我们就不管他了,还是分析一下他为什么不为空吧。
回想了一下报错的场景,因为使用了Navigation管理Fragment的跳转,而Navigation存在一个问题就是当从AFragment跳转到BFragment时,AFragment会被销毁,当从BFragment返回到AFragment会重新走onCreateView(),也就是说viewpager会重新设置setAdapter(),而我的adapter设置的是成员变量,也就是虽然AFragment走了onDestoryView(),但是对象并没有被销毁,那么adapter也没有被重新创建,所以setAdapter()设置的还是之前的adapter,那么也找到原因所在了,也就是说将adapter设置为局部变量,在调用viewPager.setAdapter()重新赋值即可,运行了一下果然没有问题了。

不过你可能会产生疑问️,为什么设置之前的adapter就会报错呢,为了找到这个原因我又查看了FragmentStateAdapter的源码,发现和onAttachedToRecyclerView对应的还有一个方法onDetachedFromRecyclerView(),没错这个方法就是用来释放mFragmentMaxLifecycleEnforcer对象的,将其赋为null的,可以看他的源码:

    @CallSuper
    @Override
    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
        mFragmentMaxLifecycleEnforcer.unregister(recyclerView);
        mFragmentMaxLifecycleEnforcer = null;
    }

那么这个方法又是在哪里被调用的呢,在setAdapterInternal(),也就是上面我们分析的那个函数,这个函数中通过判断mAdapter是否为空,然后进行重制mAdapter,如果非空,则会进行释放,然后重新将传进来的adapter赋给mAdapter,那为什么我们的没有释放呢,那是因为它本来就是null,不需要释放。这所以会产生这样的问题,是因为Navigation的机制,在打开AFragment的时候,设置adapter时通过onAttachedToRecyclerView进行了绑定,而在AFragment跳转到BFragment的时候,AFragment会走onDestoryView(),所以viewPager已经被释放了,当BFragment返回到AFragment,viewpager已经不再是之前的不是一个对象了,所以此时新的Viewpager并没有设置Adapter,所以没有能释放FramengStateFragment中的mFragmentMaxLifecycleEnforcer对象,导致viewpager和FragemntStateFragment绑定失败抛出异常。

你可能感兴趣的:(ViewPager2设置Adapter报错IllegalArgumentExceptionyichang)