做安卓开发的时候,最让人头大的问题之一就是:前端(安卓端)调接口一直报错,后端同学却一脸无辜地说“我这边测过了,完全正常啊”。这种情况大概率就是掉进了CORS(跨域资源共享)或者网络配置的坑里。今天咱们就手把手,把这个问题彻底讲清楚,顺便把安卓端前后端分离开发的数据对接全流程顺一遍。
一、 先别慌,搞清楚“跨域”到底是啥
很多安卓开发者听到“跨域”就懵了,觉得这是前端的活儿,跟安卓有什么关系?
其实关系大了!
虽然安卓不像浏览器那样严格限制JavaScript跨域,但Android 9(API 28)及以上版本默认禁止明文HTTP请求,而且很多现代网络库(如OkHttp)在处理HTTPS证书、代理、或者某些特殊请求头时,如果后端没有正确配置CORS头,请求可能会被系统或中间件拦截,导致你看到类似404、403、或者连接失败的错误。
核心误区: 安卓端不会像浏览器那样因为“跨域”而直接阻断请求并抛JS错误。但后端如果不配置CORS,安卓端的请求可能根本收不到响应,或者被中间网关拦截。所以,安卓端遇到“跨域”感知的错误,通常是网络层或安全策略的问题。
二、 安卓端常见“404/跨域”假象及真实原因
情况1:Android 9+ 明文HTTP被拦截(最常见!)
现象:
- 后端接口是
http://192.168.1.100:8080/api/user - 安卓端请求后,直接失败,日志里看不到任何HTTP响应,或者报
Cleartext HTTP traffic not permitted - 后端同学说:“我直接用Postman测试
http://...没问题啊!”
真实原因: Android 9(API 28)及以上版本,默认不允许明文网络流量(即HTTP)。Postman是桌面应用,不受此限制;但安卓App必须走HTTPS,或者在Manifest中明确声明允许明文流量。
解决方案:
方法一:使用HTTPS(推荐,最规范)
让后端配置SSL证书,接口地址改为 https://...。这是终极解决方案。
方法二:在AndroidManifest.xml中声明允许明文流量
在 AndroidManifest.xml 的 <application> 标签中添加:
<application
android:name=".MyApp"
android:usesCleartextTraffic="true"
... >
<!-- 其他配置 -->
</application>
注意:
usesCleartextTraffic="true"会降低安全性,仅在内网测试或调试时推荐。正式环境务必上HTTPS。
方法三:针对特定域名允许明文流量(更精细控制)
创建一个 res/xml/network_security_config.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.1.100</domain>
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>
然后在 AndroidManifest.xml 中引用:
<application
android:name=".MyApp"
android:networkSecurityConfig="@xml/network_security_config"
... >
情况2:请求头/方法不对,被后端网关拦截
现象:
- 报404,但接口明明存在
- 后端日志显示没有收到请求
真实原因:
安卓端可能发送了 OPTIONS 预检请求,而后端网关(如Nginx、Spring Cloud Gateway)没有正确配置CORS,导致预检失败,返回404或403。
CORS预检机制:
当安卓端发送非简单请求(如包含自定义Header、或Content-Type为 application/json 的POST请求)时,浏览器(或某些网络库)会先发送一个 OPTIONS 请求,询问服务器“我能不能这样发请求?”如果服务器不响应 OPTIONS 或响应头中没有 Access-Control-Allow-*,请求就会被拦截。
解决方案:
安卓端:确保使用简单的请求头
避免在请求头中携带自定义Header(如 Authorization: Bearer xxx),或者先测试一个最简单的GET请求,看是否成功。
后端:正确配置CORS 以Spring Boot为例,添加CORS配置:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*") // 允许所有来源
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
或者使用注解:
@CrossOrigin(origins = "*", allowedHeaders = "*")
@RestController
public class UserController {
// ...
}
Nginx配置:
location /api/ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
add_header Access-Control-Allow-Headers 'Content-Type, Authorization';
if ($request_method = OPTIONS) {
return 204;
}
proxy_pass http://backend_server;
}
情况3:IP地址或端口错误
现象:
- 报
Connection refused或404 - 后端日志无记录
真实原因:
安卓模拟器/真机的网络环境与电脑不同。如果你在后端配置的是 localhost 或 127.0.0.1,安卓端访问的是安卓设备本身的localhost,而不是你的开发电脑!
解决方案:
- 模拟器(AVD): 使用
10.0.2.2代替localhost或127.0.0.1 - 真机: 确保安卓设备和电脑在同一局域网,获取电脑的局域网IP(如
192.168.1.100),接口地址改为http://192.168.1.100:8080/api/... - 关闭电脑防火墙,或允许安卓设备IP访问相应端口
代码示例:
// 错误写法
private const val BASE_URL = "http://localhost:8080/"
// 正确写法(模拟器)
private const val BASE_URL = "http://10.0.2.2:8080/"
// 正确写法(真机,需替换为你的电脑IP)
private const val BASE_URL = "http://192.168.1.100:8080/"
情况4:SSL证书问题(HTTPS场景)
现象:
- 接口是HTTPS,但报
SSLHandshakeException或Unable to find trusted certificates - 后端同学说:“我用浏览器访问没问题啊”
真实原因: 安卓端对SSL证书校验更严格。如果后端使用的是自签名证书或Let’s Encrypt等未受信任的证书,安卓端会拒绝连接。
解决方案:
方法一:在后端部署受信任的CA证书(推荐)
方法二:临时信任所有证书(仅调试用!)
创建 NetworkSecurityConfig 或自定义 OkHttpClient:
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, arrayOf(object : X509TrustManager {
override fun checkClientTrusted(chain: Array<out java.security.cert.X509Certificate>?, authType: String?) {}
override fun checkServerTrusted(chain: Array<out java.security.cert.X509Certificate>?, authType: String?) {}
override fun getAcceptedIssuers() = arrayOf<java.security.cert.X509Certificate>()
}), java.security.SecureRandom())
}
val okHttpClient = OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, sslContext.trustManager[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true }
.build()
三、 安卓端网络请求最佳实践(Retrofit + OkHttp)
现在我们来聊聊如何优雅地发送请求,避免踩坑。
1. 依赖配置
在 build.gradle 中添加:
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' // 日志拦截器,方便调试
}
2. 创建数据模型
data class User(
val id: Int,
val name: String,
val email: String
)
data class ApiResponse<T>(
val code: Int,
val message: String,
val data: T
)
3. 创建API接口
interface ApiService {
@GET("user/list")
suspend fun getUserList(): ApiResponse<List<User>>
@POST("user/login")
suspend fun login(@Body request: LoginRequest): ApiResponse<User>
@GET("user/{id}")
suspend fun getUserById(@Path("id") userId: Int): ApiResponse<User>
}
data class LoginRequest(
val username: String,
val password: String
)
4. 创建Retrofit实例(关键:处理CORS和网络配置)
object RetrofitClient {
private const val BASE_URL = "http://192.168.1.100:8080/" // 根据情况修改
private val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY // 打印完整请求响应,方便调试
})
// 如果需要信任所有证书(仅调试)
// .sslSocketFactory(...).hostnameVerifier(...)
.build()
val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
5. 发起请求(ViewModel + Flow)
class UserViewModel : ViewModel() {
private val _userList = MutableStateFlow<List<User>>(emptyList())
val userList: StateFlow<List<User>> = _userList
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading
private val _error = MutableStateFlow<String?>(null)
val error: StateFlow<String?> = _error
fun fetchUsers() {
viewModelScope.launch {
_isLoading.value = true
_error.value = null
try {
val response = RetrofitClient.apiService.getUserList()
if (response.code == 200) {
_userList.value = response.data ?: emptyList()
} else {
_error.value = response.message
}
} catch (e: IOException) {
_error.value = "网络错误: ${e.message}"
} catch (e: HttpException) {
_error.value = "HTTP错误: ${e.code()} ${e.message()}"
} catch (e: Exception) {
_error.value = "未知错误: ${e.message}"
} finally {
_isLoading.value = false
}
}
}
}
四、 调试技巧:如何定位“404”或“跨域”问题
当安卓端请求失败时,按以下步骤排查:
1. 看Logcat日志
启用OkHttp的LoggingInterceptor后,你会看到完整的请求头和响应头:
D/OkHttp: --> GET http://192.168.1.100:8080/api/user/list
D/OkHttp: Accept: application/json
D/OkHttp: --> END GET
D/OkHttp: <-- 404 Not Found http://192.168.1.100:8080/api/user/list (123ms)
D/OkHttp: Content-Type: application/json
D/OkHttp: Content-Length: 45
D/OkHttp: {"code":404,"message":"接口不存在"}
D/OkHttp: <-- END HTTP
关键点:
- 检查URL是否拼写正确
- 检查HTTP状态码(404、403、500)
- 检查响应体中的错误信息
2. 用Postman/curl测试后端接口
在电脑终端用curl测试:
curl -X GET http://192.168.1.100:8080/api/user/list
如果curl能拿到数据,说明后端没问题,问题在安卓端网络配置。
3. 检查安卓设备的网络环境
- 模拟器:确认使用
10.0.2.2 - 真机:确认和电脑在同一WiFi,关闭电脑防火墙,或添加入站规则允许安卓设备IP访问
4. 检查后端CORS配置
在后端接口响应头中添加:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
用curl测试OPTIONS预检请求:
curl -X OPTIONS http://192.168.1.100:8080/api/user/list -H "Access-Control-Request-Method: GET" -H "Origin: http://example.com" -v
如果OPTIONS请求返回非204,说明CORS配置有问题。
五、 前后端分离开发的完整对接流程
第一步:前后端约定接口规范
- 定义统一的响应结构(如
ApiResponse<T>) - 约定HTTP状态码含义(200成功,401未登录,403无权限,404资源不存在,500服务器错误)
- 约定认证方式(如JWT Bearer Token)
第二步:后端开发并部署测试环境
- 提供Swagger/Knife4j接口文档
- 部署到内网测试服务器(如
http://192.168.1.100:8080) - 确保CORS配置正确
第三步:安卓端开发
- 配置网络权限和明文流量
- 创建Retrofit实例
- 定义API接口和数据模型
- 在ViewModel中发起请求
- 在UI层展示数据
第四步:联调测试
- 使用Postman验证后端接口
- 使用Logcat查看安卓端请求日志
- 逐步排查问题(网络、CORS、参数、数据解析)
第五步:生产环境部署
- 后端配置HTTPS证书
- 安卓端移除
usesCleartextTraffic和证书信任代码 - 修改BASE_URL为生产域名
- 进行安全测试和性能测试
六、 总结:避坑清单
- Android 9+:记得配置
usesCleartextTraffic或网络安全配置 - IP地址:模拟器用
10.0.2.2,真机用电脑局域网IP - CORS:后端必须配置,否则安卓端可能被拦截
- HTTPS:生产环境务必使用HTTPS
- 日志:启用OkHttp日志拦截器,方便调试
- 异常处理:捕获
IOException、HttpException等,避免App崩溃 - 接口文档:前后端对齐Swagger文档,减少沟通成本
前后端分离开发中,网络请求问题是最常见的“拦路虎”。但只要你掌握了CORS机制、安卓网络配置规则,以及Retrofit的正确用法,这些问题都能迎刃而解。记住,调试时多看日志,多用Postman验证后端,保持前后端沟通顺畅,是解决网络问题的三大法宝。
希望这份指南能帮你彻底搞定安卓端网络请求的问题!如果还有疑问,欢迎随时交流~
