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

View file

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