android-fragments 在底部导航视图上点击多次时,应用程序崩溃

wr98u20j  于 2022-11-14  发布在  Android
关注(0)|答案(4)|浏览(244)

我的应用程序有一个Activity托管了3个片段。这些片段可以通过点击底部导航视图来导航。它工作得很好,只是当我尝试分别点击底部导航视图时,它在运行时崩溃并出现以下错误:

java.lang.IllegalArgumentException: saveBackStack("48c3d9bf-beff-4ec0-8a1b-fb91b56a2765") must be self contained and not reference fragments from non-saved FragmentTransactions. Found reference to fragment SecondFragment{57f9be2} (dd3744e7-8aa3-4c45-b6bc-312a9d46afb4 id=0x7f0a00b0) in BackStackEntry{ba06b73 48c3d9bf-beff-4ec0-8a1b-fb91b56a2765} that were previously added to the FragmentManager through a separate FragmentTransaction.
        at androidx.fragment.app.FragmentManager.saveBackStackState(FragmentManager.java:2052)
        at androidx.fragment.app.FragmentManager$SaveBackStackState.generateOps(FragmentManager.java:3172)
        at androidx.fragment.app.FragmentManager.generateOpsForPendingActions(FragmentManager.java:1953)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1643)
        at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:480)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6819)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:497)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:912)

我已经检查了这个网站和其他几个网站的问题的解决方案,但没有找到。所以我想如果有人可以帮助。
以下是我当前活动的代码:

public class HomeActivity extends AppCompatActivity {
    private DrawerLayout drawer;
    // Last update time, click sound, search button, search panel.
    TextView time_field;
    MediaPlayer player;
    ImageView Search;
    EditText textfield;
    // For scheduling background image change(using constraint layout, start counting from dubai, down to statue of liberty.
    ConstraintLayout constraintLayout;
    public static int count = 0;
    int[] drawable = new int[]{R.drawable.dubai, R.drawable.central_bank_of_nigeria, R.drawable.eiffel_tower, R.drawable.hong_kong, R.drawable.statue_of_liberty};
    Timer _t;

    private WeatherDataViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        // use home activity layout.

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        // Allow activity to make use of the toolbar

        drawer = findViewById(R.id.drawer_layout);

        viewModel = new ViewModelProvider(this).get(WeatherDataViewModel.class);

        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar
                , R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        time_field = findViewById(R.id.textView9);
        Search = findViewById(R.id.imageView4);
        textfield = findViewById(R.id.textfield);
        //  find the id's of specific variables.

        BottomNavigationView bottomNavigationView = findViewById(R.id.bottomNavigationView);
        // host 3 fragments along with bottom navigation.
        final NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
        assert navHostFragment != null;
        final NavController navController = navHostFragment.getNavController();
        NavigationUI.setupWithNavController(bottomNavigationView, navController);

        // For scheduling background image change
        constraintLayout = findViewById(R.id.layout);
        constraintLayout.setBackgroundResource(R.drawable.dubai);
        _t = new Timer();
        _t.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                // run on ui thread
                runOnUiThread(() -> {
                    if (count < drawable.length) {

                        constraintLayout.setBackgroundResource(drawable[count]);
                        count = (count + 1) % drawable.length;
                    }
                });
            }
        }, 5000, 5000);

        Search.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // make click sound when search button is clicked.
                player = MediaPlayer.create(HomeActivity.this, R.raw.click);
                player.start();

                getWeatherData(textfield.getText().toString().trim());
                // make use of some fragment's data

                Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
                if (currentFragment instanceof FirstFragment) {
                    FirstFragment firstFragment = (FirstFragment) currentFragment;
                    firstFragment.getWeatherData(textfield.getText().toString().trim());
                } else if (currentFragment instanceof SecondFragment) {
                    SecondFragment secondFragment = (SecondFragment) currentFragment;
                    secondFragment.getWeatherData(textfield.getText().toString().trim());
                } else if (currentFragment instanceof ThirdFragment) {
                    ThirdFragment thirdFragment = (ThirdFragment) currentFragment;
                    thirdFragment.getWeatherData(textfield.getText().toString().trim());
                }
            }

            private void getWeatherData(String name) {

                ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);

                Call<Example> call = apiInterface.getWeatherData(name);

                call.enqueue(new Callback<Example>() {
                    @Override
                    public void onResponse(@NonNull Call<Example> call, @NonNull Response<Example> response) {

                        try {
                            assert response.body() != null;
                            time_field.setVisibility(View.VISIBLE);
                            time_field.setText("First Updated:" + " " + response.body().getDt());
                        } catch (Exception e) {
                            time_field.setVisibility(View.GONE);
                            time_field.setText("First Updated: Unknown");
                            Log.e("TAG", "No City found");
                            Toast.makeText(HomeActivity.this, "No City found", Toast.LENGTH_SHORT).show();
                        }
                    }

                    @Override
                    public void onFailure(@NotNull Call<Example> call, @NotNull Throwable t) {
                        t.printStackTrace();
                    }

                });
            }

        });
    }
}

编辑

第二个片段:

public class SecondFragment extends Fragment {

    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;

    public SecondFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment SecondFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static SecondFragment newInstance(String param1, String param2) {
        SecondFragment fragment = new SecondFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_second, container, false);
    }

    public void getWeatherData(String trim) {
    }
}

导航图:

<?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/my_nav"
    app:startDestination="@id/firstFragment">

    <fragment
        android:id="@+id/firstFragment"
        android:name="com.wiz.lightweatherforecast.FirstFragment"
        android:label="fragment_first"
        tools:layout="@layout/fragment_first" />
    <fragment
        android:id="@+id/secondFragment"
        android:name="com.wiz.lightweatherforecast.SecondFragment"
        android:label="fragment_second"
        tools:layout="@layout/fragment_second" />
    <fragment
        android:id="@+id/thirdFragment"
        android:name="com.com.wiz.lightweatherforecast.ThirdFragment"
        android:label="fragment_third"
        tools:layout="@layout/fragment_third" />
</navigation>

我通过点击底部的navviews(用红色勾号表示)来浏览片段:https://i.stack.imgur.com/ScFW6.jpg,利用该依赖性;实施"androidx.navigation:navigation-fragment:2.4.0-alpha01"

mm5n2pyu

mm5n2pyu1#

使用NavigationUI将setupWithNavController方法中的第三个参数(saveState)设置为false,将修复此崩溃。

NavigationUI.setupWithNavController(bottomNavigationView, navController,false)
nukf8bse

nukf8bse2#

正如你可能已经猜到的,问题是关于旧片段和新片段之间的竞争条件。

static volatile-一个看门人。如果旧的还没有完成,它将忽略新的。

private static volatile boolean isClicking = false;

...
Search.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v)
                {
                    if(!isClicking) {
                        isClicking = true;

                        // make click sound when search button is clicked.
                        player = MediaPlayer.create(HomeActivity.this, R.raw.click);
                        player.start();

                        getWeatherData(textfield.getText().toString().trim());
                        // make use of some fragment's data

                        Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
                        if(currentFragment instanceof FirstFragment) {
                            FirstFragment firstFragment = (FirstFragment) currentFragment;
                            firstFragment.getWeatherData(textfield.getText().toString().trim());
                        } else if(currentFragment instanceof SecondFragment) {
                            SecondFragment secondFragment = (SecondFragment) currentFragment;
                            secondFragment.getWeatherData(textfield.getText().toString().trim());
                        } else if(currentFragment instanceof ThirdFragment) {
                            ThirdFragment thirdFragment = (ThirdFragment) currentFragment;
                            thirdFragment.getWeatherData(textfield.getText().toString().trim());
                        }
                        
                        isClicking = false;
                    }
                }
            });
...

static synchronized-创建一个调用onClick()中所有内容的helper函数。这将对任务进行排队。

...
Search.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        syncOnClick();
    }
});
...

//Outside of "onCreate()".
private static synchronized void syncOnClick()
{
    // make click sound when search button is clicked.
    player = MediaPlayer.create(HomeActivity.this, R.raw.click);
    player.start();

    getWeatherData(textfield.getText().toString().trim());
    // make use of some fragment's data

    Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
    if(currentFragment instanceof FirstFragment) {
        FirstFragment firstFragment = (FirstFragment) currentFragment;
        firstFragment.getWeatherData(textfield.getText().toString().trim());
    } else if(currentFragment instanceof SecondFragment) {
        SecondFragment secondFragment = (SecondFragment) currentFragment;
        secondFragment.getWeatherData(textfield.getText().toString().trim());
    } else if(currentFragment instanceof ThirdFragment) {
        ThirdFragment thirdFragment = (ThirdFragment) currentFragment;
        thirdFragment.getWeatherData(textfield.getText().toString().trim());
    }
}
yk9xbfzb

yk9xbfzb3#

更新

我认为问题是你在BackStack中添加了已经存在的东西。我仍然没有看到完整的画面,因为我不知道你的NavController是如何执行BackStack操作的,但我认为我们可以通过在代码中添加以下内容来消除异常:
首先你需要在你的bottomNavigationView中添加一个监听器,然后我们在点击一个新条目时弹出backstack。如果你在你的activity onCreate()方法中添加以下代码,它应该可以工作。

bottomNavigationView.setOnNavigationItemSelectedListener(new NavigationView
                .OnNavigationItemSelectedListener() {
            @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                
              if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
                  getSupportFragmentManager().popBackStack();
              }
            }
        });

这无疑会扰乱你的后退导航,因为你告诉android在你点击一个新的片段时忘记过去的片段。换句话说,按下后退按钮将显示你过去的活动而不是过去的片段。但它应该允许你重复按任何导航项而不会抛出异常。
也有一些方法可以做到这一点,同时保留返回导航,但我认为最好是如果你尝试这一点,首先,看看它是否修复了问题之前proseding。

旧答案

我写这个作为一个答案,因为它可能会太长的一个评论。这不是一个适当的答案,因为我,我们需要看到更多的代码:
这个异常告诉您,您的片段的回栈存在问题(这基本上只是Android记住和存储你过去活动和片段状态的地方,这样一旦你按下后退按钮,你就会看到你之前看到的同样的东西)。我不能肯定地告诉是什么问题,因为我没有看到你的片段类,但它听起来像在你的代码可能有某种循环引用或smtng。也许从你的片段中添加代码。在你的位置上,我会看看名为SecondFragment的片段,它在异常中引用,特别是它的saveInstanceState方法。不确定是否以某种方式人为地使你的片段singleTasksingleInstance会有所帮助。我建议阅读BackStack。以下关于新FragmentManager发行版的文档似乎触及了您的问题

ttygqcqt

ttygqcqt4#

java.lang.IllegalArgumentException: saveBackStack must be self contained and not reference fragments from non-saved FragmentTransactions. Found reference to fragment SecondFragment in BackStackEntry that were previously added to the FragmentManager through a separate FragmentTransaction.
承欢明白:

  • 当您尝试从BottomNavigationView导航到SecondFragment时;它说这个特定片段(SecondFragment)已经存在于具有旧的/单独的FragmentTransaction的后栈中;所以把它重新加到后台堆栈是错误的。〉〉相反,它应该被重用。
    这意味着不执行:fm.beginTransaction().hide(currentFragment).show(newFragment).commit()
    它会改为:fm.beginTransaction().add(R.id.frag_container, newFragment, tag).commit()
  • 当重复敲击BottomNavView时,由于某种原因,它到达未保存的fragmentTransaction(未提交);然后当您尝试导航到另一个片段时,它会提示“您不应该从当前未提交的事务中引用新片段”。

现在尝试在项目被(重新选择)时解决此问题,方法是在BottonNavView中通过设置OnItemReselectedListener重新选择一个项目时弹出后堆栈:

BottomNavigationView bottomNavigationView = findViewById(R.id.bottomNavigationView);
// host 3 fragments along with bottom navigation.
final NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
assert navHostFragment != null;
final NavController navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(bottomNavigationView, navController);

bottomNavigationView.setOnItemReselectedListener((BottomNavigationView.OnNavigationItemReselectedListener)
        item -> navController.popBackStack(item.getItemId(), false)
);

相关问题