Presentation
As all android developer must know, the Context
is the center of the universe in the Android framework. We need it everywhere, whether for retrieving assets, requesting a system service with getSystemService()
or anything related to the android system...
But there is something that many android developer do by default (especially the novices) when a context is needed, they use their current activity. In this article I'll demonstrate you why you must not do that, what are the alternatives to it and how to trace misused contexts.
So lets get started!
The different context(s)
Before diving into the why and how not to use an activity as a context we need to know all the different contexts available to us and what make them different.
Basically there is 3 important types of context:
- The Application context
- The Activity context
- The Service context
They represent the most used context in an android application and serve different purposes. You can use them in the following situations:
Application context | Activity context | Service context | |
---|---|---|---|
Show an ui component | NO1 | YES | NO1 |
Start an activity | NO2 | YES | NO2 |
Inflate a layout | NO3 | YES | NO3 |
Getting a system service | YES | YES | YES |
Start a service | YES | YES | YES |
Bind to a service | YES | YES | YES |
Send a broadcast | YES | YES | YES |
Retrieve resource values | YES | YES | YES |
1 You can do it but only under certain conditions such as using the WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
type for example
2 An activity can be started but only under the conditions of using the Intent.FLAG_ACTIVITY_NEW_TASK
to start it. /!\ Be aware that this flag does not represent a solution for forcing the start of an activity and should be used with caution.
3 You can do it at the cost of using the default system theme
Apart from what they can do, what is important to remember is that they have a limited life scope. An Application context will live as long as your application is running (whether it be in foreground or background), the Activity context will, as you may have guessed, live as long as the said activity won't be destroyed and finally the same applies for the Service Context to Service.
Now I see you're starting to get why using the activity context everywhere is dangerous ;).
Memory leaks
One of the most sad scenario in which a context can be misused is when you create a static class needing a context and you pass your current activity to it. To illustrate this example you can find a sample project on github here.
This project simply contains a button which trigger a GPS check and get the latest user position. If the GPS is activated then a new activity is launched presenting your current position.
To keep the project simple we used the location API v1.
Remember that I do not recommend using this kind of behaviour in your projects, here we are using this API only for the sake of the demonstration an the erroneous use of an activity as a context.
Well, now there is something wrong about this project, it's leaking memory. Let's dive in it and try to understand why.
First, launch the activity and click the "Show me my position" button ("Montre moi ma position!" in french) and run a heap dump in android studio by clicking on the 1 icon as shown on the image below:
Once done you'll see the heap dumper running as shown on 2.
When the dump finishes a new window with a .hprof
file will automatically open in your IDE.
Then, prior to android studio 1.5 you are able to run an analyzer task which, once run will display the memory leaks found in your application as on the screenshots below.
As you can see our MainActivity
is leaking memory and the culprit here is the mContext
of LocationUtil
.
This is because we retained the Context
of MainActivity
as the activity was destroyed and prone to be garbage collected.
One of the worse case of passing activity to such static method is when you'll try to use it in an asynchronous task. You can end up in a case where your activity has been destroyed but your static method will try to get its context and will sadly fail with an exception like this one:
NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
.
What a sad story you would say. Well, heads up, there is some solutions to this ;).
Solutions
Remove unused references
As you may have guessed the easiest solution (and the most intuitive for this project) is to avoid keeping a reference to the context. Well, as it is an acceptable solution, it is far from being the best one. Sometimes you will deal with huge project containing references to context
everywhere (yeah these kind of project sadly exists) and instead of trying to trace where the references ends you can use a more elegant solution.
Use the appropriate context
Remember the table above? You have 3 types of context
where the exact same job can be done by any of them. What interest us here is the Application context . Why? Because:
- It is available during the entire lifecycle of your application (named before that duh!) so you're sure that you will not end up with
NullPointerException
in cases shown above. - It is available from anywhere in your codebase (in the example project you can access it with
ApplicationClass.getInstance()
)
Pretty useful isn't it? By the way, did you notice that the Application context can do nearly as much as the Activity context does? (I don't recommend using it for starting an activity except if you know what you're doing).
From that finding we can say that the Application context come in handy for our memory leak issues!
Conclusion
So what are the important things to remember from all this? First, do not pass your activity as a context where the class using it do not make use of your Activity context capabilities (such as displaying an UI component, starting another activity or inflating layout) you'll do a favor to yourself by being assured that no memory leaks will come out of this.
Secondly and finally use the Application context as much as you can as it will live as long as your application is not killed.
Thank you for reading.