java.lang.IllegalStateException:指定的子级已经有父级。您必须首先在 jetpack compose 中对子级的父级调用 removeView()

bha*_*h v 3 android illegalstateexception android-jetpack-navigation android-jetpack-compose

我有一个带有一个 URL 的 Web 视图,每当它在 Web 视图中重定向时,我都会使用具有新标题的新 URL 再次加载相同的可组合项,因为重定向发生得如此之快,它发生在实际可组合项完全组成之前,因此它崩溃了java.lang.IllegalStateException:指定的子级已经有父级。您必须首先对子级的父级调用removeView()。我目前正在使用 ram Costa 的 compose 目标库

navigator.value?.navigate(direction = MenuViewDestination,onlyIfResumed = true)
Run Code Online (Sandbox Code Playgroud)

,我之前也使用过 compose 提供的导航,在这两种情况下我都面临着同样的问题,如果我仅使用恢复 true 进行导航,则页面由于某种原因不会自行导航,并且我无法按原样处理异常发生在内部。这里我附加了菜单可组合项中使用的可组合项来加载 URL,

/* Adding custom accompanist WebView*/
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun LoadMenuWebView(mainViewModel: MainViewModel, webViewModel: MenuWebViewModel, url: String?) {
    Timber.i("LoadWebView from menu $url")
    val context = LocalContext.current
    var extraHeaders: Map<String, String?>?
    webViewModel.menuWebViewState.value = url?.let {
        rememberWebViewState(
            it
        )
    }
    mainViewModel.currentWebViewClient.value = remember {
        getWebViewClient(
            context,
            mainViewModel.backEnabled,
            mainViewModel.progressVisible,
            mainViewModel.cookieManager,
            mainViewModel
        )
    }
    val state by remember { webViewModel.menuWebViewState }
    val navigator = rememberWebViewNavigator()
    // A custom WebChromeClient can be provided via subclassing
    if (state != null) {
        ObMenuWebView(
            state = state!!,
            captureBackPresses = false,
            onCreated = { webview ->
                webview.settings.apply {
                    javaScriptEnabled = true
                    builtInZoomControls = false
                    displayZoomControls = false
                    loadWithOverviewMode = true
                    cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
                    javaScriptCanOpenWindowsAutomatically = true
                    mediaPlaybackRequiresUserGesture = false
                    mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
                    useWideViewPort = true
                    domStorageEnabled = true
                    // Allow open _blank pages in browser
                    setSupportMultipleWindows(true)
                }
                webview.addJavascriptClient(mainViewModel, context)
              
            },
            navigator = navigator,
            client = remember {
                mainViewModel.currentWebViewClient.value
            },
            chromeClient = remember {
                ExternalPagesClient(context, mainViewModel._showExternalLinkDialog)
            },
            webViewModel = webViewModel
        )
    }

}
Run Code Online (Sandbox Code Playgroud)

这里需要注意的重要一点是,我对伴奏 webview 进行了一些修改,并使用 ViewModel 来存储现有 webview 的实例,因为每次我在应用程序内的可组合项之间导航时,伴奏 webview 都会重新组合,这会导致 webview 的重新加载,所以现在的解决方法是这样的,我知道在 ViewModel 中存储视图实例可能会导致内存泄漏,但我没有其他办法,

/**
 * A wrapper around the Android View WebView to provide a basic WebView composable.
 *
 * If you require more customisation you are most likely better rolling your own and using this
 * wrapper as an example.
 *
 * @param state The webview state holder where the Uri to load is defined.
 * @param captureBackPresses Set to true to have this Composable capture back presses and navigate
 * the WebView back.
 * @param navigator An optional navigator object that can be used to control the WebView's
 * navigation from outside the composable.
 * @param onCreated Called when the WebView is first created, this can be used to set additional
 * settings on the WebView. WebChromeClient and WebViewClient should not be set here as they will be
 * subsequently overwritten after this lambda is called.
 * @param client Provides access to WebViewClient via subclassing
 * @param chromeClient Provides access to WebChromeClient via subclassing
 * @sample com.google.accompanist.sample.webview.BasicWebViewSample
 */
@Composable
fun ObMenuWebView(
    state: com.ob_core_framework.base.WebViewState,
    modifier: Modifier = Modifier,
    captureBackPresses: Boolean = true,
    navigator: WebViewNavigator = rememberWebViewNavigator(),
    onCreated: (WebView) -> Unit = {},
    client: com.ob_core_framework.base.AccompanistWebViewClient = remember { com.ob_core_framework.base.AccompanistWebViewClient() },
    chromeClient: com.ob_core_framework.base.AccompanistWebChromeClient = remember { com.ob_core_framework.base.AccompanistWebChromeClient() },
    webViewModel: MenuWebViewModel
) {

    var existingWebView by remember { webViewModel.existingWebView }


    BackHandler(captureBackPresses && navigator.canGoBack) {
        existingWebView?.goBack()
    }

    LaunchedEffect(existingWebView, navigator) {
        with(navigator) { existingWebView?.handleNavigationEvents() }
    }

    // Set the state of the client and chrome client
    // This is done internally to ensure they always are the same instance as the
    // parent Web composable
    client.stateLocal = state
    client.navigatorLocal = navigator
    chromeClient.stateLocal = state

    AndroidView(
        factory = { context ->
            existingWebView ?: WebView(context).apply {
                onCreated(this)

                layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )

                webChromeClient = chromeClient
                webViewClient = client
            }.also {
                existingWebView = it
            }
        },
        modifier = modifier
    ) { view ->
        when (val content = state.content) {
            is WebContent.Url -> {
                val url = content.url

                if (url.isNotEmpty() && url != view.url) {
                    view.loadUrl(url, content.additionalHttpHeaders.toMutableMap())
                }
            }
            is WebContent.Data -> {
                view.loadDataWithBaseURL(content.baseUrl, content.data, null, "utf-8", null)
            }
        }

        navigator.canGoBack = view.canGoBack()
        navigator.canGoForward = view.canGoForward()
    }
}
Run Code Online (Sandbox Code Playgroud)

Raf*_*ani 7

一个 Android 视图不能有多个父视图。

在您的AndroidViewlambda 中,您可以检查视图是否已被赋予父级,这在重组期间将是正确的,因为您记住了您的View. 如果它有父级,您应该从父级的视图层次结构中删除该视图。

您可以修改工厂ObMenuWebView并添加以下内容

factory = { context ->
        existingWebView ?: WebView(context).apply {
            onCreated(this)

            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )

            webChromeClient = chromeClient
            webViewClient = client
        }.also {
            if(it.parent != null) (it.parent as ViewGroup).removeView(it) // this line is very crucial
            existingWebView = it
        }
    },
Run Code Online (Sandbox Code Playgroud)