You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

问题根源分析

  1. 空对象直接传递:GetWeatherTask的doInBackground方法在网络请求失败、响应码非HTTP_OK或抛出异常时,都会返回null,但onPostExecute未做判空就直接将该对象传给convertJSONtoArrayList。
  2. 无空值防护逻辑:convertJSONtoArrayList直接使用传入的forecast对象,未检查是否为null,导致调用getJSONArray("list")时触发空指针异常。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 20:40:53