最近项目中遇到一个关于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创建之前调用,不然还是会报错的。