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:split | package — обязательный. sharedUserId — только для приложений, подписанных одним сертификатом. installLocation: internalOnly, auto, preferExternal. revisionCode — int, начиная с 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:localeConfig | debuggable — автоматически 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:name | String (FQCN или .ShortName) | Обязателен. Класс должен быть public, наследовать Activity. |
android:exported | boolean | true — доступен из других приложений; false — только внутренние вызовы. Обязателен при наличии <intent-filter>. |
android:launchMode | standard, singleTop, singleTask, singleInstance | singleInstance создаёт новую задачу, только один экземпляр в системе. |
android:taskAffinity | String | По умолчанию — package.name. Менять только при необходимости. |
android:theme | @style/... | Перекрывает application@theme. |
android:screenOrientation | unspecified, behind, landscape, portrait, reverseLandscape, reversePortrait, sensorLandscape, sensorPortrait, userLandscape, userPortrait, sensor, fullSensor, nosensor, user, fullUser, locked | locked — фиксирует текущую ориентацию. |
android:configChanges | mcc, mnc, locale, touchscreen, keyboard, keyboardHidden, navigation, screenLayout, fontScale, uiMode, orientation, screenSize, smallestScreenSize, density, layoutDirection, colorMode, density и др. | При указании — onConfigurationChanged() вызывается вместо пересоздания. |
android:windowSoftInputMode | stateUnspecified, stateUnchanged, stateHidden, stateAlwaysHidden, stateVisible, stateAlwaysVisible, adjustUnspecified, adjustResize, adjustPan | adjustResize — окно уменьшается; adjustPan — прокручивается. |
android:resizeableActivity | boolean | Влияет на multi-window. По умолчанию: true, если targetSdk ≥ 24. |
android:documentLaunchMode | none, intoExisting, always, never | Для режима «документ» (recents stack per document). |
android:autoRemoveFromRecents | boolean | Приложение не появится в Recent Apps. |
android:lockTaskMode | normal, 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:name | android.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:mimeType | mimeType: например 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:permissionGroup | maxSdkVersion — используется, когда разрешение устарело (например, WRITE_EXTERNAL_STORAGE после Android 10). |
<permission> | android:name, android:label, android:description, android:permissionGroup, android:protectionLevel, android:icon | protectionLevel: normal, dangerous, signature, signatureOrSystem, internal, appop, privileged, role, development, installer, verifier, pre23, setup, runtime — можно комбинировать через ` |
Список важнейших системных dangerous-разрешений (runtime, начиная с API 23):
| Группа | Разрешения |
|---|---|
| CALENDAR | READ_CALENDAR, WRITE_CALENDAR |
| CALL_LOG | READ_CALL_LOG, WRITE_CALL_LOG, PROCESS_OUTGOING_CALLS |
| CAMERA | CAMERA |
| CONTACTS | READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS |
| LOCATION | ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, ACCESS_BACKGROUND_LOCATION |
| MICROPHONE | RECORD_AUDIO |
| PHONE | READ_PHONE_STATE, CALL_PHONE, READ_PHONE_NUMBERS, ANSWER_PHONE_CALLS, ACCEPT_HANDOVER |
| SENSORS | BODY_SENSORS, BODY_SENSORS_BACKGROUND, ACTIVITY_RECOGNITION |
| SMS | SEND_SMS, RECEIVE_SMS, READ_SMS, RECEIVE_WAP_PUSH, RECEIVE_MMS |
| STORAGE | READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, MANAGE_EXTERNAL_STORAGE (All files access) |
⚠️
MANAGE_EXTERNAL_STORAGEтребует отдельного объяснения в Google Play Console и модерации.
1.6 <uses-feature>
| Атрибут | Значения | Примечания |
|---|---|---|
android:name | android.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:required | true/false | false — устройство может не иметь фичи, и приложение всё равно установится. Затем проверять через PackageManager.hasSystemFeature(). |
🧱 View и ViewGroup
2.1 Общие атрибуты View (унаследованные от android.view.View)
| XML-атрибут | Программный метод / поле | Тип | Возможные значения / диапазон | API+ | Примечания |
|---|---|---|---|---|---|
android:id | View.setId(int) / View.getId() | @+id/name, @id/name, @android:id/... | Любое целое, View.NO_ID = -1 | 1+ | Рекомендуется использовать R.id.*. @+id/ — объявление, @id/ — ссылка. |
android:layout_width, android:layout_height | ViewGroup.LayoutParams.width/height | dp, 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 | ≥0 | 1+ (paddingStart/End — 17+) | RTL-совместимость: paddingStart/End заменяют Left/Right. |
android:layout_margin* (margin, marginLeft, ...) | ViewGroup.MarginLayoutParams.* | dp, px | ≥0 | 1+ (marginStart/End — 17+) | Только если LayoutParams наследуют MarginLayoutParams (почти все). |
android:background | View.setBackground(Drawable) | @drawable/..., @color/..., #RRGGBB, ?attr/... | — | 1+ | Может быть ColorDrawable, GradientDrawable, StateListDrawable, InsetDrawable, LayerDrawable и др. |
android:foreground | View.setForeground(Drawable) | То же | — | 23+ (для View), 1+ для FrameLayout | Рисуется поверх содержимого, но под дочерними (если ViewGroup). |
android:elevation | View.setElevation(float) | dp, px | ≥0, по умолчанию 0 | 21+ | Зависит от translationZ. Тень определяется outlineProvider. |
android:translationX/Y/Z | View.setTranslationX/Y/Z(float) | dp, px | Любое | 21+ (Z — 21+) | Смещение без изменения layout_*. |
android:alpha | View.setAlpha(float) | float | [0.0, 1.0], по умолчанию 1.0 | 11+ | Альфа применяется рекурсивно ко всем дочерним. |
android:visibility | View.setVisibility(int) | visible, invisible, gone | — | 1+ | invisible — занимает место, gone — не учитывается в layout. |
android:clickable, android:longClickable, android:focusable, android:focusableInTouchMode, android:duplicateParentState | View.setClickable(...), и т.д. | boolean | true/false | 1+ | duplicateParentState — дочерние наследуют pressed/focused родителя. |
android:contentDescription | View.setContentDescription(CharSequence) | String, @string/... | — | 4+ | Для accessibility (TalkBack). |
android:importantForAccessibility | View.setImportantForAccessibility(int) | auto, yes, no, noHideDescendants | — | 16+ | noHideDescendants — отключает accessibility для всего поддерева. |
android:transitionName | View.setTransitionName(String) | String | — | 21+ | Для shared element transitions. |
android:outlineProvider | View.setOutlineProvider(OutlineProvider) | background, paddedBounds, bounds | — | 21+ | Определяет форму тени и clip. |
android:clipToOutline | View.setClipToOutline(boolean) | boolean | true/false | 21+ | Обрезает содержимое по outline (например, круглая ImageView). |
android:saveEnabled | View.setSaveEnabled(boolean) | boolean | true/false | 1+ | Управляет сохранением состояния в onSaveInstanceState. |
android:tag, android:tagId | View.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) | boolean | 1+ | Обрезать ли дочерние за границами ViewGroup. |
android:clipToPadding | Все ViewGroup, имеющие padding | boolean | 1+ | Обрезать ли содержимое по внутренней области (внутри padding). |
android:layoutAnimation | Любая | @anim/... | 1+ | LayoutAnimationController, применяется при добавлении/удалении дочерних. |
android:animateLayoutChanges | Любая | boolean | 11+ | Автоматически создаёт LayoutTransition для добавления/удаления/изменения размеров. |
android:descendantFocusability | Все | beforeDescendants, afterDescendants, blocksDescendants | 1+ | Управляет тем, получает ли ViewGroup фокус до/после/вместо дочерних. |
android:splitMotionEvents | ViewGroup, обрабатывающие touch | boolean | 12+ | Разделяет MotionEvent между дочерними (нужно для nested scrolling). |
2.3 LinearLayout — специфичные атрибуты
| XML-атрибут | Программный метод | Тип / Значения | API+ | Примечания |
|---|---|---|---|---|
android:orientation | setOrientation() | horizontal, vertical | 1+ | По умолчанию horizontal. |
android:gravity | setGravity() | top, bottom, left, right, center, center_vertical, center_horizontal, fill, fill_vertical, fill_horizontal, start, end и комбинации через | | 1+ | Выравнивание дочерних внутри LinearLayout. Не путать с layout_gravity. |
android:baselineAligned | setBaselineAligned() | boolean | 1+ | Выравнивание по базовой линии текста (для TextView). Может снижать производительность. |
android:weightSum | setWeightSum(float) | float ≥ суммы layout_weight | 1+ | Если не указан — сумма всех layout_weight. |
android:baselineAlignedChildIndex | setBaselineAlignedChildIndex(int) | int (индекс дочернего) | 1+ | Какой дочерний элемент использовать для baseline alignment. |
Дочерние элементы в LinearLayout:
| Атрибут | Тип / Значения | Пояснение |
|---|---|---|
android:layout_weight | float ≥ 0 | Доля оставшегося пространства. Если width/height = 0dp — только вес, без учёта содержимого. |
android:layout_gravity | То же, что gravity у LinearLayout | Выравнивание элемента внутри LinearLayout (перпендикулярно orientation). Например, в vertical — left, 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_centerVertical | boolean | Центрирование. |
android:layout_to*Of (RightOf, LeftOf, EndOf, StartOf, Above, Below) | @id/... | Позиционирование относительно другого элемента. |
android:layout_align* (Baseline, Top, Bottom, Left, Right, Start, End) | @id/... | Выравнивание краёв. |
android:layout_alignWithParentIfMissing | boolean | Если ссылка @id/... ещё не разрешена (цикл), использовать край родителя. Опасно. |
android:ignoreGravity | @id/... | ViewGroup игнорирует gravity для указанного дочернего. |
⚠️ Производительность:
RelativeLayoutделает 2 прохода layout. Избегайте вложенныхRelativeLayout.
2.5 FrameLayout — минималистичный контейнер
| Атрибут | Значения | Пояснение |
|---|---|---|
android:foregroundGravity | То же, что gravity | Выравнивание foreground (если задан). |
android:measureAllChildren | boolean | Учитывать ли все дочерние при измерении (по умолчанию — только видимые). |
Дочерние элементы:
| Атрибут | Значения | Пояснение |
|---|---|---|
android:layout_gravity | Любые комбинации | Позиционирование дочернего внутри FrameLayout. |
2.6 ConstraintLayout (AndroidX, androidx.constraintlayout:constraintlayout)
Основные атрибуты корневого ConstraintLayout
| Атрибут | Тип | Значения / Примечания |
|---|---|---|
android:layout_width/height | dp, match_parent, wrap_content | match_constraint = 0dp — «связать» размер. |
app:layout_constraintWidth/Height_default | spread, wrap, percent | percent требует constraintWidth/Height_percent. |
app:layout_constraintWidth_percent, app:layout_constraintHeight_percent | float ∈ [0.0, 1.0] | Доля от родителя (если размер 0dp и _default="percent"). |
app:layout_optimizationLevel | standard, direct, barrier, chain, dimension, ratio, all | Управляет оптимизациями. По умолчанию standard. |
Constraint-атрибуты для дочерних
| Группа | Атрибуты | Значения | Пояснение |
|---|---|---|---|
| Позиционирование | layout_constraint*to*Of (LeftToRightOf, StartToEndOf, TopToBottomOf, BaselineToBaselineOf, и т.д.) | parent, @id/..., @+id/... | 20+ комбинаций. BaselineToBaselineOf — только для TextView/наследников. |
| Margin | layout_constraint*Margin (Start, End, Left, Right, Top, Bottom) | dp, @dimen/... | Применяется, если constraint есть. |
| Gone margin | layout_constraint*GoneMargin | dp | Применяется, если цель View.GONE. |
| Bias | layout_constraintHorizontal_bias, layout_constraintVertical_bias | float ∈ [0.0, 1.0] | 0.0 = левый/верхний край, 0.5 = центр (по умолчанию), 1.0 = правый/нижний. |
| Цепочки (Chains) | layout_constraintHorizontal_chainStyle, layout_constraintVertical_chainStyle | spread, spread_inside, packed | packed + bias = группировка в углу. |
| Barrier | app:barrierDirection | top, bottom, left, right, start, end | В Barrier: определяет, по какому краю брать максимум/минимум. |
| Guideline | app:orientation | horizontal, vertical | Только для Guideline. |
app:layout_constraintGuide_* (begin, end, percent) | dp или float | begin — от начала (top/left), end — от конца (bottom/right), percent — доля (0.0–1.0). | |
| Ratio | app:layout_constraintDimensionRatio | "W,H", "H,W", "w,2:1", "16:9" | Соотношение ширины:высоты. W/H — фиксированный параметр. Пример: "H,16:9" → высота фиксирована, ширина = height * 16/9. |
| Visibility | tools:visibility | visible, invisible, gone | Только в preview (не влияет на runtime). |
Flow (внутри ConstraintLayout, AndroidX ≥ 2.0)
| Атрибут | Значения | Пояснение |
|---|---|---|
app:flow_wrapMode | none, chain, aligned | chain — создаёт цепочки, aligned — выравнивание по сетке. |
app:flow_horizontal/VerticalGap | dp | Расстояние между элементами. |
app:flow_horizontal/VerticalAlign | start, center, end | Выравнивание строк/столбцов. |
app:flow_maxElementsWrap | int | Макс. элементов в строке/столбце. |
2.7 RecyclerView — ключевые параметры
XML-атрибуты RecyclerView
| Атрибут | Тип | Значения | Примечания |
|---|---|---|---|
android:layoutManager | String или @string/... | "androidx.recyclerview.widget.LinearLayoutManager", "GridLayoutManager", "StaggeredGridLayoutManager" или кастомный FQCN | Можно указать класс напрямую. |
app:spanCount | int | ≥1 | Только если layoutManager = GridLayoutManager или StaggeredGridLayoutManager. |
app:reverseLayout | boolean | true/false | Только LinearLayoutManager/GridLayoutManager. |
app:stackFromEnd | boolean | true/false | Начинать от конца (например, чат — новые сообщения снизу). |
android:orientation | vertical, horizontal | Только для LinearLayoutManager/GridLayoutManager. | |
app:fastScrollEnabled, app:fastScrollHorizontal/VerticalThumbDrawable, app:fastScrollHorizontal/VerticalTrackDrawable | — | Для быстрой прокрутки (начиная с RecyclerView 1.3.0). |
Программные ключевые параметры
| Класс | Метод | Назначение |
|---|---|---|
RecyclerView | setHasFixedSize(boolean) | Если размер не меняется при изменении данных — true (оптимизация). |
setItemViewCacheSize(int), setRecycledViewPool(RecycledViewPool) | Настройка кэша. | |
setNestedScrollingEnabled(boolean) | Отключение nested scrolling (если внутри NestedScrollView). | |
LinearLayoutManager | setInitialPrefetchItemCount(int) | Prefetch для async-загрузки (например, изображений). |
ItemAnimator | setSupportsChangeAnimations(false) | Отключить анимации при обновлении (для производительности). |
DiffUtil / ListAdapter | — | Рекомендуемый способ обновления данных (автоматический diff). |
2.8 ScrollView и NestedScrollView
| Атрибут | Значения | Примечания |
|---|---|---|
android:fillViewport | boolean | Растягивать дочерний элемент на весь viewport (если его содержимое < высоты ScrollView). |
android:overScrollMode | always, ifContentScrolls, never | Управление overscroll-эффектом («пружинка»). |
android:scrollbars | none, horizontal, vertical, both | Отображение скроллбаров (по умолчанию — vertical). |
android:scrollbarStyle | insideOverlay, insideInset, outsideOverlay, outsideInset | Позиционирование скроллбара. |
⚠️
ScrollViewможет содержать только один прямой дочерний элемент.
2.9 Material Design Components (MDC) — ключевые атрибуты
MaterialButton
| Атрибут | Тип | Значения | Примечания |
|---|---|---|---|
app:cornerRadius | dp | ≥0 | Закругление (перекрывает shapeAppearance). |
app:strokeWidth, app:strokeColor | dp, color | Граница. | |
app:backgroundTint, app:rippleColor, app:icon, app:iconGravity, app:iconPadding | — | Поддержка ColorStateList для tint/ripple. | |
app:strokeColor | ColorStateList | Например: <selector><item android:color="@color/grey" android:state_enabled="false"/><item android:color="@color/blue"/></selector> |
TextInputLayout
| Атрибут | Значения | Пояснение |
|---|---|---|
app:boxBackgroundMode | outline, filled, none | Стиль поля ввода. |
app:startIconDrawable, app:endIconMode | drawable, none, clear_text, password_toggle, dropdown_menu, custom | endIconMode управляет поведением. |
app:helperText, app:helperTextEnabled, app:counterEnabled, app:counterMaxLength | String, boolean, int | Вспомогательные элементы. |
app:errorEnabled, app:errorIconDrawable | — | Управление состоянием ошибки. |
AppBarLayout, CollapsingToolbarLayout
| Атрибут | Значения | Примечания |
|---|---|---|
app:layout_scrollFlags | scroll, enterAlways, enterAlwaysCollapsed, exitUntilCollapsed, snap, snapMargins | Комбинируются через |. |
app:contentScrim, app:statusBarScrim | color, drawable | Цвет фона при схлопывании. |
app:expandedTitleMargin*, app:collapsedTitleTextAppearance | — | Настройка заголовка. |
BottomNavigationView
| Атрибут | Значения | Примечания |
|---|---|---|
app:menu | @menu/... | Обязателен. |
app:itemIconTint, app:itemTextColor | ColorStateList | Цвет иконок и текста (по умолчанию — ?attr/colorPrimary). |
app:itemBackground | drawable | Фон элемента (например, ripple). |
app:labelVisibilityMode | auto, selected, labeled, unlabeled | Управление отображением подписей. |
2.10 tools: namespace — атрибуты только для preview
| Атрибут | Пример | Пояснение |
|---|---|---|
tools:text | tools:text="Sample text" | Подставляет текст только в preview. |
tools:src, tools:srcCompat | tools:src="@drawable/preview_image" | Изображение для preview. |
tools:listitem | tools:listitem="@layout/item_preview" | Для RecyclerView, ListView, Spinner. |
tools:visibility, tools:gone, tools:showIn, tools:context, tools:targetApi, tools:ignore | — | showIn — для <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 | Открыть документ через DocumentsUI | 19+ | Поддерживает CATEGORY_OPENABLE, EXTRA_ALLOW_MULTIPLE. |
ACTION_CREATE_DOCUMENT | Создать новый документ | 19+ | Аналогично OPEN_DOCUMENT, но создаёт. |
ACTION_SEND | Отправить данные (текст, изображение) | 1+ | Требует EXTRA_TEXT / EXTRA_STREAM, setType(). |
ACTION_SEND_MULTIPLE | Отправить несколько элементов | 4+ | EXTRA_STREAM → ArrayList<Uri>. |
ACTION_INSERT | Вставить новый элемент в набор (например, контакт) | 1+ | Редко используется. |
ACTION_DELETE | Удалить данные по URI | 1+ | Например, 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_BROWSABLE | Activity доступна из браузера (например, по ссылке) | Обязательна для deep link’ов. |
CATEGORY_APP_BROWSER, CATEGORY_APP_CALCULATOR, ..., CATEGORY_APP_* | Группы системных приложений | Например, CATEGORY_APP_MUSIC для музыкальных приложений. |
CATEGORY_LEANBACK_LAUNCHER | Для Android TV | Аналог LAUNCHER, но для TV. |
CATEGORY_INFO | «About» экран (редко) | — |
CATEGORY_OPENABLE | URI, возвращаемый интентом, должен быть «открываемым» (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_TEXT | CharSequence | Текст для ACTION_SEND | — |
EXTRA_HTML_TEXT | String | HTML-версия текста | Поддерживается не всеми получателями. |
EXTRA_SUBJECT | CharSequence | Тема (email, SMS) | — |
EXTRA_STREAM | Uri | URI потока (content://, file://) | Для ACTION_SEND, ACTION_VIEW. |
EXTRA_STREAM | ArrayList<Uri> | Несколько URI | Для ACTION_SEND_MULTIPLE. |
EXTRA_EMAIL, EXTRA_CC, EXTRA_BCC | String[] | Адреса email | — |
EXTRA_TITLE | CharSequence | Заголовок диалога (например, для ACTION_GET_CONTENT) | — |
EXTRA_ALLOW_MULTIPLE | boolean | Разрешить множественный выбор | Для ACTION_OPEN_DOCUMENT, GET_CONTENT. |
EXTRA_MIME_TYPES | String[] | Список допустимых MIME types | Для ACTION_OPEN_DOCUMENT (подменяет setType()). |
EXTRA_PROCESS_TEXT | CharSequence | Выделенный текст | Для ACTION_PROCESS_TEXT. |
EXTRA_RETURN_RESULT | boolean | Вернуть результат вместо завершения | Для некоторых системных Activity (например, wallpaper picker). |
EXTRA_KEY_EVENT | KeyEvent | KeyEvent (редко) | — |
EXTRA_INTENT | Intent | Вложенный интент | Например, в ACTION_CHOOSER. |
EXTRA_SHORTCUT_INTENT, EXTRA_SHORTCUT_NAME, EXTRA_SHORTCUT_ICON | — | Для динамических shortcut’ов (устарело, см. ShortcutManager). | |
EXTRA_REFERRER | Uri | Источник запуска (например, android-app://package) | Устанавливается системой. Доступен через getReferrer(). |
EXTRA_PACKAGE_NAME | String | Имя пакета | В 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_FRONT | Activity была перемещена вперёд | Устанавливается системой. |
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_IMMUTABLE | PendingIntent нельзя изменить | Обязателен начиная с Android 12 (API 31), если не требуется мутабельность. |
FLAG_MUTABLE | PendingIntent можно изменить | Требуется для 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() | Intent | ActivityResult (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, RequestMultiplePermissions | — | Boolean / Map | Работает с onRequestPermissionsResult() под капотом. |
StartIntentSenderForResult() | IntentSender, Bundle, Int, Int, Int | ActivityResult | Для 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_PRIVILEGED — signature) |
Группа: 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)
| Тип | Разрешение | Требования |
|---|---|---|
location | ACCESS_FINE_LOCATION или COARSE | Должен быть указан в <service android:foregroundServiceType="location"> |
camera | CAMERA | — |
microphone | RECORD_AUDIO | — |
phoneCall | READ_PHONE_STATE | Только для системных приложений (privileged) |
mediaPlayback | — | Для аудиоплееров |
mediaProjection | android.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, IBinder | ✅ | IBinder — для IPC. |
Object, Map, List (не ArrayList) | ❌ | ClassCastException при извлечении. |
⚠️ Лимит размера Transaction: ~1 МБ на всю транзакцию (включая интент, экстра,
PendingIntent). Превышение →TransactionTooLargeException.
3.9 Часто используемые URI schemes и MIME types
| Scheme | Пример | MIME type |
|---|---|---|
http://, https:// | https://example.com | text/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/123 | image/jpeg, video/mp4, audio/mpeg, application/pdf и др. |
Стандартные MIME:
*/*— любойimage/*,video/*,audio/*,text/*,application/*application/json,application/xml,application/pdf,application/ziptext/plain,text/html,text/csv
🎨 Resources
4.1 Resource Qualifiers (в порядке приоритета применения)
Каждая папка res/ может иметь суффикс вида -qualifier1-qualifier2....
Система выбирает наиболее специфичную конфигурацию, соответствующую устройству.
| Квалификатор | Формат | Примеры | Описание | API+ | Примечания |
|---|---|---|---|---|---|
| MCC | mcc<код> | mcc310 (США), mcc250 (Россия) | Mobile Country Code | 1+ | Высший приоритет. Редко используется. |
| MNC | mnc<код> | mnc001, mnc02 | Mobile Network Code | 1+ | Всегда после MCC: mcc310-mnc004. |
| Language | b+<код> (BCP 47) или <код> | en, ru, zh, b+zh+Hans, b+sr+Latn | Язык | 1+ (b+ — 21+) | Предпочтительно b+ (например, b+zh+Hans+CN). |
| Region | r<регион> | rUS, rRU, rCN | Регион (страна) | 1+ | После языка: en-rUS, ru-rRU. |
| Screen Layout Direction | ldrtl, ldltr | ldrtl | RTL / LTR | 17+ | ldrtl — right-to-left (арабский, иврит). |
| Smallest Width | sw<dp>dp | sw600dp, sw720dp | Минимальная ширина в dp | 13+ | Ключевой для адаптивного дизайна. |
| Available Width | w<dp>dp | w720dp, w1080dp | Текущая доступная ширина | 13+ | Меняется при повороте. |
| Available Height | h<dp>dp | h720dp | Текущая доступная высота | 13+ | — |
| Screen Size | small, normal, large, xlarge | large | Устаревшая категория | 1+ | Не рекомендуется — используйте sw<N>dp. |
| Screen Aspect | long, notlong | long | Соотношение сторон (≥1.75:1 — long) | 1+ | — |
| Screen Orientation | port, land | land | Портрет / ландшафт | 1+ | — |
| UI Mode | car, 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-vrheadset | night, television | Режим устройства / тема | 8+ (night — 23+) | ui-mode-night — ночная тема (системная). |
| Night Mode | night, notnight | night | Явное управление ночным режимом | 8+ | Переопределяет ui-mode-night. |
| Screen Pixel Density | ldpi, mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi, nodpi, tvdpi, anydpi | xxhdpi, anydpi | Плотность пикселей | 4+ (anydpi — 21+) | anydpi — векторные ресурсы (SVG, VectorDrawable). |
| Touchscreen Type | notouch, stylus, finger | finger | Тип сенсора | 4+ | — |
| Keyboard Availability | keysexposed, keyshidden, keyssoft | keyshidden | Физическая клавиатура | 4+ | — |
| Primary Text Input Method | nokeys, qwerty, 12key | qwerty | — | 4+ | — |
| Navigation Key Availability | navexposed, navhidden | navhidden | Навигационные кнопки | 4+ | — |
| Primary Non-Touch Navigation Method | nonav, dpad, trackball, wheel | dpad | — | 4+ | — |
| Screen Configuration | screenLayout (устарело) | — | — | 4–30 | Заменено sw<N>dp, w<N>dp. |
| Layout Direction | ldrtl, ldltr | ldrtl | Направление текста | 17+ | Уже выше, но повтор для акцента. |
| Round Screen | round, notround | round | Круглый экран | 21+ | Для Wear OS. |
| Watch Shape | watch-round, watch-square, watch-rect | watch-round | Форма часов | 23+ | Wear OS. |
| Foldable States | folded, unfolded, hinge, posture | folded, unfolded | Состояние складного устройства | 30+ | Требует <queries> и Configuration.isLayoutSizeAtLeast(). |
| Posture | posture-natural, posture-book, posture-tabletop, posture-tent | posture-book | Положение устройства (книга, штатив) | 31+ | Через WindowManager.getCurrentWindowMetrics(). |
| Device Class | device-class-phone, device-class-tablet, device-class-desktop, device-class-tv, device-class-car, device-class-watch | device-class-tablet | Класс устройства (Material Design 3) | 31+ | Определяется системой на основе sw<N>dp и DPI. |
| Dynamic Color | dynamic-color, dynamic-color-no | dynamic-color | Поддержка Material You (Monet) | 31+ | Требует android:theme="@style/Theme.Material3.*.DynamicColors*". |
| Font Scaling | fontScale (устарело) | — | — | 1–30 | Заменено Configuration.fontScale. |
| Locale Script | script-<код> | 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-значения | Примечания |
|---|---|---|---|
string | String | "text", @string/... | — |
boolean | Boolean | true, false, @bool/... | — |
integer | Int | 123, @integer/... | Можно min/max. |
float | Float | 0.5, @fraction/... | — |
dimension | Float (px) | 16dp, 12sp, 100px, @dimen/... | Преобразуется в пиксели через TypedValue.applyDimension(). |
color | Int (ARGB) | #FF0000, @color/..., ?attr/colorPrimary | Поддерживает ColorStateList. |
reference | Int (res ID) | @drawable/..., @layout/..., @+id/... | Возвращает ID ресурса, не объект. |
fraction | Float | 50%, 0.5, @fraction/... | 50% = 0.5f. |
enum | Int | Имя из <enum> | Строго ограниченное множество. |
flags | Int | Комбинация имён через | | Например: `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, colorOnPrimaryContainer | color | Основная палитра. |
colorSecondary, colorTertiary | color | Дополнительные цвета. |
colorSurface, colorOnSurface, colorSurfaceVariant, colorOnSurfaceVariant | color | Поверхности (фон, карточки). |
colorError, colorOnError, colorErrorContainer, colorOnErrorContainer | color | Ошибки. |
colorOutline, colorOutlineVariant | color | Границы (1dp / 0.5dp). |
colorSurfaceInverse, colorOnSurfaceInverse, colorPrimaryInverse | color | Инвертированные цвета (для тёмных оверлеев). |
elevationOverlayEnabled | boolean | Накладывать ли цвет поверхности при elevation > 0 (только в night mode). |
elevationOverlayColor | color | Цвет оверлея (обычно 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_pressed | boolean | Нажатие (touch down) | Высокий |
state_focused | boolean | Фокус (клавиатура/DPAD) | Средний |
state_selected | boolean | Выбор (например, в ListView) | Средний |
state_checked | boolean | Checked (RadioButton, CheckBox) | Средний |
state_enabled | boolean | Включено/выключено | Высокий (часто !enabled первым) |
state_activated | boolean | Активировано (например, CHOICE_MODE_SINGLE) | Средний |
state_window_focused | boolean | Окно в фокусе | Низкий |
state_hovered | boolean | Наведение (мышь/трекпад) | Высокий |
state_drag_can_accept | boolean | Может принять drag | API 11+ |
state_drag_hovered | boolean | Hover во время drag | API 11+ |
⚠️ Приоритет: порядок
<item>важен — первый совпавший применяется. Обычно:!enabled→pressed→focused→default.
Material Design 3 добавляет динамические оверлеи через colorSurface + elevation — но это не в ColorStateList.
4.5 Drawable — перечень типов
4.5.1 ShapeDrawable (<shape>)
| Атрибут | Значения | Примечания |
|---|---|---|
android:shape | rectangle, oval, line, ring | — |
android:innerRadius, innerRadiusRatio | dp, float | Только для ring. |
android:thickness, thicknessRatio | dp, float | Только для ring. |
android:useLevel | boolean | Для 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:enterFadeDuration | ms |
<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
| Тип | Ключевой атрибут / метод |
|---|---|
InsetDrawable | android:insetLeft/Top/Right/Bottom |
ClipDrawable | android:clipOrientation, android:gravity, level (0–10000) |
RotateDrawable | android:fromDegrees, toDegrees, pivotX/Y |
ScaleDrawable | android:scaleWidth/Height, level |
LevelListDrawable | <item android:minLevel="..." android:maxLevel="..." android:drawable="..."/> |
TransitionDrawable | startTransition(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:propertyName | translationX, alpha, rotation, scaleX, backgroundColor, custom property | Должен быть setXxx() / getXxx(). |
android:valueFrom, valueTo, valueType | floatType, intType, pathType, colorType | colorType — ARGB (например, #FF0000 → #00FF00). |
android:duration, startDelay, repeatCount, repeatMode | ms, int, restart/reverse | — |
android:interpolator | @android:interpolator/*, @interpolator/custom | fast_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
Требует:
VectorDrawableсandroid:nameAnimatorсandroid:propertyName="@string/pathName.fillColor"<animated-vector>— связывает их
4.7.4 TransitionManager и Scene
| Класс | Метод | Назначение |
|---|---|---|
TransitionManager | beginDelayedTransition(ViewGroup, Transition) | Автоматическая анимация layout-изменений |
ChangeBounds, Fade, ChangeTransform, AutoTransition | — | Стандартные Transition’ы |
TransitionSet | addTransition(), setOrdering(ORDERING_TOGETHER/SEQUENTIAL) | Группировка |
⚙️ Gradle — build.gradle(:app)
5.1 Корневой блок android { } — ключевые свойства
| Свойство | Тип | Значение по умолчанию | AGP+ | Описание |
|---|---|---|---|---|
compileSdk | Int | — | 7.0+ | Версия SDK для компиляции (должна быть ≥ targetSdk). Использует build-tools, platforms/android-N. |
buildToolsVersion | String | Последняя, совместимая с compileSdk | 1.0+ | Явное указание build-tools (рекомендуется не указывать). |
ndkVersion | String | Последняя, установленная | 2.2+ | Версия NDK (например, "25.2.9519653"). |
defaultConfig { } | DefaultConfig | — | 1.0+ | Базовая конфигурация для всех вариантов. |
buildTypes { } | NamedDomainObjectContainer<BuildType> | debug, release | 1.0+ | Конфигурации сборки. |
productFlavors { } | NamedDomainObjectContainer<ProductFlavor> | — | 1.1+ | Варианты продукта (например, free, pro, internal, prod). |
flavorDimensions | List<String> | ["version"] | 3.0+ | Размерности для комбинирования (dimension "version" в каждом flavor’е). |
compileOptions { } | CompileOptions | sourceCompatibility = JavaVersion.VERSION_1_8, targetCompatibility = JavaVersion.VERSION_1_8 | 3.0+ | Настройки javac. |
kotlinOptions { } | KotlinJvmOptions | — | 3.0+ | Настройки Kotlin-компилятора (jvmTarget, freeCompilerArgs). |
5.2 defaultConfig { } — обязательные и часто используемые
| Свойство | Тип | Значение по умолчанию | Примечания |
|---|---|---|---|
applicationId | String | android.namespace (если задан) или package из AndroidManifest.xml | Изменяет package в APK (не в коде!). Для library не используется. |
minSdk | Int | — | Минимальная поддерживаемая версия. Влияет на доступные API и оптимизации (desugar, coreLibraryDesugaring). |
targetSdk | Int | — | Версия поведения системы (режимы совместимости, permission model, scoped storage). Должна быть ≤ compileSdk. |
versionCode | Int | — | Уникальный номер сборки (увеличивается монотонно). Максимум — 2100000000 (ограничение Play Console). |
versionName | String | — | Показывается пользователю (может быть "1.2.3-beta"). |
testApplicationId | String | ${applicationId}.test | Только для androidTest. |
testInstrumentationRunner | String | "androidx.test.runner.AndroidJUnitRunner" | Для androidTest. |
testInstrumentationRunnerArguments | Map<String, String> | [:] | Аргументы runner’а (например, "clearPackageData" to "true"). |
multiDexEnabled | Boolean | false | Если minSdk < 21, требует androidx.multidex:multidex. |
vectorDrawables.useSupportLibrary | Boolean | true | Для minSdk < 21 — использовать AppCompatDelegate.setCompatVectorFromResourcesEnabled(true). |
vectorDrawables.generatedDensities | List<String> | [] | Генерировать PNG из VectorDrawable для указанных плотностей (устарело, избегать). |
5.3 buildTypes { } — стандартные и кастомные
Стандартный debug
debug {
isDebuggable = true
isMinifyEnabled = false
isShrinkResources = false
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
signingConfig = signingConfigs.debug
}
| Свойство | Тип | Значение по умолчанию (debug) | Значение по умолчанию (release) | Эффект |
|---|---|---|---|---|
isDebuggable | Boolean | true | false | Включает Debug.startMethodTracing(), StrictMode, debug-билд нативных библиотек. |
isMinifyEnabled | Boolean | false | false | Включает R8/ProGuard (сжатие, оптимизация, обфускация). Требует proguardFiles. |
isShrinkResources | Boolean | false | false | Удаляет неиспользуемые ресурсы (только при minifyEnabled = true). |
isZipAlignEnabled | Boolean | true | true | Выравнивание 4-байтных границ в APK. Обязательно для Play Console. |
applicationIdSuffix | String | "" | "" | Добавляется к applicationId (например, .debug). Позволяет устанавливать несколько вариантов. |
versionNameSuffix | String | "" | "" | Добавляется к versionName. |
matchingFallbacks | List<String> | ["debug"] | ["release"] | При сборке библиотеки — какой build type использовать, если не найден. |
proguardFiles(...) | List<File> | [] | [] | Файлы конфигурации R8/ProGuard. |
consumerProguardFiles(...) | List<File> | [] | [] | Файлы, включаемые в AAR (для библиотек). |
signingConfig | SigningConfig? | signingConfigs.debug | null | Конфигурация подписи. |
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
}
}
| Свойство | Тип | Примечания |
|---|---|---|
storeFile | File | Файл keystore (.jks, .keystore, .p12). |
storePassword, keyPassword | String | Хранить в gradle.properties (локально) или CI secrets. |
keyAlias | String | Имя ключа в keystore. |
v1SigningEnabled, v2SigningEnabled | Boolean | v1 (JAR) — для Android < 7.0, v2 (APK Signature Scheme v2) — для ≥7.0. По умолчанию: v1 = true, v2 = true. |
v3SigningEnabled, v4SigningEnabled | Boolean | v3 (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, и т.д.
| Свойство | Тип | Примечания |
|---|---|---|
dimension | String | Должен быть объявлен в flavorDimensions. |
applicationId, versionName, versionCode | String/Int | Переопределяют defaultConfig. versionCode можно увеличивать: versionCode = defaultConfig.versionCode!! + 1000. |
manifestPlaceholders | Map<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 { } — включение/отключение фич
| Свойство | Тип | Значение по умолчанию | Эффект |
|---|---|---|---|
viewBinding | Boolean | false | Генерирует ActivityMainBinding (без findViewById). |
dataBinding | Boolean | false | Включает Data Binding (конфликтует с Compose). |
compose | Boolean | false | Включает Jetpack Compose (требует composeOptions). |
aidl | Boolean | true | Генерация AIDL. |
renderScript | Boolean | false | Устарело (удалено в AGP 7.0+). |
shaders | Boolean | false | Включает RenderScript через Shader (не RS). |
resValues | Boolean | true | Разрешает resValue. |
buildConfig | Boolean | true | Генерация BuildConfig. |
prefab | Boolean | false | Включение Prefab (для нативных библиотек из AAR). |
androidResources | Boolean | true | Обработка res/ и assets/. |
5.7 composeOptions { } (если buildFeatures.compose = true)
| Свойство | Тип | Значение по умолчанию | Примечания |
|---|---|---|---|
kotlinCompilerExtensionVersion | String | Версия, совместимая с compose-bom | Должна соответствовать androidx.compose.compiler:compiler. |
useIR | Boolean | true | Использовать 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.ExperimentalNotificationPermission— использование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, abi | enableSplit | Создавать отдельные APK для локалей, плотностей, ABI. |
storeArchive | enable | Сохранять bundle.zip (устарело). |
dex | useLegacyPackaging | AGP 7.1+ — новый формат DEX. |
uploadNativeSymbols | true | Включать отладочные символы в 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
Жизненный цикл
| Событие | ViewModel | Activity/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) | Делегат для получения ViewModel | private 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) |
| Replay | 1 (последнее значение) |
| 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 при переполнении) |
onBufferOverflow | SUSPEND, 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,flatMapLatestlifecycle.repeatOnLifecycle(Lifecycle.State.STARTED)— безопасная подписка
6.4 Room — ORM для SQLite
Аннотации Entity
| Аннотация | Параметры | Описание |
|---|---|---|
@Entity(tableName = "users") | tableName, indices, foreignKeys, ignoreColumns | Определяет таблицу |
@PrimaryKey(autoGenerate = true) | autoGenerate | INTEGER PRIMARY KEY AUTOINCREMENT |
@ColumnInfo(name = "full_name") | name, collate, defaultValue | Маппинг поля → колонки |
@Ignore | — | Игнорировать поле |
@Embedded(prefix = "addr_") | prefix | Встраивание объекта (например, Address → addr_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, @Delete | onConflict | Обновление/удаление по PK |
@Transaction | — | Гарантирует атомарность (на методе DAO или @Query("BEGIN TRANSACTION")) |
@RawQuery | — | Динамические запросы (SupportSQLiteQuery) |
Типы и конвертеры
| Случай | Решение |
|---|---|
List<String> | @TypeConverter → JSON (Gson) или ;-разделённая строка |
Enum | @TypeConverter → String/Int |
Instant, LocalDateTime | @TypeConverter → Long (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 или программно) |
NavDeepLink | Deep link’ы (<deepLink app:uri="myapp://detail/{id}" />) |
NavOptions
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 — использовать отдельные графы или
startDestinationper tab
6.6 Hilt — DI от Google
Иерархия компонентов
| Компонент | Scope | Жизненный цикл |
|---|---|---|
@SingletonComponent | @Singleton | Приложение |
@ViewModelComponent | @HiltViewModel | ViewModel |
@ActivityComponent | @ActivityScoped | Activity |
@FragmentComponent | @FragmentScoped | Fragment |
@ViewComponent | @ViewScoped | View |
@ServiceComponent | @ServiceScoped | Service |
Ключевые аннотации
| Аннотация | Применяется к | Описание |
|---|---|---|
@HiltAndroidApp | Application | Инициализация Hilt |
@AndroidEntryPoint | Activity, Fragment, View, Service | Инъекция зависимостей |
@HiltViewModel | ViewModel | Инъекция в ViewModel |
@InstallIn(SingletonComponent::class) | @Module | Где устанавливать модуль |
@Module | object/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 (рекомендуется для структурированных данных)
- Определить
settings.proto:
syntax = "proto3";
option java_package = "com.example.app";
message Settings {
string user_name = 1;
bool dark_mode = 2;
}
-
Сгенерировать классы (
protobuf-gradle-plugin) -
Использовать:
val Context.settingsDataStore: DataStore<Settings>
get() = dataStore(
fileName = "settings.pb",
serializer = SettingsSerializer
)
| Преимущество | SharedPreferences | DataStore |
|---|---|---|
| Асинхронность | apply() — async, commit() — sync | Только async (Coroutines/Flow) |
| Безопасность | Не thread-safe | thread-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 broadcasts | — | Pending broadcast’ы |
dumpsys activity top | — | Только top Activity |
dumpsys activity packages com.example | — | Информация по конкретному пакету |
Ключевые поля в выводе:
Task id #123→A/B/C— стек Activity (A— bottom,C— top)mResumed=true— Activity в foregroundmStopped=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, Providergids=[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 lock—PARTIAL,FULL,SCREEN_DIM,PROXIMITY_OUTMobile network—Rx/TxбайтыCPU—uAh(microampere-hours)Job—JobSchedulerзадачиSync—ContentResolver.requestSync()
🔋 Best Practice:
- Использовать
adb shell cmd battery unplug→adb shell dumpsys batterystats --resetперед тестом- После теста —
adb shell bugreport→ открыть в Battery Historian
7.2.4 dumpsys meminfo
Что показывает: использование памяти по процессам.
| Подкоманда | Пример | Описание |
|---|---|---|
dumpsys meminfo com.example | — | Детально по процессу |
dumpsys meminfo -a | — | Все процессы |
dumpsys meminfo --oom | — | OOM-адаптация (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 FPSDraw> 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— gonemAnimating=true— идёт анимация
Диагностический паттерн:
🔍 «Dialog не закрывается»
→ dumpsys window windows | grep -A 5 "Dialog" → проверить mAnimating, mViewVisibility.
7.3 am — Activity Manager
| Команда | Пример | Описание | API+ |
|---|---|---|---|
am start -n com.example/.MainActivity | — | Запуск Activity | 1+ |
am start -a android.intent.action.VIEW -d "https://example.com" | — | Implicit intent | 1+ |
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,WorkManageram 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_enabled | 1 | ADB включён |
settings put system screen_brightness 128 | — | Яркость (0–255) |
settings get secure enabled_input_methods | com.example/.Ime | Активные IME |
settings put global hidden_api_policy 1 | — | Доступ к hidden API (1=just warn, 2=allow) |
settings get system font_scale | 1.0 | Масштаб шрифта |
settings list system | — | Все system-настройки |
Полезные ключи:
global:adb_enabled,development_settings_enabled,wifi_on,bluetooth_onsecure:android_id,lock_screen_owner_info_enabled,default_input_methodsystem: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.example | — | AOT-компиляция (ускорение запуска) | 21+ |
cmd package compile -m quicken com.example | — | JIT + профили (Baseline Profiles) | 24+ |
cmd jobscheduler run -f com.example 123 | — | Принудительный запуск Job | 21+ |
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 | — | Сброс batterystats | 21+ |
cmd statusbar disable HOME | — | Скрыть иконки в StatusBar | 24+ |
⚠️
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 3 | — | HOME (см. KeyEvent.KEYCODE_*) |
input keyevent 4 | — | BACK |
input keyevent 82 | — | MENU |
input tap 500 1000 | — | Тап по координатам |
input swipe 100 1000 100 500 300 | — | Свайп (x1,y1,x2,y2,duration_ms) |
input text "Hello" | — | Ввод текста (только ASCII) |
Коды KeyEvent:
3=KEYCODE_HOME4=KEYCODE_BACK26=KEYCODE_POWER24/25=KEYCODE_VOLUME_UP/DOWN82=KEYCODE_MENU66=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/PKCS7Padding | AndroidKeyStore | 23 | Только для SecretKey. GCM — API ≥24 |
AES/GCM/NoPadding | AndroidKeyStore | 24 | Рекомендуется вместо CBC |
RSA/ECB/PKCS1Padding | AndroidKeyStore | 18 | Для PrivateKey (расшифровка/подпись) |
RSA/ECB/OAEPPadding | AndroidKeyStore | 23 | Более безопасная замена PKCS#1 v1.5 |
ECDSA | AndroidKeyStore | 23 | Для PrivateKey (только подпись) |
HMAC-SHA256 | AndroidKeyStore | 23 | Только для 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() | Проверка, контролируется ли аутентификация на уровне TEE | 28 | Важно для 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()
| Параметр | Возможные значения | Примечания |
|---|---|---|
KeyScheme | AES256_GCM | Единственный поддерживаемый. |
PrefKeyEncryptionScheme | AES256_SIV | SIV — synthetic IV, гарантирует детерминизм при одинаковых ключах. |
PrefValueEncryptionScheme | AES256_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.
Этапы интеграции
-
Подключение зависимости
implementation("com.google.android.play:integrity:1.2.0") -
Получение
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 < 16 | ApiException 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. |
Проверка токена на сервере
-
Декодирование JWT Интегрити-токен — это JWT без подписи (unsecured JWT,
alg: none).
ПоляrequestDetails,appIntegrity,deviceIntegrity,accountDetails— вpayload. -
Валидация через 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"
}
}
}
Возможные вердикты
| Группа | Значение | Уровень доверия | Рекомендация |
|---|---|---|---|
appRecognitionVerdict | PLAY_RECOGNIZED | ✅ Высокий | Приложение установлено из Play, подпись совпадает. |
UNRECOGNIZED | ❌ Низкий | Установлено вручную / изменено. | |
UNEVALUATED | ⚠️ Неизвестно | Ошибка или недостаточно данных. | |
deviceRecognitionVerdict | MEETS_STRONG_INTEGRITY | ✅ Очень высокий | Сертифицированное устройство, bootloader locked, TEE, не модифицировано. |
MEETS_BASIC_INTEGRITY | ✅ Средний | Устройство не рутировано, но может быть не сертифицировано (например, пользовательская прошивка без root). | |
MEETS_DEVICE_INTEGRITY | ✅ Умеренный | Устройство не в чёрном списке, но потенциально уязвимо. | |
NO_INTEGRITY | ❌ Низкий | Root, Magisk, Xposed, отладка по USB, bootloader unlocked. | |
appLicensingVerdict | LICENSED | ✅ | Куплено в Play. |
UNLICENSED | ❌ | Не куплено / side-loaded. |
Настройка в Google Play Console
-
Включение API
Play Console → Ваше приложение → Setup → App integrity → Play Integrity API→ Enable. -
Опционально: включение
dexFileDigest
Позволяет проверять целостность DEX-файлов. Увеличивает размер APK (~100–300 КБ).
Включается в той же секции → Dex file integrity. -
Cloud IAM
Сервисному аккаунту нужны роли:roles/playintegrity.deciphererroles/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.xml (в src/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) через
userCA. - Не влияет на 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
Способы настройки
-
Глобально — через
<application><application
android:usesCleartextTraffic="false"
... >Применяется ко всем доменам, если не переопределено в
network-security-config. -
Гибко — через
network-security-config.xml
Имеет приоритет надandroid:usesCleartextTraffic.<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">insecure.example.com</domain>
</domain-config>✅ Рекомендуется: разрешать cleartext только для конкретных доменов, а не глобально.
-
Отладка — через
<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 permitted | targetSdk ≥ 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_SET | 1 << 0 | Разрешение было установлено/изменено пользователем вручную (не по умолчанию). | ❌ (только чтение) |
FLAG_PERMISSION_USER_FIXED | 1 << 1 | Пользователь запретил приложению запрашивать это разрешение снова («Don’t ask again»). | ❌ |
FLAG_PERMISSION_SYSTEM_FIXED | 1 << 2 | Разрешение зафиксировано системой (например, в enterprise-политике). | ❌ |
FLAG_PERMISSION_GRANTED_BY_DEFAULT | 1 << 3 | Разрешение выдано по умолчанию (например, для GET_ACCOUNTS_PRIVILEGED). | ❌ |
FLAG_PERMISSION_REVOKE_ON_UPGRADE | 1 << 4 | Разрешение будет отозвано при обновлении приложения. | ✅ (через requestPermission()) |
FLAG_PERMISSION_REVOKE_WHEN_NOT_IN_USE | 1 << 5 | One-time permission: разрешение активно только пока приложение в foreground. | ✅ (через requestPermission() с ActivityResultContracts.RequestPermission) |
FLAG_PERMISSION_REVOKE_WHEN_APP_UPGRADED | 1 << 6 | Устаревшее (дублирует REVOKE_ON_UPGRADE). | — |
FLAG_PERMISSION_APPLY_RESTRICTION | 1 << 7 | Применено restriction через DevicePolicyManager.setPermissionGrantState(). | ❌ |
✅ — можно инициировать через UI-поток (например,
ActivityResultLauncher.launch()), но не напрямую через API.
8.6.3 One-time permissions («Only this time»)
Пользователь в диалоге requestPermissions() может выбрать:
- ✅ Allow →
FLAG_PERMISSION_USER_SET,FLAG_PERMISSION_USER_FIXED = 0 - 🕒 Only this time → дополнительно устанавливается
FLAG_PERMISSION_REVOKE_WHEN_NOT_IN_USE - ❌ Deny →
FLAG_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.*)
| Уровень | Константа | Требования устройства | Доверие | Примечания |
|---|---|---|---|---|
| Strong | BIOMETRIC_STRONG | — Отпечаток: Class 3 (FIDO2) — Лицо/радужка: Class 3 (3D depth + attention detection) | ✅ Высокое | Разрешено для платежей (например, BiometricPrompt.PromptInfo.setAllowedAuthenticators(BIOMETRIC_STRONG)). |
| Weak | BIOMETRIC_WEAK | — Отпечаток: Class 2 (2D) — Лицо: Class 2 (2D, без attention) | ⚠️ Среднее | Не подходит для CryptoObject, если setUserAuthenticationValidityDurationSeconds > 0. |
| Device Credential | DEVICE_CREDENTIAL | PIN / пароль / графический ключ | ✅ Высокое | Можно комбинировать: 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_WEAK→InvalidAlgorithmParameterException, если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
}