Kotlin天气APP遇JSON转换异常与空指针错误求助
问题概述
开发Kotlin天气APP时遇到两个核心问题:
- 解析JSON时抛出
org.json.JSONException,提示JSONObject无法转换为JSONArray; - 触发
java.lang.NullPointerException,提示在null对象上调用了String.length()方法。
错误日志
第一个错误日志
2022-12-01 13:32:16.389 5990-5990/com.example.wheather I/System.out: org.json.JSONException: Value {"coord":{"lon":77.2167,"lat":28.6667},"weather":[{"id":711,"main":"Smoke","description":"smoke","icon":"50d"}],"base":"stations","main":{"temp":23.05,"feels_like":22.61,"temp_min":23.05,"temp_max":23.05,"pressure":1015,"humidity":46},"visibility":1500,"wind":{"speed":2.57,"deg":280},"clouds":{"all":0},"dt":1669881717,"sys":{"type":1,"id":9165,"country":"IN","sunrise":1669857989,"sunset":1669895639},"timezone":19800,"id":1273294,"name":"Delhi","cod":200} of type org.json.JSONObject cannot be converted to JSONArray
第二个错误日志
2022-12-01 16:38:42.435 6838-6838/com.example.wheather I/System.out: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
原代码
class MainActivity : AppCompatActivity() { val CITY: String="Delhi,In" val API: String= "67561777e881bf52c2b82163225846ad" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) wetherTask().execute() } inner class wetherTask() :AsyncTask<String,Void,String>() { override fun onPreExecute() { super.onPreExecute() findViewById<ProgressBar>(R.id.loader).visibility= View.VISIBLE findViewById<RelativeLayout>(R.id.maincontainer).visibility=View.GONE findViewById<TextView>(R.id.errortext).visibility=View.GONE } override fun doInBackground(vararg params: String?): String? { var response:String? try { response =URL("https://api.openweathermap.org/data/2.5/weather?q=$CITY&units=metric&APPID=$API") .readText(Charsets.UTF_8) System.out.println(response); } catch (e:Exception) { response=null } return response } override fun onPostExecute(result: String?) { super.onPostExecute(result) try { val jasonObj= JSONObject(result) val main=jasonObj.getJSONObject("main") val sys=jasonObj.getJSONObject("sys") val wind=jasonObj.getJSONObject("wind") val weather=jasonObj.getJSONObject("weather") val updateAt:Long=jasonObj.getLong("dt") val updateAtText="Update at: "+SimpleDateFormat("dd/MM/yyyy hh:mm a", Locale.ENGLISH).format(Date(updateAt*1000)) val temp=main.getString("temp")+"°C" val tempMin="Min Temp: "+main.getString("temp_min")+"°C" val tempMax="Max Temp: "+main.getString("temp_max")+"°C" val pressure= main.getString("pressure") val humidity=main.getString("humidity") val sunrise:Long=sys.getLong("sunrise") val sunset:Long=sys.getLong("sunset") val windSpeed=wind.getString("speed") val weatherDescription=weather.getString("description") val address=jasonObj.getString("name")+", "+sys.getString("country") findViewById<TextView>(R.id.adress).text=address findViewById<TextView>(R.id.update_at).text=updateAtText findViewById<TextView>(R.id.status).text=weatherDescription.capitalize() findViewById<TextView>(R.id.temp).text=temp findViewById<TextView>(R.id.temp_min).text=tempMin findViewById<TextView>(R.id.temp_max).text=tempMax findViewById<TextView>(R.id.sunrise).text=SimpleDateFormat("hh:mm a",Locale.ENGLISH).format((Date(sunrise*1000))) findViewById<TextView>(R.id.sunset).text=SimpleDateFormat("hh:mm a",Locale.ENGLISH).format((Date(sunset*1000))) findViewById<TextView>(R.id.wind).text=windSpeed findViewById<TextView>(R.id.pressure).text=pressure findViewById<TextView>(R.id.humidity).text=humidity findViewById<ProgressBar>(R.id.loader).visibility=View.GONE findViewById<RelativeLayout>(R.id.maincontainer).visibility=View.VISIBLE } catch (e:Exception){ System.out.println(e); findViewById<ProgressBar>(R.id.loader).visibility=View.GONE findViewById<TextView>(R.id.errortext).visibility=View.VISIBLE } } } }
错误原因分析
1. JSON解析异常
从返回的JSON数据可以看到,weather字段是一个JSON数组(格式为[{"id":711,...}]),但原代码中使用getJSONObject("weather")尝试将其解析为JSONObject,类型不匹配直接抛出异常。
2. 空指针异常
doInBackground方法中,如果网络请求失败(比如无网络、API错误),response会被赋值为null。而onPostExecute中直接调用JSONObject(result),当result为null时,JSONObject的构造函数会尝试调用String.length(),从而触发空指针异常。
解决方案
1. 修正JSON数组解析
将weather字段的解析方式改为JSONArray,再取数组中的第一个元素获取天气描述:
val weatherArray = jasonObj.getJSONArray("weather") val weather = weatherArray.getJSONObject(0) val weatherDescription = weather.getString("description")
2. 增加空值判断
在onPostExecute开头先判断result是否为null,若为null直接显示错误界面,避免后续无效操作:
if (result == null) { findViewById<ProgressBar>(R.id.loader).visibility = View.GONE findViewById<TextView>(R.id.errortext).visibility = View.VISIBLE return }
3. 优化异常日志
在doInBackground的catch块中打印异常信息,方便排查网络问题:
catch (e: Exception) { e.printStackTrace() response = null }
修正后的完整代码
class MainActivity : AppCompatActivity() { val CITY: String = "Delhi,In" val API: String = "67561777e881bf52c2b82163225846ad" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) wetherTask().execute() } inner class wetherTask : AsyncTask<String, Void, String>() { override fun onPreExecute() { super.onPreExecute() findViewById<ProgressBar>(R.id.loader).visibility = View.VISIBLE findViewById<RelativeLayout>(R.id.maincontainer).visibility = View.GONE findViewById<TextView>(R.id.errortext).visibility = View.GONE } override fun doInBackground(vararg params: String?): String? { var response: String? try { response = URL("https://api.openweathermap.org/data/2.5/weather?q=$CITY&units=metric&APPID=$API") .readText(Charsets.UTF_8) System.out.println(response); } catch (e: Exception) { e.printStackTrace() response = null } return response } override fun onPostExecute(result: String?) { super.onPostExecute(result) // 先判断result是否为空 if (result == null) { findViewById<ProgressBar>(R.id.loader).visibility = View.GONE findViewById<TextView>(R.id.errortext).visibility = View.VISIBLE return } try { val jasonObj = JSONObject(result) val main = jasonObj.getJSONObject("main") val sys = jasonObj.getJSONObject("sys") val wind = jasonObj.getJSONObject("wind") // 修正weather字段解析为JSONArray val weatherArray = jasonObj.getJSONArray("weather") val weather = weatherArray.getJSONObject(0) val updateAt: Long = jasonObj.getLong("dt") val updateAtText = "Update at: " + SimpleDateFormat("dd/MM/yyyy hh:mm a", Locale.ENGLISH).format(Date(updateAt * 1000)) val temp = main.getString("temp") + "°C" val tempMin = "Min Temp: " + main.getString("temp_min") + "°C" val tempMax = "Max Temp: " + main.getString("temp_max") + "°C" val pressure = main.getString("pressure") val humidity = main.getString("humidity") val sunrise: Long = sys.getLong("sunrise") val sunset: Long = sys.getLong("sunset") val windSpeed = wind.getString("speed") val weatherDescription = weather.getString("description") val address = jasonObj.getString("name") + ", " + sys.getString("country") findViewById<TextView>(R.id.adress).text = address findViewById<TextView>(R.id.update_at).text = updateAtText findViewById<TextView>(R.id.status).text = weatherDescription.capitalize() findViewById<TextView>(R.id.temp).text = temp findViewById<TextView>(R.id.temp_min).text = tempMin findViewById<TextView>(R.id.temp_max).text = tempMax findViewById<TextView>(R.id.sunrise).text = SimpleDateFormat("hh:mm a", Locale.ENGLISH).format(Date(sunrise * 1000)) findViewById<TextView>(R.id.sunset).text = SimpleDateFormat("hh:mm a", Locale.ENGLISH).format(Date(sunset * 1000)) findViewById<TextView>(R.id.wind).text = windSpeed findViewById<TextView>(R.id.pressure).text = pressure findViewById<TextView>(R.id.humidity).text = humidity findViewById<ProgressBar>(R.id.loader).visibility = View.GONE findViewById<RelativeLayout>(R.id.maincontainer).visibility = View.VISIBLE } catch (e: Exception) { e.printStackTrace() findViewById<ProgressBar>(R.id.loader).visibility = View.GONE findViewById<TextView>(R.id.errortext).visibility = View.VISIBLE } } } }
内容的提问来源于stack exchange,提问作者Priyanshu Samal

