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.
Related
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 ?
This question already has answers here:
Background service for android oreo
(3 answers)
Closed 2 years ago.
I am trying to make an android app. It should do something when the phone gets connected to a particular wifi network, and do nothing the rest of the time. I use a Service and a BroadcastReceiver. Everything works fine, but the service for checking wifi state is killed for no reason in some seconds after I hide my application. Is it possible to make it persistent?
I know about android:persistent flag, but it seems to be useless for me as my app isn't system.
As of Android Oreo no background services are allowed to run when the app is closed so you must foreground start it (Google recommends using JobScheduler for SDK > Oreo).This is not the perfect solution of course but should get you started.
class NotificationService: Service() {
private var notificationUtils = NotificationUtils(this)
private var notificationManager: NotificationManager? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_STICKY
}
override fun onCreate() {
super.onCreate()
//here checking if sdk > Oreo then start foreground or it will start by default on background
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForeground(System.currentTimeMillis().toInt(), notificationUtils.foregroundNotification())
}
}
override fun onDestroy() {
super.onDestroy()
// when the OS kills the service send a broadcast to restart it
val broadcastIntent = Intent(this, NotificationServiceRestarterBroadcastReceiver::class.java)
sendBroadcast(broadcastIntent)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
class NotificationServiceRestarterBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(Intent(context, NotificationService::class.java))
}else {
// if sdk < Oreo restart the background service normally
context.startService(Intent(context, NotificationService::class.java))
}
}
}
I am building an app the needs to go through a collection of photos stored locally, which I import to a room database, and try to detect for each if it contains faces or not.
I've got everything sorted, my only issue is how to run this operation, which could take a while, in a service that wouldn't stuff up the UI thread.
At first I wanted to use a JobIntentService but couldn't because I was unable to observeForever on a background thread, and couldn't use a simple observer because I have no lifecycleOwner to give to the Observer.
I've ended up using just a service, as soon as the operation starts my UI is pretty much stuck and if I try to do anything the app crashes.
I tried maybe IntentService but I can't use the observer in onHandleIntent because it's a worker thread and it doesn't let me, and when I run the operations under onStartCommand then it's just the same thing.
I feel like I am stuck with the architecture of this thing, I'd appreciate any ideas. Thank you.
This is my service:
class DetectJobIntentService : Service() {
private val TAG = "DetectJobIntentServi22"
lateinit var repo: PhotoRepository
lateinit var observer : Observer<MutableList<Photo>>
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val options = FirebaseVisionFaceDetectorOptions.Builder()
.setClassificationMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
.setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
.setMinFaceSize(0.15f)
.build()
val detector = FirebaseVision.getInstance()
.getVisionFaceDetector(options)
repo = PhotoRepository(application)
observer = Observer {
for (file in it) {
val image = FirebaseVisionImage.fromFilePath(application, Uri.parse(file.uri))
AsyncTask.execute {
detector.detectInImage(image).addOnSuccessListener { list ->
if (list.isNotEmpty()) {
file.hasFaces = 1
repo.update(file)
} else {
file.hasFaces = 2
repo.update(file)
}
}
}
}
}
repo.getAllPhotos().observeForever(observer)
val notificationIntent= Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0)
val notification = NotificationCompat.Builder(this, getString(tech.levanter.anyvision.R.string.channel_id))
.setContentTitle("Detecting faces..")
.setContentText("64 photos detected")
.setSmallIcon(tech.levanter.anyvision.R.drawable.ic_face)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
repo.getAllPhotos().removeObserver(observer)
}
}
Seeing your code is in Kotlin, I'll advice you try out Kotlin Coroutines. This would enable you dispatch expensive operations i.e. querying databases, making network requests/calls off to other threads thereby not blocking the UIThread. Coroutines help you avoid the hassle of callbacks. Also, Google just deprecated the AsyncTask API in favour of Coroutines as the way to go for multi-threading purposes.
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.
My sample app uses targetSdkVersion 26.
I have a simple service, which is both started and bounded with the following code snippet:
val intent = BoundServiceTest.buildIntent(applicationContext)
applicationContext.startService(intent)
applicationContext.bindService(intent, serviceConnection, BIND_AUTO_CREATE)
Please be aware that I use a global application Context for binding, not an Activity Context.
The service itself does only imlements some basic logging:
class BoundServiceTest : Service() {
companion object {
private val TAG = "BoundServiceTest"
fun buildIntent(context: Context): Intent {
return Intent(context, BoundServiceTest::class.java)
}
}
private val binder = Binder()
override fun onBind(p0: Intent?): IBinder {
Log.d(TAG, "onBind")
return binder
}
override fun onUnbind(intent: Intent?): Boolean {
Log.d(TAG, "onUnbind")
return super.onUnbind(intent)
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand: startId = " + startId)
return START_STICKY
}
override fun onDestroy() {
Log.d(TAG, "onDestroy")
super.onDestroy()
}
}
Basically I am not sure, if Android O applies the Background execution limits or not, since basically the documentation states (Background Service Limitations):
Bound services are not affected
These rules do not affect bound services in any way. If your app
defines a bound service, other components can bind to that service
whether or not your app is in the foreground.
But it seems that the OS isn't quite sure as well regarding the Logcat:
de.continental.android.androidoservicetest D/BoundServiceTest: onCreate
de.continental.android.androidoservicetest D/BoundServiceTest: onStartCommand: startId = 1
de.continental.android.androidoservicetest D/BoundServiceTest: onBind
? W/ActivityManager: Stopping service due to app idle: u0a141 -1m9s733ms de.continental.android.androidoservicetest/.BoundServiceTest
I/RunningState: Unknown non-service process: de.continental.android.androidoservicetest #14943
I/chatty: uid=1000(system) RunningState:Ba identical 1 line
I/RunningState: Unknown non-service process: de.continental.android.androidoservicetest #14943
The ActivityManager log message indicates that the service shall be stopped (regardless that it is a bound one), but the OS doesn't stop my service: The log messages for calling onDestroy method are not displayed, compared to a simple started Service without binding.
How is this scenario (started and bound service) handled in Android O? Or do I encounter a bug?