Реалізація захисту від запису екрана у мобільному додатку
Запис екрана — більш серйозна угроза, ніж скриншот. Він дозволяє зафіксувати введення PIN-кода, процес авторизації, перегляд документів. На iOS вбудований Screen Recorder та AirPlay-дзеркалювання працюють системно. На Android MediaProjection API доступний сторонніх додатків з дозволу користувача (але вредоноcne ПО отримує його обманом).
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-дзеркалювання активне
- 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
Обидва cross-platform фреймворки вимагають нативних плагінів для цієї функціональності.
Flutter: flutter_windowmanager на Android встановлює FLAG_SECURE. Для iOS потрібен platform channel з нативною Swift реалізацією — готових надійних пакетів немає, пишемо самі. Platform channels у цьому випадку — стандарт.
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 дні залежно від фреймворка та кількості екранів з чутливим контентом.







