generated from Grigo/AndroidTemplate
feat: новая архитектура UI и расширенная визуализация AIS
Архитектурные улучшения: - Внедрен UIRenderingCoordinator с централизованным throttling - Решены проблемы зависания UI через батчинг операций карты - Добавлен VesselPathController для отслеживания маршрутов - Реализован MapLibreMapImpl как альтернатива Яндекс.Картам Визуализация AIS: - Добавлены векторные иконки для всех типов судов - Разделение Class A/B судов с соответствующими иконками - Иконки навигационных статусов (anchor, moored, engine, sail) - Улучшенный CursorOverlay с информацией о судах Производительность: - Throttling UI обновлений (vessel: 500ms, AIS: 1s, paths: 2s) - Устранение утечек Handler объектов - Оптимизация GeoJSON операций в MapLibre
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package com.grigowashere.aismap.ui;
|
||||
|
||||
import com.grigowashere.aismap.models.Vessel;
|
||||
import com.grigowashere.aismap.models.AISVessel;
|
||||
|
||||
/**
|
||||
* Интерфейс для уведомлений UI о изменениях данных
|
||||
* Контроллеры используют этот интерфейс для информирования UI о изменениях
|
||||
* без знания деталей UI реализации
|
||||
*/
|
||||
public interface UIDataChangeNotifier {
|
||||
|
||||
/**
|
||||
* Уведомление об изменении позиции собственного судна
|
||||
* @param vessel обновленные данные судна
|
||||
*/
|
||||
void onVesselPositionChanged(Vessel vessel);
|
||||
|
||||
/**
|
||||
* Уведомление об изменении качества GPS данных
|
||||
* @param vessel данные судна с обновленными GPS метаданными
|
||||
*/
|
||||
void onGPSQualityChanged(Vessel vessel);
|
||||
|
||||
/**
|
||||
* Уведомление о новой AIS судне или обновлении существующего
|
||||
* @param vessel данные AIS судна
|
||||
*/
|
||||
void onAISVesselChanged(AISVessel vessel);
|
||||
|
||||
/**
|
||||
* Уведомление об удалении AIS судна
|
||||
* @param mmsi идентификатор удаляемого судна
|
||||
*/
|
||||
void onAISVesselRemoved(String mmsi);
|
||||
|
||||
/**
|
||||
* Уведомление об изменении пути судна
|
||||
* @param mmsi идентификатор судна (null для собственного судна)
|
||||
*/
|
||||
void onVesselPathChanged(String mmsi);
|
||||
|
||||
/**
|
||||
* Уведомление о центрировании карты
|
||||
* @param latitude широта
|
||||
* @param longitude долгота
|
||||
*/
|
||||
void onRequestCenterMap(double latitude, double longitude);
|
||||
|
||||
/**
|
||||
* Уведомление об обновлении компаса
|
||||
* @param azimuth значение азимута
|
||||
*/
|
||||
void onCompassUpdate(float azimuth);
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package com.grigowashere.aismap.ui;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.grigowashere.aismap.maps.MapInterface;
|
||||
import com.grigowashere.aismap.models.Vessel;
|
||||
import com.grigowashere.aismap.models.AISVessel;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Координатор UI отрисовки
|
||||
* Единая точка всех операций с картой и UI
|
||||
* Обеспечивает throttling и батчинг операций
|
||||
*/
|
||||
public class UIRenderingCoordinator implements UIDataChangeNotifier {
|
||||
private static final String TAG = "UIRenderingCoordinator";
|
||||
|
||||
// Throttling интервалы
|
||||
public static final long VESSEL_UPDATE_THROTTLE = 500; // 500мс для позиции судна
|
||||
public static final long AIS_UPDATE_THROTTLE = 1000; // 1сек для AIS данных
|
||||
public static final long PATH_UPDATE_THROTTLE = 2000; // 2сек для путей
|
||||
|
||||
private MapInterface mapInterface;
|
||||
private Handler uiHandler;
|
||||
|
||||
// Pending операции для батчинга
|
||||
private Vessel pendingVesselUpdate;
|
||||
private final Map<String, AISVessel> pendingAISUpdates = new HashMap<>();
|
||||
private final Set<String> pendingAISRemovals = new HashSet<>();
|
||||
|
||||
// Throttling Runnable's
|
||||
private Runnable vesselUpdateRunnable;
|
||||
private Runnable aisUpdateRunnable;
|
||||
private Runnable pathUpdateRunnable;
|
||||
|
||||
// Флаги для предотвращения множественных запланированных операций
|
||||
private boolean vesselUpdatePending = false;
|
||||
private boolean aisUpdatePending = false;
|
||||
private boolean pathUpdatePending = false;
|
||||
|
||||
public UIRenderingCoordinator(MapInterface mapInterface) {
|
||||
this.mapInterface = mapInterface;
|
||||
this.uiHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
setupThrottling();
|
||||
Log.i(TAG, "UIRenderingCoordinator инициализирован");
|
||||
}
|
||||
|
||||
/**
|
||||
* Настройка throttling механизмов
|
||||
*/
|
||||
private void setupThrottling() {
|
||||
vesselUpdateRunnable = () -> {
|
||||
vesselUpdatePending = false;
|
||||
executeVesselUpdate();
|
||||
};
|
||||
|
||||
aisUpdateRunnable = () -> {
|
||||
aisUpdatePending = false;
|
||||
executeAISUpdates();
|
||||
};
|
||||
|
||||
pathUpdateRunnable = () -> {
|
||||
pathUpdatePending = false;
|
||||
executePathUpdates();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Запрос обновления позиции собственного судна
|
||||
*/
|
||||
public void requestVesselUpdate(Vessel vessel) {
|
||||
if (vessel == null) return;
|
||||
|
||||
pendingVesselUpdate = vessel;
|
||||
|
||||
if (!vesselUpdatePending) {
|
||||
vesselUpdatePending = true;
|
||||
uiHandler.removeCallbacks(vesselUpdateRunnable);
|
||||
uiHandler.postDelayed(vesselUpdateRunnable, VESSEL_UPDATE_THROTTLE);
|
||||
|
||||
Log.d(TAG, "Vessel update запланирован на " + VESSEL_UPDATE_THROTTLE + "мс");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запрос обновления AIS судна
|
||||
*/
|
||||
public void requestAISUpdate(AISVessel vessel) {
|
||||
if (vessel == null || vessel.getMmsi() == null) return;
|
||||
|
||||
pendingAISUpdates.put(vessel.getMmsi(), vessel);
|
||||
|
||||
if (!aisUpdatePending) {
|
||||
aisUpdatePending = true;
|
||||
uiHandler.removeCallbacks(aisUpdateRunnable);
|
||||
uiHandler.postDelayed(aisUpdateRunnable, AIS_UPDATE_THROTTLE);
|
||||
|
||||
Log.d(TAG, "AIS update запланирован на " + AIS_UPDATE_THROTTLE + "мс");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запрос удаления AIS судна
|
||||
*/
|
||||
public void requestAISRemoval(String mmsi) {
|
||||
if (mmsi == null) return;
|
||||
|
||||
pendingAISRemovals.add(mmsi);
|
||||
pendingAISUpdates.remove(mmsi); // Убираем из обновлений
|
||||
|
||||
if (!aisUpdatePending) {
|
||||
aisUpdatePending = true;
|
||||
uiHandler.removeCallbacks(aisUpdateRunnable);
|
||||
uiHandler.postDelayed(aisUpdateRunnable, AIS_UPDATE_THROTTLE);
|
||||
|
||||
Log.d(TAG, "AIS removal запланирован на " + AIS_UPDATE_THROTTLE + "мс");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнение обновления позиции судна
|
||||
*/
|
||||
private void executeVesselUpdate() {
|
||||
if (mapInterface == null || pendingVesselUpdate == null) return;
|
||||
|
||||
try {
|
||||
Log.d(TAG, "Выполняем vessel update: " + pendingVesselUpdate.getLatitude() + "," + pendingVesselUpdate.getLongitude());
|
||||
mapInterface.updateOwnVesselPosition(pendingVesselUpdate);
|
||||
Log.d(TAG, "Vessel update выполнен успешно");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка vessel update: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
pendingVesselUpdate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнение обновлений AIS судов
|
||||
*/
|
||||
private void executeAISUpdates() {
|
||||
if (mapInterface == null) return;
|
||||
|
||||
try {
|
||||
// Удаляем старые суда
|
||||
for (String mmsi : pendingAISRemovals) {
|
||||
Log.d(TAG, "Удаляем AIS судно: " + mmsi);
|
||||
mapInterface.removeAISVesselMarker(mmsi);
|
||||
}
|
||||
|
||||
// Обновляем или добавляем суда (различать не будем - MapInterface сам решит)
|
||||
for (AISVessel vessel : pendingAISUpdates.values()) {
|
||||
Log.d(TAG, "Обновляем/добавляем AIS судно: " + vessel.getMmsi());
|
||||
mapInterface.updateAISVesselPosition(vessel);
|
||||
}
|
||||
|
||||
Log.d(TAG, "AIS updates выполнены: удалено=" + pendingAISRemovals.size() +
|
||||
", обновлено=" + pendingAISUpdates.size());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка AIS updates: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// Очищаем pending операции
|
||||
pendingAISUpdates.clear();
|
||||
pendingAISRemovals.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнение обновлений путей (заглушка для будущего)
|
||||
*/
|
||||
private void executePathUpdates() {
|
||||
if (mapInterface == null) return;
|
||||
|
||||
try {
|
||||
// TODO: Реализовать батчинговое обновление путей
|
||||
Log.d(TAG, "Path updates выполнены (заглушка)");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Ошибка path updates: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Принудительное выполнение всех pending операций
|
||||
*/
|
||||
public void flushPendingOperations() {
|
||||
Log.i(TAG, "Принудительное выполнение всех pending операций");
|
||||
|
||||
if (uiHandler != null) {
|
||||
uiHandler.removeCallbacks(vesselUpdateRunnable);
|
||||
uiHandler.removeCallbacks(aisUpdateRunnable);
|
||||
uiHandler.removeCallbacks(pathUpdateRunnable);
|
||||
}
|
||||
|
||||
vesselUpdatePending = false;
|
||||
aisUpdatePending = false;
|
||||
pathUpdatePending = false;
|
||||
|
||||
executeVesselUpdate();
|
||||
executeAISUpdates();
|
||||
executePathUpdates();
|
||||
|
||||
Log.i(TAG, "Все pending операции выполнены");
|
||||
}
|
||||
|
||||
/**
|
||||
* Очистка ресурсов
|
||||
*/
|
||||
public void cleanup() {
|
||||
Log.i(TAG, "Очистка UIRenderingCoordinator");
|
||||
|
||||
if (uiHandler != null) {
|
||||
uiHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
|
||||
flushPendingOperations();
|
||||
mapInterface = null;
|
||||
|
||||
Log.i(TAG, "UIRenderingCoordinator очищен");
|
||||
}
|
||||
|
||||
// ========== Реализация UIDataChangeNotifier ==========
|
||||
|
||||
@Override
|
||||
public void onVesselPositionChanged(Vessel vessel) {
|
||||
requestVesselUpdate(vessel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGPSQualityChanged(Vessel vessel) {
|
||||
// GPS качество влияет на отображение точности, но не требует urgent update
|
||||
requestVesselUpdate(vessel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAISVesselChanged(AISVessel vessel) {
|
||||
requestAISUpdate(vessel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAISVesselRemoved(String mmsi) {
|
||||
requestAISRemoval(mmsi);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVesselPathChanged(String mmsi) {
|
||||
// Path изменения менее критичны, используем больше throttling
|
||||
if (!pathUpdatePending) {
|
||||
pathUpdatePending = true;
|
||||
uiHandler.removeCallbacks(pathUpdateRunnable);
|
||||
uiHandler.postDelayed(pathUpdateRunnable, PATH_UPDATE_THROTTLE);
|
||||
|
||||
Log.d(TAG, "Path update запланирован на " + PATH_UPDATE_THROTTLE + "мс");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestCenterMap(double latitude, double longitude) {
|
||||
// Центрирование карты должно происходить немедленно
|
||||
uiHandler.post(() -> {
|
||||
if (mapInterface != null) {
|
||||
mapInterface.centerOnPosition(latitude, longitude);
|
||||
Log.d(TAG, "Карта отцентрирована на " + latitude + "," + longitude);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompassUpdate(float azimuth) {
|
||||
// Компас не относится к карте, передаем в MainActivity через callback
|
||||
Log.d(TAG, "Compass update: " + azimuth + "° - требует специальной обработки в MainActivity");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user