atmob_location_client.dart 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:flutter_map/src/entity/map_location.dart';
  4. import 'package:injectable/injectable.dart';
  5. import 'package:location/data/repositories/friends_repository.dart';
  6. import 'package:location/di/get_it.dart';
  7. import 'package:location/socket/base_message.dart';
  8. import 'package:location/socket/socket_constants.dart';
  9. import 'package:location/utils/async_util.dart';
  10. import 'package:location/utils/atmob_log.dart';
  11. import 'package:location/utils/base_expand.dart';
  12. import 'package:web_socket_channel/web_socket_channel.dart';
  13. import '../data/bean/location_info.dart';
  14. import '../data/repositories/account_repository.dart';
  15. import '../data/repositories/contact_repository.dart';
  16. import '../data/repositories/message_repository.dart';
  17. import 'location_message.dart';
  18. typedef OnLocationChangeListener = void Function(List<LocationInfo> data);
  19. @lazySingleton
  20. class AtmobLocationClient {
  21. static final String tag = 'AtmobLocationClient';
  22. static final List<OnLocationChangeListener> _locationListeners = [];
  23. WebSocketChannel? _webSocket;
  24. StreamSubscription? _subscription;
  25. bool _isConnecting = false;
  26. CancelableFuture? cancelableFuture;
  27. AtmobLocationClient() {
  28. startWebsocketInternal();
  29. }
  30. static AtmobLocationClient getAtmobLocationClient() {
  31. return getIt.get<AtmobLocationClient>();
  32. }
  33. static void connectWebSocket() {
  34. AtmobLog.d(tag, 'connectWebSocket');
  35. AtmobLocationClient client = getIt.get<AtmobLocationClient>();
  36. client.startWebsocketInternal();
  37. }
  38. static void disConnectWebSocket() {
  39. AtmobLog.d(tag, 'disConnectWebSocket');
  40. AtmobLocationClient client = getIt.get<AtmobLocationClient>();
  41. client.stopWebsocketInternal();
  42. }
  43. void startWebsocketInternal() {
  44. if (AccountRepository.token == null ||
  45. AccountRepository.token?.isEmpty == true ||
  46. _isConnecting) {
  47. return;
  48. }
  49. cancelableFuture?.cancel();
  50. cancelableFuture = AsyncUtil.retryWithExponentialBackoff(
  51. () => _startConnect(), 4,
  52. initialInterval: Duration(seconds: 2));
  53. cancelableFuture!.catchError((error) {
  54. AtmobLog.d(tag, '重试最大次数 异常 error:$error');
  55. startWebsocketInternal();
  56. });
  57. }
  58. void stopWebsocketInternal() {
  59. AtmobLog.d(tag, 'stopWebsocketInternal');
  60. cancelableFuture?.cancel();
  61. _disposePreviousConnection();
  62. }
  63. void _disposePreviousConnection() {
  64. _webSocket?.sink.close();
  65. _webSocket = null;
  66. _subscription?.cancel();
  67. _subscription = null;
  68. _isConnecting = false;
  69. }
  70. Future<void> _startConnect() async {
  71. AtmobLog.d(tag, '_startConnect');
  72. _disposePreviousConnection();
  73. final webSocket = WebSocketChannel.connect(Uri.parse(
  74. '${SocketConstants.locationBaseUrl}${AccountRepository.token}'));
  75. _webSocket = webSocket;
  76. try {
  77. await webSocket.ready;
  78. AtmobLog.d(tag, 'webSocket 连接成功');
  79. _isConnecting = true;
  80. } catch (e) {
  81. AtmobLog.d(tag, 'webSocket 连接失败 error:$e');
  82. rethrow;
  83. }
  84. _subscription = webSocket.stream
  85. .map((s) {
  86. AtmobLog.d(tag, 'webSocket receive:$s');
  87. try {
  88. Map<String, dynamic> data = jsonDecode(s);
  89. return BaseMessage.fromJson(data);
  90. } catch (e) {
  91. AtmobLog.d(tag, 'BaseMessage jsonDecode error:$e');
  92. }
  93. return null;
  94. })
  95. .where((message) {
  96. if (message == null || message.cmd == null) {
  97. return false;
  98. }
  99. switch (message.cmd) {
  100. case SocketConstants.refreshFriendList:
  101. FriendsRepository.getInstance().refreshFriends();
  102. break;
  103. case SocketConstants.refreshFriendRequest:
  104. MessageRepository.getInstance().refreshFriendWaitingCount();
  105. break;
  106. case SocketConstants.refreshFriendMessage:
  107. MessageRepository.getInstance().refreshUnreadMessage();
  108. break;
  109. case SocketConstants.refreshContact:
  110. ContactRepository.getInstance().refreshContactList();
  111. break;
  112. case SocketConstants.refreshMember:
  113. AccountRepository.getInstance().refreshMemberStatus();
  114. break;
  115. }
  116. return SocketConstants.receiveFriendBatchLocation == message.cmd;
  117. })
  118. .map((message) => message?.data)
  119. .where((data) => data != null)
  120. .cast<String>()
  121. .map((s) {
  122. try {
  123. List<dynamic> jsonList = jsonDecode(s);
  124. return jsonList.map((e) => LocationInfo.fromJson(e)).toList();
  125. } catch (e) {
  126. AtmobLog.d(tag, 'List<LocationInfo> jsonDecode error:$e');
  127. }
  128. })
  129. .bufferTime(Duration(seconds: 5))
  130. .where((locationInfos) => locationInfos.isNotEmpty)
  131. .map((lists) {
  132. Map<String, LocationInfo> idLocation = {};
  133. for (var list in lists) {
  134. if (list == null || list.isEmpty) {
  135. continue;
  136. }
  137. for (var location in list) {
  138. String? userId = location.userId;
  139. if (userId == null ||
  140. location.longitude == 0 ||
  141. location.latitude == 0) {
  142. continue;
  143. }
  144. idLocation[userId] = location;
  145. }
  146. }
  147. return idLocation.values.toList();
  148. })
  149. .listen(
  150. _handleLocationMessage,
  151. onError: _handleError,
  152. onDone: _handleDisconnect,
  153. );
  154. }
  155. void _handleLocationMessage(List<LocationInfo> data) {
  156. AtmobLog.d(tag, '接收到位置信息: ${data.length}');
  157. for (var listener in _locationListeners) {
  158. listener(data);
  159. }
  160. }
  161. // 断开处理
  162. void _handleDisconnect() {
  163. AtmobLog.e(tag, 'WebSocket 断开连接');
  164. _isConnecting = false;
  165. startWebsocketInternal();
  166. }
  167. void _handleError(error) {
  168. AtmobLog.e(tag, 'WebSocket 错误: $error');
  169. }
  170. static void addLocationListener(OnLocationChangeListener listener) {
  171. _locationListeners.add(listener);
  172. }
  173. static void removeLocationListener(OnLocationChangeListener listener) {
  174. _locationListeners.remove(listener);
  175. }
  176. void _sendMessage(String msg) {
  177. if (_webSocket == null) {
  178. return;
  179. }
  180. _webSocket!.sink.add(msg);
  181. AtmobLog.d(tag, 'send location: $msg');
  182. }
  183. void uploadLocation(MapLocation location) {
  184. if (_webSocket == null ||
  185. location.latitude == 0 && location.longitude == 0) {
  186. return;
  187. }
  188. _sendMessage(LocationMessage.obtainMessage(location));
  189. }
  190. }