В 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...
|