moagrius / TileView

TileView is a subclass of android.view.ViewGroup that asynchronously displays, pans and zooms tile-based images. Plugins are available for features like markers, hotspots, and path drawing.
MIT License
1.46k stars 337 forks source link

Tiles don't always load successfully #4

Closed klattimer closed 11 years ago

klattimer commented 11 years ago

I've adapted the BitmapDecoderAssets to enable it to access files which are stored in an APKExtension package (obb file) the maps we're using are 10000 by 6667 pixels in size and we have 6 of them totalling 300Mb of data. Loading tiles is slow, but with the downsample underneath is passable. However, not all tiles will successfully load the tiles which fail to load are usually towards the bottom and right but are essentially random.

Can you recommend anything which would improve this situation, e.g. adding a timed retry to load tiles?

moagrius commented 11 years ago

Instead of modiyfing BitmapDecoderAssets, implement your own BitmapDecoder, and assign that to the TileView with TileView.setTileDecoder.

LMK if that's not clear or you need an example.

klattimer commented 11 years ago

Yeah I realise that I can create my own BitmapDecoder however for the purposes of making it work exceptionally fast this made perfect sense (we already have a method of accessing inputstreams from the APKExtension package which is universally accessible via a singleton).

I will create my own bitmap decoder and probably call it BitmapDecoderExtensionAssets and will send it to you ;)

I'll also provide a supporting class which can provide the APK extension handling.

klattimer commented 11 years ago

Regardless of how I'm performing the decode, it appears the ability of the TileView to load all the tiles is massively limited by the VM budget which appears to be much too small for what I'm trying to achieve. I've been looking at ways to improve the situation but I'm not able to find anything which is entirely helpful in this situation. Do you have any suggestions for how I might prevent the out of memory errors?

moagrius commented 11 years ago

Some tips for avoiding OOM errors (specifically re: bitmap data)

  1. Use as low-res tiles as possible. This may seem obvious, but find out if your tiles are smaller as jpegs or pngs (you might be surprised), and (if jpg) see how low a quality you can get away with.
  2. Make sure you're using enough tile sets. At least a new one every 50%, but more will help (balance between total size of deliverable, and optimal memory use). This is because as a tile set scales, it's still using all the memory for the tiles, and as it becomes smaller more tiles are loaded, which could be taking up much more memory than is required.
  3. Use smaller tile sizes. Any tile that is showing by at least 1px will be full rendered, so if you have a bunch of 512x512 tiles and 8 of them are on the egdes, you'd save 75% of the memory by using 256x256 tiles.
  4. Double check that your downsamples are small enough. There's no hard-and-fast rule here, but I generally resize the original image to about 500px (as a jpeg) at a quality of 3 or 4. I usually end up with a file size of between 40 and 120KB.

FWIW, I've personally used this on 9 commercial apps, released into the wild, and dozens of apps for testing and debugging, and never get OOMs. I'm not saying it won't happen or you're doing something wrong necessarily, but for what it's worth I never see them.

HTH, LMK how it goes

klattimer commented 11 years ago

Our tiles are 256x256 - and are as small sized as possible (indexed palette PNGs). Essentially the map is unable to load entirely in a lot of cases, throwing OOM errors. In the case of one of our maps we have hundreds of markers which are added to the map (and removed/re-added when zooming and panning) which is likely taking up more memory but that shouldn't be a huge amount.

The downsamples are pretty small, at 469x704 or 1056x704 depending on the orientation of the map. We're mostly seeing this on a galaxy S4 but the S3 is showing the same problem whereas the galaxy nexus isn't. I'm assuming that there's quite some variety between the VM size on these devices.

After spending some time with the code, I'm unable to find out where and how the bitmaps are being recycled which afaik is an important memory saving requirement according to: https://developer.android.com/training/displaying-bitmaps/manage-memory.html

If you could point me to where and how the destruction of tiles in memory is being handled there may be some improvements I can make to it.

The only alternative I have is to reduce the size of the tiles to 128x128 and hope that this will reduce the memory below the VM size just enough, however it seems more like the memory is being leaked as it works fine initially and after a few minutes it becomes almost impossible to avoid an OOM error.

moagrius commented 11 years ago

Bitmaps are not recycled, they are nulled. If you recycle them, subsequent attempts to decode them will throw "bitmap was recycled" errors. The bitmaps are nulled in Tile.destroy https://github.com/moagrius/TileView/blob/master/src/com/qozix/tileview/tiles/Tile.java#L88 Memory management is pretty aggressive, and it's been tested pretty thoroughly by a number of users in the previous widget with the issues you're describing. I think you're issue is elsewhere, but you're free to review and modify. I can't imagine it's leaking entire bitmaps - if it were I'd assume that any of the apps it's been in would have had similar problems.

At the start of every render pass, all tiles that don't have at least 1 pixel on-screen (including viewport padding) are scheduled to be destroyed. When the pass is complete, it re-calculates and runs again.

Assuming you're using tile sets as efficiently as possible, the only other thing I can suggest is to make sure you're cleaning up onPause and onDestroy, but if you're seeing the OOM during uninterrupted (non-onPaused) use, then that wouldn't directly apply. If you're issue is after a Pause event, then you definitely need to manage that manually in the Activity (method are provided but need to be invoked, e.g, TileView.clear)

You might also try disabling tile transitions TileView.setTransitionsEnabled( false );

HTH, GL.

klattimer commented 11 years ago

I've kinda gotten around this issue by adding largeHeap to the options in the manifest. I think the main reason for this occurring is that at the same time as we're loading the tiles, we're decompressing them from the obb file which is going to require the zip decompressor to be loaded too. I'd suggest making users aware of this in the future, if they're using extension packs, it's a good idea to use largeHeap :)

moagrius commented 11 years ago

GTK. If you get a moment and want to write up some general info about using extension packs, I'll add it to the wiki.

moagrius commented 11 years ago

can i close this issue?