네이티브 앱 개발을 하면서도 웹뷰를 이용해야 하는 경우가 종종 생긴다. 다행히도 안드로이드에서는 코드 몇 줄만 추가해주면 간단하게 웹뷰를 띄워줄 수 있다.

얼마 전, 브라우저 내에서 실행되는 미디어를 백그라운드에서 재생하는 웹뷰를 만들어야 했는데, 겸사겸사 웹뷰의 세부적인 옵션에 대해서도 정리해봤다.


사전 준비

네트워크 권한 추가

새 프로젝트 만들 때마다 첫 빌드 하고 아차! 하는 작업이다. 매니페스트 AndroidManifest.xml 파일을 열어 manifest 태그 사이에 인터넷 허용을 위한 코드를 넣어준다.

<uses-permission android:name="android.permission.INTERNET"/>


화면 구성

웹뷰만 띄워줄 예정이므로 아무것도 없이 심플하게 웹뷰로만 구성했다.

<!-- activity_main.xml -->

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

    <WebView
        android:id="@+id/myWebView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>


또, 앱 기본 액션바를 없애 웹 사용에만 최적화되도록 만들었다.

스타일 테마를 NoActionBar로 교체해주었다.

<!-- styles.xml -->

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">


WebView 실행하기

정말정말 기능 없고 간단한 웹뷰만 띄우려면 코드 한 줄이면 된다.

/* MainActivity.kt */

myWebView.loadUrl("https://www.google.com")


WebSettings 설정

하지만 위의 예제처럼만 만들면 원하는 대로 화면이 만들어지지 않거나, 일부 웹 기능을 사용할 수 없을 것이다. WebSettings 클래스는 웹에서 사용 할만한 기능을 쉽게 On, Off 할 수 있도록 도와준다.

자바 스크립트 실행 여부, 새 창, 메타태그, 화면 줌, 캐싱 등의 설정을 할 수 있다.

myWebView.settings.apply {
    javaScriptEnabled = true // 자바스크립트 실행 허용
    javaScriptCanOpenWindowsAutomatically = false // 자바스크립트에서 새창 실 행 허용
    setSupportMultipleWindows(false) // 새 창 실행 허용
    loadWithOverviewMode = true // 메타 태그 허용

    useWideViewPort = true // 화면 사이즈 맞추기 허용
    setSupportZoom(false) // 화면 줌 허용
    builtInZoomControls = false // 화면 확대 축소 허용 여부

    cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK // 브라우저 캐시 허용 여부
    domStorageEnabled = true // 로컬저장소 허용
}


WebViewClient 로 현재 웹뷰에서 URL 이동하기

MainActivity 에서 성공적으로 구글 홈페이지를 불러왔다고 해도, 검색어를 입력한 후 검색을 누르면 외부 브라우저로 검색 결과 페이지가 열리며 앱이 백그라운드로 이동할 것이다.

현재 창에서 다른 url로 연결되게 하고 싶으면 WebViewClient를 하나 연결해준 후 url 로딩을 해당 웹뷰 안에서 실행해주어야 한다. 그러면 화면 전환 없이 내부에서 다음 url로 이동할 것이다.

정확히는 웹뷰 클라이언트의 shouldOverrideUrlLoading 함수를 오버라이드해 url을 현재 웹뷰에서 로드해주는 방식이다.

myWebView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
        view.loadUrl(url)
        return true
    }
}


이 외에도, WebViewClient에서 Override 할 수 있는 함수에는 여러 가지가 있다.

/* 페이지 로딩이 시작될 때 */
fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?)

/* 페이지 로딩이 끝났을 때 */
fun onPageFinished(view: WebView?, url: String?)

/* Request 재정의 - 헤더 추가 등의 요청을 커스텀할 수 있다 */
fun shouldInterceptRequest(
    view: WebView?,
    request: WebResourceRequest?
): WebResourceResponse?

/* 에러가 발생했을 때 - 에러 코드 별로 when 조건문을 사용할 수 있다 */
fun onReceivedError(
    view: WebView?,
    errorCode: Int,
    description: String?,
    failingUrl: String?
){
    super.onReceivedError(view, errorCode, description, failingUrl)
    when (errorCode) {
        ERROR_AUTHENTICATION -> {
        }
        ERROR_BAD_URL -> {
        }
        ERROR_CONNECT -> {
        }
        ERROR_FILE_NOT_FOUND -> {
        }
        ...
    }
}

/* 잘못된 키 입력이 있는 경우 */
fun shouldOverrideKeyEvent(view: WebView?, event: KeyEvent?): Boolean

...


Background 에서 WebView 사용

모바일 Youtube 페이지를 실행하는 웹뷰를 띄우는 코드를 적어 보았다.

class MainActivity : AppCompatActivity() {
    @SuppressLint("SetJavaScriptEnabled")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        myWebView.apply {
            settings.javaScriptEnabled = true
            webViewClient = object : WebViewClient() {
                override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
                    view.loadUrl(url)
                    return true
                }
            }
        }.run {
            loadUrl(URL_M_YOUTUBE)
        }
    }

    companion object {
        const val URL_M_YOUTUBE = "https://m.youtube.com/"
    }
}


자바 스크립트를 실행할 수 있게 하고, 다른 url로 이동할 때 내부 웹뷰에서 처리되도록 설정했다.

이 코드로 원하는 동영상을 재생한 후, 시간이 지나 디바이스의 화면이 꺼지면 영상과 소리도 재생이 중단된다. 웹뷰에서 화면(Window)의 visibility 상태 변화를 감지하기 때문이다.

사용자 커스텀 웹뷰를 만들어서, 화면의 visibility 를 View.VISIBLE로 세팅하면 앱이 백그라운드 태스크로 이동하거나 화면이 꺼져도 계속 미디어를 실행시키도록 할 수 있다.


CustomWebView 생성

기본 WebView 를 상속받는 웹뷰 클래스를 만든다. 이 예제에서는 BackgroundWebView 라는 이름의 웹뷰를 만들었다. 생성자를 각각 만들어주고, 무엇보다 중요한 onWindowVisibilityChanged 함수를 재정의한다.

class BackgroundWebView : WebView {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    override fun onWindowVisibilityChanged(visibility: Int) {
        if (visibility != View.GONE) super.onWindowVisibilityChanged(View.VISIBLE)
    }
}


그리고 레이아웃도 기존 WebView -> BackgroundWebView 로 변경해준다.

<!-- activity_main.xml -->
...
<io.yena.webviewexample.BackgroundWebView
        android:id="@+id/myWebView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />


새로운 웹뷰 클래스로 교체했으면 앱을 재빌드해 실행해보자. 화면을 꺼도 내가 재생한 유튜브 영상의 소리가 나오는 것을 들을 수 있다! 물론 유튜브 음악 재생만이 목적이라면 유튜브 프리미엄을 구독하면 되겠지만, 미디어는 다양한 곳에서 사용되므로 백그라운드에서 실행되는 방법을 알아 두어도 나쁘지 않아 보인다.



References

  • https://developer.android.com/reference/android/webkit/WebView
  • https://web-inf.tistory.com/34
  • https://stackoverflow.com/questions/52028940/how-can-i-make-webview-keep-a-video-or-audio-playing-in-the-background
  • https://cofs.tistory.com/186