各种方式获取 Android Context 有什么区别?

402

在一些Android代码中,我看到了以下写法:

 public class MyActivity extends Activity {
    public void method() {
       mContext = this;    // since Activity extends Context
       mContext = getApplicationContext();
       mContext = getBaseContext();
    }
 }

然而,我找不到任何合适的解释来说明哪种方法更好,在什么情况下应该使用哪种方法。

如果能提供有关此方面的文档指引并阐述错误选择可能导致什么问题,将不胜感激。


2
这个链接可能会对你有所帮助。请查看这个链接 - Aju
8个回答

313

我认为在Android中,有关Context的文档确实很少,但您可以从各种来源中拼凑一些信息。

官方Google Android开发者博客上的这篇文章主要是为了帮助解决内存泄漏问题,但也提供了有关Context的一些好信息:

在一个常规的Android应用程序中, 通常会有两种类型的Context:Activity和Application。

进一步阅读这篇文章可以了解这两种Context之间的区别,以及何时可能要考虑使用应用程序Context(Activity.getApplicationContext())而不是使用Activity context(this)。基本上,Application Context与应用程序相关联,并且在应用程序的生命周期内始终保持相同,而Activity Context与活动相关联,在屏幕方向更改等期间可能会被销毁多次。

我找不到任何关于何时使用getBaseContext()的真正信息,除了来自Dianne Hackborn的一篇帖子。她是谷歌工程师之一,负责Android SDK的开发:

不要使用getBaseContext(),只需使用您拥有的Context。

这是来自android-developers新闻组的一个帖子,您可能也想考虑在那里提出问题,因为一些实际工作于Android上的人员会监视该新闻组并回答问题。

因此,总体而言,尽可能使用全局应用程序context似乎更可取。


14
当我有一个活动A,它可以启动活动B,然后可以使用CLEAR_TOP标志重新启动A(并可能重复多次此过程)- 在这种情况下我应该使用什么上下文,以避免建立大量引用上下文的轨迹? Diana说要使用'this'而不是getBaseContext,但是...大多数情况下会重复使用A,但也有一些情况会创建新的A对象,然后旧的A会泄漏。因此,看起来getBaseContext是大多数情况下最合适的选择。那么“不要使用getBaseContext()”为什么不清楚呢?能否有人澄清一下?答案:当您需要在应用程序组件之间共享全局上下文时,应该使用getApplicationContext()而不是getBaseContext()。 getApplicationContext()返回应用程序级别的上下文,而getBaseContext()返回由当前上下文提供支持的基本上下文。在上述情况下,可以使用Activity的上下文或this关键字来启动另一个活动,但如果您需要在应用程序范围内共享上下文,则应使用getApplicationContext()。 - JBM
2
一个没有扩展Activity的类如何访问上下文对象? - Cole
1
@Cole,你可以创建一个类,我们在这里称之为“ExampleClass”,它的构造函数接受一个Context对象并实例化一个类实例变量“appContext”。然后,你的Activity类(或任何其他类)可以调用ExampleClass方法,该方法利用ExampleClass的“appContext”实例变量。 - Archie1986

58

以下是我关于使用context的发现:

1) .Activity内部,用this来填充布局和菜单、注册上下文菜单、实例化小部件、启动其他活动、在Activity中创建新的Intent、实例化偏好设置或其他可用于Activity的方法。

填充布局:

View mView = this.getLayoutInflater().inflate(R.layout.myLayout, myViewGroup);

展开菜单:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    this.getMenuInflater().inflate(R.menu.mymenu, menu);
    return true;
}

注册上下文菜单:

this.registerForContextMenu(myView);

实例化小部件:

TextView myTextView = (TextView) this.findViewById(R.id.myTextView);

启动一个Activity

Intent mIntent = new Intent(this, MyActivity.class);
this.startActivity(mIntent);

实例化偏好设置:

SharedPreferences mSharedPreferences = this.getPreferenceManager().getSharedPreferences();

2) . 对于整个应用程序范围内的类,请使用getApplicationContext(),因为此上下文存在于应用程序的生命周期中。

检索当前Android软件包的名称:

public class MyApplication extends Application {    
    public static String getPackageName() {
        String packageName = null;
        try {
            PackageInfo mPackageInfo = getApplicationContext().getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), 0);
            packageName = mPackageInfo.packageName;
        } catch (NameNotFoundException e) {
            // Log error here.
        }
        return packageName;
    }
}

绑定一个应用程序范围的类:

Intent mIntent = new Intent(this, MyPersistent.class);
MyServiceConnection mServiceConnection = new MyServiceConnection();
if (mServiceConnection != null) {
    getApplicationContext().bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
}

3) . 对于 Listeners 和其他类型的 Android 类(如 ContentObserver),使用类似以下的 Context 替换:

mContext = this;    // Example 1
mContext = context; // Example 2

其中this或者context指类(例如Activity)的上下文。

Activity 上下文替换:

public class MyActivity extends Activity {
    private Context mContext;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        
        mContext = this;
    }
}

监听器上下文替换:

public class MyLocationListener implements LocationListener {
    private Context mContext;
    public MyLocationListener(Context context) {
        mContext = context;
    }
}

ContentObserver 上下文替换:

public class MyContentObserver extends ContentObserver {
    private Context mContext;
    public MyContentObserver(Handler handler, Context context) {
        super(handler);
        mContext = context;
    }
}

4) . 对于 BroadcastReceiver(包括内联/嵌入式接收器),请使用接收器的自身上下文。

外部的BroadcastReceiver:

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (action.equals(Intent.ACTION_SCREEN_OFF)) {
            sendReceiverAction(context, true);
        }
        private static void sendReceiverAction(Context context, boolean state) {
            Intent mIntent = new Intent(context.getClass().getName() + "." + context.getString(R.string.receiver_action));
            mIntent.putExtra("extra", state);
            context.sendBroadcast(mIntent, null);
        }
    }
}

嵌入式BroadcastReceiver

public class MyActivity extends Activity {
    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final boolean connected = intent.getBooleanExtra(context.getString(R.string.connected), false);
            if (connected) {
                // Do something.
            }
        }
    };
}

5) . 对于服务,请使用该服务的上下文。

public class MyService extends Service {
    private BroadcastReceiver mBroadcastReceiver;
    @Override
    public void onCreate() {
        super.onCreate();
        registerReceiver();
    }
    private void registerReceiver() {
        IntentFilter mIntentFilter = new IntentFilter();
        mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
        this.mBroadcastReceiver = new MyBroadcastReceiver();
        this.registerReceiver(this.mBroadcastReceiver, mIntentFilter);
    } 
}

6) . 对于 Toasts,通常使用getApplicationContext(),但在可能的情况下,请使用从 Activity、Service 等传递的 context。

使用应用程序的上下文:

Toast mToast = Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG);
mToast.show();

使用来自源的上下文:

public static void showLongToast(Context context, String message) {
    if (context != null && message != null) {
        Toast mToast = Toast.makeText(context, message, Toast.LENGTH_LONG);
        mToast.show();
    }
}

最后,不要像Android的框架开发者建议的那样使用getBaseContext()

更新:添加Context使用示例。


1
可以使用OuterClass.this代替mContext;请参见https://dev59.com/72kw5IYBdhLWcg3w2-Ik中的注释。 - Paul Verest
3
谢谢您提供的有用答案!我同意已接受的答案是可以接受的,但这个答案真是超级详细!感谢您提供这么多例子,它们帮助我更好地理解了整个语境的使用。我甚至把您的答案复制到了我的电脑上作为参考。 - Ryan

13
我几天前读过这篇帖子,也问过自己同样的问题。读完后,我的决定很简单:始终使用applicationContext。
然而,我在这方面遇到了一个问题,我花了几个小时去解决它,但只需要改变一个单词就可以解决它...()
我正在使用LayoutInflater来填充包含Spinner的视图。
所以有两种可能性:
    LayoutInflater layoutInflater = LayoutInflater.from(this.getApplicationContext());

2)

    LayoutInflater layoutInflater = LayoutInflater.from(this.getBaseContext());

然后,我正在做这样的事情:

    // managing views part
    View view = ContactViewer.mLayoutInflater.inflate(R.layout.aViewContainingASpinner, theParentView, false);
    Spinner spinner = (Spinner) view.findViewById(R.id.theSpinnerId);
    String[] myStringArray = new String[] {"sweet","love"};

    // managing adapter part
    // The context used here don't have any importance -- both work.
    ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this.getApplicationContext(), myStringArray, android.R.layout.simple_spinner_item);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);

    theParentView.addView(view);

我所注意到的是:如果您使用applicationContext实例化了您的linearLayout,那么当您在活动中点击下拉框时,您将会遇到一个未捕获的异常,此异常来自dalvik虚拟机(而不是您的代码,这就是为什么我花了很长时间才找到我的错误所在...)。
如果您使用baseContext,则一切正常,上下文菜单将打开,您将能够从选择中进行选择。
因此,我的结论是:我认为(我没有进一步测试)在处理活动中的contextMenu时需要baseContext...。
该测试经过API 8编码,在HTC Desire, android 2.3.3上进行了测试。
希望迄今为止我的评论没有使您感到无聊,祝您一切顺利。快乐编程;-)

我在创建活动视图时一直使用“this”。因为如果活动重新启动,视图将被重新创建,并且可能有一个新的上下文来再次制作视图。开发者博客中发布的缺点是,虽然ImageView被销毁,但使用的drawable / bitmap可能会保留该上下文。尽管如此,这就是我目前所做的。关于应用程序中其他代码(普通类),我只使用应用程序上下文,因为它不特定于任何活动或UI元素。 - JonWillis

6
首先,我同意尽可能使用appcontext。然后在activity中使用"this"。我从未需要basecontext。
在我的测试中,在大多数情况下它们可以互换使用。在大多数情况下,您想要获取上下文的原因是访问文件、首选项、数据库等数据。这些数据最终会反映为应用程序私有数据文件夹(/data/data/)中的文件。无论使用哪个上下文,它们都将映射到相同的文件夹/文件,所以没问题。
这就是我观察到的情况。也许有一些情况你需要区分它们。

我需要使用basecontext在启动时全局设置应用程序语言(当它与手机默认语言不匹配时)。 - TinaFrieda

3

在某些情况下,当在一个线程中运行内容时,您可能需要使用活动上下文(Activity context)而不是应用程序上下文(application context)。当线程执行完毕并且您需要将结果返回给调用方活动时,您需要使用带有处理程序(handler)的上下文。

((YourActivity) context).yourCallbackMethod(yourResultFromThread, ...);

2
简单来说,方法名为getApplicationContext()的函数将使您的应用程序了解应用程序范围内的细节,您可以从应用程序中的任何位置访问这些细节。因此,您可以在服务绑定、广播注册等方面使用它。Application context会一直存活,直到应用程序退出。 getActivity()this将使您的应用程序了解当前可见的屏幕以及application context提供的应用程序级别详细信息。因此,您可以使用此上下文了解有关当前屏幕的任何内容,例如Window ActionBar Fragementmanger等等。基本上,Activity扩展了Context。这个上下文将一直存活,直到当前组件(activity)存在。

1

混淆的原因在于有多种访问Context的方法,表面上看没有明显的区别。以下是您可能能够在Activity中访问Context的四种最常见的方式。

getContext()
getBaseContext()
getApplicationContext()
getActionBar().getThemedContext() //new

什么是Context? 我个人认为Context是应用程序在任何给定时间的状态。应用程序Context表示应用程序的全局或基本配置,Activity或Service可以在其上构建,并表示应用程序的配置实例或其传递状态。

如果您查看android.content.Context的源代码,您会发现Context是一个抽象类,类的注释如下:

接口提供有关应用程序环境的全局信息。这是一个抽象类,其实现由Android系统提供。它允许访问特定于应用程序的资源和类,以及进行应用程序级别操作的上调,例如启动活动、广播和接收意图等。 从中我得出的结论是Context提供了访问应用程序级别和系统级别资源的通用实现。应用程序级别的资源可能是访问诸如字符串资源[getResources()]或资产[getAssets()]之类的东西,而系统级别的资源则是使用Context.getSystemService() 访问的任何内容。

事实上,看一下方法的注释,它们似乎强化了这个概念:

getSystemService():通过名称返回一个系统级别的服务句柄。返回的对象类取决于所请求的名称。getResources():为您的应用程序包返回一个Resources实例。getAssets():为您的应用程序包返回一个Resources实例。值得一提的是,在Context抽象类中,上述所有方法都是抽象的!只有一个getSystemService(Class)的实例具有实现并调用抽象方法。这意味着,这些方法的实现应该主要由实现类提供,包括:

ContextWrapper
Application
Activity
Service
IntentService

查看API文档,类的层次结构如下:

Context

| — ContextWrapper

|— — Application

| — — ContextThemeWrapper

|— — — — Activity

| — — Service

|— — — IntentService

由于我们知道Context本身并没有提供任何信息,因此我们向下移动树形结构,查看ContextWrapper,意识到那里也没有什么内容。由于Application扩展了ContextWrapper,因此在那里也没有什么可看的,因为它没有覆盖ContextWrapper提供的实现。这意味着Context的实现由操作系统提供,并且对API隐藏。您可以通过查看ContextImpl类的源代码来查看Context的具体实现。


0

我只在从onClick中使用时使用了这个和getBaseContext(对Java和Android都很陌生)。当我的点击器直接在活动中时,我使用这个,而在匿名内部点击器中必须使用getBaseContext。我猜这就是getBaseContext的诀窍,它可能返回内部类所隐藏的活动的上下文。


1
这是错误的,它返回的是活动本身的基本上下文。要从匿名内部类中获取活动(您想要用作上下文的活动),请使用类似 MyActivity.this 的内容。按照您所描述的使用基本上下文可能不会引起问题,但这是错误的。 - nickmartens1980

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接