Розетка в фоновом режиме - PullRequest
0 голосов
/ 02 июля 2018

Мне нужно создать приложение, в котором, пока пользователь авторизован, оно сохраняет соединение с сокетом, пока не выйдет из системы. Для этого создается служба переднего плана, которая запускается после авторизации пользователя и останавливается при выходе из системы. Он реализует подключение и переподключение на сокете.
Все работает хорошо, пока не нажмешь кнопку питания и не выключишь зарядку. После этого пользователь прекращает получать pong-сообщения с сервера, и на OkHttp поступает исключение SocketTimeoutException, а также прекращает прием сообщений на сокете. На JavaWebsocket The connection was closed because the other endpoint did not respond with a pong in time. получено, после чего вы можете успешно создать новое соединение с сокетом, но это будет повторять ту же проблему в цикле.
В настройках оптимизация батареи для этого приложения была отключена. Что я могу сделать, чтобы стабильный сокет подключения работал в фоновом режиме?
Осуществление деятельности:

        class MainActivity : BaseFragmentPermissionActivity(), MainMvpView {   
            private var mIsSocketBound = false   
            private var mSocketBroadcastReceiver = SocketBroadcastReceiver(this)   
            private var mSocketConnection = SocketConnection(this)
            private var mSocketService: SocketService? = null

            override fun onCreate(savedInstanceState: Bundle?) {   
                super.onCreate(savedInstanceState)   
                ...
                doBindService()
            }

            private fun doBindService() {
                bindService(Intent(this, SocketService::class.java), mSocketConnection, Context.BIND_AUTO_CREATE)
                mIsSocketBound = true
            }

            override fun onStart() {
                super.onStart()
                ...
                mSocketService?.doStopForeground()
            }

            override fun onStop() {
                mSocketService?.doStartForeground()
                ...
                super.onStop()
            }

            override fun onDestroy() {
                 doUnbindService()
                 ...
                 super.onDestroy()
            }    

            private fun doUnbindService() {
                 if (mIsSocketBound) {
                     unbindService(mSocketConnection)
                     mIsSocketBound = false
                     mSocketService = null
                 }
             }

            class SocketConnection(mainActivity: MainActivity) : ServiceConnection {
                private val mMainActivity: WeakReference<MainActivity> = WeakReference(mainActivity)

                override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                    val socketService = (service as SocketService.LocalBinder).getService()
                    mMainActivity.get()?.mSocketService = socketService
                    if (socketService.isForeground()) {
                        socketService.doStopForeground()
                    }
                 }

                 override fun onServiceDisconnected(name: ComponentName?) {
                     mMainActivity.get()?.mSocketService = null
                 }
             }
        }

Реализация услуги:

class SocketService : Service(), MvpErrorHandler {   
    private val mConnectingHandler = Handler()
    private val mConnectingTask = ConnectingTask(this)
    private var mIsRunningForeground = false

    override fun onBind(intent: Intent?): IBinder {
        startService(Intent(this, SocketService::class.java))
        return mBinder
    }

    override fun onCreate() {
        super.onCreate()
        DaggerServiceComponent.builder()
                .serviceModule(ServiceModule(this))
                .applicationComponent(PatrolApplication.applicationComponent)
                .build()
                .inject(this)

        startConnecting()
        ...
    }

    override fun onDestroy() {
        ...
        stopConnecting()
        super.onDestroy()
    }

    private fun startConnecting() {
        if (!mIsConnecting) {
            mIsConnecting = true
            mConnectingHandler.post(mConnectingTask)
        }
    }

    private fun stopConnecting() {
        mConnectingHandler.removeCallbacks(mConnectingTask)
        mIsConnecting = false
    }

    private fun openConnection() {
        mCompositeDisposable.add(mDataManager.getSocketToken()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(false, this, {
                stopConnecting()
                mDataManager.openSocketConnection(it.token)
            }, {
                mConnectingHandler.postDelayed(mConnectingTask, RECONNECT_TIME.toLong())
                return@subscribe ErrorHandlerUtil.handleGetSocketError(it, this)
            }))
    }

    class ConnectingTask(socketService: SocketService) : Runnable {
        private val mSocketService: WeakReference<SocketService> = WeakReference(socketService)

        override fun run() {
            mSocketService.get()?.openConnection()
        }
    }
}

Реализация SocketHelper с использованием JavaWebsocket:

class CustomApiSocketHelper @Inject constructor() : ApiSocketHelper {

    private var mCustomSocketClient: WebSocketClient? = null

    override fun openSocketConnection(token: String) {
        mCustomSocketClient = CustomSocketClient(URI(CONNECTION_URL + token))
        mCustomSocketClient?.connect()
    }

    override fun sendMessage(text: String) {
        if (mCustomSocketClient?.isOpen == true) {
            try {
                mCustomSocketClient?.send(text)
            } catch (t: Throwable) {
                Log.e(TAG, Log.getStackTraceString(t))
                Crashlytics.logException(t)
            }
        }
    }

    override fun closeSocketConnection() {
        mCustomSocketClient?.close(CLOSE_REASON_OK)
    }

    class CustomSocketClient(uri: URI) : WebSocketClient(uri) {
        init {
            connectionLostTimeout = PING_TIMEOUT
        }

        override fun onOpen(handshakedata: ServerHandshake?) {
            sendBroadcast(SocketActionType.OPEN.action)
        }

        override fun onMessage(message: String?) {
            sendBroadcast(SocketActionType.MESSAGE.action, message)
        }

        override fun onClose(code: Int, reason: String?, remote: Boolean) {
            if (code != CLOSE_REASON_OK) {
                //call startConnecting() in service
                sendBroadcast(SocketActionType.CLOSE.action)
            }
        }

        override fun onError(ex: Exception?) {
            sendBroadcast(SocketActionType.FAILURE.action)
        }

        private fun sendBroadcast(type: Int) {
            val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
            intent.putExtra(SOCKET_MESSAGE_TYPE, type)
            LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
        }

        private fun sendBroadcast(type: Int, text: String?) {
            val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
            intent.putExtra(SOCKET_MESSAGE_TYPE, type)
            intent.putExtra(SOCKET_MESSAGE, text)
            LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
        }
    }
}

Реализация SocketHelper с использованием OkHttp:

class CustomApiSocketHelper @Inject constructor() : ApiSocketHelper {

        private var mCustomSocketClient: WebSocket? = null

        override fun openSocketConnection(token: String) {
            val request = Request.Builder()
                .url(CONNECTION_URL + token)
                .build()
            mCustomSocketClient = CustomApplication.applicationComponent.authorizedClient().newWebSocket(request, CustomSocketClient())
        }

        override fun sendMessage(text: String) {
            mPatrolSocketClient?.send(text)
        }

        override fun closeSocketConnection() {
            mCustomSocketClient?.close(CLOSE_REASON_OK, null)
        }

        class CustomSocketClient : WebSocketListener() {

            override fun onOpen(webSocket: WebSocket, response: Response) {
                super.onOpen(webSocket, response)
                sendBroadcast(SocketActionType.OPEN.action)
            }

            override fun onMessage(webSocket: WebSocket, text: String) {
                super.onMessage(webSocket, text)
                sendBroadcast(SocketActionType.MESSAGE.action, text)
            }

            override fun onClosed(webSocket: WebSocket?, code: Int, reason: String?) {
                super.onClosed(webSocket, code, reason)
                if (code != CLOSE_REASON_OK) {
                    sendBroadcast(SocketActionType.CLOSE.action)
                }
            }

            override fun onFailure(webSocket: WebSocket?, t: Throwable?, response: Response?) {
                super.onFailure(webSocket, t, response)
                sendBroadcast(SocketActionType.FAILURE.action)
            } 

            private fun sendBroadcast(type: Int) {
                val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
                intent.putExtra(SOCKET_MESSAGE_TYPE, type)
                LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
            }

            private fun sendBroadcast(type: Int, text: String?) {
                val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
                intent.putExtra(SOCKET_MESSAGE_TYPE, type)
                intent.putExtra(SOCKET_MESSAGE, text)
                LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
            }
        }
    }
    ...
    @Provides
    @Singleton
    @Named(AUTHORIZED_CLIENT)
        fun provideAuthorizedClient(builder: OkHttpClient.Builder, interceptor: Interceptor, authenticator: Authenticator): OkHttpClient = builder
            .addInterceptor(interceptor)
            .authenticator(authenticator)
            .pingInterval(PING_TIMEOUT.toLong(), TimeUnit.SECONDS)
            .build()

    @Provides
    @Singleton
        fun provideOkHttpBuilder() = CustomApiHelper.getOkHttpBuilder()

        fun getOkHttpBuilder(): OkHttpClient.Builder {
            val builder = OkHttpClient.Builder()
            builder.readTimeout(NETWORK_CALL_TIMEOUT, TimeUnit.SECONDS)
            builder.writeTimeout(NETWORK_CALL_TIMEOUT, TimeUnit.SECONDS)
            if (BuildConfig.DEBUG) {
                val logger = HttpLoggingInterceptor()
                logger.level = HttpLoggingInterceptor.Level.BASIC
                builder.addInterceptor(logger)
            }
            return builder
        }

1 Ответ

0 голосов
/ 04 июля 2018

После некоторых исследований и испытаний на разных устройствах было установлено, что для стабильной работы в сети необходимо, чтобы устройство заряжалось или имел включенный экран. В другом случае ни PARTIAL_WAKE_LOCK, ни отключение оптимизации батареи в самих настройках не могут решить проблему.

Рекомендуемый способ решения этой проблемы - добавить этот код в свою деятельность:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}

Это предотвращает выключение экрана и обеспечивает стабильное соединение с разъемом. Но у нас все еще есть ситуация, когда пользователь может нажать кнопку питания. И, если в этот момент устройство заряжается, все будет работать как раньше, но в противном случае мы получим разъединение розетки. Чтобы решить эту проблему, вам необходимо периодически пробуждать устройство, чтобы поддерживать процесс пинг-понга. Это не рекомендуемое решение, поскольку оно приведет к разряду батареи и не может гарантировать 100% производительности, но если этот момент для вас критически важен, вы можете использовать это решение. Вам нужно добавить этот код, в подходящем для вас месте, в этом примере используется во время пинга.

 @Suppress("DEPRECATION")
 override fun onWebsocketPing(conn: WebSocket?, f: Framedata?) {
     if (mSocketWakeLock == null) {
         mSocketWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK or PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG)
     }
     mSocketWakeLock?.takeIf { !it.isHeld }?.run { acquire(WAKE_TIMEOUT) }
     super.onWebsocketPing(conn, f)
     mSocketWakeLock?.takeIf { it.isHeld }?.run { release() }
}

Используя это решение, при подключении к тестовым устройствам при хорошем интернет-соединении он остается стабильным в течение 2 часов и более. Без этого он постоянно отключается.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...