简体   繁体   English

Android 为什么在使用 NavController 导航时会创建两个相同的片段

[英]Android why are two identical Fragments created when navigating with NavController

I have an app that uses the single activity and multiple fragments approach and I use the NavController for navigating.我有一个使用单一活动和多个片段方法的应用程序,我使用 NavController 进行导航。 Unfortunately, when navigating to a Fragment that contains a Runnable in an anymous class, two identical instances of this Fragment are being created and I don't understand why.不幸的是,当导航到包含任意 class 中的Runnable的片段时,正在创建该片段的两个相同实例,我不明白为什么。

Here is the code of the main activity:这是主要活动的代码:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    public static DB_SQLite_Helper sqLite_DB;

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        getSupportActionBar().hide();
        setContentView(view);

        sqLite_DB = new DB_SQLite_Helper(this);
    }
}

The Home-Fragment in the nav_graph is the Fragment FR_Menu that you can see here: nav_graph 中的 Home-Fragment 是您可以在此处看到的 Fragment FR_Menu

public class FR_Menu extends Fragment implements View.OnClickListener{

    private FragmentMenuBinding binding;


    public FR_Menu() {

    }


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

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        binding = FragmentMenuBinding.inflate(inflater, container, false);
        getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        binding.buttonExit.setOnClickListener(this);
        binding.buttonTest.setOnClickListener(this);
        return binding.getRoot();
    }

    @Override
    public void onClick(View view) {

        if(view.getId() == R.id.button_test) {
            Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToTest());
        }

        if(view.getId() == R.id.button_exit) {
            getActivity().finishAndRemoveTask();
        }

    }
}

Here I just have a OnClickListener and navigate to the Fragment with the Runnables by using the navController in the line Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToTest());在这里,我只有一个 OnClickListener 并使用Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToTest());行中的OnClickListener导航到带有 Runnables 的片段。 . . So far, so good.到目前为止,一切都很好。 Now the Fragment with the Runnable, called Test is created.现在创建了名为Test的带有 Runnable 的片段。 Here you see the code of this Fragment:在这里你可以看到这个片段的代码:

public class Test extends Fragment {

    private Handler handler = new Handler();
    int helpCounterRun =0;
    private boolean viewHasBeenCreated = false;
    private FragmentTestBinding binding;

    public Test() {

    }

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

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        binding = FragmentTestBinding.inflate(inflater, container, false);
        viewHasBeenCreated = true;
        getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        countDownTime();
        return binding.getRoot();
    }

    private void updateScreen() {
        Log.e("LogTag", "Method updateScreen - this: " + this);
    }

    private void countDownTime(){

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                helpCounterRun++;
                Log.e("LogTag", "Method run - helpCounterRun: " + helpCounterRun);
                Log.e("LogTag", "Method run - this: " + this);
                if(viewHasBeenCreated) {
                    countDownTime();
                }
            }

        }, 100);
        updateScreen();
    }

}

Next to the onCreate and onCreateView method this Fragment has 2 basic methods.除了onCreateonCreateView方法之外,这个 Fragment 有 2 个基本方法。 In the updateScreen method, the current Fragment is printed to the console.updateScreen方法中,将当前 Fragment 打印到控制台。 And in the countDownTime method a Runnable is created and an auxilliary variable helpCounterRun is incremented.并且在countDownTime方法中创建了一个 Runnable 并递增了一个辅助变量helpCounterRun The value of the auxillary variable and the current instance of the Runnable are printed to the console.辅助变量的值和 Runnable 的当前实例被打印到控制台。 The output looks like this: output 看起来像这样:

2022-04-18 10:01:33.742 25086-25086/com.example.game E/LogTag: Method run - helpCounterRun: 1
2022-04-18 10:01:33.743 25086-25086/com.example.game E/LogTag: Method run - this: com.example.game.Test$1@78ea3f9
2022-04-18 10:01:33.745 25086-25086/com.example.game E/LogTag: Method updateScreen - this: Test{706103f} (335e5b64-5e97-4f3d-ac1b-8f5a1fcc559c)}
2022-04-18 10:01:34.277 25086-25086/com.example.game E/LogTag: Method run - helpCounterRun: 1
2022-04-18 10:01:34.278 25086-25086/com.example.game E/LogTag: Method run - this: com.example.game.Test$1@9c893ee
2022-04-18 10:01:34.278 25086-25086/com.example.game E/LogTag: Method updateScreen - this: Test{5140689} (c513c6da-fb15-4273-bea2-dfd89382d9e8) id=0x7f08013e}
2022-04-18 10:01:34.294 25086-25086/com.example.game E/LogTag: Method run - helpCounterRun: 2
2022-04-18 10:01:34.305 25086-25086/com.example.game E/LogTag: Method run - this: com.example.game.Test$1@f8db08f
2022-04-18 10:01:34.306 25086-25086/com.example.game E/LogTag: Method updateScreen - this: Test{706103f} (335e5b64-5e97-4f3d-ac1b-8f5a1fcc559c)}
2022-04-18 10:01:34.382 25086-25086/com.example.game E/LogTag: Method run - helpCounterRun: 2
2022-04-18 10:01:34.382 25086-25086/com.example.game E/LogTag: Method run - this: com.example.game.Test$1@8b9ef1c
2022-04-18 10:01:34.382 25086-25086/com.example.game E/LogTag: Method updateScreen - this: Test{5140689} (c513c6da-fb15-4273-bea2-dfd89382d9e8) id=0x7f08013e}
2022-04-18 10:01:34.414 25086-25086/com.example.game E/LogTag: Method run - helpCounterRun: 3
2022-04-18 10:01:34.414 25086-25086/com.example.game E/LogTag: Method run - this: com.example.game.Test$1@9ad8725
2022-04-18 10:01:34.415 25086-25086/com.example.game E/LogTag: Method updateScreen - this: Test{706103f} (335e5b64-5e97-4f3d-ac1b-8f5a1fcc559c)}
2022-04-18 10:01:34.503 25086-25086/com.example.game E/LogTag: Method run - helpCounterRun: 3
2022-04-18 10:01:34.503 25086-25086/com.example.game E/LogTag: Method run - this: com.example.game.Test$1@9ae00fa
2022-04-18 10:01:34.504 25086-25086/com.example.game E/LogTag: Method updateScreen - this: Test{5140689} (c513c6da-fb15-4273-bea2-dfd89382d9e8) id=0x7f08013e}
2022-04-18 10:01:34.531 25086-25086/com.example.game E/LogTag: Method run - helpCounterRun: 4
2022-04-18 10:01:34.562 25086-25086/com.example.game E/LogTag: Method run - this: com.example.game.Test$1@ebec6ab
2022-04-18 10:01:34.562 25086-25086/com.example.game E/LogTag: Method updateScreen - this: Test{706103f} (335e5b64-5e97-4f3d-ac1b-8f5a1fcc559c)}
2022-04-18 10:01:34.611 25086-25086/com.example.game E/LogTag: Method run - helpCounterRun: 4
2022-04-18 10:01:34.611 25086-25086/com.example.game E/LogTag: Method run - this: com.example.game.Test$1@b04e108
2022-04-18 10:01:34.611 25086-25086/com.example.game E/LogTag: Method updateScreen - this: Test{5140689} (c513c6da-fb15-4273-bea2-dfd89382d9e8) id=0x7f08013e}

What you can see from the output of the updateScreen method, that 2 instances of this Fragment are created and are running simultaneously.updateScreen方法的 output 可以看出,此 Fragment 的 2 个实例已创建并同时运行。 One has the id Test{706103f} (335e5b64-5e97-4f3d-ac1b-8f5a1fcc559c) and the other Test{5140689} (c513c6da-fb15-4273-bea2-dfd89382d9e8) id=0x7f08013e} and thus the auxillary variable helpCounter is printed out 2 times with the same value before being incremented.一个具有 id Test{706103f} (335e5b64-5e97-4f3d-ac1b-8f5a1fcc559c)和另一个Test{5140689} (c513c6da-fb15-4273-bea2-dfd89382d9e8) id=0x7f08013e}因此辅助变量helpCounter被打印出来在递增之前具有相同值的 2 次。

My question is why is this happening.我的问题是为什么会这样。 I don't see any part of my code that explicity created 2 instances of the Fragment Test .我没有看到我的代码的任何部分明确创建了 Fragment Test的 2 个实例。 Do you have any idea what the cause of this strange behaviour might be and how I can tackle it?您是否知道这种奇怪行为的原因可能是什么以及我该如何解决?

Reminder : Does nobody have an idea why this is happening?提醒:没有人知道为什么会这样吗?

When you call setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) , you force your activity to go through a configuration change - changing from portrait to landscape orientation.当您调用setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)时,您通过配置更改将您的活动强制为 go - 从纵向更改为横向。 By default, Android is going to destroy your activity (and all fragments in it) and recreate it in your requested orientation.默认情况下,Android 将销毁您的活动(以及其中的所有片段)并按照您要求的方向重新创建它。

This is why you are getting a message Method updateScreen - getActivity(): null - that Fragment has been completely destroyed along with the activity it is in because of your configuration change.这就是为什么您收到一条消息Method updateScreen - getActivity(): null - 由于您的配置更改,Fragment 及其所在的 Activity 已被完全销毁。

However, you never stop calling countDownTime() over and over again even after your fragment's view is destroyed.但是,即使在片段的视图被销毁之后,您也永远不会停止一遍又一遍地调用countDownTime() This means you've created a permanent memory leak.这意味着您已经创建了一个永久性的 memory 泄漏。

You're already tracking whether the fragment's view is created via your viewHasBeenCreated , but you never set it back to false - you'd want to override onDestroyView() and use that as the signal that your View has been destroyed.您已经在跟踪片段的视图是否是通过您的viewHasBeenCreated创建的,但您从未将其设置回false - 您想要覆盖onDestroyView()并将其用作您的视图已被销毁的信号。 This is also the appropriate place to remove any postDelayed calls that haven't yet run by using removeCallbacksAndMessages()这也是使用removeCallbacksAndMessages()删除尚未运行的任何postDelayed调用的合适位置

@Override
public void onDestroyView() {
    super.onDestroyView();

    // Reset your variable to false
    viewHasBeenCreated = false;

    // And clean up any postDelayed callbacks that are waiting to fire
    handler.removeCallbacksAndMessages(null);
}

Note that you don't need to manually track viewHasBeenCreated - you can use getView() != null to do this same check, but by making sure you actually clean up your Handler in onDestroyView() , you won't need to do this check at all as you'll be guaranteed to only be running while your view is created.请注意,您不需要手动跟踪viewHasBeenCreated - 您可以使用getView() != null来执行相同的检查,但是通过确保在onDestroyView()中实际清理Handler ,您将不需要执行此操作完全检查,因为您将保证仅在创建视图时运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM