Перейти к основному содержимому

2.01. Справочник по Android

Разработчику Архитектору Инженеру

Справочник по Android

📄 AndroidManifest.xml

1.1 Корневые элементы

ЭлементАтрибутыОписание, допустимые значения, примечания
<manifest>xmlns:android, package, android:sharedUserId, android:sharedUserLabel, android:versionCode, android:versionName, android:installLocation, android:revisionCode, android:splitpackage — обязательный. sharedUserId — только для приложений, подписанных одним сертификатом. installLocation: internalOnly, auto, preferExternal. revisionCodeint, начиная с Android 12L (API 32), для модульных обновлений.
<uses-sdk>android:minSdkVersion, android:targetSdkVersion, android:maxSdkVersionУказываются как числа (например, 34), но в Gradle перекрываются compileSdk, targetSdk, minSdk. maxSdkVersion не рекомендуется.
<application>android:allowBackup, android:allowClearUserData, android:allowNativeHeapPointerTagging, android:allowTaskReparenting, android:allowAudioPlaybackCapture, android:appCategory, android:backupAgent, android:banner, android:dataExtractionRules, android:debuggable, android:description, android:enabled, android:extractNativeLibs, android:gwpAsanMode, android:hasCode, android:hardwareAccelerated, android:icon, android:isGame, android:killAfterRestore, android:largeHeap, android:label, android:logo, android:manageSpaceActivity, android:name, android:networkSecurityConfig, android:permission, android:persistent, android:process, android:restoreAnyVersion, android:requestLegacyExternalStorage, android:requiredAccountType, android:resizeableActivity, android:restrictedAccountType, android:supportsRtl, android:taskAffinity, android:testOnly, android:theme, android:usesCleartextTraffic, android:vmSafeMode, android:localeConfigdebuggable — автоматически true в debug-сборке, если не переопределено. extractNativeLibs: true/false, влияет на распаковку .so. networkSecurityConfig — XML-файл с настройками TLS/Pinning. localeConfig — с Android 13 (API 33), определяет, какие локали поддерживает приложение.

💡 Важно: Начиная с Android 12 (API 31), android:exported обязателен для всех <activity>, <service>, <receiver>, если содержит <intent-filter>.

1.2 <activity> — ключевые атрибуты

АтрибутТип / ЗначенияПримечания
android:nameString (FQCN или .ShortName)Обязателен. Класс должен быть public, наследовать Activity.
android:exportedbooleantrue — доступен из других приложений; false — только внутренние вызовы. Обязателен при наличии <intent-filter>.
android:launchModestandard, singleTop, singleTask, singleInstancesingleInstance создаёт новую задачу, только один экземпляр в системе.
android:taskAffinityStringПо умолчанию — package.name. Менять только при необходимости.
android:theme@style/...Перекрывает application@theme.
android:screenOrientationunspecified, behind, landscape, portrait, reverseLandscape, reversePortrait, sensorLandscape, sensorPortrait, userLandscape, userPortrait, sensor, fullSensor, nosensor, user, fullUser, lockedlocked — фиксирует текущую ориентацию.
android:configChangesmcc, mnc, locale, touchscreen, keyboard, keyboardHidden, navigation, screenLayout, fontScale, uiMode, orientation, screenSize, smallestScreenSize, density, layoutDirection, colorMode, density и др.При указании — onConfigurationChanged() вызывается вместо пересоздания.
android:windowSoftInputModestateUnspecified, stateUnchanged, stateHidden, stateAlwaysHidden, stateVisible, stateAlwaysVisible, adjustUnspecified, adjustResize, adjustPanadjustResize — окно уменьшается; adjustPan — прокручивается.
android:resizeableActivitybooleanВлияет на multi-window. По умолчанию: true, если targetSdk ≥ 24.
android:documentLaunchModenone, intoExisting, always, neverДля режима «документ» (recents stack per document).
android:autoRemoveFromRecentsbooleanПриложение не появится в Recent Apps.
android:lockTaskModenormal, never, if_whitelisted, alwaysДля режима Kiosk (Device Owner).

1.3 <service>, <receiver>, <provider>

  • <service>: аналогично activity, но без screenOrientation, windowSoftInputMode. Добавляются:

    • android:foregroundServiceType: location, camera, microphone, phoneCall, connectedDevice, mediaProjection, mediaPlayback, bluetooth, dataSync, shortService, specialUse, health, remoteMessaging (API ≥ 29, обязательны при объявлении foreground-сервиса).
    • android:isolatedProcess: boolean — сервис в изолированном процессе без прав приложения.
  • <receiver>:

    • android:enabled, android:exported, android:directBootAware, android:permission
    • Для broadcast-ов после Android 8+: почти все implicit broadcast запрещены. Разрешены только явные (setPackage()) или системные (ACTION_BOOT_COMPLETED, ACTION_LOCKED_BOOT_COMPLETED, ACTION_MY_PACKAGE_REPLACED, ACTION_TIMEZONE_CHANGED, ACTION_TIME_CHANGED и ~15 других).
  • <provider>:

    • android:authoritiesобязателен, уникальный URI (обычно package.name.provider).
    • android:grantUriPermissions: true — позволяет временно делиться URI через FLAG_GRANT_*.
    • android:readPermission / android:writePermission: custom permissions.
    • android:exported: false по умолчанию для targetSdk ≥ 17.
    • android:multiprocess: устарело.
    • android:forceUriPermissions: начиная с Android 11 (API 30), если exported="true" и нет permission, система принудительно требует grantUriPermissions="true".

1.4 <intent-filter>

ЭлементАтрибутыЗначения / Примечания
<action>android:nameСтандартные: android.intent.action.MAIN, VIEW, SEND, EDIT, INSERT, DELETE, ATTACH_DATA, SYNC, PICK, CHOOSER, GET_CONTENT, DIAL, CALL, SENDTO, ANSWER, INSERT_OR_EDIT, SEARCH, WEB_SEARCH и др. Пользовательские: com.example.MY_ACTION.
<category>android:nameandroid.intent.category.LAUNCHER, DEFAULT, BROWSABLE, ALTERNATIVE, SELECTED_ALTERNATIVE, TAB, PREFERENCE, TEST, UNIT_TEST, MONKEY, DEVELOPMENT, EMBED, LAUNCHER_SHORTCUT, LEANBACK_LAUNCHER, LE_DESIGN_PREFERENCE, NOTIFICATION_PREFERENCES, SAMPLE_CODE, INFO, APP_BROWSER, OPENABLE, HOME, CAR_MODE, DESK_MODE, TV_MODE, VR_MODE, HEALTH_PROFILE_OWNER, HEALTH_SELF_CERTIFY.
<data>android:scheme, android:host, android:port, android:path, android:pathPattern, android:pathPrefix, android:mimeTypemimeType: например image/*, text/plain, application/json. scheme: http, https, content, file, geo, tel, sms, mailto, market. pathPattern использует упрощённое регулярное выражение: .*, .*\.pdf, path/to/*. Обратите внимание: .\. в XML-атрибуте экранируется как \\..

1.5 <uses-permission>, <permission>

ЭлементАтрибутыПримеры / Примечания
<uses-permission>android:name, android:maxSdkVersion, android:permissionGroupmaxSdkVersion — используется, когда разрешение устарело (например, WRITE_EXTERNAL_STORAGE после Android 10).
<permission>android:name, android:label, android:description, android:permissionGroup, android:protectionLevel, android:iconprotectionLevel: normal, dangerous, signature, signatureOrSystem, internal, appop, privileged, role, development, installer, verifier, pre23, setup, runtime — можно комбинировать через `

Список важнейших системных dangerous-разрешений (runtime, начиная с API 23):

ГруппаРазрешения
CALENDARREAD_CALENDAR, WRITE_CALENDAR
CALL_LOGREAD_CALL_LOG, WRITE_CALL_LOG, PROCESS_OUTGOING_CALLS
CAMERACAMERA
CONTACTSREAD_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS
LOCATIONACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, ACCESS_BACKGROUND_LOCATION
MICROPHONERECORD_AUDIO
PHONEREAD_PHONE_STATE, CALL_PHONE, READ_PHONE_NUMBERS, ANSWER_PHONE_CALLS, ACCEPT_HANDOVER
SENSORSBODY_SENSORS, BODY_SENSORS_BACKGROUND, ACTIVITY_RECOGNITION
SMSSEND_SMS, RECEIVE_SMS, READ_SMS, RECEIVE_WAP_PUSH, RECEIVE_MMS
STORAGEREAD_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, MANAGE_EXTERNAL_STORAGE (All files access)

⚠️ MANAGE_EXTERNAL_STORAGE требует отдельного объяснения в Google Play Console и модерации.

1.6 <uses-feature>

АтрибутЗначенияПримечания
android:nameandroid.hardware.camera, android.hardware.camera.any, android.hardware.camera.autofocus, android.hardware.location, android.hardware.location.gps, android.hardware.location.network, android.hardware.microphone, android.hardware.sensor.accelerometer, android.hardware.sensor.compass, android.hardware.sensor.gyroscope, android.hardware.sensor.light, android.hardware.sensor.proximity, android.hardware.telephony, android.hardware.telephony.cdma, android.hardware.telephony.gsm, android.hardware.touchscreen, android.hardware.usb.host, android.hardware.usb.accessory, android.hardware.wifi, android.hardware.bluetooth, android.hardware.bluetooth_le, android.software.leanback, android.software.computing_device, android.software.freeform_window, android.software.picture_in_picture, android.software.activities_on_secondary_displays, android.hardware.type.watch, android.hardware.type.television, android.hardware.type.automotive, android.hardware.type.desk, android.hardware.type.embedded, android.hardware.type.vrheadset, android.hardware.screen.landscape, android.hardware.screen.portrait
android:requiredtrue/falsefalse — устройство может не иметь фичи, и приложение всё равно установится. Затем проверять через PackageManager.hasSystemFeature().

🧱 View и ViewGroup

2.1 Общие атрибуты View (унаследованные от android.view.View)

XML-атрибутПрограммный метод / полеТипВозможные значения / диапазонAPI+Примечания
android:idView.setId(int) / View.getId()@+id/name, @id/name, @android:id/...Любое целое, View.NO_ID = -11+Рекомендуется использовать R.id.*. @+id/ — объявление, @id/ — ссылка.
android:layout_width, android:layout_heightViewGroup.LayoutParams.width/heightdp, px, wrap_content, match_parent, ?attr/...Не sp!1+match_parent = FILL_PARENT (устарело).
android:padding* (padding, paddingLeft, paddingTop, paddingRight, paddingBottom, paddingStart, paddingEnd)View.setPadding(...)dp, px≥01+ (paddingStart/End — 17+)RTL-совместимость: paddingStart/End заменяют Left/Right.
android:layout_margin* (margin, marginLeft, ...)ViewGroup.MarginLayoutParams.*dp, px≥01+ (marginStart/End — 17+)Только если LayoutParams наследуют MarginLayoutParams (почти все).
android:backgroundView.setBackground(Drawable)@drawable/..., @color/..., #RRGGBB, ?attr/...1+Может быть ColorDrawable, GradientDrawable, StateListDrawable, InsetDrawable, LayerDrawable и др.
android:foregroundView.setForeground(Drawable)То же23+ (для View), 1+ для FrameLayoutРисуется поверх содержимого, но под дочерними (если ViewGroup).
android:elevationView.setElevation(float)dp, px≥0, по умолчанию 021+Зависит от translationZ. Тень определяется outlineProvider.
android:translationX/Y/ZView.setTranslationX/Y/Z(float)dp, pxЛюбое21+ (Z — 21+)Смещение без изменения layout_*.
android:alphaView.setAlpha(float)float[0.0, 1.0], по умолчанию 1.011+Альфа применяется рекурсивно ко всем дочерним.
android:visibilityView.setVisibility(int)visible, invisible, gone1+invisible — занимает место, gone — не учитывается в layout.
android:clickable, android:longClickable, android:focusable, android:focusableInTouchMode, android:duplicateParentStateView.setClickable(...), и т.д.booleantrue/false1+duplicateParentState — дочерние наследуют pressed/focused родителя.
android:contentDescriptionView.setContentDescription(CharSequence)String, @string/...4+Для accessibility (TalkBack).
android:importantForAccessibilityView.setImportantForAccessibility(int)auto, yes, no, noHideDescendants16+noHideDescendants — отключает accessibility для всего поддерева.
android:transitionNameView.setTransitionName(String)String21+Для shared element transitions.
android:outlineProviderView.setOutlineProvider(OutlineProvider)background, paddedBounds, bounds21+Определяет форму тени и clip.
android:clipToOutlineView.setClipToOutline(boolean)booleantrue/false21+Обрезает содержимое по outline (например, круглая ImageView).
android:saveEnabledView.setSaveEnabled(boolean)booleantrue/false1+Управляет сохранением состояния в onSaveInstanceState.
android:tag, android:tagIdView.setTag(Object), View.getTag(int)String, Object, @id/...1+ (tagId — 21+)tagId — для типизированных тегов (например, R.id.tag_type).
android:nextFocus* (nextFocusDown, Up, Left, Right, Forward)View.setNextFocus*Id(int)@id/...1+Явное управление focus traversal. Полезно при нестандартной навигации.

⚠️ Важно: android:layout_* атрибуты игнорируются, если родительский контейнер не использует LayoutParams, их поддерживающие. Например, android:layout_centerInParent работает только в RelativeLayout.


2.2 Атрибуты ViewGroup (влияют на дочерние элементы)

XML-атрибутДля каких ViewGroupТип / ЗначенияAPI+Пояснение
android:clipChildrenВсе ViewGroup, кроме FrameLayout (по умолчанию true)boolean1+Обрезать ли дочерние за границами ViewGroup.
android:clipToPaddingВсе ViewGroup, имеющие paddingboolean1+Обрезать ли содержимое по внутренней области (внутри padding).
android:layoutAnimationЛюбая@anim/...1+LayoutAnimationController, применяется при добавлении/удалении дочерних.
android:animateLayoutChangesЛюбаяboolean11+Автоматически создаёт LayoutTransition для добавления/удаления/изменения размеров.
android:descendantFocusabilityВсеbeforeDescendants, afterDescendants, blocksDescendants1+Управляет тем, получает ли ViewGroup фокус до/после/вместо дочерних.
android:splitMotionEventsViewGroup, обрабатывающие touchboolean12+Разделяет MotionEvent между дочерними (нужно для nested scrolling).

2.3 LinearLayout — специфичные атрибуты

XML-атрибутПрограммный методТип / ЗначенияAPI+Примечания
android:orientationsetOrientation()horizontal, vertical1+По умолчанию horizontal.
android:gravitysetGravity()top, bottom, left, right, center, center_vertical, center_horizontal, fill, fill_vertical, fill_horizontal, start, end и комбинации через |1+Выравнивание дочерних внутри LinearLayout. Не путать с layout_gravity.
android:baselineAlignedsetBaselineAligned()boolean1+Выравнивание по базовой линии текста (для TextView). Может снижать производительность.
android:weightSumsetWeightSum(float)float ≥ суммы layout_weight1+Если не указан — сумма всех layout_weight.
android:baselineAlignedChildIndexsetBaselineAlignedChildIndex(int)int (индекс дочернего)1+Какой дочерний элемент использовать для baseline alignment.

Дочерние элементы в LinearLayout:

АтрибутТип / ЗначенияПояснение
android:layout_weightfloat ≥ 0Доля оставшегося пространства. Если width/height = 0dp — только вес, без учёта содержимого.
android:layout_gravityТо же, что gravity у LinearLayoutВыравнивание элемента внутри LinearLayout (перпендикулярно orientation). Например, в verticalleft, right, center_horizontal.

2.4 RelativeLayout — специфичные атрибуты (устаревает, но ещё используется)

RelativeLayout не рекомендуется с 2017 года (Android Studio предупреждает). Используйте ConstraintLayout.

АтрибутЗначенияПояснение
android:layout_alignParent* (Top, Bottom, Left, Right, Start, End)booleanПрикрепление к краю родителя.
android:layout_centerInParent, layout_centerHorizontal, layout_centerVerticalbooleanЦентрирование.
android:layout_to*Of (RightOf, LeftOf, EndOf, StartOf, Above, Below)@id/...Позиционирование относительно другого элемента.
android:layout_align* (Baseline, Top, Bottom, Left, Right, Start, End)@id/...Выравнивание краёв.
android:layout_alignWithParentIfMissingbooleanЕсли ссылка @id/... ещё не разрешена (цикл), использовать край родителя. Опасно.
android:ignoreGravity@id/...ViewGroup игнорирует gravity для указанного дочернего.

⚠️ Производительность: RelativeLayout делает 2 прохода layout. Избегайте вложенных RelativeLayout.


2.5 FrameLayout — минималистичный контейнер

АтрибутЗначенияПояснение
android:foregroundGravityТо же, что gravityВыравнивание foreground (если задан).
android:measureAllChildrenbooleanУчитывать ли все дочерние при измерении (по умолчанию — только видимые).

Дочерние элементы:

АтрибутЗначенияПояснение
android:layout_gravityЛюбые комбинацииПозиционирование дочернего внутри FrameLayout.

2.6 ConstraintLayout (AndroidX, androidx.constraintlayout:constraintlayout)

Основные атрибуты корневого ConstraintLayout

АтрибутТипЗначения / Примечания
android:layout_width/heightdp, match_parent, wrap_contentmatch_constraint = 0dp — «связать» размер.
app:layout_constraintWidth/Height_defaultspread, wrap, percentpercent требует constraintWidth/Height_percent.
app:layout_constraintWidth_percent, app:layout_constraintHeight_percentfloat[0.0, 1.0]Доля от родителя (если размер 0dp и _default="percent").
app:layout_optimizationLevelstandard, direct, barrier, chain, dimension, ratio, allУправляет оптимизациями. По умолчанию standard.

Constraint-атрибуты для дочерних

ГруппаАтрибутыЗначенияПояснение
Позиционированиеlayout_constraint*to*Of (LeftToRightOf, StartToEndOf, TopToBottomOf, BaselineToBaselineOf, и т.д.)parent, @id/..., @+id/...20+ комбинаций. BaselineToBaselineOf — только для TextView/наследников.
Marginlayout_constraint*Margin (Start, End, Left, Right, Top, Bottom)dp, @dimen/...Применяется, если constraint есть.
Gone marginlayout_constraint*GoneMargindpПрименяется, если цель View.GONE.
Biaslayout_constraintHorizontal_bias, layout_constraintVertical_biasfloat[0.0, 1.0]0.0 = левый/верхний край, 0.5 = центр (по умолчанию), 1.0 = правый/нижний.
Цепочки (Chains)layout_constraintHorizontal_chainStyle, layout_constraintVertical_chainStylespread, spread_inside, packedpacked + bias = группировка в углу.
Barrierapp:barrierDirectiontop, bottom, left, right, start, endВ Barrier: определяет, по какому краю брать максимум/минимум.
Guidelineapp:orientationhorizontal, verticalТолько для Guideline.
app:layout_constraintGuide_* (begin, end, percent)dp или floatbegin — от начала (top/left), end — от конца (bottom/right), percent — доля (0.0–1.0).
Ratioapp:layout_constraintDimensionRatio"W,H", "H,W", "w,2:1", "16:9"Соотношение ширины:высоты. W/H — фиксированный параметр. Пример: "H,16:9" → высота фиксирована, ширина = height * 16/9.
Visibilitytools:visibilityvisible, invisible, goneТолько в preview (не влияет на runtime).

Flow (внутри ConstraintLayout, AndroidX ≥ 2.0)

АтрибутЗначенияПояснение
app:flow_wrapModenone, chain, alignedchain — создаёт цепочки, aligned — выравнивание по сетке.
app:flow_horizontal/VerticalGapdpРасстояние между элементами.
app:flow_horizontal/VerticalAlignstart, center, endВыравнивание строк/столбцов.
app:flow_maxElementsWrapintМакс. элементов в строке/столбце.

2.7 RecyclerView — ключевые параметры

XML-атрибуты RecyclerView

АтрибутТипЗначенияПримечания
android:layoutManagerString или @string/..."androidx.recyclerview.widget.LinearLayoutManager", "GridLayoutManager", "StaggeredGridLayoutManager" или кастомный FQCNМожно указать класс напрямую.
app:spanCountint≥1Только если layoutManager = GridLayoutManager или StaggeredGridLayoutManager.
app:reverseLayoutbooleantrue/falseТолько LinearLayoutManager/GridLayoutManager.
app:stackFromEndbooleantrue/falseНачинать от конца (например, чат — новые сообщения снизу).
android:orientationvertical, horizontalТолько для LinearLayoutManager/GridLayoutManager.
app:fastScrollEnabled, app:fastScrollHorizontal/VerticalThumbDrawable, app:fastScrollHorizontal/VerticalTrackDrawableДля быстрой прокрутки (начиная с RecyclerView 1.3.0).

Программные ключевые параметры

КлассМетодНазначение
RecyclerViewsetHasFixedSize(boolean)Если размер не меняется при изменении данных — true (оптимизация).
setItemViewCacheSize(int), setRecycledViewPool(RecycledViewPool)Настройка кэша.
setNestedScrollingEnabled(boolean)Отключение nested scrolling (если внутри NestedScrollView).
LinearLayoutManagersetInitialPrefetchItemCount(int)Prefetch для async-загрузки (например, изображений).
ItemAnimatorsetSupportsChangeAnimations(false)Отключить анимации при обновлении (для производительности).
DiffUtil / ListAdapterРекомендуемый способ обновления данных (автоматический diff).

2.8 ScrollView и NestedScrollView

АтрибутЗначенияПримечания
android:fillViewportbooleanРастягивать дочерний элемент на весь viewport (если его содержимое < высоты ScrollView).
android:overScrollModealways, ifContentScrolls, neverУправление overscroll-эффектом («пружинка»).
android:scrollbarsnone, horizontal, vertical, bothОтображение скроллбаров (по умолчанию — vertical).
android:scrollbarStyleinsideOverlay, insideInset, outsideOverlay, outsideInsetПозиционирование скроллбара.

⚠️ ScrollView может содержать только один прямой дочерний элемент.


2.9 Material Design Components (MDC) — ключевые атрибуты

MaterialButton

АтрибутТипЗначенияПримечания
app:cornerRadiusdp≥0Закругление (перекрывает shapeAppearance).
app:strokeWidth, app:strokeColordp, colorГраница.
app:backgroundTint, app:rippleColor, app:icon, app:iconGravity, app:iconPaddingПоддержка ColorStateList для tint/ripple.
app:strokeColorColorStateListНапример: <selector><item android:color="@color/grey" android:state_enabled="false"/><item android:color="@color/blue"/></selector>

TextInputLayout

АтрибутЗначенияПояснение
app:boxBackgroundModeoutline, filled, noneСтиль поля ввода.
app:startIconDrawable, app:endIconModedrawable, none, clear_text, password_toggle, dropdown_menu, customendIconMode управляет поведением.
app:helperText, app:helperTextEnabled, app:counterEnabled, app:counterMaxLengthString, boolean, intВспомогательные элементы.
app:errorEnabled, app:errorIconDrawableУправление состоянием ошибки.

AppBarLayout, CollapsingToolbarLayout

АтрибутЗначенияПримечания
app:layout_scrollFlagsscroll, enterAlways, enterAlwaysCollapsed, exitUntilCollapsed, snap, snapMarginsКомбинируются через |.
app:contentScrim, app:statusBarScrimcolor, drawableЦвет фона при схлопывании.
app:expandedTitleMargin*, app:collapsedTitleTextAppearanceНастройка заголовка.

BottomNavigationView

АтрибутЗначенияПримечания
app:menu@menu/...Обязателен.
app:itemIconTint, app:itemTextColorColorStateListЦвет иконок и текста (по умолчанию — ?attr/colorPrimary).
app:itemBackgrounddrawableФон элемента (например, ripple).
app:labelVisibilityModeauto, selected, labeled, unlabeledУправление отображением подписей.

2.10 tools: namespace — атрибуты только для preview

АтрибутПримерПояснение
tools:texttools:text="Sample text"Подставляет текст только в preview.
tools:src, tools:srcCompattools:src="@drawable/preview_image"Изображение для preview.
tools:listitemtools:listitem="@layout/item_preview"Для RecyclerView, ListView, Spinner.
tools:visibility, tools:gone, tools:showIn, tools:context, tools:targetApi, tools:ignoreshowIn — для <merge>, context — указание Activity, ignore="MissingConstraints" — подавление warning’ов.
tools:layout_*tools:layout_width="200dp"Переопределяет android:layout_* только в preview.

📬 Intent, Bundle, Permissions, Activity Result API


3.1 Intent — Actions (стандартные, Intent.ACTION_*)

КонстантаОписаниеAPI+Примечания
ACTION_MAINГлавная точка входа (без данных)1+Требует CATEGORY_LAUNCHER для иконки в лаунчере.
ACTION_VIEWОткрыть данные (URI + MIME)1+Например: intent://, http://, tel:, geo:, mailto:.
ACTION_EDITОткрыть данные для редактирования1+Например, редактирование контакта.
ACTION_ATTACH_DATAПрикрепить данные к другому интенту (редко)1+Используется IntentSender.sendIntent() с ClipData.
ACTION_PICKВыбрать элемент из набора (например, контакт)1+Возвращает URI через onActivityResult() / ActivityResultContract.
ACTION_CHOOSERПоказать выбор (аналог Intent.createChooser())1+Не требует startActivity() — используется внутри startActivity().
ACTION_GET_CONTENTВыбрать контент любого типа (файл, изображение и т.д.)1+Возвращает URI. Заменён ACTION_OPEN_DOCUMENT (DocumentsUI).
ACTION_OPEN_DOCUMENTОткрыть документ через DocumentsUI19+Поддерживает CATEGORY_OPENABLE, EXTRA_ALLOW_MULTIPLE.
ACTION_CREATE_DOCUMENTСоздать новый документ19+Аналогично OPEN_DOCUMENT, но создаёт.
ACTION_SENDОтправить данные (текст, изображение)1+Требует EXTRA_TEXT / EXTRA_STREAM, setType().
ACTION_SEND_MULTIPLEОтправить несколько элементов4+EXTRA_STREAMArrayList<Uri>.
ACTION_INSERTВставить новый элемент в набор (например, контакт)1+Редко используется.
ACTION_DELETEУдалить данные по URI1+Например, content://contacts/people/1.
ACTION_DIALОткрыть dialer с предзаполненным номером1+Не требует CALL_PHONE.
ACTION_CALLНемедленно вызвать номер1+Требует CALL_PHONE (runtime, dangerous).
ACTION_SENDTOОткрыть SMS/MMS/Email клиент с адресом1+URI: sms:, smsto:, mms:, mailto:.
ACTION_ANSWERОтветить на входящий вызов5+Требует ANSWER_PHONE_CALLS (runtime, API ≥26).
ACTION_SYNCЗапустить синхронизацию аккаунта5+Используется ContentResolver.requestSync().
ACTION_SEARCHВыполнить поиск1+Требует <meta-data android:name="android.app.searchable" ...>.
ACTION_WEB_SEARCHОткрыть веб-поиск1+Запускает Google Search или другой.
ACTION_BOOT_COMPLETEDЗагрузка системы завершена1+exported="true", permission="android.permission.RECEIVE_BOOT_COMPLETED".
ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGEDРучное изменение времени/зоны1+Разрешены для broadcast receiver даже после Android 8+.
ACTION_BATTERY_LOW, ACTION_BATTERY_OKAYНизкий заряд / восстановление1+
ACTION_POWER_CONNECTED, ACTION_POWER_DISCONNECTEDПодключение/отключение питания1+
ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENTСобытия экрана1+SCREEN_ON/OFF — нельзя зарегистрировать через manifest (только динамически).
ACTION_PACKAGE_ADDED, REPLACED, REMOVED, FULLY_REMOVEDИзменения пакетов1+Требуют android:permission="android.permission.BROADCAST_PACKAGE_REMOVED" и др. В URI — package:com.example.
ACTION_MY_PACKAGE_REPLACEDОбновление своего пакета12+Безопасно — не требует exported="true".
ACTION_CLOSE_SYSTEM_DIALOGSЗакрыть системные диалоги (например, power menu)1+Требует signature permission.
ACTION_ASSISTЗапустить assistant (например, Google Assistant)23+
ACTION_VOICE_COMMANDГолосовая команда21+
ACTION_PROCESS_TEXTОбработать выделенный текст (Android 6.0+)23+Появляется в контекстном меню выбора. Требует EXTRA_PROCESS_TEXT.

3.2 Intent — Categories (Intent.CATEGORY_*)

КонстантаОписаниеПримечания
CATEGORY_DEFAULTПодразумевается, если startActivity(intent) без категорииОбязателен в <intent-filter>, если вызов через startActivity(intent) без явного указания категории.
CATEGORY_LAUNCHERПоказывает Activity в лаунчереДолжна быть одна на приложение (обычно).
CATEGORY_BROWSABLEActivity доступна из браузера (например, по ссылке)Обязательна для deep link’ов.
CATEGORY_APP_BROWSER, CATEGORY_APP_CALCULATOR, ..., CATEGORY_APP_*Группы системных приложенийНапример, CATEGORY_APP_MUSIC для музыкальных приложений.
CATEGORY_LEANBACK_LAUNCHERДля Android TVАналог LAUNCHER, но для TV.
CATEGORY_INFO«About» экран (редко)
CATEGORY_OPENABLEURI, возвращаемый интентом, должен быть «открываемым» (readable stream)Для ACTION_GET_CONTENT, OPEN_DOCUMENT.
CATEGORY_TEST, CATEGORY_UNIT_TEST, CATEGORY_MONKEYТестирование
CATEGORY_CAR_MODE, CATEGORY_DESK_MODE, CATEGORY_VR_MODEРежимы устройства

3.3 Intent — Extras (Intent.EXTRA_*)

EXTRAТипОписаниеПримечания
EXTRA_TEXTCharSequenceТекст для ACTION_SEND
EXTRA_HTML_TEXTStringHTML-версия текстаПоддерживается не всеми получателями.
EXTRA_SUBJECTCharSequenceТема (email, SMS)
EXTRA_STREAMUriURI потока (content://, file://)Для ACTION_SEND, ACTION_VIEW.
EXTRA_STREAMArrayList<Uri>Несколько URIДля ACTION_SEND_MULTIPLE.
EXTRA_EMAIL, EXTRA_CC, EXTRA_BCCString[]Адреса email
EXTRA_TITLECharSequenceЗаголовок диалога (например, для ACTION_GET_CONTENT)
EXTRA_ALLOW_MULTIPLEbooleanРазрешить множественный выборДля ACTION_OPEN_DOCUMENT, GET_CONTENT.
EXTRA_MIME_TYPESString[]Список допустимых MIME typesДля ACTION_OPEN_DOCUMENT (подменяет setType()).
EXTRA_PROCESS_TEXTCharSequenceВыделенный текстДля ACTION_PROCESS_TEXT.
EXTRA_RETURN_RESULTbooleanВернуть результат вместо завершенияДля некоторых системных Activity (например, wallpaper picker).
EXTRA_KEY_EVENTKeyEventKeyEvent (редко)
EXTRA_INTENTIntentВложенный интентНапример, в ACTION_CHOOSER.
EXTRA_SHORTCUT_INTENT, EXTRA_SHORTCUT_NAME, EXTRA_SHORTCUT_ICONДля динамических shortcut’ов (устарело, см. ShortcutManager).
EXTRA_REFERRERUriИсточник запуска (например, android-app://package)Устанавливается системой. Доступен через getReferrer().
EXTRA_PACKAGE_NAMEStringИмя пакетаВ broadcast’ах (например, PACKAGE_ADDED).

3.4 Intent — Flags (Intent.FLAG_*)

ФлагОписаниеПримечания
FLAG_ACTIVITY_NEW_TASKЗапустить Activity в новой задачеОбязателен для startActivity() из Context без Activity (например, из Service, BroadcastReceiver).
FLAG_ACTIVITY_CLEAR_TASKОчистить текущую задачу перед запускомДолжен использоваться с NEW_TASK.
FLAG_ACTIVITY_CLEAR_TOPУдалить все Activity над целевой в стекеЕсли целевая уже есть — она получает onNewIntent().
FLAG_ACTIVITY_SINGLE_TOPАналог launchMode="singleTop"
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTSНе показывать в Recent Apps
FLAG_ACTIVITY_NO_ANIMATIONОтключить анимацию перехода
FLAG_ACTIVITY_REORDER_TO_FRONTПереместить существующую Activity вперёдВместо создания новой.
FLAG_ACTIVITY_RESET_TASK_IF_NEEDEDСбросить задачу до initial stateПри возврате в приложение из recents.
FLAG_ACTIVITY_LAUNCHED_FROM_HISTORYЗапуск из истории (recents)Устанавливается системой.
FLAG_ACTIVITY_BROUGHT_TO_FRONTActivity была перемещена вперёдУстанавливается системой.
FLAG_ACTIVITY_NEW_DOCUMENTОткрыть как новый документ (recents stack per document)Аналог documentLaunchMode="always".
FLAG_ACTIVITY_REQUIRE_DEFAULTТребовать, чтобы приложение было установлено по умолчаниюДля ACTION_VIEW, например.
FLAG_ACTIVITY_REQUIRE_LOCKEDТребовать, чтобы экран был заблокированДля ACTION_VIEW с sensitive content.
FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSIONВременно предоставить доступ к URIИспользуется с ContentProvider и FileProvider.
FLAG_GRANT_PERSISTABLE_URI_PERMISSIONРазрешить сохранение разрешения после перезагрузкиЧерез takePersistableUriPermission().
FLAG_DEBUG_LOG_RESOLUTIONЛоггировать разрешение intent-filter’овТолько в debug-сборке.

⚠️ Важно: флаги CLEAR_*, NEW_TASK, REORDER_* влияют на задачу (task), а не на стек Activity в рамках задачи.


3.5 PendingIntent — флаги и поведения

ФлагОписаниеПримечания
FLAG_IMMUTABLEPendingIntent нельзя изменитьОбязателен начиная с Android 12 (API 31), если не требуется мутабельность.
FLAG_MUTABLEPendingIntent можно изменитьТребуется для Activity, Broadcast, Service, если данные/экстра должны обновляться.
FLAG_UPDATE_CURRENTОбновить экстра существующего PendingIntentЕсли Intent совпадает (по filterEquals()), новые экстра заменят старые.
FLAG_CANCEL_CURRENTОтменить текущий, создать новый
FLAG_ONE_SHOTРазрешить использование только один разПосле send()null.

⚠️ Без явного указания IMMUTABLE/MUTABLE в Android 12+ будет SecurityException.


3.6 Activity Result API (registerForActivityResult)

Начиная с activity 1.2.0 (Jetpack), заменяет startActivityForResult().

Основные контракты (ActivityResultContracts.*)

КонтрактВходной типВозвратОписание
StartActivityForResult()IntentActivityResult (resultCode, data: Intent)Прямая замена startActivityForResult().
RequestPermission(permission: String)BooleanЗапрос одного разрешения.
RequestMultiplePermissions()Set<String>Map<String, Boolean>Запрос нескольких.
TakePicturePreview()Bitmap?Снимок без сохранения (preview only).
TakePicture()Uri (output)Boolean (success)Сохраняет в указанный content:// URI. Требует WRITE permission на URI.
TakeVideo()Uri (output)Bitmap? (thumbnail)Запись видео.
GetContent()String (MIME filter)Uri?Аналог ACTION_GET_CONTENT.
OpenDocument()Array<String> (MIME), Boolean (multiple)List<Uri>?DocumentsUI.
CreateDocument(mimeType: String)String (initial filename)Uri?Создание документа.
PickContact()Uri?Выбрать контакт.
RequestPermission, RequestMultiplePermissionsBoolean / MapРаботает с onRequestPermissionsResult() под капотом.
StartIntentSenderForResult()IntentSender, Bundle, Int, Int, IntActivityResultДля PendingIntent, MediaRouter, MediaProjection.

Пользовательские контракты

  • Наследуют ActivityResultContract<I, O>
  • Реализуют: createIntent(context, input), parseResult(resultCode, intent), getSynchronousResult(context, input) (опционально)

3.7 Permissions — перечень dangerous/runtime

Все перечисленные ниже требуют requestPermissions() при API ≥23.

Группа: CALENDAR

РазрешениеОписание
READ_CALENDARЧтение календарей и событий
WRITE_CALENDARЗапись/изменение событий

Группа: CALL_LOG

| READ_CALL_LOG | Чтение журнала вызовов | | WRITE_CALL_LOG | Изменение журнала | | PROCESS_OUTGOING_CALLS | Перехват исходящих вызовов (устарело в API 29, удалено в 29+) |

Группа: CAMERA

| CAMERA | Доступ к камере |

Группа: CONTACTS

| READ_CONTACTS | Чтение контактов и аккаунтов | | WRITE_CONTACTS | Изменение контактов | | GET_ACCOUNTS | Получение списка аккаунтов (устарело в API 26, GET_ACCOUNTS_PRIVILEGEDsignature) |

Группа: LOCATION

| ACCESS_FINE_LOCATION | GPS, Wi-Fi, Bluetooth (точное) | | ACCESS_COARSE_LOCATION | Сетевое определение (приблизительное) | | ACCESS_BACKGROUND_LOCATION | Доступ к местоположению в фоне (API ≥29, отдельный запрос, строгая модерация в GP) |

Группа: MICROPHONE

| RECORD_AUDIO | Доступ к микрофону |

Группа: PHONE

| READ_PHONE_STATE | IMEI, номер, статус вызова | | CALL_PHONE | Прямой вызов без подтверждения | | READ_PHONE_NUMBERS | Номера телефонов (API ≥26) | | ANSWER_PHONE_CALLS | Автоответ (API ≥26, RoleManager.isRoleAvailable(ROLE_CALL_SCREENING)) | | ACCEPT_HANDOVER | Принять передачу вызова от другого приложения (API ≥26) |

Группа: SENSORS

| BODY_SENSORS | Данные сенсоров (пульс, шаги) | | BODY_SENSORS_BACKGROUND | В фоне (API ≥30) | | ACTIVITY_RECOGNITION | Распознавание активности (ходьба, бег) (API ≥29) |

Группа: SMS

| SEND_SMS, RECEIVE_SMS, READ_SMS, RECEIVE_WAP_PUSH, RECEIVE_MMS | Полный контроль над SMS/MMS (строгая модерация в GP, почти недоступно для обычных приложений) |

Группа: STORAGE

| READ_EXTERNAL_STORAGE | Чтение shared storage (до Android 10) | | WRITE_EXTERNAL_STORAGE | Запись (до Android 10) | | MANAGE_EXTERNAL_STORAGE | Доступ ко всем файлам (All files access, API ≥30, Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) |

💡 Начиная с Android 10 (API 29): Scoped Storage

  • READ_EXTERNAL_STORAGE даёт доступ только к MediaStore.Images, Audio, Video, и собственной Context.getExternalFilesDir()
  • Для доступа к другим файлам — Storage Access Framework (ACTION_OPEN_DOCUMENT) или MANAGE_EXTERNAL_STORAGE (с ограничениями Google Play)

Специальные foreground-сервисы (API ≥29, targetSdk ≥29)

ТипРазрешениеТребования
locationACCESS_FINE_LOCATION или COARSEДолжен быть указан в <service android:foregroundServiceType="location">
cameraCAMERA
microphoneRECORD_AUDIO
phoneCallREAD_PHONE_STATEТолько для системных приложений (privileged)
mediaPlaybackДля аудиоплееров
mediaProjectionandroid.permission.MEDIA_PROJECTION (внутреннее)Через MediaProjectionManager.createScreenCaptureIntent()
healthДля health-устройств (API ≥34)

⚠️ Заявка типа foregroundServiceType обязательна при старте startForeground(), иначе ForegroundServiceStartNotAllowedException.


3.8 Bundle — ограничения и типы

ТипПоддержкаОграничения
boolean, byte, char, short, int, long, float, double
StringДлина ≤ 1 МБ (ограничение Binder)
Parcelable✅ (рекомендуется)Должен реализовывать Parcelable правильно (не использовать Serializable).
Serializable✅ (не рекомендуется)Медленно, создаёт много garbage.
BundleВложенные Bundle.
ArrayList<T> где T — поддерживаемый тип
SparseArray, Size, SizeF, IBinderIBinder — для IPC.
Object, Map, List (не ArrayList)ClassCastException при извлечении.

⚠️ Лимит размера Transaction: ~1 МБ на всю транзакцию (включая интент, экстра, PendingIntent). Превышение → TransactionTooLargeException.


3.9 Часто используемые URI schemes и MIME types

SchemeПримерMIME type
http://, https://https://example.comtext/html, application/json и др.
tel:tel:+79001234567
sms:, smsto:sms:12345?body=Hi
mailto:mailto:test@example.com?subject=Hello
geo:geo:0,0?q=Москва
market:market://details?id=com.example— (Google Play)
intent:intent://scan/#Intent;scheme=...— (Deep link)
content://content://media/external/images/media/123image/jpeg, video/mp4, audio/mpeg, application/pdf и др.

Стандартные MIME:

  • */* — любой
  • image/*, video/*, audio/*, text/*, application/*
  • application/json, application/xml, application/pdf, application/zip
  • text/plain, text/html, text/csv

🎨 Resources


4.1 Resource Qualifiers (в порядке приоритета применения)

Каждая папка res/ может иметь суффикс вида -qualifier1-qualifier2....
Система выбирает наиболее специфичную конфигурацию, соответствующую устройству.

КвалификаторФорматПримерыОписаниеAPI+Примечания
MCCmcc<код>mcc310 (США), mcc250 (Россия)Mobile Country Code1+Высший приоритет. Редко используется.
MNCmnc<код>mnc001, mnc02Mobile Network Code1+Всегда после MCC: mcc310-mnc004.
Languageb+<код> (BCP 47) или <код>en, ru, zh, b+zh+Hans, b+sr+LatnЯзык1+ (b+ — 21+)Предпочтительно b+ (например, b+zh+Hans+CN).
Regionr<регион>rUS, rRU, rCNРегион (страна)1+После языка: en-rUS, ru-rRU.
Screen Layout Directionldrtl, ldltrldrtlRTL / LTR17+ldrtl — right-to-left (арабский, иврит).
Smallest Widthsw<dp>dpsw600dp, sw720dpМинимальная ширина в dp13+Ключевой для адаптивного дизайна.
Available Widthw<dp>dpw720dp, w1080dpТекущая доступная ширина13+Меняется при повороте.
Available Heighth<dp>dph720dpТекущая доступная высота13+
Screen Sizesmall, normal, large, xlargelargeУстаревшая категория1+Не рекомендуется — используйте sw<N>dp.
Screen Aspectlong, notlonglongСоотношение сторон (≥1.75:1 — long)1+
Screen Orientationport, landlandПортрет / ландшафт1+
UI Modecar, desk, television, appliance, watch, vrheadset, ui-mode-night, ui-mode-night-no, ui-mode-car, ui-mode-desk, ui-mode-television, ui-mode-appliance, ui-mode-watch, ui-mode-vrheadsetnight, televisionРежим устройства / тема8+ (night — 23+)ui-mode-night — ночная тема (системная).
Night Modenight, notnightnightЯвное управление ночным режимом8+Переопределяет ui-mode-night.
Screen Pixel Densityldpi, mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi, nodpi, tvdpi, anydpixxhdpi, anydpiПлотность пикселей4+ (anydpi — 21+)anydpi — векторные ресурсы (SVG, VectorDrawable).
Touchscreen Typenotouch, stylus, fingerfingerТип сенсора4+
Keyboard Availabilitykeysexposed, keyshidden, keyssoftkeyshiddenФизическая клавиатура4+
Primary Text Input Methodnokeys, qwerty, 12keyqwerty4+
Navigation Key Availabilitynavexposed, navhiddennavhiddenНавигационные кнопки4+
Primary Non-Touch Navigation Methodnonav, dpad, trackball, wheeldpad4+
Screen ConfigurationscreenLayout (устарело)4–30Заменено sw<N>dp, w<N>dp.
Layout Directionldrtl, ldltrldrtlНаправление текста17+Уже выше, но повтор для акцента.
Round Screenround, notroundroundКруглый экран21+Для Wear OS.
Watch Shapewatch-round, watch-square, watch-rectwatch-roundФорма часов23+Wear OS.
Foldable Statesfolded, unfolded, hinge, posturefolded, unfoldedСостояние складного устройства30+Требует <queries> и Configuration.isLayoutSizeAtLeast().
Postureposture-natural, posture-book, posture-tabletop, posture-tentposture-bookПоложение устройства (книга, штатив)31+Через WindowManager.getCurrentWindowMetrics().
Device Classdevice-class-phone, device-class-tablet, device-class-desktop, device-class-tv, device-class-car, device-class-watchdevice-class-tabletКласс устройства (Material Design 3)31+Определяется системой на основе sw<N>dp и DPI.
Dynamic Colordynamic-color, dynamic-color-nodynamic-colorПоддержка Material You (Monet)31+Требует android:theme="@style/Theme.Material3.*.DynamicColors*".
Font ScalingfontScale (устарело)1–30Заменено Configuration.fontScale.
Locale Scriptscript-<код>script-Latn, script-CyrlСкрипт локали21+В b+ формате: b+zh+Hans.

⚠️ Приоритет: MCC > MNC > языки/регионы > sw/w/h > ориентация > night > density > остальное.
Пример составного пути:
res/values-sw600dp-land-night-v31/ — планшет, ландшафт, ночная тема, Android 12+


4.2 attrs.xml — объявление кастомных атрибутов

Файл res/values/attrs.xml:

<resources>
<declare-styleable name="MyCustomView">
<attr name="title" format="string" />
<attr name="icon" format="reference" />
<attr name="radius" format="dimension" />
<attr name="enabled" format="boolean" />
<attr name="progress" format="integer" min="0" max="100" />
<attr name="alpha" format="float" />
<attr name="ratio" format="fraction" />
<attr name="colorScheme" format="enum">
<enum name="light" value="0" />
<enum name="dark" value="1" />
<enum name="system" value="2" />
</attr>
<attr name="flags" format="flags">
<flag name="read" value="1" />
<flag name="write" value="2" />
<flag name="execute" value="4" />
</attr>
<attr name="backgroundTint" format="color" />
<attr name="layout_constraintGuide_percent" format="float" />
</declare-styleable>
</resources>
Формат (format)Тип в Java/KotlinДопустимые XML-значенияПримечания
stringString"text", @string/...
booleanBooleantrue, false, @bool/...
integerInt123, @integer/...Можно min/max.
floatFloat0.5, @fraction/...
dimensionFloat (px)16dp, 12sp, 100px, @dimen/...Преобразуется в пиксели через TypedValue.applyDimension().
colorInt (ARGB)#FF0000, @color/..., ?attr/colorPrimaryПоддерживает ColorStateList.
referenceInt (res ID)@drawable/..., @layout/..., @+id/...Возвращает ID ресурса, не объект.
fractionFloat50%, 0.5, @fraction/...50% = 0.5f.
enumIntИмя из <enum>Строго ограниченное множество.
flagsIntКомбинация имён через |Например: `app:flags="read

💡 ?attr/... — ссылка на атрибут темы, а не на значение. Разрешается во время inflate.


4.3 Styles и Themes — иерархия и ключевые атрибуты

Наследование

<style name="Base.Theme.MyApp" parent="Theme.Material3.DayNight">
<item name="colorPrimary">@color/md_theme_primary</item>
<item name="colorOnPrimary">@color/md_theme_onPrimary</item>
<item name="colorPrimaryContainer">@color/md_theme_primaryContainer</item>
<item name="colorOnPrimaryContainer">@color/md_theme_onPrimaryContainer</item>
<item name="colorSecondary">@color/md_theme_secondary</item>
<item name="colorOnSecondary">@color/md_theme_onSecondary</item>
<item name="colorSecondaryContainer">@color/md_theme_secondaryContainer</item>
<item name="colorOnSecondaryContainer">@color/md_theme_onSecondaryContainer</item>
<item name="colorTertiary">@color/md_theme_tertiary</item>
<item name="colorOnTertiary">@color/md_theme_onTertiary</item>
<item name="colorTertiaryContainer">@color/md_theme_tertiaryContainer</item>
<item name="colorOnTertiaryContainer">@color/md_theme_onTertiaryContainer</item>
<item name="colorError">@color/md_theme_error</item>
<item name="colorOnError">@color/md_theme_onError</item>
<item name="colorErrorContainer">@color/md_theme_errorContainer</item>
<item name="colorOnErrorContainer">@color/md_theme_onErrorContainer</item>
<item name="android:colorBackground">@color/md_theme_background</item>
<item name="colorOnBackground">@color/md_theme_onBackground</item>
<item name="colorSurface">@color/md_theme_surface</item>
<item name="colorOnSurface">@color/md_theme_onSurface</item>
<item name="colorSurfaceVariant">@color/md_theme_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/md_theme_onSurfaceVariant</item>
<item name="colorOutline">@color/md_theme_outline</item>
<item name="colorOutlineVariant">@color/md_theme_outlineVariant</item>
<item name="colorSurfaceInverse">@color/md_theme_inverseSurface</item>
<item name="colorOnSurfaceInverse">@color/md_theme_inverseOnSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_inversePrimary</item>

<!-- Elevation overlays -->
<item name="elevationOverlayEnabled">true</item>
<item name="elevationOverlayColor">?attr/colorPrimary</item>

<!-- Shapes -->
<item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.MyApp.SmallComponent</item>
<item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.MyApp.MediumComponent</item>
<item name="shapeAppearanceLargeComponent">@style/ShapeAppearance.MyApp.LargeComponent</item>

<!-- Typography -->
<item name="textAppearanceTitleLarge">@style/TextAppearance.MyApp.TitleLarge</item>
<item name="textAppearanceTitleMedium">@style/TextAppearance.MyApp.TitleMedium</item>
<item name="textAppearanceTitleSmall">@style/TextAppearance.MyApp.TitleSmall</item>
<item name="textAppearanceHeadlineLarge">@style/TextAppearance.MyApp.HeadlineLarge</item>
...
</style>

<style name="Theme.MyApp" parent="Base.Theme.MyApp" />

Ключевые Material 3 атрибуты

АтрибутТипОписание
colorPrimary, colorOnPrimary, colorPrimaryContainer, colorOnPrimaryContainercolorОсновная палитра.
colorSecondary, colorTertiarycolorДополнительные цвета.
colorSurface, colorOnSurface, colorSurfaceVariant, colorOnSurfaceVariantcolorПоверхности (фон, карточки).
colorError, colorOnError, colorErrorContainer, colorOnErrorContainercolorОшибки.
colorOutline, colorOutlineVariantcolorГраницы (1dp / 0.5dp).
colorSurfaceInverse, colorOnSurfaceInverse, colorPrimaryInversecolorИнвертированные цвета (для тёмных оверлеев).
elevationOverlayEnabledbooleanНакладывать ли цвет поверхности при elevation > 0 (только в night mode).
elevationOverlayColorcolorЦвет оверлея (обычно colorPrimary).
shapeAppearance*Component@style/ShapeAppearanceЗакругления: cornerSize, cornerFamily.
textAppearance*@style/TextAppearanceШрифты: fontFamily, fontWeight, fontSize, letterSpacing, lineHeight.

4.4 ColorStateList — перечень состояний

Файл res/color/button_states.xml:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/red" android:state_enabled="false" />
<item android:color="@color/blue" android:state_pressed="true" />
<item android:color="@color/green" android:state_focused="true" />
<item android:color="@color/gray" />
</selector>
Состояние (android:state_*)ТипОписаниеПриоритет
state_pressedbooleanНажатие (touch down)Высокий
state_focusedbooleanФокус (клавиатура/DPAD)Средний
state_selectedbooleanВыбор (например, в ListView)Средний
state_checkedbooleanChecked (RadioButton, CheckBox)Средний
state_enabledbooleanВключено/выключеноВысокий (часто !enabled первым)
state_activatedbooleanАктивировано (например, CHOICE_MODE_SINGLE)Средний
state_window_focusedbooleanОкно в фокусеНизкий
state_hoveredbooleanНаведение (мышь/трекпад)Высокий
state_drag_can_acceptbooleanМожет принять dragAPI 11+
state_drag_hoveredbooleanHover во время dragAPI 11+

⚠️ Приоритет: порядок <item> важен — первый совпавший применяется. Обычно: !enabledpressedfocuseddefault.

Material Design 3 добавляет динамические оверлеи через colorSurface + elevation — но это не в ColorStateList.


4.5 Drawable — перечень типов

4.5.1 ShapeDrawable (<shape>)

АтрибутЗначенияПримечания
android:shaperectangle, oval, line, ring
android:innerRadius, innerRadiusRatiodp, floatТолько для ring.
android:thickness, thicknessRatiodp, floatТолько для ring.
android:useLevelbooleanДля LevelListDrawable.
<solid android:color="..."/>colorЗаливка.
<stroke android:width="..." android:color="..." android:dashWidth="..." android:dashGap="..."/>dp, colorГраница.
<corners android:radius="..." android:topLeftRadius="..." ... />dpЗакругления.
<gradient android:type="linear/radial/sweep" android:angle="..." android:centerX/Y="..." android:gradientRadius="..." android:start/end/centerColor="..." />Градиент.
<padding android:left="..." ... />dpВнутренний отступ.
<size android:width="..." android:height="..."/>dpРекомендуемый размер.

4.5.2 StateListDrawable (<selector>)

АтрибутЗначения
android:exitFadeDuration, android:enterFadeDurationms
<item android:drawable="..." android:state_*="..." />

4.5.3 LayerDrawable (<layer-list>)

ЭлементАтрибуты
<item>android:drawable, android:id, android:left/top/right/bottom, android:width/height, android:gravity

4.5.4 InsetDrawable, ClipDrawable, RotateDrawable, ScaleDrawable, LevelListDrawable, TransitionDrawable, AnimatedStateListDrawable, AnimatedVectorDrawable, AnimatedImageDrawable

ТипКлючевой атрибут / метод
InsetDrawableandroid:insetLeft/Top/Right/Bottom
ClipDrawableandroid:clipOrientation, android:gravity, level (0–10000)
RotateDrawableandroid:fromDegrees, toDegrees, pivotX/Y
ScaleDrawableandroid:scaleWidth/Height, level
LevelListDrawable<item android:minLevel="..." android:maxLevel="..." android:drawable="..."/>
TransitionDrawablestartTransition(duration), reverseTransition()
AnimatedStateListDrawable<item android:id="@+id/state1" .../>, <transition android:fromId="@id/state1" android:toId="@id/state2" android:drawable="..."/>
AnimatedVectorDrawable<animated-vector android:drawable="@drawable/vec"> <target android:name="path1" android:animation="@animator/anim1"/> </animated-vector>

4.5.5 RippleDrawable (<ripple>)

АтрибутЗначения
android:colorЦвет риппла (обычно ?attr/colorControlHighlight)
<item>Фон под рипплом
android:radiusФиксированный радиус (или ?android:attr/rippleRadius)
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight">
<item android:drawable="?android:attr/selectableItemBackground" />
</ripple>

4.6 VectorDrawable

Файл res/drawable/ic_launcher_foreground.xml:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true"
android:alpha="1.0">

<group
android:name="rotationGroup"
android:pivotX="54"
android:pivotY="54"
android:rotation="0">

<path
android:name="path1"
android:pathData="M 10,10 L 98,10 L 98,98 Z"
android:fillColor="#FF0000"
android:strokeColor="#000000"
android:strokeWidth="2"
android:strokeLineCap="butt"
android:strokeLineJoin="miter"
android:strokeMiterLimit="4"
android:fillType="nonZero" />

<clip-path
android:name="clip1"
android:pathData="M 20,20 L 88,20 L 88,88 Z" />

</group>
</vector>
АтрибутЭлементЗначенияПримечания
android:width, height<vector>dp, pxФизический размер.
android:viewportWidth, viewportHeight<vector>floatЛогическая система координат.
android:tint<vector>color, ColorStateListНаложение цвета на все пути.
android:tintMode<vector>src_in, src_over, multiply, screen, add, overlayРежим наложения.
android:autoMirrored<vector>booleanЗеркалирование для RTL.
android:alpha<vector>, <group>, <path>[0.0, 1.0]Прозрачность.
android:name<vector>, <group>, <path>, <clip-path>StringДля анимации (AnimatedVectorDrawable).
android:pathData<path>, <clip-path>SVG-like path commands (M, L, C, Q, Z и др.)
android:fillColor, strokeColor<path>color, ColorStateList
android:strokeWidth<path>px (в viewport)
android:strokeLineCap<path>butt, round, square
android:strokeLineJoin<path>miter, round, bevel
android:strokeMiterLimit<path>float
android:fillType<path>nonZero, evenOddПравило заливки.
android:pivotX/Y, rotation, scaleX/Y, translateX/Y<group>float (viewport)Трансформации.

⚠️ SVG → VectorDrawable: не все SVG поддерживаются. Используйте Android Studio → Vector Asset → Local SVG.


4.7 Анимации

4.7.1 Property Animation (animator/, ObjectAnimator)

Файл res/animator/slide_in.xml:

<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="translationX"
android:valueFrom="1080"
android:valueTo="0"
android:valueType="floatType"
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in" />
АтрибутЗначенияПримечания
android:propertyNametranslationX, alpha, rotation, scaleX, backgroundColor, custom propertyДолжен быть setXxx() / getXxx().
android:valueFrom, valueTo, valueTypefloatType, intType, pathType, colorTypecolorType — ARGB (например, #FF0000#00FF00).
android:duration, startDelay, repeatCount, repeatModems, int, restart/reverse
android:interpolator@android:interpolator/*, @interpolator/customfast_out_slow_in, linear, accelerate_decelerate, overshoot и др.

4.7.2 View Animation (anim/, устаревает)

ТипТегАтрибуты
Alpha<alpha>fromAlpha, toAlpha
Scale<scale>fromX/YScale, toX/YScale, pivotX/Y
Translate<translate>fromX/YDelta, toX/YDelta
Rotate<rotate>fromDegrees, toDegrees, pivotX/Y
Set<set>android:shareInterpolator, вложенные анимации

Не рекомендуется: не меняет реальные свойства (только отрисовку), нет onAnimationEnd у View.

4.7.3 AnimatedVectorDrawable

Требует:

  1. VectorDrawable с android:name
  2. Animator с android:propertyName="@string/pathName.fillColor"
  3. <animated-vector> — связывает их

4.7.4 TransitionManager и Scene

КлассМетодНазначение
TransitionManagerbeginDelayedTransition(ViewGroup, Transition)Автоматическая анимация layout-изменений
ChangeBounds, Fade, ChangeTransform, AutoTransitionСтандартные Transition’ы
TransitionSetaddTransition(), setOrdering(ORDERING_TOGETHER/SEQUENTIAL)Группировка

⚙️ Gradle — build.gradle(:app)


5.1 Корневой блок android { } — ключевые свойства

СвойствоТипЗначение по умолчаниюAGP+Описание
compileSdkInt7.0+Версия SDK для компиляции (должна быть ≥ targetSdk). Использует build-tools, platforms/android-N.
buildToolsVersionStringПоследняя, совместимая с compileSdk1.0+Явное указание build-tools (рекомендуется не указывать).
ndkVersionStringПоследняя, установленная2.2+Версия NDK (например, "25.2.9519653").
defaultConfig { }DefaultConfig1.0+Базовая конфигурация для всех вариантов.
buildTypes { }NamedDomainObjectContainer<BuildType>debug, release1.0+Конфигурации сборки.
productFlavors { }NamedDomainObjectContainer<ProductFlavor>1.1+Варианты продукта (например, free, pro, internal, prod).
flavorDimensionsList<String>["version"]3.0+Размерности для комбинирования (dimension "version" в каждом flavor’е).
compileOptions { }CompileOptionssourceCompatibility = JavaVersion.VERSION_1_8, targetCompatibility = JavaVersion.VERSION_1_83.0+Настройки javac.
kotlinOptions { }KotlinJvmOptions3.0+Настройки Kotlin-компилятора (jvmTarget, freeCompilerArgs).

5.2 defaultConfig { } — обязательные и часто используемые

СвойствоТипЗначение по умолчаниюПримечания
applicationIdStringandroid.namespace (если задан) или package из AndroidManifest.xmlИзменяет package в APK (не в коде!). Для library не используется.
minSdkIntМинимальная поддерживаемая версия. Влияет на доступные API и оптимизации (desugar, coreLibraryDesugaring).
targetSdkIntВерсия поведения системы (режимы совместимости, permission model, scoped storage). Должна быть ≤ compileSdk.
versionCodeIntУникальный номер сборки (увеличивается монотонно). Максимум — 2100000000 (ограничение Play Console).
versionNameStringПоказывается пользователю (может быть "1.2.3-beta").
testApplicationIdString${applicationId}.testТолько для androidTest.
testInstrumentationRunnerString"androidx.test.runner.AndroidJUnitRunner"Для androidTest.
testInstrumentationRunnerArgumentsMap<String, String>[:]Аргументы runner’а (например, "clearPackageData" to "true").
multiDexEnabledBooleanfalseЕсли minSdk < 21, требует androidx.multidex:multidex.
vectorDrawables.useSupportLibraryBooleantrueДля minSdk < 21 — использовать AppCompatDelegate.setCompatVectorFromResourcesEnabled(true).
vectorDrawables.generatedDensitiesList<String>[]Генерировать PNG из VectorDrawable для указанных плотностей (устарело, избегать).

5.3 buildTypes { } — стандартные и кастомные

Стандартный debug

debug {
isDebuggable = true
isMinifyEnabled = false
isShrinkResources = false
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
signingConfig = signingConfigs.debug
}
СвойствоТипЗначение по умолчанию (debug)Значение по умолчанию (release)Эффект
isDebuggableBooleantruefalseВключает Debug.startMethodTracing(), StrictMode, debug-билд нативных библиотек.
isMinifyEnabledBooleanfalsefalseВключает R8/ProGuard (сжатие, оптимизация, обфускация). Требует proguardFiles.
isShrinkResourcesBooleanfalsefalseУдаляет неиспользуемые ресурсы (только при minifyEnabled = true).
isZipAlignEnabledBooleantruetrueВыравнивание 4-байтных границ в APK. Обязательно для Play Console.
applicationIdSuffixString""""Добавляется к applicationId (например, .debug). Позволяет устанавливать несколько вариантов.
versionNameSuffixString""""Добавляется к versionName.
matchingFallbacksList<String>["debug"]["release"]При сборке библиотеки — какой build type использовать, если не найден.
proguardFiles(...)List<File>[][]Файлы конфигурации R8/ProGuard.
consumerProguardFiles(...)List<File>[][]Файлы, включаемые в AAR (для библиотек).
signingConfigSigningConfig?signingConfigs.debugnullКонфигурация подписи.

proguardFiles — стандартные файлы

ФайлСодержание
getDefaultProguardFile("proguard-android-optimize.txt")Базовые правила Android + оптимизации (рекомендуется).
getDefaultProguardFile("proguard-android.txt")Базовые правила без оптимизаций (устарело).
proguard-rules.proПользовательские правила (удержание классов, сериализация, reflection, JNI).

⚠️ R8 — замена ProGuard, встроен в AGP ≥3.4.0. Использует те же правила, но быстрее и агрессивнее.


5.4 signingConfigs { }

signingConfigs {
create("release") {
storeFile = file("../keystore/release.jks")
storePassword = providers.gradleProperty("STORE_PASSWORD").orNull
keyAlias = providers.gradleProperty("KEY_ALIAS").orNull
keyPassword = providers.gradleProperty("KEY_PASSWORD").orNull
enableV3Signing = true
enableV4Signing = true
}
}
СвойствоТипПримечания
storeFileFileФайл keystore (.jks, .keystore, .p12).
storePassword, keyPasswordStringХранить в gradle.properties (локально) или CI secrets.
keyAliasStringИмя ключа в keystore.
v1SigningEnabled, v2SigningEnabledBooleanv1 (JAR) — для Android < 7.0, v2 (APK Signature Scheme v2) — для ≥7.0. По умолчанию: v1 = true, v2 = true.
v3SigningEnabled, v4SigningEnabledBooleanv3 (Android 9+), v4 (для incremental installation, adb install-multi-package). По умолчанию: v3 = false, v4 = false (но bundletool включает v3/v4 для AAB).
enableV1Signing, enableV2Signing, enableV3Signing, enableV4Signing(Kotlin DSL)Альтернативные имена.

🔐 Best Practice:

  • Использовать android.injected.signing.store.file и др. для CI.
  • Для Google Play Signing — загружать только upload key (не production key!).
  • v2Signing обязателен для Play Console.

5.5 productFlavors { } и flavorDimensions

flavorDimensions += listOf("version", "environment")

productFlavors {
create("free") { dimension = "version" }
create("pro") { dimension = "version" }
create("dev") { dimension = "environment" }
create("prod") { dimension = "environment" }
}

Результат: 4 варианта: freeDevDebug, freeDevRelease, proProdRelease, и т.д.

СвойствоТипПримечания
dimensionStringДолжен быть объявлен в flavorDimensions.
applicationId, versionName, versionCodeString/IntПереопределяют defaultConfig. versionCode можно увеличивать: versionCode = defaultConfig.versionCode!! + 1000.
manifestPlaceholdersMap<String, Any>Подстановки в AndroidManifest.xml (например, mapKey для API).
buildConfigField("String", "API_BASE_URL", "\"https://api.dev.example.com\"")Генерирует BuildConfig.API_BASE_URL.
resValue("string", "app_name", "MyApp Dev")Генерирует R.string.app_name (перекрывает res/values/strings.xml).
sourceSets { main { java.srcDirs(...) } }Добавление кастомных исходников.

⚠️ Ограничение: applicationId в flavor’е не может содержать placeholder’ы (например, ${applicationId}) — только константы.


5.6 buildFeatures { } — включение/отключение фич

СвойствоТипЗначение по умолчаниюЭффект
viewBindingBooleanfalseГенерирует ActivityMainBinding (без findViewById).
dataBindingBooleanfalseВключает Data Binding (конфликтует с Compose).
composeBooleanfalseВключает Jetpack Compose (требует composeOptions).
aidlBooleantrueГенерация AIDL.
renderScriptBooleanfalseУстарело (удалено в AGP 7.0+).
shadersBooleanfalseВключает RenderScript через Shader (не RS).
resValuesBooleantrueРазрешает resValue.
buildConfigBooleantrueГенерация BuildConfig.
prefabBooleanfalseВключение Prefab (для нативных библиотек из AAR).
androidResourcesBooleantrueОбработка res/ и assets/.

5.7 composeOptions { } (если buildFeatures.compose = true)

СвойствоТипЗначение по умолчаниюПримечания
kotlinCompilerExtensionVersionStringВерсия, совместимая с compose-bomДолжна соответствовать androidx.compose.compiler:compiler.
useIRBooleantrueИспользовать IR-бэкенд (устарело, всегда true в Kotlin ≥1.5.30).

💡 Используйте compose-bom для управления версиями:

implementation(platform("androidx.compose:compose-bom:2024.09.02"))
implementation("androidx.compose.ui:ui")

5.8 packagingOptions { } — управление конфликтами и включением файлов

packagingOptions {
resources {
excludes += listOf(
"**/*.kotlin_module",
"**/META-INF/*.version",
"**/kotlin/**",
"META-INF/AL2.0",
"META-INF/LGPL2.1"
)
merges += listOf("META-INF/LICENSE.md")
pickFirsts += listOf("lib/x86/libc++_shared.so")
}
jniLibs {
useLegacyPackaging = false // AGP 7.1+
}
}
МетодЭффект
excludes += [...]Исключает файлы из APK (например, дублирующие лицензии).
merges += [...]Объединяет файлы с одинаковым путём (например, LICENSE).
pickFirsts += [...]Берёт первый найденный файл (например, для libc++_shared.so).
doNotStrip += [...]Не обрезать символы в нативных библиотеках (для отладки).

⚠️ packagingOptions.jniLibs.useLegacyPackaging — если true, использует старый алгоритм упаковки (AGP < 7.1). По умолчанию false.


5.9 aaptOptions { } — параметры AAPT2

aaptOptions {
noCompress += listOf("tflite", "lite", "pdf", "mp3")
cruncherEnabled = true
additionalParameters += listOf("--rename-manifest-package", "com.example.test")
}
СвойствоЭффект
noCompressСписок расширений, которые не сжимать в APK (например, .so, .mp3, .pdf). По умолчанию: [".arsc"].
cruncherEnabledСжатие PNG (pngcrush). По умолчанию true.
failOnMissingConfigEntryПадать, если нет локали/квалификатора. По умолчанию false.
additionalParametersПрямая передача аргументов в aapt2 link.

🔍 Проверка: ./gradlew assembleRelease --info | grep "aapt2 link" — увидите фактические параметры.


5.10 lint { } — настройка статического анализа

lint {
abortOnError = false
checkReleaseBuilds = true
checkDependencies = true
htmlOutput = file("build/reports/lint-results.html")
xmlOutput = file("build/reports/lint-results.xml")
textOutput = file("build/reports/lint-results.txt")
disable += listOf("InvalidPackage", "UnusedResources")
enable += listOf("Interoperability")
check += listOf("NewApi", "HardcodedText")
ignoreWarnings = false
warningsAsErrors = false
absolutePaths = false
explainIssues = true
}
СвойствоОписание
abortOnErrorПрерывать сборку при ошибках.
checkReleaseBuildsЗапускать lint только для release.
checkDependenciesАнализировать зависимости.
disable, enable, checkУправление конкретными проверками (см. полный список).
htmlOutput, xmlOutput, textOutputОтчёты.

💡 Полезные проверки:

  • UnusedResources — неиспользуемые ресурсы
  • MissingTranslation — отсутствие перевода
  • UnsafeExperimentalUsageError — Compose.Experimental
  • NotificationPermission — использование POST_NOTIFICATIONS без проверки

5.11 testOptions { } — настройка тестов

testOptions {
unitTests {
isIncludeAndroidResources = true
isReturnDefaultValues = false
}
animationsDisabled = true
execution = "ANDROIDX_TEST_ORCHESTRATOR"
}
СвойствоЭффект
unitTests.isIncludeAndroidResourcesВключать android.jar с ресурсами (для Robolectric).
animationsDisabledОтключать анимации в UI-тестах (Settings.Global.ANIMATOR_DURATION_SCALE = 0).
execution = "ANDROIDX_TEST_ORCHESTRATOR"Перезапускать процесс для каждого теста (изоляция).

5.12 androidResources { } — управление ресурсами

androidResources {
noCompress += listOf("res/raw/large.bin")
additionalParameters += listOf("--allow-reserved-package-id", "0x7f", "--package-id", "0x7f")
}
СвойствоПримечания
noCompressТо же, что в aaptOptions, но для AAPT2 напрямую.
additionalParametersРедко используется (например, для кастомных ID пакетов).

5.13 ndk { } — настройка нативной сборки

ndk {
abiFilters += listOf("arm64-v8a", "x86_64")
debugSymbolLevel = "FULL" // или "SYMBOL_TABLE"
moduleName = "native-lib"
}
СвойствоОписание
abiFiltersКакие ABI включать в APK (armeabi-v7a, arm64-v8a, x86, x86_64).
debugSymbolLevelУровень отладочной информации: FULL (для Play Console) или SYMBOL_TABLE (только имена функций).
moduleNameИмя библиотеки (System.loadLibrary("native-lib")).

🔍 Play Console: для FULL требуется uploadNativeSymbols = true в bundle { }.


5.14 bundle { } — настройка Android App Bundle (AAB)

bundle {
language {
enableSplit = true
}
density {
enableSplit = true
}
abi {
enableSplit = true
}
storeArchive {
enable = false
}
dex {
useLegacyPackaging = false
}
uploadNativeSymbols = true
}
БлокСвойствоЭффект
language, density, abienableSplitСоздавать отдельные APK для локалей, плотностей, ABI.
storeArchiveenableСохранять bundle.zip (устарело).
dexuseLegacyPackagingAGP 7.1+ — новый формат DEX.
uploadNativeSymbolstrueВключать отладочные символы в AAB (требуется для debugSymbolLevel = "FULL").

Рекомендация: enableSplit = true для всех — уменьшает размер скачиваемого APK на 15–40%.


5.15 dynamicFeatures { } — модули по требованию

Если в settings.gradle подключён :feature:chat, то:

dynamicFeatures += setOf(":feature:chat")

В AndroidManifest.xml модуля:

<dist:module
dist:title="@string/title_chat"
dist:onDemand="true"
dist:immediate="false">
<dist:fusing dist:include="true" />
</dist:module>
АтрибутОписание
dist:onDemand="true"Загружается по запросу (SplitInstallManager).
dist:immediate="false"Не устанавливать сразу после основного.
dist:fusing dist:include="true"Включать в legacy APK (если пользователь не поддерживает AAB).

5.16 variantFilter { } и applicationVariants.all { }

androidComponents {
onVariants { variant ->
if (variant.buildType == "debug" && variant.productFlavors.contains("prod")) {
variant.enable = false
}
}
}

// Или старый способ (устаревает):
android {
variantFilter {
if (buildType.name == "debug" && flavors.any { it.name == "prod" }) {
setIgnore(true)
}
}

applicationVariants.all {
outputs.forEach { output ->
output.outputFileName = "MyApp-${versionName}-${name}.apk"
}
}
}

⚠️ variantFilter устарел в AGP 8.0+. Используйте androidComponents { } (новый variant API).


🧰 Jetpack


6.1 ViewModel

Жизненный цикл

СобытиеViewModelActivity/Fragment
onCreate()Создаётся (если не существует)
onDestroy() (configuration change)Не уничтожаетсяУничтожается и создаётся заново
onDestroy() (finish / popBackStack)Уничтожается (onCleared())Уничтожается

Ключевые классы и методы

Класс / АннотацияНазначениеПримечания
ViewModelБазовый классРеализует onCleared() для освобождения ресурсов (отписка от Flow, CoroutineScope.cancel()).
AndroidViewModel(application: Application)Доступ к ApplicationИзбегать, если не требуется Context (нарушает SRP).
SavedStateHandleСохранение состояния через onSaveInstanceState()Инъектируется в конструктор (Hilt) или через AbstractSavedStateViewModelFactory.
ViewModelProvider.FactoryСоздание ViewModel с параметрамиТребуется для не-дефолтных конструкторов.
ViewModelProvider.AndroidViewModelFactoryФабрика по умолчанию (для AndroidViewModel)
by viewModels() (KTX)Делегат для получения ViewModelprivate val vm: MainViewModel by viewModels()
by activityViewModels()Общая ViewModel для нескольких Fragment’ов

SavedStateHandle — API

МетодОписание
get<T>(key: String): T?Получить значение
set(key: String, value: T)Установить значение
contains(key: String): BooleanПроверка наличия
keys(): Set<String>Все ключи
LiveData<T> = getLiveData<T>(key)LiveData, синхронизированный с сохранённым состоянием
MutableStateFlow<T> = getStateFlow(scope, initialValue, key)(Compose) StateFlow, синхронизированный с сохранённым состоянием

💡 Best Practice:

  • Использовать SavedStateHandle для навигационных параметров (например, id из deep link’а), а не для всего состояния.
  • Избегать хранения больших объектов (bitmap, список >100 элементов) — это замедляет onSaveInstanceState().

6.2 LiveData (устаревает в пользу StateFlow, но ещё широко используется)

КлассОписание
MutableLiveData<T>Изменяемый источник
LiveData<T>Только для чтения (возвращается из ViewModel)
MediatorLiveData<T>Комбинирует несколько LiveData
Transformations.map(source, mapper)Отображение (source.map { it.length })
Transformations.switchMap(source, mapper)Зависимые запросы (userId.map { repo.getUser(it) })

Ограничения LiveData

  • Не cold: эмитит последнее значение при подписке.
  • Нет backpressure.
  • Не поддерживает ошибки (try/catch внутри map).
  • Не thread-safe: postValue() для background → main, setValue() — только на main.
  • Нет операторов: debounce, throttle, retry.

⚠️ Google рекомендует: использовать StateFlow или SharedFlow в новых проектах (см. Android Dev Blog, 2021).


6.3 Kotlin Flows

StateFlow<T> — замена LiveData

СвойствоЗначение
Холодный?Нет (hot)
Replay1 (последнее значение)
DistinctНет (но distinctUntilChanged() в collect)
Безопасность потоковMutableStateFlow — нет, StateFlow — да
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

// В Compose:
val state by viewModel.uiState.collectAsStateWithLifecycle()

SharedFlow<T> — для событий (аналог SingleLiveEvent)

private val _events = MutableSharedFlow<Event>(replay = 0, extraBufferCapacity = 1)
val events = _events.asSharedFlow()

// В ViewModel:
viewModelScope.launch {
_events.emit(Event.ShowToast("Success"))
}

// В UI:
lifecycleScope.launch {
viewModel.events.collect { event -> /* handle */ }
}
Параметр MutableSharedFlowОписание
replayСколько последних значений хранить (0 — события)
extraBufferCapacityБуфер для backpressure (1 — drop oldest при переполнении)
onBufferOverflowSUSPEND, DROP_OLDEST, DROP_LATEST

callbackFlow — обёртка для callback-API

fun observeLocation(): Flow<Location> = callbackFlow {
val listener = LocationListener { location -> trySend(location) }
locationManager.requestLocationUpdates(listener)
awaitClose { locationManager.removeUpdates(listener) }
}

Преимущества Flows:

  • Поддержка ошибок (try { ... } catch { emit(error) })
  • Операторы: debounce(300), map, filter, combine, flatMapLatest
  • lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) — безопасная подписка

6.4 Room — ORM для SQLite

Аннотации Entity

АннотацияПараметрыОписание
@Entity(tableName = "users")tableName, indices, foreignKeys, ignoreColumnsОпределяет таблицу
@PrimaryKey(autoGenerate = true)autoGenerateINTEGER PRIMARY KEY AUTOINCREMENT
@ColumnInfo(name = "full_name")name, collate, defaultValueМаппинг поля → колонки
@IgnoreИгнорировать поле
@Embedded(prefix = "addr_")prefixВстраивание объекта (например, Addressaddr_street, addr_city)
@ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["user_id"], onDelete = ForeignKey.CASCADE)Внешний ключ

Аннотации DAO

АннотацияПараметрыОписание
@DaoИнтерфейс/абстрактный класс DAO
@Query("SELECT * FROM users WHERE id = :id")Любая SQL-строкаПоддерживает :param, IN (:ids), ?1 (устарело)
@Insert(onConflict = OnConflictStrategy.REPLACE)onConflictВставка
@Update, @DeleteonConflictОбновление/удаление по PK
@TransactionГарантирует атомарность (на методе DAO или @Query("BEGIN TRANSACTION"))
@RawQueryДинамические запросы (SupportSQLiteQuery)

Типы и конвертеры

СлучайРешение
List<String>@TypeConverter → JSON (Gson) или ;-разделённая строка
Enum@TypeConverterString/Int
Instant, LocalDateTime@TypeConverterLong (timestamp)
BitmapНе хранить в БД — только путь или URI

Асинхронность

Тип возвращаемого значенияПоведение
suspend fun getUsers(): List<User>Корутина (рекомендуется)
fun getUsers(): Flow<List<User>>Реактивное обновление при изменениях
fun getUsers(): LiveData<List<User>>Для совместимости с LiveData
Single<List<User>>Для RxJava

@Relation и @Transaction — связанные данные

data class UserWithPosts(
@Embedded val user: User,
@Relation(
parentColumn = "id",
entityColumn = "user_id"
)
val posts: List<Post>
)

@Transaction
@Query("SELECT * FROM users")
suspend fun getUsersWithPosts(): List<UserWithPosts>

⚠️ Важно: без @Transaction возможна гонка: user изменился между чтением users и posts.


6.5 Navigation Component

Архитектура

NavHost (Compose/XML)
└── NavGraph (res/navigation/nav_graph.xml или `NavHostController`)
├── composable("home") { HomeScreen() }
├── composable("detail/{id}", arguments = listOf(navArgument("id") { type = NavType.IntType })) { BackStackEntry ->
DetailScreen(id = it.arguments?.getInt("id")!!)
}
└── dialog("login") { LoginDialog() }

Ключевые классы

КлассНазначение
NavControllerУправление навигацией (navigate(), popBackStack())
NavHostController (Compose)Расширение NavController для Compose
NavBackStackEntryЭкземпляр экрана в стеке (содержит arguments, savedStateHandle)
NavGraphГраф навигации (XML или программно)
NavDeepLinkDeep link’ы (<deepLink app:uri="myapp://detail/{id}" />)
val navOptions = NavOptions.Builder()
.setLaunchSingleTop(true)
.setPopUpTo(R.id.home, inclusive = false, saveState = true)
.setRestoreState(true)
.build()
ОпцияЭффект
setLaunchSingleTop(true)Если уже в стеке — onNewIntent() (аналог singleTop)
setPopUpTo(id, inclusive = true)Удалить всё до (и включая) id
setRestoreState(true)Восстановить состояние экрана при возврате

Safe Args (Gradle plugin)

// В nav_graph.xml:
<argument android:name="userId" app:argType="integer" />

// Генерирует:
val args = DetailFragmentArgs.fromBundle(requireArguments())
val userId = args.userId

Best Practice:

  • Использовать savedStateHandle для параметров, а не Bundle
  • Избегать popUpTo с inclusive = true в корне — можно потерять стартовый экран
  • Для bottom nav — использовать отдельные графы или startDestination per tab

6.6 Hilt — DI от Google

Иерархия компонентов

КомпонентScopeЖизненный цикл
@SingletonComponent@SingletonПриложение
@ViewModelComponent@HiltViewModelViewModel
@ActivityComponent@ActivityScopedActivity
@FragmentComponent@FragmentScopedFragment
@ViewComponent@ViewScopedView
@ServiceComponent@ServiceScopedService

Ключевые аннотации

АннотацияПрименяется кОписание
@HiltAndroidAppApplicationИнициализация Hilt
@AndroidEntryPointActivity, Fragment, View, ServiceИнъекция зависимостей
@HiltViewModelViewModelИнъекция в ViewModel
@InstallIn(SingletonComponent::class)@ModuleГде устанавливать модуль
@Moduleobject/classГруппа провайдеров
@ProvidesФункция в @ModuleСоздаёт экземпляр
@BindsФункция в @ModuleПривязывает интерфейс → реализацию
@EntryPoints.get(Activity::class).entryPoint()КодПолучение зависимостей из не-@AndroidEntryPoint классов

Пример модуля

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase =
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.build()

@Provides
fun provideUserDao(db: AppDatabase): UserDao = db.userDao()

@Binds
fun bindUserRepository(repo: RoomUserRepository): UserRepository = repo
}

⚠️ Ограничения:

  • Нельзя инжектить ViewModel через конструктор (только @HiltViewModel)
  • @ApplicationContext, @ActivityContext — built-in квалификаторы
  • Для тестов — @UninstallModules, @TestInstallIn

6.7 DataStore — замена SharedPreferences

PreferencesDataStore

val Context.dataStore: DataStore<Preferences>
get() = preferencesDataStore("settings")

val USER_NAME = stringPreferencesKey("user_name")

suspend fun saveUserName(name: String) {
dataStore.edit { settings ->
settings[USER_NAME] = name
}
}

val userNameFlow: Flow<String> = dataStore.data
.map { preferences ->
preferences[USER_NAME] ?: "Guest"
}

ProtoDataStore (рекомендуется для структурированных данных)

  1. Определить settings.proto:
syntax = "proto3";
option java_package = "com.example.app";
message Settings {
string user_name = 1;
bool dark_mode = 2;
}
  1. Сгенерировать классы (protobuf-gradle-plugin)

  2. Использовать:

val Context.settingsDataStore: DataStore<Settings>
get() = dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer
)
ПреимуществоSharedPreferencesDataStore
Асинхронностьapply() — async, commit() — syncТолько async (Coroutines/Flow)
БезопасностьНе thread-safethread-safe
ОшибкиНетIOException в Flow
ТипизацияString-ключиСтрогая типизация (Proto)
МиграцииРучныеproduceMigrations

6.8 Jetpack Compose — core API

Основы

КонцептОписание
@ComposableФункция, описывающая UI
remember { mutableStateOf(0) }Сохраняет состояние между рекомпозициями
derivedStateOf { ... }Оптимизированный remember с зависимостями
LaunchedEffect(Unit) { ... }Запуск корутины при входе в состав
DisposableEffect(Unit) { ... }Ресурсы с onDispose
SideEffect { ... }Побочные эффекты (например, аналитика)
produceStateПреобразование не-Compose состояния в State

State и Flow

// Из ViewModel:
val uiState by viewModel.uiState.collectAsStateWithLifecycle()

// Из DataStore:
val userName by dataStore.data.collectAsState(initial = "Guest")

// Из LiveData (не рекомендуется):
val liveDataValue by liveData.observeAsState()

Modifier

ГруппаПримеры
РазмерModifier.size(100.dp), fillMaxWidth(), wrapContentSize()
Позиционированиеpadding(), offset(), align(), layoutId()
Вводclickable, focusable, indication, combinedClickable
Графикаbackground(), border(), shadow(), clip(), graphicsLayer()
АнимацияanimateContentSize(), AnimatedVisibility(), Crossfade()

MaterialTheme и Scaffold

MaterialTheme(
colorScheme = darkColorScheme(),
typography = Typography(),
shapes = Shapes()
) {
Scaffold(
topBar = { TopAppBar(title = { Text("Home") }) },
bottomBar = { BottomNavigation { ... } },
floatingActionButton = {
FloatingActionButton(onClick = {}) { Icon(Icons.Default.Add) }
}
) { padding ->
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen(Modifier.padding(padding)) }
}
}
}

Best Practice:

  • Использовать collectAsStateWithLifecycle() вместо collectAsState()
  • Избегать remember { mutableStateOf(...) } в @Composable без derivedStateOf для производных значений
  • Не вызывать viewModel напрямую — передавать как параметр (@Composable fun Screen(vm: ViewModel))
  • Для анимаций — updateTransition, animate*AsState, Transition

🔍 Часть 7. ADB и системные команды


7.1 adb — базовые команды

КомандаОписаниеПримечания
adb devices -lСписок устройств с моделью, серийным номером, статусом-l — long format (product:model device:serial)
adb shellЗапуск shell на устройстве (пользователь shell, UID 2000)Не root. Для root — adb root (только debug-сборки)
adb logcat -b main,system,crash -v threadtimeФильтрация по буферам и формату-b all — все буферы (radio, events, kernel и др.)
adb bugreport ./report.zipПолный отчёт (логи, dumpsys, dmesg, ps, netstat)Требует ~30 сек. Работает на любом устройстве.
adb install -r -t -g app-release.apkУстановка с заменой (-r), разрешение тестовых (-t), всех разрешений (-g)-d — downgrade, -s — на SD-карту
adb uninstall --user 0 com.exampleУдаление для конкретного пользователя--user 0 — основной пользователь
adb backup -apk -shared -all -f backup.abРезервное копирование (устарело, не работает на Android ≥12)
adb restore backup.abВосстановление (устарело)
adb forward tcp:8080 tcp:8080Проброс порта (например, для Flipper)adb reverse tcp:8080 tcp:8080 — с устройства на хост
adb pull /sdcard/log.txt .Копирование с устройстваadb push ./file /sdcard/ — на устройство

⚠️ Важно:

  • adb root работает только на userdebug/eng сборках (не на user).
  • adb shell su — только если установлен su (кастомное recovery, Magisk).
  • adb logcat --pid=<PID> — фильтр по процессу (API ≥24).

7.2 dumpsys — диагностика системных сервисов

Общий синтаксис:

adb shell dumpsys <service> [<args>]

7.2.1 dumpsys activity

Что показывает: стек задач, Activity, сервисы, broadcast receiver’ы, recent tasks.

ПодкомандаПримерОписание
dumpsys activity activitiesТекущие Activity и их состояния (mResumed, mStopped, mDestroyed)
dumpsys activity servicesРаботающие сервисы (startService, bindService)
dumpsys activity broadcastsPending broadcast’ы
dumpsys activity topТолько top Activity
dumpsys activity packages com.exampleИнформация по конкретному пакету

Ключевые поля в выводе:

  • Task id #123A/B/C — стек Activity (A — bottom, C — top)
  • mResumed=true — Activity в foreground
  • mStopped=true — остановлена, но не уничтожена
  • hasTopResumeActivity=true — задача на переднем плане
  • realActivity=com.example/.MainActivity — реальный класс

Диагностический паттерн:
🔍 «Почему Activity не умирает при finish()
dumpsys activity activities | grep -A 10 "com.example" → проверить mFinishing, mDestroyed.


7.2.2 dumpsys package

Что показывает: информация о пакетах — разрешения, компоненты, настройки.

ПодкомандаПримерОписание
dumpsys package com.exampleПолная информация о пакете
dumpsys package permissionsВсе разрешения системы и их статус
dumpsys package queries<queries> из манифеста (API ≥30)
dumpsys package installsИстория установок

Ключевые секции:

  • userId=10123 — UID приложения
  • pkgFlags=[ SYSTEM DEBUGGABLE ] — флаги пакета
  • requested permissions:install permissions:runtime permissions:
    → проверить, какие dangerous-разрешения не выданы
  • ComponentInfo{...} — все Activity, Service, Receiver, Provider
  • gids=[3003] — группы (например, 3003 = inet для INTERNET)

Диагностический паттерн:
🔍 «Приложение не видит другие пакеты»
dumpsys package queries | grep com.example → проверить, объявлены ли <queries> для PackageManager.queryIntentActivities().


7.2.3 dumpsys batterystats

Что показывает: статистика энергопотребления по компонентам.

ПодкомандаПримерОписание
dumpsys batterystats --chargedС последней полной зарядки
dumpsys batterystats --historyХронология событий (screen on/off, wake locks)
dumpsys batterystats com.example --dailyЗа сутки
adb shell dumpsys batterystats --resetСброс статистики (требует adb shell cmd battery reset)

Ключевые метрики:

  • Wake lockPARTIAL, FULL, SCREEN_DIM, PROXIMITY_OUT
  • Mobile networkRx/Tx байты
  • CPUuAh (microampere-hours)
  • JobJobScheduler задачи
  • SyncContentResolver.requestSync()

🔋 Best Practice:

  • Использовать adb shell cmd battery unplugadb shell dumpsys batterystats --reset перед тестом
  • После теста — adb shell bugreport → открыть в Battery Historian

7.2.4 dumpsys meminfo

Что показывает: использование памяти по процессам.

ПодкомандаПримерОписание
dumpsys meminfo com.exampleДетально по процессу
dumpsys meminfo -aВсе процессы
dumpsys meminfo --oomOOM-адаптация (adj, importance)

Ключевые поля:

  • Native Heap — нативная память (C++, JNI)
  • Dalvik Heap — Java heap (allocated, free, max)
  • Gfx dev — GPU-память (текстуры)
  • EGL mtrack — OpenGL ресурсы
  • Unknown — утечки (проверить через adb shell showmap <PID>)

Диагностический паттерн:
🔍 «Утечка памяти в Bitmap»
dumpsys meminfo com.example → рост Gfx dev → профилировать через Android Studio Profiler → Allocation Tracker.


7.2.5 dumpsys gfxinfo

Что показывает: производительность рендеринга.

ПодкомандаПримерОписание
dumpsys gfxinfo com.example framestatsКадры за последние 120 сек (VSYNC, Input, Animation, Measure/Layout, Draw, Sync, GPU)
dumpsys gfxinfo com.exampleСводка по Activity (Janky frames, 90th percentile)

Ключевые метрики:

  • Janky frames > 5% — проблема
  • 90th percentile > 16.6 мс — не укладывается в 60 FPS
  • Draw > 8 мс — тяжёлый onDraw()
  • Measure/Layout > 4 мс — сложная иерархия

📊 Анализ:
framestats → скопировать в CSV → открыть в FrameMetrics или Perfetto.


7.2.6 dumpsys window

Что показывает: иерархия окон, фокус, анимации.

ПодкомандаПримерОписание
dumpsys window windowsВсе окна (mSurfaceLayer, mToken, mViewVisibility)
dumpsys window policyСостояние StatusBar, NavigationBar
dumpsys window tokensТокены Activity/Dialog

Ключевые поля:

  • mSurfaceLayer=21100 — выше StatusBar (21000)
  • mToken=ActivityRecord{... com.example/.MainActivity} — какому Activity принадлежит
  • mViewVisibility=0 — видимо, 8 — gone
  • mAnimating=true — идёт анимация

Диагностический паттерн:
🔍 «Dialog не закрывается»
dumpsys window windows | grep -A 5 "Dialog" → проверить mAnimating, mViewVisibility.


7.3 am — Activity Manager

КомандаПримерОписаниеAPI+
am start -n com.example/.MainActivityЗапуск Activity1+
am start -a android.intent.action.VIEW -d "https://example.com"Implicit intent1+
am start -W com.example/.MainActivityС замером времени (TotalTime)3+
am force-stop com.exampleПринудительная остановка (kill + очистка)1+
am kill com.exampleУбить фоновые процессы (не foreground)21+
am stack listСписок стеков задач21+
am task lock 123Закрепить задачу (Kiosk mode)21+
am set-inactive com.example trueПометить как неактивное (для Adaptive Battery)28+

⚠️ Ограничения:

  • am start требует adb shell (не su)
  • am force-stop сбрасывает JobScheduler, AlarmManager, WorkManager
  • am set-inactive влияет на UsageStatsManager

7.4 pm — Package Manager

КомандаПримерОписание
pm list packages -fВсе пакеты с путями APK
pm list packages -3Только third-party
pm path com.exampleПуть к APK (/data/app/.../base.apk)
pm dump com.exampleТо же, что dumpsys package com.example
pm grant com.example android.permission.ACCESS_FINE_LOCATIONВыдать runtime-разрешение
pm revoke com.example android.permission.CAMERAОтозвать
pm clear com.exampleОчистить данные (/data/data/com.example)
pm uninstall --user 0 com.exampleУдалить для пользователя 0
pm install-existing /data/app/com.example-1/base.apkУстановить существующий APK (без копирования)

🔐 Важно:

  • pm grant работает только для protectionLevel="dangerous"
  • Для signature — невозможно
  • pm clear = «Очистить данные» в настройках

7.5 svc — Service Control

КомандаПримерОписание
svc wifi enableВключить Wi-Fi
svc wifi disableВыключить
svc data enableМобильный интернет
svc power stayon trueЭкран всегда включён (USB/AC/battery)
svc usb setFunctions rndisРежим USB (MTP, PTP, RNDIS, MIDI)

⚠️ Требует adb shell с правами shell (не root). Некоторые команды не работают на user-сборках.


7.6 settings — системные настройки

КомандаПримерОписание
settings get global adb_enabled1ADB включён
settings put system screen_brightness 128Яркость (0–255)
settings get secure enabled_input_methodscom.example/.ImeАктивные IME
settings put global hidden_api_policy 1Доступ к hidden API (1=just warn, 2=allow)
settings get system font_scale1.0Масштаб шрифта
settings list systemВсе system-настройки

Полезные ключи:

  • global: adb_enabled, development_settings_enabled, wifi_on, bluetooth_on
  • secure: android_id, lock_screen_owner_info_enabled, default_input_method
  • system: screen_brightness, screen_off_timeout, volume_ring, volume_music

💡 Автоматизация:
adb shell settings put system screen_off_timeout 30000 → экран гаснет через 30 сек.


7.7 cmd — low-level системные команды

КомандаПримерОписаниеAPI+
cmd package compile -m speed com.exampleAOT-компиляция (ускорение запуска)21+
cmd package compile -m quicken com.exampleJIT + профили (Baseline Profiles)24+
cmd jobscheduler run -f com.example 123Принудительный запуск Job21+
cmd notification post -S bigtext -t "Title" "tag" "com.example"Пост нотификации24+
cmd device_config put activity_manager max_phantom_processes 32Настройка системных параметров (Treble)28+
cmd battery resetСброс batterystats21+
cmd statusbar disable HOMEСкрыть иконки в StatusBar24+

⚠️ cmd — более стабильный API, чем service call. Предпочтителен для автоматизации.


7.8 wm — Window Manager

КомандаПримерОписание
wm size 1080x1920Эмуляция разрешения
wm density 420Эмуляция DPI
wm overscan 0,0,0,100Обрезка экрана (top, left, bottom, right)
wm size resetСброс
wm density resetСброс

📱 Использование:

  • Тестирование на разных экранах без физических устройств
  • adb shell wm size 720x1280 && adb shell wm density 320

7.9 input — эмуляция ввода

КомандаПримерОписание
input keyevent 3HOME (см. KeyEvent.KEYCODE_*)
input keyevent 4BACK
input keyevent 82MENU
input tap 500 1000Тап по координатам
input swipe 100 1000 100 500 300Свайп (x1,y1,x2,y2,duration_ms)
input text "Hello"Ввод текста (только ASCII)

Коды KeyEvent:

  • 3 = KEYCODE_HOME
  • 4 = KEYCODE_BACK
  • 26 = KEYCODE_POWER
  • 24/25 = KEYCODE_VOLUME_UP/DOWN
  • 82 = KEYCODE_MENU
  • 66 = KEYCODE_ENTER

⚠️ input text не поддерживает кириллицу. Для этого — adb shell am broadcast -a ADB_INPUT_TEXT --es msg "Привет" (требует стороннего APK, например, ADB Keyboard).


7.10 uiautomator — UI-автоматизация

КомандаПримерОписание
uiautomator dump /sdcard/window.xmlДамп UI-иерархии в XML
adb pull /sdcard/window.xml .Скачать
uiautomator runtest AutoTest.jar -c com.example.AutoTestЗапуск теста (устарело, используйте adb shell am instrument)

🆕 Современный способ:

adb shell am instrument -w -r \
-e debug false \
-e class 'com.example.test.MainActivityTest#testButton' \
com.example.test/androidx.test.runner.AndroidJUnitRunner

7.11 tasker — управление задачами (устаревшее, но ещё встречается)

КомандаПримерОписание
tasker task run TestTaskЗапуск задачи в Tasker

🚫 Не рекомендуется для новых проектов. Используйте WorkManager или JobScheduler.


🔐 Часть 8. Security

8.1 Android Keystore System

Общее назначение

Система безопасного хранения криптографических ключей, изолированных от приложения и ОС. Ключи не экспортируются в открытом виде (hardware-backed, если поддерживается), а операции (шифрование/подпись) выполняются внутри TEE/SE.

Поддерживаемые алгоритмы и API

АлгоритмПровайдерminSdkПримечания
AES/CBC/PKCS7PaddingAndroidKeyStore23Только для SecretKey. GCM — API ≥24
AES/GCM/NoPaddingAndroidKeyStore24Рекомендуется вместо CBC
RSA/ECB/PKCS1PaddingAndroidKeyStore18Для PrivateKey (расшифровка/подпись)
RSA/ECB/OAEPPaddingAndroidKeyStore23Более безопасная замена PKCS#1 v1.5
ECDSAAndroidKeyStore23Для PrivateKey (только подпись)
HMAC-SHA256AndroidKeyStore23Только для SecretKey

Создание ключа (программно)

val keyGenParameterSpec = KeyGenParameterSpec.Builder(
"my_key_alias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(true)
.setUserAuthenticationRequired(true) // требует биометрии/PIN
.setUserAuthenticationValidityDurationSeconds(30) // после аутентификации
.setUnlockedDeviceRequired(true) // после разблокировки (API ≥24)
.setIsStrongBoxBacked(true) // StrongBox (API ≥28, если есть)
.build()

val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()

Ключевые флаги и условия

Флаг / методОписаниеAPI+Ограничения
setUserAuthenticationRequired(true)Требует аутентификации перед использованием ключа23Без setUserAuthenticationValidityDurationSeconds() — только «прямо сейчас» (однократно).
setUserAuthenticationValidityDurationSeconds(N)Действие после аутентификации — N секунд23Макс. 300 сек (политика безопасности).
setUnlockedDeviceRequired(true)Использовать только после разблокировки (а не только после старта)24Заменяет setUserAuthenticationRequired(false) при minSdk ≥ 24.
setIsStrongBoxBacked(true)Использовать StrongBox (отдельный чип)28Если недоступен — StrongBoxUnavailableException. Проверять через isStrongBoxBacked.
setInvalidatedByBiometricEnrollment(true)Ключ аннулируется при добавлении нового биометрического шаблона24По умолчанию true для PURPOSE_SIGN.
setAttestationChallenge(...)Генерация сертификата аттестации (для удалённой проверки устройства)24Только для RSA, EC. Требует KeyGenParameterSpec.Builder.setAttestationChallenge().
KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware()Проверка, контролируется ли аутентификация на уровне TEE28Важно для compliance (например, финансовые приложения).

Диагностика и проверки

# Проверить поддержку StrongBox:
adb shell cmd keystore2 list | grep -A 5 "my_key_alias"

# Проверить аттестационный сертификат:
keytool -printcert -file attestation_cert.der
# → искать extension 1.3.6.1.4.1.11129.2.1.17 (KeyDescription)

⚠️ Ограничения:

  • Hardware-бэкенд не гарантируется даже при isStrongBoxBacked = true (устройство может эмулировать).
  • На эмуляторах и userdebug-сборках ключи не hardware-backed.
  • setRandomizedEncryptionRequired(true) обязателен для AES/GCM (иначе InvalidAlgorithmParameterException).

8.2 EncryptedSharedPreferences и EncryptedFile

Общее назначение

Безопасная замена SharedPreferences и File с прозрачным шифрованием (ключ хранится в AndroidKeyStore, данные — в EncryptedFile).

EncryptedSharedPreferences

val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()

val encryptedPrefs = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// Использовать как обычные SharedPreferences
encryptedPrefs.edit().putString("token", "xyz").apply()
ПараметрВозможные значенияПримечания
KeySchemeAES256_GCMЕдинственный поддерживаемый.
PrefKeyEncryptionSchemeAES256_SIVSIV — synthetic IV, гарантирует детерминизм при одинаковых ключах.
PrefValueEncryptionSchemeAES256_GCMРекомендуется (аутентифицированное шифрование).

⚠️ Производительность:

  • На каждый commit()/apply() — 1 вызов AndroidKeyStore (дорого).
  • Не использовать для часто изменяемых данных (например, счётчик кадров).
  • Кэшировать значения в памяти, синхронизируя только при необходимости.

EncryptedFile

val encryptedFile = EncryptedFile.Builder(
context,
File(context.filesDir, "secret.bin"),
masterKey,
EncryptedFile.FileEncryptionScheme.AES256_GCM
).build()

// Запись
encryptedFile.openFileOutput().use { it.write("Hello".toByteArray()) }

// Чтение
val bytes = encryptedFile.openFileInput().use { it.readBytes() }
СхемаОписание
AES256_GCMЕдинственная поддерживаемая.

⚠️ Важно:

  • EncryptedFile не совместим с FileInputStream/FileOutputStream напрямую — только через .openFileInput()/.openFileOutput().
  • Размер зашифрованного файла ≈ исходный + 48 байт (IV + tag).
  • Нельзя использовать RandomAccessFile — только потоковый доступ.

8.3 Play Integrity API

Общее назначение

Замена устаревших SafetyNet Attestation и SafetyNet reCAPTCHA. Предназначен для:

  • проверки подлинности устройства и приложения,
  • детектирования модификаций (root, custom ROM, hook framework),
  • оценки риска мошенничества (на основе поведенческих и контекстных сигналов).

Работает только на устройствах с Google Play Services ≥ 22.12.13 и Google Play Store ≥ 22.12.13.

Этапы интеграции

  1. Подключение зависимости

    implementation("com.google.android.play:integrity:1.2.0")
  2. Получение IntegrityToken (клиентская часть)

    val integrityManager = IntegrityManagerFactory.create(context)

    val request = IntegrityTokenRequest.builder()
    .setNonce("UNIQUE_REQUEST_NONCE") // ≥16 байт, криптостойкий (например, Base64(SHA-256(random + timestamp)))
    .build()

    integrityManager.requestIntegrityToken(request)
    .addOnSuccessListener { response ->
    val token = response.token()
    // отправить на сервер
    }
    .addOnFailureListener { e ->
    // обработать:
    // - ApiException → Play Services недоступны / неверный nonce
    // - IntegrityServiceException → политика безопасности
    }

Ключевые ограничения и требования

УсловиеПоследствиеПримечание
nonce.length < 16ApiException STATUS_INVALID_ARGUMENTМинимум 16 байт в бинарной форме. Base64-строка должна быть ≥22 символа.
Кэширование nonceРиск replay-атакКаждый запрос — уникальный nonce.
Вызов вне UI-потокаНе гарантируетсяРекомендуется вызывать в Lifecycle.State.STARTED.
targetSdk ≥ 34, установка через сторонний APK (не Play)IntegrityServiceException ERROR_PLAY_SERVICES_NOT_FOUNDТребуется установка из Google Play Store.
Отсутствие Google Play Services (Huawei, AOSP)ApiException API_NOT_AVAILABLEПеред вызовом — проверить через GoogleApiAvailability.

Проверка токена на сервере

  1. Декодирование JWT Интегрити-токен — это JWT без подписи (unsecured JWT, alg: none).
    Поля requestDetails, appIntegrity, deviceIntegrity, accountDetails — в payload.

  2. Валидация через Google Cloud

    POST https://integritycheck.googleapis.com/v1/integrityTokens:decode
    Authorization: Bearer <service-account-access-token>
    Content-Type: application/json

    {
    "integrityToken": "<полученный_токен>",
    "nonce": "<оригинальный_nonce>"
    }

    Ответ содержит:

    {
    "tokenPayloadExternal": {
    "requestDetails": { "requestPackageName": "com.example", "timestampMillis": "1700000000000" },
    "appIntegrity": {
    "appRecognitionVerdict": "PLAY_RECOGNIZED",
    "packageName": "com.example",
    "certificateSha256Digest": ["ab12..."],
    "dexFileSha256Digest": ["cd34..."] // если включено в консоли
    },
    "deviceIntegrity": {
    "deviceRecognitionVerdict": ["MEETS_STRONG_INTEGRITY"]
    },
    "accountDetails": {
    "appLicensingVerdict": "LICENSED"
    }
    }
    }

Возможные вердикты

ГруппаЗначениеУровень доверияРекомендация
appRecognitionVerdictPLAY_RECOGNIZED✅ ВысокийПриложение установлено из Play, подпись совпадает.
UNRECOGNIZED❌ НизкийУстановлено вручную / изменено.
UNEVALUATED⚠️ НеизвестноОшибка или недостаточно данных.
deviceRecognitionVerdictMEETS_STRONG_INTEGRITY✅ Очень высокийСертифицированное устройство, bootloader locked, TEE, не модифицировано.
MEETS_BASIC_INTEGRITY✅ СреднийУстройство не рутировано, но может быть не сертифицировано (например, пользовательская прошивка без root).
MEETS_DEVICE_INTEGRITY✅ УмеренныйУстройство не в чёрном списке, но потенциально уязвимо.
NO_INTEGRITY❌ НизкийRoot, Magisk, Xposed, отладка по USB, bootloader unlocked.
appLicensingVerdictLICENSEDКуплено в Play.
UNLICENSEDНе куплено / side-loaded.

Настройка в Google Play Console

  1. Включение API
    Play Console → Ваше приложение → Setup → App integrity → Play Integrity APIEnable.

  2. Опционально: включение dexFileDigest
    Позволяет проверять целостность DEX-файлов. Увеличивает размер APK (~100–300 КБ).
    Включается в той же секции → Dex file integrity.

  3. Cloud IAM
    Сервисному аккаунту нужны роли:

    • roles/playintegrity.decipherer
    • roles/serviceusage.serviceUsageConsumer

Диагностика

# Проверить наличие Play Services и версию:
adb shell dumpsys package com.google.android.gms | grep versionName

# Проверить, установлено ли приложение из Play:
adb shell dumpsys package com.example | grep installerPackageName
# → installerPackageName=com.android.vending

⚠️ Важно:

  • Play Integrity не заменяет backend-валидацию. Он лишь предоставляет сигналы.
  • MEETS_BASIC_INTEGRITY не означает безопасность — многие банковские приложения требуют MEETS_STRONG_INTEGRITY.
  • Для устройств без Google Mobile Services (GMS) — альтернатива: App Attest (iOS), App Defense (Samsung), или отказ от high-risk операций.

8.4 TLS Pinning и network-security-config.xml

Общее назначение

TLS pinning (certificate/public key pinning) — механизм, при котором клиент жёстко привязывает ожидаемый сертификат или публичный ключ сервера, чтобы предотвратить MITM-атаки даже при наличии доверенного CA на устройстве.

С Android 7.0 (API 24) устарел android.security.NetworkSecurityPolicy.setCleartextTrafficPermitted() и WebView.setCertificateHandler() — вместо этого используется единый механизм: res/xml/network_security_config.xml, подключаемый через AndroidManifest.xml#application@networkSecurityConfig.


8.4.1 Структура network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- 1. Глобальные настройки -->
<base-config
cleartextTrafficPermitted="false"
trust-anchors="@xml/trust_anchors_global" />

<!-- 2. Настройки по домену -->
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">api.example.com</domain>
<domain includeSubdomains="true">cdn.example.com</domain>

<!-- 2.1. Pinning -->
<pin-set expiration="2026-12-31">
<pin digest="SHA-256">7HIpActkEGhJZ...Jt4Zw7lVyQ3r8QeT</pin>
<pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UK2w==</pin>
</pin-set>

<!-- 2.2. Trust anchors (опционально) -->
<trust-anchors>
<certificates src="@raw/internal_ca"/>
<certificates src="system" />
</trust-anchors>
</domain-config>

<!-- 3. Отладочные настройки -->
<debug-overrides>
<trust-anchors>
<certificates src="@raw/debug_ca"/>
<certificates src="user"/>
</trust-anchors>
</debug-overrides>
</network-security-config>

Подключается в AndroidManifest.xml:

<application
android:networkSecurityConfig="@xml/network_security_config"
... >

8.4.2 Элементы и атрибуты

ЭлементАтрибутыОписание
<base-config>cleartextTrafficPermitted, trust-anchorsНастройки по умолчанию для всех доменов, не покрытых <domain-config>.
<domain-config>cleartextTrafficPermitted, use-strict-clear-text (API 34+)Конфиг для конкретных доменов. Может быть вложен (наследует параметры родителя).
<domain>includeSubdomains (true/false)Доменное имя. Поддерживает только ASCII (IDN → Punycode вручную).
<pin-set>expiration (ISO 8601)Набор pinned-хешей. Обязан содержать ≥2 пинов, причём один — «резервный» (будущий).
<pin>digest (SHA-256 только)Base64-хеш SubjectPublicKeyInfo (не сертификата!). Получается через openssl x509 -in cert.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64.
<trust-anchors>Набор доверенных сертификатов.
<certificates>src (system, user, @raw/..., @xml/...)Источник: system — встроенные CA; user — добавленные пользователем; @raw/ca_cert — PEM-файл; @xml/trust_anchors<certificates> в XML.
<debug-overrides>Применяется только при android:debuggable="true" (игнорируется в release-сборке).

⚠️ Важно:

  • Если <pin-set> задан, но не указан <trust-anchors>, используются system + user по умолчанию.
  • Если pinned-сертификат не проходит валидацию по цепочке (CN, SAN, срок), соединение отклоняется — даже при совпадении пина.
  • expirationобязателен (Google Play требует, чтобы pin имел срок ≤1 год). По истечении — система возвращается к обычной проверке CA.

8.4.3 Генерация пинов

# 1. Извлечь SPKI из сертификата:
openssl x509 -in api.example.com.crt -pubkey -noout \
| openssl pkey -pubin -outform der \
| openssl dgst -sha256 -binary \
| base64

# Вывод: 7HIpActkEGhJZ...Jt4Zw7lVyQ3r8QeT

Для цепочки (например, leaf + intermediate):

# Leaf:
openssl x509 -in leaf.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64
# Intermediate:
openssl x509 -in intermediate.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64

Best Practice:

  • Включать leaf + один intermediate (не root!).
  • Обновлять пины до истечения expiration, используя «резервный» пин.
  • Хранить пины в CI/CD, не в коде.

8.4.4 Отладка и обход в debug-сборке

Файл res/xml/network_security_config.xml (release) → строгий pinning.
Файл res/xml-debug/network_security_config.xmlsrc/debug/res/):

<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system"/>
<certificates src="user"/> <!-- Разрешить пользовательские CA -->
</trust-anchors>
</base-config>
<debug-overrides>
<trust-anchors>
<certificates src="system"/>
<certificates src="user"/>
</trust-anchors>
</debug-overrides>
</network-security-config>

Такой подход:

  • Разрешает HTTP (cleartextTrafficPermitted="true") в debug.
  • Разрешает MITM-прокси (Charles, Burp) через user CA.
  • Не влияет на release-сборку — Gradle использует src/main/res/ для release, src/debug/res/ — только для debug.

8.4.5 Диагностика

# Проверить, какой config загружен:
adb shell dumpsys package com.example | grep networkSecurityConfig

# Проверить, разрешён ли cleartext:
adb shell "cat /data/misc/profiles/cur/0/com.example/nsc_cache.xml"
# → <domain-config cleartextTrafficPermitted="false">

# Проверить, включён ли debug override:
adb shell getprop ro.debuggable # 1 = debug, 0 = release

Если приложение падает с SecurityException при HTTPS-запросе — скорее всего:

  • пин устарел (expiration прошёл),
  • не совпадает SPKI,
  • missing <pin-set> при targetSdk ≥ 24 и отсутствии <trust-anchors>.

8.4.6 Совместимость с OkHttpClient

Если используется OkHttpClient вручную (не через HttpURLConnection), network-security-config не применяется автоматически. Требуется явная интеграция:

val client = OkHttpClient.Builder()
.sslSocketFactory(
// Использует system TrustManager + pinning из nsc.xml
SSLCertificateSocketFactory.getDefault(0),
// ИЛИ кастомный TrustManager с CertificatePinner:
CertificatePinner.Builder()
.add("api.example.com", "sha256/7HIpActkEGhJZ...Jt4Zw7lVyQ3r8QeT")
.add("api.example.com", "sha256/fwza0LRMXouZHRC8Ei+4PyuldPDcf3UK2w==")
.build()
)
.build()

⚠️ CertificatePinner в OkHttp не поддерживает expiration — его нужно управлять вручную.


8.5 android:usesCleartextTraffic

Общее назначение

Атрибут android:usesCleartextTraffic управляет разрешением на использование незашифрованного HTTP-трафика (в отличие от HTTPS).
По умолчанию:

targetSdkЗначение по умолчанию
≤ 27 (Android 8.1)true
≥ 28 (Android 9, Pie)false

Если приложение с targetSdk ≥ 28 пытается выполнить HTTP-запрос (например, через HttpURLConnection, OkHttp, WebView.loadUrl("http://...")), возникает исключение:

  • java.net.UnknownServiceException: CLEARTEXT communication not permitted
  • или в логах: Cleartext HTTP traffic to X not permitted

Способы настройки

  1. Глобально — через <application>

    <application
    android:usesCleartextTraffic="false"
    ... >

    Применяется ко всем доменам, если не переопределено в network-security-config.

  2. Гибко — через network-security-config.xml
    Имеет приоритет над android:usesCleartextTraffic.

    <domain-config cleartextTrafficPermitted="true">
    <domain includeSubdomains="true">insecure.example.com</domain>
    </domain-config>

    ✅ Рекомендуется: разрешать cleartext только для конкретных доменов, а не глобально.

  3. Отладка — через <debug-overrides>

    <debug-overrides>
    <trust-anchors>
    <certificates src="system"/>
    <certificates src="user"/>
    </trust-anchors>
    <!-- cleartext разрешён неявно в debug, если не запрещён явно -->
    </debug-overrides>

    Но лучше явно указать:

    <base-config cleartextTrafficPermitted="true" />

    — в res/xml-debug/network_security_config.xml.

Совместимость с WebView

Для WebView действуют те же правила:

  • Если targetSdk ≥ 28 и не разрешён cleartext — webView.loadUrl("http://example.com") → белый экран, в logcat:
    Blocked cleartext HTTP request (not HTTPS)

Обход только для отладки (не использовать в release!):

if (BuildConfig.DEBUG) {
WebView.setWebContentsDebuggingEnabled(true)
// И добавить в network-security-config.xml (debug-only):
// <base-config cleartextTrafficPermitted="true" />
}

Как проверить текущую политику в runtime

val policy = NetworkSecurityPolicy.getInstance()
Log.d("NSP", "Cleartext permitted globally: ${policy.isCleartextTrafficPermitted}")
// По домену (API ≥24):
Log.d("NSP", "example.com: ${policy.isCleartextTrafficPermitted("example.com")}")

⚠️ Метод isCleartextTrafficPermitted(String domain)
— Учитывает как android:usesCleartextTraffic, так и network-security-config.xml.

Типичные ошибки и диагностика

СимптомПричинаРешение
java.io.IOException: Cleartext HTTP traffic not permittedtargetSdk ≥ 28, usesCleartextTraffic="false", и нет <domain-config> для доменаДобавить <domain-config> или перейти на HTTPS
WebView не грузит HTTP даже в debug-сборкеnetwork-security-config в main не переопределён в debugУбедиться, что src/debug/res/xml/network_security_config.xml существует и разрешает cleartext
HTTP работает на эмуляторе, но не на устройствеНа устройстве установлено корпоративное CA или MDM-политикаПроверить Settings → Security → Trusted credentials → User

Принудительное включение HTTP (не рекомендуется)

Только для legacy-сервисов, где HTTPS невозможен:

<application
android:usesCleartextTraffic="true"
... >

Приведёт к отклонению в Google Play, если нет веской причины и документации.

Google требует оправдания в форме:

  • Declaration в Play Console → «App functionality» → «Cleartext traffic»
  • Обоснование: «Требуется для совместимости со старым промышленным оборудованием без TLS-поддержки»

8.6 permissionFlags и детальный контроль разрешений (API ≥30)

Общее назначение

Начиная с Android 11 (API 30), PackageManager предоставляет расширенный API для управления поведением выданных разрешений — без полного отзыва. Это позволяет:

  • различать, было ли разрешение выдано пользователем вручную или автоматически (например, при первом запуске),
  • отслеживать, используется ли разрешение «только в этом запуске» (one-time),
  • запрещать автоматическое восстановление разрешения после обновления или сброса настроек,
  • реализовывать гибкие политики конфиденциальности (например, «временно отключить доступ к местоположению в фоне»).

Эти флаги не являются разрешениями — они метаданные к уже выданным dangerous-разрешениям.


8.6.1 Получение и установка флагов

val pm = context.packageManager

// Получить текущие флаги для разрешения
val flags = pm.getPermissionFlags(
permission = Manifest.permission.ACCESS_FINE_LOCATION,
packageName = context.packageName,
userHandle = Process.myUserHandle()
)

// Установить флаг (требует android.permission.GRANT_RUNTIME_PERMISSIONS)
pm.updatePermissionFlags(
permission = Manifest.permission.ACCESS_FINE_LOCATION,
packageName = context.packageName,
flagMask = PackageManager.FLAG_PERMISSION_USER_SET or PackageManager.FLAG_PERMISSION_USER_FIXED,
flagValues = PackageManager.FLAG_PERMISSION_USER_SET,
userHandle = Process.myUserHandle()
)

⚠️ updatePermissionFlags() требует системного разрешения GRANT_RUNTIME_PERMISSIONS (только для signature|privileged приложений).
Обычные приложения могут читать флаги, но не могут их менять — только через UI-диалоги или Settings.


8.6.2 Основные флаги PackageManager.FLAG_PERMISSION_*

ФлагЗначение (бит)ОписаниеИзменяемо обычным приложением?
FLAG_PERMISSION_USER_SET1 << 0Разрешение было установлено/изменено пользователем вручную (не по умолчанию).❌ (только чтение)
FLAG_PERMISSION_USER_FIXED1 << 1Пользователь запретил приложению запрашивать это разрешение снова («Don’t ask again»).
FLAG_PERMISSION_SYSTEM_FIXED1 << 2Разрешение зафиксировано системой (например, в enterprise-политике).
FLAG_PERMISSION_GRANTED_BY_DEFAULT1 << 3Разрешение выдано по умолчанию (например, для GET_ACCOUNTS_PRIVILEGED).
FLAG_PERMISSION_REVOKE_ON_UPGRADE1 << 4Разрешение будет отозвано при обновлении приложения.✅ (через requestPermission())
FLAG_PERMISSION_REVOKE_WHEN_NOT_IN_USE1 << 5One-time permission: разрешение активно только пока приложение в foreground.✅ (через requestPermission() с ActivityResultContracts.RequestPermission)
FLAG_PERMISSION_REVOKE_WHEN_APP_UPGRADED1 << 6Устаревшее (дублирует REVOKE_ON_UPGRADE).
FLAG_PERMISSION_APPLY_RESTRICTION1 << 7Применено restriction через DevicePolicyManager.setPermissionGrantState().

✅ — можно инициировать через UI-поток (например, ActivityResultLauncher.launch()), но не напрямую через API.


8.6.3 One-time permissions («Only this time»)

Пользователь в диалоге requestPermissions() может выбрать:

  • AllowFLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_FIXED = 0
  • 🕒 Only this time → дополнительно устанавливается FLAG_PERMISSION_REVOKE_WHEN_NOT_IN_USE
  • DenyFLAG_PERMISSION_USER_FIXED = 1, если повторно — галочка «Don’t ask again»

Поведение системы:

  • Как только приложение уходит в background (onPause()onStop()), система автоматически отзывает разрешение.
  • При возврате в foreground — разрешение не восстанавливается — нужно запрашивать снова.
  • Проверка:
    val granted = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
    val isOneTime = pm.getPermissionFlags(permission, pkg, user) and
    PackageManager.FLAG_PERMISSION_REVOKE_WHEN_NOT_IN_USE != 0

💡 Best Practice:

  • Не кэшировать true из checkSelfPermission() надолго — для one-time permissions состояние меняется без уведомления.
  • В onResume() — повторно проверять checkSelfPermission(), особенно для location/mic/camera.
  • Не показывать обучающие подсказки до первого отказа — Android 11+ уже объясняет «Only this time» в системном диалоге.

8.6.4 FLAG_PERMISSION_REVOKE_ON_UPGRADE

Можно запросить разрешение с пометкой «отозвать при обновлении»:

// Напрямую — нельзя, но можно через Settings:
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
startActivity(intent)

Или — после первого отказа, пользователь может вручную поставить галку:
Settings → Apps → MyApp → Permissions → ⋮ → “Revoke after update”

Полезно для:

  • временных аккаунтов (demo-режим),
  • enterprise-устройств (MDM с политикой «обнулять привилегии после обновления»).

8.6.5 Диагностика через ADB

# Получить флаги разрешений для пакета:
adb shell dumpsys package com.example | grep -A 20 "runtime permissions:"

# Пример вывода:
# android.permission.ACCESS_FINE_LOCATION: granted=true, flags=0x11
# → 0x11 = 0b10001 = FLAG_PERMISSION_USER_SET (1) | FLAG_PERMISSION_REVOKE_ON_UPGRADE (16)
# Принудительно сбросить флаги (только на debug-устройстве):
adb shell pm reset-permissions com.example
# → все runtime-разрешения отозваны, флаги сброшены

8.6.6 Совместимость и fallback

APIВозможности
≤28 (Android 9)Только checkSelfPermission() / requestPermissions(). Нет флагов.
29 (Android 10)Появляется shouldShowRequestPermissionRationale() с улучшенной логикой (учитывает «Don’t ask again»), но нет permissionFlags.
≥30 (Android 11+)Полный доступ к getPermissionFlags().

Кросс-версионная проверка:

fun isPermissionOneTime(permission: String): Boolean {
return if (Build.VERSION.SDK_INT >= 30) {
val flags = packageManager.getPermissionFlags(
permission, packageName, Process.myUserHandle()
)
flags and PackageManager.FLAG_PERMISSION_REVOKE_WHEN_NOT_IN_USE != 0
} else {
// На API <30 one-time нет → false
false
}
}

8.7 BiometricPrompt (API ≥28)

Общее назначение

Унифицированный системный API для биометрической аутентификации (отпечаток, лицо, радужка) с единым UI (системный диалог), управлением уровней безопасности и fallback-механизмами. Заменяет устаревший FingerprintManager (API ≥23, deprecated в API 29).

Поддержка зависит от:

  • наличия биометрических датчиков,
  • того, зарегистрированы ли шаблоны,
  • уровня надёжности (BIOMETRIC_STRONG, BIOMETRIC_WEAK, DEVICE_CREDENTIAL).

8.7.1 Уровни безопасности (BiometricManager.Authenticators.*)

УровеньКонстантаТребования устройстваДовериеПримечания
StrongBIOMETRIC_STRONG— Отпечаток: Class 3 (FIDO2) — Лицо/радужка: Class 3 (3D depth + attention detection)✅ ВысокоеРазрешено для платежей (например, BiometricPrompt.PromptInfo.setAllowedAuthenticators(BIOMETRIC_STRONG)).
WeakBIOMETRIC_WEAK— Отпечаток: Class 2 (2D) — Лицо: Class 2 (2D, без attention)⚠️ СреднееНе подходит для CryptoObject, если setUserAuthenticationValidityDurationSeconds > 0.
Device CredentialDEVICE_CREDENTIALPIN / пароль / графический ключ✅ ВысокоеМожно комбинировать: BIOMETRIC_STRONG or DEVICE_CREDENTIAL.

⚠️ Важно:

  • BIOMETRIC_STRONG — только если isStrongBiometric() возвращает true (см. ниже).
  • На API 28: только BIOMETRIC_STRONG or DEVICE_CREDENTIAL, BIOMETRIC_WEAK появился в API 29.
  • На API 30+: BiometricManager.canAuthenticate(authenticators) — единственный способ проверить поддержку.

8.7.2 Проверка доступности биометрии

val biometricManager = BiometricManager.from(context)
val canAuthenticate = biometricManager.canAuthenticate(
BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)

when (canAuthenticate) {
BiometricManager.BIOMETRIC_SUCCESS -> { /* можно аутентифицировать */ }
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> { /* датчик отсутствует */ }
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> { /* датчик недоступен */ }
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> { /* нет шаблонов */ }
BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> { /* требуется обновление */ }
BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> { /* authenticators недоступны на этом API */ }
}

💡 Best Practice:

  • Всегда проверять canAuthenticate() перед вызовом BiometricPrompt.
  • Не полагаться на PackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT) — он не учитывает шаблоны.

8.7.3 Создание BiometricPrompt

val executor = ContextCompat.getMainExecutor(context)

val biometricPrompt = BiometricPrompt(
fragmentOrActivity, // или (context as LifecycleOwner).lifecycle
executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
// errorCode:
// - ERROR_USER_CANCELED (5)
// - ERROR_NEGATIVE_BUTTON (12)
// - ERROR_NO_BIOMETRICS (11), ERROR_HW_UNAVAILABLE (1)
// - ERROR_LOCKOUT (7), ERROR_LOCKOUT_PERMANENT (9)
}

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
// result.cryptoObject — если использовался CryptoObject
// result.authenticationType — BIOMETRIC_WEAK, STRONG, DEVICE_CREDENTIAL
}

override fun onAuthenticationFailed() {
// Скан прошёл, но не распознан (например, чужой отпечаток)
}
}
)

⚠️ Lifecycle:

  • Не передавать this как LifecycleOwner, если Activity/Fragment может быть уничтожен.
  • Использовать lifecycleScope.launchWhenStarted { ... } для контроля времени жизни.

8.7.4 Настройка диалога: PromptInfo

val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Вход в приложение")
.setSubtitle("Подтвердите личность")
.setDescription("Используйте отпечаток или PIN")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
.setConfirmationRequired(true) // требует нажатия кнопки «Подтвердить» (API ≥30)
.setNegativeButtonText("Отмена")
.build()

biometricPrompt.authenticate(promptInfo)
МетодAPI+Эффект
setConfirmationRequired(true)30+После успешного сканирования — показывает кнопку «Подтвердить» (защита от атак «пассивного лица»).
setDeviceCredentialAllowed(true)29–29Устарело. Использовать setAllowedAuthenticators(...).

8.7.5 Интеграция с CryptoObject (hardware-backed enforcement)

// 1. Генерация ключа с аутентификацией
val keyGenSpec = KeyGenParameterSpec.Builder("auth_key", KeyProperties.PURPOSE_SIGN)
.setUserAuthenticationRequired(true)
.setUserAuthenticationParameters(
timeoutSeconds = 30, // действует 30 сек после аутентификации
authenticatorTypes = KeyProperties.AUTH_BIOMETRIC_STRONG or
KeyProperties.AUTH_DEVICE_CREDENTIAL
)
.build()

val keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"
)
keyPairGenerator.initialize(keyGenSpec)
val keyPair = keyPairGenerator.generateKeyPair()

// 2. Создание CryptoObject
val signature = Signature.getInstance("SHA256withECDSA").apply {
initSign(keyPair.private)
}
val cryptoObject = BiometricPrompt.CryptoObject(signature)

// 3. Аутентификация с привязкой к ключу
biometricPrompt.authenticate(promptInfo, cryptoObject)

Преимущества CryptoObject:

  • Ключ нельзя использовать, пока пользователь не аутентифицирован.
  • Система гарантирует, что аутентификация прошла аппаратно (TEE/SE).
  • Защита от bypass’а через Xposed/Magisk (без ro.debuggable=1).

⚠️ Ограничения:

  • setUserAuthenticationValidityDurationSeconds(N) разрешён только для AUTH_BIOMETRIC_STRONG or DEVICE_CREDENTIAL.
  • AUTH_BIOMETRIC_WEAKInvalidAlgorithmParameterException, если N > 0.
  • На некоторых устройствах (Samsung Knox < v3.4) — AUTH_BIOMETRIC_STRONG не поддерживается для CryptoObject.

8.7.6 Диагностика через ADB и Settings

# Проверить типы биометрии:
adb shell cmd biometric list

# Пример вывода:
# Authenticators: BIOMETRIC_STRONG (FINGERPRINT: Class 3), DEVICE_CREDENTIAL

# Проверить, включён ли confirmation prompt (API ≥30):
adb shell settings get global biometric_confirmation_prompt_enabled
# → 1 = включён, 0 = выключён (политика MDM может переопределить)

В настройках:
Settings → Security → Biometric preferences

  • «Require confirmation» (API ≥30)
  • «Use biometrics for payments» (только для Class 3)

8.7.7 Совместимость и fallback

СценарийРешение
API 23–27 (до BiometricPrompt)Использовать FingerprintManagerCompat (из androidx.core:core), но без поддержки лица/глаз.
Нет биометрииПерейти на PIN/пароль через KeyguardManager.createConfirmDeviceCredentialIntent().
BIOMETRIC_ERROR_LOCKOUTПоказать сообщение: «Слишком много попыток. Повторите через N секунд». Через BiometricManager нельзя получить оставшееся время — только повторная проверка canAuthenticate().
Требуется высокая надёжность (платежи)Использовать только BIOMETRIC_STRONG, иначе — отказать.

Рекомендуемая стратегия:

val authenticators = when {
Build.VERSION.SDK_INT >= 30 -> BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL
Build.VERSION.SDK_INT >= 29 -> BiometricManager.Authenticators.BIOMETRIC_WEAK or BiometricManager.Authenticators.DEVICE_CREDENTIAL
else -> 0 // fallback на PIN
}