kaazing / java.client

Kaazing WebSocket client using RFC-6455 and emulated protocols, plus AMQP protocol client
http://kaazing.org
Apache License 2.0
20 stars 18 forks source link

Empty ServiceLoader<SseEventSourceFactory>collection issue #36

Closed 4gus71n closed 6 years ago

4gus71n commented 7 years ago

Hi,

Description: I stumbled across a very weird bug in the App in which I'm working on. I'm working in an Android App. I'm using the compile 'org.kaazing:gateway.client.java:5.1.0.3' version. Basically we only had one report of this issue in only one device. But we would like to know if you guys know the cause behind this crash. Please let me know if I can give you more info.

Stacktrace:

Fatal Exception: java.util.NoSuchElementException
       at java.util.ServiceLoader$ServiceIterator.next(ServiceLoader.java:210)
       at org.kaazing.net.sse.SseEventSourceFactory.createEventSourceFactory(SseEventSourceFactory.java:51)
       at com.siplay.android_siplay.data.sockets.SSEService.establishConnection(SSEService.java:94)
       at com.siplay.android_siplay.data.sockets.SSEService.access$200(SSEService.java:40)
       at com.siplay.android_siplay.data.sockets.SSEService$2.run(SSEService.java:129)
       at android.os.Handler.handleCallback(Handler.java:733)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:146)
       at android.app.ActivityThread.main(ActivityThread.java:5635)
       at java.lang.reflect.Method.invokeNative(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:515)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
       at dalvik.system.NativeStart.main(NativeStart.java)

Client code: This is the code that we are using in the App to connect. Is in an Android Service.

public class SSEService extends Service {

    private final int RECONNECT_MILLIS = 30000;

    private final String SSE_ENDPOINT = "/sse/session/";
    private final String JSON_KEY_TYPE = "type";
    private final String JSON_KEY_DATA = "data";
    private final String JSON_VALUE_HEARTBEAT = "heartbeat";
    private final String JSON_VALUE_UPDATE = "update";

    private static boolean mConnected = false;
    private IBinder mBinder = new SSEServiceBinder();

    /**
     * A handler is used to check if we need to reconnect after N seconds without receving a heartbeat
     * value on text changed
     */
    private Handler mHandler = new Handler();

    @Inject
    GetToken mGetToken;

    @Override
    public void onCreate() {
        super.onCreate();

        DaggerViewInjectorComponent.builder()
                .appComponent(SipApplication.getInstance().getComponent())
                .build()
                .inject(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!mConnected) {
            establishConnection();
        }

        return START_NOT_STICKY;
    }

    /**
     * Establish a connection with the SSE server
     */
    private void establishConnection() {

        String mToken = mGetToken.get();

        if (!TextUtils.isEmpty(mToken)) {

            mConnected = true;

            try {

                final SseEventSourceFactory sseEventSourceFactory = SseEventSourceFactory.createEventSourceFactory();
                final SseEventSource sseEventSource = sseEventSourceFactory.createEventSource(new URI(BuildConfig.HOST + SSE_ENDPOINT + mToken));

                Thread t = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            sseEventSource.connect();
                            mConnected = true;
                            listen(sseEventSource.getEventReader());
                        }catch (IOException e) {
                            e.printStackTrace();
                            mConnected = false;
                        }
                    }
                });

                t.start();

            } catch (URISyntaxException e) {
                e.printStackTrace();
                mConnected = false;
            } catch (Exception e) {
                Crashlytics.logException(e);
            }
        }

    }

    /**
     * This task will reconnect the service
     */
    Runnable mReconnectTask = new Runnable() {

        @Override
        public void run() {
            mConnected = false;
            establishConnection();
        }
    };

    /**
     * Keeps listening for events
     * @param sseEventReader
     * @throws IOException
     */
    private void listen(SseEventReader sseEventReader) throws IOException {

        SseEventType type = sseEventReader.next();

        mHandler.postDelayed(mReconnectTask, RECONNECT_MILLIS);

        while (type != SseEventType.EOS && mConnected) {
            if (type != null && type.equals(SseEventType.DATA)) {
                CharSequence dataString = sseEventReader.getData();

                try {
                    JSONObject message = new JSONObject(dataString.toString());
                    String messageType = message.getString(JSON_KEY_TYPE);

                    if (messageType.equals(JSON_VALUE_HEARTBEAT)) {
                        mHandler.removeCallbacks(mReconnectTask);
                        mHandler.postDelayed(mReconnectTask, RECONNECT_MILLIS);
                    }

                    if (messageType.equals(JSON_VALUE_UPDATE)) {
                        JSONObject data = message.getJSONObject(JSON_KEY_DATA);
                        new SocketMessageHandler().handleMessage(data);
                    }

                }catch (JsonSyntaxException e) {
                    // Do Nothing with this message
                }catch (JSONException e) {
                    // Do Nothing with this message
                }
            }

            type = sseEventReader.next();
        }

        mConnected = false;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent){
        return false;
    }

    /**
     * Binder for communicating the service with the activity
     */
    public class SSEServiceBinder extends Binder {
        public SSEService getService() {
            return SSEService.this;
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mConnected = false;
        mHandler.removeCallbacks(mReconnectTask);
    }

}
dpwspoon commented 7 years ago

When compiling for Android or loading in Android is it possible you overwrote the META-INF? I would expect that exception to occur if this file was missing in the compiled jar.

See shade plugin configuration here on how we ensure these files make it into the combined jar.

4gus71n commented 7 years ago

I checked the META-INF of the very same .apk that we uploaded into the Playstore It seems to be fine. Also, if this happened because a corrupted apk we should have a lot more of reports about this crash. Currently we only had one crash report about this.

4gus71n commented 6 years ago

Closing this issue a dev on the team found out that we were missing some file when offuscating the code through Proguard.

java/org/kaazing/getaway/. java/org/kaazing/net/auth/. java/org/kaazing/net/http/. java/org/kaazing/net/impl/. java/org/kaazing/net/auth/. java/org/kaazing/net/ws/.

The Proguard's lines that we were missing:

keep class org.kaazing.* { ; } keep interface org.kaazing.* { ; }