[Android] WebView 알고 쓰기

박주호·2022년 5월 5일
4

Hi, Android

목록 보기
4/7
post-thumbnail
post-custom-banner

WebView 세팅

하이브리드 앱을 만들기위해 웹뷰를 띄우는데, 여러 설정들을 통해 웹뷰의 상태와 상태값들을 알아낼 수 있다.

이전까지만해도 구글링을 통해 나온 블로그들을 참고하여 각 항목들이 어떤 기능을 하는지 명확하지 않은 상태에서 사용하곤 했다. 웹뷰를 많이 다루면서 알아야되는 세팅항목들과 유용하게 사용했던 설정값들을 알아보려한다.

웹뷰를 세팅하는 것은 크게 3가지가 있다.

  1. WebView Settings 설정
  2. WebViewClient 설정
  3. WebCromeClient 설정

2번과 3번은 명칭이 비슷해서 헷갈릴 수 있지만 용도가 다르므로 차이를 숙지해야한다.

WebViewClient 는 웹페이지가 로딩될 때 생기는 콜백 함수들로 구성되어있다. 웹 페이지 로딩의 시작과 끝을 알 수 있다.

WebChromeClient 는 웹페이지에서 일어나는 콜백 함수들로 구성되어 있다. 대표적으로 새 창을 띄우거나 파일을 첨부하는 경우다.

📌 WebView Settings

웹뷰의 가장 큰 범주에서 웹뷰를 세팅한다.

webView.settings.*로 설정해준다.

settings.apply{
  javaScriptEnabled= true // 자바스크립트 사용여부
  setSupportMultipleWindows(true) // 새창 띄우기 허용여부
  javaScriptCanOpenWindowsAutomatically= true // 자바스크립트가 window.open()을 사용할 수 있도록 설정
  loadWithOverviewMode= true // html의 컨텐츠가 웹뷰보다 클 경우 스크린 크기에 맞게 조정
  useWideViewPort= true // 화면 사이즈 맞추기 허용여부
  setSupportZoom(false) // 화면 줌 허용여부
  domStorageEnabled= true // DOM(html 인식) 저장소 허용여부

  // 파일 허용
  allowContentAccess= true
  allowFileAccess= true
  mixedContentMode= WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
  loadsImagesAutomatically= true
}

📌 WebViewClient

WebViewClient 클래스에서 자주 쓰이는 오버라이드 함수는 다음과 같다.

함수명반환설명
shouldOverringUrlLoadingboolean웹뷰에서 url이 로딩될 때 호출되며 앱에서 제어할 수 있다. 반환 default는 false이며 로딩 제어시 true를 반환해주어야 한다.
onPageStartedvoid페이지가 로딩이 시작되는 시점에 호출된다.
onPageFinishedvoid페이지가 로딩이 완료되는 시점에 호출된다.
onReceivedSslErrorvoid수신받은 SSL에러가 발생한 경우 호출되며 분기로직을 통해 처리 해준다.
webview.apply{
	...
	webViewClient = WebViewClientClass()
	...
}

inner class WebViewClientClass : WebViewClient(){
        override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
            (context as MainActivity).loadingCircleDialog.show()
            super.onPageStarted(view, url, favicon)
        }

        override fun onPageFinished(view: WebView?, url: String?) {
            (context as MainActivity).loadingCircleDialog.dismiss()
            super.onPageFinished(view, url)
        }

        override fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
            super.onReceivedSslError(view, handler, error)
            handler?.proceed()
            val builder: android.app.AlertDialog.Builder = android.app.AlertDialog.Builder(context)
            var message = "SSL Certificate error"
            when (error?.primaryError) {
                SslError.SSL_UNTRUSTED -> message = "신뢰할 수 없는 사이트입니다."
                SslError.SSL_EXPIRED -> message = "만료된 사이트입니다."
                SslError.SSL_IDMISMATCH -> message = "도메인이 없습니다."
                SslError.SSL_NOTYETVALID -> message = "검증되지 않은 사이트입니다."
            }
            message += "페이지로 이동 하시겠습니까?"
            builder.setTitle("SSL Certificate Error")
            builder.setMessage(message)
            builder.setPositiveButton("확인") { _, _ -> handler?.proceed() }
            builder.setNegativeButton("취소") { _, _ -> handler?.cancel() }
            val dialog: android.app.AlertDialog = builder.create()
            dialog.show()
        }

        override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
            Timber.i( "shouldOverrideUrlLoading url: $url")
            try {
                var intent: Intent? = null
                var isKakaoLogin = false
                if (url.contains(context.getString(R.string.kakao_intent))) {
                    intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME)
                    val packageManager = context.packageManager
                    isKakaoLogin = true
                    if (intent.resolveActivity(packageManager) != null) {
                        isKakaoLogin = true
                    }
                }
                if (intent != null && isKakaoLogin) {
                    context.startActivity(intent)
                } else {
                    webView.loadUrl(url)
                }
            } catch (e: Exception) {
                Timber.e( e.toString())
            }
            return true
        }
    }

📌 WebChromeClient

WebChromeClient 클래스에서 자주 쓰이는 오버라이드 함수는 다음과 같다.

함수명반환설명
onCreateWindowboolean웹뷰에서 새창이 로딩될 때 호출되며 앱에서 제어할 수 있다. 반환 default는 false이며 로딩 제어시 true를 반환해주어야 한다.
onCloseWindowvoid웹뷰가 창을 닫는 시점에 호출된다.
onPermissionRequestvoid웹뷰에서 권한을 사용 시 호출되며 권한 사용을 수락할 수 있다.
onShowFileChooserboolean파일을 웹으로 전송할 수 있다. 반환값을 true설정하고 인텐트를 통해 데이터를 전송한다.
getDefaultVideoPosterbitmap플레이어 화면을 로딩할 때 디폴트 포스터가 노출된다. 반환 값을 수정해서 포스터를 없앨 수 있다.
var uploadMessage: ValueCallback<Array<Uri?>>? = null
...

webview.apply{
	...
	webChromeClient = WebChromeClientClass()
	...
}

inner class WebChromeClientClass : WebChromeClient() {
        override fun onCreateWindow(view: WebView?, isDialog: Boolean, isUserGesture: Boolean, resultMsg: Message?): Boolean {
            Timber.i( "onCreateWindow url")
            val url = view?.url
            if (url != null) {
                Timber.i("new load url: $url")
            }

            val newWebView = WebView(context).apply {
                settings.run {
                    javaScriptEnabled = true
                    setSupportMultipleWindows(false)
                }
            }

            newWebView.webChromeClient = object : WebChromeClient() {
                override fun onCloseWindow(window: WebView?) {}
            }
            (resultMsg?.obj as WebView.WebViewTransport).webView = newWebView
            resultMsg.sendToTarget()
            return true
        }

        override fun onPermissionRequest(request: PermissionRequest?) {
            Timber.e( "onPermissionRequest")
            try {
                request?.grant(request.resources)
            } catch (e: Exception) {
                Timber.e( "permissionRequest: $e")
            }
        }

        override fun onShowFileChooser(
            webView: WebView?,
            filePathCallback: ValueCallback<Array<Uri?>>,
            fileChooserParams: FileChooserParams
        ): Boolean {
            if(uploadMessage != null){ // 값이 존재하면 널값을 넣어 초기화해주어야 한다.
                uploadMessage!!.onReceiveValue(null)
            }
            uploadMessage = filePathCallback

            val intent = Intent()
            intent.apply {
                action = Intent.ACTION_GET_CONTENT
                addCategory(Intent.CATEGORY_OPENABLE)
                type = "*/*"
            }
            (this@WebViewSetting.context as MainActivity).requestActivity.launch(Intent.createChooser(intent, "File Chooser"))
            return true
        }

        override fun getDefaultVideoPoster(): Bitmap? {
            return Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
        }
    }

파일선택의 경우 인텐트에 파일 카테고리와 타입을 설정하고 미리 선언한 requestActivity 변수를 통해 선택한 파일을 전송한다.

비고

WebViewClient()shouldOverrideUrlLoadingWebChromeClient()onCreateWindow 가장 큰 차이는 새탭이 열리는지의 유무이다.

참고

안드로이드 웹뷰(WebView) 셋팅 (Kotlin)

[안드로이드] WebViewClient와 WebChromeClient

[JavaScript] DOM이란 무엇인가?

WebView에서 노출되는 Player Default Poster 없애기!

profile
항상 배우려는 자세로
post-custom-banner

0개의 댓글