Android多次点击底部导航视图触发IllegalArgumentException崩溃求助
问题复现
我的App有一个承载3个Fragment的Activity,可通过点击底部导航视图完成Fragment间导航。功能本身运行正常,但当我多次快速点击底部导航视图时,运行时会抛出如下错误导致崩溃:
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)
相关代码
HomeActivity代码
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(); } }); } }); } }
SecondFragment代码
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>
依赖版本
当前使用的导航组件依赖为:"androidx.navigation:navigation-fragment:2.4.0-alpha01"
解决方案
方案1:升级导航组件版本(优先推荐)
该异常是2.4.0-alpha01测试版本的已知bug,快速点击时会同时触发多个Fragment事务,回退栈保存时出现跨事务引用冲突。直接将navigation依赖升级到2.4.0正式版及以上即可修复该问题,示例依赖配置:
implementation "androidx.navigation:navigation-fragment:2.5.3" implementation "androidx.navigation:navigation-ui:2.5.3"
方案2:添加点击防抖(临时兼容)
如果暂时无法升级依赖,可以给底部导航的点击事件添加防抖逻辑,避免短时间内多次触发导航,替换原有的NavigationUI.setupWithNavController(bottomNavigationView, navController)代码即可:
long lastNavTime = 0; // 自定义点击拦截逻辑 bottomNavigationView.setOnItemSelectedListener(item -> { long currentTime = System.currentTimeMillis(); // 500ms内只响应一次导航点击 if (currentTime - lastNavTime < 500) { return false; } lastNavTime = currentTime; return NavigationUI.onNavDestinationSelected(item, navController); }); // 同步导航目的地和底部导航选中状态 navController.addOnDestinationChangedListener((controller, destination, arguments) -> { Menu menu = bottomNavigationView.getMenu(); for (int i = 0; i < menu.size(); i++) { MenuItem item = menu.getItem(i); if (item.getItemId() == destination.getId()) { item.setChecked(true); break; } } });
内容的提问来源于stack exchange,提问作者Richard Wilson
相关产品推荐
相关产品推荐

