Яка різниця між різними методами, щоб отримати контекст?


390

У різних бітах коду Android я бачив:

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

Однак я не можу знайти жодного гідного пояснення, яке є кращим та за яких обставин, які слід використовувати.

Вказівки на документацію щодо цього та вказівки щодо того, що може зламатися, якщо буде обраний неправильний, були б дуже вдячні.


2
Це посилання може допомогти вам. Пройдіть через цю ..
Аю

Відповіді:


305

Я погоджуюся, що документація є рідкою, коли мова йде про контексти в Android, але ви можете зібрати кілька фактів з різних джерел.

Ця публікація в офіційному блозі розробників Google Android була написана здебільшого, щоб допомогти усунути витоки пам’яті, але також містить хорошу інформацію про контексти:

У звичайному додатку для Android, як правило, є два типи контексту, активності та програми.

Читаючи статтю трохи далі, розповідається про різницю між ними та коли ви можете розглянути можливість використання програми Context ( Activity.getApplicationContext()), а не використання контексту Activity this). В основному контекст програми пов'язаний із програмою і завжди буде однаковим протягом усього життєвого циклу вашої програми, коли контекст діяльності пов'язаний з діяльністю і може бути зруйнований багато разів, оскільки діяльність знищується під час зміни орієнтації екрана та таких.

Я не міг знайти насправді нічого про те, коли використовувати getBaseContext (), окрім публікації від Діани Хакборн, одного з інженерів Google, що працює над Android SDK:

Не використовуйте getBaseContext (), просто використовуйте контекст, який у вас є.

Це було з допису групи новин для розробників андроїдів , можливо, ви захочете попросити поставити своє запитання і там, бо жменька людей, які працюють на Android, фактично стежать за цією групою новин і відповідають на питання.

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


13
Коли у мене є діяльність A, яка може запустити діяльність B, яка, в свою чергу, може перезапустити A із прапором CLEAR_TOP (і, можливо, повторити цей цикл багато разів) - який контекст я повинен використовувати в цьому випадку, щоб уникнути створення величезного сліду посилаються на контексти? Діана каже, що використовує "це", а не getBaseContext, але потім ... більшість випадків A буде повторно використаний, але є ситуації, коли новий об’єкт для A буде створений, а потім старий A протікає. Тож здається, що getBaseContext є найбільш правильним вибором для більшості випадків. Тоді незрозуміло, чому Don't use getBaseContext(). Може хтось це уточнив?
JBM

2
як можна отримати доступ до контекстного об’єкта всередині класу, який не розширює Діяльність?
Коул

1
@Cole, ви можете створити клас, який ми будемо називати "ExampleClass" тут, конструктор якого бере об'єкт Context і створює змінну екземпляра класу "appContext". Потім ваш клас активності (або будь-який інший клас з цього питання) може викликати метод 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 підстановка контексту:

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(), але де це можливо, використовуйте контекст, переданий із діяльності, служби тощо.

Використовуйте контекст програми:

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

Що я помітив: Якщо ви створили свій лінійний макет за допомогою applicationContext, то, коли ви натиснете на спінер у своїй діяльності, у вас з’явиться невдале виняток, який виходить з віртуальної машини dalvik (не з вашого коду, тому я витратив чимало часу, щоб знайти, де була моя помилка ...).

Якщо ви використовуєте baseContext, то це все в порядку, контекстне меню відкриється, і ви зможете вибирати серед своїх варіантів.

Тож ось мій висновок: я вважаю, що (я не перевіряв його далі), ніж базовий контекст потрібен, коли ви маєте справу з контекстуванням у своїй діяльності ...

Тест був виконаний з кодуванням API 8 та протестований на HTC Desire, android 2.3.3.

Я сподіваюся, що мій коментар поки що не набридв вам і бажаю всього найкращого. Щасливе кодування ;-)


Я завжди використовував "це" під час створення переглядів у діяльності. Виходячи з того, що якщо активність перезапуститься, перегляди переглянуті, і, можливо, з’явиться новий контекст, який слід використовувати для повторного перегляду поглядів. Недолік, опублікований в блозі розробника, в той час як ImageView призначений для використання, що використовується для малювання / растрового зображення, може зависати в цьому контексті. Тим не менш це я зараз роблю. Щодо коду в іншому додатку (звичайні класи), я просто використовую контекст програми як його не характерний для будь-якої діяльності чи елементів інтерфейсу.
JonWillis

6

По-перше, я погоджуюся, що ми повинні використовувати appcontext, коли це можливо. то "це" в діяльності. У мене ніколи не було потреби в базовому контексті.

У моїх тестах, у більшості випадків їх можна замінити. У більшості випадків причина, з якою ви хочете розібратися в контексті, - це доступ до файлів, налаштувань, баз даних тощо. Ці дані в кінцевому підсумку відображаються як файли в папці приватних даних вашого додатка (/ data / data /). Незалежно від того, який контекст ви не використовуєте, вони будуть відображені в одній папці / файлах, щоб ви були в порядку.

Це те, що я спостерігав. Можливо, є випадки, коли ви повинні їх відрізняти.


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

3

У деяких випадках ви можете використовувати контекст діяльності над контекстом програми, коли запускаєте щось у потоці. Коли потік завершує виконання і вам потрібно повернути результат назад до активності виклику, вам потрібен цей контекст з обробником.

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

2

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

getApplicationContext()як випливає з назви методу, ваш додаток дасть вам інформацію про додатки, до яких можна отримати доступ з будь-якої точки програми. Таким чином, ви можете скористатися цим в службовій прив'язці, реєстрації трансляції тощо, Application contextбуде живим до завершення роботи програми.

getActivity()або thisповідомить вашій програмі про поточний екран, на якому видно також дані про рівень програми, надані користувачем application context. Тому все, що ви хочете знати про поточний екран, як Window ActionBar Fragementmangerі так, є у цьому контексті. В основному і Activityпоширюються Context. Цей контекст буде живий, поки поточний компонент (активність) не стане живим


1

Плутанина випливає з того, що існує безліч способів доступу до Контексту, при цьому (на поверхні) немає помітних відмінностей. Нижче наведено чотири найпоширеніші способи доступу до контексту в рамках діяльності.

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

Що таке контекст? Мені особисто подобається вважати Context як стан вашої заявки в будь-який момент. Контекст програми являє собою глобальну або базову конфігурацію вашої програми, а діяльність або послуга може будуватись на ній і являє собою екземпляр конфігурації вашої програми або перехідний стан для неї.

Якщо ви подивитесь на джерело для android.content.Context, ви побачите, що Context - це абстрактний клас, а коментарі до класу такі:

Інтерфейс до глобальної інформації про середовище програми. Це абстрактний клас, реалізацію якого забезпечує система Android. Це дозволяє отримати доступ до application-specificресурсів і класів, а також додаткові виклики для application-levelтаких операцій, як запуск діяльності, трансляція та отримання намірів і т. Д. Те, що я забираю від цього, полягає в тому, що Context забезпечує загальну реалізацію для доступу до рівня додатків, а також до системного рівня ресурси. До ресурсів рівня додатків може бути доступ до таких речей, як String ресурси [getResources()]або активи, [getAssets()]а ресурс на рівні системи - це все, з чим ви маєте доступContext.getSystemService().

Власне, погляньте на коментарі щодо методів, і вони ніби підкріплюють це поняття:

getSystemService(): Поверніть ручку в system-levelслужбу по імені. Клас повернутого об'єкта залежить від запитуваного імені. getResources(): Поверніть екземпляр ресурсів для пакету вашої програми. getAssets(): Поверніть екземпляр ресурсів для пакету вашої програми. Можливо, варто зазначити, що в абстрактному класі Контексту всі перераховані вище методи абстрактні! Лише один екземпляр 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.