atmob_location_client.dart 6.8 KB

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