I want to use MutableSharedFlow in the Service class, but I'm not sure how to stop subscribing when Service ends. How to implement the MutableSharedFlow function in service or any other function available to listen to stream data?
To use a Flow in an android Service class we need a CoroutineScope instance to handle launching coroutines and cancellations. Please see the following code with my comments:
class CoroutineService : Service() {
private val scope = CoroutineScope(Dispatchers.IO)
private val flow = MutableSharedFlow<String>(extraBufferCapacity = 64)
override fun onCreate() {
super.onCreate()
// collect data emitted by the Flow
flow.onEach {
// Handle data
}.launchIn(scope)
}
override fun onStartCommand(#Nullable intent: Intent?, flags: Int, startId: Int): Int {
scope.launch {
// retrieve data from Intent and send it to Flow
val messageFromIntent = intent?.let { it.extras?.getString("KEY_MESSAGE")} ?: ""
flow.emit(messageFromIntent)
}
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() {
scope.cancel() // cancel CoroutineScope and all launched coroutines
}
}
I need to make an application where, while the user is authorized, it keeps the socket connection until it is logged out. For this purpose, a foreground service is created, which starts after the authorization of the user, and stops when it is logged out. It implements connection and reconnection on the socket.
All works well until you press the power button and turn off the charging. After this, the user stops receiving pongs from the server and the SocketTimeoutException is received on the OkHttp, and also stops receiving messages on the socket. On JavaWebsocket, The connection was closed because the other endpoint did not respond with a pong in time is received, after which you can successfully create a new socket connection, but it will repeat the same problem in the loop.
In the settings, the optimization of the battery for this application was disabled. What can I do to make a stable connection socket work in the background?
Implementation of activity:
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
}
}
}
Implementation of service:
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()
}
}
}
Implementation of SocketHelper using 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)
}
}
}
Implementation of SocketHelper using 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
}
After some research and testing on different devices, it was found that for stable operation on the network, it is necessary that the device is charging or has a screen enabled. In the other case, neither PARTIAL_WAKE_LOCK nor the disabling of battery optimization in the settings itself can solve the problem.
The recommended way to solve this problem is to add this code to your activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
This prevents the screen from turning off and provides a stable socket connection. But we still have the situation that the user can press the power button. And, if at this moment the device is charging, everything will work as before, but otherwise, we will get the socket disconnect. To solve this problem, you need to periodically wake the device, in order to support the ping-pong process. This is not a recommended solution because it will lead to battery draining, and can not guarantee 100% performance, but if this moment is critical for you, then you can use this solution. You need to add this code, in a suitable place for you, in this example is used at the time of 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() }
}
Using this solution, on the test devices socket connection, with good Internet, stays stable for 2 hours or more. Without it, it is constantly disconnect.
My MainActivity implements the Observer class. I also have a class called ObservedObject that extends the Observable class.
Here is my custom Observable , called ObservedObject:
class ObservedObject(var value: Boolean) : Observable() {
init {
value = false
}
fun setVal(vals: Boolean) {
value = vals
setChanged()
notifyObservers()
}
fun printVal() {
Log.i("Value" , "" + value)
}
}
Here is my Application called SpeechApp which contains my ObservedObject (an Observable actually):
class SpeechApp: Application() {
var isDictionaryRead = ObservedObject(false)
override fun onCreate() {
super.onCreate()
wordslist = ArrayList()
Thread {
execute()
}.start()
}
fun execute() {
while (/* Condition */) {
//Log.i("Read" , line)
/*Does Something Here*/
}
isDictionaryRead.setVal(true)
}
}
In my MainActivity, I mainly have a dialog, that should be displayed after I have got the output after Speech Recognition. It will display as long as the value of isDictionaryRead doesn't change to true:
class MainActivity(private val REQ_CODE_SPEECH_INPUT: Int = 100) : AppCompatActivity() , Observer{
override fun update(o: Observable?, arg: Any?) {
(o as ObservedObject).printVal()
dialog.hide()
}
private lateinit var app : SpeechApp
private lateinit var dialog: MaterialDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
dialog = MaterialDialog.Builder(this)
.title("Please Wait")
.content("Loading from the Dictionary")
.progress(true , 0)
.build()
app = application as SpeechApp
app.isDictionaryRead.addObserver(this)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_speech, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
val id = item?.itemId
when(id) {
R.id.menu_option_speech -> {
invokeSpeech()
}
}
return super.onOptionsItemSelected(item)
}
private fun invokeSpeech() {
/* Does Something, Works Fine */
try {
startActivityForResult(intent , REQ_CODE_SPEECH_INPUT)
}
catch (ex: ActivityNotFoundException) {
/* Does Something */
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQ_CODE_SPEECH_INPUT -> {
if (resultCode == Activity.RESULT_OK && null != data) {
dialog.show()
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
}
Now the problem is, when the SpeechApp sets the value of isDictionaryRead to true, I expect it to call the MainActivity update() method, wherein I have given the code to hide the dialog. That particular code is not working, and my dialog box doesn't go away. Where am I going wrong?
PS. I've pushed my code to Github now, just in case anyone could help me where I am going wrong.
The only thing I can think of that would cause this problem is that the execute() thread that was started in SpeechApp.onCreate finished execution and called isDictionaryRead.setVal(true) before the activity could call app.isDictionaryRead.addObserver(this). As a result, notifyObservers is called before the activity even starts observing, and as a result it is not notified. Here's my proposed solution: Start the execute thread in the activity's onCreate method after adding it as an observer.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
dialog = MaterialDialog.Builder(this)
.title("Please Wait")
.content("Loading from the Dictionary")
.progress(true , 0)
.build()
app = application as SpeechApp
app.isDictionaryRead.addObserver(this)
app.asyncReadDictionary()
}
Then remove the thread call from SpeechApp.onCreate and use this instead
// in SpeechApp
fun asyncReadDictionary() {
if (!isDictionaryRead.value) {
Thread { execute() }.start()
}
}
private fun execute() {
while (/* Condition */) {
//Log.i("Read" , line)
/*Does Something Here*/
}
isDictionaryRead.value = true
}
Also, reimplement ObservableObject as follows
class ObservedObject : Observable() {
var value: Boolean = false
set(newValue) {
field = newValue
setChanged()
notifyObservers()
}
fun printVal() {
Log.i("Value" , "" + value)
}
}
I hope to execute a repeated task with JobScheduler.
I can't understand completely about the fun jobFinished() from official document.
Which code is correct between jobFinished(parameters, false) and jobFinished(parameters, true) if I hope the task can be executed repeatedly?
BTW, I have set setPeriodic(interval) for JobScheduler
Code
private fun startScheduleRestore(mContext:Context){
logError("Start Server")
val interval=10 *1000L
val mJobScheduler = mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val jobInfo = JobInfo.Builder(mContext.getInteger(R.integer.JobID), ComponentName(mContext, RestoreService::class.java))
.setPeriodic(interval)
.setPersisted(true)
.build()
mJobScheduler.schedule(jobInfo)
}
class RestoreService : JobService() {
override fun onCreate() {
logError("OnCreate")
super.onCreate()
}
override fun onDestroy() {
logError("OnDestory")
super.onDestroy()
}
override fun onStartJob(params: JobParameters): Boolean {
Thread(Runnable { completeRestore(params) }).start()
return true
}
override fun onStopJob(params: JobParameters): Boolean {
logError("OnStop")
return false
}
fun completeRestore(parameters: JobParameters) {
logError("Starting")
jobFinished(parameters, false)
}
}
Based on the documentation:
You can request that the job be scheduled again by passing true as the
wantsReschedule parameter. This will apply back-off policy for the
job; this policy can be adjusted through the setBackoffCriteria(long,
int) method when the job is originally scheduled. The job's initial
requirements are preserved when jobs are rescheduled, regardless of
backed-off policy.
jobFinished(parameters, true) will re-schedule your job.
If you want to execute something periodically, then use setPeriodic in your JobInfo.Builder
I'm trying to do some background work in my android application. As the web suggested I'm using JobScheduler to do so.
The jobs are sometimes firing 5-15 times instead of once. Sometimes they are never firing.
My testdevices run on 5.1.1 and 7.0. The one with Nougat fires way less then the one with lollipop.
This is how I enable my jobs (the 5 seconds interval is only for test purpose):
fun enableTasks() {
val jobScheduler = App.getContext().getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
if (PreferenceDao.getInstance().shouldUpdateJob()) jobScheduler.cancelAll()
scheduleJob(jobScheduler, MoniInfoJob.getJob())
scheduleJob(jobScheduler, QueueJob.getJob())
scheduleJob(jobScheduler, MontageOrderUpdateJob.getJob())
PreferenceDao.getInstance().setJobUpdated()
}
private fun scheduleJob(jobScheduler: JobScheduler, jobInfo: JobInfo) {
val jobExists = jobScheduler.allPendingJobs.any { it.id == jobInfo.id }
if (!jobExists) jobScheduler.schedule(jobInfo)
}
All three jobs look kind of the same so I only post one:
The JobService
class QueueJob : JobService() {
override fun onStartJob(jobParameters: JobParameters?): Boolean {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, p1: Intent?) {
unregisterBroadcastReceiver(this)
jobFinished(jobParameters, false)
}
}
registerBroadcastReceiver(receiver)
MainController.startQueueService()
return true;
}
override fun onStopJob(jobParameters: JobParameters): Boolean {
Log.d(MontageOrderUpdateJob.TAG, "onStopJob")
return false;
}
private fun registerBroadcastReceiver(receiver: BroadcastReceiver) {
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, IntentFilter(JOB_FINISHED))
}
private fun unregisterBroadcastReceiver(receiver: BroadcastReceiver) {
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
}
companion object {
val TAG = QueueJob::class.java.name
val jobId: Int = 2
val JOB_FINISHED = TAG + "_finished"
fun getJob(): JobInfo {
val builder = JobInfo.Builder(jobId, ComponentName(App.getContext(), TAG))
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
builder.setPeriodic(5000L)
builder.setPersisted(true)
return builder.build()
}
}
}
And the JobIntentService:
class QueueService : JobIntentService() {
private val TAG = QueueService::class.java.name
override fun onHandleWork(intent: Intent) {
try {
Log.d(TAG, "Jobservice started")
TimerecordQueue().workThroughQueue()
DangerAllowanceQueue().workThroughQueue()
ProjektEndQueue().workThroughQueue()
PhotoUploadQueue().workThroughQueue()
} finally {
sendFinishedBroadcast()
}
}
private fun sendFinishedBroadcast() {
val jobFinishedIntent = Intent(QueueJob.JOB_FINISHED)
LocalBroadcastManager.getInstance(this).sendBroadcast(jobFinishedIntent)
}
}
I had a similar problem once. My problem then was that I didn't check for preexisting schedules.
Could it be you need to do the same?