Android Webview err-unknown-url-scheme 에러 처리

부트페이 결제연동
7 min readOct 17, 2018

--

문제 현상

err-unknown-url-scheme 와 같은 에러가 날 수 있다. webview에 다음과 같은 메시지가 예로 찍힌다.

intent;package=kr.co.bootpay;launchFlags=0x15008000;end;의 웹페이지를 로드할 수 없습니다.

해당 로직은 Android Intent를 지원하지 않거나, 올바르게 수행되지 않아 발생하는 에러일 수 있다. 동일 시나리오를 Android Webview 가 아닌, 브라우저 환경에서는 정상 수행되는 모습을 볼 것이다.

문제 원인

Android 4.4부터 Webview는 Chromium 기반으로 변경되었는데, 이 스펙은 IntentURI를 지원하지 않는다. 따라서 해당 부분을 개발자가 커스터마이징 해야 한다.

문제 해결

Android Webview 클라이언트 구현으로 url을 핸들링해야 하며, 해당 소스로 외부 앱 실행, 앱 다운, 외부 앱 결제 등을 모두 확인하였다.

private class BWebviewClient extends WebViewClient { 

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Intent intent = parse(url);
if (isIntent(url)) {
if (isExistInfo(intent, view.getContext()) || isExistPackage(intent, view.getContext()))
return start(intent, view.getContext());
else
gotoMarket(intent, view.getContext());
} else if (isMarket(url)) {
return start(intent, view.getContext());
}
return url.contains("https://bootpaymark");
}

private Intent parse(String url) {
try {
return Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
} catch (URISyntaxException e) {
e.printStackTrace();
return null;
}
}

private Boolean isIntent(String url) {
return url.matches("^intent:?\\w*://\\S+$");
}

private Boolean isMarket(String url) {
return url.matches("^market://\\S+$");
}

private Boolean isExistInfo(Intent intent, Context context) {
try {
return intent != null && context.getPackageManager().getPackageInfo(intent.getPackage(), PackageManager.GET_ACTIVITIES) != null;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return false;
}
}

private Boolean isExistPackage(Intent intent, Context context) {
return intent != null && context.getPackageManager().getLaunchIntentForPackage(intent.getPackage()) != null;
}

private boolean start(Intent intent, Context context) {
context.startActivity(intent);
return true;
}

private boolean gotoMarket(Intent intent, Context context) {
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + intent.getPackage())));
return true;
}
}

Java로 구현했을 시 위와 같다. Kotlin 버전은 아래와 같다.

override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
val intent = parse(url)
return if (isIntent(url)) {
if (isExistInfo(intent) or isExistPackage(intent))
start(intent)
else
gotoMarket(intent)
} else if (isMarket(url))
start(intent)
else
url.contains("https://bootpaymark")
}
private fun isIntent(url: String?) = url?.matches(Regex("^intent:?\\w*://\\S+$")) ?: false

private fun isMarket(url: String?) = url?.matches(Regex("^market://\\S+$")) ?: false

private fun isExistInfo(intent: Intent?): Boolean {
return try {
intent != null && context.packageManager.getPackageInfo(intent.`package`, PackageManager.GET_ACTIVITIES) != null
} catch (e: PackageManager.NameNotFoundException) {
false
}

}

private fun isExistPackage(intent: Intent?): Boolean =
intent != null && context.packageManager.getLaunchIntentForPackage(intent.`package`) != null

private fun parse(url: String): Intent? {
return try {
Intent.parseUri(url, Intent.URI_INTENT_SCHEME)
} catch (e: URISyntaxException) {
null
}

}

private fun start(intent: Intent?): Boolean {
intent?.let { context.startActivity(it) }
return true
}
private fun gotoMarket(intent: Intent?): Boolean {
intent?.let { start(Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("market://details?id=${it.`package`}") }) }
return true
}

전체 소스는 이곳에서 볼 수 있다. Android에서 PG를 연동하는 경우라면 이곳에 매뉴얼 형태의 설명을 볼 수 있으며, 이미 만들어진 라이브러리를 사용하면 된다.

--

--