WebView篇之问题记录
2. ERR_UNKNOWN_URL_SCHEME
原因
- WebView只能识别http、https协议,有些服务器会自定义协议(例微信weixin://),若跳转自定义协议WebView是无法识别的,就会出现ERR_UNKNOWN_URL_SCHEME
解决方案
- 给WebView设置WebViewClient,重写
shouldOverrideUrlLoading,该方法在加载WebView中链接时回调,实现拦截作用
- 返回true使用自身WebView加载,false调用系统或三方浏览器加载
override fun shouldOverrideUrlLoading(webview: WebView?, url: String?): Boolean {
LogTool.d("url: $url")
url?:return false
try {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
return true
}
} catch (e: Exception) {
LogTool.e(e)
return true
}
mBinding.webView.loadUrl(url)
return true
}
1. AndroidRuntime: Caused by: java.lang.UnsupportedOperationException: For security reasons, WebView is not allowed in privileged processes
原因
- android:sharedUserId="android.uid.system"申请app为系统应用
- Android 8.0新增安全机制
- 使用Hook技术对sProviderInstance赋值,就不会抛异常
(Hook需在WebView加载前设置)
# 源码
...
static WebViewFactoryProvider getProvider() {
synchronized (sProviderLock) {
if (sProviderInstance != null) return sProviderInstance;
final int uid = android.os.Process.myUid();
if (...) {
throw new UnsupportedOperationException("For security reasons, WebView is not allowed in privileged processes")
}
...
}
}
...
解决方案
fun hookWebView() {
val sdkInt = Build.VERSION.SDK_INT
try {
val factoryClass = Class.forName("android.webkit.WebViewFactory")
val field = factoryClass.getDeclaredField("sProviderInstance")
field.isAccessible = true
var sProviderInstance = field[null]
if (sProviderInstance != null) {
LogTool.i("sProviderInstance isn't null")
return
}
val getProviderClassMethod: Method =
if (sdkInt > 22) {
factoryClass.getDeclaredMethod("getProviderClass")
} else if (sdkInt == 22) {
factoryClass.getDeclaredMethod("getFactoryClass")
} else {
LogTool.i("Don't need to Hook WebView")
return
}
getProviderClassMethod.isAccessible = true
val factoryProviderClass = getProviderClassMethod.invoke(factoryClass) as Class<*>
val delegateClass = Class.forName("android.webkit.WebViewDelegate")
val delegateConstructor = delegateClass.getDeclaredConstructor()
delegateConstructor.isAccessible = true
if (sdkInt < 26) {
val providerConstructor = factoryProviderClass.getConstructor(delegateClass)
if (providerConstructor != null) {
providerConstructor.isAccessible = true
sProviderInstance = providerConstructor.newInstance(delegateConstructor.newInstance())
}
} else {
val chromiumMethodName = factoryClass.getDeclaredField("CHROMIUM_WEBVIEW_FACTORY_METHOD")
chromiumMethodName.isAccessible = true
var chromiumMethodNameStr = chromiumMethodName[null] as String
if (chromiumMethodNameStr == null) {
chromiumMethodNameStr = "create"
}
val staticFactory = factoryProviderClass.getMethod(chromiumMethodNameStr, delegateClass)
if (staticFactory != null) {
sProviderInstance = staticFactory.invoke(null, delegateConstructor.newInstance())
}
}
if (sProviderInstance != null) {
field["sProviderInstance"] = sProviderInstance
LogTool.i("Hook success!")
} else {
LogTool.i("Hook failed!")
}
} catch (e: Throwable) {
LogTool.w("throwable = " + e.message)
}
}