0

java.lang.InterruptedException error is thrown onDestroy because of the Thread.sleep in the Runnable. I thought that the Runnable created a new Thread and allowed for sleeps?

MainActivity.kt

class MainActivity : AppCompatActivity() {

    lateinit var startBtn: Button
    lateinit var stopBtn: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        startBtn = findViewById(R.id.startButton)
        stopBtn = findViewById(R.id.stopButton)
        startBtn.setOnClickListener { startService() }
        stopBtn.setOnClickListener { stopService() }
    }

    private fun startService() {
        val serviceIntent = Intent(this, ForegroundService::class.java)
        ContextCompat.startForegroundService(this, serviceIntent)
    }

    private fun stopService() {
        val serviceIntent = Intent(this, ForegroundService::class.java)
        stopService(serviceIntent)
    }
}
ForegroundService.kt

class ForegroundService : Service() {
    private lateinit var thread: Thread

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel("channel_service", "Foreground Service Channel", NotificationManager.IMPORTANCE_DEFAULT)

            val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channel)

            val notification: Notification = Notification.Builder(this, "channel_service")
                    .setContentTitle("Title")
                    .setContentText("Text")
                    .setSmallIcon(R.drawable.ic_launcher_foreground)
                    .build()

            startForeground(1, notification)

            runnable = TestRunnable()
            thread = Thread(runnable)
            thread.start()
        }

        return START_NOT_STICKY
    }

    override fun onDestroy() {
        if(thread.isAlive) {
            thread.interrupt()
        }
        super.onDestroy()
    }

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    class TestRunnable : Runnable {
        override fun run() {
            for(i in 1..15) {
                Log.d("Debug", "startThread " + i)
                Thread.sleep(1000) //exception through here onDestroy
            }
        }
    }
}
Tom
  • 717
  • 2
  • 6
  • 23

2 Answers2

1

Use thread.interrupt() to interrupt the thread. Add try-catch around Thread.sleep(1000) call:

class TestRunnable : Runnable {
    override fun run() {
        var isInterrupted = false
        for(i in 1..15) {
            Log.d("Debug", "startThread " + i)
            try {
                Thread.sleep(1000)
            } catch (e: InterruptedException) {
                isInterrupted = true
            }
            if (isInterrupted) break
        }
    }
}

You can also use some AtomicBoolean variable to break the loop in Runnable implementation:

class TestRunnable : Runnable {
    private val isStopped = AtomicBoolean(false)

    fun stop() {
        isStopped.set(true)
    }
     
    override fun run() {
        for(i in 1..15) {
            if (isStopped.get()) break
            // ...
        }
    }
}

override fun onDestroy() {
    if(thread.isAlive) {
        runnable.stop()
        thread.interrupt()
    }
    super.onDestroy()
}
Sergio
  • 27,326
  • 8
  • 128
  • 149
  • thanks. This makes sense. I wonder, is there any reason not to just put the break in the catch? I understand error handling is important, but it doesn't seem best practice to code in program error handling as the normal logic flow. Is this the best way to handle interrupting the thread? – Tom Jul 18 '20 at 06:05
  • I also added implementation using `break`, please check. I think it is a normal practice to use both - the flag and `interrupt()` call . Here is some article about stopping a thread https://www.baeldung.com/java-thread-stop – Sergio Jul 18 '20 at 06:11
-1

Add thread = null; after Thread(runnable).interrupt(). This should work. If you want it to be killed when the user stops the activity you should try overriding onStop instead of onDestroy().

Hope it helps! Happy Coding:)

Bensal
  • 3,316
  • 1
  • 23
  • 35
  • not sure how you mean? `thread = null` doesn't resolve. – Tom Jul 18 '20 at 05:27
  • What I have [read](https://stackoverflow.com/a/51880173/13856570) is that setting a thread to null will not kill it. It actually must be interrupted for it to die. – Tom Jul 18 '20 at 05:52