Android Context in Dire Straits ? — Part 1

Abhriya Roy
6 min readAug 1, 2020

--

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.

Fig 1. Context heirarchy
Fig 1. Context hierarchy

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 the Application class acts as a Singleton and persists throughout the lifecycle of the app.
  • The ContextImpl provided by the system to the Service 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 the Activity 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 the getContext method but checks if the returned context is null or not. In case the returned context is null, it throws IllegalStateException. The following is the definition for the requireContext method:
  • getActivity() vs requireActivity(): same as getContext, getActivity returns a nullable activity instance and requireActivity checks if the instance returned by the getActivity method is null or not. Incase it is null, it throws an IllegalStateException. the following is the definition for the requireActivity 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.

Recommended Reading:

--

--

Abhriya Roy
Abhriya Roy

Written by Abhriya Roy

Android developer keen on knowing things in detail and giving back to the community.

No responses yet