From 806c49dbd6b5307942279226d3901f966016c3f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Mon, 25 May 2026 23:18:04 +0400 Subject: [PATCH] doc: android client bugs found and fixed 2026-05-25 --- doc/android-bugs-2026-05-25.md | 110 +++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 doc/android-bugs-2026-05-25.md diff --git a/doc/android-bugs-2026-05-25.md b/doc/android-bugs-2026-05-25.md new file mode 100644 index 0000000..e7243eb --- /dev/null +++ b/doc/android-bugs-2026-05-25.md @@ -0,0 +1,110 @@ +# Android-клиент: баги найденные при ревизии (2026-05-25) + +Коммиты с исправлениями: `d57336a` (предыдущий раунд) → `7f9b83c` (этот раунд). + +--- + +## Баг 1 — КРИТИЧЕСКИЙ: NetworkOnMainThreadException при подключении + +### Что было неверно + +`onStartCommand` вызывался на **главном (UI) потоке**. Из него напрямую вызывались `btConnect()` и `tcpConnect()`. + +Внутри `btConnect()`: +```kotlin +btSocket?.connect() // ← БЛОКИРУЮЩИЙ вызов на main thread +``` + +Внутри `tcpConnect()`: +```kotlin +tcpSocket = Socket(host, port) // ← БЛОКИРУЮЩИЙ вызов на main thread +``` + +### Почему это баг + +Android с версии 3.0 (Honeycomb, API 11) запрещает сетевые операции на главном потоке. Если нарушение — **`NetworkOnMainThreadException`** и краш при запуске. Даже если бы не падало — UI зависал бы на время соединения (ANR через 5 секунд). + +### Как исправлено + +Оба вызова обёрнуты в `thread { }`: + +```kotlin +if (debug != null) { + val p = debug.split(":") + thread(name = "ElmConnect", isDaemon = true) { + tcpConnect(p[0], p.getOrNull(1)?.toIntOrNull() ?: 35000) + } +} else { + intent.getStringExtra(EXTRA_DEVICE_MAC)?.let { mac -> + thread(name = "ElmConnect", isDaemon = true) { btConnect(mac) } + } +} +``` + +`isDaemon = true` — поток не удерживает JVM, умирает вместе с сервисом. + +--- + +## Баг 2 — КРИТИЧЕСКИЙ: TCP-режим слал данные на obdai.ru вместо локального сервера + +### Что было неверно + +В `MainActivity`, в ветке TCP-режима: + +```kotlin +putExtra(ElmForwardService.EXTRA_SERVER_URL, "https://obdai.ru/api/v1/raw-obd") +``` + +URL захардкожен на продакшн-сервер. + +### Почему это баг + +TCP-режим предназначен для **теста без ELM327**: mock ELM327 (`tools/mock_elm327.py`) и Flask-сервер (`web/app.py`) запускаются на одной машине в локальной сети. Поле URL в UI в этом режиме занято адресом mock-устройства (`192.168.X.X:35000`), поэтому у пользователя нет способа указать другой сервер. + +Результат: телефон подключается к локальному mock по TCP, но OBD-данные уходят на `obdai.ru` — тест ничего не проверяет. + +### Как исправлено + +Сервер выводится автоматически из того же хоста, что и устройство, на стандартный порт Flask (5005): + +```kotlin +val deviceHost = debugHost.split(":")[0] +val localServerUrl = "http://$deviceHost:5005/api/v1/raw-obd" +``` + +Итого: ввёл `192.168.1.42:35000` → mock на `:35000`, сервер на `http://192.168.1.42:5005`. + +--- + +## Баг 3 — МАЛЫЙ: символ `\n` попадал в raw-строку + +### Что было неверно + +В `loop()` при накоплении байт из устройства: + +```kotlin +} else if (c != '\r') sb.append(c) +``` + +Фильтровался только `\r`, но не `\n`. + +### Почему это баг + +ELM327 отвечает в формате `DATA\r\n>`. Разделитель ответа — `>`. Код правильно использует `>` как признак конца пакета, но `\n` перед `>` или внутри многострочного ответа накапливается в `sb`. + +Пример: ответ `4 1 05 4B\r\n>` → в буфере будет `4 1 05 4B\n` вместо `4 1 05 4B`. `.trim()` при отправке на сервер снимает крайние `\n`, но если `\n` внутри многострочного ответа — он остаётся. Сервер должен быть устойчив, но лучше не давать мусор. + +### Как исправлено + +```kotlin +} else if (c != '\r' && c != '\n') sb.append(c) +``` + +--- + +## Итог по файлам + +| Файл | Изменение | +|------|-----------| +| `ElmForwardService.kt` | `btConnect`/`tcpConnect` → в `thread {}`, скип `\n` | +| `MainActivity.kt` | TCP-режим → URL из IP устройства + порт 5005 |