네이티브 환경에서 웹 페이지를 제공하려고 하는 경우 사용합니다.
Android의 View 클래스를 확장하여 WebView라는 이름으로 쓰입니다.
<manifest ... >
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
val myWebView: WebView = findViewById(R.id.webview)
myWebView.loadUrl("http://www.example.com")
아래에서 어떤 웹뷰를 어떻게 띄울거고, 어떻게 상호작용할건지 알아보겠습니다.
웹 뷰를 띄우기 위한 세팅을 합니다.
WebView가 "viewport" HTML 메타 태그에 대한 지원을 활성화해야 하는지 아니면 넓은 뷰포트를 사용해야 하는지를 설정합니다.
false 인 경우 : 레이아웃 너비는 항상 장치 독립적(CSS) 픽셀 단위의 WebView 컨트롤 너비로 설정됩니다.
true 이고 페이지에 뷰포트 메타 태그가 포함 : 태그에 지정된 너비 값이 사용됩니다. 페이지에 태그가 없거나 너비를 제공하지 않으면 넓은 뷰포트가 사용됩니다.
JavaScript 실행을 활성화 여부를 WebView에 지시합니다.
true로 설정하지 않을 시, 웹 페이지가 열리지 않거나 디바이스의 크롬을 통해 열립니다.
기본값은 false 입니다.
WebView가 화면 확대/축소 컨트롤 및 동작을 사용하여 확대/축소를 지원해야 하는지 여부를 설정합니다.
사용해야 하는 특정 확대/축소 메커니즘은 setBuiltInZoomControls(boolean) 으로 설정할 수 있습니다. WebView#zoomIn() 및 WebView#zoomOut() 메서드를 사용하여 수행되는 확대/축소에 영향을 주지 않습니다.
기본값은 true입니다.
WebView가 내장된 확대/축소 메커니즘을 사용해야 하는지 여부를 설정합니다. 아래의 displayZoomControls와 세트입니다.
내장된 확대/축소 메커니즘은 WebView의 콘텐츠 위에 표시되는 화면 확대/축소 컨트롤과 확대/축소를 제어하기 위한 핀치 제스처의 사용으로 구성됩니다.
이러한 화면 컨트롤의 표시 여부는 setDisplayZoomControls(boolean) 을 사용하여 설정할 수 있습니다.
기본값은 false입니다.
내장된 메커니즘은 현재 지원되는 유일한 확대/축소 메커니즘이므로 이 설정을 항상 활성화하는 것이 좋습니다. 그러나 화면 확대/축소 컨트롤은 Android에서 더 이상 사용되지 않으므로(ZoomButtonsController 참조) setDisplayZoomControls(boolean) 을 비활성화하는 것이 좋습니다.
어차피 기본값이 false이므로, displayZoomControls를 굳이 호출할 필요 없이 builtInZoomControls 자체를 호출하지 않는 것도 좋아보입니다.
내장된 확대/축소 메커니즘을 사용할 때 WebView가 화면 확대/축소 컨트롤을 표시해야 하는지 여부를 설정합니다. setBuiltInZoomControls(boolean) 을 참조하세요.
기본값은 true입니다.
그러나 화면 확대/축소 컨트롤은 Android에서 더 이상 사용되지 않으므로(ZoomButtonsController 참조) 이를 false로 설정하는 것이 좋습니다.
HTML 페이지를 디코딩할 때 사용할 기본 텍스트 인코딩 이름을 설정합니다.
기본값은 "UTF-8"입니다.
WebView가 overview 모드에서 페이지를 로드할지 여부, 즉 너비에 따라 화면에 맞게 콘텐츠를 축소할지 여부를 설정합니다.
이 설정은 콘텐츠 너비가 WebView 컨트롤의 너비보다 클 때(예: getUseWideViewPort()가 활성화된 경우) 고려됩니다.
기본값은 false입니다.
데이터베이스 스토리지 API 활성화 여부를 설정합니다.
기본값은 false입니다.
데이터베이스 저장소 API를 올바르게 설정하는 방법은 setDatabasePath(String)를 참조하세요.
이 설정은 프로세스의 모든 WebView 인스턴스에 전역적으로 적용됩니다.
WebView 구현은 해당 시점 이후에 이 설정에 대한 변경 사항을 무시할 수 있으므로 지정된 프로세스 내에서 WebView 페이지를 로드하기 전에만 이 설정을 수정해야 합니다.
DOM 저장소 API의 활성화 여부를 설정합니다. DomStorage인 localStorage, sessionStorage 를 사용할수 있도록 허용합니다
localStorage
sessionStorage
localStorage나 sessionStorage를 쓰지 않고, 세션이나 쿠키만 사용하는 경우가 있을수 있겠지만 그렇지 않은 경우에 true로 설정합니다.
기본값은 false입니다.
보안 원본이 안전하지 않은 원본에서 리소스를 로드하려고 시도할 때(=HTTPS 프로토콜을 사용하는 사이트에서 HTTP 리소스를 로드하는 것) WebView의 동작을 구성합니다.
Build.VERSION_CODES.KITKAT 이하 앱의 기본값은 MIXED_CONTENT_ALWAYS_ALLOW 입니다.
Build.VERSION_CODES.LOLLIPOP 이상 앱의 기본값은 MIXED_CONTENT_NEVER_ALLOW 입니다.
캐시가 사용되는 방식을 재정의합니다.
정상적인 페이지 로드의 경우 캐시를 확인하고 필요에 따라 콘텐츠의 유효성을 다시 검사합니다. 뒤로 탐색할 때 콘텐츠의 유효성이 다시 검사되지 않고 대신 캐시에서 콘텐츠가 검색됩니다.
기본값은 LOAD_DEFAULT 입니다.
LOAD_CACHE_ONLY - 캐시만 사용하여 페이지를 로드합니다.
LOAD_CACHE_ELSE_NETWORK - 캐시로 먼저 로드하고 나머지를 네트워크로 로드합니다.
LOAD_DEFAULT - 캐시를 사용하지만, 서버에서 갱신된 내용이 있다면 불러옵니다.
LOAD_NO_CACHE - 항상 네트워크만 사용하여 페이지를 로드합니다.
자바 스크립트에서는 새 창을 띄울 때, window.open()을 사용합니다.
이 때 window.open()을 사용할 수 있도록 해주는 설정이 javaScriptCanOpenWindowsAutomatically입니다.
기본값은 false입니다.
WebView가 다중 창을 지원하는지 여부를 설정합니다.
true로 설정된 경우 호스트 애플리케이션에서 WebChromeClient#onCreateWindow를 구현해야 합니다.
기본값은 거짓입니다.
allowFileAccess - 파일 접근 허용 설정
allowContentAccess - 웹뷰를 통해 content url에 접근할지 여부
둘 다 웹 뷰의 속성들을 제어하기 위해 필요한 구현체들입니다. 하지만 둘은 다릅니다.
WebViewClient : 웹 페이지를 로딩할 때 생기는 콜백 함수들로 구성되어 있습니다.
WebChromeClient : 웹 페이지에서 일어나는 액션들에 관한 콜백 함수들로 구성되어 있습니다.
class BaseWebViewClient(vm: WebViewModel) : WebViewClient() {
private var viewModel: WebViewModel? = null
init {
viewModel = vm
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
viewModel!!.showProgress()
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
viewModel!!.hideProgress()
}
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
return super.shouldOverrideUrlLoading(view, request)
}
// WebView Error Control
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
// custom exceptionThrow. Check ERROR_*
when (error?.errorCode) {
ERROR_BAD_URL -> { }
ERROR_CONNECT -> { }
ERROR_UNKNOWN -> { }
else -> { }
}
super.onReceivedError(view, request, error)
}
}
true를 리턴하는 상황인데 loadUrl과 같은 메서드가 포함되면, 불필요한 취소가 발생하게 됩니다.
class BaseWebChromeClient(vm: WebViewModel) : WebChromeClient() {
private var viewModel: WebViewModel? = null
init {
viewModel = vm
}
override fun onCreateWindow(
view: WebView?,
isDialog: Boolean,
isUserGesture: Boolean,
resultMsg: Message?
): Boolean {
return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg)
}
override fun onCloseWindow(window: WebView?) {
super.onCloseWindow(window)
}
// WebView Loading Check newProgress : 0 to 100
override fun onProgressChanged(view: WebView?, newProgress: Int) {
super.onProgressChanged(view, newProgress)
Log.d("newProgressCheck" , "now ? $newProgress")
viewModel!!.progressPercent(newProgress.toString())
}
override fun onJsAlert(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
return super.onJsAlert(view, url, message, result)
}
override fun onJsConfirm(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
return super.onJsConfirm(view, url, message, result)
}
}
addJavascriptInterface, evaluateJavascript 이런 키워드로 검색해보면 다음과 같은 이미지를 쉽게 찾아볼 수 있습니다.
방향성에 따라 통신의 방식이 조금 다릅니다.
웹 뷰 사용 시, 우리는 상상해 볼 수 있습니다.
다이나믹 링크로 웹 뷰를 띄우고 특정 링크로 이동하는 과정이 있습니다.
우리는 웹 뷰를 띄워줄 수 있지만, 특정 링크로 이동시키는 것은 웹 뷰의 역할 입니다.
즉, Android 환경에서 Javascript 함수를 호출할 수 있도록 하는 것이 evaluateJavascript()
입니다.
이 때 Javascript 함수는 웹 뷰 내부의 함수일 수도 있고, 새로운 스크립트 함수가 될 수도 있습니다.
> evaluateJavascript(String script, ValueCallback<String> resultCallback)
: Asynchronously evaluates JavaScript in the context of the currently displayed page.
webView.evaluateJavascript()는 Android WebView에서 Javascript 코드를 실행하기 위한 메서드 입니다.
다음 예시는 웹 뷰에 alert 창을 띄우는 예시입니다.
webViewer.evaluateJavascript("alert('Hello, World!');", null)
실행할 javascript 코드를 문자열로 넘겨줍니다. null이 위치한 두 번째 인자는 실행 결과를 받을 callback입니다.
다음은 특정 페이지로 넘어가기 위한 이벤트 예시입니다.
webView.evaluateJavascript(
JavascriptEvent<JavascriptEvent.MoveToPage>().dispatch(
"moveToPage", JavascriptEvent.MoveToPage(path)
)
) {}
class JavascriptEvent<T> {
fun dispatch(name: String, detail: T): String {
return "window.dispatchEvent(new CustomEvent(\"${name}\", {detail: ${Gson().toJson(detail)}}));"
}
}
evaluateJavascript()
메서드를 사용하여 JavaScript를 실행하고, 이 때 호출할 JavaScript 함수를 JavaScriptEvent
라는 클래스를 사용하여 구현했습니다.
여기서 JavaScriptEvent 안 dispatch()
함수는 자바스크립트 코드를 실행시킬 값을 함수로 구현한 것입니다.
정리하자면,
1. 실행할 코드를 준비하거나, 실행할 스크립트를 준비하거나, 필요한 이벤트를 준비합니다.
2. 이를 evaluateJavascript() 하여 연결해줍니다.
3. 웹 뷰에서 웹 개발자분들이 가져다가 사용하십니다.
끗
위에서는 웹 뷰에다가 Android가 요청하는 방식이었습니다.
하지만 되려 이런 경우도 있을 수 있습니다. 웹 뷰에서 현재 디바이스의 앨범에 접근해서 이미지를 가져오고 싶을 수 있습니다.
이때는 웹 뷰에서 Android의 코드를 실행해야 합니다. 이를 가능케 해주는 것이 addJavascriptInterface()
입니다.
class AlbumInit(
private val webViewe: WebView
) {
@JavascriptInterface
fun getImage(image: Bitmap) {
//앨범 접근, 이미지 가져오기 등등
}
}
webView.addJavascriptInterface(AlbumInit(webView),"approachAlbum")
// 웹 뷰에서 다음과 같이 가져올 수 있습니다.
approachAlbum.getImage(image)
reference :
https://developer.android.com/develop/ui/views/layout/webapps/webview#kotlin
https://heegs.tistory.com/103
https://kimdabang.tistory.com/entry
https://itssweetrain.tistory.com/5
https://gradler.tistory.com/32
https://velog.io/@jjing9/
https://kotlinworld.com/364