⬆️ ⬇️

Deep immersion in positioning

This post is a translation of the topic from the android-developers blog. Further narration is from Reto Meier, the author of the book Professional Android 2 Application Development . He writes about how you can improve applications that use location, in the sense of caching results, speed, and so on.



It doesn't matter if you are looking for a place to eat, or the nearest place of Boris Bike bikes, there is always a delay in getting location data from GPS and filling in an abstract list of results in a vacuum. When you are in a place where you would like to receive contextual information, then often you encounter a lack of data connection.



Instead of punching the sky, I wrote an open-source application that includes tips and tricks to reduce the time between opening the application and viewing relevant information about nearby places, coupled with a reasonable provision of offline operation. And all this while keeping battery usage at the lowest possible.



Show me the code!



You can check out my Android project Protips for Location . Do not forget to read the Readme.txt in order to successfully compile and run the application.

')

What does it actually do?



It uses the Google Places API to implement basic functionality of applications that use a location to determine a list of nearby attractions (points on the map), allow you to view their details, and also to check or evaluate.



The code implements many of the best practices that I described in detail at my session on Google I / O 2011, Android Protips: Advanced Topics for Expert Android Developers ( video ). Including using Intents to get location updates using Passive Location Provider, monitoring device status to change update frequency, switching Receivers during application execution, and also using Cursor Loader.



The application is written for Honeycomb, but can also work on version 1.6 and higher.



Now that you have the code, let's take a closer look at it.



My main priority is freshness: minimizing the delays between opening the app and the ability to check the right places, and at the same time minimize battery usage.



Requirements:



Freshness means never wait



You can significantly reduce the waiting time for getting the first approach to a location by selecting the last known location manager from the location each time the application goes into active mode.



In this example, from the GingerbreadLastLocationFinder , we search through all the location providers on the device, even those that are currently unavailable, to find the most accurate last location.



List<String> matchingProviders = locationManager.getAllProviders(); for (String provider: matchingProviders) { Location location = locationManager.getLastKnownLocation(provider); if (location != null) { float accuracy = location.getAccuracy(); long time = location.getTime(); if ((time > minTime && accuracy < bestAccuracy)) { bestResult = location; bestAccuracy = accuracy; bestTime = time; } else if (time < minTime && bestAccuracy == Float.MAX_VALUE && time > bestTime){ bestResult = location; bestTime = time; } } } 


If there are coordinates of one or more locations, then the most accurate one is chosen. Otherwise, the most recent result is simply returned.



In the second case (when it is determined that the latest update of the location is not recent enough), the value is somehow returned, but we request one update of the location using the fastest provider.



 if (locationListener != null && (bestTime < maxTime || bestAccuracy > maxDistance)) { IntentFilter locIntentFilter = new IntentFilter(SINGLE_LOCATION_UPDATE_ACTION); context.registerReceiver(singleUpdateReceiver, locIntentFilter); locationManager.requestSingleUpdate(criteria, singleUpatePI); } 


Unfortunately, we cannot determine the fastest providers, but, in practice, it is clear that determining the location using the network returns results faster.



Note also that this code snippet shows GingerbreadLastLocationFinder , which uses the requestSingleUpdate method to get a one-time location update. This functionality was not available before Gingerbread - check out LegacyLastLocationFinder , see how I implemented the same functionality for earlier versions of Android.



singleUpdateReceiver sends the received update back to the calling class through the Location Listener .

 protected BroadcastReceiver singleUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { context.unregisterReceiver(singleUpdateReceiver); String key = LocationManager.KEY_LOCATION_CHANGED; Location location = (Location)intent.getExtras().get(key); if (locationListener != null && location != null) locationListener.onLocationChanged(location); locationManager.removeUpdates(singleUpatePI); } }; 


Use Intentions to get location updates.



Having received the most accurate / timely assessment of the current position, we also want to receive its updates.



The PlacesConstants class includes a set of values ​​that determine the frequency of location updates. Configure them to make sure updates come as often as required.



 // The default search radius when searching for places nearby. public static int DEFAULT_RADIUS = 150; // The maximum distance the user should travel between location updates. public static int MAX_DISTANCE = DEFAULT_RADIUS/2; // The maximum time that should pass before the user gets a location update. public static long MAX_TIME = AlarmManager.INTERVAL_FIFTEEN_MINUTES; 


The next step is to request a location update from the Location Manager. In the following code snippet, taken from the GingerbreadLocationUpdateRequester, we can pass the criteria used to determine which Location Manager will request updates directly in the call to the requestLocationUpdates method.



 public void requestLocationUpdates(long minTime, long minDistance, Criteria criteria, PendingIntent pendingIntent) { locationManager.requestLocationUpdates(minTime, minDistance, criteria, pendingIntent); } 


Please note that we are transmitting Pending Intent, not a Location Listener.



 Intent activeIntent = new Intent(this, LocationChangedReceiver.class); locationListenerPendingIntent = PendingIntent.getBroadcast(this, 0, activeIntent, PendingIntent.FLAG_UPDATE_CURRENT); 


In general, I prefer to use Location Listeners, because they provide flexibility in registering receivers in several Activities or Services, or directly in a manifest.



In this application, a new location means an updated list of nearby places. This happens through the services that send requests to the server and update the Content Provider, which fills the list of places.



Since a location change does not directly update the UI, it makes sense to create and register the associated LocationChangedReceiver in the manifest, and not in the Activity.



 <receiver android:name=".receivers.LocationChangedReceiver"/> 


The LocationChangedReceiver retrieves the location from each update and runs the PlaceUpdateService service to update the base of nearby places.



 if (intent.hasExtra(locationKey)) { Location location = (Location)intent.getExtras().get(locationKey); Log.d(TAG, "Actively Updating place list"); Intent updateServiceIntent = new Intent(context, PlacesConstants.SUPPORTS_ECLAIR ? EclairPlacesUpdateService.class : PlacesUpdateService.class); updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_LOCATION, location); updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_RADIUS, PlacesConstants.DEFAULT_RADIUS); updateServiceIntent.putExtra(PlacesConstants.EXTRA_KEY_FORCEREFRESH, true); context.startService(updateServiceIntent); } 


Retrieving data during battery life



To add support for offline mode, we'll start by caching the search results for PlacesContentProvider and PlaceDetailsContentProvider .



Also, in certain circumstances, you need to sample the location information. This code snippet from the PlacesUpdateService service demonstrates how prefetching is enabled for a limited number of places.



Note that data prefetching potentially turns off when the battery is low.



 if ((prefetchCount < PlacesConstants.PREFETCH_LIMIT) && (!PlacesConstants.PREFETCH_ON_WIFI_ONLY || !mobileData) && (!PlacesConstants.DISABLE_PREFETCH_ON_LOW_BATTERY || !lowBattery)) { prefetchCount++; // Start the PlaceDetailsUpdateService to prefetch the details for this place. } 


A similar technique is used to implement offline marks.



Optimizing battery life: smart services and using device status to switch receivers



It makes no sense to start the update service when the device is offline. The PlaceUpdateService service checks the connectivity before attempting to get an update.



 NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); 


If there is no connection, then ActiveLocationChangedReceiver and PassiveLocationChangedReceiver are turned off, and ConnectivityChangedReceiver is turned on.



 ComponentName connectivityReceiver = new ComponentName(this, ConnectivityChangedReceiver.class); ComponentName locationReceiver = new ComponentName(this, LocationChangedReceiver.class); ComponentName passiveLocationReceiver = new ComponentName(this, PassiveLocationChangedReceiver.class); pm.setComponentEnabledSetting(connectivityReceiver, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); pm.setComponentEnabledSetting(locationReceiver, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); pm.setComponentEnabledSetting(passiveLocationReceiver, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); 


ConnectivityChangedReceiver listens on all network connection changes. When a new connection is made, it simply turns itself off and turns on location listeners.



Battery monitoring to reduce functionality and save energy



When the phone is at the last 15%, then most applications do not work in order to save charge. We can register receivers in the manifest to issue a warning when the device enters or leaves the low battery state.



 <receiver android:name=".receivers.PowerStateChangedReceiver"> <intent-filter> <action android:name="android.intent.action.ACTION_BATTERY_LOW"/> <action android:name="android.intent.action.ACTION_BATTERY_OKAY"/> </intent-filter> </receiver> 


This snippet from the PowerStateChangedReceiver turns off the PassiveLocationChangedReceiver whenever the device goes into a low battery state and turns it on when the charge is good.



 boolean batteryLow = intent.getAction().equals(Intent.ACTION_BATTERY_LOW); pm.setComponentEnabledSetting(passiveLocationReceiver, batteryLow ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, PackageManager.DONT_KILL_APP); 


You can extend this logic by turning off all prefetching samples or by reducing the refresh rate under low battery conditions.



What's next?



The post has already turned out great, so I will stop here. Read more on my blog The Radioactive Yak.

Source: https://habr.com/ru/post/122705/



All Articles