I am using android google maps v2 and I am creating map markers and assigning a bitmap. I am getting the bitmap from my assets directory. The reason I get them from assets is that the path I am using to find the 'right' icon is dynamic.
My question is should I be saving the underlying bitmap so that I can reuse it again and again. I read:
http://developer.android.com/training/displaying-bitmaps/manage-memory.html
but frankly a lot of there really big sample project was over my head.
Should I be recreating the bitmap each time I need to pass that to a marker for creation, or should I read each bitmap( up to a limit) into memory and save them for reuse.
Each will be drawn on the map regardless so I am not sure I can reuse anyway.
Example: I have 300 markers on the map, with 20 different possible bitmaps. I.E. about 15% are the same marker icon.
Since bitmap decoding is quite expensive operation I would definitely recommend to go with bitmap caching. Especially in your case when the number of unique bitmaps is much smaller than number of markers on a map. Simple [LruCache][1] - based in-memory cache would work just fine. You just need to be careful about how much memory do you use for your cache.
Here is a good example of how to implement in-memory cache:
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
Follow up
Just thought about this a bit more and you can do even better job here. Instead of caching bitmaps you can cache BitmapDescriptor for every unique bitmap you have. This way you can get some extra performance by avoiding making calls to BitmapDescriptorFactory every time you need to create a marker
UPDATE
Here is what I mean:
LruCache<String, BitmapDescriptor> cache;
private void initCache()
{
//Use 1/8 of available memory
cache = new LruCache<String, BitmapDescriptor>((int)(Runtime.getRuntime().maxMemory() / 1024 / 8));
}
private void addMarker(LatLng position, String assetPath)
{
MarkerOptions opts = new MarkerOptions();
opts.icon(getBitmapDescriptor(assetPath));
opts.position(position);
mMap.addMarker(opts);
}
private BitmapDescriptor getBitmapDescriptor(String path) {
BitmapDescriptor result = cache.get(path);
if (result == null) {
result = BitmapDescriptorFactory.fromAsset(path);
cache.put(path, result);
}
return result;
}
Related
I'm developing a App which display a Google map and a bunch of markers on it. There's a lot of markers so I divided them in smaller groups and display only those, which are in some bounds depending on the current position of the camera.
To do that I'm using the GoogleMap.OnCameraIdleListener. First I remove the listener, do my calculations and drawing and then I restore the listener to the Fragment containing my map:
#Override
public void onCameraIdle() {
mMap.setOnCameraIdleListener(null);
clearMap();
findTheMarkersInBounds();
displayTheMarkers();
mMap.setOnCameraIdleListener(this);
}
This way I only draw the markers I need to display and the performance is way better then having 1000 markers on the map at once. I also draw about the same number of polylines but that's not the point now.
For some strange reasons, after some panning and zooming the maps doesn't respond anymore. Can't zoom it nor pan it. App displays a dialog that it is not responding and I should wait or close the app. No erros are displayed in logcat. I can't exactly tell when this happens. Sometimes after the first pan, sometimes I can move around 2-3 minutes. Same thing happens on the emulator and on the physical device.
Anyone experienced something like this? Thanks!
Or am I approaching this the wrong way? How else should I optimize the map to display about 1000 markers and polylines. (The markers have text on them, so it can't be the same Bitmap and all of the polylines can have different colors and need to be clickable, so I can't combine them into one large polyline)
EDIT. A little more info about my methods:
After all the marker positions are loaded from the internal database, I do a for-loop through all of them and based on their position and I place them to the corresponding region. Its an 2D array of lists.
My whole area is divided to 32x32 smaller rectangular areas. When I'm searching for the markers to display, I determine which region is in view and display only those markers, which are in this area.
This way I don't need to loop over all of the markers.
My methods (very simplified) look like this:
ArrayList<MarkerObject> markersToDisplay = new ArrayList<MarkerObject>();
private void findTheMarkersInBounds() {
markersToDisplay.clear();
LatLngBounds bounds = mMap.getProjection().getVisibleRegion().latLngBounds;
int[] regionCoordinates = getRegionCoordinates(bounds); // i, j coordinates of my regions [0..31][0..31]
markersToDisplay.addAll(subdividedMarkers[regionCoordinates[0]][regionCoordinates[1]]);
}
private void drawMarkers() {
if ((markersToDisplay != null) && (markersToDisplay.size() > 0)) {
for (int i=0; i<markersToDisplay.size(); i++) {
MarkerObject mo = markersToDisplay.get(i);
LatLng position = new LatLng(mo.gpsLat, mo.gpsLon);
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(createMarker(getContext(), mo.title));
GroundOverlay m = mMap.addGroundOverlay(groundOverlayOptions.image(bitmapDescriptor).position(position, 75));
m.setClickable(true);
}
}
}
It is hard to help you without source code of findTheMarkersInBounds() and displayTheMarkers(), but seems, you need different approach to increase performance, for example:
improve your findTheMarkersInBounds() logic if it possible;
runfindTheMarkersInBounds() in separate thread and show not all markers in same time, but one by one (or bunch of 10..20 at one time) during findTheMarkersInBounds() searching;
improve your displayTheMarkers() if it possible, actually may be use custom drawing on canvas (like in this answer) instead of creating thousands Marker objects.
For question updates:
Small improvements (first, because they are used for main):
pass approximately max size of markersToDisplay as constructor parameter:
ArrayList<MarkerObject> markersToDisplay = new ArrayList<MarkerObject>(1000);
Instead for (int i=0; i<markersToDisplay.size(); i++) {
use for (MarkerObject mo: markersToDisplay) {
Do not create LatLng position every time, create it once and store in MarkerObject fields.
Main improvement:
This lines are the source of issues:
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(createMarker(getContext(), mo.title));
GroundOverlay m = mMap.addGroundOverlay(groundOverlayOptions.image(bitmapDescriptor).position(position, 75));
IMHO using Ground Overlays for thousands of markers showing is bad idea. Ground Overlay is for several "user" maps showing over default Google Map (like local plan of Park or Zoo details). Use custom drawing on canvas like on link above. But if you decide to use Ground Overlays - do not recreate them every time: create it once, store references to them in MarkerObject and reuse:
// once when marker created (just example)
mo.overlayOptions = new GroundOverlayOptions()
.image(BitmapDescriptorFactory.fromBitmap(createMarker(getContext(), mo.title)))
.position(mo.position, 75))
.setClickable(true);
...
// in your drawMarkers() - just add:
...
for (MarkerObject mo: markersToDisplay) {
if (mo.overlayOptions == null) {
mo.overlayOptions = createOverlayOptionsForThisMarker();
}
mMap.addGroundOverlay(mo.overlayOptions)
}
But IMHO - get rid of thousands of Ground Overlays at all - use custom drawing on canvas.
After further investigation and communication with the google maps android tech support we came to a solution. There's a bug in the GroundOverlay.setZIndex() method.
All you have to do is to update to the newest API version. The bug is not present anymore in Google Maps SDK v3.1.
At this moment it is in Beta, but the migration is pretty straightforward.
I have an app that reuses images in a recyclerview. I'd like to know the best way to save resources using the same images repeatedly. My knowledge of memory use is none at best.
I have an image that gets scaled depending on its use in the app, but when in the list what is the best way to display the same image. Is the image memory getting reused for either of these or is each image taking of a chunk of memory separately. Is there a better way?
pholder.nodeImage.setImageBitmap(Tools.decodeSampledBitmapFromResource(context.getResources(),R.drawable.folder,50,50));
or
pholder.nodeImage.setImageResource(R.drawable.folder);
Put your images in a cache, you can use the LruCache class
Images are cached during the app lifecycle and discarded when the app exists. You can adjust the cache sized based on the expected number of images to cache, their size, and the device the app is running on.
int cacheSize = 4 * 1024 * 1024; // 4MiB
LruCache bitmapCache = new LruCache(cacheSize) {
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
}
synchronized (cache) {
if (cache.get(key) == null) {
cache.put(key, value);
}
}
I am using the Android BitmapFun sample code to manage bitmaps in my application. I have been experiencing garbled or duplicated images in a ViewPager. I have tracked this down to the following code in ImageCache.java:
/**
* Notify the removed entry that is no longer being cached
*/
#Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
// The removed entry is a recycling drawable, so notify it
// that it has been removed from the memory cache
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
// The removed entry is a standard BitmapDrawable
if (Utils.hasHoneycomb()) {
// We're running on Honeycomb or later, so add the bitmap
// to a SoftRefrence set for possible use with inBitmap later
mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
The bitmap is added to the reusable bitmap list when it is removed from the cache. In this case the bitmap is still in use by a ViewPager view. When a later view is created the bitmap (still in use) is reused and the bitmap appears in two positions in the ViewPager.
A bitmap that is removed from the LruCache isn't necessarily available for reuse. I have disabled the reuse of bitmaps in this code and am no longer having an issue. This problem doesn't occur with lower resolution images because the bitmaps aren't removed from the cache while in the range of the ViewPager's offscreen limit. I don't have an issue with 60 DPI images but see this issue frequently at 160 DPI. I think this would show up in the original BitmapFun sample with higher resolution images.
Anyone else experienced this problem or I am not understanding the issue properly?
Kevin
What I think the problem with the code is in the line
mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
That line adds a bitmap that was removed from LRU cache to a reusable bitmap set to be used for inBitmap re-use. It doesn't check whether it is still being used by an ImageView or not. If you try to re-use a bitmap that is still being used by an ImageView, the underlying bitmap will be replaced with another bitmap making it not valid anymore. My suggestion is to track whether a bitmap is still being used by an ImageView before adding it to the reusable bitmap set. I've created a sample github project for this issue. Tell me what you think with my solution.
I have been using this technique to alter tiles in our game, but I have been noticing quite a large loss in performance when many tiles are changed at once. I am using that technique to animate tiles on the TMX map. I was wondering if anyone else had noticed any performance issues using that method to change tmx tiles? Is there a more efficient way to alter TMX Tiles?
The only other option I can think of, to get around the performance loss, is to attach animating sprites to the TMX layer, but that does not seem like an ideal solution.
Any advice would be appreciated.
Below is the method I was referring to:
mTestTile.setGlobalTileID(mTMXTiledMap, mGloabalIndex);
//After changing the global ID do this
final int TileHeight = mTMXTiledMap.getTileHeight();
final int TileWidth = mTMXTiledMap.getTileWidth();
//See TMXLayer Class line 308 (getSpriteBatchIndex)
lTMXLayer.setIndex(mTestTile.getTileRow() * mTMXTiledMap.getTileColumns() + mTestTile.getTileColumn());
lTMXLayer.drawWithoutChecks(mTestTile.getTextureRegion(), mTestTile.getTileX(), mTestTile.getTileY(), lTileWidth, lTileHeight, Color.WHITE_ABGR_PACKED_FLOAT);
mTMXTiledMap.mTestTMXLayer.submit();[/syntax]
Note: I am using GLES2 Anchor_Center Branch
I am not sure if you can use this, but I needed something similar. I have few objects in my TMX map that I wanted to have two frames (on/off). Instead of using map layer, I used object layer for these. I've added this code to TMXTileset class:
public ITiledTextureRegion getTiledTextureRegionFromGlobalTileID(final int pGlobalTileID, final int pTiles) {
ITextureRegion[] regions = new ITextureRegion[pTiles];
for (int i = 0; i < pTiles; i++) {
regions[i] = getTextureRegionFromGlobalTileID(pGlobalTileID + i);
}
return new TiledTextureRegion(this.mTexture, regions);
}
I put all on/off objects to special object layer (using Tiled editor). Of course, the tileset must be organized so that the on and off frames are in sequence.
Then when I am creating the map, I iterate through these objects and create the tiled sprite. I save it to a special list for later retrieval. Then of course switching from one to another is as easy as using setCurrentTileIndex()
I have some dynamic tile content to display on top of a map (specifically, weather images -- radar, satellite, temperatures, etc.). I'm using Google Maps API for Android v2.
The problem I'm having is that apparently the only way to update the tile images (i.e. when new data arrives, or when the frame advances in a time lapse animation) is to call TileOverlay.clearImageCache. Unfortunately, when I do that, the tile overlay flickers off for a moment. This is because clearImageCache immediately removes the existing tile images from the display, but there's a delay before it can decode and display new tile images.
I'm using a custom TileProvider that caches the tile images, rather than fetching them from the server each time. But even when it's only feeding cached tiles (i.e. there's no significant delay imposed by my TileProvider.getTile implementation), there's still enough of a delay in the process that the user can see a flicker.
Does anyone know of a way to avoid this flicker? Is there some way I can double-buffer the tile overlay? I tried to double-buffer it with two TileOverlays attached to the map, one of which is invisible. But the invisible TileOverlay does not start fetching any tiles from my TileProvider -- even after I call clearImageCache.
Would it be possible to load the upcoming tile image however have the visibility set to false? Tile.setVisible(false);
Then when you want to change (after the upcoming tile has loaded), set the upcoming tile to be visible and the current tile to be invisible?
CurrentTile.setVisible(false);
NewTile.setVisible(true);
This way the change all occurs within the same render frame, and there is no delay waiting for cached images to load.
A fairly good solution would be to separate tile downloading and caching from TileProvider. This way you can fully control when they are downloaded and only replace byte[] references after downloading all.
This might be a bit more complex, because you have to take care of the current visible region and zoom not to download them all, but only those that will be visible.
Edit
Tested with the following code:
try {
InputStream is = getAssets().open("tile1.jpg");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = is.read();
while (b != -1) {
baos.write(b);
b = is.read();
}
byte[] array = baos.toByteArray();
cache = new Tile(256, 256, array);
is = getAssets().open("tile2.jpg");
baos = new ByteArrayOutputStream();
b = is.read();
while (b != -1) {
baos.write(b);
b = is.read();
}
array = baos.toByteArray();
nextCache = new Tile(256, 256, array);
} catch (IOException ex) {
Log.e("tag", "error reading tiles", ex);
}
tileOverlay = map.addTileOverlay(new TileOverlayOptions().tileProvider(new TileProvider() {
#Override
public Tile getTile(int x, int y, int zoom) {
return cache;
}
}));
and somewhere later:
Tile temp = cache;
cache = nextCache;
nextCache = temp;
tileOverlay.clearTileCache();
"Fastest" possible code still fails.
If you cannot switch to GroundOverlay or Markers, another idea is trying to use third party map tiles, your current weather tiles above and next tiles below, so they can load and switch them (using zOrder) after few seconds.
The solution I found for this is to make the layer have a transparency of 1. This makes them hidden but still requesting tiles. You can then switch which layers have a transparency of 1 and 0.
There is still possibility of some flicker due to having a render call in between the changing of the transparency of the two layers. There is no way to make this an atomic operation.
Also found that there's not a great way to know when the layer has fully loaded the tile. Even after you return the tile from the TileProvider, Google Maps has to do some processing on it, so you need some delay before switching tiles.
The solution that worked for me (I tried refreshing tiles every second):
// Adding "invisible" overlay
val newTileOverlay = mMap?.addTileOverlay(
TileOverlayOptions()
.tileProvider(getTileProvider()).transparency(1f).visible(true)
)
mTileOverlay?.transparency = 0.5f // making previous overlay visible
mOldTileOverlay?.remove() // removing previously displayed visible overlay
mOldTileOverlay = mTileOverlay
mTileOverlay = newTileOverlay
So we have two layers at a time (one visible and one invisible). I'm not sure how it influences the performance.