Android Context in Dire Straits ? — Part 1
We android developers have to deal with contexts that form an integral part of our day to day work as they are the only way to obtaining the application’s environment and resources. But there are so many different types of contexts and so many different ways of obtaining an instance of a context.
This is the first part of a series of articles where we will tear down the Android Context and will try to dive into the details and why it is one of the most poorly written codes in Android.
Okay, you are an experienced Android Developer and want an Easter egg? Answer this — Does the Context
implementation in Android adheres to the SOLID principles?
This article, however, is the Part 1 of this Series and hence will provide a deep understanding of what context is. In the articles to follow, we will look into the pitfalls of context
in Android.
So, What is a Context?
According to the official documentation,
Context is an interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.
I think this is self-explanatory. But let’s dive deep into what this class actually offers.
So if we look into the source code, we find that the Context
is an abstract class and thus we cannot directly instantiate it. Rather it is extended directly by two classes :
Out of these two, ContextWrapper
is of significance to us as MockContext
is just a mock class in which all methods are non-functional and throw a UnsopportedOperationException
and is used only for writing tests.
ContextWrapper
on the other hand, is a proxy implementation of the Context
class which defines a variable called mBase
of type Context
and for every method call, just delegates the call by executing mBase.sampleMethodCall()
like:
You might ask, why is this even required? And the answer to that would by delegating calls, we can easily use any subclass to modify behavior without changing the original Context.
ContextWrapper
class is just an adapter of any Context type so it can be used at various places where the adapter pattern is required rather than using Context child classes.
Also the attachBaseContext
the method helps in maintaining only one base context
for the instance of the wrapper.
This ContextWrapper
class is again extended by a few classes.
Application class
The Application
class extends the ContextWrapper
class and calls the attachBaseContext
method of its superclass i.e.ContextWrapper
to set itself as the mBase
object, which we have discussed just before this. The code looks like:
Service class
A service
, like the application class, also extends the ContextWrapper
and thus has an attach
method using which it sets up the base context like this:
ContextThemeWrapper class
The third class which extends the ContextWrapper
and is of significance to us is the ContextThemeWrapper
which adds theming support to a Context. Thus Activities (which are subclasses ofContextThemeWrapper
) can be attributed with a theme, but the Views
don't have a theme attached to them.
Here, the ContextThemeWrapper
class does something different from what we have seen and overrides the attachBaseContext
method instead of defining an attach
method.
Activity class
At this point, if we look at Fig 1 above, we have defined all of the classes except for the Activity
class. So, if we look into the Activity class we will see that it extends the ContextThemeWrapper
class and defines an attach
method as follows:
This receives a Context
instance and then calls the attachBaseContext
method, which is an overloaded method and is defined as follows:
We can see, it attaches the passed context
instance as the base context.
Yes, you must be wondering, “But you showed us that Context is an abstract class so where is the actual implementation of Context class?”
The answer is ContextImpl!
Well, we have seen that the Context class is an abstract class and hence it cannot be passed around directly, but we need an implementation of it to pass around. Thus comes into the picture, the ContextImpl
class which is an implementation of the Context
class and contains the definition for the abstract methods in the context class, and is provided by the system to all of these classes.
Few things to notice here :
- The
ContextImpl
provided by the system to theApplication
class acts as a Singleton and persists throughout the lifecycle of the app. - The
ContextImpl
provided by the system to theService
class is specific to each service and is provided only once to that service. It does not matter how many times we bind/unbind with that service. - The
ContextImpl
provided by the system to theActivity
changes every time the activity is recreated/destroyed. For eg, after orientation change, a new context instance will be provided to the same Activity.
The three monkeys — ApplicationContext, BaseContext, ActivityContext
From the discussion above, we can now infer why the three contexts
are different and when should each one be put into use.
But just to make the air clearer, we’ll take some examples and discuss a few scenarios.
Application Context: This can be obtained using Activity.getApplicationContext()
. If we have to create a singleton object for our application and that object needs a context, we should pass the application context. Example — when we have to initialize a database library that should exist throughout the lifecycle of the app, always pass the application context.
Activity Context: An instance of this can be called from inside the activity itself by calling the this
keyword or from a fragment using requireActivity()
. The activity context is tied to the lifecycle of an activity. Say we have a view which shows some message on the screen and takes in a context
argument. Then we should always pass the activity context as we want the view to persist only as long as the activity lives.
Base Context: If we need access to a Context from within another context, we use a ContextWrapper. The Context referred to from inside that ContextWrapper is accessed via ContextWrapper.getBaseContext()
.
Bonus
- getContext() vs requireContext():
getContext
can return a nullable context whereas,requrieContext
internally calls thegetContext
method but checks if the returned context is null or not. In case the returned context is null, it throwsIllegalStateException
. The following is the definition for therequireContext
method:
- getActivity() vs requireActivity(): same as
getContext
,getActivity
returns a nullable activity instance andrequireActivity
checks if the instance returned by thegetActivity
method is null or not. Incase it is null, it throws anIllegalStateException
. the following is the definition for therequireActivity
method:
Note: requireContext and requireActivity are only available post Support Library 27.1.0
I hope you had a nice time reading this and I was able to provide some useful information through this article.
If you liked it, please help me with your precious 👏 to this article. Thanks.
In the next article, we will dive deeper into the flaws and pitfalls with Android Context.