Сокет-клиент в сервисе и обновление UI через BroadcastReceiver

в 10:53, , рубрики: android, Разработка под android, метки:

Задача:
Сделать клиент-приложение «на сокетах», так чтобы при закрытии главного активити работа продолжалась и соединение не терялось.

Решение:
Это возможно сделать с помощью сервисов в андроиде, о том что такое сервис и как он работает написано много статей, по этому я не буду вдаваться в подробности и приступлю к реализации.

Наше приложение состоит из 3 самых главных классов это:

  • MainActivity — активити в котором будем видеть отображение работы нашего сокет-клиента
  • ServiceExchange — собственно сам сервис
  • SocketAsync — наш асинхронный сокет-клиент


MainActivity:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
        this.startService(new Intent(this, ServiceExchange.class)); // Запуск сервиса
    }
}

private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
                onDataFromService(intent);
        }
};

public void onResume() {
        super.onResume();
        registerReceiver(broadcastReceiver, new IntentFilter(ServiceExchange.BROADCAST_ACTION));
}

@Override
public void onPause() {
        super.onPause();
        unregisterReceiver(broadcastReceiver);
}

public void onDataFromService(Intent intent) {
       Log.d("onDataFromService", intent.getStringExtra("text"));
}

Здесь мы запускаем наш сервис, и запускам слушателя «broadcastReceiver» который будет нам передавать сообщения из сервиса в функцию onDataFromService.
ServiceExchange.BROADCAST_ACTION обязательно нужно указывать тег наших сообщение иначе они будут теряться.

ServiceExchange:

public class ServiceExchange extends Service {

	private Intent intent;
	public static final String BROADCAST_ACTION = "com.rheit.base.event"; // Наш тег сообщений

	@Override
	public void onCreate() {
		super.onCreate();
		intent = new Intent(BROADCAST_ACTION);

		createConnect();
	}

	Handler myUpdateHandler = new Handler() {
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case config.ERR_SOCKET_SERVER_UNAVAILABLE: // Сервер не доступен
				createConnect(); // Переподключаемся
				sendBroadcast((Intent) msg.obj); // Посылаем сообшение в UI
				break;

			case config.CODE_SOCKET_SERVER_ANSWER: // Пришел ответ от сервера
				// Некие операции с данными но мы просто
				sendBroadcast((Intent) msg.obj); // Посылаем сообшение в UI
				break;

			case config.CODE_SOCKET_SERVER_CONNECTED: // Соединился с сервером успешно
				// Некие операции с данными но мы просто
				sendBroadcast((Intent) msg.obj); // Посылаем сообшение в UI
				break;
			default:
				break;
			}
			super.handleMessage(msg);
		}
	};

	@Override
	public IBinder onBind(Intent arg0) {
		Log.v(this.getClass().getName(), "---> Service binded.");
		return null;
	}

	@Override
	public void onDestroy() {
		Toast.makeText(this, "Service destoyed", Toast.LENGTH_LONG).show();
		Log.v(this.getClass().getName(), "Service destoyed.");
	}

	private void createConnect() {
		if (ServerTask == null
				|| ServerTask.getStatus().equals(AsyncTask.Status.FINISHED)) {
			ServerTask = new SocketAsync(myUpdateHandler);
			ServerTask.execute();

			config.SOCKET_MESSAGE = ServerCommands.login("name", "pass");
		}
	}
}

В сервисе реализован контроль соединения с сервером через класс SocketAsync, и передача данных из SocketAsync в MainActivity через BroadcastReceiver.

SocketAsync:

class SocketAsync extends AsyncTask<Void, Integer, Void> {

	public Socket socket;
	public String message;
	public Handler threadHandler;
	public Context parent;

	public SocketAsync(Handler threadHandler) {
		this.threadHandler = threadHandler;
	}

	@Override
	protected Void doInBackground(Void... params) {

		try {
			if (config.SOCKET_CONNECTED == false) {
				InetAddress serverAddr = InetAddress
						.getByName(config.SERVER_ADDR);
				socket = new Socket(serverAddr, config.SERVER_PORT);
				config.SOCKET_CONNECTED = true;

				Intent intent = new Intent(ServiceExchange.BROADCAST_ACTION);
				intent.putExtra("SERVER_STATUS", true);

				Message threadMessage = new Message();
				threadMessage.what = com.rheit.config.CODE_SOCKET_SERVER_CONNECTED;
				threadMessage.obj = intent;
				threadHandler.sendMessage(threadMessage);
			}
		} catch (Exception e) {
			Intent intent = new Intent(ServiceExchange.BROADCAST_ACTION);
			intent.putExtra("SERVER_STATUS", false);

			Message threadMessage = new Message();
			threadMessage.what = com.rheit.config.ERR_SOCKET_SERVER_UNAVAILABLE;
			threadMessage.obj = intent;
			threadHandler.sendMessage(threadMessage);

			Log.e(SocketAsync.class.toString(),
					"ERR_SOCKET_SERVER_UNAVAILABLE doInBackground");
		}

		Thread threadWrite = new Thread(new Runnable() {
			@Override
			public void run() {
				send(socket);
			}
		});
		threadWrite.start(); // запускаем на отправку

		while (socket != null && socket.isConnected()) {

			Message m = new Message();
			m.what = config.CODE_SOCKET_SERVER_ANSWER;
			try {
				BufferedReader input = new BufferedReader(
						new InputStreamReader(socket.getInputStream()));
				String st = null;
				st = input.readLine();

				if (st == null) {
					threadWrite.stop();
					socket.close();
					socket = null;
				} else {
					m.obj = st;
					myUpdateHandler.sendMessage(m);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		threadWrite.stop();

		return null;
	}

	protected void send(Socket socket) {
		while (socket != null && socket.isConnected()) {
			if (config.SOCKET_MESSAGE != null) {
				Log.d("Send Message", config.SOCKET_MESSAGE);
				try {
					PrintWriter out = new PrintWriter(new BufferedWriter(
							new OutputStreamWriter(socket.getOutputStream())),
							true);
					out.println(config.SOCKET_MESSAGE);
				} catch (Exception e) {
					Log.e("TCP", "S: Error", e);
					return;
				}

				config.SOCKET_MESSAGE = null;
			}
		}
	}

	protected void onProgressUpdate(Integer... values) {
		super.onProgressUpdate(values);
		if (values.length > 0) {
			Log.d("onProgressUpdate", values[0].toString());
		}
	}

	@Override
	protected void onPostExecute(Void result) {
		super.onPostExecute(result);

		if (socket == null || !socket.isConnected()) {
			config.SOCKET_CONNECTED = false;

			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			Intent intent = new Intent(ServiceExchange.BROADCAST_ACTION);
			intent.putExtra("SERVER_STATUS", false);

			Message threadMessage = new Message();
			threadMessage.what = com.rheit.config.ERR_SOCKET_SERVER_UNAVAILABLE;
			threadMessage.obj = intent;
			threadHandler.sendMessage(threadMessage);

			Log.e(SocketAsync.class.toString(),
					"ERR_SOCKET_SERVER_UNAVAILABLE onPostExecute");
		}
	}

	Handler myUpdateHandler = new Handler() {
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case config.CODE_SOCKET_SERVER_ANSWER:
				Intent intent = new Intent(ServiceExchange.BROADCAST_ACTION);
				intent.putExtra("text", "test");

				Message threadMessage = new Message();
				threadMessage.what = com.rheit.config.CODE_SOCKET_SERVER_ANSWER;
				threadMessage.obj = intent;
				threadHandler.sendMessage(threadMessage);
				break;
			default:
				break;
			}
			super.handleMessage(msg);
		}
	};
}

Когда запустили задачу SocketAsync мы стали подключаться к серверу если подключились то посылаем сообщение в UI о том что подключились через:

Intent intent = new Intent(ServiceExchange.BROADCAST_ACTION);
intent.putExtra("SERVER_STATUS", true);

Message threadMessage = new Message();
threadMessage.what = com.rheit.config.CODE_SOCKET_SERVER_CONNECTED;
threadMessage.obj = intent;
threadHandler.sendMessage(threadMessage);

Дальше оно попадает в ServiceExchange в созданный нами myUpdateHandler который в свою очередь передает данные в UI через sendBroadcast:

case config.CODE_SOCKET_SERVER_CONNECTED: // Соединился с сервером успешно
// Некие операции с данными но мы просто 
sendBroadcast((Intent) msg.obj); // Посылаем сообшение в UI
break;

После чего наши данные передаются в MainActivity:

private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
                onDataFromService(intent);
        }
};

public void onDataFromService(Intent intent) {
       Log.d("onDataFromService", intent.getStringExtra("text"));
}

Когда мы закрываем наше приложение то функция unregisterReceiver уничтожает слушателя и все что будет передаваться из нашего сокет-клиента потеряется, для этого советую данные приходящие из вне, записывать в базу а BroadcastReceiver использовать для того чтобы оповещать что нужно что-то прочитать из базы.

Автор: RooTooZ


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js