ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 系统签名应用 WebView 报错 “For security reasons, WebView is not allowed in privileged processes” 完美解

Android 系统签名应用 WebView 报错 “For security reasons, WebView is not allowed in privileged processes” 完美解 一、问题背景最近在开发一个需要系统签名的 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.0API 24开始系统出于安全考虑禁止在特权进程privileged processes中使用WebView。特权进程包括root(UID 0)system(UID 1000)phone(UID 1001)nfc(UID 1027)bluetooth(UID 1002) 等当应用使用系统签名并且AndroidManifest.xml中声明了android:sharedUserIdandroid.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.javaimport 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 isnt null); return; } Method getProviderClassMethod; if (sdkInt 22) { getProviderClassMethod factoryClass.getDeclaredMethod(getProviderClass); } else if (sdkInt 22) { getProviderClassMethod factoryClass.getDeclaredMethod(getFactoryClass); } else { Log.i(TAG, Dont 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标签中指定自定义 Applicationapplication android:name.MyApplication android:allowBackuptrue android:iconmipmap/ic_launcher android:labelstring/app_name android:roundIconmipmap/ic_launcher_round android:supportsRtltrue android:themestyle/Theme.AppCompat.Light.DarkActionBar ... /application3.MainActivity.javaWebView 基础用法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 version1.0 encodingutf-8? FrameLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent WebView android:idid/webView android:layout_widthmatch_parent android:layout_heightmatch_parent / ProgressBar android:idid/progressBar style?android:attr/progressBarStyleHorizontal android:layout_widthmatch_parent android:layout_height4dp android:layout_gravitytop android:max100 android:visibilitygone / /FrameLayout5. 添加网络权限uses-permission android:nameandroid.permission.INTERNET /五、验证与日志重新使用系统签名打包安装启动应用。观察 LogcatWebViewHook: 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 多个版本上验证有效。希望能帮到遇到同样问题的开发者少走弯路。
返回列表