mik3y / usb-serial-for-android

Android USB host serial driver library for CDC, FTDI, Arduino and other devices.
MIT License
4.85k stars 1.59k forks source link

Export app crash restart #579

Open 0cococ opened 4 months ago

0cococ commented 4 months ago

How can I stop communicating with the device? I try to change the device status or unplug the device, which causes the app to crash and restart. I try to close it with mSerialIoManager.stop() port.close() connection.close(), but it still crashes. Is there any way to close the device connection as long as the device status changes or is unplugged? I don't want the app to crash and restart. image

kai-morich commented 4 months ago

that's not a crash, only a warning trace showing the SerialInputOutputManager thread termination reason

0cococ commented 4 months ago

这不是崩溃,只是显示 SerialInputOutputManager 线程终止原因的警告跟踪 This problem caused me to re-run the app It will search for the device again. It will not cause the app to close directly, but it will re-execute some processes of MainActivity. image

0cococ commented 4 months ago

image When I change the state of esp32, it will report an error and re-run the code of MainActivity. For example, println("====") prints for the first time. If I unplug or change the state of the device, it will print again. That is, the code of MainActivity will run again. It should cause the jvm to restart.

kai-morich commented 4 months ago

To stop communication yourself, close the UsbSerialPort. To stop communication on external error (e.g. disconnect) handle SerialInputOutpManager.Listener.onRunError() and close the port

IagonaDevTeam commented 3 months ago

Hey @kai-morich, almost created a new issue before checking this one, which is the exact same one I encounter. I tried many (Not to say dozen) of code check in order to avoid this issue without any success. I've got the same warning as @0cococ and after that the onPause of my app is called and everything seems "restarted" but in a very stange way. So I admit this come from the plugin as it never appear if we don't use COM object. I tried your solution with addition of my private closeConnection function inside the onRunError listener, but it didn't change anything. I've checked the code of the plugin another time (Already done a PR on this project a year before) and think it can be workarounded by adding a Looper on the listerner call. Do you think its viable?

Here is what I do for closing connection:

      this.usbIoManager?.listener = null
      this.usbIoManager?.stop()

      try
      {
          this.port?.close()
      }
      catch (e: Exception)
      {
          Log.error("Unable to close port: ${e.message}")
      }

      try
      {
          this.myConnection?.close()
      }
      catch (e: Exception)
      {
          Log.error("Unable to close connection: ${e.message}")
      }

Here's the workaround

@Override
public void run() {
    synchronized (this) {
        if (getState() != State.STOPPED) {
            throw new IllegalStateException("Already running");
        }
        mState = State.RUNNING;
    }
    Log.i(TAG, "Running ...");
    try {
        if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT)
            Process.setThreadPriority(mThreadPriority);
        while (true) {
            if (getState() != State.RUNNING) {
                Log.i(TAG, "Stopping mState=" + getState());
                break;
            }
            step();
        }
    } catch (Exception e) {
        if (mSerialPort.isOpen()) {
            Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e);
        } else {
            Log.i(TAG, "Socket closed");
        }
        final Listener listener = getListener();
        if (listener != null) {
            // Here, handle the exception without triggering onPause
            new Handler(Looper.getMainLooper()).post(() -> listener.onRunError(e));
        }
    } finally {
        synchronized (this) {
            mState = State.STOPPED;
            Log.i(TAG, "Stopped");
        }
    }
}

Thanks in advance

kai-morich commented 3 months ago

The listener is invoked in the same background thread as the SerialInputOutputManager is running. If you want to perform UI operations, you have to switch to the main thread with 'looper.post'. This should be done in the Listener, as done in the example app.

ExPl0siF commented 3 months ago

The listener is invoked in the same background thread as the SerialInputOutputManager is running. If you want to perform UI operations, you have to switch to the main thread with 'looper.post'. This should be done in the Listener, as done in the example app.

Thanks for the infos (Sorry, wrong account on the first post). But I don't do any UI operations (Except if you count UsbSerial disconnection and other disconnect things as UI Operations). I use this plugin in order to communicate with custom Serial object and it just get status every 30 seconds and send it to a server online. If I don't disconnect anything in case of failure, do the app can "crash" too? Thanks in advance

IagonaDevTeam commented 3 months ago

So, I just tested what you've said (Added Handler(Looper.getMainLooper()).post() inside the onRunError) and It didn't change anything, the behavior is still the same. Do you have any other potential fix for this issue?

kai-morich commented 3 months ago

The listener is invoked in the same background thread as the SerialInputOutputManager is running. If you want to perform UI operations, you have to switch to the main thread with 'looper.post'. This should be done in the Listener, as done in the example app.

Thanks for the infos (Sorry, wrong account on the first post). But I don't do any UI operations (Except if you count UsbSerial disconnection and other disconnect things as UI Operations). I use this plugin in order to communicate with custom Serial object and it just get status every 30 seconds and send it to a server online. If I don't disconnect anything in case of failure, do the app can "crash" too? Thanks in advance

what kind of "crash" do you get? Can you provide a callstack or logcat output?

IagonaDevTeam commented 3 months ago

The listener is invoked in the same background thread as the SerialInputOutputManager is running. If you want to perform UI operations, you have to switch to the main thread with 'looper.post'. This should be done in the Listener, as done in the example app.

Thanks for the infos (Sorry, wrong account on the first post). But I don't do any UI operations (Except if you count UsbSerial disconnection and other disconnect things as UI Operations). I use this plugin in order to communicate with custom Serial object and it just get status every 30 seconds and send it to a server online. If I don't disconnect anything in case of failure, do the app can "crash" too? Thanks in advance

what kind of "crash" do you get? Can you provide a callstack or logcat output?

It's doens't seems to be a real crash, but the onPause of my current activity is called, then, the activity is fully relaunched on top of the previous one, so, something bad seems to happen. I got the same Warning as the first post of the issue and then everything goes wrong.

Here is the logcat log that is generated:

06-17 12:03:23.684 D/CCodecBuffers(14495): [c2.android.aac.decoder#672:1D-Input.Impl[N]] codec released a buffer owned by client (index 0)
06-17 12:03:24.946 W/SerialInputOutputManager(14495): Run ending due to exception: USB get_status request failed
06-17 12:03:24.946 W/SerialInputOutputManager(14495): java.io.IOException: USB get_status request failed
06-17 12:03:24.946 W/SerialInputOutputManager(14495):   at com.hoho.android.usbserial.driver.CommonUsbSerialPort.testConnection(CommonUsbSerialPort.java:171)
06-17 12:03:24.946 W/SerialInputOutputManager(14495):   at com.hoho.android.usbserial.driver.CommonUsbSerialPort.read(CommonUsbSerialPort.java:224)
06-17 12:03:24.946 W/SerialInputOutputManager(14495):   at com.hoho.android.usbserial.driver.CommonUsbSerialPort.read(CommonUsbSerialPort.java:183)
06-17 12:03:24.946 W/SerialInputOutputManager(14495):   at com.hoho.android.usbserial.driver.FtdiSerialDriver$FtdiSerialPort.read(FtdiSerialDriver.java:172)
06-17 12:03:24.946 W/SerialInputOutputManager(14495):   at com.hoho.android.usbserial.driver.FtdiSerialDriver$FtdiSerialPort.read(FtdiSerialDriver.java:150)
06-17 12:03:24.946 W/SerialInputOutputManager(14495):   at com.hoho.android.usbserial.util.SerialInputOutputManager.step(SerialInputOutputManager.java:229)
06-17 12:03:24.946 W/SerialInputOutputManager(14495):   at com.hoho.android.usbserial.util.SerialInputOutputManager.run(SerialInputOutputManager.java:203)
06-17 12:03:24.946 W/SerialInputOutputManager(14495):   at java.lang.Thread.run(Thread.java:923)
06-17 12:03:24.954 I/System.out(14495): [ERROR] : Controller: Error on run: USB get_status request failed
06-17 12:03:24.954 I/SerialInputOutputManager(14495): Stopped
06-17 12:03:24.976 I/System.out(14495): [INFO] : Controller: Closing all connections
06-17 12:03:24.977 D/UsbDeviceConnectionJNI(14495): close
06-17 12:03:24.983 I/System.out(14495): [INFO] : Controller: All Connection Closed
06-17 12:03:24.987 I/System.out(14495): [ERROR] : Controller: No available drivers
06-17 12:03:25.271 D/BufferPoolAccessor2.0(14495): bufferpool2 0xee04dca8 : 5(40960 size) total buffers - 4(32768 size) used buffers - 1/6 (recycle/alloc) - 5/242 (fetch/transfer)
06-17 12:03:25.271 D/BufferPoolAccessor2.0(14495): evictor expired: 1, evicted: 1
06-17 12:03:25.486 D/CCodecBuffers(14495): [c2.android.aac.decoder#672:1D-Input.Impl[N]] codec released a buffer owned by client (index 2)
06-17 12:03:25.513 I/System.out(14495): [INFO] : BroadcastActivity onPause Called
kai-morich commented 3 months ago

Android lifecycle is complex. I recommend https://github.com/xxv/android-lifecycle. To prevent multiple instances of your app, you could try different launchMode as used here.

IagonaDevTeam commented 3 months ago

Android lifecycle is complex. I recommend https://github.com/xxv/android-lifecycle. To prevent multiple instances of your app, you could try different launchMode as used here.

Thanks, I know Android Lifecycle is complex, but it shouldn't be triggered with the plugin and a warning. I already tried to setup singleTask and singleTop but it just a bad workaround that create other problems. This behavior only happen with USB plugged in and the plugin running. So, do you have more infos about this behavior and maybe a fix? As there is already 3 issues opened that seems to trigger this problem :/ Thank you very much in advance

IagonaDevTeam commented 3 months ago

Ok! I have some (good?) news about this! I think I've managed to find the root cause of all the problems! The problem was an app restart after the get_status error. The fact is, when this issue is triggered it seems that the COM device (I admitted this come from the device) create a micro-disconnection that trigger the warning (Which is right, as the connection is/seems lost) and the micro-disconnection trigger the Intent registered with the app. SO, the app onPause is called in order to let another instance (Of the same app/Activity) to be launched and this is why there is this strange behavior. So, for all the other that will read this, you have mainly two solutions:

kai-morich commented 3 months ago

what kind of intent are you using?

IagonaDevTeam commented 3 months ago

what kind of intent are you using?

I'm using android.hardware.usb.action.USB_DEVICE_ATTACHED, so when a device is attached it launch my Launchscreen, this is why I'm thinking of micro-disconnection from the COM device

kai-morich commented 3 months ago

android.hardware.usb.action.USB_DEVICE_ATTACHED is not really needed. Access permissions are handled differently. USB_DEVICE_ATTACHED is more like a convenience feature for your App and for users. If multiple Apps can handle USB devices the previously selected can be automatically started. You should use https://github.com/mik3y/usb-serial-for-android/blob/275590027b6fbdeffe3ff9293b49594a56c53418/usbSerialExamples/src/main/AndroidManifest.xml#L17 to get the intent delivered to the existing App instance.