Make EspFlasher cancellable to prevent resource leak

Add @Volatile cancelled flag with cancel() method. Check flag at 3
checkpoints (after initial sleep, after transport setup, before each
flash step) so the thread exits early and closes the USB connection
in the finally block. SettingsActivity.onDestroy now calls cancel().

Reviewed in codereview.md (recommendation 3.2).
This commit is contained in:
eddy 2026-07-26 17:02:06 +02:00
commit e116f2b493
2 changed files with 16 additions and 2 deletions

View file

@ -25,7 +25,15 @@ class EspFlasher(private val context: Context) {
private val handler = Handler(Looper.getMainLooper())
@Volatile
private var cancelled = false
fun cancel() {
cancelled = true
}
fun flashDevice(usbManager: UsbManager, device: UsbDevice, callback: FlashCallback) {
cancelled = false
Thread {
try {
doFlash(usbManager, device, callback)
@ -47,6 +55,7 @@ class EspFlasher(private val context: Context) {
private fun doFlash(usbManager: UsbManager, device: UsbDevice, callback: FlashCallback) {
Thread.sleep(500)
if (cancelled) return
val connection = usbManager.openDevice(device)
?: throw IOException("Kan USB-apparaat niet openen")
@ -67,6 +76,7 @@ class EspFlasher(private val context: Context) {
postProgress(callback, 0, "Verbinden met ESP bootloader...")
val transport = UsbSerialTransport(connection, epOut, epIn)
if (cancelled) return
val assets = context.assets
val bootloader = assets.open("firmware/bootloader.bin").readBytes()
@ -107,6 +117,7 @@ class EspFlasher(private val context: Context) {
stub.eraseFlash()
for (step in steps) {
if (cancelled) return
val sizeStr = if (step.data.size > 1024) "${step.data.size / 1024}KB" else "${step.data.size} bytes"
postProgress(callback, step.baseProgress, "${step.name} schrijven ($sizeStr)...")

View file

@ -43,6 +43,7 @@ class SettingsActivity : AppCompatActivity() {
private var currentAction = UsbAction.NONE
private var progressDialog: AlertDialog? = null
private var dialog: AlertDialog? = null
private var flasher: EspFlasher? = null
private val handler = Handler(Looper.getMainLooper())
private val usbAttachedReceiver = object : BroadcastReceiver() {
@ -120,6 +121,8 @@ class SettingsActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
currentAction = UsbAction.NONE
flasher?.cancel()
flasher = null
try { unregisterReceiver(usbAttachedReceiver) } catch (e: Exception) {}
try { unregisterReceiver(usbPermissionReceiver) } catch (e: Exception) {}
try { unregisterReceiver(usbDetachedReceiver) } catch (e: Exception) {}
@ -203,8 +206,8 @@ class SettingsActivity : AppCompatActivity() {
private fun doFlashing(usbManager: UsbManager, device: UsbDevice) {
showProgressDialog("Firmware Flashen", "Initialiseren...", STYLE_HORIZONTAL)
val flasher = EspFlasher(this)
flasher.flashDevice(usbManager, device, object : EspFlasher.FlashCallback {
flasher = EspFlasher(this)
flasher?.flashDevice(usbManager, device, object : EspFlasher.FlashCallback {
override fun onProgress(percent: Int, message: String) {
runOnUiThread {
val d = progressDialog ?: return@runOnUiThread