Android模拟器中USGS地震JSON数据无法显示问题求助
我正在Udacity上学习Android开发免费课程,使用课程提供的代码从USGS网站获取地震相关JSON数据并在应用中显示,但始终无法显示数据。请问可能的原因是什么?另外我使用的是Android模拟器,没有真机,这会不会是原因?
以下是相关代码:
/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.soonami; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.TextView; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.Charset; import java.text.SimpleDateFormat; /** * Displays information about a single earthquake. */ public class MainActivity extends AppCompatActivity { /** Tag for the log messages */ public static final String LOG_TAG = MainActivity.class.getSimpleName(); /** URL to query the USGS dataset for earthquake information */ private static final String USGS_REQUEST_URL = "https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2014-01-01&endtime=2014-12-01&minmagnitude=7"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Kick off an {@link AsyncTask} to perform the network request TsunamiAsyncTask task = new TsunamiAsyncTask(); task.execute(); } /** * Update the screen to display information from the given {@link Event}. */ private void updateUi(Event earthquake) { // Display the earthquake title in the UI TextView titleTextView = (TextView) findViewById(R.id.title); titleTextView.setText(earthquake.title); // Display the earthquake date in the UI TextView dateTextView = (TextView) findViewById(R.id.date); dateTextView.setText(getDateString(earthquake.time)); // Display whether or not there was a tsunami alert in the UI TextView tsunamiTextView = (TextView) findViewById(R.id.tsunami_alert); tsunamiTextView.setText(getTsunamiAlertString(earthquake.tsunamiAlert)); } /** * Returns a formatted date and time string for when the earthquake happened. */ private String getDateString(long timeInMilliseconds) { SimpleDateFormat formatter = new SimpleDateFormat("EEE, d MMM yyyy 'at' HH:mm:ss z"); return formatter.format(timeInMilliseconds); } /** * Return the display string for whether or not there was a tsunami alert for an earthquake. */ private String getTsunamiAlertString(int tsunamiAlert) { switch (tsunamiAlert) { case 0: return getString(R.string.alert_no); case 1: return getString(R.string.alert_yes); default: return getString(R.string.alert_not_available); } } /** * {@link AsyncTask} to perform the network request on a background thread, and then * update the UI with the first earthquake in the response. */ private class TsunamiAsyncTask extends AsyncTask<URL, Void, Event> { @Override protected Event doInBackground(URL... urls) { // Create URL object URL url = createUrl(USGS_REQUEST_URL); // Perform HTTP request to the URL and receive a JSON response back String jsonResponse = ""; try { jsonResponse = makeHttpRequest(url); } catch (IOException e) { // TODO Handle the IOException } // Extract relevant fields from the JSON response and create an {@link Event} object Event earthquake = extractFeatureFromJson(jsonResponse); // Return the {@link Event} object as the result of the {@link TsunamiAsyncTask} return earthquake; } /** * Update the screen with the given earthquake (which was the result of the * {@link TsunamiAsyncTask}). */ @Override protected void onPostExecute(Event earthquake) { if (earthquake == null) { return; } updateUi(earthquake); } /** * Returns new URL object from the given string URL. */ private URL createUrl(String stringUrl) { URL url = null; try { url = new URL(stringUrl); } catch (MalformedURLException exception) { Log.e(LOG_TAG, "Error with creating URL", exception); return null; } return url; } /** * Make an HTTP request to the given URL and return a String as the response. */ private String makeHttpRequest(URL url) throws IOException { String jsonResponse = ""; HttpURLConnection urlConnection = null; InputStream inputStream = null; try { urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("GET"); urlConnection.setReadTimeout(10000 /* milliseconds */); urlConnection.setConnectTimeout(15000 /* milliseconds */); urlConnection.connect(); inputStream = urlConnection.getInputStream(); jsonResponse = readFromStream(inputStream); } catch (IOException e) { // TODO: Handle the exception } finally { if (urlConnection != null) { urlConnection.disconnect(); } if (inputStream != null) { // function must handle java.io.IOException here inputStream.close(); } } return jsonResponse; } /** * Convert the {@link InputStream} into a String which contains the * whole JSON response from the server. */ private String readFromStream(InputStream inputStream) throws IOException { StringBuilder output = new StringBuilder(); if (inputStream != null) { InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8")); BufferedReader reader = new BufferedReader(inputStreamReader); String line = reader.readLine(); while (line != null) { output.append(line); line = reader.readLine(); } } return output.toString(); } /** * Return an {@link Event} object by parsing out information * about the first earthquake from the input earthquakeJSON string. */ private Event extractFeatureFromJson(String earthquakeJSON) { try { JSONObject baseJsonResponse = new JSONObject(earthquakeJSON); JSONArray featureArray = baseJsonResponse.getJSONArray("features"); // If there are results in the features array if (featureArray.length() > 0) { // Extract out the first feature (which is an earthquake) JSONObject firstFeature = featureArray.getJSONObject(0); JSONObject properties = firstFeature.getJSONObject("properties"); // Extract out the title, time, and tsunami values String title = properties.getString("title"); long time = properties.getLong("time"); int tsunamiAlert = properties.getInt("tsunami"); // Create a new {@link Event} object return new Event(title, time, tsunamiAlert); } } catch (JSONException e) { Log.e(LOG_TAG, "Problem parsing the earthquake JSON results", e); } return null; } } }
先明确一点:Android模拟器本身不会导致数据无法显示,只要模拟器能正常连接外网,就能和真机一样发起网络请求。下面是具体的排查方向和修复建议:
一、最可能的原因:缺失网络权限
Android应用默认没有访问互联网的权限,必须在AndroidManifest.xml中显式声明。你需要在<application>标签外部添加以下权限:
<uses-permission android:name="android.permission.INTERNET"/>
如果没有这个权限,所有网络请求都会静默失败,不会有任何日志提示,这是新手最容易踩的坑。
二、代码中的关键问题:异常处理缺失
你的代码里多个异常块都只是TODO注释,没有任何日志输出,导致你根本不知道请求过程中哪里出错了。比如:
- 网络连接超时
- 服务器返回错误状态码(如404、500)
- 输入流读取失败
修复方法:添加异常日志
在doInBackground和makeHttpRequest的catch块中添加日志,方便定位问题:
// 在doInBackground的catch块中 catch (IOException e) { Log.e(LOG_TAG, "Failed to perform HTTP request", e); } // 在makeHttpRequest的catch块中 catch (IOException e) { Log.e(LOG_TAG, "Error connecting to server or reading response", e); }
添加后,你可以在Android Studio的Logcat中过滤LOG_TAG(即MainActivity),查看具体的错误信息。
三、未校验HTTP响应状态码
代码中直接调用urlConnection.getInputStream(),但如果服务器返回非200的状态码(比如URL无效、服务器维护),这一步会抛出异常,导致jsonResponse为空,最终解析出的Event对象为null,onPostExecute直接返回,不更新UI。
修复方法:添加状态码校验
在makeHttpRequest中连接服务器后,先检查响应状态码:
urlConnection.connect(); // 新增:检查响应码是否在成功范围内(200-299) int responseCode = urlConnection.getResponseCode(); if (responseCode < 200 || responseCode >= 300) { Log.e(LOG_TAG, "Server returned error code: " + responseCode); throw new IOException("Invalid response code: " + responseCode); } // 只有响应成功才读取输入流 inputStream = urlConnection.getInputStream();
四、JSON解析前未判空
如果jsonResponse为空字符串,new JSONObject(earthquakeJSON)会抛出异常,虽然你有catch块,但最好提前判空,避免不必要的异常:
private Event extractFeatureFromJson(String earthquakeJSON) { // 新增:先判断JSON字符串是否为空 if (android.text.TextUtils.isEmpty(earthquakeJSON)) { Log.e(LOG_TAG, "JSON response is empty"); return null; } // 原解析代码... }
记得要导入android.text.TextUtils类。
五、验证模拟器网络是否正常
打开模拟器的浏览器,输入你使用的USGS URL:https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2014-01-01&endtime=2014-12-01&minmagnitude=7
如果能正常加载JSON数据,说明模拟器网络没问题;如果不能,检查模拟器的网络设置(比如是否开启代理、宿主机网络是否正常)。
按照以上步骤排查,应该能快速找到问题并解决。
内容的提问来源于stack exchange,提问作者BiT4000

