버튼 수집상

[안드로이드] 웹크롬클라이언트로 웹뷰 새 창 처리하기 WebChromeClient onCreateWindow 본문

TIL - 안드로이드

[안드로이드] 웹크롬클라이언트로 웹뷰 새 창 처리하기 WebChromeClient onCreateWindow

cocokaribou 2024. 4. 1. 10:35

안드로이드 웹뷰에서 setSupportMultipleWindows를 true로 세팅하면 

웹에서 window.open()로 이동할 때 WebChromeClient의 onCreateWindow 함수를 타게 된다.

웹뷰 설정을 알고자 간단한 웹페이지와 앱을 만들어 테스트해봤다.

 

웹페이지의 구조는 이렇다.

① =새창=> ② => ③  => ① =새창=> ② => ③ => ① =새창=> ② => ③ ...

 

 

기본웹뷰 세팅

BaseWebview.kt

import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.webkit.CookieManager
import android.webkit.WebSettings
import android.webkit.WebView

@SuppressLint("SetJavaScriptEnabled")
class BaseWebView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : WebView(context, attrs, defStyleAttr) {
    init {
        settings.setSupportMultipleWindows(true) // 새 창 허용
        
        settings.javaScriptCanOpenWindowsAutomatically = true
        settings.javaScriptEnabled = true
        settings.setSupportZoom(true)
        settings.builtInZoomControls = false
        settings.useWideViewPort = true
        settings.domStorageEnabled = true
        settings.cacheMode = WebSettings.LOAD_DEFAULT
        settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
        settings.textZoom = 100

        val cookieManager = CookieManager.getInstance()
        cookieManager.setAcceptCookie(true)
        cookieManager.setAcceptThirdPartyCookies(this, true)
    }
}

 

메인 액티비티 레이아웃

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.pionnet.new_webview_test.BaseWebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <RelativeLayout
        android:id="@+id/sub_webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        android:background="@drawable/border"
        tools:visibility="visible"
        android:visibility="gone" />

</androidx.constraintlayout.widget.ConstraintLayout>

sub_webview라는 아이디의 RelativeLayout에 새 창을 쌓을 것.

서브 창에는 하얀색 테두리를 둘렀다.

 

메인 액티비티

MainActivity.kt

import android.os.Bundle
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    private val webview: BaseWebView by lazy { findViewById(R.id.webview) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        webview.webViewClient = WebViewClient()
        webview.webChromeClient = MyWebChromeClient(this, onBackPressedDispatcher)
        webview.loadUrl(Config.SITE_URL)
    }
}

WebViewClient는 그냥 디폴트로 붙였다.

 

새 창 처리하는 크롬클라이언트

MyWebChromeClient.kt

import android.app.AlertDialog
import android.content.Context
import android.os.Message
import android.view.ViewGroup
import android.webkit.JsResult
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.RelativeLayout
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams

var counter = 10

open class MyWebChromeClient(private val mContext: Context, private val onBackCallback: OnBackPressedDispatcher) : WebChromeClient() {
    private val subBaseView: RelativeLayout by lazy { (mContext as AppCompatActivity).findViewById(R.id.sub_webview) }
    private val mainWebView: BaseWebView by lazy { (mContext as AppCompatActivity).findViewById(R.id.webview) }

    init {
        // 백 키 누를 때마다 쌓여있는 새 창 벗겨내기
        onBackCallback.addCallback {
            if (subBaseView.isVisible) {
                val lastWebView = subBaseView.getChildAt(subBaseView.childCount - 1) as? BaseWebView
                lastWebView?.let {
                    if (it.canGoBack()) {
                        // 웹뷰 히스토리가 있으면 뒤로가기
                        it.goBack()
                    } else {
                        // 웹뷰 히스토리 없으면 새 창 remove
                        subBaseView.removeView(lastWebView)
                        counter -= 10
                        val secondLastWebView = subBaseView.getChildAt(subBaseView.childCount - 1) as? BaseWebView
                        secondLastWebView?.let {
                            // 새 창 remove 했는데 아래에 새 창이 또 있을 경우
                            secondLastWebView.evaluateJavascript("revealed();", null);
                        } ?: run {
                            // 새 창 remove 했는데 메인 웹뷰가 나왔을 경우
                            counter = 10
                            subBaseView.removeAllViews()
                            subBaseView.isVisible = false
                            mainWebView.evaluateJavascript("revealed();", null);
                        }
                    }
                }
            }
        }
    }

    override fun onCreateWindow(view: WebView, isDialog: Boolean, isUserGesture: Boolean, resultMsg: Message): Boolean {
        subBaseView.isVisible = true

        // subBaseView.removeAllViews() // 이러면 쌓이지 않음

        val subWebView = BaseWebView(mContext).apply {
            webChromeClient = this@MyWebChromeClient
            webViewClient = WebViewClient() // 디폴트값 shouldOverrideUrlLoading = false
            layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
            
            // 새 창이 쌓일 때마다 margin 값을 늘려서 쌓이는 것을 잘 보이게 만듦
            updateLayoutParams<ViewGroup.MarginLayoutParams> {
                setMargins(counter, counter, counter, counter)
            }
        }

        // WebViewTransport 설정 안 하면 새 창 처리 안 됨
        val transport = resultMsg.obj as WebView.WebViewTransport
        transport.webView = subWebView
        resultMsg.sendToTarget()

        subBaseView.addView(subWebView)
        counter += 10

        return true
    }

    override fun onCloseWindow(window: WebView) {
        super.onCloseWindow(window)
    }

    override fun onJsAlert(view: WebView, url: String, message: String, result: JsResult): Boolean {
        return jsAlert(message, result)
    }

    override fun onJsConfirm(view: WebView, url: String, message: String, result: JsResult): Boolean {
        return jsAlert(message, result)
    }

    private fun jsAlert(message: String, result: JsResult, hasNegativeBtn: Boolean = false): Boolean {
        AlertDialog.Builder(mContext).apply {
            setMessage(message)
            setCancelable(false)
            setPositiveButton(android.R.string.ok) { _, _ -> result.confirm() }
        }.create().show()
        return true
    }
}

웹페이지에서 revealed(); 자바스크립트 함수를 호출하면 간단한 alert 창이 뜬다.

 

위처럼 구현하면 아래처럼 동작한다.

웹페이지를 이동하면서 새 창을 쌓다가 -> 안드로이드 백키를 눌러서 새 창을 하나씩 remove할 때마다 웹페이지의 revealed(); 함수 호출.

 

setSupportMultipleWindows 값을 false로 설정하면, window.open()으로 이동시켜도 WebChromeClient를 타지 않는다.

 

728x90