Activity与前台WindowManager间WebView对象共享异常排查
问题描述
我实现了将WebView从Activity移到前台悬浮视图的功能:通过Activity的companion object获取WebView,移除其父视图后添加到悬浮视图中,能成功保留已加载的页面。但当用户返回应用时,无法将WebView移回Activity——用同样的方法(从前台获取WebView、移除父视图后添加到Compose的AndroidView组件)完全无效,Activity显示不出WebView,切回悬浮组件也无法显示。
期望:WebView能带着已加载的页面无缝回到Activity中。
关键代码
前台悬浮视图代码
class OverlayWebViewScreen( private val context: Context, private val webView: WebView, val lifecycleService: ForegroundComposeLifeCycle ) { private lateinit var windowManager: WindowManager private lateinit var layoutParams: WindowManager.LayoutParams fun open() { PermissionManager.askForDisplayOverApps(context) context.run { windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager layoutParams = WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT ) val parentView = webView.parent as? ViewGroup parentView?.removeView(webView) val composableView = ComposeView(context) lifecycleService.attachToDecorView(composableView) lifecycleService.onResume() composableView.setViewTreeLifecycleOwner(lifecycleService) composableView.setContent { var offsetX by remember { mutableFloatStateOf(0f) } var offsetY by remember { mutableFloatStateOf(0f) } Column(modifier = Modifier.size(300.dp)) { Row { Icon( modifier = Modifier .background(color = Color.Magenta) .pointerInput(Unit) { detectDragGestures { change, dragAmount -> offsetX += dragAmount.x offsetY -= dragAmount.y layoutParams.x = offsetX.toInt() layoutParams.y = offsetY.toInt() windowManager.updateViewLayout(composableView, layoutParams) } }, imageVector = Icons.Default.DragHandle, contentDescription = null ) IconButton(onClick = { windowManager.removeView(composableView) ServiceManager.restartActivityWhenKilled(context) }) { Icon( modifier = Modifier .background(color = Color.Magenta) , imageVector = Icons.Default.OpenInFull, contentDescription = null ) } } AndroidView( modifier = Modifier.size(200.dp), factory = { webView } ) AdsManager.BannerAdView(modifier = Modifier) } } // Set the position of the WebView at the bottom of the screen layoutParams.gravity = Gravity.BOTTOM or Gravity.START windowManager.addView(composableView,layoutParams) } } fun close(){ ServiceManager.restartActivityWhenKilled(context) } }
Activity的onStart方法
override fun onStart() { val backPressedCallback = onBackPressedCallbackOverride() logger("onStart") if (ServiceManager.isServiceActive()){ val parentView = webView.parent as? ViewGroup parentView?.removeView(webView) webView = AutoRefreshService.getWebView() webViewChanged.value = webViewChanged.value.not() logger(webView.toString()) } setContent { val context = LocalContext.current val activity = context as? ComponentActivity activity?.onBackPressedDispatcher?.addCallback(this, backPressedCallback) InitVariables() AutoRefreshWebPageTheme { Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { Scaffold( modifier = Modifier.fillMaxSize(), ) { paddingValues -> Column(modifier = Modifier.padding(paddingValues)) { AndroidView( modifier = Modifier.weight(1f), update = { if (webViewChanged.value) webView.bringToFront() }, factory = { webView }, ) AdsManager.BannerAdView(modifier = Modifier) } } if (autoRefreshDialog.value) { AutoRefreshDialog() } } } } if (preferences.getAutoRefreshStatus()){ if (ServiceManager.isServiceActive().not()) { startPageReloading(this) } } super.onStart() }
解决方案
问题核心是WebView移动时的生命周期绑定错误、Compose视图更新逻辑缺失,以及悬浮窗视图清理不彻底,以下是针对性修复:
1. 移回WebView时绑定Activity生命周期
WebView的可见性依赖于正确的生命周期绑定,从悬浮窗移回Activity时,必须重新绑定Activity的LifecycleOwner:
if (ServiceManager.isServiceActive()){ val webViewFromService = AutoRefreshService.getWebView() // 先从悬浮窗父视图移除WebView val parentView = webViewFromService.parent as? ViewGroup parentView?.removeView(webViewFromService) // 绑定Activity的生命周期 webViewFromService.setViewTreeLifecycleOwner(this) webView = webViewFromService webViewChanged.value = !webViewChanged.value }
2. 让AndroidView感知WebView变化
AndroidView的factory仅在首次创建时执行,需通过key参数触发视图重建,确保加载最新的WebView:
AndroidView( modifier = Modifier.weight(1f), factory = { webView }, key = webView // 当WebView对象变化时,重新执行factory挂载新视图 )
3. 清理悬浮窗时先剥离WebView
悬浮窗关闭时,先将WebView从ComposeView中移除,避免视图状态被破坏:
// 修改OverlayWebViewScreen的close方法 fun close() { // 先从父视图移除WebView val webViewParent = webView.parent as? ViewGroup webViewParent?.removeView(webView) // 再移除悬浮窗视图 if (::windowManager.isInitialized && this::composableView.isInitialized) { windowManager.removeView(composableView) } ServiceManager.restartActivityWhenKilled(context) }
4. 避免重复重建Compose树
将setContent移到onCreate中,仅通过State触发局部重组,而非每次onStart都重建整个界面:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val backPressedCallback = onBackPressedCallbackOverride() setContent { val context = LocalContext.current val activity = context as? ComponentActivity activity?.onBackPressedDispatcher?.addCallback(this, backPressedCallback) InitVariables() AutoRefreshWebPageTheme { Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { Scaffold( modifier = Modifier.fillMaxSize(), ) { paddingValues -> Column(modifier = Modifier.padding(paddingValues)) { AndroidView( modifier = Modifier.weight(1f), factory = { webView }, key = webViewChanged.value ) AdsManager.BannerAdView(modifier = Modifier) } } if (autoRefreshDialog.value) { AutoRefreshDialog() } } } } } override fun onStart() { super.onStart() logger("onStart") if (ServiceManager.isServiceActive()){ val webViewFromService = AutoRefreshService.getWebView() val parentView = webViewFromService.parent as? ViewGroup parentView?.removeView(webViewFromService) webViewFromService.setViewTreeLifecycleOwner(this) webView = webViewFromService webViewChanged.value = !webViewChanged.value logger(webView.toString()) } if (preferences.getAutoRefreshStatus()){ if (!ServiceManager.isServiceActive()) { startPageReloading(this) } } }
内容的提问来源于stack exchange,提问作者Ro'zimuhammad Xolxo'jayev
相关产品推荐
相关产品推荐

