Android为什么使用NavController导航时创建两个相同的fragment ?



我有一个使用单个活动和多个片段方法的应用程序,我使用NavController进行导航。不幸的是,当导航到一个片段,其中包含一个Runnable在一个匿名类,这个片段的两个相同的实例正在创建,我不明白为什么。

下面是主活动的代码:

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);
}
}

nav_graph中的Home-Fragment是FragmentFR_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();
}
}
}

在这里,我只有一个OnClickListener,并通过使用行Navigation.findNavController(getView()).navigate(FR_MenuDirections.actionFRMenuToTest());中的navController导航到带有Runnables的片段。到目前为止,一切顺利。现在,创建了具有Runnable的片段,称为Test。下面是这个片段的代码:

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();
}
}

onCreateonCreateView方法旁边,这个Fragment有2个基本方法。在updateScreen方法中,当前Fragment被打印到控制台。在countDownTime方法中,创建了一个Runnable,并增加了一个辅助变量helpCounterRun。辅助变量的值和Runnable的当前实例被打印到控制台中。输出如下所示:

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}

您可以从updateScreen方法的输出中看到,该Fragment的2个实例被创建并同时运行。一个id为Test{706103f} (335e5b64-5e97-4f3d-ac1b-8f5a1fcc559c),另一个id为Test{5140689} (c513c6da-fb15-4273-bea2-dfd89382d9e8) id=0x7f08013e},因此辅助变量helpCounter在增加之前以相同的值打印2次。

我的问题是为什么会发生这种情况。我没有看到我的代码的任何部分显式地创建了片段Test的2个实例。你知道这种奇怪行为的原因是什么吗?我该如何解决?

没有人知道为什么会这样吗?

当您调用setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)时,您强制您的活动进行配置更改-从纵向更改为横向。默认情况下,Android会销毁你的activity(和其中所有的fragment),并在你请求的方向中重新创建它。

这就是为什么你得到一个消息Method updateScreen - getActivity(): null-片段已经完全被破坏,随着活动,因为你的配置更改。

然而,你永远不会停止调用countDownTime()一遍又一遍,即使在你的片段的视图被销毁。这意味着您已经创建了永久内存泄漏。

您已经跟踪片段的视图是否通过您的viewHasBeenCreated创建,但您从未将其设置回false-您想要覆盖onDestroyView()并使用它作为您的视图已被销毁的信号。这也是通过使用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);
}

注意,你不需要手动跟踪viewHasBeenCreated-你可以使用getView() != null来做同样的检查,但通过确保你在onDestroyView()中实际上清理了Handler,你将不需要做这个检查,因为你将保证只在视图创建时运行。

最新更新