You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 05:35:56