-
Android) WebView 정리Android 2021. 7. 8. 12:19
WebView
- 웹 탐색과 웹 브라우저는 Android, iOS, PC 모두 가진 기능입니다.
- WebView는 웹 브라우저를 구성하는 HTML과 같은 요소들을 받아들여 이를 브라우저와 동일한 형식으로 해석해서 표현해주는 뷰입니다.
- 그래서 WebView는 PC의 서버에서 response 한 웹 파일을 받아서 Android에서도 똑같이 보여주고 다룰 수 있고, 디바이스 상관없이 정보 공유가 가능한 하이브리드 앱을 쉽게 구현할 수 있도록 도와줍니다.
URL 웹 페이지 요청
- 단순히 WebView를 참조하고 loadUrl로 웹 브라우저를 실행하여 해당 url을 로드하는 방법입니다.
- loadUrl 메소드를 사용해서 해당 주소에 요청을 보내고, 응답받은 html 파일을 사용하여 웹 화면을 표시합니다.
- WebView에 WebViewClient를 제공함으로써 웹 페이지를 WebView에 띄울 수 있게 됩니다.
binding.webView.apply { webViewClient = WebViewClient() loadUrl("https://www.google.com/") }
내부 html 요청
- 개발자가 가진 html 파일을 표시하려면, 이 파일이 assets 폴더 안에 존재해야 합니다.
- assets은 res폴더 처럼 리소스를 모아두는 곳인데, res에서 분류한 타입 외의 html, css, js 등을 사용할 때에 사용하는 폴더입니다.
- 아래와 같이 assets 폴더 안에 html 파일을 넣고 loadUrl 메소드를 사용하여 불러옵니다.
webView.loadUrl("file:///android_asset/www/index.html")
html을 parsing하여 보여주기
- 일반적으로 사용하는 loadUrl() 메소드로 html을 parsing 하여 필요한 부분만 보여줄 수 없습니다.
- 이러한 경우 loadData 메소드나 loadDataWithBaseURL 메소드를 이용하면 됩니다.
- loadData(String data, String mimeType, String encoding) -> data : encoding으로 작성된 data는 html 코드를 string 형태로 넣어서 사용할 수 있습니다. mimeType : 문서의 다양성을 알려주기 위함으로 "text/html" 등의 값을 가질 수 있습니다. encoding : data가 어떤 형태의 encoding으로 작성되었는지 알려줍니다.
- 하지만, loadData 메소드는 network 상의 content를 WebView에 보여줄 수 없습니다. 즉 html 내용 중 상대 경로로 있는 스타일이나 이미지 등을 못 가져오는 경우가 있습니다.
- 예를 들어 <link="stylesheet" href="/Content/Css/main.css?2016011909href="/Content/Css/main.css? 2016011909" /> 이런 css가 있다고 하면 /Content 폴더가 어디 경로를 기점인지 알 수가 없어 loadDataWithBaseUrl을 사용하여 해결합니다.
- loadDataWithURL(String baseUrl, String data, String mimeType, String encoding, String histroyUrl) -> baseUrl : 상대 경로를 해결하기 위해 사용됨. historyUrl : histroy entry로 사용됨.
loadData(url, "text/html; charset=utf-8", "UTF-8") loadDataWithBaseURL(null, url, "text/html; charset=utf-8", "UTF-8", null)
java script 연동 (앱 -> 웹뷰 JavaScript 호출)
- 웹 브라우저는 java script를 사용합니다.
- 단순히 받아온 코드를 해석하는 것은 문제가 없을지 몰라도, 화면에 표시되는 웹 페이지와 안드로이드 코드가 유기적으로 동작하기 어려울 수 있습니다.
- 하지만, 이를 지원해주는 api가 존재합니다.
- loadUrl 메소드에 java script의 함수명을 적어주어, 정의된 함수를 실행시킬 수 있습니다.
- 앱의 동작에 연동하여 java script의 어떠한 동작을 하도록 이벤트를 걸 때 사용하면 좋습니다.
webView.loadUrl("javascript:alerthello()")
WebView와 앱 통신 (웹뷰 -> 앱 코드 호출)
- WebView는 보안상의 이유로 디바이스 자원을 100% 사용할 수 없습니다. 보통 안드로이드는 디바이스 유일 값으로 Android Id를 사용하는데 디바이스 영역으로 웹뷰에서 바로 접근하여 추출할 수 없습니다.
- 하지만, 네이티브에서는 해당 값을 자유롭게 추출 가능합니다. 이와 같은 경우 웹뷰와 네이티브의 연동을 통해 값을 전달합니다.
- 본 연동방식은 안드로이드 OS 4.1 JELLY_BEAN 이하 디바이스에서는 보안상 문제가 있어, 안드로이드 OS 4.2 이상에서 사용을 권장하고 있습니다. 안드로이드 OS 4.1 이하에서는 @JavaScriptinterface 어노테이션이 동작하지 않습니다.
- JavaScript에서 네이티브 코드 호출 -> 네이티브 코드가 JavaScriptInterface 어노테이션이 지정되었는지 확인하고 지정되어 있으면 네이티브 메소드를 실행 -> 네이티브 메소드의 실행결과를 반환.
- evaluteJavascript를 통해 JavaScript 호출 -> 실행결과를 반환.
웹뷰에서 앱 코드를 호출하는 예제를 보겠습니다.
- assets 폴더에 html 파일을 만들어줍니다. 안드로이드에서 선언한 함수를 호출할 수 있습니다.
<input type="button" value="WebView Test" onClick="showAndroidToast('Connection Success!')"/> <script type="text/javascript"> function showAndroidToast(toast) { Android.showToast(toast); } </script>
- @JavascriptInterface 함수 생성.
webview.settings.javaScriptEnabled = true webview.addJavascriptInterface(WebAppInterface(this), "Android") webview.loadUrl("file:///android_asset/www/index.html") /** Instantiate the interface and set the context */ class WebAppInterface(private val mContext: Context) { /** Show a toast from the web page */ @JavascriptInterface fun showToast(toast: String) = Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show() }
Full Screen Issue
- HTML video 태그 혹은 비디오 전체 화면 버튼 자체가 보이지 않을 때, 또한 전체화면 모드에서 가로모드로 플레이되지 않을 때.
더보기binding.webView.settings.apply {
javaScriptEnabled = true
setAppCacheEnabled(true)
pluginState = WebSettings.PluginState.ON
}
binding.webView.webChromeClient = object : WebChromeClient() {
override fun onShowCustomView(view: View?, callback: CustomViewCallback?) {
super.onShowCustomView(
view,
callback
)
binding.webView.isVisible = false
}
override fun onHideCustomView() {
super.onHideCustomView()
binding.webView.isVisible = true
}
}
binding.webView.webChromeClient = FullscreenableChromeClient(this)
val html = getHTML(videoId)
binding.webView.loadData(html, "text/html", "UTF-8")
}
fun getHTML(videoId: String): String {
return ("<iframe class=\"youtube-player\" style=\"border: 0; width: 100%; height: 100%;padding:0px; margin:0px\" id=\"ytplayer\" type=\"text/html\" src=\"https://www.youtube.com/embed/$videoId?theme=dark&autohide=2&modestbranding=1&showinfo=0&autoplay=1&rel=0&enablejsapi=1\" frameborder=\"0\" allowfullscreen autobuffer controls onclick=\"this.play()\">\n</iframe>\n")
}import android.app.Activity
import android.content.Context
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
import android.os.Build
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.webkit.WebChromeClient
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
class FullscreenableChromeClient(activity: Activity) : WebChromeClient() {
private var mActivity: Activity? = null
private var mCustomView: View? = null
private var mCustomViewCallback: WebChromeClient.CustomViewCallback? = null
private var mOriginalOrientation: Int = 0
private var mFullscreenContainer: FrameLayout? = null
companion object {
private val COVER_SCREEN_PARAMS = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
init {
this.mActivity = activity
}
override fun onShowCustomView(
view: View,
callback: WebChromeClient.CustomViewCallback
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
if (mCustomView != null) {
callback.onCustomViewHidden()
return
}
this.mActivity?.requestedOrientation =
SCREEN_ORIENTATION_LANDSCAPE
mOriginalOrientation = mActivity!!.requestedOrientation
val decor =
mActivity?.window?.decorView as FrameLayout
mFullscreenContainer =
FullscreenHolder(mActivity!!)
mFullscreenContainer!!.addView(
view,
COVER_SCREEN_PARAMS
)
decor.addView(
mFullscreenContainer,
COVER_SCREEN_PARAMS
)
mCustomView = view
setFullscreen(true)
mCustomViewCallback = callback
}
super.onShowCustomView(view, callback)
}
override fun onShowCustomView(
view: View,
requestedOrientation: Int,
callback: WebChromeClient.CustomViewCallback
) {
this.onShowCustomView(view, callback)
}
override fun onHideCustomView() {
if (mCustomView == null) {
return
}
setFullscreen(false)
val decor =
mActivity!!.window.decorView as FrameLayout
decor.removeView(mFullscreenContainer)
mFullscreenContainer = null
mCustomView = null
mCustomViewCallback!!.onCustomViewHidden()
mActivity?.requestedOrientation = SCREEN_ORIENTATION_PORTRAIT
}
private fun setFullscreen(enabled: Boolean) {
val win = mActivity!!.window
val winParams = win.attributes
val bits =
WindowManager.LayoutParams.FLAG_FULLSCREEN
if (enabled) {
winParams.flags = winParams.flags or bits
} else {
winParams.flags =
winParams.flags and bits.inv()
if (mCustomView != null) {
mCustomView!!.systemUiVisibility =
View.SYSTEM_UI_FLAG_VISIBLE
}
}
win.attributes = winParams
}
private class FullscreenHolder(ctx: Context) :
FrameLayout(ctx) { init {
setBackgroundColor(
ContextCompat.getColor(
ctx,
android.R.color.black
)
)
}
override fun onTouchEvent(evt: MotionEvent): Boolean {
return true
}
}
}WebView Settings
binding.webView.settings.apply { setSupportMultipleWindows(false) // 새창 띄우기 허용 setSupportZoom(false) // 화면 확대 허용 javaScriptEnabled = true // 자바스크립트 허용 javaScriptCanOpenWindowsAutomatically = false // 자바스크립트 새창 띄우기 허용 loadWithOverviewMode = true // html의 컨텐츠가 웹뷰보다 클 경우 스크린 크기에 맞게 조정 useWideViewPort = true // html의 viewport 메타 태그 지원 builtInZoomControls = false // 기본적으로 줌 기능이 되지 않습니다. displayZoomControls = false // 화면 확대/축소 허용 layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN // 컨텐츠 사이즈 맞추기 cacheMode = WebSettings.LOAD_NO_CACHE // 브라우저 캐쉬 허용 domStorageEnabled = true // 로컬 저장 허용 databaseEnabled = true /** * * This request has been blocked; the content must be served over HTTPS * * https 에서 이미지가 표시 안되는 오류를 해결하기 위한 처리 * */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW } } binding.webView.apply { webViewClient = WebViewClient() // WebView 위젯을 사용해 여러 기능 사용 webChromeClient = WebChromeClient() // 브라우저 크롬에 특화 }
반응형'Android' 카테고리의 다른 글
Android) KAPT를 대체할 KSP(Kotlin Symbol Process) 소개 with Kotlin DSL (0) 2021.07.17 Android) 이미지 로딩 라이브러리 자세히 알아보자 (0) 2021.07.11 Android) Coroutine Exception Handling 어떻게 처리 할까 (0) 2021.06.27 Android) ImageURL -> Bitmap 으로 변경하기 feat) HttpURLConnection, Coroutines (1) 2021.06.24 Android) Navigation Component IllegalStateException, IllegalArgumentException 예외 (0) 2021.06.10