반응형

안드로이드 웹뷰에는 WebViewClient와 WebChromeClient가 있습니다. 처음에는 이 두 클라이언트 클래스가 헷갈리는데요. 하나하나 차근차근 정리하면서 각각의 역할을 구분한다면 그냥 웹뷰만 사용했을 때보다 훨씬 풍부한 활용이 가능하게 됩니다.

 

이번 포스팅에서는 먼저 WebViewClient 에 대해 간략하게 소개해드리도록 하고, 추후 활용하는 부분에 대해서도 계속해서 다뤄보도록 하겠습니다.

 

Methods

WebViewClient의 역할은 간단하게 말해서 notification 입니다. 우리는 WebViewClient를 통해 웹뷰에서 일어나는 요청, 상태, 에러 등 다양한 상황에서의 콜백을 조작할 수 있습니다. 다양한 메소드를 제공하고 있습니다만 대표적으로 사용되는 몇 가지 메소드만 살펴보도록 하겠습니다. 전체 메소드에 대해서는 developer 사이트에서 확인하실 수 있습니다.

 

  • onPageStarted(view: WebView?, url: String?, favicon: Bitmap?)
    - page loading을 시작했을 때 호출되는 콜백 메소드
  • onPageFinished(view: WebView?, url: String?)
    - page loading을 끝냈을 때 호출되는 콜백 메소드
  • onLoadResource(view: WebView?, url: String?)
    - 파라미터로 넘어온 url 에 의해 특정 리소스를 load 할 때 호출되는 콜백 메소드
  • onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?)
    - request 에 대해 에러가 발생했을 때 호출되는 콜백 메소드. error 변수에 에러에 대한 정보가 담겨져있음
  • shouldInterceptRequest(view: WebView?, request: WebResourceRequest?)
    - resource request를 가로채서 응답을 내리기 전에 호출되는 메소드
    - 이 메소드를 활용하여 특정 요청에 대한 필터링 및 응답 값 커스텀 가능
  • shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?)
    - 현재 웹뷰에 로드될 URL에 대한 컨트롤을 할 수 있는 메소드

 

이렇듯 WebViewClient를 사용하면 다양한 콜백 메소드를 활용할 수 있게 됩니다.

WebViewClient는 WebView의 setWebViewClient 메소드를 통해 등록해주면 됩니다.

 

예제

위에서 알아본 메소드들이 잘 호출되는지, 언제 호출되는지 알아보기 위해서 간단한 예제를 작성해보도록 하겠습니다.

WebViewClient를 상속하여 각 메소드들을 재정의하는 클래스를 하나 만들고, 이를 웹뷰에 등록한 다음 제 블로그 사이트를 로드할 때 각 메소드 호출부에서 기록되는 값들을 텍스트뷰에 기록해보도록 하겠습니다.

 

먼저 WebViewClient를 상속하는 클래스를 작성하겠습니다.

class CustomWebViewClient(val writeLog: (String) -> Unit) : WebViewClient() {
    override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
        writeLog("onPageStarted : ${url}\n")
        super.onPageStarted(view, url, favicon)
    }

    override fun onPageFinished(view: WebView?, url: String?) {
        writeLog("onPageFinished : ${url}\n")
        super.onPageFinished(view, url)
    }

    override fun onLoadResource(view: WebView?, url: String?) {
        writeLog("onLoadResource : ${url}\n")
        super.onLoadResource(view, url)
    }

    @TargetApi(Build.VERSION_CODES.M)
    override fun onReceivedError(
        view: WebView?,
        request: WebResourceRequest?,
        error: WebResourceError?
    ) {
        writeLog("onReceivedError : ${error?.description.toString()}\n")
        super.onReceivedError(view, request, error)
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    override fun shouldInterceptRequest(
        view: WebView?,
        request: WebResourceRequest?
    ): WebResourceResponse? {
        writeLog("shouldInterceptRequest : ${request?.url.toString()}\n")
        return super.shouldInterceptRequest(view, request)
    }

    @TargetApi(Build.VERSION_CODES.N)
    override fun shouldOverrideUrlLoading(
        view: WebView?,
        request: WebResourceRequest?
    ): Boolean {
        writeLog("shouldOverrideUrlLoading : ${request?.url.toString()}\n")
        return super.shouldOverrideUrlLoading(view, request)
    }
}

여기서 짚고 넘어갈 부분은 먼저 CustomWebViewClient 의 생성자에 정의된 writeLog 프로퍼티 입니다. 이는 코틀린의 특징 중 하나인 "kotlin functions are first-class citizen." 이라는 것을 이용한 것으로, 잠시 후 액티비티 코드에서 작성될 텍스트뷰에 로깅하는 함수를 받아오는데 사용됩니다.

 

그리고 아래 3개의 함수에서는 @RequiresApi 어노테이션을 볼 수 있는데요. 이는 어노테이션이 붙은 각 함수들이 일정 버전 이상에서 지원하는 버전이기 때문에 요구되는 API 버전을 명시해준 것입니다. 안드로이드는 파편화 현상이 심해서 사용하는 클래스나 메소드가 내 프로젝트의 버전에 적용 가능한지 항상 잘 확인해줘야 합니다.

 

다음으로 레이아웃을 작성해줍니다.

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

    <WebView
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/log_text"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <TextView
        android:id="@+id/log_text"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="#ccc"
        android:scrollbars="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

많은 로그가 기록될 것이기 때문에 TextView에 스크롤바를 설정해주었습니다.

 

마지막으로 액티비티 클래스를 작성해줍니다.

import android.os.Bundle
import android.os.Handler
import android.text.method.ScrollingMovementMethod
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private val handler = Handler()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        web_view.run {
            webViewClient = CustomWebViewClient(::writeLog)
            loadUrl("https://readykim.com/")
        }
        log_text.movementMethod = ScrollingMovementMethod()
    }

    private fun writeLog(log: String) {
        handler.post {
            log_text.append(log)
        }
    }
}

 

위 코드에서 살펴볼 건 CustomWebViewClient의 생성자에 앞서 말했던 일급 객체로써 writeLog 라는 함수를 넣어줬다는 것과 writeLog 함수 내에 Handler를 두어 UI Thread에서 로깅할 수 있도록 처리한 부분입니다. 그리고 TextView에 스크롤을 하기 위해 movementMethod를 설정해주었습니다.

 

결과 화면은 아래와 같습니다.

결과 화면

아래 로그가 찍힌 것을 보시면 액티비티에서 호출해준 loadUrl() 메소드에서 설정한 제 블로그 URL 이 나와있고, 이어서 제 블로그 HTML 코드에서 호출하는 각종 css 파일과 js 파일들에 대한 Request들도 잘 찍히고 있습니다. 테스트 해보실 때 존재하지 않는 URL을 호출해보시면 onReceivedError() 메소드도 잘 호출되는 것을 확인하실 수 있습니다.

 

위 예제에 작성된 전체 코드는 Github 저장소에서 확인할 수 있습니다.

 

 

반응형
반응형