A Topo map viewing application for the Android
April 9, 2013
Introduction
In March of 2013 I got serious about writing the topo map viewing application
for my android that I had been talking about for two years.
See details here:
This article discusses bug fixing, feature addition and other "maintenance" activities
subsequent to development.
We keep going back to the Grand Canyon
For no particular reason, my application starts up with the maps centered on top of
Horseshoe Mesa in the Grand Canyon, at the 24K scale. I have been finding that for
reasons I could not explain, the application was continually returning to this
starting location after displaying at a different location. What appears to be
happening is that when the tablet changes orientation, the activity gets restarted
from scratch. Not what I would have expected, but that is how Android does things:
It is possible to screw with the android manifest and specify that you want to be notified of configuration changes, in which case
you must respond to onConfigurationChanges(Configuration), but this is not the best approach.
The real fix is to expect a configuration change to cause the activity to pass through onPause(), onStop(), onDestroy() when a
configuration change happens and to save state via saveInstanceState(Bundle), and then restore it again when the activity starts.
Note that onCreate(Bundle) gets passed Bundle as an argument.
Setting up an onSaveInstanceState(Bundle state) callback and then processing the Bundle (which is null on initial startup)
in the onCreate() callback fixes everything nicely.
Better handling of LruCache
The Google documentation on this is sketchy, but maybe you can piece together the details with some effort.
I have tried adding the sizeOf and entryRemoved callbacks and just stirred up trouble so far, so have
commented them out.
Some things I should do, but don't yet:
- Inform the LruCache about the sizes of the things I am caching.
- Keep the cache in a fragment so it persists across orientation changes.
- Release memory when items get evicted from the cache.
The Xoom has enough memory in "big heap" mode that I can get away with being sloppy, but eventually the map viewer
will get an "out of memory exception" and die if I roam around a lot. Fixing the above would make things better
(and make things more responsive when I fix the orientation change persistence thing).
It looks like there is an entryRemoved() method to LruCache that gets called
when entries get bumped from the cache, but there are tricky issues involving using
this that I don't understand fully yet, so I am avoiding tangling with this.
Another whole angle on all this is to keep the map cache in what in the Android
world is known as a "content provider". This would be a separate activity with its own
memory limits, and had already caught my attention as a way of circumventing the Google
per-application memory limit (which has become less of a pressing need given the 256M
limit I get on the Xoom via the large heap option). Even Google suggests using a
content provider to handle the cache, which somewhat surprised me (to my way of thinking,
a content provider would only make sense if more than one application were sharing
the resource provided).
Android SDK version
I began using some API feature and found eclipse scolding me that it would not be present
in the level 8 android API that I had specified at the minimum I wanted my application to
be compliant with. This is an easily changed setting in the android manifest, and I
bumped it up to 12 to get things on the air. However, this now means that my application
won't run on stone age tablets and phones. Here is a quick breakdown of what these SDK level
numbers mean as far as android releases:
- SDK 8 = Android 2.2 (Froyo)
- SDK 13 = Android 3.2 (Honeycomb)
- SDK 15 = Android 4.0.x (Ice Cream Sandwich)
- SDK 16 = Android 4.1 (Jellybean)
My Xoom is running 4.1.2 (Jellybean), so clearly level 12 is not at all unreasonable.
If I was worried about marketing this application, I might have to think harder about this.
The following link gives some statistics about how many devices out there are running
what version of android:
According to this, about 40 percent of android devices are running Gingerbread, which explains
why the Eclipse default was level 8. I tend to think a lot of these are older phones, which are
not the target for this application anyhow.
Fix the blue bar problem
The problem here is that as we move north or south in latitude, sometimes the width of maplets
changes. And we have been assuming they do not and can just be drawn without scaling onto
a grid determined by the size of the maplet containing the center point. The fix is to
scale each maplet to fill the rectangle in the grid it is expected to fill.
I do this "brute force" by always setting a source and destination rectangle when I call drawBitmap().
There is every chance that drawBitmap() is clever enough to recognize when the rectangles are identical
and to avoid needless computations. I don't know, and maybe should just assume so, but am tempted to
check and make the simpler call to just translate the bitmap when that is all that is needed.
I might be even smarter instead of letting the central maplet define the scaling, to define the
central scaling based on something absolute like degrees per screen pixel. Doing this would have
a couple of advantages. One is that it would make it very easy to implement a variable scale slider
or any kind of intermediate scales by just setting one variable. The other is that it would avoid what
is happening now, namely that the map scale can change and the map (breathe) when the center point
moves across a pixel size discontinuity. One of these is particularly obnoxious moving south of
31.75 north latitude right around the entrance to Madera Canyon. Letting the map set the scale
south of this line makes the map look squeezed - it needs to be stretched. And the scale really
needs to be set so that the X and Y scales match to avoid aspect ratio distortion.
Android exit on error
I would like to do this when I encounter ugly situations, but surprisingly it does not
seem that a proper Android application should ever exit, or that is the party line.
The short answer is that you do this by calling the finish method
in the main activity:
this.finish()
The longer answer drags you into all kinds of peculiar android design
philosopy. People will argue that an application should never decide to
exit or even offer a button to a user to allow them to exit.
All this is well and good (maybe?), but the reason I would like to do this
is because I have detected some kind of fatal error, probably due to a bug
of my own, and would like to exit given some hopeless condition.
I guess I can always divide by zero or keep a null pointer around handy
to reference, but there should be a better way. Perhaps throwing an
exception that I catch in the main activity and call finish()?
Feedback? Questions?
Drop me a line!h
Gtopo / [email protected]