관리 메뉴

나만을 위한 블로그

[Android] shouldOverrideUrlLoading()이란? 본문

Android

[Android] shouldOverrideUrlLoading()이란?

참깨빵위에참깨빵 2023. 11. 21. 21:47
728x90
반응형

안드로이드에서 웹뷰를 다루려면 피할 수 없는 함수 중 하나가 shouldOverrideUrlLoading()이다. 이 함수는 WebViewClient 구현 시 재정의할 수 있는 함수인데, URL과 딥링크를 앱에서 다루기 위해 사용할 수 있는 함수다.

아래는 디벨로퍼에서 설명하는 shouldOverrideUrlLoading()이다.

 

https://developer.android.com/reference/android/webkit/WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView,%20android.webkit.WebResourceRequest)

 

WebViewClient  |  Android Developers

 

developer.android.com

현재 웹뷰에 URL이 로드되려고 할 때 호스트 앱에 제어권을 부여할 수 있다. WebViewClient가 제공되지 않으면 기본적으로 웹뷰는 액티비티 매니저에게 URL에 대한 적절한 핸들러를 선택하도록 요청한다. WebViewClient가 제공되는 경우, true를 리턴하면 현재 웹뷰가 URL 로드를 중단한다. false를 리턴하면 웹뷰가 평소처럼 URL을 계속 로드한다
request URL로 WebView.loadUrl(String)을 호출한 다음 true를 리턴하지 마라. 이로 인해 현재 로드가 불필요하게 취소되고 동일한 URL을 사용해서 로드가 새로 시작된다. 지정된 URL을 계속 로드하는 올바른 방법은 WebView.loadUrl(String)을 호출하지 않고 단순히 false를 리턴하는 것이다. 이 메서드는 POST 요청에 대해 호출되지 않으며, 서브프레임 및 HTTP(S)가 아닌 메서드에 의해 호출될 수 있다. 이런 URL을 써서 WebView.loadUrl(String)을 호출하면 실패한다
public boolean shouldOverrideUrlLoading (WebView view, WebResourceRequest request)

 

이 말만 놓고 보면 웹뷰가 URL을 계속 불러오게 할지 말지를 결정하는 메서드라고 생각된다.

이후에 POST 요청을 받을 경우 호출되지 않는다는 말이 있는데, GET과 POST 요청은 일반적으로 사용되는 HTTP 요청 메서드다. 예를 들어 POST 방식으로 아래 케이스가 발생할 경우, shouldOverrideUrlLoading()은 작동하지 않는다.

 

  • 로그인 form 입력 후 제출이나 파일 업로드 같은 POST 요청으로 인한 URL 로딩 발생 시, shouldOverrideUrlLoading()은 이걸 캐치할 수 없다
  • POST 요청에 대한 커스텀 URL 처리 로직을 shouldOverrideUrlLoading() 안에 구현한 경우, 이 로직은 POST 요청을 받을 경우 실행되지 않는다

 

이 경우, 대안으로 @JavaScriptInterface 어노테이션을 사용하는 브릿지 방식을 사용하거나, 액티비티나 프래그먼트에 POST 요청을 받았을 경우 수행할 함수를 만들어 둔 뒤 WebViewClient의 생성자로 액티비티 / 프래그먼트 참조변수를 넘긴 다음, shouldOverrideUrlLoading() 안에서 이 참조변수를 통해 해당 함수를 호출하면 된다.

그러나 오직 shouldOverrideUrlLoading() 안에서만 POST 요청을 컨트롤하겠다는 건 불가능하다는 게 공식문서의 요점이다. 때문에 안드로이드에서 웹뷰 작업을 진행할 때 이 점을 참고해야 한다.

 

또한 shouldOverrideUrlLoading()의 구현은 현재 2종류가 있다.

 

public boolean shouldOverrideUrlLoading (WebView view, WebResourceRequest request)
public boolean shouldOverrideUrlLoading (WebView view, String url) // Deprecated in API level 24

 

아래의 String url을 매개변수로 받는 함수가 API 24(누가)부터 deprecated된 함수다. 파편화가 심한 안드로이드의 특성 상 호환성을 위해 2가지 모두 구현한 다음, @TargetApi나 @Deprecated 어노테이션을 써서 호환성을 보장하는 처리를 해주면 좋다.

 

본론으로 돌아와서 다른 곳에선 shouldOverrideUrlLoading()을 어떻게 설명하는지 확인한다.

 

https://stackoverflow.com/a/29839731

 

Difference between shouldoverrideurlloading and shouldinterceptrequest?

Anyone please tell me the difference between methods public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) and public boolean shouldOverrideUrlLoading(WebView...

stackoverflow.com

shouldOverrideUrlLoading()은 새 페이지가 열릴 때 호출된다...(중략)...유저가 웹뷰 안에서 대화형으로 리소스를 요청하는 경우 WebViewClient.shouldOverrideUrlLoading()을 통해 요청을 가로챌 수 있다
 private class MyWebViewClient extends WebViewClient {
     @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (Uri.parse(url).getHost().equals("www.google.com")) {
            return true;
        }
        return false;
    }
 }
이 메서드는 현재 웹뷰에 새 URL이 로드되려고 할 때 호스트 앱에 제어권을 넘겨받을 수 있게 한다. 리턴값이 true면 호스트 앱이 URL을 처리한다는 뜻이고, false를 리턴하면 현재 웹뷰가 URL을 처리한다는 의미다. 위 코드는 "www.google.com"에서 리소스가 로드되는 걸 방지한다. 그러나 이 메서드는 HTML 또는 <script> 태그 안의 IFrame 또는 src 속성 같이 내부에서 로드되는 리소스를 차단하지 않는다. XmlHttpRequests도 가로채지 않는다...(중략)

 

https://developer.android.com/develop/ui/views/layout/webapps/webview?hl=ko#HandlingNavigation

 

WebView에서 웹 앱 빌드  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English WebView에서 웹 앱 빌드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. WebView를 사용하여

developer.android.com

(중략)...클릭된 링크가 로드되는 위치를 더 세부적으로 관리하려면 shouldOverrideUrlLoading()을 재정의하는 자체 WebViewClient를 만들어라. 아래 예시에선 MyWebViewClient가 액티비티의 내부 클래스라고 가정한다
private class MyWebViewClient : WebViewClient() {

    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        if (Uri.parse(url).host == "www.example.com") {
            // 이것은 당신의 웹사이트기 때문에 재정의하지 마라. 웹뷰가 페이지를 로드하게 하라
            return false
        }
        // 그렇지 않으면 링크가 사이트의 페이지에 대한 것이 아니므로, URL을 처리하는 다른 액티비티를 시작하라
        Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
            startActivity(this)
        }
        return true
    }
}
이후 웹뷰에 새 WebViewClient의 인스턴스를 만든다
val myWebView: WebView = findViewById(R.id.webview)
myWebView.webViewClient = MyWebViewClient()
이제 유저가 링크를 탭하면 시스템에서 shouldOverrideUrlLoading()을 호출해서 앞의 예시에 정의된 대로 URL 호스트가 특정 도메인과 일치하는지 확인한다. 일치하면 메서드가 false를 리턴하고 URL 로드를 재정의하지 않는다. 이렇게 하면 웹뷰가 평소와 같이 URL을 로드할 수 있다. URL 호스트가 불일치한다면 URL을 처리하기 위한 기본 액티비티를 실행하기 위해 인텐트가 생성돼 유저의 기본 웹 브라우저로 확인된다

< 커스텀 URL 처리 >

웹뷰는 커스텀 URL 스키마를 쓰는 리소스를 요청하고 링크를 확인할 때 제한사항을 적용한다. 예를 들어 shouldOverrideUrlLoading() 또는 shouldInterceptRequest() 같은 콜백을 구현하면, 웹뷰는 유효한 URL에 대해서만 이런 콜백을 호출한다. 예를 들어 웹뷰는 아래와 같은 링크의 shouldOverrideUrlLoading()를 호출하지 않을 수 있다
<a href="showProfile">Show Profile</a>
잘못된 URL은 웹뷰에서 일관되지 않게 처리되므로 올바른 형식의 URL을 쓰는 게 좋다. 조직에서 제어하는 도메인에 커스텀 스키마 또는 HTTPS URL을 쓸 수 있다. 아래와 같은 커스텀 스키마를 쓸 경우
<a href="example-app:showProfile">Show Profile</a>
아래처럼 shouldOverrideUrlLoading()에서 이 URL을 처리할 수 있다
// URL 구성표는 비계층적이어야 한다. 즉, 뒤에 슬래시가 없어야 한다
const val APP_SCHEME = "example-app:"

override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
    return if (url?.startsWith(APP_SCHEME) == true) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length), "UTF-8")
        respondToData(urlData)
        true
    } else {
        false
    }
}
shouldOverrideUrlLoading() API는 기본적으로 특정 URL의 인텐트를 실행하는 데 쓰인다. API 구현 시에는 웹뷰가 처리하는 모든 URL에 false를 리턴해야 한다. 하지만 인텐트를 실행하는 것만으로 제한되지는 않는다. 앞의 코드 샘플에서 시작 인텐트를 모든 커스텀 동작으로 바꿀 수 있다
shouldOverrideUrlLoading() 안에서 loadUrl(), reload() 또는 이와 유사한 메서드를 호출하지 마라. 이로 인해 앱의 효율성이 떨어진다. false를 리턴해서 웹뷰가 기본 구현으로 URL을 계속 로드하게 하는 게 더 효율적이다...(중략)

 

추가로 웹뷰에서 결제 기능을 구현해야 하는 경우도 있다. 토스 페이먼츠 개발자센터에서 이 경우 어떻게 해야 하는지 자세하게 적어놨다.

 

https://docs.tosspayments.com/guides/webview

 

웹뷰(WebView) 연동하기 | 토스페이먼츠 개발자센터

브라우저가 아닌 모바일 웹뷰로 결제창을 띄울 때 카드사별 결제수단을 인증하려면 외부 앱(3rd-party 앱)을 연동해야 합니다. 연동에 필요한 외부 앱 스킴(App URL Scheme)목록과 추가 로직을 살펴보

docs.tosspayments.com

 

물론 shouldOverrideUrlLoading()을 어떻게 작성해야 하는지도 써 있다.

 

override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean
  url?.let {
    if (!URLUtil.isNetworkUrl(url) && !URLUtil.isJavaScriptUrl(url)) {
      val uri = try {
        Uri.parse(url)
      } catch (e: Exception) {
        return false
      }

      return when (uri.scheme) {
        "intent" -> {
          startSchemeIntent(it)
        }
        else -> {
          return try {
            startActivity(Intent(Intent.ACTION_VIEW, uri))
            true
          } catch (e: java.lang.Exception) {
            false
          }
        }
      }
    } else {
      return false
    }
  } ?: return false

private fun startSchemeIntent(url: String): Boolean {
  val schemeIntent: Intent = try {
    Intent.parseUri(url, Intent.URI_INTENT_SCHEME)
  } catch (e: URISyntaxException) {
    return false
  }
  try {
    startActivity(schemeIntent)
    return true
  } catch (e: ActivityNotFoundException) {
    val packageName = schemeIntent.getPackage()

    if (!packageName.isNullOrBlank()) {
      startActivity(
        Intent(
          Intent.ACTION_VIEW,
          Uri.parse("market://details?id=$packageName")
        )
      )
      return true
    }
  }
  return false
}

 

shouldOverrideUrlLoading()에 대해 정리하면 아래와 같다.

 

  • WebViewClient 클래스 구현 시 재정의할 수 있는 메서드로, 웹뷰에 불러오려는 URL에 대한 제어권(그대로 웹뷰에 로드할지, 새 브라우저를 띄워 표시할지 등)을 앱이 가져올 수 있는 기회를 제공하는 메서드다
  • POST 방식의 요청은 shouldOverrideUrlLoading() 안에서 처리될 수 없다
  • 딥링크를 포함한 어떤 URL 패턴을 처리하거나, URL을 처리하기 위한 다른 메서드를 지정할 수 있다
  • API 24를 기준으로 deprecated된 구현이 존재하며, 앱 호환성을 위해 2가지 방식 모두 구현하고 어노테이션을 적어두는 게 호환성 보장 면에서 좋다
반응형
Comments