Задача:
Сделать клиент-приложение «на сокетах», так чтобы при закрытии главного активити работа продолжалась и соединение не терялось.
Решение:
Это возможно сделать с помощью сервисов в андроиде, о том что такое сервис и как он работает написано много статей, по этому я не буду вдаваться в подробности и приступлю к реализации.
Наше приложение состоит из 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