Android WebView 运行在系统进程引发的异常
2017-10-14 22:07
351 查看
一、问题描述
由于需要用到系统的某些权限,在manifest中所以配置了android:sharedUserId="android.uid.system", 让应用共享系统进程,项目中用到了webview,由于是自定义,但是使用的时候报错,(去掉android:sharedUserId="android.uid.system"可以正常运行)crash日志如下(这是其中最关键的信息,其他的日志信息干扰性太大了)
java.lang.UnsupportedOperationException: For security reasons, WebView is not allowed in privileged processes at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:96) at android.webkit.WebView.getFactory(WebView.java:2194) at android.webkit.WebView.ensureProviderCreated(WebView.java:2189) at android.webkit.WebView.setOverScrollMode(WebView.java:2248) at android.view.View.<init>(View.java:3588) at android.view.View.<init>(View.java:3682) at android.view.ViewGroup.<init>(ViewGroup.java:497) at android.widget.AbsoluteLayout.<init>(AbsoluteLayout.java:55) at android.webkit.WebView.<init>(WebView.java:544) at android.webkit.WebView.<init>(WebView.java:489) at android.webkit.WebView.<init>(WebView.java:472) at android.webkit.WebView.<init>(WebView.java:459) at android.webkit.WebView.<init>(WebView.java:449)
日志信息内容大致说为了安全性考虑,不允许在享有特权的进程也就是系统进程里面使用 WebView,异常是在 WebView 初始化的时候抛出的,想要解决这个问题还要看源码(Read the fucking source code)。
二、问题根源分析
这是 Android 5.1(API 22)(25的源码基本一致) 里面的类 WebViewFactory 的 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) { throw new UnsupportedOperationException( "For security reasons, WebView is not allowed in privileged processes"); } Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()"); try { Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()"); loadNativeLibrary(); Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); Class<WebViewFactoryProvider> providerClass; Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getFactoryClass()"); try { providerClass = getFactoryClass(); } catch (ClassNotFoundException e) { Log.e(LOGTAG, "error loading provider", e); throw new AndroidRuntimeException(e); } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); } StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()"); try { try { sProviderInstance = providerClass.getConstructor(WebViewDelegate.class) .newInstance(new WebViewDelegate()); } catch (Exception e) { sProviderInstance = providerClass.newInstance(); } 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); StrictMode.setThreadPolicy(oldPolicy); } } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); } } }
可以看出,首次使用时,系统会进行检查,如果 UID 是 root 进程或者系统进程,直接抛出异常。sProviderInstance 是 WebViewFactoryProvider 的对象,主要提供创建 WebView 内核的机制。WebView在 Android 4.4 之前使用的是 Webkit 内核,在 Android 4.4 以后切换到了 Chromium 内核。Google 使用了工厂方法模式,优雅地切换 WebView 内核的实现方式。我们注意到只有
sProviderInstance 为空的时候系统才去检查进程,然后创建 sProviderInstance对象。所以这给了我们一个启发 ---- 能不能一开始就主动创建 sProviderInstance 对象,把她塞到 WebViewFactory 类里面,从而欺骗 API 绕过系统检查呢?
三、解决方案
下面就要用到 Hook 的思想了,首先要找到一个合适的点,静态变量、单例是最佳选择,刚刚好 sProviderInstance 是静态的。那就开始拿它开刀,看看系统是怎么创建 sProviderInstance 的,我们自己也模仿它这么做。其实系统也是通过反射来做的,这是 getFactoryClass 的源码,我们来看看。private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
Application initialApplication = AppGlobals.getInitialApplication();
try {
// First fetch the package info so we can log the webview package version.
String packageName = getWebViewPackageName();
sPackageInfo = initialApplication.getPackageManager().getPackageInfo(packageName, 0);
Log.i(LOGTAG, "Loading " + packageName + " version " + sPackageInfo.versionName +
" (code " + sPackageInfo.versionCode + ")");
// Construct a package context to load the Java code into the current app.
Context webViewContext = initialApplication.createPackageContext(packageName,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
initialApplication.getAssets().addAssetPath(
webViewContext.getApplicationInfo().sourceDir);
ClassLoader clazzLoader = webViewContext.getClassLoader();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
try {
return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
clazzLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (PackageManager.NameNotFoundException e) {
// If the package doesn't exist, then try loading the null WebView instead.
// If that succeeds, then this is a device without WebView support; if it fails then
// swallow the failure, complain that the real WebView is missing and rethrow the
// original exception.
try {
return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
} catch (ClassNotFoundException e2) {
// Ignore.
}
Log.e(LOGTAG, "Chromium WebView package does not exist", e);
throw new AndroidRuntimeException(e);
}
}
返回值是一个 WebViewFactoryProvider 的类,可以看到系统会首先加载 CHROMIUM_WEBVIEW_FACTORY,也就是使用 Chrome 内核的 WebView。这个方法是静态的,我们就可以用反射调用了。整个创建 sProviderInstance 的过程都可以用反射搞定,其他细节就不多说了。需要注意的是 API 21 以上在使用 WebView 时系统才会检查进程。但是 API 22 和 22 以上源码还是有差别,这里只是方法名字的改动,我们根据版本处理一下就好。
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); } }
在使用 WebView 之前,我们先 调用Hook WebViewFactory(如果是自定以的webview,在inflater xml之前调用) 创建 sProviderInstance 对象,从而绕过系统检查。
在用
android:sharedUserId="android.uid.system"的时候,最好先确认是否真的需要该配置,如果不需要就最好了。
转载至落英坠露 链接:http://www.jianshu.com/p/e71761597697
相关文章推荐
- Android webview 运行时不调用系统自带浏览器
- Android WebView在系统进程中无法使用
- android webview 运行时不调用系统自带浏览器
- Android WebView截取所有内容生成长图在5.0以上系统异常问题
- Android Webview异常之易忽略的点:更新系统Webview
- android 7.0系统上webview的渲染异常以及解决方案
- WebView运行在系统进程出现的问题 WebView is not allowed in privileged processes
- WebView运行在系统进程的问题
- 【Android游戏开发十九】(必看篇)SurfaceView运行机制详解—剖析Back与Home按键及切入后台等异常处理!
- 【Android游戏开发十九】(必看篇)SurfaceView运行机制详解—剖析Back与Home按键及切入后台等异常处理!
- 在Android中获取系统正在运行的进程方法
- Android---WebView(网页中文本框调用系统联系人号码)
- 让python在android系统上飞一会儿:第四节 使用WebView制作程序界面
- Android Webview中Flash播放,在3.0及4.0以上系统全屏
- android webview中的html代码 @media screen 在低版本系统下显示不正常的解决办法
- 为何android的webview运行不了javascript?
- Android游戏开发19:SurfaceView运行机制剖析--处理切换到后台再重新进入程序时的异常
- Android 3.0 以上系统 webView 无法在html中传值的解决办法
- 让python在android系统上飞一会儿:第四节 使用WebView制作程序界面
- android开发我的新浪微博客户端-OAuth认证过程中用WebView代替原来的系统自带浏览器