后台套接字连接

时间:2018-07-02 10:12:58

标签: android sockets android-background

我需要制作一个应用程序,在该应用程序中,在授权用户的同时,它保持套接字连接,直到注销为止。为此,将创建一个前台服务,该服务在用户授权后开始,并在注销后停止。它在套接字上实现连接和重新连接。
一切正常,直到您按下电源按钮并关闭充电。此后,用户停止从服务器接收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()
        }
    }
}

使用JavaWebsocket实现SocketHelper:

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

使用OkHttp实现SocketHelper:

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 :(得分:0)

在对不同设备进行一些研究和测试后,发现为了使网络稳定运行,有必要对设备进行充电或启用屏幕。在其他情况下,PARTIAL_WAKE_LOCK或在设置本身中禁用电池优化都不能解决问题。

解决此问题的推荐方法是将以下代码添加到您的活动中:

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

这可以防止屏幕关闭,并提供稳定的插座连接。但是我们仍然遇到这样的情况,用户可以按下电源按钮。而且,如果此时设备正在充电,则一切将照常进行,否则,我们将断开套接字的连接。要解决此问题,您需要定期唤醒设备,以支持乒乓球过程。不建议使用此解决方案,因为这会导致电池耗尽,并且不能保证100%的性能,但是如果这一刻对您很重要,则可以使用此解决方案。您需要在适合您的位置添加此代码,此示例在ping时使用。

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

使用此解决方案,在具有良好Internet的测试设备套接字连接上,可以保持稳定2小时或更长时间。没有它,它将不断断开连接。