0

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.
Totem
  • 454
  • 5
  • 18

2 Answers2

2

I'm not sure what you are expecting, but this line:

this.startService(new Intent(getApplicationContext(), WebSocketService.class));

is not a "blocking" or synchronous call. So when you call this:

final WebSocketService service = WebSocketService.getInstance();

the service probably has not instantiated and this will probably return "null."

That said, your core problem is in your manifest. You are adding a "filter" that requires an action to be set. See this post:

Android Manifest- intent filter and activity

but with this:

    <service android:name="org.pickme.service.WebSocketService" android:enabled="true">
        <intent-filter>
            <action android:name="org.pickme.service.WebSocketService" />
        </intent-filter>
    </service>

you need to do this:

Intent myIntent = new Intent(getApplicationContext(), WebSocketService.class));
myIntent.setAction("org.pickme.service.WebSocketService");
this.startService(myIntent);

or remove the intent-filter:

    <service android:name="org.pickme.service.WebSocketService" android:enabled="true">
    </service>
Community
  • 1
  • 1
Jim
  • 10,172
  • 1
  • 27
  • 36
  • I added the intent filter because it was not working before, and it doesn't work this way either. And additionally, onCreate is not even reached so it is irrelevant that startService is blocked, because the service never starts. – Totem Nov 23 '14 at 20:02
1

At first glance service.runWhenConnected() call is wrong since it executes a networking code on main UI thread. It is not recommended (I'd say prohibited) since it may block UI thread and cause onCreate not finishing at all. I think you may see error messages in logcat about this case (running network code on UI thread).

ADDED1: Also setting WebSocketService.instance in Service's onCreate is not right. instance should be set in a constructor, or near by (in getInstance() for example). When you call

final WebSocketService service = WebSocketService.getInstance();

the 'instance' is not set yet (Service's onCreate hasn't been called yet - both Activity's and Service's onCreate methods are called on the same UI thread, so Service's onCreate won't be called unless Activity's onCreate finishes. startService() is just a "delayed call" of Service's onCreate). So the code

service.runWhenConnected(...);

most likely throws a NullPointerException.

In general using static variables for Activities and Services is a bad practice, it may cause memory leaks.

ADDED2: If you want to deal with your Service from an Activity, you should use binding, it a standard pattern in Android for such cases: developer.android.com: Bound Services. Do not use direct access to Service's instance via static variable. Use binding instead.

ADDED3: Seems that there may be troubles with Service binding when Activity is re-created (ie on screen rotation): Communicate with Activity from Service (LocalService) - Android Best Practices. The binding must be recreated as well. This post provides quite good info and links to read.

ADDED4: This post Bind service to FragmentActivity or Fragment? explains that Fragments also are not good places for binding, but you can bind Service to context of your Application object instead, and avoid problems with Activity re-creation workflow.

Community
  • 1
  • 1
Mixaz
  • 4,068
  • 1
  • 29
  • 55