I simply cannot understand why my phone kills my foreground service.
Relevant code
In AndroidManifest:
<service
android:name=".recorder.RecorderService"
android:exported="false"
android:process=":Recorder"
android:foregroundServiceType="microphone">
</service>
In my service launcher
ContextCompat.startForegroundService(application, Intent(application, RecorderService::class.java))
Inside the actual service
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO)
val notification = buildRecordingNotification()
startServiceWithNotification(notification)
recordingJob = recordSounds.executeCancellable(Unit, serviceScope)
return START_STICKY
}
private fun buildRecordingNotification(): Notification {
val channelId = buildNotificationChannelId()
val builder = NotificationCompat.Builder(this, channelId)
return builder
.setSmallIcon(R.drawable.ic_stat_notify)
.setContentTitle("Recording...")
.setWhen(0)
.setChannelId(channelId)
.build()
}
private fun buildNotificationChannelId() =
NotificationChannelHelper.createChannel(this, NotificationChannelHelper.NOTIF_ID_RECORDER, NotificationManagerCompat.IMPORTANCE_LOW)
private fun startServiceWithNotification(notification: Notification) =
try {
startForeground(NOTIFICATION_ID, notification)
} catch (e: Exception) {
Log.e("RecorderService", "Failed to startForeground", e)
}
Inside my recording job
private suspend fun startRecorder() {
audioRecord = withContext(Dispatchers.Main) {
CustomAudioRecord().also { ar ->
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO)
ar.startRecording()
recordingSessionId = UUID.randomUUID().toString()
}
}
}
The problem
On OnePlus phones, the foreground service dies after around 30 minutes (so does the app) when the screen is closed and app put into background (easiest way to reproduce)
What I have tried
I tried making a lightweight process to host the service, in which I inject only the needed dependencies for the service to run. The whole process takes up around 120mb of ram, compared to nearly 700mb when the main process is running.
I checked dumpsys logs and got the following reading on my service:
Proc #25: prcp F/S/FGS --M t: 0 22173:***Recorder/u0a714 (fg-service)
Looks like it checks out as being a correctly created foreground service.
I tried using android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO)
Didn't help.
I checked for wakelocks (tried using another one explicitly) and the Mic IN wakelock is active during my service.
A few notes
We have another foreground service for media playback, and it is usually not killed on OnePlus phones. It is built the same way.
On some devices the service runs all day all night without a problem.
Related
I am trying to create an Android app which plays a sound every few seconds. I want this to work even when the phone is idle. At the moment everything works fine even when the phone screen is off. But after about a minute, the timer stops working. As soon as the screen is turned back on, the missed sounds are played in quick succession. I struggle to find the right terms and concepts to properly find a solution with Google.
When I first encountered this issue, I made sure that my service was running in the background. As it seems, the service is also enabled in the background because everything works fine as long as the screen is not turned off.
Code for running the service in the background:
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
timer.scheduleAtFixedRate(TimeTask(), 0, 100);
return START_STICKY
}
private inner class TimeTask() : TimerTask() {
override fun run() {
sendBroadcast(Intent(TIMER_UPDATED))
}
}
Since this didn't work, I tried to make the service a foreground service. But this didn't work either. (I tried to do it as shown here)
Code for running the service in foreground:
private fun runInBackground() {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel("service", "something")
} else {
""
}
val notification: Notification = NotificationCompat.Builder(this, channelId)
.setContentTitle("title")
.setContentText("text")
.setSmallIcon(R.drawable.alert_dark_frame)
.build()
startForeground(1, notification)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String{
val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE)
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
.createNotificationChannel(channel)
return channelId
}
I read something about scheduling tasks in Android. And found the AlarmManager, but I don't think this would really work the way I want it to because I would have to schedule an alarm for every 100ms. The official doc also states that this shouldn't be used in that way and that "handlers" are more suited, but I struggle to understand how I could replace my current timer with such a handler. I have tried to implement something, but failed.
val updateHandler = Handler()
val runnable = Runnable {
// some action
}
updateHandler.looper(runnable, 100)
Finally solved my problem. Something that I didn't understand before was a "wakelock". It looks something like this:
val wakeLock: PowerManager.WakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ShuttleRun::DefaultWakeLock").apply {
acquire(30*60*1000L)
}
}
It basically just keeps the device awake, which doesn't mean that the screen is turned on, but rather that the service can run in the background without being disturbed.
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 want to make a feature to notify user when there's data changes in my firebase database. The problem is when the app is destroyed, then the service automatically destroyed. After that when the data in database change, my app won't notify the user.
Here's some snippet code for my apps.
Service :
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "DogNotificationService created")
val phone = intent?.extras?.getString("PHONE")
if(isFirstTime) {
val firebaseDatabase = FirebaseDatabase.getInstance()
notificationReference = firebaseDatabase.getReference("walker/$phone/notification")
val coroutineScope = CoroutineScope(Job() + Dispatchers.IO)
coroutineScope.launch {
launchListener()
}
isFirstTime = false
}
return super.onStartCommand(intent, flags, startId)
}
fun launchListener() {
valueEventListener =
notificationReference.addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
Log.w(TAG, "Read failed: " + p0.message)
}
override fun onDataChange(p0: DataSnapshot) {
val notificationData = p0.getValue(String::class.java)
if(count > 0) {
Log.d(TAG, notificationData)
sendNotification(notificationData!!)
}
count++
}
})
}
When I start the service :
private fun startNotificationService() {
val intent = Intent(context, DogNotificationService::class.java)
intent.putExtra("PHONE", "081293312313")
Log.d(TAG, "Start notification service")
activity?.startService(intent)
}
If any idea to do this approach, please help.
I was working in an app to keep track of the users to allow them record their tracks and found out that services can be killed at any moment if the android system requires free memory. Even if your service has wakelocks or is running in foreground.
The solution I found was to use alarms with a foreground service, if you schedule alarms this alarms will be fired whether your app is still executing or not. That way my app could get the device position even though the system had killed the app due to lack of resources. It's the only solution I found that works in this scenario. An alarm that wakes up the service.
The idea came to me in some google i/o when they said that if you really need your app to continue no matter what you should use alarms instead of services.
Besides that, if you need the app to be awaked constantly then use exact alarms as the inexact ones in some devices they might be fired 5 minutes later if the time that they should be fired is too near to the current time.
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?