Issue
First of all, i don't know where, i don't know why, i get ConcurrentModificationException. My Logcat is out of order (or just i can't use it) but never shows any information about exceptions ( i read lots of article about it, and nothing helped, maybe can't debug my phone correctly )
Secondly sorry about my confused english, i try to formulate as clearly as i can, codes could help, please help me
So, The problem is the next:
i use Mapview and 5 custom CustomItemizedOverlay (Source no. 1) on it. From MapView i start some (1, 2, max 5) threads to webservice (Source no. 2) and after i get results back (it's List) i draw them into mapoverlay (source no. 3)
so MapView ( implements 5 ResponseHandlerInterfaces ) sends requests to webservice through myActions (extends Thread) and when actions gets responses, they call responseHandler.reportResponseList(List list) methods. (MapView get back the control right here)
and all of it causes ConcurrentModificationException sometimes
(rarely ArrayIndexOutOfBoundsException)
i have got Options Activity to set required lists and i also have got Refresh button, to get lists. let me lead you through one example.
I just opened MapView, it's empty. I need only 1 kind of objects. I tap refresh, after network communication, i get markers on my mapview. cool, it's working. Now i'm going to Options, and i set more objects to request. Use Refresh again at mapview, and sometimes i get all kinds of objects, sometimes i get ConcurrentModificationException.
Source No. 1
public class CustomItemizedOverlay<T> extends ItemizedOverlay<T>{
private Context mContext;
private Object lock = new Object();
private CopyOnWriteArrayList<T> overlays = new CopyOnWriteArrayList<T>();
public CustomItemizedOverlay(Drawable marker, Context context) {
super(boundCenterBottom(marker));
this.mContext = context;
populate();
}
@Override
protected boolean onTap(int index){
// doesn't matter
}
public void clear(){
synchronized (lock) {
overlays.clear();
}
}
public void addOverlay(T overlay){
synchronized (lock) {
overlays.add(overlay);
setLastFocusedIndex(-1);
populate();
}
}
public void removeOverlay(int selected){
synchronized (lock) {
overlays.remove(selected);
populate();
setLastFocusedIndex(-1);
}
}
@Override
protected T createItem(int i) {
synchronized (lock) {
return overlays.get(i);
}
}
@Override
public int size() {
synchronized (lock) {
return overlays.size();
}
}
public void setLock(Object o){
this.lock = o;
}
}
Source No. 2
MapView:
public class MyMap extends MapActivity implements LocationListener, RestResPonseHandler { // there are 5 type of responsehandlers, one for each overlay
private MapView mapView;
private MyLocationOverlay myLocationOverlay;
private Object lock = new Object();
private CustomItemizedOverlay<CustomOverlayItem<MyObject1>> my1Overlay;
private CustomItemizedOverlay<CustomOverlayItem<MyObject2>> my2Overlay;
private CustomItemizedOverlay<CustomOverlayItem<MyObject3>> my3Overlay;
private CustomItemizedOverlay<CustomOverlayItem<MyObject4>> my4Overlay;
private CustomItemizedOverlay<CustomOverlayItem<MyObject5>> my5Overlay;
public void getObject1List(){ // there are 5 getList methods
new RestAction(this).start(); // 'this' is the object which implements required RestResponseHandler interface. in every case it will be 'this'. MyMap implements all kind of required RestResponseHandler interfaces
}
Source No. 3 (non main thread) // This is pattern for each 'CustomItemizedOverlay filling method'. After actions reports results (list of objects), mapview fills actual overlay with OverlayItems
@Override
public void reportResponseList(List<MyObject1> objects) {
if (my1Overlay == null){
List<Overlay> mapOverlays = mapView.getOverlays();
Drawable marker = this.getResources().getDrawable(R.drawable.icon);
my1Overlay = new CustomItemizedOverlay<CustomOverlayItem<MyObject1>>(marker, this);
my1Overlay.setLock(lock); // MyMap has lock object, look at source 2 (also CustomItemizedOverlay (source 1) )
mapOverlays.add(my1Overlay);
} else {
my1Overlay.clear();
}
synchronized (lock) {
for(int i=0;i<objects.size();++i){
MyObject1 object = objects.get(i);
CustomOverlayItem<MyObject1> item = new CustomOverlayItem<CustomBuilding>(object.getPositionId(), object);
my1Overlay.addOverlay(item);
}
refreshView();
}
}
Where refreshView posts runnable to main thread to update mapView.
public void refreshView(){
new Thread(new Runnable(){
@Override
public void run(){
mapView.post(new Runnable(){
@Override
public void run(){
mapView.invalidate();
}
});
}
}).start();
}
The Solution: After CommonsWare's answer, i modified my source to :
@Override
public void reportResponseList(final List<MyObject1> objects) {
if (my1Overlay == null){
List<Overlay> mapOverlays = mapView.getOverlays();
Drawable marker = this.getResources().getDrawable(R.drawable.icon);
my1Overlay = new CustomItemizedOverlay<CustomOverlayItem<MyObject1>>(marker, this);
my1Overlay.setLock(lock);
mapOverlays.add(my1Overlay);
} else {
runOnUiThread(new Runnable(){
@Override
public void run(){
my1Overlay.clear();
}
});
}
runOnUiThread(new Runnable(){
@Override
public void run(){
for(int i=0;i<objects.size();++i){
MyObject1 object = objects.get(i);
CustomOverlayItem<MyObject1> item = new CustomOverlayItem<MyObject1>(object.getPositionId(), object);
my1Overlay.addOverlay(item);
}
refreshView();
}
});
}
and now at this moment it seems to work. i don't know how pretty is it, but seems to work. (maybe mapOverlays.add() method should be on main thread too) Thank you very much.
Solution
If my1Overlay is already part of the MapView by the time reportResponseList() is called, you should not be modifying it on a background thread. MapView will be using that Overlay. Instead, create a new Overlay in the background thread, the swap overlays (remove the old, add the new) on the main application thread.
Answered By - CommonsWare
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.