如何避免屏幕旋转时重新创建活动?

发布于 2025-01-15 20:24:56 字数 2224 浏览 4 评论 0原文

我使用导航组件在我的应用程序中使用单一活动模式。我使用 YouTube Android 库来播放视频。当我单击视频播放器上的全屏图标时,顶部和底部工具栏必须消失,并且必须在横向模式下更改屏幕。但屏幕旋转后,活动被重新创建,视频停止并重新开始。问题是屏幕旋转后如何继续播放视频? 我找到了一种将 configChanges 添加到清单文件的解决方案,

<activity
            android:name=".ui.MainActivity"
            android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

这解决了我的问题,当屏幕旋转时,活动停止重新创建。但我不希望在所有片段中出现这种行为,我只需要在视频播放器所在的片段中出现这种行为。

这是我在片段中的代码:

 private fun fullScreenListener() {

        val decorView = activity?.window?.decorView?.let {
            val screenListener = object : YouTubePlayerFullScreenListener {
                override fun onYouTubePlayerEnterFullScreen() {
                    binding.youtubePlayer.enterFullScreen()
                    hideSystemUi(it)
                }

                override fun onYouTubePlayerExitFullScreen() {
                    showSystemUi(it)
                }

            }
            binding.youtubePlayer.addFullScreenListener(screenListener)
        }

    }

    private fun hideSystemUi(view: View) {
        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE

        MainActivity.hideBottomNavBar()

        WindowCompat.setDecorFitsSystemWindows(requireActivity().window, false)
        WindowInsetsControllerCompat(requireActivity().window,view).let { controller ->
            controller.hide(WindowInsetsCompat.Type.systemBars())
            controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        }
    }
    
    private fun showSystemUi(view: View) {

        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT

        MainActivity.showBottomNavBar()

        WindowCompat.setDecorFitsSystemWindows(requireActivity().window, true)
        WindowInsetsControllerCompat(requireActivity().window, view).show(WindowInsetsCompat.Type.systemBars())

    }

I use single Activity pattern in my app using Navigation component. I use YouTube Android library for playing the video. When I click full screen icon on video player the top and bottom tool bars have to be gone and the screen has to be changed on landscape mode. But after the screen has rotated the activity was recreated and video stops and starts over. The question is how to keep playing the video after the screen has rotated?
I found one solution to add configChanges to the manifest file

<activity
            android:name=".ui.MainActivity"
            android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

This solved my problem, the activity stopped being recreated when the screen was rotated. But I do not want this behavior in all fragments, I need it only in the fragment where the video player is located.

This is my code in Fragment:

 private fun fullScreenListener() {

        val decorView = activity?.window?.decorView?.let {
            val screenListener = object : YouTubePlayerFullScreenListener {
                override fun onYouTubePlayerEnterFullScreen() {
                    binding.youtubePlayer.enterFullScreen()
                    hideSystemUi(it)
                }

                override fun onYouTubePlayerExitFullScreen() {
                    showSystemUi(it)
                }

            }
            binding.youtubePlayer.addFullScreenListener(screenListener)
        }

    }

    private fun hideSystemUi(view: View) {
        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE

        MainActivity.hideBottomNavBar()

        WindowCompat.setDecorFitsSystemWindows(requireActivity().window, false)
        WindowInsetsControllerCompat(requireActivity().window,view).let { controller ->
            controller.hide(WindowInsetsCompat.Type.systemBars())
            controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        }
    }
    
    private fun showSystemUi(view: View) {

        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT

        MainActivity.showBottomNavBar()

        WindowCompat.setDecorFitsSystemWindows(requireActivity().window, true)
        WindowInsetsControllerCompat(requireActivity().window, view).show(WindowInsetsCompat.Type.systemBars())

    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

青朷 2025-01-22 20:24:56

抱歉,我不是 kotlin 开发人员,但这个解决方案修复了我的问题。但在我发布代码之前,让我向您解释一下,虽然它不是最可靠但更好的选择。

注意:如果您将这些行添加到清单中,则以下行将不会处理许多 Android 配置更改。

举例来说,您将此行添加到清单文件中。

 android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout">

现在,我将向您证明添加上述行根本不是一个选项:

  • 首先,假设此行仍在您的清单文件中,那么如果您的应用程序面向 Api 级别 29 及以上,请切换 Android 系统 ui深色模式位于设置>;显示&亮度>深色主题然后返回到您的应用,您会注意到您的活动已重新创建并且视频重新启动。
    现在,为了避免这种情况,您需要将 Uimode 添加到上面的代码行。

    android:configChanges="uimode|orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout" >
    

(注意它和第一个代码之间的区别)

现在您已经将 uimode 添加到了 configChanges,当 Android 系统 ui 暗模式开关切换时,特定的 Activity 将无法检测到更改。但它仍然不是最好的,因为它会导致以下问题:

  • 糟糕的用户体验,即如果用户切换主题但主题更改不会反映在您的应用程序中。
  • 假设您有一个仍在显示的警报对话框,并且您旋转屏幕,由于 smallestScreenSize | ,宽度往往会与屏幕重叠。 screenLayout 属性。
  • 假设您处于 MultiWindowChanged 状态,它也会导致糟糕的用户体验,因为 Activity 需要调整大小并重新创建屏幕 UI 布局以适应多窗口模式,但您最终会看到重叠。

无论如何,有太多的配置更改会导致活动重新启动,而不是添加此行并每次更改 configChanges 属性,只需使用:

  • onSavedInsatnceStateonRestoreInstanceState 属性或者
  • 利用android新的保存ui状态的方法,即viewModelsavedStateHandle

现在,如果你想使用方法1,你需要了解首先使用 Android 生命周期架构组件,然后使用 onSavedInsatnceState 保存并使用onRestoreInsatnceState 恢复用户界面状态。但根据 https://developer.android.com/reference/android/app/Activity

从 Honeycomb 开始,应用程序在其 onStop() 返回之前不会处于可终止状态。这会影响 onSaveInstanceState(android.os.Bundle) 何时被调用(它可以在 onPause() 之后安全地调用),并允许应用程序安全地等待直到 onStop() 保存持久状态。

将此声明为全局变量

private final String KEY_YOUTUBE_VIDEO_LENGTH_STATE = "youtube_length_state";

覆盖 onSavedInsatnceState 方法并添加以下代码。

@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
 // save YouTube video length state
    long videoStateLong = binding.youtubePlayer.getVideoLength();
    savedInstanceState.putLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE, videoStateLong);

//Call below line to save bundle

   super.onSaveInstanceState(savedInstanceState);
}

然后重写 onRestoreInstanceState 并添加以下行。

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);

// Retrieve video state and it's length.
if(savedInstanceState != null) {
    binding.youtubePlayer.setVideoLength = savedInstanceState.getLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}

最后,如果 onRestoreInstanceState 未被调用,则重写 onResume 方法并添加以下代码行。

@Override
protected void onResume() {
super.onResume();
Bundle savedInstanceState = new Bundle();
 if (savedInstanceState != null) {
    binding.youtubePlayer.getVideoLength.onRestoreInstanceState(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}

}
现在,在 onCreate 方法中,添加以下行

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState==null){
        playVideoFromBeginning(); // No video length is saved yet, play video from beginning 
    }else{
        restoreVideoPreviousLength(savedInstanceState); // Restore video length found in the Bundle and pass savedInstanceState as an argument 
    }
}

public void restoreVideoPreviousLength(Bundle savedInstanceState) {

 binding.youtubePlayer.setVideoLength = savedInstanceState.getLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}

注意:

  • onCreate 方法中的代码仅适用于屏幕旋转,但 onResume 中的代码适用于 uimode 更改等。
  • onSavedInsatnceStateonRestoreInstanceState 不应该永远用于存储大型数据集,例如获取 Recyclerview 项目。在这种情况下应该使用 ViewModel。

现在,如果你想使用第二种方法,即viewModel方法:

注意:ViewModel 的唯一职责是管理 UI 的数据。它永远不应该访问您的视图层次结构或保留对 Activity 或 Fragment 的引用。

现在您可以从这里了解更多信息 https://www.geeksforgeeks.org/ viewmodel-with-savedstate-in-android/

请记住,我不是 kotlin 开发人员

Sorry i am not a kotlin developer, but this solution fixed mine. But before i post the codes, let me explain it to you, although it's not the most reliable but a better option.

Note: If you add these lines to your manifest, there are a lot of android configuration changes that will not be handled by below lines.

Take for instance you added this line to your manifest file.

 android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout">

Now, i'll prove to you that adding above line is not an option at all:

  • Firstly, assuming this line is still in your manifest file, then if your app targets Api level 29 and above, toggle the android system ui dark mode which is located in Settings > Display & Brightness > Dark theme then return back to your app and you'll notice that your activity has been recreated and the video restarts.
    Now, to avoid that, then you'll need to add Uimode to the above line of code.

    android:configChanges="uimode|orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout" >
    

(Notice the difference between it and the first code)

Now that you've added uimode to configChanges, the particular activity won't be able to detect changes when the android system ui dark mode switch is toggled. But it's still not the best because it will cause the following:

  • A bad user experience whereby if a user switches theme but theme changes doesn't reflect in your app.
  • Let's assume that you have an Alert dialog that's still showing and you rotate your screen, the width tends to overlap the screen due to the smallestScreenSize | screenLayout attribute.
  • Let's assume that you're onMultiWindowChanged, it'll cause bad user experience too wherby the activity will want to resize and recreate the screen ui layout in order to adjust to the multi window mode but you'll end up seeing overlaps.

Anyways, there are so many configuration changes that will cause activity to restart and instead of adding this line and changing the configChanges attribute everytime just make use of the:

  • onSavedInsatnceState and onRestoreInstanceState attributes or
  • Make use of android new method of saving ui state which is viewModel and savedStateHandle

Now, if you want use method 1, you need to understand Android lifecycle architecture component first then use the onSavedInsatnceState to save and use the onRestoreInsatnceState to restore the ui states. But according to https://developer.android.com/reference/android/app/Activity

Starting with Honeycomb, an application is not in the killable state until its onStop() has returned. This impacts when onSaveInstanceState(android.os.Bundle) may be called (it may be safely called after onPause()) and allows an application to safely wait until onStop() to save persistent state.

Declare this as global variable

private final String KEY_YOUTUBE_VIDEO_LENGTH_STATE = "youtube_length_state";

Override onSavedInsatnceState method and add below codes.

@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
 // save YouTube video length state
    long videoStateLong = binding.youtubePlayer.getVideoLength();
    savedInstanceState.putLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE, videoStateLong);

//Call below line to save bundle

   super.onSaveInstanceState(savedInstanceState);
}

Then override onRestoreInstanceState and add below lines.

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);

// Retrieve video state and it's length.
if(savedInstanceState != null) {
    binding.youtubePlayer.setVideoLength = savedInstanceState.getLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}

Finally, incase onRestoreInstanceState is not called then override onResume method and add below lines of codes.

@Override
protected void onResume() {
super.onResume();
Bundle savedInstanceState = new Bundle();
 if (savedInstanceState != null) {
    binding.youtubePlayer.getVideoLength.onRestoreInstanceState(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}

}
Now, in the onCreate method, add below lines

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState==null){
        playVideoFromBeginning(); // No video length is saved yet, play video from beginning 
    }else{
        restoreVideoPreviousLength(savedInstanceState); // Restore video length found in the Bundle and pass savedInstanceState as an argument 
    }
}

public void restoreVideoPreviousLength(Bundle savedInstanceState) {

 binding.youtubePlayer.setVideoLength = savedInstanceState.getLong(KEY_YOUTUBE_VIDEO_LENGTH_STATE);
}

Note:

  • codes in onCreate method will only work for screen rotations but those in onResume will work for uimode change etc.
  • onSavedInsatnceState and onRestoreInstanceState should NEVER be used to store large datasets like fetching Recyclerview items. ViewModel should be used in cases like this instead.

Now, if you want to use the second method which is viewModel method:

Note: ViewModel's only responsibility is to manage the data for the UI. It should never access your view hierarchy or hold a reference back to the Activity or the Fragment.

Now you can learn more from here https://www.geeksforgeeks.org/viewmodel-with-savedstate-in-android/

Remember, i'm not a kotlin developer

〆凄凉。 2025-01-22 20:24:56

你真的不想这样做。问题不仅仅是轮流重新启动,而是至少有十几种情况可能导致活动重新启动,并且您无法阻止其中一些情况。在 Android 上,这实际上只是您需要忍受的事情,并学习如何编码以使其干净地重新启动。

不,您不能在运行时或仅对某些片段执行 configChanges。它适用于活动级别。

相反,您应该问一个不同的问题 - 告诉用户轮换时哪些部分不起作用,并询问如何通过重新启动来解决该问题。

You really do not want to do this. The problem isn't just the restart on rotation, it's that there's at LEAST a dozen situations that can cause an Activity restart, and you can't block some of them. On Android this is really just something you need to live with, and learn how to code to make it cleanly restart.

And no, you can't do configChanges at runtime or only for some fragments. It works on an Activity level.

Instead, you should ask a different question- tell use what isn't working when you rotate, and ask how to fix that with restart.

陌路终见情 2025-01-22 20:24:56

根据您的新答案-我很惊讶您的视频视图在没有工作的情况下不支持这一点。但是,如果您实现 onSaveInstanceState 来保存视频的查找时间,并实现 onRestoreInstanceState 来查找该时间,则在读取视频时最多应该出现短暂的停顿。

Based on your new answer- I'm surprised your video view doesn't support this without work. However, if you implement onSaveInstanceState to save the seek time of the video and onRestoreInstanceState to seek to that time, it should work with at most a brief hiccup as it reads in the video.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文