Android异步读取JSON并渲染地图标记时应用崩溃问题
解决你的AsyncTask+GoogleMap崩溃问题
嘿,我仔细看了你的代码,发现崩溃的核心原因有两个,咱们一步步解决:
问题根源
1. AsyncTask根本没启动
你在onMapReady里创建了FetchJSON实例,但忘了调用execute()方法!这就导致后台下载JSON的逻辑完全没执行,lat和lng始终是空字符串,转成Double的时候肯定会抛出NumberFormatException,直接崩溃。
2. 异步时序错误
就算你调用了execute(),AsyncTask是在后台线程跑的,你立刻调用f.getLat()的时候,后台任务还没完成,lat和lng还是初始的空值,同样会触发转换异常。
修复方案
我们需要让AsyncTask完成JSON下载和解析后,再在主线程里操作GoogleMap添加标记。这里用回调接口的方式,既保证异步逻辑的时序正确,又能避免内存泄漏问题。
步骤1:重构FetchJSON类,添加回调机制
public class FetchJSON extends AsyncTask<Void, Void, List<LocationData>> { // 定义回调接口,通知主Activity任务结果 public interface OnJsonFetchListener { void onJsonFetched(List<LocationData> locationList); void onFetchFailed(Exception e); } // 用弱引用持有回调,避免Activity内存泄漏 private WeakReference<OnJsonFetchListener> mListenerRef; public FetchJSON(OnJsonFetchListener listener) { mListenerRef = new WeakReference<>(listener); } // 封装解析后的位置数据 public static class LocationData { public String id; public String name; public String address; public double lat; public double lng; public String type; public LocationData(String id, String name, String address, double lat, double lng, String type) { this.id = id; this.name = name; this.address = address; this.lat = lat; this.lng = lng; this.type = type; } } @Override protected List<LocationData> doInBackground(Void... voids) { List<LocationData> locationList = new ArrayList<>(); try { URL url = new URL("https://api.myjson.com/bins/ehzqu"); HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("GET"); InputStream inputStream = httpURLConnection.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder sb = new StringBuilder(); String line; // 用StringBuilder高效拼接JSON数据 while ((line = bufferedReader.readLine()) != null) { sb.append(line); } String data = sb.toString(); JSONArray JA = new JSONArray(data); for (int i = 0; i < JA.length(); i++) { JSONObject JO = JA.getJSONObject(i); String id = JO.getString("id"); String name = JO.getString("name"); String address = JO.getString("address"); // 直接解析为double,避免字符串转换异常 double lat = JO.getDouble("lat"); double lng = JO.getDouble("lng"); String type = JO.getString("type"); locationList.add(new LocationData(id, name, address, lat, lng, type)); Log.i("JSON Values", lat + " " + lng); } return locationList; } catch (Exception e) { e.printStackTrace(); // 通知主Activity下载失败 if (mListenerRef.get() != null) { mListenerRef.get().onFetchFailed(e); } return null; } } @Override protected void onPostExecute(List<LocationData> locationList) { super.onPostExecute(locationList); OnJsonFetchListener listener = mListenerRef.get(); // 任务完成后通知主Activity处理数据 if (listener != null && locationList != null) { listener.onJsonFetched(locationList); } } }
步骤2:修改TrackDifferentLocation类,实现回调并更新地图
public class TrackDifferentLocation extends AppCompatActivity implements OnMapReadyCallback, FetchJSON.OnJsonFetchListener { private GoogleMap mMap; public static TextView data; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_track_different_location); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map_fragment); mapFragment.getMapAsync(this); getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; Log.i("", "onMapReady()"); // 启动AsyncTask,传入回调接口 new FetchJSON(this).execute(); } // 回调方法:JSON下载完成后添加地图标记 @Override public void onJsonFetched(List<FetchJSON.LocationData> locationList) { if (mMap == null || locationList.isEmpty()) return; for (FetchJSON.LocationData loc : locationList) { LatLng latlng = new LatLng(loc.lat, loc.lng); // 给标记添加标题和详情,提升用户体验 mMap.addMarker(new MarkerOptions() .position(latlng) .title(loc.name) .snippet(loc.address)); // 自动移动相机到第一个标记的位置 if (locationList.indexOf(loc) == 0) { mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latlng, 12)); } } } // 回调方法:处理下载失败的情况 @Override public void onFetchFailed(Exception e) { Toast.makeText(this, "加载位置数据失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } // 原有菜单逻辑保持不变 @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == android.R.id.home) { this.finish(); } switch (item.getItemId()) { case R.id.mapTypeNone: mMap.setMapType(GoogleMap.MAP_TYPE_NONE); break; case R.id.mapTypeNormal: mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); break; case R.id.mapTypeTerrain: mMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN); break; case R.id.mapTypeSatellite: mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE); break; case R.id.mapTypeHybrid: mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); break; default: break; } return super.onOptionsItemSelected(item); } }
额外优化说明
- 用
StringBuilder拼接JSON数据,比data = data + line更高效,避免频繁创建字符串对象。 - 直接用
getDouble()解析经纬度,省去字符串转double的步骤,避免格式异常。 - 使用
WeakReference持有回调接口,防止Activity被销毁后AsyncTask还持有引用导致内存泄漏。 - 增加了下载失败的提示,提升用户体验。
内容的提问来源于stack exchange,提问作者JamesDoh96
相关产品推荐
相关产品推荐

