Android问题篇之WebView问题(六)

893 阅读1分钟

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 {
    // 处理url
    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)

        // 防止crash,若未安装处理未知scheme开头的url(例微信weixin://、去哪儿qunaraphone://),会crash
        // 返回true,拦截自定义scheme,不跳转
        return true
    }

    // webView加载网页
    mBinding.webView.loadUrl(url)
    return true
}

1. AndroidRuntime: Caused by: java.lang.UnsupportedOperationException: For security reasons, WebView is not allowed in privileged processes

原因

  1. android:sharedUserId="android.uid.system"申请app为系统应用
  2. Android 8.0新增安全机制
  3. 使用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)  
    }  
}