使用 Context
英文文档原地址:戳这里
概览
Context
提供了有关于获取、读写程序状态信息的渠道。通常是活动 Activity
、碎片 Fragment
、服务 Service
访问它们的资源文件、布局信息、主题、样式、以及 Android
设备的外部目录空间。通过它也可以访问 Android
内部服务,比如布局加载器(LayoutInflater
),键盘,和文件共享器(Content Provider
)。
在调用某些需要 Context
的方法中,我们通常会直接传入当前 Activity
的实例。例如我们把 Context
对象传入到 Activity
需要(依赖)的对象中去,比如适配器 (Adapter
)、碎片 (Fragment
);又如我们在无关 Activity
的类中,使用 getApplicationContext()
获取应用对应的 Context
。
Context 到底是干什么的?
下面是几个需要 Context
到场合:
明确的启动一个组件
//如果MainActivity是一个内部组件,那么传入 context.
Intent intent = new Intent(context,MainActivity.class);
startActivity(intent);
当我们明确的启动一个组件时,我们需要提供两个信息:
- 详细包名,它指明了包含了该组件的应用。
- 组件 Class 的全名。
如果启动的是一个内部组件,那么可以直接传入Context
,因为当前应用的详细包名可以通过调用context.gerPackageName()
获取到。
创建一个视图(View
)
TextView textview = new TextView(context);
创建一个 View
需要提供以下信息:
- 设备的屏幕分辨率和将 dp/sp 转换为像素的比例
- 样式属性
-
Activity
的onClick
方法的引用
而Context
可以提供这些信息。
加载一个 XML
布局文件
通过传入 Context
获取一个布局加载器(LayoutInflater
),之后通过布局加载器加载 XML 布局文件。
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.mylayout,parent);
发送一个本地广播(Broadcast
)
通过 Context
获取一个广播管理器(LocalBroadcastManager
)。然后通过它发送一个本地广播
//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);
获取系统服务
从应用中发送一个通知信息(Notification
),需要传入 Context
。
// 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());
Application 和 Activity 的区别是什么?
样式和主题通常会统一嵌入在整个应用中,但是也可以为某个 Activity
设定一个不同的主题(例子:在某些特定的 Activity
中关闭 ActionBar
控件)。你可能注意到在 AndroidMainfest.xml
文件中 Application
标签通常有一个名为 android:theme
的属性,我们同样可以给 Activity
标签添加这个属性。
从这个角度考虑,你需要知道一点—— Application
和 Activity
拥有各自的 Context
对象,并且它们对生命周期是不同的。某个 Activity
内的 View
控件应该传入对应的 Context
以获取对应的主题,样式文件。如果一个 Activity
没有明确表明它的主题属性,那么就会使用 Application
的主题资源。
大部分情况下你都应该使用 Activity
的 Context
对象。通常情况下,关键字 this
指向当前的 Class
的实例,可以当作一个 Context
来使用。下面的例子展示了如何通过这种方法显示一个吐司( Toast
);
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();
}
}
匿名函数
当你实现一个匿名内部类作为参数传入一个方法时,在类内部,关键字 this
就不再是指向当前 Activity
的引用了。它将指向最近的一个类的实例。在下面的代码段中,必须通过类名来显示传入一个 Context
。
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();
}
});
}
}
适配器( Context
)
ArrayAdapter
当创建一个适配器给 ListView
时,通常会在布局加载期间,通过调用 getContext()
方法获得 Context
:
if (convertView == null) {
convertView = LayoutInflater.from(getContext())
.inflate(R.layout.item_user, parent, false);
}
如果你在这里使用了错误的 Context
对象,那么将无法正确加载主题样式。如此考虑,你应该正确使用 Context
。
RecyclerView Adapter
public class MyRecyclerAdapter extends RecyclerView.Adapter {
@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 i) {
// 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(i == 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);
}
}
}
ArrayAdapter
的构造方法需要传入一个 Context
对象,而 RecyclerView
则不需要。在加载时,它能够正确的获取到 Context
。关联的 RecyclerView
能够将自己当作参数传送给 RecyclerView.Adapter.onCreateViewHolder()
。
避免内存泄漏
在使用单例设计模式时通常传入 Application
的 Context
。当你创建了一个管理类,通过持有 Context
来获取系统信息时,如果使用了 Activity
的 Context
,将会获得若干个不同的 Context
。当 Activity
持有了 Context
的引用时,静态引用不会随着 Activity
的销毁而同时释放,如此便会引发内存泄漏问题。
下面是一个例子,如果一个 Activity
的 Context
被一个静态管理类持有引用,那么当 Activity
释放时, Context
无法被释放。因为静态管理类持有对它的引用。
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;
}
}
正确使用 Context
- 使用 Application Context
避免内存泄漏的方法是,不让生命周期长的对象持有生命周期较短的 Context
引用。检查后台线程,handler,内部类确保不会发生这种情况。
正确使用 Context
的方式是通过 Application
来创建管理类。它被生命周期为整个应用的进程,全局只有一个实例。
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;
}