Как использовать Firebase JobDispatcher для асинхронного переподключения nv-websocket-client?

В приложении для Android (с minSdkVersion 16) я использую библиотеку nv-websocket-client:

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();
        }
    }

И Firebase JobDispatcher (версия 0.8.4) для повторного подключения 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);
}

И в AndroidManifest.xml у меня есть:

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

К сожалению, я получаю ошибку времени выполнения через 60 секунд:

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

Я попытался добавить конструктор по умолчанию в свой класс, но ошибка сохраняется:

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

У кого-нибудь есть идея, что здесь не так?

Я также отправил свою проблему как issue #189.


person Alexander Farber    schedule 19.11.2017    source источник


Ответы (1)


Поместите ReconnectService в свой собственный класс (не внутренний класс для MainActivity). В качестве альтернативы попробуйте создать внутренний класс static.

Нестатические внутренние классы работают в Java так, что они могут быть созданы только экземпляром внешнего класса. Именно это позволяет экземплярам внутреннего класса получать доступ к членам внешнего класса. Итак, когда Android пытается создать экземпляр внутреннего класса, он не может этого сделать, потому что не знает, частью какого экземпляра внешнего класса он должен быть. Сообщение об ошибке немного вводит в заблуждение.

person Doug Stevenson    schedule 19.11.2017
comment
Спасибо Дуг (+1 за ответ в воскресенье!), однако, если я изменю внутренний класс на static, я больше не смогу получить доступ к mWs, mWsFactory и mWsListener. Как обойти это? Также я сбит с толку, если я должен вернуть true по onStartJob() после вызова mWs.connectAsynchronously() и, таким образом, (повторно) использовать другой поток. - person Alexander Farber; 19.11.2017
comment
Да, сервисы в Android работают не так. У них есть свой полностью независимый от Activity контекст. Таким образом, они должны оставаться изолированными. Попробуйте изменить архитектуру своего кода, чтобы веб-сокет не управлялся в самом действии, или изучите другой способ повторного подключения, отличный от службы (например, обработчик с запланированными сообщениями). - person Doug Stevenson; 19.11.2017