[Android] WebView 알고 쓰기

Juho Park
11 min readJun 19, 2024

--

Photo by rivage on Unsplash

시중에 출시된 앱 중 모든 기능을 네이티브로 구현한 앱이 얼마나 있을까?

그 수가 적지는 않겠지만 반대로 웹뷰를 포함한 (혹은 웹뷰를 패키징만한) 앱도 적지 않을 것입니다.

처음 웹뷰를 구현했을 당시 링크만 넣었을 뿐인데 이렇게까지 잘나온다고 생각했는데 세부 기능에 대해 작동을 시키기 위해선 여러 설정들을 필요로한다는 것을 알게되었습니다.

이번 글에서는 수 많은 앱에 사용하는 웹뷰의 세부 설정에 대해 설명합니다.

해당 글은 이전에 작성했던 블로그에서 발췌하였습니다.

WebView 세팅

웹뷰를 많이 다루면서 알아야되는 세팅항목들과 유용하게 사용했던 설정값들을 설명합니다.

웹뷰 세팅은 크게 3가지 클래스로 구성됩니다.

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

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

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

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

📌 WebView Settings

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

webView.settings.*로 설정해줍니다.

webView.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 클래스에서 자주 쓰이는 오버라이드 함수는 다음과 같습니다.

shouldOverringUrlLoaing
웹뷰에서 url이 로딩될 때 호출되며 앱에서 제어할 수 있습니다.
기본 반환 값은 false이며 로딩 제어 시 true를 반환해주어야 합니다.

onPageStarted
페이지가 로딩을 시작하는 시점에 호출됩니다.

onPageFinished
페이지가 로딩을 완료하는 시점에 호출됩니다.

onReceivedSslError
수신받은 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 클래스에서 자주 쓰이는 오버라이드 함수는 다음과 같습니다.

OnCreateWindow
웹뷰에서 새창이 로딩될 때 호출되며 앱에서 제어할 수 있습니다.
기본 반환 값은 false이며 로딩 제어 시 true를 반환해주어야 합니다.

onCloseWindow
웹뷰가 창을 닫는 시점에 호출됩니다.

onPermissionRequest
웹뷰에서 권한을 사용 시 호출되며 권한 사용을 수락할 수 있습니다.

onShowFileChooser
파일을 웹으로 전송할 수 있습니다. 반환값을 true로 설정하고 인텐트를 통해 데이터를 전송합니다.

getDefaultVidioPoster
플레이어 화면을 로딩할 때 기본 포스터가 노출됩니다. 반환 값을 수정해 포스터를 제거할 수 있습니다.

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 없애기!

--

--