I am trying to make a service performs and action at intervals, with the help of this article I was able to set up a service and set and interval of 1000miliseconds to log to my console, but I noticed that the service only runs once. Here is a snippet of my code:
class MessageService : Service() {
private var serviceLooper: Looper? = null
private var serviceHandler: ServiceHandler? = null
override fun onCreate() {
val context:Context = this
HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
serviceLooper = looper
serviceHandler = ServiceHandler(context, looper)
}
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()
serviceHandler?.obtainMessage()?.also { msg ->
msg.arg1 = startId
serviceHandler?.sendMessage(msg)
}
return START_STICKY
}
override fun onBind(intent: Intent): IBinder? { return null}
override fun onDestroy() {}
private inner class ServiceHandler(context: Context, looper: Looper) : Handler(looper) {
val baseContext = context
override fun handleMessage(msg: Message) {
val runnable = Runnable {
Log.i("thread", "service has been called")
}
this.postDelayed(runnable, 1000)
}
}
}
please what am I doing wrong?
The main problem is that you're creating a NEW Handler using val handler = Handler() after the first correct "handleMessage()" call, so each postDelayed() will post the runnable in an "unmanaged" Handler (I call "unmanaged" because that just-created-one hasn't overridden the "handleMessage(..)" method).
You need to postDelayed() to serviceHandler and not to a new Handler. To fix you can use this.postDelayed(...) because you're inside ServiceHandler and you want to post the Runnable in the same Handler.
Related
I cannot seem to create an infinite loop that runs periodically for a background service and is reliable.
The app uses a Service to post a GPS location every 5 seconds using a FusedLocationClient LocationCallback.
The functions loop as expected when running on the main thread, however the functions stop looping shortly after starting a different app. This is the case for a new thread, as well as a background service, and even a new thread created by a background service, it consistently stops looping shortly after starting a different app. Shortly thereafter onDestroy() is called.
The only way I have been able to successfully continue looping a function in a Service while the user is in a different app is by having a while(true) loop. However, when I implement this method, I never get a call back from the FusedLocationClient. I cannot figure out why or how to get around this problem.
I have already reviewed the Android Guides and API documentation for
Background processing, Background Service, Handler, Looper, Thread.
As well as the StackExchange questions:how to run infinite loop in background thread and restart it, How to run an infinite loop in Android without freezing the UI.
My question is how do I maintain a continuous loop in a background service that does not interfere with the UI AND does not interfere with the FusedLocationCallback
Below is a snippet of my code.
And yes, I declared everything correctly in the manifest.
class MyService: Service(){
private lateinit var locationRequest: LocationRequest
private lateinit var locationCallback: LocationCallback
private lateinit var looper: Looper
private lateinit var context: Context
data class postGPS(...)
val runnable: Runnable = object : Runnable {
override fun run() {
getLocation()
}
}
override fun onStartCommand(...): Int {
context = this
val thread = BackgroundThread()
thread.start()
return START_STICKY
}
inner class BackgroundThread: Thread(){
override fun run() {
Looper.prepare()
looper = Looper.myLooper()!!
handler = Handler(looper)
getLocation()
Looper.loop()
}
}
#SuppressLint("MissingPermission")
fun getLocation()
{
fusedLocationClient = LocationServices.getFusedLocationProviderClient(context!!)
locationRequest = LocationRequest()
locationRequest.interval = 1000
locationRequest.fastestInterval = 1000
locationRequest.smallestDisplacement = 10f
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult ?: return
if (locationResult.locations.isNotEmpty()) {
PostGPS(postGPS(locationResult.lastLocation)
}
}
}
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
looper
)
}
private fun PostGPS(gps: postGPS){
val queue = Volley.newRequestQueue(context)
val url = "https://www.URL.com/api"
val stringReq: StringRequest =
object : StringRequest(
Method.POST, url,
Response.Listener { response ->
Toast.makeText(context, response, Toast.LENGTH_SHORT).show()
handler.postDelayed(runnable,5000) //Loop for posting GPS location
},
Response.ErrorListener { error ->
Toast.makeText(context, error.message.toString(),Toast.LENGTH_SHORT).show()
}
) {
override fun getBody(): ByteArray {
return gps.toByteArray(Charset.defaultCharset())
}
}
queue.add(stringReq)
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
}
}
This is the code with problems :
object MainThreadPoster : Handler(Looper.getMainLooper()) {
fun postRunnableAtFixRate(runnable: Runnable, token: Any, delay: Long, period: Long) {
postAtTime(object : Runnable {
override fun run() {
runnable.run()
}
}, token, SystemClock.uptimeMillis() + delay)
}
}
The MainThreadPoster is initialized with mainLooper, so the runnable function (in the postRunnableAtFixRate method) is expected to be executed in the main thread, but the problem is that the runnable function may be executed in a HandlerThread sometime.
This is the expected stack trace
This is the stack trace with problem
Do not invoke Message.recycle() in your code. In Android 4.4, if you invoke Message.recycle() multi time, the message will occur in the message pool multi times, and the message may exist in multi message queue at the same time。
this is the poc:
class MainActivity : AppCompatActivity() {
private val mMainHandler = Handler(Looper.getMainLooper())
private lateinit var mSubHandler: Handler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_test.setOnClickListener {
start()
}
val handlerThread = HandlerThread("sub thread")
handlerThread.start()
mSubHandler = Handler(handlerThread.looper)
}
private fun start(){
val message = Message()
message.recycle()
message.recycle()
mMainHandler.postDelayed({
if(Looper.myLooper() != Looper.getMainLooper()){
throw Exception("should run in main thread")
}
start()
}, 100)
mSubHandler.sendEmptyMessage(1)
}
}
I'm building a temporary demo app and I need a service that polls a server every minutes. (I know there are better mechanisms for this). Now I have a class I call APIHandler that both my MainActivity and service should use. I have run into issues providing the service with the instance of my APIHandler class. So what I basically want here is my service to be able to use my APIHandler instance. The APIHandler class cannot be made static as it needs a Volley.newRequestQueue object which needs a context instance.
This is how I start my service from my MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setup()
apiHandler = APIHandler(this) //I neeed this instance...
PollingService.enqueueWork(this, Intent(this, PollingService::class.java))
}
This is my service
class PollingService(private val apiHandler: APIHandler) : JobIntentService() {
private val timer = Timer()
private val tag = "PollingService"
//To be present here!
companion object {
fun enqueueWork(context: Context, work: Intent) {
enqueueWork(context, PollingService::class.java, 1, work)
}
}
override fun onHandleWork(intent: Intent) {
Log.d(tag, "Starting")
timer.scheduleAtFixedRate(timerTask {
run {
Log.d(tag, "Polling...")
apiHandler.getLEDState(1)
apiHandler.getLEDState(2)
}
}, 0, 5000)
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
this.timer.cancel()
super.onDestroy()
}
}
If all you need is the context, you can get it in the onHandleWork function, see below (sorry I'm using Java instead of Kotlin):
#Override
protected void onHandleWork(#NonNull Intent intent) {
Context context = getApplicationContext();
// Instantiate your APIHandler with the context here
}
Try copy-pasting this into your project to have AS automatically convert it to Kotlin for you.
Kotlin and Android rookie here...
I'm trying to create a job but I'm having some trobule gettint my async task to run. Here's my JobService:
class DbUpdaterJob: JobService(){
private var activityMessenger: Messenger? = null
private var isWorking = false
private var cancelled = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
//receive messenger used to get info back to the ui thread of activity
activityMessenger = intent?.getParcelableExtra(MESSENGER_DB_UPDATE_KEY)
return Service.START_NOT_STICKY
}
override fun onStartJob(params: JobParameters?): Boolean {
//asynctasks in order not to lock main thread
object: AsyncTask<Void, Void, Boolean>(){
override fun onPreExecute() {
super.onPreExecute()
}
override fun doInBackground(vararg params: Void?): Boolean {
isWorking = true
//do something
return true
}
override fun onPostExecute(result: Boolean?) {
isWorking = false
jobFinished(params, false)
//notify of update on main thread
if(result!!){
notifyActivity()
}
}
}.execute()
return true
}
override fun onStopJob(params: JobParameters?): Boolean {
cancelled = true
//if still working, must reschedule
jobFinished(params, isWorking)
return isWorking
}
private fun notifyActivity(){
val msg = Message.obtain()
msg.run {
what = MSG_DB_UPDATED
}
activityMessenger?.send(msg)
}
}
The idea is to put a couple of web services calls and local db code in the doInBackground. Unfortunately, it never gets called...Can someone please give some pointers on what I'm missing? I've also tried to rewrite the code with threads but still haven't got any luck with it. Not sure if it's important, but here's the code I'm using to schedule the job:
private fun scheduleJob(){
//NOTE: https://stackoverflow.com/questions/38344220/job-scheduler-not-running-on-android-n?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
//less than 15m does not work!
var jobInfo = JobInfo.Builder(DB_UPDATER_JOB_ID, serviceComponent)
.setRequiredNetworkType(NETWORK_TYPE_NOT_ROAMING)
.setPeriodic(4 * 60 * 60 *1000)
.build()
val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as? JobScheduler
val res = scheduler?.schedule(jobInfo)
Log.i("MainActivity", "Job scheduled with $res")
}
Here i am running a service for music play back.
This code snippet is in my onStart() method of my Activity
if(musicServiceStartIntent == null) {
musicServiceStartIntent = new Intent(this, MusicService.class);
startService(musicServiceStartIntent);
bindService(musicServiceStartIntent, musicConnection, Context.BIND_AUTO_CREATE);
}
First i'm starting my service then binding it. And i am calling unbindservice() in onDestroy() method. My Activity got destroyed and service stopped.
unbindService(musicConnection);
Manifest file declaration
<service android:name=".Services.MusicService"/>
How can i keep my service running in background even after activity destroyed. I refer few threads of StackOverflow but they are not helpful.
You just need to start the service, don't bind it to activity lifecycle
Intent intent = new Intent(context, SomeService.class);
startService(intent);
And your service can use START_STICKY / START_REDELIVER_INTENT to make sure that your service will be re-created when the android system kill your service
#Override
public int onStartCommand(final Intent intent, int flags, int startId) {
//other code
return START_STICKY;
}
If needed you can use Service.startForeground(notificationId, notification) to make sure that your service will not be killed by the system
Use your service in startForeground, using Notification you can keep your service alive..
Refer to https://developer.android.com/guide/components/services.html#Foreground.
A music player that plays music from a service should be set to run in the foreground, because the user is explicitly aware of its operation. The notification in the status bar might indicate the current song and allow the user to launch an activity to interact with the music player.
To request that your service run in the foreground, call startForeground().
return service.START_STICKY or service.START_REDELIVER_INTENT in onStartCommand
There is three important tricks:
Call startForegroundService which creates a long running service not limited to the binded context and make a promise to call startForeground later.
Return START_STICKY in onStartComand
Call startForeground with a notification as promised in (1).
For example, if you want to run a TimerService, in your TimerActivity you will do:
private var timerService: TimerService? = null
private val timerServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as TimerService.Binder
timerService = binder.getService()
}
override fun onServiceDisconnected(arg0: ComponentName) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
...
startButton.setOnClickListener {
timerService?.startTimer(60L, 0L)
}
}
override fun onStart() {
super.onStart()
Intent(this, TimerService::class.java).also {
ContextCompat.startForegroundService(this, it) // that's the first trick
bindService(it, timerServiceConnection, Context.BIND_AUTO_CREATE)
}
}
Your TimerService will be something like that:
class TimerService : Service() {
private val binder = Binder()
private var serviceLooper: Looper? = null
private var serviceHandler: ServiceHandler? = null
private var timer: CountDownTimer? = null
private val notificationUtil by lazy {
NotificationUtil(this)
}
override fun onCreate() {
HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
serviceLooper = looper
serviceHandler = ServiceHandler(looper)
}
}
override fun onBind(intent: Intent?): IBinder? = binder
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val timerRemaining = intent?.getLongExtra(EXTRA_REMAINING, 0) ?: 0L
if (timerRemaining != 0L) {
serviceHandler?.obtainMessage()?.also { msg ->
msg.arg1 = startId
msg.data.putLong(EXTRA_REMAINING, timerRemaining)
serviceHandler?.sendMessage(msg)
}
}
return START_STICKY // that's the second trick
}
override fun onDestroy() {
super.onDestroy()
timer?.cancel()
}
fun startTimer(secondsRemaining: Long, id: Long) {
Intent(this, TimerService::class.java).apply {
putExtra(EXTRA_REMAINING, secondsRemaining)
}.also {
onStartCommand(it, 0, id.toInt())
}
}
fun stopTimer() {
timer?.cancel()
}
fun updateNotification(secondsRemaining: Long){
val notification = NotificationCompat.Builder(this, NotificationUtil.CHANNEL_ID_TIMER)
.setSmallIcon(R.drawable.ic_timer)
.setAutoCancel(true)
.setDefaults(0)
.setContentTitle(secondsRemaining.formatSeconds())
.setContentText("Timer")
.setContentIntent(notificationUtil.getPendingIntentWithStack(this, TimerActivity::class.java))
.setOngoing(true)
.build()
startForeground(NotificationUtil.NOTIFICATION_ID, notification) // that's the last trick
}
private fun sendMessage(remaining: Long) {
Intent(TimerService::class.java.simpleName).apply {
putExtra(EXTRA_REMAINING, remaining)
}.also {
LocalBroadcastManager.getInstance(this).sendBroadcast(it)
}
}
private inner class ServiceHandler(looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {
val secondsRemaining = msg.data.getLong(EXTRA_REMAINING)
notificationUtil.showTimerStarted(secondsRemaining)
timer = object : CountDownTimer(secondsRemaining * 1000, 1000) {
override fun onTick(millisUntilFinished: Long) {
Log.i(this::class.java.simpleName, "tick ${(millisUntilFinished / 1000L).formatSeconds()}")
updateNotification(millisUntilFinished / 1000)
sendMessage(millisUntilFinished / 1000)
}
override fun onFinish() {
Log.i(this::class.java.simpleName, "finish")
notificationUtil.showTimerEnded()
sendMessage(0)
stopSelf()
}
}.start()
}
}
inner class Binder : android.os.Binder() {
// Return this instance of LocalService so clients can call public methods
fun getService(): TimerService = this#TimerService
}
companion object {
const val EXTRA_REMAINING = "EXTRA_REMAINING"
const val NOTIFICATION_ID = 1 // cannot be 0
fun Long.formatSeconds(): String {
val s = this % 60
val m = this / 60 % 60
val h = this / (60 * 60) % 24
return if (h > 0) String.format("%d:%02d:%02d", h, m, s)
else String.format("%02d:%02d", m, s)
}
}
}