Реализация Shake-to-Report (отправка бага встряхиванием) в мобильном приложении
Shake-to-Report — это паттерн, при котором пользователь или тестировщик встряхивает устройство и получает форму отправки бага с автоматически захваченным скриншотом и диагностической информацией. Внутри команды — удобнее TestFlight feedback. Для beta-тестеров — намного ниже порог входа, чем заполнять форму вручную.
Детектирование встряхивания
iOS
// UIWindow subclass для перехвата shake
class FeedbackWindow: UIWindow {
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
FeedbackManager.shared.presentFeedbackForm()
}
super.motionEnded(motion, with: event)
}
}
// Использование в SceneDelegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
window = FeedbackWindow(windowScene: windowScene)
window?.rootViewController = UIHostingController(rootView: ContentView())
window?.makeKeyAndVisible()
}
}
Переопределение UIWindow — стандартный подход, не требует SwiftUI-specific кода и работает с любой архитектурой.
Android — акселерометр
На Android нет встроенного события «shake» — детектируем через акселерометр:
class ShakeDetector(private val onShake: () -> Unit) : SensorEventListener {
private val SHAKE_THRESHOLD_GRAVITY = 2.7f
private val SHAKE_SLOP_TIME_MS = 500
private var lastShakeMs: Long = 0
override fun onSensorChanged(event: SensorEvent) {
val gX = event.values[0] / SensorManager.GRAVITY_EARTH
val gY = event.values[1] / SensorManager.GRAVITY_EARTH
val gZ = event.values[2] / SensorManager.GRAVITY_EARTH
val gForce = sqrt(gX * gX + gY * gY + gZ * gZ.toDouble()).toFloat()
if (gForce > SHAKE_THRESHOLD_GRAVITY) {
val now = System.currentTimeMillis()
if (lastShakeMs + SHAKE_SLOP_TIME_MS > now) return
lastShakeMs = now
onShake()
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
// Регистрация в Activity/Fragment
val sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
val shakeDetector = ShakeDetector { showFeedbackDialog() }
sensorManager.registerListener(
shakeDetector,
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_UI
)
SHAKE_SLOP_TIME_MS = 500 предотвращает многократный вызов от одного встряхивания. Threshold 2.7g — компромисс между чувствительностью и ложными срабатываниями при ходьбе.
Контекст, который собирается автоматически
При встряхивании до показа UI:
data class BugReport(
val screenshot: Bitmap,
val appVersion: String = BuildConfig.VERSION_NAME,
val buildNumber: String = BuildConfig.VERSION_CODE.toString(),
val osVersion: String = "Android ${Build.VERSION.RELEASE}",
val device: String = "${Build.MANUFACTURER} ${Build.MODEL}",
val currentScreen: String = screenTracker.currentScreenName,
val recentLogs: List<String> = LogBuffer.getLast(50), // последние 50 строк лога
val memoryInfo: String = getMemoryInfo(),
val networkType: String = getNetworkType()
)
recentLogs — если в приложении настроен in-memory log buffer (Timber tree, записывающий в кольцевой буфер), баг-репорт сразу содержит последние события. Разработчик видит всё, что происходило за 30 секунд до встряхивания.
Режимы доступности
Shake неудобен для пользователей с тремором или тех, кто держит устройство на столе. Для QA и beta-программ добавляем альтернативные триггеры:
- Долгое нажатие на логотип или версию в «О приложении»
- Скрытое меню через тройной тап по пустой области экрана
- Жест двумя пальцами (3 пальца, 3 тапа)
Shake обычно отключают в production или делают опциональным через developer settings — чтобы обычный пользователь не открывал форму случайно.
Готовые инструменты
| Инструмент | Платформы | Особенности |
|---|---|---|
| Instabug | iOS, Android, Flutter, RN | Shake + screen recording, Jira/Slack интеграции |
| Shake.io | iOS, Android | Встроенный тред-модель обсуждения |
| BugShaker | iOS (open source) | Простой, email-only |
| Собственная реализация | Любые | Полный контроль, без внешних зависимостей |
Instabug — промышленный стандарт для мобильных QA-команд. Собственная реализация оправдана, если нужно контролировать, какие данные покидают устройство.
Ориентиры по срокам
Кастомная реализация shake-детектора с захватом скриншота и отправкой в Jira — 3–5 дней. Интеграция Instabug с кастомной темой и настройкой маршрутизации репортов — 1–2 дня.







