diff options
author | David Christie <dnchrist@google.com> | 2015-10-27 16:55:58 -0700 |
---|---|---|
committer | David Christie <dnchrist@google.com> | 2015-10-27 18:09:53 -0700 |
commit | 923b2602583021b97ac7a8dcbca8395e309b938e (patch) | |
tree | 2dbbb782370dbe0f6dbab5007831f634756b1053 | |
parent | ab253faa47de946b311522925c9875d2cccaaff5 (diff) |
Make Location objects take less memory.
-Use bitmask for has*** methods.
-Use ThreadLocal for caching intermediate computations
rather than preallocating memory in every Location
Change-Id: If2fa17bfd59511ec0b809f4b7d7cd8028360c340
-rw-r--r-- | location/java/android/location/Location.java | 215 | ||||
-rw-r--r-- | location/tests/locationtests/src/android/location/LocationTest.java | 35 |
2 files changed, 150 insertions, 100 deletions
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java index bca9be673f5d..4d0d1bd64b0e 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -78,32 +78,51 @@ public class Location implements Parcelable { */ public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation"; + /** + * Bit mask for mFieldsMask indicating the presence of mAltitude. + */ + private static final byte HAS_ALTITUDE_MASK = 1; + /** + * Bit mask for mFieldsMask indicating the presence of mSpeed. + */ + private static final byte HAS_SPEED_MASK = 2; + /** + * Bit mask for mFieldsMask indicating the presence of mBearing. + */ + private static final byte HAS_BEARING_MASK = 4; + /** + * Bit mask for mFieldsMask indicating the presence of mAccuracy. + */ + private static final byte HAS_ACCURACY_MASK = 8; + /** + * Bit mask for mFieldsMask indicating location is from a mock provider. + */ + private static final byte HAS_MOCK_PROVIDER_MASK = 16; + + // Cached data to make bearing/distance computations more efficient for the case + // where distanceTo and bearingTo are called in sequence. Assume this typically happens + // on the same thread for caching purposes. + private static ThreadLocal<BearingDistanceCache> sBearingDistanceCache + = new ThreadLocal<BearingDistanceCache>() { + @Override + protected BearingDistanceCache initialValue() { + return new BearingDistanceCache(); + } + }; + private String mProvider; private long mTime = 0; private long mElapsedRealtimeNanos = 0; private double mLatitude = 0.0; private double mLongitude = 0.0; - private boolean mHasAltitude = false; private double mAltitude = 0.0f; - private boolean mHasSpeed = false; private float mSpeed = 0.0f; - private boolean mHasBearing = false; private float mBearing = 0.0f; - private boolean mHasAccuracy = false; private float mAccuracy = 0.0f; private Bundle mExtras = null; - private boolean mIsFromMockProvider = false; - // Cache the inputs and outputs of computeDistanceAndBearing - // so calls to distanceTo() and bearingTo() can share work - private double mLat1 = 0.0; - private double mLon1 = 0.0; - private double mLat2 = 0.0; - private double mLon2 = 0.0; - private float mDistance = 0.0f; - private float mInitialBearing = 0.0f; - // Scratchpad - private final float[] mResults = new float[2]; + // A bitmask of fields present in this object (see HAS_* constants defined above). + private byte mFieldsMask = 0; /** * Construct a new Location with a named provider. @@ -131,18 +150,14 @@ public class Location implements Parcelable { mProvider = l.mProvider; mTime = l.mTime; mElapsedRealtimeNanos = l.mElapsedRealtimeNanos; + mFieldsMask = l.mFieldsMask; mLatitude = l.mLatitude; mLongitude = l.mLongitude; - mHasAltitude = l.mHasAltitude; mAltitude = l.mAltitude; - mHasSpeed = l.mHasSpeed; mSpeed = l.mSpeed; - mHasBearing = l.mHasBearing; mBearing = l.mBearing; - mHasAccuracy = l.mHasAccuracy; mAccuracy = l.mAccuracy; mExtras = (l.mExtras == null) ? null : new Bundle(l.mExtras); - mIsFromMockProvider = l.mIsFromMockProvider; } /** @@ -152,18 +167,14 @@ public class Location implements Parcelable { mProvider = null; mTime = 0; mElapsedRealtimeNanos = 0; + mFieldsMask = 0; mLatitude = 0; mLongitude = 0; - mHasAltitude = false; mAltitude = 0; - mHasSpeed = false; mSpeed = 0; - mHasBearing = false; mBearing = 0; - mHasAccuracy = false; mAccuracy = 0; mExtras = null; - mIsFromMockProvider = false; } /** @@ -297,7 +308,7 @@ public class Location implements Parcelable { } private static void computeDistanceAndBearing(double lat1, double lon1, - double lat2, double lon2, float[] results) { + double lat2, double lon2, BearingDistanceCache results) { // Based on http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf // using the "Inverse Formula" (section 4) @@ -382,19 +393,19 @@ public class Location implements Parcelable { } float distance = (float) (b * A * (sigma - deltaSigma)); - results[0] = distance; - if (results.length > 1) { - float initialBearing = (float) Math.atan2(cosU2 * sinLambda, - cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); - initialBearing *= 180.0 / Math.PI; - results[1] = initialBearing; - if (results.length > 2) { - float finalBearing = (float) Math.atan2(cosU1 * sinLambda, - -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda); - finalBearing *= 180.0 / Math.PI; - results[2] = finalBearing; - } - } + results.mDistance = distance; + float initialBearing = (float) Math.atan2(cosU2 * sinLambda, + cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); + initialBearing *= 180.0 / Math.PI; + results.mInitialBearing = initialBearing; + float finalBearing = (float) Math.atan2(cosU1 * sinLambda, + -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda); + finalBearing *= 180.0 / Math.PI; + results.mFinalBearing = finalBearing; + results.mLat1 = lat1; + results.mLat2 = lat2; + results.mLon1 = lon1; + results.mLon2 = lon2; } /** @@ -420,8 +431,16 @@ public class Location implements Parcelable { if (results == null || results.length < 1) { throw new IllegalArgumentException("results is null or has length < 1"); } + BearingDistanceCache cache = sBearingDistanceCache.get(); computeDistanceAndBearing(startLatitude, startLongitude, - endLatitude, endLongitude, results); + endLatitude, endLongitude, cache); + results[0] = cache.mDistance; + if (results.length > 1) { + results[1] = cache.mInitialBearing; + if (results.length > 2) { + results[2] = cache.mFinalBearing; + } + } } /** @@ -433,21 +452,14 @@ public class Location implements Parcelable { * @return the approximate distance in meters */ public float distanceTo(Location dest) { + BearingDistanceCache cache = sBearingDistanceCache.get(); // See if we already have the result - synchronized (mResults) { - if (mLatitude != mLat1 || mLongitude != mLon1 || - dest.mLatitude != mLat2 || dest.mLongitude != mLon2) { - computeDistanceAndBearing(mLatitude, mLongitude, - dest.mLatitude, dest.mLongitude, mResults); - mLat1 = mLatitude; - mLon1 = mLongitude; - mLat2 = dest.mLatitude; - mLon2 = dest.mLongitude; - mDistance = mResults[0]; - mInitialBearing = mResults[1]; - } - return mDistance; + if (mLatitude != cache.mLat1 || mLongitude != cache.mLon1 || + dest.mLatitude != cache.mLat2 || dest.mLongitude != cache.mLon2) { + computeDistanceAndBearing(mLatitude, mLongitude, + dest.mLatitude, dest.mLongitude, cache); } + return cache.mDistance; } /** @@ -461,21 +473,14 @@ public class Location implements Parcelable { * @return the initial bearing in degrees */ public float bearingTo(Location dest) { - synchronized (mResults) { - // See if we already have the result - if (mLatitude != mLat1 || mLongitude != mLon1 || - dest.mLatitude != mLat2 || dest.mLongitude != mLon2) { - computeDistanceAndBearing(mLatitude, mLongitude, - dest.mLatitude, dest.mLongitude, mResults); - mLat1 = mLatitude; - mLon1 = mLongitude; - mLat2 = dest.mLatitude; - mLon2 = dest.mLongitude; - mDistance = mResults[0]; - mInitialBearing = mResults[1]; - } - return mInitialBearing; + BearingDistanceCache cache = sBearingDistanceCache.get(); + // See if we already have the result + if (mLatitude != cache.mLat1 || mLongitude != cache.mLon1 || + dest.mLatitude != cache.mLat2 || dest.mLongitude != cache.mLon2) { + computeDistanceAndBearing(mLatitude, mLongitude, + dest.mLatitude, dest.mLongitude, cache); } + return cache.mInitialBearing; } /** @@ -591,7 +596,7 @@ public class Location implements Parcelable { * True if this location has an altitude. */ public boolean hasAltitude() { - return mHasAltitude; + return (mFieldsMask & HAS_ALTITUDE_MASK) != 0; } /** @@ -611,7 +616,7 @@ public class Location implements Parcelable { */ public void setAltitude(double altitude) { mAltitude = altitude; - mHasAltitude = true; + mFieldsMask |= HAS_ALTITUDE_MASK; } /** @@ -622,14 +627,14 @@ public class Location implements Parcelable { */ public void removeAltitude() { mAltitude = 0.0f; - mHasAltitude = false; + mFieldsMask &= ~HAS_ALTITUDE_MASK; } /** * True if this location has a speed. */ public boolean hasSpeed() { - return mHasSpeed; + return (mFieldsMask & HAS_SPEED_MASK) != 0; } /** @@ -648,7 +653,7 @@ public class Location implements Parcelable { */ public void setSpeed(float speed) { mSpeed = speed; - mHasSpeed = true; + mFieldsMask |= HAS_SPEED_MASK; } /** @@ -659,14 +664,14 @@ public class Location implements Parcelable { */ public void removeSpeed() { mSpeed = 0.0f; - mHasSpeed = false; + mFieldsMask &= ~HAS_SPEED_MASK; } /** * True if this location has a bearing. */ public boolean hasBearing() { - return mHasBearing; + return (mFieldsMask & HAS_BEARING_MASK) != 0; } /** @@ -698,7 +703,7 @@ public class Location implements Parcelable { bearing -= 360.0f; } mBearing = bearing; - mHasBearing = true; + mFieldsMask |= HAS_BEARING_MASK; } /** @@ -709,7 +714,7 @@ public class Location implements Parcelable { */ public void removeBearing() { mBearing = 0.0f; - mHasBearing = false; + mFieldsMask &= ~HAS_BEARING_MASK; } /** @@ -719,7 +724,7 @@ public class Location implements Parcelable { * accuracy. */ public boolean hasAccuracy() { - return mHasAccuracy; + return (mFieldsMask & HAS_ACCURACY_MASK) != 0; } /** @@ -757,7 +762,7 @@ public class Location implements Parcelable { */ public void setAccuracy(float accuracy) { mAccuracy = accuracy; - mHasAccuracy = true; + mFieldsMask |= HAS_ACCURACY_MASK; } /** @@ -768,7 +773,7 @@ public class Location implements Parcelable { */ public void removeAccuracy() { mAccuracy = 0.0f; - mHasAccuracy = false; + mFieldsMask &= ~HAS_ACCURACY_MASK; } /** @@ -786,7 +791,7 @@ public class Location implements Parcelable { @SystemApi public boolean isComplete() { if (mProvider == null) return false; - if (!mHasAccuracy) return false; + if (!hasAccuracy()) return false; if (mTime == 0) return false; if (mElapsedRealtimeNanos == 0) return false; return true; @@ -804,8 +809,8 @@ public class Location implements Parcelable { @SystemApi public void makeComplete() { if (mProvider == null) mProvider = "?"; - if (!mHasAccuracy) { - mHasAccuracy = true; + if (!hasAccuracy()) { + mFieldsMask |= HAS_ACCURACY_MASK; mAccuracy = 100.0f; } if (mTime == 0) mTime = System.currentTimeMillis(); @@ -844,7 +849,7 @@ public class Location implements Parcelable { s.append("Location["); s.append(mProvider); s.append(String.format(" %.6f,%.6f", mLatitude, mLongitude)); - if (mHasAccuracy) s.append(String.format(" acc=%.0f", mAccuracy)); + if (hasAccuracy()) s.append(String.format(" acc=%.0f", mAccuracy)); else s.append(" acc=???"); if (mTime == 0) { s.append(" t=?!?"); @@ -855,10 +860,10 @@ public class Location implements Parcelable { s.append(" et="); TimeUtils.formatDuration(mElapsedRealtimeNanos / 1000000L, s); } - if (mHasAltitude) s.append(" alt=").append(mAltitude); - if (mHasSpeed) s.append(" vel=").append(mSpeed); - if (mHasBearing) s.append(" bear=").append(mBearing); - if (mIsFromMockProvider) s.append(" mock"); + if (hasAltitude()) s.append(" alt=").append(mAltitude); + if (hasSpeed()) s.append(" vel=").append(mSpeed); + if (hasBearing()) s.append(" bear=").append(mBearing); + if (isFromMockProvider()) s.append(" mock"); if (mExtras != null) { s.append(" {").append(mExtras).append('}'); @@ -879,18 +884,14 @@ public class Location implements Parcelable { Location l = new Location(provider); l.mTime = in.readLong(); l.mElapsedRealtimeNanos = in.readLong(); + l.mFieldsMask = in.readByte(); l.mLatitude = in.readDouble(); l.mLongitude = in.readDouble(); - l.mHasAltitude = in.readInt() != 0; l.mAltitude = in.readDouble(); - l.mHasSpeed = in.readInt() != 0; l.mSpeed = in.readFloat(); - l.mHasBearing = in.readInt() != 0; l.mBearing = in.readFloat(); - l.mHasAccuracy = in.readInt() != 0; l.mAccuracy = in.readFloat(); l.mExtras = in.readBundle(); - l.mIsFromMockProvider = in.readInt() != 0; return l; } @@ -910,18 +911,14 @@ public class Location implements Parcelable { parcel.writeString(mProvider); parcel.writeLong(mTime); parcel.writeLong(mElapsedRealtimeNanos); + parcel.writeByte(mFieldsMask); parcel.writeDouble(mLatitude); parcel.writeDouble(mLongitude); - parcel.writeInt(mHasAltitude ? 1 : 0); parcel.writeDouble(mAltitude); - parcel.writeInt(mHasSpeed ? 1 : 0); parcel.writeFloat(mSpeed); - parcel.writeInt(mHasBearing ? 1 : 0); parcel.writeFloat(mBearing); - parcel.writeInt(mHasAccuracy ? 1 : 0); parcel.writeFloat(mAccuracy); parcel.writeBundle(mExtras); - parcel.writeInt(mIsFromMockProvider? 1 : 0); } /** @@ -946,7 +943,7 @@ public class Location implements Parcelable { * Attaches an extra {@link Location} to this Location. * * @param key the key associated with the Location extra - * @param location the Location to attach + * @param value the Location to attach * @hide */ public void setExtraLocation(String key, Location value) { @@ -962,7 +959,7 @@ public class Location implements Parcelable { * @return true if this Location came from a mock provider, false otherwise */ public boolean isFromMockProvider() { - return mIsFromMockProvider; + return (mFieldsMask & HAS_MOCK_PROVIDER_MASK) != 0; } /** @@ -973,6 +970,24 @@ public class Location implements Parcelable { */ @SystemApi public void setIsFromMockProvider(boolean isFromMockProvider) { - mIsFromMockProvider = isFromMockProvider; + if (isFromMockProvider) { + mFieldsMask |= HAS_MOCK_PROVIDER_MASK; + } else { + mFieldsMask &= ~HAS_MOCK_PROVIDER_MASK; + } + } + + /** + * Caches data used to compute distance and bearing (so successive calls to {@link #distanceTo} + * and {@link #bearingTo} don't duplicate work. + */ + private static class BearingDistanceCache { + private double mLat1 = 0.0; + private double mLon1 = 0.0; + private double mLat2 = 0.0; + private double mLon2 = 0.0; + private float mDistance = 0.0f; + private float mInitialBearing = 0.0f; + private float mFinalBearing = 0.0f; } } diff --git a/location/tests/locationtests/src/android/location/LocationTest.java b/location/tests/locationtests/src/android/location/LocationTest.java index 847ac7a000be..dc8d0f7b3876 100644 --- a/location/tests/locationtests/src/android/location/LocationTest.java +++ b/location/tests/locationtests/src/android/location/LocationTest.java @@ -16,6 +16,7 @@ package android.location; +import android.os.Parcel; import android.test.suitebuilder.annotation.SmallTest; import junit.framework.TestCase; @@ -225,6 +226,40 @@ public class LocationTest extends TestCase { assertEquals(message, loc.getBearing(), 0, 0); } + public void testParcel() { + final double expectedLat = 33; + final double expectedLon = -122; + final float expectedAccuracy = 15; + final float expectedSpeed = 5; + Location loc = new Location("test"); + loc.setLatitude(expectedLat); + loc.setLongitude(expectedLon); + loc.setAccuracy(expectedAccuracy); + loc.setSpeed(expectedSpeed); + + // Serialize location object into bytes via parcelable capability + Parcel parcel = Parcel.obtain(); + loc.writeToParcel(parcel, 0); + byte[] rawBytes = parcel.marshall(); + parcel.recycle(); + + // Turn the bytes back into a location object + parcel = Parcel.obtain(); + parcel.unmarshall(rawBytes, 0, rawBytes.length); + parcel.setDataPosition(0); + Location deserialized = Location.CREATOR.createFromParcel(parcel); + parcel.recycle(); + + assertEquals(expectedLat, deserialized.getLatitude()); + assertEquals(expectedLon, deserialized.getLongitude()); + assertEquals(expectedAccuracy, deserialized.getAccuracy()); + assertTrue(deserialized.hasAccuracy()); + assertEquals(expectedSpeed, deserialized.getSpeed()); + assertTrue(deserialized.hasSpeed()); + assertFalse(deserialized.hasBearing()); + assertFalse(deserialized.hasAltitude()); + assertFalse(deserialized.isFromMockProvider()); + } } |