Android WeatherViewer应用搜索崩溃,JSONArray空指针异常求助
WeatherViewer应用搜索天气崩溃的NullPointerException排查与修复
问题概述
开发WeatherViewer引导式Android应用时,触发天气搜索操作后应用立即崩溃,抛出java.lang.NullPointerException,错误指向MainActivity.java第166行——对null对象调用getJSONArray("list")方法。
报错栈信息
java.lang.NullPointerException: Attempt to invoke virtual method 'org.json.JSONArray org.json.JSONObject.getJSONArray(java.lang.String)' on a null object reference at com.example.weatherviewer.MainActivity.convertJSONtoArrayList(MainActivity.java:166) at com.example.weatherviewer.MainActivity.access$300(MainActivity.java:28) at com.example.weatherviewer.MainActivity$GetWeatherTask.onPostExecute(MainActivity.java:154) at com.example.weatherviewer.MainActivity$GetWeatherTask.onPostExecute(MainActivity.java:103) at android.os.AsyncTask.finish(AsyncTask.java:771) at android.os.AsyncTask.-$$Nest$mfinish(Unknown Source:0) at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:788) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:205) at android.os.Looper.loop(Looper.java:294) at android.app.ActivityThread.main(ActivityThread.java:8177) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
问题根源分析
- 空对象直接传递:
GetWeatherTask的doInBackground方法在网络请求失败、响应码非HTTP_OK或抛出异常时,都会返回null,但onPostExecute未做判空就直接将该对象传给convertJSONtoArrayList。 - 无空值防护逻辑:
convertJSONtoArrayList直接使用传入的forecast对象,未检查是否为null,导致调用getJSONArray("list")时触发空指针异常。 - UI线程违规操作:
doInBackground是后台线程,直接调用Snackbar.make()会触发线程安全问题,同时可能导致错误提示无法正常显示。
修复方案
1. 给onPostExecute添加空值检查
修改onPostExecute方法,确保仅当返回的JSONObject不为null时,才执行后续解析操作:
@Override protected void onPostExecute(JSONObject weather) { if (weather != null) { convertJSONtoArrayList(weather); // 重新填充weatherList weatherArrayAdapter.notifyDataSetChanged(); // 重新绑定ListView weatherListView.smoothScrollToPosition(0); // 滚动到顶部 } else { Snackbar.make(findViewById(R.id.coordinatorLayout), R.string.data_load_error, Snackbar.LENGTH_LONG).show(); } }
(需在strings.xml中添加对应错误提示:<string name="data_load_error">加载天气数据失败,请重试</string>)
2. 给convertJSONtoArrayList添加空值防护
在方法开头增加判空逻辑,避免对null对象执行操作:
// 从JSON对象中创建Weather对象列表 private void convertJSONtoArrayList(JSONObject forecast) { weatherList.clear(); // 清空旧数据 if (forecast == null) { return; } try { // 获取预报数据的"list"数组 JSONArray list = forecast.getJSONArray("list"); // 将数组中每个元素转换为Weather对象 for (int i = 0; i < list.length(); ++i) { JSONObject day = list.getJSONObject(i); // 获取单日数据 // 获取温度对象 JSONObject temperatures = day.getJSONObject("temp"); // 获取天气描述和图标对象 JSONObject weather = day.getJSONArray("weather").getJSONObject(0); // 将新Weather对象加入列表 weatherList.add(new Weather( day.getLong("dt"), // 时间戳 temperatures.getDouble("min"), // 最低温 temperatures.getDouble("max"), // 最高温 day.getDouble("humidity"), // 湿度 weather.getString("description"), // 天气描述 weather.getString("icon"))); // 图标名称 } } catch (JSONException e) { e.printStackTrace(); Snackbar.make(findViewById(R.id.coordinatorLayout), R.string.json_parse_error, Snackbar.LENGTH_LONG).show(); } }
(需在strings.xml中添加对应提示:<string name="json_parse_error">数据解析失败,请重试</string>)
3. 修复后台线程的UI操作问题
将doInBackground中的Snackbar调用移到主线程执行,通过runOnUiThread实现:
// 修改doInBackground中的异常处理块 catch (IOException e) { runOnUiThread(() -> { Snackbar.make(findViewById(R.id.coordinatorLayout), R.string.read_error, Snackbar.LENGTH_LONG).show(); }); e.printStackTrace(); } // ... else { runOnUiThread(() -> { Snackbar.make(findViewById(R.id.coordinatorLayout), R.string.connect_error, Snackbar.LENGTH_LONG).show(); }); } // ... catch (Exception e) { runOnUiThread(() -> { Snackbar.make(findViewById(R.id.coordinatorLayout), R.string.connect_error, Snackbar.LENGTH_LONG).show(); }); e.printStackTrace(); }
4. 额外优化:URL创建的日志输出
在createURL方法中添加日志,方便排查URL构造问题:
private URL createURL(String city) { String apiKey = getString(R.string.api_key); String baseUrl = getString(R.string.web_service_url); try { // 构造指定城市的请求URL(华氏度单位) String urlString = baseUrl + URLEncoder.encode(city, "UTF-8") + "&units=imperial&cnt=16&APPID=" + apiKey; Log.d("MainActivity", "构造的URL: " + urlString); return new URL(urlString); } catch (Exception e) { e.printStackTrace(); Log.e("MainActivity", "URL构造失败", e); } return null; // URL格式错误 }
完整修改后MainActivity代码
import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ListView; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.snackbar.Snackbar; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { // 存储预报数据的Weather对象列表 private List<Weather> weatherList = new ArrayList<>(); // 绑定Weather对象到ListView的适配器 private WeatherArrayAdapter weatherArrayAdapter; private ListView weatherListView; // 显示天气信息的ListView // 配置Toolbar、ListView和FAB @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); // 创建适配器并绑定到ListView weatherListView = findViewById(R.id.weatherListView); weatherArrayAdapter = new WeatherArrayAdapter(this, weatherList); weatherListView.setAdapter(weatherArrayAdapter); // 配置FAB的点击事件:隐藏键盘并发起网络请求 FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(view -> { EditText locationEditText = findViewById(R.id.locationEditText); URL url = createURL(locationEditText.getText().toString()); if (url != null) { dismissKeyboard(locationEditText); GetWeatherTask getLocalWeatherTask = new GetWeatherTask(); getLocalWeatherTask.execute(url); } else { Snackbar.make(findViewById(R.id.coordinatorLayout), R.string.invalid_url, Snackbar.LENGTH_LONG).show(); } }); } // 隐藏键盘 private void dismissKeyboard(View view) { InputMethodManager imm = (InputMethodManager) getSystemService( Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } // 构造OpenWeatherMap的请求URL private URL createURL(String city) { String apiKey = getString(R.string.api_key); String baseUrl = getString(R.string.web_service_url); try { String urlString = baseUrl + URLEncoder.encode(city, "UTF-8") + "&units=imperial&cnt=16&APPID=" + apiKey; Log.d("MainActivity", "构造的URL: " + urlString); return new URL(urlString); } catch (Exception e) { e.printStackTrace(); Log.e("MainActivity", "URL构造失败", e); } return null; } // 异步执行网络请求获取天气数据 private class GetWeatherTask extends AsyncTask<URL, Void, JSONObject> { @Override protected JSONObject doInBackground(URL... params) { HttpURLConnection connection = null; try { connection = (HttpURLConnection) params[0].openConnection(); int response = connection.getResponseCode(); if (response == HttpURLConnection.HTTP_OK) { StringBuilder builder = new StringBuilder(); try (BufferedReader reader = new BufferedReader( new InputStreamReader(connection.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { builder.append(line); } } catch (IOException e) { runOnUiThread(() -> { Snackbar.make(findViewById(R.id.coordinatorLayout), R.string.read_error, Snackbar.LENGTH_LONG).show(); }); e.printStackTrace(); } return new JSONObject(builder.toString()); } else { runOnUiThread(() -> { Snackbar.make(findViewById(R.id.coordinatorLayout), R.string.connect_error, Snackbar.LENGTH_LONG).show(); }); } } catch (Exception e) { runOnUiThread(() -> { Snackbar.make(findViewById(R.id.coordinatorLayout), R.string.connect_error, Snackbar.LENGTH_LONG).show(); }); e.printStackTrace(); } finally { if (connection != null) { connection.disconnect(); } } return null; } // 处理返回的JSON数据并更新UI @Override protected void onPostExecute(JSONObject weather) { if (weather != null) { convertJSONtoArrayList(weather); weatherArrayAdapter.notifyDataSetChanged(); weatherListView.smoothScrollToPosition(0); } else { Snackbar.make(findViewById(R.id.coordinatorLayout), R.string.data_load_error, Snackbar.LENGTH_LONG).show(); } } } // 将JSON数据转换为Weather对象列表 private void convertJSONtoArrayList(JSONObject forecast) { weatherList.clear(); if (forecast == null) { return; } try { JSONArray list = forecast.getJSONArray("list"); for (int i = 0; i < list.length(); ++i) { JSONObject day = list.getJSONObject(i); JSONObject temperatures = day.getJSONObject("temp"); JSONObject weather = day.getJSONArray("weather").getJSONObject(0); weatherList.add(new Weather( day.getLong("dt"), temperatures.getDouble("min"), temperatures.getDouble("max"), day.getDouble("humidity"), weather.getString("description"), weather.getString("icon"))); } } catch (JSONException e) { e.printStackTrace(); Snackbar.make(findViewById(R.id.coordinatorLayout), R.string.json_parse_error, Snackbar.LENGTH_LONG).show(); } } }
内容的提问来源于stack exchange,提问作者BlueDiamond
相关产品推荐
相关产品推荐

