Android ViewPager 2 - 密钥不再存在片段

问题描述 投票:0回答:2

我有一个应用程序,其中主片段包含与 ViewPager2 绑定的选项卡布局,以便在三个不同片段之间导航。当我旋转手机屏幕时,应用程序崩溃并出现以下错误:

键 f#0 的片段不再存在:唯一 id c4576c9c-4bbd-4dc0-a304-b86653ed2820 (完整的错误堆栈在帖子末尾)

这是我的设置:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:id="@+id/main_activity_coordinator"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical">
    <com.google.android.material.appbar.AppBarLayout
        app:layout_constraintTop_toTopOf="parent"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:id="@+id/app_bar"
        android:theme="@style/Theme.VirtualGymBuddy.AppBarOverlay"
    >

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:title="@string/app_name" />


    </com.google.android.material.appbar.AppBarLayout>


    <androidx.fragment.app.FragmentContainerView
        android:tag="MAIN-FRAG"
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph"
        />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

main_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:tag="MAIN-FRAG"
    android:id="@+id/main_relative_layout"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:paddingTop="?attr/actionBarSize"
    xmlns:tools="http://schemas.android.com/tools">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabTextAppearance="@style/MyTabLayoutStyle"
        >

        <com.google.android.material.tabs.TabItem
            android:id="@+id/tab1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/tab1" />

        <com.google.android.material.tabs.TabItem
            android:id="@+id/tab2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/tab2" />

        <com.google.android.material.tabs.TabItem
            android:id="@+id/tab3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/tab3" />

    </com.google.android.material.tabs.TabLayout>
        <androidx.viewpager2.widget.ViewPager2
            android:layout_below="@id/tabBar"
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
        />
</RelativeLayout>

内容_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/content_main_constraint"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <fragment
        android:id="@+id/nav_host_fragment_content_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph"
        />
</androidx.constraintlayout.widget.ConstraintLayout>

ActivityMain.java

public class MainActivity extends AppCompatActivity {
    private AppBarConfiguration appBarConfiguration;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(null);
        setContentView(R.layout.activity_main);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onSupportNavigateUp() {
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
        return NavigationUI.navigateUp(navController, appBarConfiguration)
                || super.onSupportNavigateUp();
    }

}

MainFragment.java

public class MainFragment extends Fragment {
    private ViewPager2 viewPager;
    private NavigationAdapter navigationAdapter;

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

        getActivity().setContentView(R.layout.main_fragment);
        TabLayout tabLayout = getActivity().findViewById(R.id.tabBar);
        int[] tabTexts = {
                R.string.tab_1,
                R.string.tab_2,
                R.string.tab_3
        };

        // set up view pager and attach mediator to tab layout
        this.viewPager = getActivity().findViewById(R.id.pager);
        this.navigationAdapter = new NavigationAdapter(this);
        viewPager.setAdapter(this.navigationAdapter);
        new TabLayoutMediator(tabLayout, viewPager,
                (tab, position) -> tab.setText(
                        tabTexts[position]
                )
        ).attach();
    }
}

NavigationAdapter.java

public class NavigationAdapter extends FragmentStateAdapter {


    public NavigationAdapter(@NonNull FragmentActivity fragmentActivity) {
        super(fragmentActivity);
    }

    public NavigationAdapter(@NonNull Fragment fragment) {
        super(fragment);
    }

    public NavigationAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
        super(fragmentManager, lifecycle);
    }


    @NonNull
    @Override
    public Fragment createFragment(int position) {
        switch (position) {
            case 0:
                return new Fragment1();
            case 1:
                return new Fragment2();
            case 2:
                return new Fragment3();

        }
        throw new AssertionError();
    }

    @Override
    public int getItemCount() {
        return 3;
    }
}

nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/MainFragment">

    <fragment
        android:id="@+id/MainFragment"
        android:name="com.myapp.fragments.MainFragment"
        android:label="@string/first_fragment_label"
        tools:layout="@layout/main_fragment" />
</navigation>

一切正常,我可以浏览 ViewPager2 处理的片段。

但是,一旦我在其中一个片段内旋转屏幕,我就会得到:

2022-05-08 00:08:12.339 12343-12343/com.myapp E/FragmentManager: Fragment no longer exists for key f#0: unique id c4576c9c-4bbd-4dc0-a304-b86653ed2820
2022-05-08 00:08:12.339 12343-12343/com.myapp E/FragmentManager: Activity state:
2022-05-08 00:08:12.358 12343-12343/com.myapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.myapp, PID: 12343
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.myapp/com.myapp.MainActivity}: java.lang.IllegalStateException: Fragment no longer exists for key f#0: unique id c4576c9c-4bbd-4dc0-a304-b86653ed2820
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2827)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2902)
        at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4784)
        at android.app.ActivityThread.-wrap18(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1609)
        at android.os.Handler.dispatchMessage(Handler.java:105)
        at android.os.Looper.loop(Looper.java:169)
        at android.app.ActivityThread.main(ActivityThread.java:6578)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
     Caused by: java.lang.IllegalStateException: Fragment no longer exists for key f#0: unique id c4576c9c-4bbd-4dc0-a304-b86653ed2820
        at androidx.fragment.app.FragmentManager.getFragment(FragmentManager.java:975)
        at androidx.viewpager2.adapter.FragmentStateAdapter.restoreState(FragmentStateAdapter.java:549)
        at androidx.viewpager2.widget.ViewPager2.restorePendingState(ViewPager2.java:350)
        at androidx.viewpager2.widget.ViewPager2.dispatchRestoreInstanceState(ViewPager2.java:375)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3729)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3729)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3729)
        at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3729)
        at android.view.View.restoreHierarchyState(View.java:17620)
        at com.android.internal.policy.PhoneWindow.restoreHierarchyState(PhoneWindow.java:2130)
        at android.app.Activity.onRestoreInstanceState(Activity.java:1122)
        at android.app.Activity.performRestoreInstanceState(Activity.java:1077)
        at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1260)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2800)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2902) 
        at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4784) 
        at android.app.ActivityThread.-wrap18(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1609) 
        at android.os.Handler.dispatchMessage(Handler.java:105) 
        at android.os.Looper.loop(Looper.java:169) 
        at android.app.ActivityThread.main(ActivityThread.java:6578) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767) 

我的设置有什么问题吗?

java android xml android-fragments
2个回答
3
投票

viewpager2 与 recyclerview 分离存在问题 https://issuetracker.google.com/issues/154751401

据我所知,有两个修复:

  1. 将此行添加到您的 xml viewpager2 组件中。

       <androidx.viewpager2.widget.ViewPager2
        android:layout_below="@id/tabBar"
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:saveEnabled="false">
    

但是上面的实例并没有保存当前实例,片段每次弹出时都会重新创建该实例。

  1. 推荐的解决方案是在您的主片段上添加以下行

     override fun onDestroyView() {
        super.onDestroyView()
        viewPager.adapter = null
    }
    

0
投票

我发现只有第一个修复有效: 安卓:saveEnabled =“假”

将适配器设置为 null 是不够的,因为 viewPager2 仍然使用片段 id 保存状态,然后尝试恢复它

© www.soinside.com 2019 - 2024. All rights reserved.