Зачем backend-инженеру знать Android
При проектировании систем важно понимать клиентскую сторону. Android -- самая распространённая мобильная ОС (72%+ рынка). Знание её архитектуры помогает:
- Проектировать API, учитывающие ограничения мобильных клиентов
- Понимать, почему мобильные клиенты ведут себя иначе (батарея, сеть, lifecycle)
- Принимать решения об offline-first архитектуре
- Оптимизировать протоколы для мобильных сетей
Android Software Stack
Android построен на ядре Linux с несколькими дополнительными уровнями.
┌─────────────────────────────────┐
│ System Apps │ Телефон, Контакты, Камера
├─────────────────────────────────┤
│ Application Framework │ Activity Manager, Content Providers
├─────────────────────────────────┤
│ Android Runtime │ Native Libs │ ART, libc, OpenGL, SQLite
├─────────────────────────────────┤
│ Hardware Abstraction Layer │ Camera HAL, Audio HAL
├─────────────────────────────────┤
│ Linux Kernel │ Drivers, Binder, Power Mgmt
└─────────────────────────────────┘
Уровни стека
| Уровень | Компоненты | Аналогия в backend |
|---|---|---|
| Applications | Пользовательские приложения | Клиентские приложения |
| Framework | Activity, Service, ContentProvider | Application framework (Symfony) |
| Runtime | ART (Android Runtime) | PHP Runtime / Go Runtime |
| Native Libraries | libc, SQLite, OpenSSL | Системные библиотеки |
| HAL | Абстракция аппаратуры | Device drivers |
| Kernel | Linux kernel | ОС сервера |
ART (Android Runtime)
От Dalvik к ART
Android использовал JIT-компиляцию (Dalvik VM), а затем перешёл на AOT + JIT (ART).
| Параметр | Dalvik (старый) | ART (современный) |
|---|---|---|
| Компиляция | JIT (Just-In-Time) | AOT + JIT (гибрид) |
| Установка приложения | Быстрая | Медленнее (компиляция) |
| Запуск приложения | Медленнее | Быстрее |
| RAM | Больше (JIT кэш) | Оптимизирован |
| Garbage Collection | Stop-the-world | Concurrent, generational |
Garbage Collection в ART
ART использует generational GC, разделяющий объекты на поколения:
- Young generation: короткоживущие объекты (часто собираются)
- Old generation: долгоживущие объекты (редко собираются)
- Large Object Space: большие объекты (отдельный пул)
Аналогия с PHP: PHP использует reference counting + cycle collector. В PHP каждый запрос -- это "поколение": в конце запроса вся память освобождается. Это проще, чем generational GC, но не подходит для долгоживущих процессов.
Процессы в Android
Модель процессов
Каждое Android-приложение работает в отдельном процессе Linux с собственным UID. Это обеспечивает изоляцию -- одно приложение не может читать память другого.
App A (UID 10001) ──── Process A (Dalvik/ART VM)
App B (UID 10002) ──── Process B (Dalvik/ART VM)
App C (UID 10003) ──── Process C (Dalvik/ART VM)
│
Linux Kernel (общее ядро)
Приоритеты процессов
Android агрессивно управляет процессами для экономии батареи и памяти:
| Приоритет | Описание | Когда убивается |
|---|---|---|
| Foreground | Активно используется | Последний (только при критическом OOM) |
| Visible | Виден, но не в фокусе | При нехватке памяти |
| Service | Фоновый сервис | При значительной нехватке |
| Background | В фоне, не активен | Первый кандидат |
| Empty | Нет активных компонентов | Первый убивается |
Для System Design: Мобильное приложение может быть убито ОС в любой момент. API должен быть idempotent, поддерживать retry, и данные должны синхронизироваться корректно после восстановления.
IPC (Inter-Process Communication)
Binder
Binder -- это основной механизм IPC в Android. Это высокопроизводительный RPC-механизм, реализованный как драйвер ядра.
App Process System Server
│ │
├── Binder Proxy ──► Binder Stub ──► Service
│ (клиент) (сервер)
│ │
└────── Binder Driver (kernel) ──────┘
Особенности Binder:
- Один копирование данных (вместо двух в обычных pipe/socket)
- Встроенная аутентификация (UID/PID вызывающего)
- Поддержка передачи файловых дескрипторов
- Синхронные вызовы (блокирующие)
Другие механизмы IPC
| Механизм | Использование | Аналогия в backend |
|---|---|---|
| Binder | Вызовы системных сервисов | gRPC/REST API |
| Intent | Запуск Activity/Service | Message Queue |
| ContentProvider | Доступ к данным между приложениями | REST API для данных |
| Broadcast | Широковещательные уведомления | Pub/Sub (Redis, RabbitMQ) |
| Shared Memory (Ashmem) | Быстрый обмен большими данными | Shared Memory / mmap |
Activity Lifecycle
Жизненный цикл Activity
onCreate() → onStart() → onResume() → [Активна]
│
onPause() → onStop() → onDestroy()
│ │
│ onRestart() → onStart()
│
[Частично видна]
Влияние на проектирование API
Жизненный цикл Android создаёт специфические требования к backend:
<?php
declare(strict_types=1);
/**
* API design considerations for mobile clients.
* Mobile apps can be killed and restarted at any moment.
*/
final class MobileAwareApiController
{
public function __construct(
private readonly SyncService $syncService,
private readonly IdempotencyService $idempotency,
) {}
/**
* Idempotent endpoint for mobile clients.
* Mobile app may retry requests if it was killed mid-request.
*
* @param string $idempotencyKey Client-generated unique key
* @param array<string, mixed> $payload
* @return array{status: string, data: mixed}
*/
public function createOrder(
string $idempotencyKey,
array $payload,
): array {
// Check if this request was already processed
$existing = $this->idempotency->find($idempotencyKey);
if ($existing !== null) {
// Return same response without re-processing
return ['status' => 'ok', 'data' => $existing];
}
// Process the order
$order = $this->processOrder($payload);
// Store result with idempotency key
$this->idempotency->store($idempotencyKey, $order, ttlSeconds: 86400);
return ['status' => 'created', 'data' => $order];
}
/**
* Delta sync endpoint for mobile clients.
* Instead of fetching all data, client sends last sync timestamp.
* Returns only changes since that timestamp.
*/
public function syncData(
string $userId,
string $lastSyncAt,
): array {
$changes = $this->syncService->getChangesSince(
userId: $userId,
since: new \DateTimeImmutable($lastSyncAt),
);
return [
'created' => $changes['created'],
'updated' => $changes['updated'],
'deleted' => $changes['deleted_ids'],
'sync_timestamp' => (new \DateTimeImmutable())->format('c'),
];
}
}
Батарея
Android ограничивает фоновую активность для экономии батареи:
- Doze Mode: ограничивает сеть, sync, alarms в режиме простоя
- App Standby: приложения в фоне получают меньше ресурсов
- Background Execution Limits: ограничения на фоновые сервисы
Сеть
Мобильная сеть принципиально отличается от проводной:
| Параметр | Wi-Fi | 4G LTE | 3G |
|---|---|---|---|
| Латентность | 1-10 мс | 30-100 мс | 100-500 мс |
| Пропускная способность | 100+ Мбит/с | 10-100 Мбит/с | 1-5 Мбит/с |
| Стабильность | Высокая | Средняя | Низкая |
| Энергопотребление | Низкое | Высокое | Очень высокое |
Рекомендации для API
- Batch API: объединять несколько запросов в один
- Compression: gzip/brotli для уменьшения трафика
- Pagination: не отдавать все данные разом
- Delta sync: отдавать только изменения
- Offline-first: клиент работает без сети, синхронизация при подключении
- Idempotent APIs: безопасные повторные запросы
<?php
declare(strict_types=1);
/**
* Batch API endpoint to reduce number of network requests.
* Mobile clients benefit from fewer, larger requests.
*/
final class BatchApiController
{
/**
* Process multiple API calls in a single HTTP request.
* Reduces latency overhead from multiple round-trips.
*
* @param array<int, array{method: string, path: string, body?: array}> $requests
* @return array<int, array{status: int, body: mixed}>
*/
public function batch(array $requests): array
{
if (count($requests) > 20) {
throw new \InvalidArgumentException('Maximum 20 requests per batch');
}
$responses = [];
foreach ($requests as $index => $request) {
$responses[$index] = $this->processSubRequest(
method: $request['method'],
path: $request['path'],
body: $request['body'] ?? [],
);
}
return $responses;
}
/**
* @return array{status: int, body: mixed}
*/
private function processSubRequest(
string $method,
string $path,
array $body,
): array {
// Route sub-request to appropriate handler
// Each sub-request runs independently
try {
$result = $this->router->dispatch($method, $path, $body);
return ['status' => 200, 'body' => $result];
} catch (\Throwable $e) {
return ['status' => 500, 'body' => ['error' => $e->getMessage()]];
}
}
}
Firebase Cloud Messaging (FCM) -- стандартный механизм push-уведомлений для Android.
Backend → FCM Server → Google Play Services → Приложение
Особенности:
- Размер payload: до 4 КБ
- Гарантия доставки: at-least-once (может дублироваться)
- Батарея: эффективнее polling, использует общий канал
- Rate limits: зависят от приоритета сообщения
Для System Design: Push-уведомления -- это не надёжный канал доставки. Они могут не дойти (Doze mode, нет сети) или дублироваться. Для критичных данных используйте pull-based синхронизацию в дополнение к push.
Выводы
- Android построен на Linux, но с существенными модификациями (Binder, ART, HAL)
- Каждое приложение изолировано в своём процессе и UID
- ОС может убить приложение в любой момент -- API должен быть idempotent
- Мобильная сеть нестабильна -- нужны retry, delta sync, batch API
- Push-уведомления -- at-least-once, не гарантируют доставку
- Понимание клиентских ограничений помогает проектировать правильные API