ConcurrentModificationException using mylocation overlay and timer
this problem has been doing my head in and I hope you can help! I've found the answers to a number of issues here before and hoping you people can work your magic again :)
Ok so part of my app uses the Google Maps API to display an overlay on the map (using a custom overlay class) as well as the users location using mylocationoverlay. Now if I don't activate the mylocationoverlay everything works fine but if its activated then the app force closes with a ConcurrentModificationException
.
Now the timer is used to refresh the overlay as its position is always changing. For this I used an async task to remove the overlay and add a new one with the updated position. Am I correct to assume the mylocationoverlay does a similar thing in the background? If that assumption is valid then I have come to the conclusion that the mylocationoverlay and my async task are attempting to iterate/modify the same array at the same time. The only this is, I have no idea how to stop this from happening!
I can't really post my code here as its very big but I used an example code taken from github created by commonsguy and modified it to produce the same issue.
Any help would be greatly appreciated as this has been really really frustrating me. Thanks in advance and sorry for the long description!
Edit: Link for commonsguy code is - https://github.com/commonsguy/cw-advandroid/blob/master/Maps/NooYawkAsync/
Edit 2: Added error trace after java code.
package com.commonsware.android.maps;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Asyn开发者_如何学JAVAcTask;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import com.google.android.maps.MyLocationOverlay;
import com.google.android.maps.OverlayItem;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class NooYawk extends MapActivity {
private MapView map=null;
private MyLocationOverlay me=null;
private SitesOverlay sites=null;
public Timer timer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
map=(MapView)findViewById(R.id.map);
map.getController().setCenter(getPoint(40.76793169992044, -73.98180484771729));
map.getController().setZoom(17);
map.setBuiltInZoomControls(true);
me=new MyLocationOverlay(this, map);
me.enableMyLocation();
map.getOverlays().add(me);
timer = new Timer();
timer.scheduleAtFixedRate(new RemindTask(), 10000, 10000);
new OverlayTask().execute();
}
@Override
public void onResume() {
super.onResume();
me.enableMyLocation();
me.enableCompass();
}
@Override
public void onPause() {
super.onPause();
me.disableMyLocation();
me.disableCompass();
}
@Override
protected boolean isRouteDisplayed() {
return(false);
}
private GeoPoint getPoint(double lat, double lon) {
return(new GeoPoint((int)(lat*1000000.0), (int)(lon*1000000.0)));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////// TIMER CLASS //////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class RemindTask extends TimerTask {
public void run() {
new OverlayTask().execute();
}
}
private class SitesOverlay extends ItemizedOverlay<CustomItem> {
private Drawable heart=null;
private List<CustomItem> items=new ArrayList<CustomItem>();
public SitesOverlay() {
super(null);
heart=getMarker(R.drawable.heart_full);
items.add(new CustomItem(getPoint(40.748963847316034, -73.96807193756104),
"UN", "United Nations", getMarker(R.drawable.blue_full_marker), heart));
populate();
}
@Override
protected CustomItem createItem(int i) {
return(items.get(i));
}
@Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
super.draw(canvas, mapView, shadow);
}
@Override
public int size() {
return(items.size());
}
void toggleHeart() {
CustomItem focus=getFocus();
if (focus!=null) {
focus.toggleHeart();
}
map.invalidate();
}
private Drawable getMarker(int resource) {
Drawable marker=getResources().getDrawable(resource);
marker.setBounds(0, 0, marker.getIntrinsicWidth(), marker.getIntrinsicHeight());
boundCenter(marker);
return(marker);
}
}
class PopupPanel {
View popup;
boolean isVisible=false;
PopupPanel(int layout) {
ViewGroup parent=(ViewGroup)map.getParent();
popup=getLayoutInflater().inflate(layout, parent, false);
popup.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
hide();
}
});
}
View getView() {
return(popup);
}
void show(boolean alignTop) {
RelativeLayout.LayoutParams lp=new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT
);
if (alignTop) {
lp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
lp.setMargins(0, 20, 0, 0);
}
else {
lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
lp.setMargins(0, 0, 0, 60);
}
hide();
((ViewGroup)map.getParent()).addView(popup, lp);
isVisible=true;
}
void hide() {
if (isVisible) {
isVisible=false;
((ViewGroup)popup.getParent()).removeView(popup);
}
}
}
class CustomItem extends OverlayItem {
Drawable marker=null;
boolean isHeart=false;
Drawable heart=null;
CustomItem(GeoPoint pt, String name, String snippet, Drawable marker, Drawable heart) {
super(pt, name, snippet);
this.marker=marker;
this.heart=heart;
}
@Override
public Drawable getMarker(int stateBitset) {
Drawable result=(isHeart ? heart : marker);
setState(result, stateBitset);
return(result);
}
void toggleHeart() {
isHeart=!isHeart;
}
}
class OverlayTask extends AsyncTask<Void, Void, Void> {
@Override
public void onPreExecute() {
if (sites!=null) {
map.getOverlays().remove(sites);
map.postInvalidate();
sites=null;
}
}
@Override
public Void doInBackground(Void... unused) {
//SystemClock.sleep(5000); // simulated work
sites=new SitesOverlay();
return(null);
}
@Override
public void onPostExecute(Void unused) {
map.getOverlays().add(sites);
map.postInvalidate();
}
}
}
Stack Trace:
java.util.ConcurrentModificationException at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573) at com.google.android.maps.OverlayBundle.draw(OverlayBundle.java:44) at com.google.android.maps.MapView.onDraw(MapView.java:494) at android.view.View.draw(View.java:6740) at android.view.ViewGroup.drawChild(ViewGroup.java:1640) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367) at android.view.ViewGroup.drawChild(ViewGroup.java:1638) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367) at android.view.ViewGroup.drawChild(ViewGroup.java:1638) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367) at android.view.View.draw(View.java:6743) at android.widget.FrameLayout.draw(FrameLayout.java:352) at android.view.ViewGroup.drawChild(ViewGroup.java:1640) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367) at android.view.View.draw(View.java:6743) at android.widget.FrameLayout.draw(FrameLayout.java:352) at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:1847) at android.view.ViewRoot.draw(ViewRoot.java:1407) at android.view.ViewRoot.performTraversals(ViewRoot.java:1163) at android.view.ViewRoot.handleMessage(ViewRoot.java:1727) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:123) at android.app.ActivityThread.main(ActivityThread.java:4627) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:521) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:878) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636) at dalvik.system.NativeStart.main(Native Method)
Why are you removing and replacing the overlay?
Why not leave the overlay alone, and change its marker? Just call populate()
again on the ItemizedOverlay
and it will call size()
and getItem()
all over again. Just be sure to return the new correct data.
Your error definitely feels like it is tied to adding and removing the overlay. In fact, I thought perhaps you were doing that in doInBackground()
, but you're not. Hence, I would have thought that what you're doing would be safe from a threading standpoint, just overkill from a processing standpoint.
精彩评论