diff --git a/pos_android_studio_demo/pos_android_app/build.gradle b/pos_android_studio_demo/pos_android_app/build.gradle index 6f871e1d..b2b48d47 100644 --- a/pos_android_studio_demo/pos_android_app/build.gradle +++ b/pos_android_studio_demo/pos_android_app/build.gradle @@ -32,7 +32,7 @@ android { minSdk 24 targetSdk 34 versionCode 146 - versionName "8.5.4-2608201515" + versionName "8.5.4-2609021540" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ndk { // Set up supported SO library architecture @@ -41,6 +41,9 @@ android { } buildTypes { + debug { + debuggable false + } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' diff --git a/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/ui/setting/connection_settings/ConnectionSettingsFragment.java b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/ui/setting/connection_settings/ConnectionSettingsFragment.java index 97ec20c5..9ac84b27 100644 --- a/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/ui/setting/connection_settings/ConnectionSettingsFragment.java +++ b/pos_android_studio_demo/pos_android_app/src/main/java/com/dspread/pos/ui/setting/connection_settings/ConnectionSettingsFragment.java @@ -212,8 +212,26 @@ public String getTitle() { } - // Thread pool for executing asynchronous operations - private static final java.util.concurrent.ExecutorService asyncExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(); + // Thread pool for executing asynchronous operations. + // Instance-level (not static): a static pool would be permanently terminated by the first + // fragment's onDestroy, causing RejectedExecutionException for later instances. + private final java.util.concurrent.ExecutorService asyncExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(); + + /** + * Safely submit a task to the executor, guarding against a terminated pool + * (e.g. USB permission callback fired after the fragment was destroyed). + */ + private void executeAsync(Runnable task) { + if (asyncExecutor.isShutdown() || asyncExecutor.isTerminated()) { + TRACE.d("asyncExecutor already terminated, skip task submission"); + return; + } + try { + asyncExecutor.execute(task); + } catch (java.util.concurrent.RejectedExecutionException e) { + Log.e("ConnectionSettingsFragment", "submit task to executor failed: " + e.getMessage()); + } + } private void showUsbDeviceDialog() { USBClass usb = new USBClass(); @@ -221,7 +239,7 @@ private void showUsbDeviceDialog() { @Override public void onPermissionGranted(UsbDevice device) { // Get USB device list in sub-thread - asyncExecutor.execute(() -> { + executeAsync(() -> { ArrayList deviceList = usb.GetUSBDevices(getActivity()); // Update UI in main thread, using main thread's Looper new Handler(Looper.getMainLooper()).post(() -> { @@ -239,7 +257,7 @@ public void onPermissionDenied(UsbDevice device) { }); // Get USB device list in sub-thread - asyncExecutor.execute(() -> { + executeAsync(() -> { ArrayList deviceList = usb.GetUSBDevices(getActivity()); // Update UI in main thread, using main thread's Looper new Handler(Looper.getMainLooper()).post(() -> {