흑우마스터의 마법의 공간

.NET MAUI Android에서 웹뷰 구현 시 결제 페이지(토스 같은거) Intent(net::ERR_UNKNOWN_URL_SCHEME) 문제 해결 본문

프로그래밍/Xamarin

.NET MAUI Android에서 웹뷰 구현 시 결제 페이지(토스 같은거) Intent(net::ERR_UNKNOWN_URL_SCHEME) 문제 해결

흑우마스터 2023. 10. 31. 18:32

우선 Platform -> Android에 있는 AndroidManifest.xml 파일을 수정해야 한다. manifest 하위 노드에 다음 내용을 추가한다

<queries>
		<package android:name="com.kakao.talk" /> <!-- 카카오톡 -->
		<package android:name="com.nhn.android.search" /> <!-- 네이버페이 -->
		<package android:name="com.samsung.android.spay" /> <!-- 삼성페이 -->
		<package android:name="com.samsung.android.spaylite" /> <!-- 삼성페이 -->
		<package android:name="com.ssg.serviceapp.android.egiftcertificate" /> <!-- SSGPAY -->
		<package android:name="com.nhnent.payapp" /> <!-- PAYCO -->
		<package android:name="com.lottemembers.android" /> <!-- L.POINT -->
		<package android:name="viva.republica.toss" /> <!-- 토스-->
		<package android:name="com.shcard.smartpay" /> <!-- 신한페이판 -->
		<package android:name="com.shinhancard.smartshinhan" /> <!-- 신한페이판-공동인증서 -->
		<package android:name="com.hyundaicard.appcard" /> <!-- 현대카드 -->
		<package android:name="com.lumensoft.touchenappfree" /> <!-- 현대카드-공동인증서 -->
		<package android:name="kr.co.samsungcard.mpocket" /> <!-- 삼성카드 -->
		<package android:name="nh.smart.nhallonepay" /> <!-- 올원페이 -->
		<package android:name="com.kbcard.cxh.appcard" /> <!-- KB Pay -->
		<package android:name="com.kbstar.liivbank" /> <!-- Liiv(KB국민은행) -->
		<package android:name="com.kbstar.reboot" /> <!-- Liiv Reboot(KB국민은행) -->
		<package android:name="com.kbstar.kbbank" /> <!-- 스타뱅킹(KB국민은행) -->
		<package android:name="kvp.jjy.MispAndroid320" /> <!-- ISP/페이북 -->
		<package android:name="com.lcacApp" /> <!-- 롯데카드 -->
		<package android:name="com.hanaskcard.paycla" /> <!-- 하나카드 -->
		<package android:name="com.hanaskcard.rocomo.potal" />  <!--하나카드-->
		<package android:name="kr.co.hanamembers.hmscustomer" /> <!-- 하나멤버스 -->
		<package android:name="kr.co.citibank.citimobile" /> <!-- 씨티모바일 -->
		<package android:name="com.wooricard.wpay" /> <!-- 우리페이 -->
		<package android:name="com.wooricard.smartapp" /> <!-- 우리카드 -->
		<package android:name="com.wooribank.smart.npib" /> <!-- 우리WON뱅킹 -->
		<package android:name="com.lguplus.paynow" /> <!-- 페이나우 -->
		<package android:name="com.kftc.bankpay.android" /> <!-- 뱅크페이 -->
		<package android:name="com.TouchEn.mVaccine.webs" /> <!-- TouchEn mVaccine (신한) -->
		<package android:name="kr.co.shiftworks.vguardweb" /> <!-- V-Guard (삼성) -->
		<package android:name="com.ahnlab.v3mobileplus" /> <!-- V3 (NH, 현대) -->
	</queries>

 

그리고 웹뷰 컨트롤을 플랫폼별로 따로 처리해주어야 되는데 다음과 같이 작성하여 WebViewClient를 상속 받은 커스텀 객체의 인스턴스를 넣어주었다.

 

물론 플랫폼 별로 WebViewHandler를 구현해줘도 되지만 테스트해본 결과 이렇게 작성하면 실제로 동작하지 않은 이상한 버그가 있다. 아마 생성시점에 만들게 되면 적용을 해도 우리가 필요한 ShouldOverrideUrlLoading에 접근하지 못하게 되므로 다음과 같이 선언하는게 맘 편하다

 

#if ANDROID

            var androidWebview = this.webview.Handler.PlatformView as Android.Webkit.WebView;
            androidWebview.SetWebViewClient(new Controls.CustomWebViewClientSample();
#endif

 

CustomWebViewClientSample은 다음과 같다.  SetWebViewClient를 정상적으로 추가하게 되면 Maui Control에 있는 웹뷰 컨트롤은 더이상 Navigation, Navigated 이벤트를 제대로 타지 않는다.

 

그 이유는 ShouldOverrideUrlLoading와  OnPageStarted 그리고 OnPageFinished를 오버라이드 해보면 알 수 있는데 앞으로는 이 친구들이 그 역할을 하게 되기 때문에 현재 웹뷰의 현재 페이지에 해당하는 Source와 안드로이드 웹뷰의 Url가 제대로 반영 되지 않기 때문인데 OnPageStarted에서 주소를 캐치해서 Maui 컨트롤인 웹뷰에 주소를 바꿔치기 해줄 수 있겠지만 이러면 로딩만 두번 발생하게 된다.

 

이 문제를 해결하기 위해서는 WebViewClient 내에 네비게이션 스택을 별도로 만드는 것이 효율적이다. 하지만 이건 다음에 하도록 하겠다. 일단 네비게이션 이벤트를 받아와서 처리하는 과정이 없다면 그냥 아래 코드를 쓰면 된다.

 

이렇게만 작성해도 외부앱을 호출하는 인텐트에 대한 오류는 발생하지 않지만 웹뷰 특성상 이렇게 작성하게 되면 물리키 등을 포함해서 뒤로가기를 구현하게 되면 CanGoBack이나 GoBack 같은게 적용되지 않는다. 그 이유는 위에서 언급한 웹뷰 컨트롤의 Source가 네비게이션이 되더라도 스택 관리를 커스텀으로 만든 WebViewClient가 관리하기 때문에 안되는 것이다.

 

이 부분은 다음 시간에 다루겠다

public partial class CustomWebViewClientSample : WebViewClient
    {

        public CustomWebViewClientSample()
        {
         
        }

        public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, string url)
        {
            if (!URLUtil.IsNetworkUrl(url) && !URLUtil.IsJavaScriptUrl(url))
            {
                Android.Net.Uri uri;
                try
                {
                    uri = Android.Net.Uri.Parse(url);
                }
                catch (Exception)
                {
                    return false;
                }

                if ("intent".Equals(uri.Scheme, StringComparison.OrdinalIgnoreCase))
                {
                    return StartSchemeIntent(url);
                }
                else
                {
                    try
                    {
                        var intent = new Android.Content.Intent(Android.Content.Intent.ActionView, uri);
                        Platform.CurrentActivity.StartActivity(intent);
                        return true;
                    }
                    catch (Exception)
                    {
                        return false;
                    }
                }
            }

            return false;
        }

        private bool StartSchemeIntent(string url)
        {
            Android.Content.Intent schemeIntent;
            try
            {
                schemeIntent = Android.Content.Intent.ParseUri(url, Android.Content.IntentUriType.Scheme);
            }
            catch (Java.Net.URISyntaxException)
            {
                return false;
            }

            try
            {
                Platform.CurrentActivity.StartActivity(schemeIntent);
                return true;
            }
            catch (Android.Content.ActivityNotFoundException ex)
            {
                string packageName = schemeIntent.Package;
                if (!string.IsNullOrEmpty(packageName))
                {
                    var marketUri = Android.Net.Uri.Parse("market://details?id=" + packageName);
                    var marketIntent = new Android.Content.Intent(Android.Content.Intent.ActionView, marketUri);
                    Platform.CurrentActivity.StartActivity(marketIntent);
                    return true;
                }
            }
            catch (Exception ex)
            {
                return false;
            }

            return false;
        }
    }
}