Android API 7 (2.1), MapView, MultiThreading, ConcurrentModificationException - android

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.

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.

Related

Refresh many Google Map markers position, without refresh the whole map in Android

I'm currently developing an Android app that have to receive GPS positions from other devices every minutes, and display them on a map.
I'm using the GoogleMaps Api V2,
What i'd like to do, is to refresh the position marker very time a new position is received. (I don't want to refresh the whole map)
For the moment, I've added a button in the menu that enables me to refresh the entire map.
To detail to structure, I have a service that run MQTT, and every time a position is received, I add it into an Hashmap, that represents my map data source.
This HashMap is a Singleton that extends Observable.
Moreover, my fragment that display the my implements Observer.
Code from my Fragment that implements Observer
public void update(Observable observable, final Object object)
{
if (observable instanceof ListPositions && object != null)
{
Position p = (Position) object;
LatLng position = new LatLng(p.getLatitude(), p.getLongitude());
// Where i'd like to move the markers
}
else
{
// Where i'd like to remove the marker from the map
}
}
Code From my Singleton List of position
public class ListPositions extends Observable{
private HashMap<String,Position> mapPosition;
private ListPositions()
{
mapPosition = new HashMap<String, Position>();
VehicleMapFragment mapFragmentObserver = new VehicleMapFragment();
this.addObserver(mapFragmentObserver);
}
private static ListPositions INSTANCE = null;
public static synchronized ListPositions getInstance()
{
if (INSTANCE == null)
{ INSTANCE = new ListPositions();
}
return INSTANCE;
}
public int getNumberOfPosition()
{
return mapPosition.size();
}
public void addPosition(String key, Position p){
mapPosition.put(key,p);
setChanged();
notifyObservers(p);
}
public void removePosition(String key){
mapPosition.remove(key);
setChanged();
notifyObservers();
}
Code From myService that runs MQTT
public void onPositionMessageReceived(MqttMessage message, String source)
{
Gson gson = new Gson();
String s = gson.toJson(message.toString());
String jsonPosition = gson.toJson(message.toString());
jsonPosition = formatMessage(jsonPosition);
Position p = gson.fromJson(jsonPosition, Position.class);
ListPositions.getInstance().addPosition(source, p);
}
Can someone know how to move each markers individually without refreshing the whole map, in my update function from my Observer Fragment?
May I use a Handler to update the Map, from an other thread to modify the Main UI Thread ?
Many thanks
EDIT :
Because the first methode given by AniV didn't work for me, I've tried with an Asyntask that runs when my Observer get a notification from the Observable List.
Code from the Observer Fragment :
public void update(Observable observable, final Object object)
{
if (observable instanceof ListPositions && object != null)
{
Position p = (Position) object;
position = new LatLng(p.getLatitude(), p.getLongitude());
options = new MarkerOptions().position(position).title("TEST").snippet("TEST");
PositionUpdate positionUpdaterTask = new PositionUpdate(myGoogleMap, options, position);
positionUpdaterTask.execute();
}
}
Code from the AsyncTask :
public class PositionUpdate extends AsyncTask<Void, Void, Void>{
private GoogleMap myGoogleMap;
private MarkerOptions options;
private LatLng positionToAdd;
public PositionUpdate(GoogleMap googleMap, MarkerOptions options, LatLng position)
{
this.myGoogleMap = googleMap;
this.options = options;
this.positionToAdd = position;
}
#Override
protected Void doInBackground(Void...voids)
{
return null;
}
#Override
protected void onPostExecute(Void aVoid)
{
if (myGoogleMap != null)
{
myGoogleMap.addMarker(options.position(positionToAdd));
Log.i(ConstElisa.LOG_ELISA, "MARKER ADDED");
}
else
{
Log.e(ConstElisa.LOG_ELISA, "ERROR ADDING THE MARKER");
}
super.onPostExecute(aVoid);
}
However, in this case, myGoogleMap variable is always null, so the marker is never added to the Google Map.
Does someone have an idea why this variable is null ?
I finally succeeded in doing that thing by using the AsyncTask.
In my EDIT, I said that I had some trouble with the null instance of my Google Maps object.
This was caused by my Singleton Observable. Indeed, in the constructor, I used
VehicleMapFragment mapFragmentObserver = new VehicleMapFragment();
this.addObserver(mapFragmentObserver);
This code sample recreated another instance of my Fragment, and that the reason why ii had Null objects.
To correct this problem, I simply used :
ListPositions.getInstance().addObserver(this);
in my Fragment Observer.
So if you want to update a marker position without refreshing the whole map,
you can use the Observer/Observable Pattern, and use an Asynctask to update the marker position.
You have two options here:
Either update the marker position programatically using setPosition() method.
Marker marker = googleMap.addMarker(new MarkerOptions().position(entry.getValue()).title(entry.getKey()));
Use this object to change its position:
marker.setPosition(new LatLng(5, 5));
OR as you said, Make use of Handlers:
Handler locationHandler;
final static long REFRESH = 10 * 1000;
final static int SUBJECT = 0;
private GoogleMap mMap;
private Marker myMarker = null;
and onCreate()
locationHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == SUBJECT) {
updateMarker();
this.sendEmptyMessageDelayed(SUBJECT, REFRESH);
}
}
};
Handler option is better in cases where you have to update the position at a specific time interval.
I have something like this. I receive a list of devices every 5 minutes to update locations of the map.
What do you think about this
private HashMap<String, Marker> mMarkers = new HashMap<>();
private void drawDevicesOnMap() {
if (isMapReady) {
for (Device device : mDeviceList) {
List<com.model.Location> locationList = device.getLocations();
if (locationList != null && !locationList.isEmpty()) {
if (mMarkers.containsKey(device.getId())) { //update
Marker m = mMarkers.get(device.getId());
m.setPosition(device.getLocations().get(0).getCoordinates().getLatLng());
mMarkers.put(device.getId(), m);
} else { //add
MarkerOptions markerOptions = new MarkerOptions();
markerOptions.position(locationList.get(0).getCoordinates().getLatLng());
markerOptions.icon(BitmapDescriptorFactory.defaultMarker(device.getHue()));
markerOptions.title(device.getNickname());
Marker marker = mMap.addMarker(markerOptions);
mMarkers.put(device.getId(), marker);
}
}
}
}
}
if the marker for a device with x id is found in the HashMap, you update its location.

MapView v2 snapshot() creates an unfinished Bitmap

I'm using the new snapshot() method on the GoogleMap object, and in my SnapshotReadyCallback I'm swapping out the MapView in my layout for an ImageView with the Bitmap passed to me from the callback. The unfortunate thing about this is, even though I'm getting a Bitmap it appears that the snapshot was taken before the map finished rendering.
I should also add that this View is being created programmatically, and isn't added to the Activity right away (it's actually being placed in a ScrollView with a bunch of other Views).
The gist of my code (for brevity's sake) is:
public class MyCustomView extends LinearLayout {
private FrameLayout mapContainer;
#Override public void onAttachedToWindow() {
super.onAttachedToWindow();
// Setup GoogleMapOptions ...
mapView = new MapView(getContext(), googleMapOptions);
mapContainer.addView(mapView);
mapView.onCreate(null);
mapView.onResume();
// Setup CameraUpdate ...
mapView.getViewTreeObserver().addOnGlobalLayoutListener(new MapLayoutListener(mapView, cameraUpdate));
}
private class MapLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
private final WeakReference<MapView> mapViewReference;
private final CameraUpdate cameraUpdate;
public MapLayoutListener(MapView mapView, CameraUpdate cameraUpdate) {
mapViewReference = new WeakReference<MapView>(mapView);
this.cameraUpdate = cameraUpdate
}
#Override public void onGlobalLayout() {
MapView mapView = mapViewReference.get();
if (mapView == null) {
return;
}
mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
GoogleMap map = mapView.getMap();
if (map == null) {
return;
}
try {
map.moveCamera(cameraUpdate);
} catch (IllegalStateException e) {
e.printStackTrace();
}
map.snapshot(new GoogleMap.SnapshotReadyCallback() {
#Override public void onSnapshotReady(Bitmap bitmap) {
MapView mapView = mapViewReference.get();
if (mapView == null) {
return;
}
mapContainer.removeAllViews();
ImageView imageView = new ImageView(getContext());
imageView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
imageView.setImageBitmap(bitmap);
mapContainer.addView(imageView);
}
});
}
}
}
Sometimes an IllegalStateException is thrown when map.moveCamera(cameraUpdate) is called, complaining that the View has not been laid out yet, but I'm calling it inside the OnGlobalLayoutListener which I thought was the proper way to get a callback when your View is all done rendering/laying out etc. This exception makes me think otherwise.
So what's the proper way to do this? The docs are scant on details, and the View "lifecycle" isn't very clear. What I need is a callback when the View is ready for me to call snapshot()

android line intersect with itself

Hello friends,
I draw a line using canvas.drawPath(mPath, mPaint); method.
but having some problem, i want to get whether line intersect with itself or not.
so please help.
thanks in advance
For better performance and UI response create a separate Thread with a Handler and Looper. Also use a HashSet to store the path which tells you of an intersection when you add a position since add() returns a boolean.
Here is some code which should hopefully give better performance.
public class MainActivity extends Activity {
private Handler mHandler;
private String position, lastPosition;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
#Override
protected void onResume() {
super.onResume();
Tracker t = new Tracker();
t.start();
}
#Override
protected void onPause() {
if (mHandler != null)
mHandler.getLooper().quit();
super.onPause();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_MOVE:
position = event.getX()+","+event.getY();
if (position.equals(lastPosition)) break;
if (mHandler != null) {
Message msg = Message.obtain();
msg.obj = position;
mHandler.sendMessage(msg);
}
lastPosition = position;
break;
}
return true;
}
private class Tracker extends Thread {
HashSet<String> path = new HashSet<String>();
#Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
String position = String.valueOf(msg.obj);
if (!path.add(position))
Log.w("Intersection", position);//Handle the intersection
}
};
Looper.loop();
}
}
}
save the coordinates from events
#Override
public boolean onTouchEvent(MotionEvent event) {
event.getX();event.getY()`
}
to a Hashmap and compare
I think you're best bet would be to:
1) Keep one path representing what's currently on the canvas, and every time you use canvas.drawPath(nextPath), also add it to your global path and do something like globalPath.addPath(nextPath)
2) Take a look at this post: Collision detection with bitmaps on SurfaceView's canvas in Android. It seems that you should be able to compare the globalPath to the nextPath and tell if they ever collide.
3) If instead you wanted to just know if a single path collides with itself and don't care about adding new paths or anything, we'd need more information on how you're drawing this path. With lines, arcs, circles??

Android: Running thread preventing animation from starting

I currently have an Android activity which manages some locally-stored RSS feeds. In this activity, these feeds are updated in their own thread via a private class. I'm also trying to include an "updating" icon that rotates with a RotateAnimation while this thread is running.
The animation works by itself, but doesn't work while the thread is running despite the log entries stating the code is being executed. I suspect this is due to the thread not being entirely safe, and taking up most of the CPU time. However I'd just like to know if there's a better way of achieving this.
The function updateAllFeeds() is called from a button press. Here's the relevant code:
/**
* Gets the animation properties for the rotation
*/
protected RotateAnimation getRotateAnimation() {
// Now animate it
Log.d("RSS Alarm", "Performing animation");
RotateAnimation anim = new RotateAnimation(359f, 0f, 16f, 21f);
anim.setInterpolator(new LinearInterpolator());
anim.setRepeatCount(Animation.INFINITE);
anim.setDuration(700);
return anim;
}
/**
* Animates the refresh icon with a rotate
*/
public void setUpdating() {
btnRefreshAll.startAnimation(getRotateAnimation());
}
/**
* Reverts the refresh icon back to a still image
*/
public void stopUpdating() {
Log.d("RSS Alarm", "Stopping animation");
btnRefreshAll.setAnimation(null);
refreshList();
}
/**
* Updates all RSS feeds in the list
*/
protected void updateAllFeeds() {
setUpdating();
Updater updater = new Updater(channels);
updater.run();
}
/**
* Class to update RSS feeds in a new thread
* #author Michael
*
*/
private class Updater implements Runnable {
// Mode flags
public static final int MODE_ONE = 0;
public static final int MODE_ALL = 1;
// Class vars
Channel channel;
ArrayList<Channel> channelList;
int mode;
/**
* Constructor for singular update
* #param channel
*/
public Updater(Channel channel) {
this.mode = MODE_ONE;
this.channel = channel;
}
/**
* Constructor for updating multiple feeds at once
* #param channelList The list of channels to be updated
*/
public Updater(ArrayList<Channel> channelList) {
this.mode = MODE_ALL;
this.channelList = channelList;
}
/**
* Performs all the good stuff
*/
public void run() {
// Flag for writing problems
boolean write_error = false;
// Check if we have a singular or list
if(this.mode == MODE_ONE) {
// Updating one feed only
int updateStatus = channel.update(getApplicationContext());
// Check for error
if(updateStatus == 2) {
// Error - show dialog
write_error = true;
}
}
else {
// Iterate through all feeds
for(int i = 0; i < this.channelList.size(); i++) {
// Update this item
int updateStatus = channelList.get(i).update(getApplicationContext());
if(updateStatus == 2) {
// Error - show dialog
write_error = true;
}
}
}
// If we have an error, show the dialog
if(write_error) {
runOnUiThread(new Runnable(){
public void run() {
showDialog(ERR_SD_READ_ONLY);
}
});
}
// End updater
stopUpdating();
} // End run()
} // End class Updater
(I know the updateStatus == 2 bit is bad practice, that's one of the next things I plan to tidy up).
Any help is greatly appreciated, many thanks in advance.
Run the updater runnable in a separate thread. Do the following changes.
protected void updateAllFeeds() {
setUpdating();
new Thread( new Updater(channels)).start();
}
Call stopUpdating() in runOnUiThread block.
private class Updater implements Runnable {
.........
.........
.........
public void run() {
.........
.........
// End updater
runOnUiThread(new Runnable(){
public void run() {
stopUpdating();
}
});
} // End run()
} // End class Updater
Move anything that affects the UI off into its own Runnable and then post it with your button
btnRefreshAll.post(new StopUpdating());
I managed to get this working last night using Android's AsyncTask class. It was surprisingly easy to implement, though the downside was I had to write one class for updating an individual feed, and another for updating all feeds. Here's the code for updating all feeds at once:
private class MassUpdater extends AsyncTask<ArrayList<Channel>, Void, Void> {
#Override
protected Void doInBackground(ArrayList<Channel>... channels) {
ArrayList<Channel> channelList = channels[0];
// Flag for writing problems
boolean write_error = false;
// Iterate through all feeds
for(int i = 0; i < channelList.size(); i++) {
// Update this item
int updateStatus = channelList.get(i).update(getApplicationContext());
if(updateStatus == FileHandler.STATUS_WRITE_ERROR) {
// Error - show dialog
write_error = true;
}
}
// If we have an error, show the dialog
if(write_error) {
runOnUiThread(new Runnable(){
public void run() {
showDialog(ERR_SD_READ_ONLY);
}
});
}
return null;
}
protected void onPreExecute() {
btnRefreshAll.setAnimation(getRotateAnimation());
btnRefreshAll.invalidate();
btnRefreshAll.getAnimation().startNow();
}
protected void onPostExecute(Void hello) {
btnRefreshAll.setAnimation(null);
refreshList();
}
}
Thanks for your answers guys. userSeven7s' reply also makes a lot of sense so I may use that as a backup should I run into any problems with AsyncTask.

Why are my overlays on a mapview not shown?

I followed the instructions from the google hellomapview tutorial. I get a working mapview etc. But the two items that are added to the map are not shown. It seems they are there somewhere because tapping at the specified location shows the message that was added to the items.
Edit
Here is my source code. It should be very close to the google tutorial source code.
public class MapOverlay extends ItemizedOverlay<OverlayItem> {
private List<OverlayItem> overlays = new ArrayList<OverlayItem>();
private Context context;
public MapOverlay(Drawable defaultMarker, Context context) {
super(defaultMarker);
overlays = new ArrayList<OverlayItem>();
this.context = context;
}
#Override
protected OverlayItem createItem(int i) {
return overlays.get(i);
}
#Override
public int size() {
return overlays.size();
}
public void addOverlay(OverlayItem overlay) {
overlays.add(overlay);
this.populate();
}
#Override
protected boolean onTap(int index) {
OverlayItem item = overlays.get(index);
AlertDialog.Builder dialog = new AlertDialog.Builder(this.context);
dialog.setTitle(item.getTitle());
dialog.setMessage(item.getSnippet());
dialog.show();
return true;
}
}
public class MapsActivity extends MapActivity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
MapView mapView = (MapView) findViewById(R.id.mapview);
mapView.setBuiltInZoomControls(true);
MapOverlay overlay = new MapOverlay(this.getResources().getDrawable(
R.drawable.androidmarker), this);
overlay.addOverlay(new OverlayItem(new GeoPoint(19240000,-99120000), "Blubb", "See?"));
mapView.getOverlays().add(overlay);
}
#Override
protected boolean isRouteDisplayed() {
return false;
}
}
Is the source code from the google tutorial available somewhere?
The problem is that I forgot to set the bounds of the drawable. It seems that if the mapview doesn't know how to align the image it won't show it at all.
I changed the first line in my constructor from:
super(defaultMarker);
to
super(boundCenterBottom(defaultMarker));
and know its working perfect.
At the same time, I have no idea how to help you directly.
Here are links to various editions of a project that definitely work with overlays, perhaps they will help.

Categories

Resources