使用 mylocation 覆盖和计时器的 ConcurrentModificationException
这个问题一直困扰着我,希望你能帮忙!我之前已经在这里找到了许多问题的答案,希望你们能再次施展魔法:)
好吧,我的应用程序的一部分使用 Google 地图 API 在地图上显示叠加层(使用自定义叠加层类),如下所示以及使用 mylocationoverlay 的用户位置。现在,如果我不激活 mylocationoverlay ,一切都会正常工作,但如果它被激活,则应用程序会强制关闭,并出现 ConcurrentModificationException 。
现在,计时器用于刷新覆盖层,因为它的位置总是在变化。为此,我使用了一个异步任务来删除覆盖层并添加一个具有更新位置的新覆盖层。我假设 mylocationoverlay 在后台执行类似的操作是否正确?如果该假设成立,那么我得出的结论是 mylocationoverlay 和我的异步任务正在尝试同时迭代/修改同一数组。唯一的是,我不知道如何阻止这种情况发生!
我无法在这里发布我的代码,因为它非常大,但我使用了从 commonsguy 创建的 github 中获取的示例代码,并对其进行了修改以产生相同的问题。
任何帮助将不胜感激,因为这真的让我非常沮丧。提前致谢,并对冗长的描述表示歉意!
编辑:commonsguy 代码的链接是 - https://github.com/ commonsguy/cw-advandroid/blob/master/Maps/NooYawkAsync/
编辑2:在java代码后添加错误跟踪。
package com.commonsware.android.maps;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
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();
}
}
}
堆栈跟踪:
java.util.ConcurrentModificationException 在 java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573) 在 com.google.android.maps.OverlayBundle.draw(OverlayBundle.java:44) 在 com.google.android.maps.MapView.onDraw(MapView.java:494) 在 android.view.View.draw(View.java:6740) 在 android.view.ViewGroup.drawChild(ViewGroup.java:1640) 在 android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367) 在 android.view.ViewGroup.drawChild(ViewGroup.java:1638) 在 android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367) 在 android.view.ViewGroup.drawChild(ViewGroup.java:1638) 在 android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367) 在 android.view.View.draw(View.java:6743) 在 android.widget.FrameLayout.draw(FrameLayout.java:352) 在 android.view.ViewGroup.drawChild(ViewGroup.java:1640) 在 android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367) 在 android.view.View.draw(View.java:6743) 在 android.widget.FrameLayout.draw(FrameLayout.java:352) 在 com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:1847) 在 android.view.ViewRoot.draw(ViewRoot.java:1407) 在 android.view.ViewRoot.performTraversals(ViewRoot.java:1163) 在 android.view.ViewRoot.handleMessage(ViewRoot.java:1727) 在 android.os.Handler.dispatchMessage(Handler.java:99) 在 android.os.Looper.loop(Looper.java:123) 在 android.app.ActivityThread.main(ActivityThread.java:4627) 在 java.lang.reflect.Method.invokeNative(本机方法) 在 java.lang.reflect.Method.invoke(Method.java:521) 在 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:878) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636) 在dalvik.system.NativeStart.main(本机方法)
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.AsyncTask;
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)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
为什么要移除并更换覆盖层?
为什么不保留覆盖层并更改其标记呢?只需在
ItemizedOverlay
上再次调用populate()
,它就会全部调用size()
和getItem()
再次。请务必返回新的正确数据。您的错误肯定感觉与添加和删除覆盖层有关。事实上,我以为您可能在
doInBackground()
中执行此操作,但事实并非如此。因此,我认为从线程的角度来看你所做的事情是安全的,从处理的角度来看只是过度杀伤力。Why are you removing and replacing the overlay?
Why not leave the overlay alone, and change its marker? Just call
populate()
again on theItemizedOverlay
and it will callsize()
andgetItem()
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.