Реализация защиты от записи экрана в мобильном приложении
Запись экрана — более серьёзная угроза, чем скриншот. Она позволяет зафиксировать ввод PIN-кода, процесс авторизации, просмотр документов. На iOS встроенный Screen Recorder и AirPlay-миррoring работают системно. На Android MediaProjection API доступен сторонним приложениям с разрешения пользователя (но вредоносное ПО получает его обманом).
Android: FLAG_SECURE против MediaProjection
FLAG_SECURE — основная защита:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
setContentView(R.layout.activity_main)
}
Флаг блокирует захват через MediaProjection: запись экрана получит чёрный прямоугольник вместо содержимого Activity. Работает для системного рекордера и сторонних приложений, использующих MediaProjection API.
Для Jetpack Compose — тот же подход на уровне Activity, Compose контент находится внутри того же Window.
Ограничение: hardware-based захват (HDMI capture card, физическая камера) не блокируется. Это ограничение всей платформы, не конкретной реализации.
iOS: обнаружение и перекрытие
iOS не предоставляет API для блокировки записи. Но UIScreen.isCaptured и UIScreen.capturedDidChangeNotification — мощный инструмент.
isCaptured возвращает true в следующих ситуациях:
- Активна встроенная запись экрана (Control Center Screen Recording)
- AirPlay-миррoring активен
- QuickTime Player захватывает экран через Lightning/USB
При этом не срабатывает для скриншота одного кадра — это важное отличие от Android FLAG_SECURE.
Правильная реакция — не крэш, не логаут, а скрытие чувствительного контента:
private var cancellables = Set<AnyCancellable>()
func setupScreenCaptureProtection() {
NotificationCenter.default.publisher(
for: UIScreen.capturedDidChangeNotification
)
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.updateContentVisibility()
}
.store(in: &cancellables)
// проверяем текущее состояние при запуске
updateContentVisibility()
}
private func updateContentVisibility() {
let isBeingRecorded = UIScreen.main.isCaptured
sensitiveContainerView.isHidden = isBeingRecorded
if isBeingRecorded {
recordingWarningView.isHidden = false
}
}
recordingWarningView — замена-заглушка, которую пользователь видит при записи вместо данных. Это и защита, и UX-объяснение почему контент скрылся.
SwiftUI вариант
struct SensitiveContentView: View {
@State private var isScreenBeingRecorded = UIScreen.main.isCaptured
var body: some View {
Group {
if isScreenBeingRecorded {
RecordingBlockerView()
} else {
ActualSensitiveContent()
}
}
.onReceive(
NotificationCenter.default.publisher(
for: UIScreen.capturedDidChangeNotification
)
) { _ in
isScreenBeingRecorded = UIScreen.main.isCaptured
}
}
}
Flutter и React Native
Оба кросс-платформенных фреймворка требуют нативных плагинов для этой функциональности.
Flutter: flutter_windowmanager на Android выставляет FLAG_SECURE. Для iOS нужен platform channel с нативной Swift реализацией — готовых надёжных пакетов нет, пишем сами. Платформенные каналы в этом случае — стандарт.
React Native: react-native-flag-secure-android для Android. Для iOS — нативный модуль через NativeModules.
Аудит переключателя задач
Отдельная уязвимость — скриншот, который iOS и Android делают автоматически при переходе приложения в фон (для превью в App Switcher). Этот снимок сохраняется в системном кэше.
iOS: перекрываем оверлейным View в sceneWillResignActive:
func sceneWillResignActive(_ scene: UIScene) {
overlayView.isHidden = false
}
func sceneDidBecomeActive(_ scene: UIScene) {
overlayView.isHidden = true
}
Android: FLAG_SECURE покрывает и этот кейс — App Switcher превью тоже будет чёрным.
Полная реализация защиты от записи экрана на Android + iOS, включая защиту App Switcher превью и обработку состояния isCaptured — 1–3 дня в зависимости от фреймворка и количества экранов с чувствительным контентом.







