Main activity should start a service which handles socket communication. This is only a test:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
this.setContentView(R.layout.contacts_test);
this.startService(new Intent(getApplicationContext(), WebSocketService.class));
final WebSocketService service = WebSocketService.getInstance();
service.runWhenConnected(new Runnable() {
@Override
public void run() {
service.getWebSocket().emit("login", new Credentials("...", "..."));
}
});
service.registerHandler("login successful", new WebSocketEventHandler() {
@Override
public void onEvent(WebSocket webSocket, Object... args) {
Log.i("SOCKET-IO", "Login successful");
}
});
}
You may find the code of the service below. But before you start reading it, notice that the control flow won't even reach the service constructor or onCreate method; and that's the thing I cannot understand. The service is declared in the manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.gtd.test"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="15"/>
<application android:label="@string/app_name" android:icon="@drawable/tmp">
<activity android:name="MyActivity" android:label="@string/app_name" android:theme="@style/LightCustomTheme"> </activity>
<activity android:name="PickersTestActivity" android:theme="@style/LightCustomTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ContactsTestActivity" android:theme="@style/LightCustomTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name="org.pickme.service.WebSocketService" android:enabled="true">
<intent-filter>
<action android:name="org.pickme.service.WebSocketService" />
</intent-filter>
</service>
</application>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
</manifest>
I am using the Gottox java client for socket.io. But the socket isn't even started, so that's not the problem (and using it outside of a service works).
package org.pickme.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import io.socket.IOAcknowledge;
import io.socket.IOCallback;
import io.socket.SocketIO;
import io.socket.SocketIOException;
import org.json.JSONObject;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WebSocketService extends Service {
private static final String SERVER_ENDPOINT = "http://192.168.1.83:3000";
private static WebSocketService instance;
private SocketIO socket;
private WebSocket socketWrapper;
private boolean initialized;
private Thread connectionThread;
private IOCallback ioCallback;
private Runnable connectHandler, disconnectHandler;
private Map<String, List<WebSocketEventHandler>> eventHandlers;
public static WebSocketService getInstance() {
return instance;
}
public boolean isConnected() {
return socket.isConnected();
}
public boolean isInitialized() {
return initialized;
}
public WebSocket getWebSocket() {
return socketWrapper;
}
public WebSocketService() {
this.ioCallback = new CallbackStub();
this.eventHandlers = new HashMap<String, List<WebSocketEventHandler>>();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
this.initialized = false;
try {
this.socket = new SocketIO(SERVER_ENDPOINT);
this.initialized = true;
} catch (MalformedURLException e) {
this.initialized = false;
return;
}
this.initWrappers();
this.connectionThread.start();
WebSocketService.instance = this;
}
@Override
public void onDestroy() {
super.onDestroy();
this.socket.disconnect();
WebSocketService.instance = null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
private void initWrappers() {
final IOCallback callbackReference = this.ioCallback;
final SocketIO socketReference = this.socket;
this.socketWrapper = new WebSocket() {
@Override
public void emit(String event, Object... args) {
socketReference.emit(event, args);
}
@Override
public void disconnect() {
socketReference.disconnect();
}
};
this.connectionThread = new Thread(new Runnable() {
@Override
public void run() {
socketReference.connect(callbackReference);
}
});
}
public void runOnConnect(Runnable connectHandler) {
this.connectHandler = connectHandler;
}
public void runOnDisconnect(Runnable disconnectHandler) {
this.disconnectHandler = disconnectHandler;
}
public void runWhenConnected(Runnable runnable) {
if (this.isConnected() && (runnable != null))
runnable.run();
else
this.runOnConnect(runnable);
}
public void registerHandler(String event, WebSocketEventHandler handler) {
List<WebSocketEventHandler> handlersList;
if (eventHandlers.containsKey(event))
handlersList = eventHandlers.get(event);
else
eventHandlers.put(event, handlersList = new ArrayList<WebSocketEventHandler>());
handlersList.add(handler);
}
public void unregisterHandler(String event, WebSocketEventHandler handler) {
if (!eventHandlers.containsKey(event))
return;
List<WebSocketEventHandler> handlersList = eventHandlers.get(event);
handlersList.remove(handler);
}
public void unregisterHandlers(String event) {
if (!eventHandlers.containsKey(event))
return;
List<WebSocketEventHandler> handlersList = eventHandlers.get(event);
eventHandlers.clear();
eventHandlers.remove(event);
}
public void unregisterAllHandlers() {
eventHandlers.clear();
}
private class CallbackStub implements IOCallback {
@Override
public void onDisconnect() {
if (WebSocketService.this.disconnectHandler != null)
WebSocketService.this.disconnectHandler.run();
}
@Override
public void onConnect() {
if (WebSocketService.this.connectHandler != null)
WebSocketService.this.connectHandler.run();
}
@Override
public void onMessage(String data, IOAcknowledge ack) { }
@Override
public void onMessage(JSONObject json, IOAcknowledge ack) { }
@Override
public void on(String event, IOAcknowledge ack, Object... args) {
if (!WebSocketService.this.eventHandlers.containsKey(event)) {
Log.i("WEBSOCKET-SERVICE", event + " unhandled");
return;
}
List<WebSocketEventHandler> handlers = WebSocketService.this.eventHandlers.get(event);
for (WebSocketEventHandler handler : handlers)
handler.onEvent(WebSocketService.this.socketWrapper, args);
}
@Override
public void onError(SocketIOException socketIOException) {
Log.e("SOCKET-IO", socketIOException.getMessage(), socketIOException);
}
}
}
EDIT:
- The WebSocketService constructor and onCreate are NEVER invoked.
- Additionally, the same happens either with and without intent filter.
- I can't use binding because I need the service to keep running even when the activity terminates.
- runWhenConnected is NOT called in the main thread. But if it were an exception would have been raised.