A Context provides access to information about the application state. It provides Activities, Fragments, and Services access to resource files, images, themes/styles, and external directory locations. It also enables access to Android's built-in services, such as those used for layout inflation, keyboard, and finding content providers.
In many cases when the "context is required", we simply need to pass in the instance of the current activity. In situations where we are inside objects created by the activity such as adapters or fragments, we need to pass in the activity instance into those objects. In situations where we are outside of an activity (in an application or service), we can use the "application" context instead.
Below are a few use cases that require a Context object.
// Provide context if MyActivity is an internal activity.
Intent intent = new Intent(context, MyActivity.class);
startActivity(intent);
// Provide context if MyActivity is an internal activity.
val intent = Intent(context, MyActivity::class.java)
startActivity(intent)
When explicitly starting a component, two pieces of information are required:
If starting an internal component, the context can be passed in since the package name of the current application can be extracted through context.getPackageName()
.
TextView textView = new TextView(context);
val textView = TextView(context)
Contexts contain the following information that views require:
We use the context to fetch the LayoutInflater
in order to inflate an XML layout into memory:
// A context is required when creating views.
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.my_layout, parent);
// A context is required when creating views.
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.my_layout, parent)
We use the context to fetch the LocalBroadcastManager
when sending out or registering a receiver for a broadcast:
// The context contains a reference to the main Looper,
// which manages the queue for the application's main thread.
Intent broadcastIntent = new Intent("custom-action");
LocalBroadcastManager.getInstance(context).sendBroadcast(broadcastIntent);
// The context contains a reference to the main Looper,
// which manages the queue for the application's main thread.
val broadcastIntent = Intent("custom-action")
LocalBroadcastManager.getInstance(context).sendBroadcast(broadcastIntent)
To send notifications from an application, the NotificationManager
system service is required:
// Context objects are able to fetch or start system services.
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
int notificationId = 1;
// Context is required to construct RemoteViews
Notification.Builder builder =
new Notification.Builder(context).setContentTitle("custom title");
notificationManager.notify(notificationId, builder.build());
// Context objects are able to fetch or start system services.
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationId = 1
// Context is required to construct RemoteViews
val builder = Notification.Builder(context).setContentTitle("custom title")
notificationManager.notify(notificationId, builder.build())
Refer to the list of all available system services that can be retrieved through a Context.
While themes and styles are usually applied at the application level, they can be also specified at the Activity level. In this way, the activity can have a different set of themes or styles than the rest of the application (e.g. if the ActionBar needs to be disabled for certain pages). You will notice in the AndroidManifest.xml
file that there is typically an android:theme
for the application. We can also specify a different one for an Activity:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/MyCustomTheme">
For this reason, it's important to know that there is an Application Context and an Activity Context, which last for the duration of their respective lifecycle. Most Views should be passed an Activity Context in order to gain access to what themes, styles, dimensions should be applied. If no theme is specified explicitly for the Activity, the default is to use the one specified for the application.
In most cases, you should use the Activity Context. Normally, the keyword this
in Java references the instance of the class and can be used whenever Context is needed inside an Activity. The example below shows how Toast messages can be displayed using this approach:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(this, "hello", Toast.LENGTH_SHORT).show();
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Toast.makeText(this, "hello", Toast.LENGTH_SHORT).show()
}
}
When using anonymous functions when implementing listeners, the keyword this
in Java applies to the most immediate class being declared. In these cases, the outer class MainActivity
has to be specified to refer to the Activity instance.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
TextView tvTest = (TextView) findViewById(R.id.abc);
tvTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "hello", Toast.LENGTH_SHORT).show();
}
});
}
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// resource lookup using Kotlin extensions
val tvTest = abc
tvTest.setOnClickListener({ view -> Toast.makeText(this@MainActivity, "hello", Toast.LENGTH_SHORT).show()})
}
When constructing adapters for a ListView, typically getContext()
is usually called during the layout inflation process. This method usually uses the context that instantiated the ArrayAdapter:
if (convertView == null) {
convertView =
LayoutInflater
.from(getContext())
.inflate(R.layout.item_user, parent, false);
}
If you instantiate the ArrayAdapter with the application context, however, you are likely to notice that the themes/styles are not being applied. For this reason, make sure you are passing in the Activity context in these cases.
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v =
LayoutInflater
.from(parent.getContext())
.inflate(R.layout.itemLayout, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
// If a context is needed, it can be retrieved
// from the ViewHolder's root view.
Context context = viewHolder.itemView.getContext();
// Dynamically add a view using the context provided.
if(position == 0) {
TextView tvMessage = new TextView(context);
tvMessage.setText("Only displayed for the first item.")
viewHolder.customViewGroup.addView(tvMessage);
}
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public FrameLayout customViewGroup;
public ViewHolder(View imageView) {
// Very important to call the parent constructor
// as this ensures that the imageView field is populated.
super(imageView);
// Perform other view lookups.
customViewGroup = (FrameLayout) imageView.findById(R.id.customViewGroup);
}
}
}
class MyRecyclerAdapter : RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v =
LayoutInflater
.from(parent.context)
.inflate(R.layout.itemLayout, parent, false);
return ViewHolder(v);
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
// If a context is needed, it can be retrieved
// from the ViewHolder's root view.
val context = viewHolder.itemView.context
// Dynamically add a view using the context provided.
if(position == 0) {
val tvMessage = TextView(context);
tvMessage.setText("Only displayed for the first item.")
viewHolder.customViewGroup.addView(tvMessage);
}
}
inner class ViewHolder(imageView: View): RecyclerView.ViewHolder(imageView) {
val customViewGroup : FrameLayout
init {
// Perform other view lookups.
// Kotlin extension view lookups
customViewGroup = imageView.customViewGroup
}
}
}
Whereas an ArrayAdapter
requires a context to be passed into it's constructor, a RecyclerView.Adapter
does not. Instead, the correct context can be inferred from the parent view when inflation is necessary.
The associated RecyclerView
always passes itself as the parent view into the RecyclerView.Adapter.onCreateViewHolder()
call.
If a context is needed outside of the onCreateViewHolder()
method, as long as there's a ViewHolder
instance available, a context can be retrieved via: viewHolder.itemView.getContext()
. itemView
is a public, non-null, and final
field in the base ViewHolder class.
The Application Context is typically used when singleton instances need to be created, such as a custom manager class that requires Context information to gain access to system services but gets reused across multiple Activities. Since retaining a reference to the Activity context would cause memory not to be reclaimed after it is no longer running, it's important to use the Application Context instead.
In the example below, if the context being stored is an Activity or Service and it's destroyed by the Android system, it won't be able to be garbage collected because the CustomManager class holds a static reference to it.
public class CustomManager {
private static CustomManager sInstance;
public static CustomManager getInstance(Context context) {
if (sInstance == null) {
// This class will hold a reference to the context
// until it's unloaded. The context could be an Activity or Service.
sInstance = new CustomManager(context);
}
return sInstance;
}
private Context mContext;
private CustomManager(Context context) {
mContext = context;
}
}
class CustomManager private constructor(private val context: Context) {
companion object {
@Volatile
private var instance: CustomManager? = null
// double-checked locking algorithm
// https://medium.com/@BladeCoder/kotlin-singletons-with-argument-194ef06edd9e
// http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
fun getInstance(context: Context): CustomManager {
val i = instance
if (i != null) {
return instance as CustomManager
}
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = CustomManager(context)
instance = created
created
}
}
}
}
}
To avoid memory leaks, never hold a reference to a context beyond its lifecycle. Check any of your background threads, pending handlers, or inner classes that may be holding onto context objects as well.
The right way is to store the application context in CustomManager.getInstance()
. The application context is a singleton and is tied to the lifespan of application's process, making it safe to store a reference to it.
Use the application context when a context reference is needed beyond the lifespan of a component, or if it should be independent of the lifecycle of the context being passed in.
public static CustomManager getInstance(Context context) {
if (sInstance == null) {
// When storing a reference to a context, use the application context.
// Never store the context itself, which could be a component.
sInstance = new CustomManager(context.getApplicationContext());
}
return sInstance;
}
class CustomManager private constructor(private val context: Context) {
companion object {
fun getInstance(context: Context): CustomManager {
// synchronized code
val created = CustomManager(context.applicationContext)
}
}
}