111 lines
4.6 KiB
Markdown
111 lines
4.6 KiB
Markdown
# 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 |
|