Android 8: Cleartext HTTP traffic not permitted
技术背景
从 Android 9(API 级别 28)开始,默认情况下禁用了明文(Cleartext)支持。当应用尝试使用 HTTP 进行网络通信时,就会出现 “Cleartext HTTP traffic not permitted” 错误。这是出于安全考虑,因为明文通信容易受到中间人攻击,导致数据泄露和篡改。
实现步骤
方案一:使用 HTTPS 代替 HTTP
首先尝试将请求的 URL 从 http://
替换为 https://
。例如:
1
| webView.loadUrl("https://www.example.com");
|
方案二:配置网络安全文件
- 创建
res/xml/network_security_config.xml
文件:
1 2 3 4 5 6
| <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">api.example.com</domain> </domain-config> </network-security-config>
|
- 在
AndroidManifest.xml
中引用该配置文件:
1 2 3 4 5 6 7 8 9 10
| <?xml version="1.0" encoding="utf-8"?> <manifest ...> <uses-permission android:name="android.permission.INTERNET" /> <application ... android:networkSecurityConfig="@xml/network_security_config" ...> ... </application> </manifest>
|
方案三:在 AndroidManifest.xml
中设置 android:usesCleartextTraffic
1 2 3 4 5 6 7 8 9 10
| <?xml version="1.0" encoding="utf-8"?> <manifest ...> <uses-permission android:name="android.permission.INTERNET" /> <application ... android:usesCleartextTraffic="true" ...> ... </application> </manifest>
|
方案四:降低 android:targetSandboxVersion
如果 AndroidManifest.xml
中设置了 android:targetSandboxVersion="2"
,将其降低为 1
:
1 2 3 4 5
| <?xml version="1.0" encoding="utf-8"?> <manifest android:targetSandboxVersion="1"> <uses-permission android:name="android.permission.INTERNET" /> ... </manifest>
|
针对不同环境的配置
开发和生产环境使用不同配置
- 创建
res/xml/network_security_config_dev.xml
和 res/xml/network_security_config_prod.xml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">10.0.2.2</domain> </domain-config> </network-security-config>
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="false"> <domain includeSubdomains="true">yourdomain.com</domain> </domain-config> </network-security-config>
|
- 在
build.gradle
中配置不同环境的配置文件:
1 2 3 4 5 6 7 8 9
| buildTypes { release { minifyEnabled false manifestPlaceholders.securityConfig = "@xml/network_security_config_prod" } debug { manifestPlaceholders.securityConfig = "@xml/network_security_config_dev" } }
|
- 在
AndroidManifest.xml
中使用占位符:
1 2 3 4 5 6 7 8 9 10
| <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:networkSecurityConfig="${securityConfig}" ...> ... </application>
|
仅在调试时允许明文通信
在 build.gradle
中配置:
1 2 3 4 5 6 7 8 9 10
| buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' manifestPlaceholders = [usesCleartextTraffic:"false"] } debug { manifestPlaceholders = [usesCleartextTraffic:"true"] } }
|
在 AndroidManifest.xml
中使用占位符:
1 2 3 4 5 6
| <application ... android:usesCleartextTraffic="${usesCleartextTraffic}" ...> ... </application>
|
针对不同框架的解决方案
React Native 项目
创建 android/app/src/debug/res/xml/react_native_config.xml
:
1 2 3 4 5 6 7 8
| <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="false">localhost</domain> <domain includeSubdomains="false">10.0.2.2</domain> <domain includeSubdomains="false">10.0.3.2</domain> </domain-config> </network-security-config>
|
创建 android/app/src/debug/AndroidManifest.xml
:
1 2 3 4 5 6 7 8 9 10
| <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" android:networkSecurityConfig="@xml/react_native_config" /> </manifest>
|
Android:在 AssemblyInfo.cs
中添加:
1
| [assembly: Application(UsesCleartextTraffic = true)]
|
iOS:在 info.plist
中添加:
1 2 3 4 5
| <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
|
Ionic 项目
在 config.xml
中添加:
1 2 3 4
| <edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application" xmlns:android="http://schemas.android.com/apk/res/android"> <application android:networkSecurityConfig="@xml/network_security_config" /> <application android:usesCleartextTraffic="true" /> </edit-config>
|
在 network_security_config.xml
中添加:
1 2 3 4 5 6
| <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">xxx.yyyy.com</domain> </domain-config> </network-security-config>
|
.NET 8 Maui 项目
在 AndroidManifest.xml
的 application
标签中添加:
1 2 3
| <application ... android:usesCleartextTraffic="true"> ... </application>
|
核心代码
以下是一个使用 OkHttp 进行网络请求的示例代码,同时允许明文和 TLS 连接:
1 2 3 4 5 6 7 8 9 10 11
| import okhttp3.ConnectionSpec; import okhttp3.OkHttpClient; import java.util.Collections; import java.util.concurrent.TimeUnit;
OkHttpClient okHttpClient = new OkHttpClient.Builder() .readTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS) .cache(null) .connectionSpecs(Collections.singletonList(ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT)) .build();
|
最佳实践
- 优先使用 HTTPS 进行网络通信,因为 HTTPS 提供了数据加密和身份验证,能有效提高通信的安全性。
- 如果必须使用 HTTP,可以针对特定的开发环境允许明文通信,而在生产环境中禁止,以平衡开发的便利性和安全性。
- 定期检查服务器的 SSL 证书链,确保其完整性和有效性。
常见问题
配置后仍然出现错误
- 检查
network_security_config.xml
中的域名配置是否正确,确保包含了所有需要访问的域名。 - 检查
AndroidManifest.xml
中是否正确引用了配置文件或设置了 android:usesCleartextTraffic
属性。
性能问题
使用 HTTPS 可能会带来一定的性能开销,因为需要进行加密和解密操作。可以通过优化服务器配置和使用 HTTP/2 等协议来提高性能。
兼容性问题
不同版本的 Android 系统可能对网络安全配置有不同的要求,需要确保配置在目标设备上兼容。