原文地址:https://github.com/codepath/android_guides/wiki/Using-Context
概述
总的来说,Context为使用者提供了一种获取应用状态相关信息的途径。.有了它,Activities,Fragments和Services就可以取得资源文件,images,themes/styles以及外部目录的位置。同时,它也提供了Android内建服务的获取方式,这些服务包括布局填充,键盘以及content providers的获取。
在很多时候,我们都需要用到Context。一般情况下,我们都只需要传递一个当前activity的实例。当我们处于activity内部,操作的是由其创建的对象,比如adapters或者fragments时,我们需要把activity的实例传给这些对象。当我们处在activity外部,如在application或者service中,我们则可以使用"application"。
Context到底用来做什么呢?
下面是使用Context的几个具体例子。
显式的启动组件
// Provide context if MyActivity is an internal activity. Intent intent = new Intent(context, MyActivity.class); startActivity(intent);当显式启动一个组件时,需要提供两类信息:
1.包名,确定了包含该组件的application
2.该组件完整的Java类名
如果启动的是一个内部组件,则包名可以通过context.getPackageName()来获取。
创建一个View
TextView textView = new TextView(context);Contexts包含了Views需要的几种信息:
1.设备的屏幕尺寸,以便将dp,sp转化为pixels
2.样式属性
3.onClick属性所持有的acitivity引用
填充XML布局文件
在使用LayoutInflater时,我们需要使用context将XML布局填充到内存:
// A context is required when creating views. LayoutInflater inflater = LayoutInflater.from(context); inflater.inflate(R.layout.my_layout, parent);
发送local broadcast
在我们需要发送广播或者注册receiver时,我们通过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(intent);
调用系统服务
在应用发送通知时,需要使用到NotificationManager的系统服务:
// Context objects are able to fetch or start system services. NotificationManager manager = (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 Context 与 Acitivity Context
尽管themes和 styles通常是在application层使用的,但他们也可以被用在activity层。activity可以设置为与application不同的样式(例如,在某些特定页面你可以选择不适用ActionBar)。你肯定注意到过AndroidManifest.xml文件中通过android:theme设置application主题。我们也可以为特定Activity设置不同的theme:
<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">
由于以上原因,我们必须了解Application Context 和Activity Conttext以及他们各自的生命周期。我们应该为大部分Views传递一个Activity Context,以便使其获得themes,styles和dimensions的相关信息。如果我们没有为当前Activity设置theme,则会默认使用application的对应设置。
多数情况下,你应该使用Activity Context。我们都知道,Java中的关键字this表示对当前类实例的引用,通常,在一个Activity内部,当你需要使用Context时,可以直接使用this。下面是一个例子:
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(); } }
匿名函数
当在实现listeners而需要使用匿名函数时,关键字this对应于最接近的class。在下面这个例子中,使用this时,外部类MainActivity必须被显示地申明以指向Activity实例。
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(); } }); } }
Array Adapter
当我们为ListView构造适配器时,通常会在布局填充的过程中使用到getContext()。这个方法使用到是实例化ArrayAdapter的context:
if (convertView == null) { convertView = LayoutInflater .from(getContext()) .inflate(R.layout.item_user, parent, false); }如果你使用application context实例化ArrayAdapter,你可能会注意到themes/styles没有被使用。所以,请确保你传递的是Acitivity context。
RecyclerView Adapter
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> { @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater .from(parent.getContext()) .inflate(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.Adapter并不需要。在它的布局填充过程中,可以通过其父视图来传递正确的context。
而关联的RecyclerView总是把它自己当做父视图传递到RecyclerView.Adapter.onCreateViewHolder()防范中间那个。
如果需要再onCreateViewHolder()方法外部使用context,那么只要存在一个可用的ViewHolder实例,就可以通过viewHolder.itemView.getContext()方法来获得context。这里的itemView是ViewHolder中一个public,non-null和final的字段。
避免内存泄露
当我们使用单例模式时,通常需要使用Application Context。例如,自定义一个manager类,它就需要使用Contex来获取系统服务。然后再Activity不在运行后,仍然持有一个Acitivity context的银鬃将会造成内存无法回收,这是就应该使用Application Context。
在下面的例子中,如果被传入的是一个Activity或Service context并且其已经被Android系统销毁,那么它也不能被回收利用,因为CustonManager类扔持有它的静态引用。
pubic 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; } }
使用Application Context
为了避免内存泄露,千万不要在context的生命周期之外持有其引用。检查你的所有后台线程,悬挂的handlers以及其他可能持有context对象的内部类。
上面的例子应该做出修改,在CustomManager.getInstance()方法中使用application context。application是单例的并且与application的生命周期绑定,这样就可以安全的持有它的引用。修改后如下所示:
pubic 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; } }