Форум программистов, компьютерный форум, киберфорум
mobDevWorks
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  

Context и глубины Android

Запись от mobDevWorks размещена 18.07.2025 в 17:20
Показов 12159 Комментарии 0

Нажмите на изображение для увеличения
Название: Context и глубины Android.jpg
Просмотров: 385
Размер:	228.4 Кб
ID:	10999
В Android разработки Context напоминает воздух - он везде, жизненно необходим, но мало кто может детально объяснить его природу. Мы привыкли получать его как параметр, передавать дальше и использовать для доступа к ресурсам, запуска активностей или получения системных сервисов. Но что на самом деле происходит внутри этого объекта? Откуда он берется? Почему бывает нескольких видов? И главное - почему неправильное использование Context может привести к утечкам памяти или даже крешам приложения?

Я видел немало кода, где Context используется как черный ящик - "оно просто работает, и хватит". Иногда это приводит к странным багам, которые проявляются только в продакшене и только на определенных устройствах. Анализируя код опенсорс-проектов, я наблюдал как опытные разработчики порой делают ошибки в обращении с Context. Неправильно хранят ссылки, используют Activity вместо ApplicationContext, игнорируют жизненый цикл или вовсе забывают о многопроцессности Android.

Помню случай, когда в одном крупном приложении была странная проблема - оно изредка крешилось после возвращения из бэкграунда, но только на некоторых устройствах Samsung. Все указывало на проблему с Context. Несколько дней отладки, чтение исходников фреймворка Android - и да, причина была в неправильном хранении ссылки на Activity Context в классе-синглтоне. Суть проблемы в том, что Context - это не просто интерфейс для доступа к системным ресурсам. Это сложная иерархическая структура, тесно связанная с жизненным циклом компонентов приложения и даже с процессной моделью Android. Сегодня я хочу глубоко погрузиться в эту тему и разобраться, что же скрывается под капотом, как на самом деле функционирует Context и как избежать типичных ошибок, связаных с ним.

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

Внутренняя архитектура Context



Давай разберемся, что же такое Context изнутри. Многие разработчики воспринимают его как черную магию, но на самом деле все куда прозаичнее, хотя и достаточно сложно. В основе архитектуры Context лежит иерархия классов, где каждый уровень добавляет свою функциональность и специализацию. Эта структура напоминает матрешку - снаружи видна только оболочка, но внутри скрывается настоящий механизм.

Application Context vs Activity Context



Первое, что нужно понять - Context бывает разных видов, и они не взаимозаменяемы. Основные типы:

ApplicationContext - живет весь жизненый цикл приложения,
ActivityContext - привязан к жизненному циклу конкретной Activity,
ServiceContext - связан с жизненным циклом Service,
ReceiverRestrictedContext - временный контекст для BroadcastReceiver.

Многие думают, что разница только в длительности жизни, но это не так. Например, ApplicationContext лишен UI-возможностей - попробуйте с его помощью создать диалог без явного указания темы, и получите крэш. С другой стороны, Activity имеет все UI-фичи, но хранить ссылку на него дольше, чем живет сама Activity - верный путь к утечке памяти.

На самом деле в ядре этой системы лежит два ключевых класса:
ContextImpl - реальная рабочая лошадка, выполняющая всю работу,
ContextWrapper - делегирующая обертка, которая перенаправляет вызовы

Интересно, что знакомые нам Activity, Service и Application - это не прямые наследники Context, а наследники ContextWrapper. Система создает для каждого из них экземпляр ContextImpl, который и выполняет всю реальную работу.

Java
1
2
3
4
5
6
7
8
9
10
11
12
// Упрощенная схема
public class ContextWrapper extends Context {
    Context mBase; // Обычно это ContextImpl
    
    public Resources getResources() {
        return mBase.getResources();  // Делегирование
    }
}
 
public class Activity extends ContextWrapper {
    // Добавляет UI-специфичные возможности
}

Жизненный цикл и управление памятью



Context тесно связан с жизненным циклом компонентов Android. Когда вы создаете Activity, система:
1. Создает ContextImpl для этой Activity.
2. Связывает его с объектом Activity через метод attach().
3. Когда Activity уничтожается, ссылки на ContextImpl удаляются.

Вот почему хранение ссылки на Activity после его уничтожения - проблема. Технически Activity может быть уничтожен (например, при повороте экрана), но если где-то осталась ссылка на него, сборщик мусора не сможет освободить память.

Я часто вижу такой код:
Java
1
2
3
4
5
6
7
class SingletonManager {
    private static Context context;
    
    public static void init(Context context) {
        SingletonManager.context = context; // Потенциальная утечка!
    }
}
И в Activity:
Java
1
SingletonManager.init(this); // Передаем Activity
Это бомба замедленого действия. Правильно использовать:
Java
1
SingletonManager.init(getApplicationContext());

Связь с системными службами



Одна из ключевых функций Context - предоставлять доступ к системным сервисам Android. Когда вы вызываете:

Java
1
2
LocationManager locationManager = 
    (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
Происходит сложная цепочка событий:
1. ContextImpl обращается к хранимому им кэшу системных сервисов.
2. Если сервис еще не был запрошен, инициируется IPC-вызов через Binder.
3. Запрос поступает в ServiceManager в системном процессе.
4. ServiceManager находит нужный сервис и возвращает его IBinder интерфейс.
5. Context создает клиентскую обертку (в данном случае LocationManager) над полученным IBinder.
6. Обертка кэшируется для последующих запросов.
Важный момент: возвращаемый Context.getSystemService() объект - это не сам системный сервис, а прокси-объект, который общается с реальным сервисом через Binder IPC. Сами системные сервисы работают в отдельном процессе system_server, а не в процессе приложения.

Механизмы кэширования и переиспользования Context



ContextImpl активно использует кэширование для оптимизации. Например, при каждом вызове getResources() не создается новый объект Resources - возвращается закэшированный экземпляр. То же самое происходит с системными сервисами, базами данных, файловыми директориями и другими ресурсами. Механизм кэширования внутри ContextImpl выглядит примерно так:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private HashMap<String, Object> mServiceCache;
 
public Object getSystemService(String name) {
    if (mServiceCache == null) {
        mServiceCache = new HashMap<String, Object>();
    }
    Object service = mServiceCache.get(name);
    if (service != null) {
        return service;  // Возвращаем из кэша
    }
    // Иначе получаем сервис, кэшируем и возвращаем
    service = createService(name);
    mServiceCache.put(name, service);
    return service;
}
Это важно для производительности - получение некоторых системных сервисов может быть ресурсоемкой операцией, особенно если требуется IPC-взаимодействие.

Context как медиатор между приложением и системой Android



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

Все эти действия проходят через Context. Он выступает посредником, который:
1. Идентифицирует ваше приложение для системы.
2. Обеспечивает доступ к ресурсам приложения.
3. Инициирует системные операции от имени приложения.
4. Управляет жизненным циклом компонентов.

Это делает Context одним из самых важных классов в Android, и именно поэтому его правильное использование критично для стабильной работы приложения. Важно отметить, что некоторые компоненты Android вообще не являются наследниками Context. В отличие от Activity и Service, классы BroadcastReceiver и ContentProvider - это не Context-ы. Они получают Context извне и используют его для доступа к системе. Возьмем BroadcastReceiver. Когда система вызывает метод onReceive(), она передает ему специальный временный Context:

Java
1
2
3
4
5
@Override
public void onReceive(Context context, Intent intent) {
    // Внимание! Этот context живет только во время выполнения onReceive
    // Сохранять его где-то - плохая идея
}
Для зарегистрированных в манифесте ресиверов этот Context называется ReceiverRestrictedContext и имеет ограниченные возможности. Он нужен только для выполнения непосредственных операций во время обработки бродкаста. Причем в новых версиях Android через него даже нельзя запускать фоновые сервисы.
А что насчет ContentProvider? Он получает Context через метд attachInfo():

Java
1
2
3
4
5
@Override
public void attachInfo(Context context, ProviderInfo info) {
    super.attachInfo(context, info);
    // Теперь можно использовать context
}
Обычно ContentProvider получает ApplicationContext, поскольку живет на протяжении всего времени работы приложения.
Ещё один интересный аспект - Activity получает не просто ContextWrapper, а ContextThemeWrapper, который добавляет поддержку UI-тем:

Java
1
2
3
4
5
public class ContextThemeWrapper extends ContextWrapper {
    private Resources.Theme mTheme;
    private LayoutInflater mInflater;
    // ...
}
Именно поэтому через Activity можно нормально надувать лейауты и использовать атрибуты темы, а через ApplicationContext - нет.

Все это часть глубоко продуманной архитектуры. Context - это не монолитный класс, а система специализированных компонентов, каждый из которых оптимизирован под свои задачи. Эта дифференциация позволяет Android эффективно управлять ресурсами и поддерживать правильный жизненный цикл компонентов. Стоит также разобраться, как Context взаимодействует с Binder - ключевым механизмом межпроцессного взаимодействия в Android. Когда ваш Context запрашивает системный сервис или запускает Activity, это не просто вызов метода - это сложная последовательность IPC-операций.
Например, когда вы вызываете startActivity(), происходит примерно следующее:

1. Context.startActivity() делегирует вызов в ActivityManager через IPC.
2. ActivityManager (точнее, ActivityTaskManagerService с Android 10) запускает в системном процессе.
3. ATMS проверяет разрешения, определяет целевой процесс и т.д..
4. Если процесс не запущен, ATMS просит Zygote форкнуть новый процесс.
5. ATMS отправляет сообщение процессу через Binder IPC с инструкцией создать Activity.
6. ActivityThread в процессе создает Activity и связанный с ним ContextImpl.

Не всегда нужно понимать все эти детали, но знание того, что Context - это часть сложной системы межпроцессных взаимодействий, помогает лучше осознавать последствия вызовов его методов.

Программирование Android. Context
Добрый день! Прошу подсказать, почему в доках...

Could not find method showFileChooser(View) in a parent or ancestor Context for android:onClick
Есть ошибка возможно ктото знает как решить? public static boolean AskPermissions(Context...

java.lang.IllegalStateException: Could not find method lesson1go(View) in a parent or ancestor Context for android:onCli
При нажатии на текст (любой) лог не выводится, а выдает ошибку Что выдает Logcat: 2021-11-18...

Android Studio, импорт не видит в проекте import android.annotation.AttrRes? - Android
Android Studio не видит классы из пэкэджа android, хотя он есть. На скрине видно, открыт класс...


Как Android создает и управляет Context



Чтобы полностью понять, как работает Context, необходимо разобраться в том, как он создается и управляется системой Android. Давайте рассмотрим весь жизненный путь Context от момента запуска приложения до его уничтожения.

Процесс инициализации приложения



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

1. Launcher отправляет Intent в ActivityManagerService (AMS)
2. AMS определяет, что приложение ещё не запущено
3. AMS обращается к Zygote для создания нового процесса

Zygote - это ключевой компонент в архитектуре Android. Название пришло из биологии, где зигота - это первичная клетка, из которой развивается организм. В Android Zygote - это предварительно инициализированный процесс, который содержит загруженную Android Runtime и все базовые классы фреймворка. Именно от него "отпочковываются" все процессы приложений. Раньше Zygote просто форкал новый процесс по требованию. Но с Android 10 появилась технология USAP (Unspecialized App Process) - пул уже форкнутых, но еще не специализированных процессов, ожидающих назначения.

Java
1
2
3
4
5
6
// Псевдокод процесса создания приложения
// В системном процессе:
Process newProcess = Zygote.forkProcess();
// или в Android 10+:
Process newProcess = UsapPool.getProcess();
newProcess.specialize(appInfo);
После создания процесса запускается его точка входа - метод ActivityThread.main(). И вот тут начинается самое интересное.

Роль ActivityThread и LoadedApk



ActivityThread - это не поток (Thread) в буквальном смысле, а класс, который управляет выполнением основного потока приложения. Несмотря на своё название, он отвечает за все компоненты: Activity, Service, BroadcastReceiver и ContentProvider.
Первое, что делает ActivityThread.main():
1. Создаёт объект ActivityThread.
2. Настраивает Looper для главного потока.
3. Вызывает метод attach(), который связывает процесс с AMS.

Java
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
    // ...
    Looper.prepareMainLooper(); // Настройка обработчика сообщений
    ActivityThread thread = new ActivityThread();
    thread.attach(false); // Связывание с системой
    
    Looper.loop(); // Запуск обработки сообщений
    // Если мы здесь, значит произошла ошибка
    throw new RuntimeException("Main thread loop unexpectedly exited");
}
Во время метода attach() происходит первое создание Context - создается ApplicationContext:
1. ActivityThread получает информацию о пакете приложения (LoadedApk).
2. Создается экземпляр ContextImpl для приложения.
3. Создается экземпляр Application (указанный в манифесте).
4. ContextImpl связывается с Application через attachBaseContext().
5. Вызывается Application.onCreate().

LoadedApk - это класс, который содержит всю информацию о пакете приложения: ресурсы, классы, манифест и т.д. Он играет ключевую роль в создании Context, предоставляя необходимую информацию о приложении.

Механизмы передачи контекста между компонентами



Когда AMS решает запустить Activity в процессе, он отправляет сообщение через Binder в ActivityThread. ActivityThread обрабатывает это сообщение в методе handleLaunchActivity():

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
public void handleLaunchActivity(ActivityClientRecord r, ...) {
    // Создание контекста для Activity
    ContextImpl appContext = createBaseContextForActivity(r);
    
    // Создание Activity
    Activity activity = mInstrumentation.newActivity(...);
    
    // Связывание Activity с контекстом
    activity.attach(appContext, ...);
    
    // Запуск жизненного цикла
    mInstrumentation.callActivityOnCreate(activity, ...);
}
Здесь для каждой Activity создается отдельный ContextImpl. Хотя все ContextImpl в одном процессе разделяют некоторые ресурсы (например, LoadedApk), каждый имеет свою конфигурацию, связанную с конкретным экземпляром Activity.
Передача контекста между компонентами обычно происходит через параметры методов. Например, когда вы запускаете Service из Activity:

Java
1
2
Intent intent = new Intent(this, MyService.class);
startService(intent);
Ваш текущий Context (Activity) используется для создания Intent и передачи его в AMS. AMS затем инструктирует процесс создать Service, и для этого Service создается новый ContextImpl. Стоит отметить важный нюанс: создание нового ContextImpl не означает создание полностью изолированного окружения. Все ContextImpl в одном процессе разделяют одно и то же приложение, ресурсы, классы и т.д. Они отличаются своей конфигурацией, жизненным циклом и некоторыми специфическими возможностями. Изнутри ActivityThread содержит таблицы всех активных компонентов и их контекстов. Когда приходит сообщение от системы (например, о необходимости паузы Activity), ActivityThread находит нужный компонент по его токену и вызывает соответствующий метод жизненного цикла.

Java
1
2
3
4
5
6
7
8
9
10
final HashMap<IBinder, ActivityClientRecord> mActivities = new HashMap<>();
final HashMap<IBinder, Service> mServices = new HashMap<>();
// ...
 
public void handlePauseActivity(IBinder token, ...) {
    ActivityClientRecord r = mActivities.get(token);
    if (r != null) {
        performPauseActivity(r, ...);
    }
}

Хуки и перехватчики в системе Context



Помимо стандартных механизмов, Android предоставляет несколько способов для перехвата и модификации поведения Context. Самый известный - это ContextWrapper, о котором мы уже упоминали. Но есть и другие, менее очевидные механизмы. Одним из таких механизмов является использование прокси-классов в инструментировании. Когда приложение запускается под Instrumentation (например, при запуске тестов), стандартное поведение Context может быть перехвачено и модифицировано. Этим активно пользуются системы тестирования вроде Espresso и Robolectric.

Java
1
2
3
4
5
6
7
8
9
10
public class TestInstrumentation extends Instrumentation {
    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) {
        // Перехватываем создание активити и можем вернуть
        // модифицированную версию
        Activity activity = super.newActivity(cl, className, intent);
        // Можем внедрить мок-сервисы или другие модификации
        return activity;
    }
}
Другой малоизвестный механизм - ContextHook, используемый внутри системы Android. Он позволяет системе внедрять дополнительную функциональность в Context без изменения его основного кода.

Влияние мультипроцессности на работу Context



Android изначально проектировался как мультипроцессная система. Каждое приложение может запускать несколько процессов, указав атрибут android:process в манифесте. Это влияет на работу Context самым непосредственным образом.
Каждый процесс получает свой собственный ApplicationContext, даже если эти процессы принадлежат одному приложению. То есть, если у вас в манифесте указано:

XML
1
2
3
4
5
6
<activity 
    android:name=".MainActivity" 
    android:process=":main" />
<service 
    android:name=".MyService" 
    android:process=":background" />
То MainActivity и MyService будут работать в разных процессах, каждый со своим ApplicationContext. Они не смогут напрямую обмениваться объектами через статические переменные или синглтоны - потребуется межпроцессное взаимодействие (IPC).
Я часто видел ошибки, когда разработчики пытались передать сложные объекты между компонентами в разных процессах через Intent:

Java
1
2
3
4
5
// В процессе :main
ComplexObject obj = new ComplexObject();
Intent intent = new Intent(this, MyService.class);
intent.putExtra("object", obj); // Крэш на рантайме - объект не Parcelable!
startService(intent);
Для IPC Android требует, чтобы объекты были Parcelable или Serializable. Но даже в этом случае важно понимать, что происходит не передача ссылки, а сериализация-десериализация. То есть в другом процессе вы получите копию объекта, а не оригинал.

Взаимодействие Context с Binder и межпроцессным взаимодействием



Context тесно интегрирован с системой межпроцессного взаимодействия Binder. Многие методы Context, такие как startActivity(), bindService(), sendBroadcast(), на самом деле инициируют Binder-транзакции. Например, когда вы вызываете context.startActivity(), на низком уровне происходит:
1. Создание транзакции Binder для ActivityManagerService
2. Сериализация Intent и других параметров в Parcel
3. Передача транзакции через драйвер Binder в процесс system_server
4. Обработка запроса в ActivityManagerService
5. Инициация запуска Activity (возможно, в другом процессе)
6. Передача результата обратно через Binder

Вот упрощенная схема того, как это выглядит в коде:

Java
1
2
3
4
5
6
7
8
9
// В ContextImpl.startActivity()
try {
    intent.prepareToLeaveProcess(this);
    ActivityTaskManager.getService()
        .startActivity(whoThread, who.getBasePackageName(),
            intent, ... );
} catch (RemoteException e) {
    throw new RuntimeException("Failure from system", e);
}
ActivityTaskManager.getService() возвращает IActivityTaskManager.Stub.Proxy - это прокси-объект, реализующий интерфейс IActivityTaskManager, но все вызовы переправляющий через Binder в процесс system_server.

Интересно, что многие методы Context генерируют исключение RemoteException, которое потом заворачивается в RuntimeException. Это происходит потому, что операция фактически удаленная - она выполняется в другом процессе.

Особенности работы Context в многопоточной среде



Context создан для использования в многопоточной среде, но с некоторыми оговорками. Большинство методов Context потокобезопасны, но не все. Например, доступ к SharedPreferences через Context требует осторожности:

Java
1
2
3
// В разных потоках - потенциальная гонка данных
context.getSharedPreferences("prefs", MODE_PRIVATE).edit().putString("key", "value1").apply();
context.getSharedPreferences("prefs", MODE_PRIVATE).edit().putString("key", "value2").apply();
Кроме того, все методы Context, которые взаимодействуют с UI (например, показ Toast или Dialog), должны вызываться из главного (UI) потока. Android строго разделяет UI-операции и фоновые вычисления. Есть еще один нюанс: Context.getApplicationContext() может возвращать разные объекты в разных потоках, если они принадлежат разным процессам приложения. Не стоит полагаться на сравнение этих объектов по ссылке (==) для определения, находитесь ли вы в том же самом процессе.

Для работы с Context в многопоточной среде я рекомендую:
1. Избегать хранения ссылок на Context в статических полях.
2. Всегда проверять, не уничтожен ли компонент, прежде чем использовать его Context.
3. Использовать для UI-операций только главный поток.
4. Быть осторожным с доступом к ресурсам из нескольких потоков.
Понимание этих особеностей работы Context позволит избежать многих сложных и труднодиагностируемых проблем в ваших приложениях.

Практические проблемы и их решения



Теория Context важна, но еще важнее понимать, как избежать распространенных проблем с ним в реальных приложениях. За годы работы я повидал множество случаев, когда неправильное использование Context приводило к серьезным багам и падениям.

Утечки памяти через Context



Самая распространенная проблема связана с утечками памяти. Классический сценарий: хранение ссылки на Activity в объекте с более долгим жизненным циклом. Я натыкался на такой код сотни раз:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ImageLoader {
private static ImageLoader instance;
private Context context; // Опасная ссылка!
 
private ImageLoader(Context context) {
    this.context = context;
}
 
public static ImageLoader getInstance(Context context) {
    if (instance == null) {
        instance = new ImageLoader(context);
    }
    return instance;
}
}
И где-то в Activity:
Java
1
ImageLoader.getInstance(this); // Передаем Activity в синглтон
Когда Activity уничтожается (например, при повороте экрана), она не может быть собрана сборщиком мусора, поскольку синглтон удерживает на нее ссылку. Это приводит к утечке памяти и, возможно, к странному поведению приложения. Решение? Всегда используйте ApplicationContext для долгоживущих объектов:

Java
1
2
3
4
5
6
7
public static ImageLoader getInstance(Context context) {
    if (instance == null) {
        // Берем ApplicationContext, а не переданный напрямую
        instance = new ImageLoader(context.getApplicationContext());
    }
    return instance;
}

Выбор правильного типа контекста



Другая частая проблема - использование неподходящего типа Context для конкретной задачи. Например, вызов getApplicationContext() там, где нужен контекст Activity с темой:

Kotlin
1
2
3
4
5
// Ошибка: ApplicationContext не имеет темы
val dialog = AlertDialog.Builder(applicationContext).create()
 
// Правильно: используем Activity как контекст с темой
val dialog = AlertDialog.Builder(this).create()
Или противоположная ситуация - использование Activity там, где достаточно ApplicationContext:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
// Неэффективно: Activity контекст избыточен и может вызвать утечку
private fun scheduleJob(context: Context) {
    jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    // ...
}
 
// Лучше:
private fun scheduleJob(context: Context) {
    jobScheduler = context.applicationContext
        .getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    // ...
}
Простое правило: если вам нужны UI-возможности (темы, инфлейшн лейаутов, диалоги) - используйте Activity. Для всего остального - ApplicationContext.

Безопасная работа с системными ресурсами



Context дает доступ к различным системным ресурсам, но не все ресурсы доступны из всех типов контекстов. Например, некоторые операции запрещены в ReceiverRestrictedContext:

Kotlin
1
2
3
4
5
6
7
8
9
// В BroadcastReceiver.onReceive():
override fun onReceive(context: Context, intent: Intent) {
    // Начиная с Android 8.0, это будет работать только если приложение на переднем плане
    context.startService(Intent(context, MyService::class.java))
    
    // Лучше использовать JobScheduler или WorkManager
    val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    // ...
}
Еще одна распространенная проблема - использование Context после его уничтожения. Типичный пример - асинхронные операции:

Kotlin
1
2
3
4
5
6
7
8
9
10
// Потенциальная проблема: Activity может быть уничтожена к моменту завершения задачи
fun loadData() {
    CoroutineScope(Dispatchers.IO).launch {
        val data = fetchDataFromNetwork()
        withContext(Dispatchers.Main) {
            // Если Activity уничтожена, это вызовет крэш
            updateUI(data)
        }
    }
}
Решение? Проверять, жив ли контекст, или использовать архитектурные компоненты, которые учитывают жизненный цикл:

Kotlin
1
2
3
4
5
6
7
8
// Безопасное использование с lifecycle-aware компонентами
fun loadData() {
    lifecycleScope.launch {
        val data = fetchDataFromNetwork()
        // Корутина будет автоматически отменена, если lifecycle завершится
        updateUI(data)
    }
}

Тестирование компонентов с зависимостями от Context



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

Kotlin
1
2
3
4
5
6
7
8
9
10
11
interface ResourceProvider {
    fun getString(resId: Int): String
    fun getDrawable(resId: Int): Drawable?
    // ...
}
 
class ContextResourceProvider(private val context: Context) : ResourceProvider {
    override fun getString(resId: Int): String = context.getString(resId)
    override fun getDrawable(resId: Int): Drawable? = ContextCompat.getDrawable(context, resId)
    // ...
}
Теперь вы можете легко мокировать ResourceProvider в тестах:

Kotlin
1
2
3
4
5
6
// В тесте:
val mockProvider = mock(ResourceProvider::class.java)
`when`(mockProvider.getString(R.string.app_name)).thenReturn("Test App")
 
val viewModel = MyViewModel(mockProvider)
// Теперь можно тестировать без реального Context

Производительность Context: узкие места и оптимизация



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

Kotlin
1
2
3
4
5
fun isNetworkAvailable(): Boolean {
    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val networkInfo = connectivityManager.activeNetworkInfo
    return networkInfo != null && networkInfo.isConnected
}
Проблема в том, что при каждом вызове метода происходит поиск системного сервиса, что включает в себя относительно дорогие операции. Если этот метод вызывается в цикле или часто выполняющемся коде, производительность может заметно пострадать. Правильный подход - кэшировать ссылки на системные сервисы:

Kotlin
1
2
3
4
5
6
7
8
9
class NetworkUtils(private val context: Context) {
    private val connectivityManager = context.applicationContext
        .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    
    fun isNetworkAvailable(): Boolean {
        val networkInfo = connectivityManager.activeNetworkInfo
        return networkInfo != null && networkInfo.isConnected
    }
}
Еще одно узкое место - доступ к ресурсам. Метод context.getResources() сам по себе оптимизирован и возвращает кэшированный объект Resources. Но последующие вызовы, например getString() или getDrawable(), могут быть достаточно затратными, особенно для сложных ресурсов.

Я замечал, что в адаптерах RecyclerView часто встречается такой код:

Kotlin
1
2
3
4
5
6
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val item = items[position]
    holder.title.text = context.getString(R.string.item_title, item.name)
    holder.icon.setImageDrawable(ContextCompat.getDrawable(context, item.iconResId))
    // ...
}
Если список большой, это может привести к заметным лагам при прокрутке. Решение - предварительно загружать и кэшировать часто используемые ресурсы:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyAdapter(context: Context, private val items: List<Item>) : RecyclerView.Adapter<ViewHolder>() {
    private val titleFormat = context.getString(R.string.item_title)
    private val iconCache = SparseArray<Drawable>()
    
    init {
        // Предзагрузка иконок для частых элементов
        items.distinctBy { it.iconResId }.forEach { item ->
            ContextCompat.getDrawable(context, item.iconResId)?.let {
                iconCache.put(item.iconResId, it)
            }
        }
    }
    
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = items[position]
        holder.title.text = String.format(titleFormat, item.name)
        holder.icon.setImageDrawable(iconCache.get(item.iconResId))
        // ...
    }
}
Особое внимание стоит уделить работе с Context в многопоточной среде. Хотя большинство методов Context потокобезопасны, некоторые операции (особенно с UI-компонентами) должны выполняться в главном потоке.

Я часто вижу неоптимальный код вроде:

Kotlin
1
2
3
4
5
// В фоновом потоке
val bitmap = loadBitmapFromNetwork()
runOnUiThread {
    ImageView(context).setImageBitmap(bitmap) // Создание ImageView в UI-потоке
}
Создание View через Context - операция не из дешевых. Лучше заранее создать все необходимые View и просто обновлять их состояние в UI-потоке:

Kotlin
1
2
3
4
5
6
7
8
9
// Заранее создаем View
val imageView = ImageView(context)
layout.addView(imageView)
 
// В фоновом потоке
val bitmap = loadBitmapFromNetwork()
runOnUiThread {
    imageView.setImageBitmap(bitmap) // Только обновление, без создания
}
Одна из самых неочевидных проблем производительности связана с конфигурационными изменениями (например, поворот экрана). При каждом повороте:
1. Существующая Activity уничтожается
2. Все её ресурсы освобождаются (теоретически)
3. Создается новая Activity с новым Context
4. Все ресурсы загружаются заново

Если у вас есть тяжелые ресурсы, которые не меняются при смене конфигурации, разумно использовать ViewModel или другие механизмы сохранения состояния, чтобы избежать повторной загрузки.

Нестандартные применения Context



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

Кастомные обертки и паттерны



Одна из самых удачных техник, которую я часто использую - создание специализированных оберток вокруг Context. Это позволяет изолировать специфическую функциональность и сделать код более тестируемым.
Например, вместо прямого использования SharedPreferences можно создать обертку:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class PreferencesWrapper(private val context: Context) {
    private val prefs by lazy {
        context.applicationContext.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
    }
    
    fun saveUserToken(token: String) {
        prefs.edit().putString("user_token", token).apply()
    }
    
    fun getUserToken(): String? {
        return prefs.getString("user_token", null)
    }
    
    // Другие методы...
}
Это дает массу преимуществ: централизованный доступ к преференсам, возможность добавления дополнительной логики (валидация, шифрование), а также легкую мокируемость в тестах.

Интеграция с архитектурными решениями



Context можно элегантно интегрировать с современными архитектурными подходами. Вот как я внедряю его в паттерн Repository:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class UserRepository(private val contextProvider: ContextProvider) {
    fun getUserProfile(): UserProfile {
        val context = contextProvider.getContext()
        // Получение данных из локального хранилища или сети
        return UserProfile(...)
    }
}
 
// Интерфейс-провайдер для инверсии зависимостей
interface ContextProvider {
    fun getContext(): Context
}
 
// Реализация для продакшена
class AppContextProvider(private val app: Application) : ContextProvider {
    override fun getContext() = app
}
 
// Реализация для тестов
class TestContextProvider(private val testContext: Context) : ContextProvider {
    override fun getContext() = testContext
}
Такой подход делает репозиторий независимым от конкретной реализации Context, что критично для архитектурно правильного кода.

Lazy-инициализация и Context factories



Еще один полезный паттерн - фабрики контекста с отложенной инициализацией. Это особенно полезно в многомодульных проектах, где нужно предоставить Context в модули, которые не имеют прямого доступа к Application:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// В базовом модуле:
object ContextRegistry {
    private var applicationContext: Context? = null
    
    fun init(context: Context) {
        if (applicationContext == null) {
            applicationContext = context.applicationContext
        }
    }
    
    fun getContext(): Context = applicationContext 
        ?: throw IllegalStateException("Context not initialized")
    
    // Можно добавить cleanup для тестов
    fun reset() {
        applicationContext = null
    }
}
 
// Использование в модулях:
class FeatureManager {
    fun doSomething() {
        val context = ContextRegistry.getContext()
        // Использование контекста
    }
}
В реальных проектах я обычно заменяю этот синглтон на Dagger/Hilt модуль, который предоставляет Context через DI.

Реактивные Context-провайдеры с RxJava и Kotlin Coroutines



С появлением реактивного программирования стало возможным создавать Context-провайдеры, которые реагируют на изменения жизненого цикла:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ReactiveContextProvider(private val lifecycle: Lifecycle) {
    private val contextFlow = MutableStateFlow<Context?>(null)
    
    init {
        lifecycle.addObserver(object : LifecycleEventObserver {
            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    contextFlow.value = null
                } else if (event == Lifecycle.Event.ON_CREATE) {
                    contextFlow.value = source as? Context
                }
            }
        })
    }
    
    fun getContextFlow(): Flow<Context?> = contextFlow
    
    suspend fun awaitContext(): Context {
        return contextFlow.filterNotNull().first()
    }
}
Такой подход позволяет асинхронно работать с Context, автоматически учитывая его жизненный цикл - что особенно ценно в сложных асинхронных операциях.

Паттерн Context Delegation для сложных иерархий



Когда нужно создать собственную реализацию Context с дополнительной функциональностью, стандартный подход через наследование от ContextWrapper иногда бывает недостаточно гибким. В таких случаях я использую паттерн делегирования:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class TrackingContext(private val baseContext: Context) : Context by baseContext {
    override fun startActivity(intent: Intent) {
        // Логирование или отслеживание аналитики перед запуском Activity
        analyticsTracker.trackScreenOpen(intent.component?.className ?: "unknown")
        baseContext.startActivity(intent)
    }
    
    override fun getSystemService(name: String): Any? {
        // Можно перехватывать запросы к определенным сервисам
        return when(name) {
            Context.LOCATION_SERVICE -> {
                // Возвращаем модифицированный или мониторящий сервис
                LocationServiceWrapper(baseContext.getSystemService(name) as LocationManager)
            }
            else -> baseContext.getSystemService(name)
        }
    }
}
Этот паттерн намного мощнее, чем может показаться на первый взгляд. С его помощью можно реализовать сложную логику перехвата и модификации поведения Context, не переопределяя десятки методов.

Мокирование Context в юнит-тестах



Тестирование кода, зависящего от Context - это отдельная песня, которая часто превращается в грустный блюз. Я столкнулся с этой проблемой, когда пытался покрыть тестами библиотеку, которая активно использовала Context для доступа к ресурсам и системным сервисам.

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

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class MockContext : Context() {
    private val resourceMap = mutableMapOf<Int, String>()
    private val serviceMap = mutableMapOf<String, Any>()
    private val permissionResults = mutableMapOf<String, Int>()
    
    // Устанавливаем ожидаемые значения для тестов
    fun setStringResource(id: Int, value: String) {
        resourceMap[id] = value
    }
    
    fun setSystemService(name: String, service: Any) {
        serviceMap[name] = service
    }
    
    fun setPermissionResult(permission: String, result: Int) {
        permissionResults[permission] = result
    }
    
    // Переопределяем только нужные методы
    override fun getString(id: Int): String {
        return resourceMap[id] ?: "mock_string_$id"
    }
    
    override fun getSystemService(name: String): Any? {
        return serviceMap[name]
    }
    
    override fun checkSelfPermission(permission: String): Int {
        return permissionResults[permission] ?: PackageManager.PERMISSION_DENIED
    }
    
    // Остальные методы реализуем минимально
    // или выбрасываем UnsupportedOperationException
    // ...
}
Это позволяет тестировать только нужную функциональность, не подгружая весь контекст операционной системы. В тестах использование выглядит так:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
fun testFeatureWithMockContext() {
    val mockContext = MockContext().apply {
        setStringResource(R.string.app_name, "Test App")
        setPermissionResult(Manifest.permission.CAMERA, PackageManager.PERMISSION_GRANTED)
        
        // Настраиваем моки для системных сервисов
        val mockLocationManager = mock(LocationManager::class.java)
        [INLINE]when[/INLINE](mockLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)).thenReturn(true)
        setSystemService(Context.LOCATION_SERVICE, mockLocationManager)
    }
    
    val feature = MyFeature(mockContext)
    val result = feature.doSomething()
    
    assertTrue(result)
}

Оптимизация производительности с Context



За годы работы я обнаружил несколько неочевидных техник оптимизации работы с Context. Одна из них связана с локализацией и ресурсами. Когда ваше приложение поддерживает много языков, при каждом вызове getString() система выполняет немало работы для определения текущей локализации и загрузки соответствующего ресурса.
Я создал кэширующую обертку, которая значительно ускоряет доступ к часто используемым строкам:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ResourceCache(context: Context) {
    private val appContext = context.applicationContext
    private val cache = ConcurrentHashMap<Pair<Int, Array<out Any>?>, String>()
    
    fun getString(resId: Int, vararg formatArgs: Any): String {
        val key = Pair(resId, formatArgs.takeIf { it.isNotEmpty() })
        return cache.getOrPut(key) {
            if (formatArgs.isEmpty()) {
                appContext.getString(resId)
            } else {
                appContext.getString(resId, *formatArgs)
            }
        }
    }
    
    fun clearCache() {
        cache.clear()
    }
}
Эта техника особенно полезна в адаптерах RecyclerView и других компонентах, где одни и те же строки запрашиваются многократно.

Другая техника касается оптимизации получения системных сервисов. Начиная с Android Marshmallow можно использовать typed-методы, которые избавляют от необходимости приведения типов:

Kotlin
1
2
3
4
5
// Вместо:
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
 
// Лучше использовать:
val locationManager = context.getSystemService(LocationManager::class.java)
Это не только делает код чище, но и позволяет компилятору проверить корректность типов, а JIT-компилятору лучше оптимизировать код.

Но не всегда Context - это только про производительность. Иногда речь идет о безопасности. Например, я создал обертку, которая защищает от обхода разрешений путем перехвата вызовов getSystemService:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SecureContext(private val baseContext: Context) : ContextWrapper(baseContext) {
    override fun getSystemService(name: String): Any? {
        // Перехватываем получение сервисов, требующих особых разрешений
        return when (name) {
            Context.TELEPHONY_SERVICE -> {
                // Проверяем, есть ли у приложения нужные разрешения
                if (checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE) 
                        != PackageManager.PERMISSION_GRANTED) {
                    SecureTelephonyManager() // Ограниченная реализация
                } else {
                    super.getSystemService(name)
                }
            }
            else -> super.getSystemService(name)
        }
    }
}
Такие обертки помогают создавать "песочницы" для сторонних библиотек, потенциально небезопасных, ограничивая их доступ к чувствительным API.

Полный пример: Context-aware архитектура



На протяжении своей карьеры я перепробовал разные подходы, но в последнее время остановился на одном особенно удачном. Назовем его условно "Context-aware архитектура". Суть в том, чтобы сделать работу с контекстом безопасной, тестируемой и гибкой через хорошо продуманные абстракции.

Проектирование архитектуры системы



Основа моего подхода - строгое разделение на слои:

1. Presentation layer - UI-компоненты, которые используют Context напрямую,
2. Domain layer - бизнес-логика, полностью независимая от Android и Context,
3. Data layer - работа с данными через абстракции Context-зависимых API.

Главное - изолировать Domain-слой от прямых зависимостей Context. Для этого нужно создать интерфейсы-провайдеры ресурсов:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
// В Domain-слое (чистая бизнес-логика)
interface ResourceProvider {
    fun getString(id: Int, vararg args: Any): String
    fun getQuantityString(id: Int, quantity: Int, vararg args: Any): String
    // другие методы...
}
 
interface PreferencesStore {
    fun saveString(key: String, value: String)
    fun getString(key: String, defaultValue: String = ""): String
    // другие методы...
}

Реализация Context-провайдера



Теперь создадим конкретные реализации в Data-слое:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// В Data-слое (Android-зависимый код)
class AndroidResourceProvider(private val context: Context) : ResourceProvider {
    override fun getString(id: Int, vararg args: Any): String {
        return if (args.isEmpty()) {
            context.getString(id)
        } else {
            context.getString(id, *args)
        }
    }
    
    override fun getQuantityString(id: Int, quantity: Int, vararg args: Any): String {
        return context.resources.getQuantityString(id, quantity, *args)
    }
}
 
class SharedPreferencesStore(context: Context) : PreferencesStore {
    private val prefs = context.applicationContext
        .getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
        
    override fun saveString(key: String, value: String) {
        prefs.edit().putString(key, value).apply()
    }
    
    override fun getString(key: String, defaultValue: String): String {
        return prefs.getString(key, defaultValue) ?: defaultValue
    }
}

Интеграция с DI-контейнерами



Самое интересное - как интегрировать это всё с системой внедрения зависимостей. Я предпочитаю Dagger/Hilt для этих целей:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    
    @Provides
    @Singleton
    fun provideContext(@ApplicationContext context: Context): Context {
        return context
    }
    
    @Provides
    @Singleton
    fun provideResourceProvider(context: Context): ResourceProvider {
        return AndroidResourceProvider(context)
    }
    
    @Provides
    @Singleton
    fun providePreferencesStore(context: Context): PreferencesStore {
        return SharedPreferencesStore(context)
    }
}
Теперь в ViewModel-ях и других компонентах можно использовать абстракции без прямой зависимости от Context:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@HiltViewModel
class ProfileViewModel @Inject constructor(
    private val userRepository: UserRepository,
    private val resourceProvider: ResourceProvider
) : ViewModel() {
    
    fun getUserGreeting(): LiveData<String> = liveData {
        val user = userRepository.getCurrentUser()
        val greeting = resourceProvider.getString(
            R.string.greeting_format, 
            user.name
        )
        emit(greeting)
    }
}
Самый неочевидный бонус такого подхода - автоматическое решение проблемы с жизненным циклом Context. Поскольку в DI-контейнер мы внедряем ApplicationContext, все компоненты, которые его используют, гарантированно не вызовут утечек памяти. Для случаев, когда нужен имено Activity-контекст (например, для диалогов), я создаю отдельный интерфейс:

Kotlin
1
2
3
4
5
6
7
8
9
10
interface UiContextProvider {
    fun getUiContext(): Context
}
 
// Реализация в Activity
class MainActivity : AppCompatActivity(), UiContextProvider {
    override fun getUiContext(): Context = this
    
    // Внедряем через Hilt/Dagger в нужные компоненты
}

Реализация Context-менеджера с пулом ресурсов



Теперь давайте рассмотрим последний кусочек мозаики - создание полноценного Context-менеджера с пулингом ресурсов. В крупных приложениях я часто сталкивался с проблемой: постоянное получение одних и тех же ресурсов через Context становится узким местом производительности.

Для решения этой проблемы я разработал паттерн, который назвал ContextResourcePool. Его суть - централизованное управление часто используемыми ресурсами с автоматическим отслеживанием их жизненного цикла:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class ContextResourcePool private constructor(private val context: Context) {
    private val drawableCache = LruCache<Int, Drawable>(100)
    private val stringCache = ConcurrentHashMap<Int, String>()
    private val systemServiceCache = ConcurrentHashMap<String, Any>()
    
    // Счетчики для метрик производительности
    private val cacheHits = AtomicInteger(0)
    private val cacheMisses = AtomicInteger(0)
    
    fun getDrawable(resId: Int): Drawable? {
        return drawableCache.get(resId) ?: run {
            cacheMisses.incrementAndGet()
            ContextCompat.getDrawable(context, resId)?.also {
                drawableCache.put(resId, it)
            }
        }
    }
    
    fun getString(resId: Int): String {
        return stringCache.getOrPut(resId) {
            cacheMisses.incrementAndGet()
            context.getString(resId)
        }.also { cacheHits.incrementAndGet() }
    }
    
    @Suppress("UNCHECKED_CAST")
    fun <T> getSystemService(serviceClass: Class<T>): T {
        val name = getServiceName(serviceClass)
        return systemServiceCache.getOrPut(name) {
            cacheMisses.incrementAndGet()
            context.getSystemService(serviceClass)
        } as T
    }
    
    fun getCacheMetrics(): CacheMetrics {
        return CacheMetrics(
            hits = cacheHits.get(),
            misses = cacheMisses.get(),
            drawableCacheSize = drawableCache.size(),
            stringCacheSize = stringCache.size
        )
    }
    
    // Очистка по необходимости
    fun trimMemory(level: Int) {
        if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
            drawableCache.evictAll()
        }
    }
    
    companion object {
        @Volatile
        private var INSTANCE: ContextResourcePool? = null
        
        fun getInstance(context: Context): ContextResourcePool {
            return INSTANCE ?: synchronized(this) {
                INSTANCE ?: ContextResourcePool(context.applicationContext).also {
                    INSTANCE = it
                }
            }
        }
    }
}
 
data class CacheMetrics(
    val hits: Int,
    val misses: Int,
    val drawableCacheSize: Int,
    val stringCacheSize: Int
)
Этот менеджер решает сразу несколько задач:

1. Кэширование ресурсов - наиболее часто используемые строки, изображения и сервисы хранятся в памяти.
2. Метрики производительности - с помощью счетчиков hits/misses можно определить эффективность кэширования.
3. Управление памятью - при нехватке памяти кэш может быть очищен.

Для интеграции с жизненным циклом приложения я реализую интерфейс ComponentCallbacks2:

Kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ResourcePoolManager(private val application: Application) : ComponentCallbacks2 {
    private val resourcePool = ContextResourcePool.getInstance(application)
    
    init {
        application.registerComponentCallbacks(this)
    }
    
    override fun onTrimMemory(level: Int) {
        resourcePool.trimMemory(level)
    }
    
    override fun onConfigurationChanged(newConfig: Configuration) {
        // Сбросить кэши, зависящие от конфигурации
        // (например, при смене языка или ориентации)
    }
    
    override fun onLowMemory() {
        resourcePool.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE)
    }
    
    fun getMetrics(): CacheMetrics = resourcePool.getCacheMetrics()
}
Настоящая сила этого подхода проявляется при интеграции с профилированием. Я обычно добавляю отчеты по метрикам в Firebase Performance Monitoring:

Kotlin
1
2
3
4
5
6
7
8
9
10
fun reportMetrics() {
    val metrics = resourcePoolManager.getMetrics()
    val hitRatio = if (metrics.hits + metrics.misses > 0) {
        metrics.hits.toFloat() / (metrics.hits + metrics.misses)
    } else 0f
    
    FirebasePerformance.getInstance().newTrace("resource_pool_metrics")
        .putMetric("hit_ratio", (hitRatio * 100).toLong())
        .putMetric("cache_size", metrics.drawableCacheSize.toLong() + metrics.stringCacheSize.toLong())
}
Такой подход позволяет системно отслеживать эффективность работы с ресурсами и оптимизировать приложение, основываясь на реальных данных, а не на догадках.

Clojure Список глубины подсписков
Написать функцию, выводящую список глубины подсписков (атом – глубина 0). Помогите блондинке,...

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

IDEA: Context Help???
Установил IDEA 2.5. Приатачил к ней jdk1.3.1 и документацию к ней. Как установить чтобы при...

Как из Thread в отдельном классе получить context приложения?
Например есть AcceptThread extends Thread и MainActivity extends Activity Как в AcceptThread...

Fragment and context
Есть шерлокбар. Два таба у него. Каждому табу привязан свой фрагмент через tabListener. ...

non-static variable cannot be referenced from a static context
Здравствуйте, Пытаюсь создать запись в таблице MySQL c JFrame. Выдает ошибку non-static...

Maven exception sending context initialized event to listner instance of class
Здравствуйте. Не могу раздеплоить проект в Мавен.. все зависимости есть и были установлены...

AsyncTask должен использовать Context
Добрый день! Мое приложение должно использовать сетевое соединение, вынесенное в отдельный...

Вызов методов Context из статичного контекста
Столкнулся с тем, что не могу получить содержимое ресурса из созданных классов. К примеру...

Context , Бд, активити
У меня есть 2 активити. В 1 активити я добавляю данные в базу данных. t = new...

О чем говорит ошибка "Non-static variable D cannot be referenced from a static context"
non-static variable D cannot be referenced from a static context Вот эта ошибка мне часто...

Можно ли так передавать context?
Не приведет ли это к утечке памяти? public class A{ public static void fileRead(Context...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Саморегулирующийся социальный контракт для сервера cross-section.
Hrethgir 14.08.2026
С кодом конечно таких глубоких размышлений пока не было, впрочем я уже привык к алгоритмизации. Суть предмета записи: снова в диалоге с нейросетью (я взял пока себе ник для учётки админа - Rector). . . .
Часы электронные
Uhbif79 12.08.2026
Выкладываю программу часов. Программа позволяет: 1. Использовать системное время и дату, 2. Есть возможность вводить время и дату вручную. 3. Реализованы 2 будильника: начало и конец рабочего дня. . . .
Часы с будильником на основе класса QLCDNumber
Uhbif79 12.08.2026
Всем добрый день, выкладываю программу часов с будильником на основе класса QLCDNumber. Здесь я пробовал самостоятельно создавал классы, впервые столкнулся с видимостью переменной одного класса из. . .
Установка MinGW GCC 16.2 и CMake
8Observer8 10.08.2026
VK Видео: https:/ / vkvideo. ru/ video-240781534_456239017 YouTube: eY5-5PyI9NM Текстовая версия
Неделя из жизни имитационной модели склада: мои кривые руки растут, откуда надо
anaschu 10.08.2026
Неделя из жизни имитационной модели склада: как я почти написал неправильную логику и что с этим делать Работаю сейчас над учебно-рабочим проектом: строю в AnyLogic имитационную модель процессов. . .
Калькулятор для расчета родства
russiannick 07.08.2026
1. Задача: Создать калькулятор для расчета родства. Родственных связей существует 8 ступеней, такие как: p - отец P - мать q - муж Q - жена b - брат B - сестра s - сын S - дочь
Мир по моей воле
kumehtar 07.08.2026
Когда-то кажется, что всё просто. Ты весь такой светлый. Причиняешь добро. Борешься за справедливость в этом тёмном мире. Потом начинаешь замечать одну неприятную вещь. Почти каждый хороший. . .
Кредитный калькулятор
Maks 05.08.2026
Решение задачи по прикладной информатике средствами 1С. Задача: Напишите приложение-калькулятор, которое помогает рассчитывать параметры кредита для аннуитетного и дифференцированного видов. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru