如何按顺序调用双URL API,展示农贸市场名称与地址?
解决农贸市场数据加载的异步时序问题
你的核心问题在于异步请求的执行时序不匹配:在getCurrentDetails方法中,你循环发起获取市场详情的异步OkHttp请求,但这些请求还未返回结果时,就已经创建了Market对象并初始化了Adapter——此时marketAddress还没有被赋值(异步回调还没执行),导致最终ListView里的地址为空。另外marketAddressArrayList未初始化,直接调用add会触发空指针异常。
下面提供两种可行的解决方案,你可以根据需求选择:
方案1:等待所有详情请求完成后统一更新UI
适合需要所有数据加载完成后再一次性展示的场景,用CountDownLatch来跟踪所有异步请求的完成状态。
修改步骤与代码:
- 先初始化
marketAddressArrayList(在onCreate中):
marketAddressArrayList = new ArrayList<>();
- 修改
getCurrentDetails方法:
private void getCurrentDetails(String jsonData) throws JSONException { JSONObject usdaJSON = new JSONObject(jsonData); JSONArray resultsJSON = usdaJSON.getJSONArray("results"); final List<Market> marketList = new ArrayList<>(); // 计数器初始值等于市场数量,每完成一个请求就减1 final CountDownLatch latch = new CountDownLatch(resultsJSON.length()); // 先收集所有市场的基础信息(名称、ID) for(int i = 0; i < resultsJSON.length(); i++){ JSONObject marketJSON = resultsJSON.getJSONObject(i); String marketname = marketJSON.getString("marketname"); String id = marketJSON.getString("id"); // 地址先设为null,后续再填充 Market market = new Market(marketname, id, null); marketList.add(market); } // 逐个发起详情请求 for(int i = 0; i < marketList.size(); i++){ final Market currentMarket = marketList.get(i); String id = currentMarket.getId(); // 复用全局OkHttpClient实例,不要每次循环创建新对象 OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("https://search.ams.usda.gov/farmersmarkets/v1/data.svc/mktDetail?id=" + id) .build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { e.printStackTrace(); latch.countDown(); // 失败也要减少计数器 } @Override public void onResponse(Response response) throws IOException { try { if (response.isSuccessful()) { String marketDetailsJsonData = response.body().string(); JSONObject detailsJSON = new JSONObject(marketDetailsJsonData); JSONObject marketDetailsJSON = detailsJSON.getJSONObject("marketdetails"); String address = marketDetailsJSON.getString("Address"); currentMarket.setMarketAddress(address); // 更新当前市场的地址 } } catch (JSONException e) { e.printStackTrace(); } finally { latch.countDown(); // 无论成功失败,计数器减1 } } }); } // 开启线程等待所有请求完成,再更新UI new Thread(new Runnable() { @Override public void run() { try { latch.await(); // 阻塞直到所有请求完成 runOnUiThread(new Runnable() { @Override public void run() { MarketAdapter adapter = new MarketAdapter(MainActivity.this, new ArrayList<>(marketList)); mListView.setAdapter(adapter); // 打印测试数据 for(Market market : marketList){ Log.d(TAG, market.getMarketname() + " - " + market.getMarketAddress()); } } }); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }
方案2:加载一个更新一个,实时刷新UI
适合需要逐步展示数据的场景,先初始化Adapter显示基础信息,每获取到一个市场的地址就更新对应项并通知Adapter刷新。
修改步骤与代码:
- 在Activity中添加全局变量:
private MarketAdapter marketAdapter; private List<Market> marketList;
- 在
onCreate中初始化集合:
marketAddressArrayList = new ArrayList<>(); marketList = new ArrayList<>();
- 修改
getCurrentDetails方法:
private void getCurrentDetails(String jsonData) throws JSONException { JSONObject usdaJSON = new JSONObject(jsonData); JSONArray resultsJSON = usdaJSON.getJSONArray("results"); // 先初始化市场列表,地址显示"加载中" for(int i = 0; i < resultsJSON.length(); i++){ JSONObject marketJSON = resultsJSON.getJSONObject(i); String marketname = marketJSON.getString("marketname"); String id = marketJSON.getString("id"); Market market = new Market(marketname, id, "Loading..."); marketList.add(market); } // 初始化Adapter并设置到ListView marketAdapter = new MarketAdapter(this, new ArrayList<>(marketList)); mListView.setAdapter(marketAdapter); // 逐个发起详情请求 for(int i = 0; i < marketList.size(); i++){ final int index = i; final Market currentMarket = marketList.get(i); String id = currentMarket.getId(); OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("https://search.ams.usda.gov/farmersmarkets/v1/data.svc/mktDetail?id=" + id) .build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { currentMarket.setMarketAddress("加载失败"); marketAdapter.notifyDataSetChanged(); // 通知Adapter更新 } }); } @Override public void onResponse(Response response) throws IOException { try { if (response.isSuccessful()) { String marketDetailsJsonData = response.body().string(); JSONObject detailsJSON = new JSONObject(marketDetailsJsonData); JSONObject marketDetailsJSON = detailsJSON.getJSONObject("marketdetails"); final String address = marketDetailsJSON.getString("Address"); runOnUiThread(new Runnable() { @Override public void run() { currentMarket.setMarketAddress(address); marketAdapter.notifyDataSetChanged(); // 更新单个项 } }); } else { runOnUiThread(new Runnable() { @Override public void run() { currentMarket.setMarketAddress("加载失败"); marketAdapter.notifyDataSetChanged(); } }); } } catch (JSONException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { currentMarket.setMarketAddress("加载失败"); marketAdapter.notifyDataSetChanged(); } }); } } }); } }
额外注意事项
- OkHttpClient复用:不要在循环中每次创建新的
OkHttpClient,建议全局初始化一个实例,提升性能。 - 权限处理:确保Manifest中添加了定位权限(
ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION),并在运行时请求权限,否则GPS无法获取位置。 - 内存泄漏:如果
GPSTracker持有Activity引用,需在Activity销毁时解绑,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Macey
相关产品推荐
相关产品推荐

