Привет, Хабр! Меня зовут Александр. В YADRO я разрабатываю приложения внутри оболочки kvadraOS: да, у нас есть своя оболочка на основе AOSP — в команде One UI, Color OS и MiUi прибыло. 

Сегодня расскажу о тайнах и тонкостях приложения «Системные настройки» — это наша реликвия, очень старое приложение с нагромождением костылей стилей и подходов. Когда наша команда взялась его перерабатывать, у нас было 230 000 строк legacy-кода на Java и около 300 активностей и фрагментов. Как вы понимаете, задача была не из легких.

Через что нам пришлось пройти, чтобы улучшить приложение, и почему мы не убежали не переписали его с нуля, расскажу дальше. Спойлер: было жестко, но все закончилось хорошо. Теперь у нас есть все основания утверждать, что работа над «внутренностями» Android — это высшая лига.

Кратко о видах приложений 

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

Платформенные приложения (собственные, системные)

Это приложения, которые родились вместе с операционной системой, то есть часть «тела и души» Android. Входят в состав AOSP (в нашем случае версии 12L) и собираются Soong — из этого и вытекают их ограничения. Soong-приложения могут использовать Java, XML и Kotlin 1.4 (а поднимать версию языка в рамках платформы, поверьте, задание не простое), Compose, но с ограниченной версией платформы из дерева AOSP. Можно разрабатывать и на С++, но это редкость. 

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

  • Примеры платформенных приложений: «Настройки», System UI, «Телефон», «Сообщения».

  • В чем особенность: они «свои» для системы, поэтому имеют доступ к скрытому и системному API.

Вендорские приложения, предустановленные производителем

Это приложения от производителя вашего девайса — Kvadra, Samsung, Xiaomi и так далее. Некоторые могут быть интегрированы в манифесты и системные разделы. Обычно их сложно удалить, так как на них могут опираться другие функции устройства. Но бывают и просто установленные автоматически при первом включении. Они могут выглядеть как встроенные, но чаще всего их можно удалить стандартными средствами. Для многих вендоров такие приложения — это снижение зависимости от Google Play Store. 

Еще бывают приложения, которые поставляет Chipset Vendor (QC, MTK, RockChip и другие). ODM тоже может предоставлять свои приложения.

  • Примеры таких приложений: «Фото» или «Галерея», «Заметки», Galaxy Store (Samsung), GetApps (Xiaomi), Mi Browser, Samsung Internet, Mi Cloud, Samsung Cloud, «Антивирусы», «Очистители памяти».

  • В чем особенность: в отличие от сторонних приложений, вендор сам решает, быть приложению системным или обычным. Это позволяет устанавливать свои сервисы как стандарт по умолчанию — и не только, но это уже материал для отдельной статьи. 

Standalone-приложения (сторонние)

Это приложения, которые вы сами скачали из Google Play или других магазинов и выбрали из списка для установки при первом запуске устройства. Некоторые из них могут устанавливаться через MDM-решения. 

Создают такие приложения сторонние разработчики — от огромных корпораций до программистов-одиночек. Разработчики OEM-компаний тоже могут их поставлять в качестве обновлений для предустановленных приложений на устройстве.

  • Примеры таких приложений: Telegram, Wildberries, YouTube.

  • В чем особенность: приложения не всегда сразу подстраиваются под новый дизайн системы (с отдельными оговорками), так как в основном не полагаются на системные темы. Если после смены обоев калькулятор стал розовым, а Telegram остался синим — это потому, что «сторонняя» программа живет по своим правилам.  

Settings — не просто приложение

Что такое собственные (они же «системные») приложения, понятно. Теперь давайте поговорим о «Настройках» — самом важном и интересном лично для меня приложении. До работы над kvadraOS мне попадались под руку только сторонние приложения — как нативные, так и веб. Так что работа над «Настройками» была для меня очень увлекательным и даже экстремальным опытом мобильной разработки в Android. В чем тут особенность? 

Приложение «Настройки» для Android настолько «свое», что практически является частью AOSP: оно неразрывно связано с внутренними библиотеками Android.

Что здесь интересного:

  • «Настройки» по умолчанию находятся в привилегированной директории системного раздела /system/priv-app/;

  • в манифесте у них заявленные привилегии пользователя android:sharedUserId="android.uid.system";

  • подписаны платформенным сертификатом;

  • имеют набор привилегированных разрешений.

Все это в совокупности открывает доступ к скрытым и системным API и дает приложению права пользователя UID System, которые обычно недоступны для сторонних приложений. Такие возможности позволяют «Настройкам» управлять правами доступа, изменять конфигурацию устройства и получать доступ к чувствительным частям платформы. Думаю, тут уже становится понятно, что в неумелых руках это может привести к непоправимым последствиям для любого пользователя.

Поскольку «Настройки» — это часть AOSP, каждый производитель (Kvadra, Samsung, Xiaomi и так далее) берет «базу» от Google и кастомизирует ее визуальную часть под свою оболочку. Поэтому на разных устройствах одно и то же системное приложение выглядит по-разному, хотя функции внутри одни и те же. Так поступили и мы, изменив внешний вид, структуру разделов и не только. Об этом расскажу чуть позже.

Пока хочу отметить, что разработка системных «Настроек» — это работа для терпеливых. Это мир, где обычно нет места модным библиотекам, но есть огромная ответственность: одна ошибка в XML может превратить устройство в «кирпич», который даже не загрузится.

Добро пожаловать в ад? Почему разработчики боятся «Настроек»

Если вы думаете, что системные приложения пишутся на острие технологий, вынужден вас разочаровать. На самом деле работа с «Настройками» — это археология и борьба с наследием десятилетней давности. 

Немного деталей:

Смерть от тысячи XML. Современный разработчик мобильных приложений, в основном пишет на Compose интерфейс кодом — это быстро и наглядно. В «Настройках» же вы попадаете в океан XML-файлов. 

Статистика ужаса. В типичной сборке Settings для Android 12L находится более 1500 layout-файлов и еще столько же файлов с ресурсами: строки, стили, анимации. К тому же поиск нужного экрана превращается в детективное расследование: вы ищете фрагмент, который ссылается на контроллер (Привет MVC!), который загружает XML, в котором прописан другой XML.

При этом XML имеет и ряд преимуществ. У вас есть разделение кода и дизайна, а еще вы получаете зрелую технологию с огромным комьюнити. 90% проблем, которые у вас возникнут, скорее всего, уже были разобраны, так что вы легко найдете решения. 

Проклятие Preference Framework. В «Настройках» все строится на фреймворке Preference: он еще поддерживается в виде androidx.preference. Каждый пункт меню — это не просто кнопка, а объект Preference. Мы получаем высокоуровневую абстракцию над RecyclerView. Вместо отдельного описания адаптеров и холдеров описываем структуру в XML через <PreferenceScreen>, хотя иногда и приходится спуститься «вниз» и начать работать с тем же PreferenceGroupAdapter

Библиотека androidx.preference пытается упростить разработку, но в системных настройках она обрастает костылями для поддержки специфических кейсов и кастомного дизайна. Разобраться в иерархии вызовов становится достаточно тяжело – и да, до ее исходного кода мы тоже добрались, и это позволило сделать ее более гибкой для наших задач.

SettingsLib — черная дыра логики. Большая часть логики (статус Wi-Fi, уровень заряда, права доступа) вынесена в общую библиотеку SettingsLib. Она общая для «Настроек», «Системного интерфейса» (SystemUI) и нескольких других приложений и, кстати, еще и позволяет им добиться того самого «системного» вида. Но есть проблема: изменив одну строчку, чтобы поправить баг в отображении батареи в «Настройках», вы можете случайно «сломать» иконку батареи в строке состояния. Тестирование таких изменений превращается в достаточно сложный процесс.

Стоит сказать, что в умелых руках, этот минус автоматически становится плюсом, так как все необходимое агрегировано в одном месте. А про прелести следить за контролем корпоративных политик (через те же RestrictedPreferences) можно тоже написать отдельную статью. 

Лабиринт Overlays (Наложения). Помните, мы говорили, что в Kvadra, Samsung и Google «Настройки» выглядят по-разному? Внешний вид приложения изменяется через подмену XML-файлов при сборке платформы — это механизм Static Overlays. Отладка превращается в гадание: «Почему моя кнопка синяя, если в коде она красная? Точно! Ее перекрасил overlay из такой-то папки типа сборки»

Сборка без Gradle (и тут не все просто). Обычный программист стороннего приложения нажимает кнопку Run в Android Studio и через ~30 секунд (если это не тяжелое приложение) видит результат. Системный инженер запускает сборку через make или soong, часто используя для удобства не простую Android Studio, а Android Studio For Platform. Хотя никто, конечно, не мешает строиться из консоли. На мощном сервере пересборка части системы может занимать от 5 до 20 минут. Никакого мгновенного Hot Reload из коробки и превью интерфейса здесь нет. 

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

Испуг для непосвященных?

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

Как это делает «белый человек» (Jetpack Compose)? В современном приложении такая задача — это несколько строк прямо в Kotlin-файле. Ты сразу видишь дизайн в превью:

@Composable
fun SettingsScreen() {
	Column(modifier = Modifier.padding(dimensionResource(R.dimen.settings_padding_medium))) {
    	Text(
        	text = stringResource(R.string.notification_settings_title),
        	style = MaterialTheme.typography.h6,
        	color = MaterialTheme.colors.onBackground
    	)
    	Row(
        	modifier = Modifier
            	.fillMaxWidth()
            	.padding(vertical = dimensionResource(R.dimen.row_padding_small)),
        	verticalAlignment = Alignment.CenterVertically
    	) {
        	Text(
            	text = stringResource(R.string.enable_sounds_label),
            	style = MaterialTheme.typography.body1
        	)
        	
        	Spacer(Modifier.weight(1f))
    	// Состояние Switch обычно приходит из ViewModel
    	// но для наглядности пока не будем её добавлять
        	Switch(
            	checked = true,
            	onCheckedChange = { /* some logic here */ },
            	colors = SwitchDefaults.colors(
                	checkedThumbColor = MaterialTheme.colors.primary
            	)
        	)
    	}
	}
}
@Preview(
	name = "Dark Mode",
	showBackground = true,
	uiMode = Configuration.UI_MODE_NIGHT_YES
)
@Composable
fun SettingsScreenPreview() {
	MyApplicationTheme {
    	Surface(color = MaterialTheme.colors.background) {
        	SettingsScreen()
    	}
	}
}

Итог: 1 файл, 0 XML, сборка за 5 секунд через Gradle. Сиди, наслаждайся и смотри за своим свитчом.

Как это происходит в системных Settings (AOSP)? А здесь у нас начинается «Путь героя» по разным папкам и технологиям. 

  • Шаг 1: XML-декларация —res/xml/notification_settings.xml

Ты не рисуешь интерфейс, ты описываешь его иерархию в статичном файле, используя androidx.preference:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
	xmlns:android=" http://schemas.android.com/apk/res/android"
	xmlns:settings="http://schemas.android.com/apk/res-auto"
	android:key="notification_settings_screen"
	android:title="@string/notification_settings_title">
 
	<PreferenceCategory
    	android:key="sound_category"
    	android:title="@string/sound_category_title">
 
    	<SwitchPreference
        	android:key="notification_sound_key"
        	android:title="@string/notification_sound_title"
        	android:summary="@string/notification_sound_summary"
        	android:icon="@drawable/ic_notifications_sound"
        	settings:controller="com.android.settings.notification.SoundPreferenceController"
        	settings:allowDividerAbove="true" />
 
	</PreferenceCategory>
</PreferenceScreen>
  • Шаг 2: Java/Kotlin Контроллер — src/com/android/settings/...

Теперь тебе нужно создать Controller (а делается это для каждого Preference отдельно), который будет «оживлять» этот свитч. И тут в игру вступает SettingsLib:

public class SoundPreferenceController extends BasePreferenceController {
// В системных настройках мы часто используем Helper-классы из SettingsLib
	private final AudioHelper mAudioHelper;
	private final NotificationBackend mBackend;
 
	public SoundPreferenceController(Context context, String key) {
    	super(context, key);
    	// Вот тут мы показываем власть системного доступа
    	mAudioHelper = new AudioHelper(context);
    	mBackend = new NotificationBackend();
	}
 
	@Override
	public int getAvailabilityStatus() {
    	// Мы не просто показываем свитч, мы спрашиваем систему:
    	// "А можно ли сейчас вообще менять звук?"
    	return mAudioHelper.isSingleVolume() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
	}
 
	@Override
	public boolean setChecked(boolean isChecked) {
    	// Обращаемся к менеджеру уведомлений
      mBackend.setNotificationsEnabledForPackage(mContext.getPackageName(), isChecked);
    	
    	// Или дергаем системный сервис звука через SettingsLib обертку
    	mAudioHelper.setStreamVolume(AudioManager.STREAM_NOTIFICATION, isChecked ? 7 : 0);
    	
    	return true;
	}
 
	@Override
	public void updateState(Preference preference) {
    	super.updateState(preference);
    	// А вдруг настройка "залочена" администратором?
    	EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
            	mContext, UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId());
    	
    	if (admin != null) {
        	((RestrictedPreference) preference).setDisabledByAdmin(admin);
    	}
	}
}
  • Шаг 3: регистрация в манифесте и поиск индекса. Чтобы этот экран вообще открылся через поиск в «Настройках», его нужно прописать для поисковой системы приложения.

  • Шаг 4: фрагмент-клей — NotificationSettings.java.

Если в Compose мы просто вызываем функцию, то в «Настройках» нам нужно наследоваться от тяжеловесного DashboardFragment. Это своего рода «диспетчерская», которая управляет жизненным циклом экрана.

public class NotificationSettings extends DashboardFragment {
	private static final String TAG = "NotificationSettings";
 
	// 1. Указываем, какой XML-файл с разметкой (из тех самых 1500+) мы используем
	@Override
	protected int getPreferenceScreenResId() {
    	return R.xml.notification_settings;
	}
 
	// 2. Регистрируем наш контроллер, чтобы он оживил Switch
	@Override
	protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
    	final List<AbstractPreferenceController> controllers = new ArrayList<>();
    	controllers.add(new SoundPreferenceController(context)); // Тот самый контроллер, конкретно этот можно было указать в xml, но для наглядности добавим его тут
    	return controllers;
	}
 
	// 3. Интеграция в поиск настроек
	public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
        	new BaseSearchIndexProvider(R.xml.notification_settings);
 
	@Override
	public int getMetricsCategory() {
    	// Каждая страница должна иметь ID для аналитики
    	return SettingsEnums.CONFIGURE_NOTIFICATION;
	}
}

Почему «больно» даже на этом этапе:

  1. Жесткая иерархия: мы должны наследоваться от DashboardFragment, иначе не сможем использовать современные контроллеры и интеграцию с SettingsLib.

  2. Поиск (Search Index): если забудешь прописать SEARCH_INDEX_DATA_PROVIDER, твой новый экран будет «призраком»: зайти на него можно, но найти через поиск вверху «Настроек» — нет.

  3. MetricsCategory: в системных настройках за каждым твоим кликом следит аналитика (метрики). Забыл указать ID категории — получишь невалидный отчет.

Чуть не забыл! Чтобы каждый экран можно было открыть напрямую из другого приложения, у него должна быть своя Activity или связанный с ней activity-alias. Например, когда вы нажимаете «Настройки батареи» в шторке, система шлет Intent, который сопоставляется на вполне конкретный «псевдоним» <activity-alias>. То есть каждый экран открывается в отдельной активности (хоть многие из них просто наследуют SettingsActivity или его потомка SubSettings), а каждая активность может содержать всего лишь один фрагмент на момент отрисовки. 

Активность — это чаще всего просто контейнер, и вся логика находится во фрагменте. На выходе у нас получается несколько сотен активностей и еще больше фрагментов.

Ниже — пример из нашего манифеста. В Android 12L там 4000 строк, дальше еще больше. Помним, что все exported-активности, прописанные в манифесте, — это точки входа в приложение.

Увлекательно, правда? Но подождите, мало просто менять Activity одну на другую — у нас же планшет. Значит, было бы хорошо реализовать Activity Embedding! 

Activity Embedding — планшетный «костыль» на стероидах

В Android 12L ребята из Google решили, что для планшетов и складных устройств (Foldables) не очень подходит, если «Настройки» выглядят как одна растянутая на 13 дюймов строка. В качестве решения представили технологию Activity Embedding. В чем идея? Вместо того чтобы переписывать все и ломать архитектуру «одна активность один фрагмент» (что в Settings заняло бы вечность), компания Google сказала: «А давайте просто показывать две Activity рядом!». Слева — список разделов, справа — конкретный раздел.

Думаете, это делается одной кнопкой? Как бы не так. Для этого используется библиотека Jetpack WindowManager, но в системных приложениях это превращается в квест:

  • нужно создать отдельный XML-конфиг main_navigation_split_config.xml;

  • прописать правила (SplitPairRule): какая Activity с какой и как должна инициироваться;

  • если вы забыли прописать правило для нового экрана, то при повороте планшета ваше приложение превратится в тыкву — точнее, в одну растянутую Activity среди двухпанельного интерфейса.

В Android 12L эта технология была настолько свежей, что у системных разработчиков на первых порах возникли новые трудности:

  • Z-order и тени: иногда правая Activity перекрывала левую так, что тени отрисовывались поверх контента.

  • Жизненный цикл (Lifecycle): Activity в сплите могут умирать и воскресать в непредсказуемом порядке. Обработка onSaveInstanceState превращается в своеобразную «лотерею».

  • Backstack: нажимая кнопку «Назад» на планшете в Android 12L, вы должны следить, сейчас закроется только правая панель или все приложение. А еще — поняла ли система, куда вернуть фокус.

Контраст с Compose (Adaptive Layouts)

В то время как системные разработчики работают с Activity Embedding, в Compose все решается гораздо более элегантно: 

// В Compose мы просто меняем количество колонок в зависимости от ширины экрана
val windowSize = calculateWindowSizeClass(activity)
if (windowSize.widthSizeClass == WindowWidthSizeClass.Expanded) {
	TwoPaneLayout(mainContent, detailContent)
} else {
	SinglePaneLayout(mainContent)
}

Никаких XML-конфигов на 500 строк, никаких проблем со стеком Activity — просто декларативное описание интерфейса.

Но вот чего точно не сможет Compose, так это отобразить две Activity из разных приложений, помогая незаметно мимикрировать второму под «Настройки». Как раз это возможно благодаря Activity Embedding. А еще не забываем, что выше мы говорили про SettingsLib и androidx.preference: благодаря им отдельные приложения, такие как PermissionController или BuiltInPrintService, в правой части экрана будут выглядеть как родные экраны приложения «Настроек».

Подытожим? Activity Embedding в Android 12L — это попытка Google войти в мир многооконного режима, а заодно придать коду «Настроек» современный вид на планшетах без тотального рефакторинга. Это хорошо работает и в современных версиях. Правда, внутри 12L Android местами держится на «синей изоленте».

«Конец!». Хотел бы я сказать еще в начале своего пути, но трудности же нас только закаляют.

Почему нельзя просто взять и переписать «Настройки»?

Итак, у нас есть общая библиотека Compose-элементов для наших вендорских Kvadra приложений. Казалось бы, вариант решения — снести старый XML и собрать все на Compose. Но в реальности мы упираемся в три стены: Наследие, Архитектуру и Концепцию.

1. Наследие, мешающее единообразию: визуальный разрыв. Мы стремимся к тому, чтобы kvadraOS ощущалась как единый организм. Пользователь не должен замечать швов, переходя из «Галереи» в «Настройки». Но пока наши основные приложения сияют свежим дизайном из Figma, «Настройки» остаются консервативным родственником.

Есть нюанс: чтобы внедрить новую фишку в UI, нам приходится вручную «вытачивать» ее на XML специально для этого приложения. Можно было бы рисовать на Canvas, но при нашем специфическом дизайне это сожжет столько ресурсов, что проще построить ракету.

2. Архитектурный долг: когда код не твой. Основная проблема: «Настройки» не принадлежат нам до конца. Хотя мы и под Apache-лицензией, но надо помнить, что у нас форк (ответвление) глобального кода Google, изменения в котором мы хотим переносить и на новые версии Android в обновленную версию AOSP Settings. Мы ограничены архитектурой текущей версией AOSP. 

3. Кризис концепции: талмуд против современности. Давайте честно: сама идея «Настроек» как списка из 150 экранов несколько устарела. В эпоху ИИ-ассистентов заставлять пользователя вручную пробираться через лабиринты подменю — это база для не самого лучшего пользовательского опыта. Мы понимаем, что будущее за предиктивными действиями в поиске, но обязаны поддерживать существующую иерархию. Ведь это фундамент для сложнейшего узла управления всей системой, на котором нужно воздвигать новые решения. Поэтому наш путь — это эволюция внутри жестких рамок. Мы должны поддерживать и улучшать существующее, принося с собой новое.

Какие тогда у нас остаются варианты? Нет, не убежать. А развивать существующее, добавляя новые модули в существующий код настроек (как на примере расширения раздела биометрии, внедрения двухфакторной аутентификации, менеджмента подключенных устройств и не только). И написать свое приложение с интеграцией в текущее. 

Итак, встречайте SettingsLite! Наше APK, интегрируемое в «Настройки».

SettingsLite — Poc-Corn

С апдейтом AOSP «Настроек» понятно: переделываем разделы, учитывая пользовательский опыт, добавляем свои стили, layout, создаем кастомные элементы, наследуясь от тех же androidx.preference. А что же тогда будет в нашем интегрируемом приложении?

 Основная концепция SettingsLite:

  • современный Settings Framework на основе Google SPA (Settings Platform Architecture), поддерживающий реализацию экранов настроек, аналогичный android.preference, с поддержкой Legacy API;

  • использование общей библиотеки Compose UI-элементов;

  • разработка с использованием современного стека: JetPack Compose, Hilt, Koltin Coroutines, Gradle Kotlin Dsl, Detekt, Kover;

  • реализующий шаблон MVVM;

  • построенный по принципам Clean Architecture и поддерживающий модульную структуру;

  • поддержка Feature Toggle (Managed Redirection) для возможности откатить или выключить нашу функциональность, если что-то пойдет не так.

Что взяли за референс

Референсом стал так называемый SPA — это как раз та самая попытка Google навести порядок в хаосе, о котором мы говорили выше. Если в вебе SPA — это Single Page Application, то в мире Android 14+ это фреймворк, призванный перетащить «Настройки» в современную эру. Правда, в итоге мы сильно все изменили. Почему?

Сначала мы сделали на SPA небольшую демку из трех экранов и оценили результат. Оказалось, архитектура Google противоречит архитектурным рекомендациям Google: даты и UI смешаны в одну кучу. С этой проблемой мы сталкивались уже и в старых «Настройках». К тому же оказалось, что в глубинах не совсем новая архитектура, в нее просто пытаются завернуть все то же Legacy. Наконец, когда мы начали «прикручивать» уже реальную функциональность, она просто не стала реализовываться. То есть проект тогда был еще сырым: ребята из Маунтин-Вью его придумали, но не продумали.

Первые результаты миссии

Сначала мы выбрали простейший раздел на планшете — без динамических настроек и с минимумом ограничений по доступам. Накидали план. Главными целями было обкатать framework и убедиться, что мы можем на нем быстро и просто реализовывать новые экраны с гибким UI.

 В итоге центральный паттерн позволяет:

  • описывать структуру страниц декларативно;

  • динамически строить иерархию «Настроек»;

  • интегрироваться с системой поиска;

  • управлять доступностью страниц на основе feature-флагов.

Мы создали собственное приложение настроек на базе Android, сохраняя при этом возможность использовать существующие технологии из стандартного приложения Settings. А благодаря модульной архитектуре, четкому разделению ответственности и современным технологиям, SettingsLite обеспечивает отличную основу для развития и поддержки будущих проектов.

Эстетику же вы можете оценить по результату:

Или экрана обновлений ПО:

Это до
Это до
Это после
Это после

Эпилог: почему все это мучение того стоит?

Казалось бы, после 1500 XML-файлов и борьбы с Activity Embedding хочется все бросить и уйти писать лендинги. Но есть причины, по которым работа над системными приложениями Android — это высшая лига и уникальный опыт.

Вы управляете реальностью, а не картинкой. Когда вы пишете стороннее приложение, то живете в «песочнице». Система позволяет вам ровно то, что разрешено всем. Но в Settings вы хозяин положения: работаете с менеджерами над модулями связи, драйверами дисплея, контроллерами питания и не только. Ваш код дает вам полную власть над устройством.

Масштаб, который кружит голову. Ваш экран «Настроек» увидят все пользователи системы. Буквально. Каждое ваше решение влияет на пользовательский опыт и пользовательские возможности. Благодаря вам пользователь может настраивать устройство полностью под себя. Сделаете удобно — сэкономите огромное количество человеко-часов. 

Мастерство «Старой школы». Работа с AOSP превращает вас в инженера-универсала. Вы начинаете понимать Android не как набор библиотек, а как сложнейший механизм:

  • знаете, как работает межпроцессное взаимодействие (AIDL/Binder);

  • понимаете, как система распределяет ресурсы и права;

  • учитесь писать код, который обязан быть стабильным.

Рождение эстетики и создание своей системы. Для меня именно работа над системными приложениями стала драйвовой. А учитывая работу над SettingsLite, мы получили элегантное решение для создания масштабируемых и поддерживаемых приложений настроек, сочетающее лучшие практики Clean Architecture с современными технологиями Android-разработки.


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

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

Учитывая все вышесказанное, теперь у нас есть все права утверждать: разработка «Настроек» — это не чтение мануалов, а постоянный критический анализ. Мы не просто копируем то, что делает Google — мы расширяем существующее, чтобы пользователь kvadraOS получил стабильный и современный продукт.

На сегодня историй об испытаниях достаточно. Если есть вопросы или дополнения по теме, задавайте их в комментариях — буду рад обменяться опытом.

Комментарии (2)


  1. dimazizinoviev
    06.05.2026 15:17

    С одной стороны ад из XML и костылей, с другой, кайф от того, что ты реально управляешь устройством, а не просто рисуешь кнопки. Интересно было про SettingsLite, выглядит как адекватный способ не утонуть в легаси.


  1. Sergey_pc
    06.05.2026 15:17

    Зачем все пытаются переделать настройки? Оставьте уже вид по умолчанию и добавьте раздел со своими плюшками.