When writing applications using the ArcGIS Runtime SDK for Android or Java SE we naturally use asynchronous programming when working with potentially slow to respond resources. This means that we have user interfaces which are always responsive and don’t freeze. Typically, these resources are web services which may have very good server infrastructure, but your experience of these services is at the mercy of the internet. Mostly it’s pretty good, but sometimes it requires a little patience!
The solution to these patience-testing situations in applications is well established, we don’t block the UI thread of our app but instead ensure that potentially blocking code is run on a separate thread until it completes its task.
In other words, when we are waiting for something to respond, we will use a call-back which will respond on a separate thread once something useful has happened. Let’s take a look at some code which has a potential bug. This code may work, but sometimes if you are unlucky, it won’t.
// create feature layer with its service feature table // create the service feature table ServiceFeatureTable serviceFeatureTable = new ServiceFeatureTable( getResources().getString(R.string.sample_service_url)); // create the feature layer using the service feature table FeatureLayer featureLayer = new FeatureLayer(serviceFeatureTable); // trigger the layer to load featureLayer.loadAsync(); // this code gets run on a new thread when the feature is loaded. featureLayer.addDoneLoadingListener(() -> { String desc = featureLayer.getDescription(); Toast.makeText(this, "Description: " + desc, Toast.LENGTH_SHORT).show(); });
It is very simple, we are creating a feature layer from a service feature table and loading a feature layer. Once the layer has loaded, we are outputting a message with the name of the layer in a dialog.
Now, when running this code a few times in an Android app, most times the Toast dialog did not display i.e. the code within the loading listener was never executed. Running similar code on a desktop Java Runtime Environment (JRE) was the opposite, and it ran reliably most of the time.
So why would this work sometimes, but sometimes fail albeit randomly? After all, the IDE didn’t show this up as bad code! So, what’s going on?
Well the answer to this is around how the JRE works. The story will be the same for Android Runtime (ART) or Desktop based JREs. Every so often (and you can’t predict when), the JRE will free up resources through a process known as garbage collection. As resources are more limited on Android devices, the garbage collection tends to be more aggressive. All this is good, as it means we don’t run out of resources.
Let’s take a closer look at the code above. You will notice that we are seeing the problem when we are calling back when the resource has loaded. This is happening on a separate thread after the function has completed. The code in the listener run after the featureLayer variable has gone out of scope, so there is a fair probability that the garbage collector will target your variable for recycling before you’ve had a chance to use it! It’s a bit like an impatient waiter clearing away your dinner plate before you finished because you turned to have a conversation with someone beside you! The solution of course it to keep your hand on the plate so it doesn’t disappear when you are looking away.
So how do we “keep our hand on the plate” in code? The solution is to keep your class instance away from the merciless garbage collector. All you need to do is make your featureLayer variable a class member of your application. These do not go out scope, and are therefore safe from garbage collection.
public class MainActivity extends AppCompatActivity { private FeatureLayer featureLayer;
This is a pattern that should be adopted in your application code regardless of the resources you are using. You may think that working with a local file will be fast enough to beat the garbage collector, but your users may occasionally experience unexplained app behaviour. It is always best to make your asynchronous code as reliable as possible and remember this applies to Kotlin as well as Java.
Article Discussion: