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

Context и глубины Android

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

Нажмите на изображение для увеличения
Название: Context и глубины Android.jpg
Просмотров: 398
Размер:	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
Комментарии
 
Новые блоги и статьи
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
мат медиц модель 30. презентация проекта
anaschu 27.08.2026
хоп хоп хоп хидахоп, а я кладую))
Как у меня протекала болезнь
zorxor 27.08.2026
Здравствуйте, друзья! Эта запись блога предназначена именно для вас - для моих дорогих друзей, которые знали меня лично. Чтобы ответить на вопрос - а что же со мной произошло на самом деле? Я учился. . .
Нашел вот забавное видео о измерениях. Лучшее что я видел на эту тему
kumehtar 26.08.2026
ILETXiw9bMQ Основная суть и тезисы по измерениям: 0D (Нулевое измерение): точка, не имеющая длины, ширины, высоты или объема. Объект не может перемещаться в 0D. 1D (Первое измерение):. . .
[EasyBuilder Pro] Памятка по разработке для панелей Weintek
ФедосеевПавел 26.08.2026
Памятка по разработке для панелей Weintek ВВЕДЕНИЕ Ранее, при реализации проектов основное внимание уделял разработке управляющей программы для контроллера, а панели оператора доставалось время. . .
Модель по догадкам
anaschu 25.08.2026
Прошло две недели. Я уже рассказывал, как разговаривал с сотрудниками у сортировки и как понял, что главная ветка — не про приёмку, а про отбор. Но тогда я думал, что понял механику. На этой неделе я. . .
Запись в регистр сведений независимо от заполненности табличной части
Maks 25.08.2026
Реализация из решения ниже выполнена на нетиповом документе с несколькими табличными частями, разработанного в КА2. Задача: Обеспечить запись документа в регистр сведений независимо от. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru