Android Kotlin调用天气接口URL.readText返回null报JSONException
问题解决方案:Android Kotlin请求OpenWeatherMap接口返回null及JSON解析异常
问题根因
主要由以下几个常见问题共同导致:
- 明文HTTP请求被系统拦截:Android 9(API 28)及以上版本默认禁止明文HTTP流量,你当前使用的
http://接口会被系统直接拦截,请求抛出异常被catch后返回null,浏览器没有该限制所以可以正常拿到响应 - 请求参数未做URL编码:如果定位参数包含空格、中文或特殊字符,直接拼接URL会导致请求非法,浏览器会自动完成参数编码,代码中无编码逻辑会导致请求失败
- 异常被直接吞掉无排查依据:当前代码捕获所有异常后直接返回null,无法定位具体请求错误原因
- 空响应直接解析逻辑不合理:response为null时你创建了空JSONObject,后续直接调用
getJSONObject方法找不到对应字段就会抛出JSONException
解决方案
步骤1:修复HTTP明文限制(优先选方案1)
- 方案1:直接替换为HTTPS接口,OpenWeatherMap原生支持HTTPS,将请求地址的
http://api.openweathermap.org修改为https://api.openweathermap.org即可 - 方案2:如果必须使用HTTP,新增网络安全配置放开限制:
- 在
res/xml目录下新建network_security_config.xml文件,内容如下:
- 在
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">api.openweathermap.org</domain> </domain-config> </network-security-config>
- 在
AndroidManifest.xml的<application>标签中添加属性:android:networkSecurityConfig="@xml/network_security_config"
步骤2:确认网络权限配置
检查AndroidManifest.xml中已添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
步骤3:优化网络请求函数
suspend fun getWeatherResponse(locationCode: String, apiKey: String): String? { return withContext(Dispatchers.IO) { // 对定位参数做URL编码,兼容特殊字符场景 val encodedLocation = URLEncoder.encode(locationCode, StandardCharsets.UTF_8.name()) val apiUrl = "https://api.openweathermap.org/data/2.5/weather" + "?q=$encodedLocation&appid=$apiKey" return@withContext try { Log.d("WeatherRequest", "请求地址:$apiUrl") // 调试用,可确认拼接的URL是否正确 URL(apiUrl).readText(StandardCharsets.UTF_8) } catch (ex: Exception) { Log.e("WeatherRequest", "请求失败", ex) // 打印异常便于排查问题 null } } }
步骤4:优化JSON解析逻辑避免崩溃
val response = getWeatherResponse(location, apiKey) // 先判断响应是否合法,空响应直接走失败逻辑 if (response.isNullOrBlank()) { // 此处添加请求失败提示逻辑,比如弹Toast Toast.makeText(context, "网络请求失败,请稍后重试", Toast.LENGTH_SHORT).show() return@coroutineScope // 终止后续解析流程 } val jsonWeatherObj = JSONObject(response) // 解析时用optXxx代替getXxx,避免字段缺失直接崩溃,可按需添加失败提示 val main = jsonWeatherObj.optJSONObject("main") ?: run { Toast.makeText(context, "天气数据解析失败", Toast.LENGTH_SHORT).show() return@coroutineScope } val sys = jsonWeatherObj.optJSONObject("sys") ?: return@coroutineScope val wind = jsonWeatherObj.optJSONObject("wind") ?: return@coroutineScope val weather = jsonWeatherObj.optJSONArray("weather")?.optJSONObject(0) ?: return@coroutineScope // 字段获取也可以用optString设置默认值,提升容错性 val temp = "${main.optString("temp", "0")}°C" val tempMin = "Min Temp: ${main.optString("temp_min", "0")}°C" val tempMax = "Max Temp: ${main.optString("temp_max", "0")}°C" // 其余字段按相同逻辑处理即可
内容的提问来源于stack exchange,提问作者exactly
相关产品推荐
相关产品推荐

