一、问题背景
最近在开发一个需要系统签名的 Android 应用,布局中使用了WebView。用普通签名调试时一切正常,但换成系统签名后一启动就崩溃,报错如下:
java.lang.RuntimeException: Unable to start activity ComponentInfo{...MainActivity}: android.view.InflateException: Binary XML file line #9 in ...activity_main: Error inflating class android.webkit.WebView ... Caused by: java.lang.UnsupportedOperationException: For security reasons, WebView is not allowed in privileged processes at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:332) at android.webkit.WebView.getFactory(WebView.java:2594) at android.webkit.WebView.ensureProviderCreated(WebView.java:2588) ...关键错误信息:
For security reasons, WebView is not allowed in privileged processes
二、原因分析
从Android 7.0(API 24)开始,系统出于安全考虑,禁止在特权进程(privileged processes)中使用WebView。特权进程包括:
root(UID 0)system(UID 1000)phone(UID 1001)nfc(UID 1027)bluetooth(UID 1002) 等
当应用使用系统签名,并且AndroidManifest.xml中声明了android:sharedUserId="android.uid.system",或者应用被预置到系统分区时,进程就会以system身份运行。此时WebViewFactory.getProvider()会检查当前 UID,若为特权 UID,则直接抛出UnsupportedOperationException。
因此,普通签名正常,系统签名崩溃。
三、解决方案
方案一:HookWebViewFactory(推荐,无需修改系统源码)
WebViewFactory.getProvider()方法内部逻辑大致如下:
static WebViewFactoryProvider getProvider() { synchronized (sProviderLock) { if (sProviderInstance != null) return sProviderInstance; // ... 安全检查 ... final int uid = android.os.Process.myUid(); if (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID || ...) { throw new UnsupportedOperationException("For security reasons, WebView is not allowed in privileged processes"); } // ... 后续初始化 ... } }可以看到,如果sProviderInstance不为null,方法会直接返回,跳过安全检查。我们可以通过反射提前初始化sProviderInstance,从而绕过限制。
方案二:修改系统源码(仅适用于 ROM 开发者)
在frameworks/base/core/java/android/webkit/WebViewFactory.java中添加系统属性开关,绕过检查。需要重新编译系统并刷机,不适用于普通应用开发者。
四、完整代码实现
1. 新建MyApplication.java
import android.app.Application; import android.os.Build; import android.util.Log; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class MyApplication extends Application { private static final String TAG = "WebViewHook"; @Override public void onCreate() { super.onCreate(); hookWebView(); } private 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(null, sProviderInstance); // 静态字段,第一个参数为 null Log.i(TAG, "Hook success!"); } else { Log.i(TAG, "Hook failed!"); } } catch (Throwable e) { Log.w(TAG, "Hook WebView failed", e); } } }2. 修改AndroidManifest.xml
在<application>标签中指定自定义 Application:
<application android:name=".MyApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.AppCompat.Light.DarkActionBar"> ... </application>3.MainActivity.java(WebView 基础用法)
import android.annotation.SuppressLint; import android.os.Bundle; import android.view.View; import android.webkit.WebChromeClient; import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ProgressBar; import androidx.activity.OnBackPressedCallback; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private WebView webView; private ProgressBar progressBar; @SuppressLint("SetJavaScriptEnabled") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); webView = findViewById(R.id.webView); progressBar = findViewById(R.id.progressBar); WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true); settings.setDomStorageEnabled(true); webView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { return false; } @SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { return false; } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); progressBar.setVisibility(View.GONE); } }); webView.setWebChromeClient(new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); progressBar.setProgress(newProgress); if (newProgress >= 100) { progressBar.setVisibility(View.GONE); } else { progressBar.setVisibility(View.VISIBLE); } } }); webView.loadUrl("https://www.example.com"); getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { if (webView.canGoBack()) { webView.goBack(); } else { setEnabled(false); getOnBackPressedDispatcher().onBackPressed(); } } }); } @Override protected void onDestroy() { if (webView != null) { webView.destroy(); } super.onDestroy(); } }4. 布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:id="@+id/webView" android:layout_width="match_parent" android:layout_height="match_parent" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="4dp" android:layout_gravity="top" android:max="100" android:visibility="gone" /> </FrameLayout>5. 添加网络权限
<uses-permission android:name="android.permission.INTERNET" />五、验证与日志
重新使用系统签名打包安装,启动应用。观察 Logcat:
WebViewHook: Hook success!
如果看到Hook success!,说明反射初始化sProviderInstance成功,WebView 可以正常加载页面,不再抛出UnsupportedOperationException。
如果看到Hook failed!或异常堆栈,请检查:
是否在
Application.onCreate()中调用 Hook,且在任何 WebView 创建之前;设备 Android 版本是否在反射兼容范围内(代码已覆盖 API 22~34);
是否因隐藏 API 限制导致反射失败(系统应用通常不受限)。
六、注意事项
Hook 时机:必须在任何
WebView实例创建之前执行,放在Application.onCreate()是最稳妥的。版本兼容:不同 Android 版本的
WebViewFactory内部字段可能略有差异,但上述代码已覆盖主流版本。若遇NoSuchFieldException,需根据具体版本调整反射字段名。隐藏 API 限制:Android 9 开始对非 SDK 接口有限制,但系统签名应用通常在白名单内,一般不会受阻。
安全性:此方案绕过了系统安全检查,仅建议在系统应用或受控设备中使用,普通应用请勿滥用。
替代方案:如果 Hook 失效,可考虑修改系统源码或使用 Crosswalk/GeckoView,但代价较高。
七、总结
系统签名应用使用WebView时,由于 Android 的特权进程限制,会抛出UnsupportedOperationException。通过反射提前初始化WebViewFactory.sProviderInstance,可以巧妙地绕过getProvider()中的 UID 检查。本文提供的 Java 代码可直接复制到项目中使用,已在 Android 7.0~14 多个版本上验证有效。希望能帮到遇到同样问题的开发者,少走弯路。