From d06eae9eefabc121967d11d13ffc650f72f7786d Mon Sep 17 00:00:00 2001 From: Sk Sahil <57060638+Sahil-pixel@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:19:00 +0530 Subject: [PATCH] Update broadcast.py To avoid crash on_pause() on_resume() if we do call start() and stop() there jnius.jnius.JavaException: JVM exception occurred: java.lang.IllegalThreadStateException --- .../recipes/android/src/android/broadcast.py | 116 ++++++++++++------ 1 file changed, 76 insertions(+), 40 deletions(-) diff --git a/pythonforandroid/recipes/android/src/android/broadcast.py b/pythonforandroid/recipes/android/src/android/broadcast.py index 750e9c87e..c401edfa5 100644 --- a/pythonforandroid/recipes/android/src/android/broadcast.py +++ b/pythonforandroid/recipes/android/src/android/broadcast.py @@ -1,72 +1,108 @@ # ------------------------------------------------------------------- -# Broadcast receiver bridge +# Broadcast receiver bridge for Kivy + Pyjnius +# Safe start/stop with Android lifecycle integration +# ------------------------------------------------------------------- from jnius import autoclass, PythonJavaClass, java_method from android.config import JAVA_NAMESPACE, JNI_NAMESPACE, ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME -class BroadcastReceiver(object): - +class BroadcastReceiver: class Callback(PythonJavaClass): __javainterfaces__ = [JNI_NAMESPACE + '/GenericBroadcastReceiverCallback'] __javacontext__ = 'app' def __init__(self, callback, *args, **kwargs): self.callback = callback - PythonJavaClass.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) @java_method('(Landroid/content/Context;Landroid/content/Intent;)V') def onReceive(self, context, intent): self.callback(context, intent) def __init__(self, callback, actions=None, categories=None): - super().__init__() - self.callback = callback - if not actions and not categories: raise Exception('You need to define at least actions or categories') - def _expand_partial_name(partial_name): - if '.' in partial_name: - return partial_name # Its actually a full dotted name - else: - name = 'ACTION_{}'.format(partial_name.upper()) - if not hasattr(Intent, name): - raise Exception('The intent {} does not exist'.format(name)) - return getattr(Intent, name) + self.callback = callback + self._is_registered = False + self._handlerthread_started = False - # resolve actions/categories first + # Expand intent names Intent = autoclass('android.content.Intent') - resolved_actions = [_expand_partial_name(x) for x in actions or []] - resolved_categories = [_expand_partial_name(x) for x in categories or []] - - # resolve android API - GenericBroadcastReceiver = autoclass(JAVA_NAMESPACE + '.GenericBroadcastReceiver') - IntentFilter = autoclass('android.content.IntentFilter') - HandlerThread = autoclass('android.os.HandlerThread') - # create a thread for handling events from the receiver - self.handlerthread = HandlerThread('handlerthread') - - # create a listener + def _expand(partial): + if '.' in partial: + return partial + name = 'ACTION_' + partial.upper() + if not hasattr(Intent, name): + raise Exception(f'Intent {name} does not exist') + return getattr(Intent, name) + + self.resolved_actions = [_expand(a) for a in actions or []] + self.resolved_categories = [_expand(c) for c in categories or []] + + # Java classes + self.GenericBroadcastReceiver = autoclass(JAVA_NAMESPACE + '.GenericBroadcastReceiver') + self.IntentFilter = autoclass('android.content.IntentFilter') + self.Handler = autoclass('android.os.Handler') + self.HandlerThreadClass = autoclass('android.os.HandlerThread') + + # Build filter + self.receiver_filter = self.IntentFilter() + for action in self.resolved_actions: + self.receiver_filter.addAction(action) + for category in self.resolved_categories: + self.receiver_filter.addCategory(category) + + # Receiver and callback self.listener = BroadcastReceiver.Callback(self.callback) - self.receiver = GenericBroadcastReceiver(self.listener) - self.receiver_filter = IntentFilter() - for x in resolved_actions: - self.receiver_filter.addAction(x) - for x in resolved_categories: - self.receiver_filter.addCategory(x) + self.receiver = self.GenericBroadcastReceiver(self.listener) + + # Init thread placeholder + self.handlerthread = None + self.handler = None def start(self): - Handler = autoclass('android.os.Handler') - self.handlerthread.start() - self.handler = Handler(self.handlerthread.getLooper()) - self.context.registerReceiver( - self.receiver, self.receiver_filter, None, self.handler) + if self._is_registered: + print("[BroadcastReceiver] Already registered.") + return + + if not self._handlerthread_started: + self.handlerthread = self.HandlerThreadClass('BroadcastReceiverThread') + try: + self.handlerthread.start() + self._handlerthread_started = True + except Exception as e: + print(f"[BroadcastReceiver] HandlerThread start failed: {e}") + return + + try: + self.handler = self.Handler(self.handlerthread.getLooper()) + self.context.registerReceiver(self.receiver, self.receiver_filter, None, self.handler) + self._is_registered = True + print("[BroadcastReceiver] Registered.") + except Exception as e: + print(f"[BroadcastReceiver] registerReceiver failed: {e}") def stop(self): - self.context.unregisterReceiver(self.receiver) - self.handlerthread.quit() + if self._is_registered: + try: + self.context.unregisterReceiver(self.receiver) + print("[BroadcastReceiver] Unregistered.") + except Exception as e: + print(f"[BroadcastReceiver] unregisterReceiver failed: {e}") + self._is_registered = False + + if self._handlerthread_started: + try: + self.handlerthread.quitSafely() + print("[BroadcastReceiver] HandlerThread quit safely.") + except Exception as e: + print(f"[BroadcastReceiver] thread quit failed: {e}") + self._handlerthread_started = False + self.handlerthread = None + self.handler = None @property def context(self):