因为最近有个需求是在系统应用中使用 WebView,所以配置了 android:sharedUserId="android.uid.system", 让应用共享系统进程。

错误日志是这样的:

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.(View.java:3588)  at android.view.View.(View.java:3682)  at android.view.ViewGroup.(ViewGroup.java:497)  at android.widget.AbsoluteLayout.(AbsoluteLayout.java:55)  at android.webkit.WebView.(WebView.java:544)  at android.webkit.WebView.(WebView.java:489)  at android.webkit.WebView.(WebView.java:472)  at android.webkit.WebView.(WebView.java:459)  at android.webkit.WebView.(WebView.java:449)
就是说为了安全性考虑,不允许在享有特权的进程也就是系统进程里面使用 WebView,异常是在 WebView 初始化的时候抛出的。

查看源码:

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 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 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) 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) 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,创建 sProviderInstance 对象,从而绕过系统检查。


更多相关文章

  1. android 设置系统屏幕亮度
  2. FregClient进程,创建一个BpFregService类型的代理对象
  3. 2011.09.07(3)——— android 跨进程通信之Broadcast
  4. android 查看系统分配程序内存限制
  5. CreateProcess error = 2,系统找不到指定的文件
  6. Android AudioManager控制系统声音的流程

随机推荐

  1. Android实现文字垂直滚动、纵向走马灯效
  2. Android中Handle总结
  3. Process 'command 'D:\SDK\ndk-bundle/
  4. 第三方Android(安卓)软件商店:现状和思考
  5. android studio git 上传代码,分支,tag,回退
  6. 2011 APP年终总结——日均160元的收入经
  7. android电话系统和ril分析(2)————rild
  8. Android之intents and intentFilters
  9. android状态栏中多个通知冲突的问题
  10. Android(安卓)Design Support 介绍