0

In an Android app (with minSdkVersion 16) I use the nv-websocket-client library:

public class MainActivity extends AppCompatActivity {

    private WebSocket mWs;
    private WebSocketFactory mWsFactory = new WebSocketFactory();
    private WebSocketListener mWsListener = new WebSocketAdapter() {
        @Override
        public void onConnected(WebSocket ws, Map<String, List<String>> headers) throws Exception {
            mReconnectDispatcher.cancelAll();
        }

        // connection on the background thread has failed (onDisconnected will not be called!)
        @Override
        public void onConnectError(WebSocket ws, WebSocketException ex) throws Exception {
            reconnect();
        }

        @Override
        public void onDisconnected(WebSocket ws,
                                   WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame,
                                   boolean closedByServer) throws Exception {
            reconnect();
        }

        @Override
        public void onTextMessage(WebSocket ws, String str) throws Exception {
            // here the WebSockets communication happens
        }
    };

    @Override
    public void onResume() {
        super.onResume();
        reconnect();
    }

    @Override
    public void onPause() {
        super.onPause();
        if (mWs != null) {
            mWs.disconnect();
        }
    }

And Firebase JobDispatcher (version 0.8.4) for reconnecting the WebSocket:

private FirebaseJobDispatcher mReconnectDispatcher;

public class ReconnectService extends JobService {
    @Override
    public boolean onStartJob(JobParameters job) {
        try {
            mWs = mWsFactory.createSocket("ws://demos.kaazing.com/echo");
            mWs.addListener(mWsListener);
            mWs.connectAsynchronously(); // uses background thread
        } catch (Exception ex) {
            Log.w(TAG, "Can not create WebSocket connection", ex);
        }

        return true; // Answers the question: "Is there still work going on?"
    }

    @Override
    public boolean onStopJob(JobParameters job) {
        return false; // Answers the question: "Should this job be retried?"
    }
}

private void reconnect() {
    Job myJob = mReconnectDispatcher.newJobBuilder()
        .setService(ReconnectService.class)
        .setTag(ReconnectService.class.getSimpleName())
        .setRecurring(false)
        .setLifetime(Lifetime.UNTIL_NEXT_BOOT)
        .setTrigger(Trigger.executionWindow(0, 60))
        .setReplaceCurrent(true)
        .setConstraints(Constraint.ON_ANY_NETWORK)
        .build();

    mReconnectDispatcher.mustSchedule(myJob);
}

And in AndroidManifest.xml I have:

<service
    android:name=".MainActivity$ReconnectService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
    </intent-filter>
</service>

Unfortunately, I get the runtime error after 60 seconds:

16998-16998/de.slova E/AndroidRuntime: FATAL EXCEPTION: main
   Process: de.slova, PID: 16998
   java.lang.RuntimeException: Unable to instantiate service de.slova.MainActivity$ReconnectService: java.lang.InstantiationException: java.lang.Class<de.slova.MainActivity$ReconnectService> has no zero argument constructor
       at android.app.ActivityThread.handleCreateService(ActivityThread.java:3389)
       at android.app.ActivityThread.-wrap4(Unknown Source:0)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1683)
       at android.os.Handler.dispatchMessage(Handler.java:105)
       at android.os.Looper.loop(Looper.java:164)
       at android.app.ActivityThread.main(ActivityThread.java:6541)
       at java.lang.reflect.Method.invoke(Native Method)
       at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
    Caused by: java.lang.InstantiationException: java.lang.Class<de.slova.MainActivity$ReconnectService> has no zero argument constructor
       at java.lang.Class.newInstance(Native Method)
       at android.app.ActivityThread.handleCreateService(ActivityThread.java:3386)
       at android.app.ActivityThread.-wrap4(Unknown Source:0) 
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1683) 
       at android.os.Handler.dispatchMessage(Handler.java:105) 
       at android.os.Looper.loop(Looper.java:164) 
       at android.app.ActivityThread.main(ActivityThread.java:6541) 
       at java.lang.reflect.Method.invoke(Native Method) 
       at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) 
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767) 
1472-1487/? I/ActivityManager: Showing crash dialog for package de.slova u0

I have tried adding a default constructor to my class, but the error persists:

public class ReconnectService extends JobService {
    public ReconnectService() {
    }

Does anybody please have an idea, what is wrong here?

I have also submitted my problem as issue #189.

Alexander Farber
  • 21,519
  • 75
  • 241
  • 416

1 Answers1

1

Put ReconnectService in its own class (not an inner class to MainActivity). Alternatively, try making the inner class static.

The way non-static inner classes work in Java is that they can only be instantiated by an instance of the outer class. This is what allows instances of the inner class to access members of the outer class. So, when Android is trying to create an instance of the inner class, it can't do so because it doesn't know what instance of the outer class it should be a part of. The error message is a bit misleading.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Thanks Doug (+1 for replying on Sunday!), however if I change the inner class to `static`, then I can not access `mWs`, `mWsFactory` and `mWsListener` anymore. How to workaround this? Also I am confused, if I should return `true` by the `onStartJob()` after calling `mWs.connectAsynchronously()` and thus (re)using another thread. – Alexander Farber Nov 19 '17 at 17:38
  • 1
    Yeah, that's just not the way Services work in Android. They have their own completely independent context from Activities. They are supposed to remain isolated that way. Consider re-architecting your code so that the websocket is not managed within the activity itself, or look into another way to reconnect other than a service (such as a Handler with scheduled messages). – Doug Stevenson Nov 19 '17 at 17:44