In an Android app made to play some audio file in the background, I have the following situation. The app plays the audio as I expect, also keeping playing in the background. The issue I am facing is when I want to stop the service.
I have two buttons on the main activity, one for starting the service and the other one to stop it.
This is the function fired by the START button:
fun startHandler(view: View) {
val audioName = "myAudio"
val serviceIntent = Intent(this, TheService::class.java)
serviceIntent.putExtra("name", audioName);
startForegroundService(serviceIntent)
}
This is the function fired by the STOP button:
fun stopHandler(view: View) {
val serviceIntent = Intent(this, TheService::class.java)
stopService(serviceIntent)
}
When I tap the STOP button, the audio stops (as expected), but the app crashes right after (this is not expected).
Here is the onDestroy() function on the service side:
override fun onDestroy() {
super.onDestroy()
audioPlayer.stop()
}
And the onStartCommand() function on the service side:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val runnable = Runnable {
val audioName = intent?.getStringExtra("name")
val audioID = resources.getIdentifier(audioName,"raw", packageName)
audioPlayer = MediaPlayer.create(this, audioID)
audioPlayer?.setLooping(true)
audioPlayer.start()
}
val thread = Thread(runnable)
thread.start()
return START_NOT_STICKY
}
Is there any mistake that can be seen in the code above or some place I should look at in order to solve the problem ?
Related
In my case , I use std::this_thread::sleep_for(10ms) to sleep 10ms.
If the Android app is in foreground, will sleep about 10ms.
But if app in background ,it will sleep about 50ms~.
I also tried usleep(),nanosleep(),std::condition::wait_for(), and also java Thread.sleep(), NONE of them works fine in this case.
But this code works fine always:
int64_t startTimeStemp = now_ms();
while(true) {
int64_t nowTimeStemp = now_ms();
if(now - start > 10) {
break;
}
}
How can I solve this problem? Thanks.
So there is a solution. Its called foreground notification service. First I was doing wake lock but this is insufficient:
Now my code looks like this (sorry I use a lot of custom extensions)
app.startService<LooperPlayNotificationService>()
wakeLock.acquire()
So I keep app alive and working fine in background.
class LooperPlayNotificationService : Service() {
companion object {
val NOTIFICATIONS_CHANNEL = "${app.packageName} notifications"
}
override fun onBind(intent: Intent): IBinder? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
start()
return START_STICKY
}
override fun onCreate() {
super.onCreate()
start()
}
private val playButtonActionId = "play_button_action"
private lateinit var playButtonAction: BroadcastReceiver
private var started = false
// https://stackoverflow.com/questions/6619143/start-sticky-foreground-android-service-goes-away-without-notice
// There's a bug in 2.3 (not sure if it was fixed yet) where when a Service is killed and restarted,
// its onStartCommand() will NOT be called again. Instead you're going to have to do any setting up in onCreate()
private fun start() {
if (started) return
started = true
startForeground(647823876, createNotification())
playButtonAction = register(playButtonActionId) {
main.looper?.player?.asStarted { it.stop() }
}
}
override fun onDestroy() {
super.onDestroy()
unregister(this.playButtonAction)
}
private fun createNotification() = Builder(this, NOTIFICATIONS_CHANNEL)
.setSmallIcon(outline_all_inclusive_24)
.setContentIntent(getActivity(this, 0, Intent<InstrumentsActivity>(this),
FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE))
.setPriority(PRIORITY_DEFAULT)
.setAutoCancel(false).setOngoing(true)
.addAction(ic_stop_circle_black_24dp, "Stop",
getBroadcast(this, 0, Intent(playButtonActionId),
FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE))
.setContentText(getString(R.string.app_name))
.setContentText(main.looper?.preset?.item?.value?.title?.value).build()
}
this is basically just a service, it has to be defined in manifest:
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<service
android:name=".model.mode.looper.player.state.LooperPlayNotificationService"
android:enabled="true"
android:exported="true" />
And so on, there is bunch of examples about this matter, but overall it was not so trivial to implement due to various details you can see in code I posted.
I am making a stopwatch app and would like my stopwatch to continue after 1 minute of the app being closed or the phone being turned off. I was wondering if there was any way for me to make my timer service continue working after the the app is paused. In the background service I am currently using the timer function stop exactly 1 minute after the app is closed. I have been searching the internet for solutions which have pointed me towards foreground services but I am very inexperienced and cannot figure out how to make them work.
Here is my timer service (I got this from Code With Cal on youtube)
class TimerService : Service() {
override fun onBind(p0: Intent?): IBinder? = null
private val timer = Timer()
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val time = intent.getDoubleExtra(TIME_EXTRA, 0.0)
timer.scheduleAtFixedRate(TimeTask(time), 0, 1000)
return START_NOT_STICKY
}
override fun onDestroy() {
timer.cancel()
super.onDestroy()
}
private inner class TimeTask(private var time: Double) : TimerTask() {
override fun run() {
val intent = Intent(TIMER_UPDATED)
time++
intent.putExtra(TIME_EXTRA, time)
sendBroadcast(intent)
}
}
companion object {
const val TIMER_UPDATED = "timerUpdated"
const val TIME_EXTRA = "timeExtra"
}
Can I make this into a service that continues on when the app is closed? Thank you so much (Note: This is my first app and I am very inexperienced. If there is any more code you need, please tell me, thanks!)
As Mike M. Answered in the comments:
"Nope, you can't do that, but you don't really need/want to, anyway. All you really need to know whenever your app is setting up the stopwatch is the starting time of the current timing session, and the time it is right now. If you save that starting time somewhere persistent – e.g., in SharedPreferences – then you just need to retrieve that value and do some simple arithmetic to get the stopwatch re-set correctly."
-Thanks so much (-Casper)
I am developing a game that has a certain number of moves that replenish every 10 seconds, only 250 moves. You may have seen this in many modern mobile games as energy units.
The method that counts down 10 seconds and adds moves is implemented in the "Service". But the fact is that after the death of the application, the service does not work for a long time. Or it is restarted by first calling "OnStartCommand", then immediately "onDestroy" and somehow works. In the event of such a restart, if you open an application where the "Service" is launched in "OnCreate", another "Service" is launched and works in parallel with another (this breaks the logic of the recovery of moves, because you can create a lot of "Service" in this way) ... And even in this case, the "Service" do not live long and die.
class MyService : Service() {
lateinit var pref: SharedPreferences
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i("MYTAG", "Сервис работает")
return START_STICKY
}
fun check() {
if (pref.getInt("servicestep", 250) < 250) {
reload()
} else stopSelf()
}
private fun reload() {
var progress = 0
val b = "com.example.driverclicker"
val intent = Intent(b)
object : CountDownTimer(10100, 1000) {
override fun onTick(millisUntilFinished: Long) {
Log.i("MYTAG", Thread.currentThread().name)
Log.i("MYTAG", "Таймер $millisUntilFinished")
progress += 1
intent.putExtra("step", progress)
sendBroadcast(intent)
if (progress == 10) {
var step = pref.getInt("servicestep", 249)
step += 1
pref.edit().putInt("servicestep", step).commit()
Log.i("MYTAG", "Таймер сохранился $step")
Log.i("MYTAG", "Progress if= $progress")
}
Log.i("MYTAG", "Progress после if= $progress")
}
override fun onFinish() {
intent.putExtra("step", 0)
sendBroadcast(intent)
check()
Log.i("MYTAG", "Таймер закончился")
}
}.start()
}
override fun onCreate() {
super.onCreate()
pref = getSharedPreferences("save", Context.MODE_PRIVATE)
check()
Log.i("MYTAG", "Сервис запущен")
}
override fun onDestroy() {
super.onDestroy()
Log.i("MYTAG", "Сервис отключен")
}
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
}
BroadcastReceiver in Activity
br = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val serviceSteps = intent?.getIntExtra("step", 0)
if (serviceSteps != null) {
showProgress(serviceSteps, R.id.moves_bar)
if (serviceSteps == 10) {
val steps = presenter.loadMoveValue()
Log.i(TAGNAME, "Ресивер получил $steps")
presenter.showText("$steps", R.id.text_movesValue)
Log.i(TAGNAME, "Ресивер отобразил $steps")
}
}
}
}
registerReceiver(br, IntentFilter(BROAD))
I have a question. How do, for example, farm games work on Android? Resources are generated there in the background, and you just come and collect. How to implement this in the background in Android?
There are different methods to handle this, the easiest is to implement an offline calculation. Get a timestamp, when the service stopped working and get a timestamp when it is recreated and working again, with the time between these two timestamps you can calculate how often your service would have triggered.
This is as you already noticed not very safe and can easily be manipulated.
The 2nd approach is have a server handle the current moves and sync with the device. This needs a server, serverlogic and a database, but this can't be manipulated easy.
For multiplayer a serversynced solution is the better choice, if your game is single player only you can do it locally with services and offline calculation.
Which method you chose is your free choice.
I have a little problem with android.speech.SpeechRecognizer method
speechRecognizer.startListening(speechIntent)
Sometimes it takes a long time untill "ready" sound plays(mostly after app rerun). I cannot find something like onSpeechRecognitionReady listener. How can I catch this event to make a progressBar?
I init recognizer this way
private fun initSpeechRecognizer() {
speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
speechIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "en-US")
speechIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, packageName)
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this)
speechRecognizer.setRecognitionListener(object : RecognitionListenerAdapter(){
override fun onResults(results: Bundle) {
val matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
tv_speech.text = matches[0]
btn_speech.isChecked = false
}
})
}
I think you should use the RecognitionListener which has an onReadyForSpeech method so you could show your progressbar on init and hide it onReadyForSpeech
Now i have to do this ugly hack. It produces redundant start listening and stop listening sounds.
override fun onCreate(savedInstanceState: Bundle?) {
initSpeechRecognizer()
speechRecognizer.startListening(speechIntent)
speechRecognizer.stopListening()
}
I am trying to create an application to update a persistent notification even while the app is closed. Right now, I'm using a service that is started in MainActivity's onCreate():
serviceIntent = Intent(this, PersistentService::class.java)
val stopped = stopService(serviceIntent)
println("[123] stopped: $stopped")
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent)
} else {
startService(serviceIntent)
}
This works perfectly to start it. However, when I reopen and close the app, even though stopped: true is printed, the previous service is still running and the previous service's stopService() was not called.
Stripped down version of the PersistentService class:
var timesStarted = 0
var lastService: PersistentService? = null
class PersistentService: Service(){
private var timer: Timer? = null
private var task: AsyncTask<*, *, *>? = null
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
timesStarted++
println("[123]starting persistent service. timesStarted: $timesStarted lastService===this: ${lastService===this} lastService==this: ${lastService==this} lastService: $lastService")
println("[123] hashCode: ${hashCode()}")
lastService = this
if(timer == null){
timer = Timer()
}
setToLoadingNotification()
timer?.scheduleAtFixedRate(object : TimerTask(){
override fun run(){
println("[123]Updating hashCode: ${this#PersistentService.hashCode()}")
if(task?.status == AsyncTask.Status.RUNNING){
// send notification saying last request timed out
}
task?.cancel(true)
task = DataUpdaterTask(DatabaseDataRequester { GlobalData.connectionProperties }) { dataRequest ->
// send notification based on dataRequest value
}.execute()
}
}, 1000L, UPDATE_PERIOD)
return START_STICKY
}
private fun notify(notification: Notification){
getManager().notify(NOTIFICATION_ID, notification)
startForeground(NOTIFICATION_ID, notification)
}
private fun getBuilder(): Notification.Builder {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
return Notification.Builder(this, NotificationChannels.PERSISTENT_STATUS.id)
}
return Notification.Builder(this)
}
private fun getManager() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
override fun stopService(name: Intent?): Boolean {
println("[123]Stopping persistent service")
timer?.cancel() // stop the timer from calling the code any more times
task?.cancel(true) // stop the code from running if it's running
// getManager().cancel(NOTIFICATION_ID)
return super.stopService(name)
}
}
Here is the output
[123] stopped: false
[123]starting persistent service. timesStarted: 1 lastService===this: false lastService==this: false lastService: null
[123] hashCode: 4008007
[123]Updating hashCode: 4008007
[123]Got successful data request
[123]Updating hashCode: 4008007
[123]Got successful data request
// *close and reopen the app*
[123] stopped: true
[123]starting persistent service. timesStarted: 2 lastService===this: false lastService==this: false lastService: me.retrodaredevil.solarthing.android.PersistentService#3d2847
[123] hashCode: 7823272
[123]Updating hashCode: 7823272
[123]Got successful data request
[123]Updating hashCode: 4008007
[123]Got successful data request
[123]Updating hashCode: 7823272
[123]Got successful data request
As you can see from the output, both services are running at the same time. The hashcode shows that they are not the same object so it wouldn't matter if I put my code in the onCreate() instead of onStartCommand() (I already tested this anyway)
It would be helpful if anyone could point me in the right direction. I am new to android development and I had trouble finding the correct way to do this. I'm not even sure if what I'm doing right now is the best way to update the notification.
and the previous service's stopService() was not called.
stopService() is not a lifecycle method of a Service. Perhaps you are thinking of onDestroy().
the previous service is still running
No, the previous service instance was stopped. You just leaked the Timer, because you did not cancel the Timer in onDestroy().
So, override onDestroy(), put your cancel() calls in there, get rid of the rest of stopService(), and you should be in better shape.