Error inflating class android.webkit.WebView

最近项目中遇到一个关于WebView的问题,报错:Error inflating class android.webkit.WebView

特此记录一下解决方案(这里是针对如下配置问题的对应方案,不符合的可以参考其他同行的解决方案,我这边也是参考了别人的方案,该文章仅做记录自用)

当我们申请我们的app为系统应用,也就是当我们在AndroidManifest文件中添加

android:sharedUserId="android.uid.system"

这一行的时候,如果我们使用webView就会报错:For security reasons, WebView is not allowed in privileged processes。

这个是8.0代码中的一种安全机制

网上查找资料,解决方案都是通过Hook技术根本思想是在异常发生前就给sProviderInstance赋值,因为在getProvider方法中当判断这个值不为空就直接返回了就不会抛异常了

 static WebViewFactoryProvider getProvider() {
        synchronized (sProviderLock) {
            // For now the main purpose of this function (and the factory abstraction) is to keep
            // us honest and minimize usage of WebView internals when binding the proxy.
            if (sProviderInstance != null) return sProviderInstance;
 
            final int uid = android.os.Process.myUid();
            if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
                    || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
                    || uid == android.os.Process.BLUETOOTH_UID) {
                throw new UnsupportedOperationException(
                        "For security reasons, WebView is not allowed in privileged processes");
            }
 
            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
            try {
                Class providerClass = getProviderClass();
                Method staticFactory = null;
                try {
                    staticFactory = providerClass.getMethod(
                        CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class);
                } catch (Exception e) {
                    if (DEBUG) {
                        Log.w(LOGTAG, "error instantiating provider with static factory method", e);
                    }
                }
 
                Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactoryProvider invocation");
                try {
                    sProviderInstance = (WebViewFactoryProvider)
                            staticFactory.invoke(null, new WebViewDelegate());
                    if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
                    return sProviderInstance;
                } catch (Exception e) {
                    Log.e(LOGTAG, "error instantiating provider", e);
                    throw new AndroidRuntimeException(e);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                }
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                StrictMode.setThreadPolicy(oldPolicy);
            }
        }
    }

通过源码可以看到这一行代码if (sProviderInstance != null) return sProviderInstance; 紧接着就有抛异常的代码。

所以我们要让sProviderInstance不为空。

解决方案一:

public static void hookWebView() {
        int sdkInt = Build.VERSION.SDK_INT;
        try {
            Class factoryClass = Class.forName("android.webkit.WebViewFactory");
            Field field = factoryClass.getDeclaredField("sProviderInstance");
            field.setAccessible(true);
            Object sProviderInstance = field.get(null);
            if (sProviderInstance != null) {
                log.debug("sProviderInstance isn't null");
                return;
            }
            Method getProviderClassMethod;
            if (sdkInt > 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");
            } else if (sdkInt == 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");
            } else {
                log.info("Don't need to Hook WebView");
                return;
            }
            getProviderClassMethod.setAccessible(true);
            Class providerClass = (Class) getProviderClassMethod.invoke(factoryClass);
            Class delegateClass = Class.forName("android.webkit.WebViewDelegate");
            Constructor providerConstructor = providerClass.getConstructor(delegateClass);
            if (providerConstructor != null) {
                providerConstructor.setAccessible(true);
                Constructor declaredConstructor = delegateClass.getDeclaredConstructor();
                declaredConstructor.setAccessible(true);
                sProviderInstance = providerConstructor.newInstance(declaredConstructor.newInstance());
                log.debug("sProviderInstance:{}", sProviderInstance);
                field.set("sProviderInstance", sProviderInstance);
            }
            log.debug("Hook done!");
        } catch (Throwable e) {
            log.error(e);
        }

搜索了好多博客都是这个答案,一顿操作发现当执行到

Constructor providerConstructor = providerClass.getConstructor(delegateClass);

这一行代码的时候必然会报NoSuchMethod的错误,仔细看了一下frameWork代码,发现WebViewFactoryProvider这个类是个借口类,里面没有构造方法,自然会报这个错误。

方案2:

private static String TAG = "hookWebView";
    public static void hookWebView(){
        int sdkInt = Build.VERSION.SDK_INT;
        try {
            Class factoryClass = Class.forName("android.webkit.WebViewFactory");
            Field field = factoryClass.getDeclaredField("sProviderInstance");
            field.setAccessible(true);
            Object sProviderInstance = field.get(null);
            if (sProviderInstance != null) {
                Log.i(TAG,"sProviderInstance isn't null");
                return;
            }
 
            Method getProviderClassMethod;
            if (sdkInt > 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");
            } else if (sdkInt == 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");
            } else {
                Log.i(TAG,"Don't need to Hook WebView");
                return;
            }
            getProviderClassMethod.setAccessible(true);
            Class factoryProviderClass = (Class) getProviderClassMethod.invoke(factoryClass);
            Class delegateClass = Class.forName("android.webkit.WebViewDelegate");
            Constructor delegateConstructor = delegateClass.getDeclaredConstructor();
            delegateConstructor.setAccessible(true);
            if(sdkInt < 26){//低于Android O版本
                Constructor providerConstructor = factoryProviderClass.getConstructor(delegateClass);
                if (providerConstructor != null) {
                    providerConstructor.setAccessible(true);
                    sProviderInstance = providerConstructor.newInstance(delegateConstructor.newInstance());
                }
            } else {
                Field chromiumMethodName = factoryClass.getDeclaredField("CHROMIUM_WEBVIEW_FACTORY_METHOD");
                chromiumMethodName.setAccessible(true);
                String chromiumMethodNameStr = (String)chromiumMethodName.get(null);
                if (chromiumMethodNameStr == null) {
                    chromiumMethodNameStr = "create";
                }
                Method staticFactory = factoryProviderClass.getMethod(chromiumMethodNameStr, delegateClass);
                if (staticFactory!=null){
                    sProviderInstance = staticFactory.invoke(null, delegateConstructor.newInstance());
                }
            }
 
            if (sProviderInstance != null){
                field.set("sProviderInstance", sProviderInstance);
                Log.i(TAG,"Hook success!");
            } else {
                Log.i(TAG,"Hook failed!");
            }
        } catch (Throwable e) {
            Log.w(TAG,e);
        }
    }

这个方案完美的解决了这个问题。因为中间做了一次判断,判断是低于o版本还是高于o版本。

然后我只要在Activity的oncreate方法中调用就行了,切记一定要在setContentView之前调用,或者在WebView创建之前调用,不然还是会报错的

你可能感兴趣的:(android,android)