В чем разница между различными методами для получения контекста?


390

В различных частях кода Android я видел:

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

Однако я не могу найти какое-либо достойное объяснение того, что является предпочтительным, и при каких обстоятельствах, которые должны быть использованы.

Будем весьма благодарны за указатели на документацию по этому вопросу и рекомендации о том, что может сломаться при выборе неправильного.


2
Эта ссылка может помочь вам. Пройдите через это ..
Aju

Ответы:


305

Я согласен, что документация скудна, когда дело доходит до контекстов в Android, но вы можете собрать воедино несколько фактов из различных источников.

Этот пост в официальном блоге разработчиков Google Android был написан в основном для устранения утечек памяти, но также содержит некоторую полезную информацию о контекстах:

В обычном приложении для Android у вас обычно есть два вида контекста: активность и приложение.

Чтение статьи чуть дальше расскажет о разнице между ними и о том, когда вы можете рассмотреть возможность использования Context приложения ( Activity.getApplicationContext()) вместо использования контекста Activity this). В основном контекст приложения связан с приложением и всегда будет одинаковым на протяжении жизненного цикла вашего приложения, где контекст действия связан с действием и может быть уничтожен много раз, так как действие разрушается во время изменения ориентации экрана и например.

Я не мог найти ничего о том, когда использовать getBaseContext (), кроме сообщения от Dianne Hackborn, одного из инженеров Google, работающих над Android SDK:

Не используйте getBaseContext (), просто используйте имеющийся у вас Контекст.

Это было из сообщения в новостной группе android-разработчиков , вы можете подумать и о том, чтобы задать свой вопрос там, потому что горстка людей, работающих над Android, фактически следят за этой группой новостей и отвечают на вопросы.

Поэтому в целом представляется предпочтительным использовать глобальный контекст приложения, когда это возможно.


13
Когда у меня есть действие A, которое может начать действие B, которое, в свою очередь, может перезапустить A с флагом CLEAR_TOP (и, возможно, повторить этот цикл много раз) - какой контекст мне следует использовать в этом случае, чтобы избежать создания огромного следа ссылочные контексты? Диана говорит, что использует «this» вместо getBaseContext, но тогда ... большую часть времени A будет использоваться повторно, но есть ситуации, когда будет создан новый объект для A, а затем утечка старого A. Таким образом, кажется, что getBaseContext является наиболее правильным выбором для большинства случаев. Тогда не понятно почему Don't use getBaseContext(). Может ли кто-нибудь уточнить это?
JBM

2
как можно получить доступ к объекту контекста внутри класса, который не расширяет Activity?
Коул

1
@Cole, вы можете создать класс, который мы будем называть здесь «ExampleClass», конструктор которого принимает объект Context и создает экземпляр переменной класса «appContext». Затем ваш класс Activity (или любой другой класс в этом отношении) может вызвать метод ExampleClass, который использует переменную экземпляра "appContext" ExampleClass.
Archie1986

54

Вот что я нашел относительно использования context:

1) Внутри Activityсебя, используйте thisдля раздувания макетов и меню, зарегистрируйте контекстные меню, создайте экземпляры виджетов, запустите другие действия, создайте новое Intentвнутри , создайте Activityэкземпляры предпочтений или другие методы, доступные в 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) Для прослушивателей и других типов классов Android (например, ContentObserver) используйте подстановку контекста, например:

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) Для тостов обычно используют getApplicationContext(), но, где возможно, используют контекст, переданный из Activity, Service и т. Д.

Используйте контекст приложения:

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();
    }
}

И последнее, не используйте getBaseContext()в соответствии с рекомендациями разработчиков платформы Android.

ОБНОВЛЕНИЕ: Добавьте примеры Contextиспользования.


1
Вместо mContext можно использовать OuterClass.this; см. комментарии в stackoverflow.com/questions/9605459/…
Пол Верест

3
+1 за такой полезный ответ! Я согласен, что принятый ответ хорош как принятый ответ, но святой Молли, этот ответ был супер информативным! Спасибо за все эти примеры, они помогли мне лучше понять использование контекста в целом. Я даже скопировал ваш ответ в текстовый файл на моем компьютере в качестве справочного материала.
Райан

13

Я прочитал эту ветку несколько дней назад, задавая себе тот же вопрос. Мое решение после прочтения было простым: всегда используйте applicationContext.

Однако, я столкнулся с проблемой, я потратил несколько часов, чтобы найти ее, и несколько секунд, чтобы ее решить ... (меняя одно слово ...)

Я использую LayoutInflater, чтобы надуть представление, содержащее Spinner.

Итак, вот две возможности:

1)

    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);

Что я заметил: если вы создали свой экземпляр linearLayout с помощью applicationContext, то при нажатии на счетчик в вашей активности вы получите неперехваченное исключение, исходящее от виртуальной машины dalvik (а не из вашего кода, поэтому я потратил много времени найти где была моя ошибка ...).

Если вы используете baseContext, то все в порядке, откроется контекстное меню, и вы сможете выбрать один из вариантов.

Итак, вот мой вывод: я полагаю (я не проверял это дальше), чем baseContext требуется при работе с contextMenu в вашей деятельности ...

Тест был выполнен с использованием API 8 и протестирован на HTC Desire, Android 2.3.3.

Я надеюсь, что мой комментарий не скучал до сих пор, и желаю вам всего наилучшего. Удачного кодирования ;-)


Я всегда использовал «это» при создании представлений в деятельности. Исходя из того, что если действие возобновляется, представления переделываются, и может появиться новый контекст, из которого можно сделать представления снова. Недостаток, опубликованный в блоге разработчика, заключается в том, что пока ImageView уничтожен, используемый чертеж / растровое изображение могут зависеть от этого контекста. Тем не менее это то, что я делаю в данный момент. Что касается кода в другом месте приложения (обычные классы), я просто использую контекст приложения, поскольку он не специфичен для какой-либо активности или элементов пользовательского интерфейса.
ДжонВиллис

6

Во-первых, я согласен, что мы должны использовать appcontext, когда это возможно. тогда «это» в деятельности. У меня никогда не было необходимости в базовом контексте.

В моих тестах в большинстве случаев их можно поменять местами. В большинстве случаев причина, по которой вы хотите получить контекст, заключается в доступе к файлам, настройкам, базе данных и т. Д. Эти данные в конечном итоге отражаются в виде файлов в папке личных данных вашего приложения (/ data / data /). Независимо от того, какой контекст вы используете, они будут сопоставлены с одной и той же папкой / файлами, так что вы в порядке.

Это то, что я заметил. Может быть, есть случаи, вы должны их различать.


Мне нужен базовый контекст, чтобы глобально установить язык приложения при запуске (когда он не совпадает с языком по умолчанию для телефона).
Тина

3

В некоторых случаях вы можете использовать контекст Activity поверх контекста приложения при запуске чего-либо в потоке. Когда поток завершает выполнение, и вам нужно вернуть результат обратно в действие вызывающего, вам нужен этот контекст с обработчиком.

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

2

Простыми словами

getApplicationContext()Как подсказывает название метода, ваше приложение узнает подробности приложения, к которым вы можете получить доступ из любой точки приложения. Таким образом, вы можете использовать это в привязке сервисов, регистрации трансляций и т. Д., Пока Application contextприложение не закроется.

getActivity()или thisсделает ваше приложение осведомленным о текущем экране, который также виден, и сведения об уровне приложения, предоставленные application context. Так что все, что вы хотите знать о текущем экране, как Window ActionBar Fragementmangerи так, доступно в этом контексте. В основном так и Activityрасширяется Context. Этот контекст будет активен до тех пор, пока текущий компонент (активность) не будет активен


1

Путаница проистекает из того факта, что существует множество способов доступа к контексту, без видимых различий. Ниже приведены четыре наиболее распространенных способа доступа к контексту в действии.

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

Что такое контекст? Мне лично нравится думать о контексте как о состоянии вашего приложения в любой момент времени. Контекст приложения представляет собой глобальную или базовую конфигурацию вашего приложения, на которую может опираться действие или служба, и представляет экземпляр конфигурации вашего приложения или переходное состояние для него.

Если вы посмотрите на источник для android.content.Context, вы увидите, что Context является абстрактным классом, и комментарии к классу следующие:

Интерфейс для глобальной информации о среде приложения. Это абстрактный класс, реализация которого обеспечивается системой Android. Он позволяет получить доступ к application-specificресурсам и классам, а также к дополнительным вызовам для application-levelтаких операций, как запуск, широковещание и получение и т. Д. От этого я отказываюсь от того, что Context предоставляет общую реализацию для доступа к уровню приложения, а также к уровню системы. Ресурсы. Ресурсы уровня приложения могут иметь доступ к таким вещам, как ресурсы [getResources()]или активы String, а ресурсы [getAssets()]уровня системы - это все, к чему вы обращаетесьContext.getSystemService().

На самом деле, посмотрите на комментарии к методам, и они, кажется, усиливают это понятие:

getSystemService(): Вернуть дескриптор в system-levelсервис по имени. Класс возвращаемого объекта зависит от запрошенного имени. getResources()Возвращает экземпляр ресурсов для пакета вашего приложения. getAssets()Возвращает экземпляр ресурсов для пакета вашего приложения. Возможно, стоит отметить, что в абстрактном классе Context все вышеперечисленные методы являются абстрактными! Только один экземпляр getSystemService (Class) имеет реализацию, которая вызывает абстрактный метод. Это означает, что реализация для них должна обеспечиваться в основном реализующими классами, которые включают в себя:

ContextWrapper
Application
Activity
Service
IntentService

Глядя на документацию API, иерархия классов выглядит следующим образом:

контекст

| - ContextWrapper

| - - Применение

| - - ContextThemeWrapper

| - - - - Деятельность

| - - Обслуживание

| - - - IntentService

Так как мы знаем, что Contextсамо по себе не дает никакого понимания, мы спускаемся по дереву, смотрим на ContextWrapperи понимаем, что там тоже не так много. Поскольку приложение расширяется ContextWrapper, там тоже не на что смотреть, поскольку оно не отменяет реализацию, предоставляемую ContextWrapper. Это означает, что реализация для Context предоставляется ОС и скрыта от API. Вы можете взглянуть на конкретную реализацию для Context, посмотрев на источник для класса ContextImpl.


0

Я использовал только это и getBaseContextпри поджаривании из onClick(очень зеленый нуб на Java и Android). Я использую это, когда мой кликер находится непосредственно в действии и должен использоваться getBaseContextв анонимном внутреннем кликере. Я предполагаю, что это в значительной степени хитрость getBaseContext, возможно, она возвращает контекст действия, в котором скрывается внутренний класс.


1
Это неправильно, это возвращает базовый контекст самой деятельности. Чтобы получить действие (которое вы хотите использовать в качестве контекста) от анонимного внутреннего класса, используйте что-то вроде MyActivity.this. Использование описанного вами базового контекста, вероятно, не вызовет проблем, но это неправильно.
nickmartens1980
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.