关于Android的Context

使用 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
       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 和 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<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 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;
}
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容