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绑定失败抛出异常。