1

I'm trying to better understand the behavior of threads in my android app. For some reason, when I use while(true) in one of my worker threads, code within that thread's run method that exists sequentially BEFORE the while(true) loop never executes. To be clear, I'm not sure if the code(toast messages) actually isn't executing or if the way the thread synchronization is handled by the Android OS is causing my Toast messages not to display. This behavior appears to be some sort of blocking but I can't figure out why this happens.

My app uses 3 threads: the UI thread(default/main thread in an Android app), a thread to infinitely read data from the device's USB port during runtime, and a thread to process this data via messages from the USB-read thread. The problem seems to occur in my USBController class. When I comment out my infinite while loop, all of the Toast messages before the start of the loop display just fine. When I don't comment out my while(true), NO TOAST MESSAGES EVER DISPLAY! I'm pretty confused by this, I think i'm misunderstanding something fundamental about thread handling by the Android OS. Even if a while loop were to cause blocking, which i don't think it since it resides in a worker thread, why wouldn't the toast messages that occur before the while loop be triggered? Is this a synchronization issue? Am I misusing Android's Handler-Looper system?

Code below. Note: I've included the relevant portion of the main activity and the entirety of the USBController class. My implementation of this class relies heavily on the USB to Serial library found here mik3y/usb-serial-for-android. I don't think it's necessary, but i've included the class that contains my third thread, SensorDataBuffer, that receives messages from the thread UsbController.

UsbController.java

    public class UsbController extends Thread{
    ...
    @Override
    public void run() {
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DEFAULT); //sets thread to default queing priority
        Looper.prepare();
        Toast.makeText(mContext.getApplicationContext(), "Hello from UsbController's run method!", Toast.LENGTH_SHORT).show();

        // **********************USB otg*******************************
        //Obtain permission to use Android device's USB intent
        PendingIntent mPermissionIntent;
        mPermissionIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_USB_PERMISSION), 0);

        // Find all available drivers from attached devices.
        ProbeTable customTable = new ProbeTable();
        customTable.addProduct(0x03EB, 0x2044, CdcAcmSerialDriver.class);                 
        UsbManager manager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
        UsbSerialProber prober = new UsbSerialProber(customTable);
        List<UsbSerialDriver> availableDrivers = prober.findAllDrivers(manager);

        if (availableDrivers.isEmpty()) {
            Toast.makeText(mContext.getApplicationContext(), "No available USB drivers found",Toast.LENGTH_SHORT).show(); // Toast message for debugging
        }
        else {                                                  // open connection to first avail. driver
            UsbSerialDriver driver = availableDrivers.get(0);
            Toast.makeText(mContext.getApplicationContext(), "Driver found",Toast.LENGTH_SHORT).show(); // Toast message for debugging
            UsbDeviceConnection connection = manager.openDevice(driver.getDevice());
            Toast.makeText(mContext.getApplicationContext(), "Device Driver Opened",Toast.LENGTH_SHORT).show(); // Toast message for debugging
            if (connection == null) {           // You probably need to call UsbManager.requestPermission(driver.getDevice(), ..)
                Toast.makeText(mContext.getApplicationContext(),"Connection to device not allowed, need permissions",Toast.LENGTH_LONG).show();
                manager.requestPermission(driver.getDevice(),mPermissionIntent);  //conn test
                if (manager.hasPermission(driver.getDevice())==true){
                    Toast.makeText(mContext.getApplicationContext(),"Permissions granted",Toast.LENGTH_SHORT).show();
                }
            }
            else {                      // Read some data! Most have just one port (port 0).
                List<UsbSerialPort> myPortList = driver.getPorts();
                UsbSerialPort port = myPortList.get(0);
                Toast.makeText(mContext.getApplicationContext(),"USB OTG Connection Established",Toast.LENGTH_SHORT).show();
                try {
                    port.open(connection);
                    port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); // sets baud rate,databits, stopbits, & parity
                    port.setDTR(true);                 //necessary to make Arduino Micro begin running it's program
                    Toast.makeText(mContext.getApplicationContext(),"port opened, parameters set, DTR set",Toast.LENGTH_SHORT).show();
                    byte buffer[] = new byte[16];      
                    String incompPacket = "";
                    Toast.makeText(mContext.getApplicationContext(), "hi again!"), Toast.LENGTH_LONG).show();
                    while (true){                  //continuous loop to read data
                        numBytesRead = port.read(buffer, 100);          
                        arduinoData = new String(buffer, "US-ASCII");
                        String raw = arduinoData.substring(0, numBytesRead);
                        if (numBytesRead > 0) {
                            ...
                        }
                    }
                } catch (IOException e) {
                    Toast.makeText(mContext, e.getMessage(), Toast.LENGTH_SHORT).show();
                }
            }
        }
        Looper.loop(); 
    }
}

MainActivity.java

...
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        //Multi-threading
        //Create thread to handle incoming data from USB Controller thread
        SensorDataBuffer pressureDataBuffer = new SensorDataBuffer(MainActivity.this);
        Thread bufferThread = new Thread(pressureDataBuffer);
        bufferThread.start();

        //Create USB Serial Worker thread which will continuously receive data
        UsbController serialDataLink = new UsbController(PlayFrets.this);
        Thread sensorMonitorThread = new Thread(serialDataLink);
        sensorMonitorThread.start();
        //Toast.makeText(this, "USB Controller thread started", Toast.LENGTH_SHORT).show();

        //Build GUI
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);           //Removes action bar from display
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);  //Removes status bar from display

        //Create AsyncTask to load the note files. A splash screen will be displayed while task is executing
        new AsyncTask_NoteFileLoader(this).execute();
        }
...

SensorDataBuffer.java

public class SensorDataBuffer extends Thread{

    //Handler subclass which accepts messages one by one in
    //the main activitiy's FIFO message que called a "Looper"
    //The worker thread, sensorMonitor, runs UsbController in parallel
    //with the UI thread and continuously formats and sends pressure sensor
    //values read from the microcontroller to the Handler which updates the
    //corresponding pressure state logic variables in the UI thread.
    public void run(){
        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO); //TODO:priority was previously more favorable, test this to ensure UI doesn't lag
        Looper.prepare(); //create MessageQue to receive messages from USB Controller thread
        UsbController.setHandler(bufferHandler);

        bufferHandler = new Handler(Looper.myLooper()) {
                //do stuff
        };
        Looper.loop();
    }
}
Cody
  • 1,801
  • 3
  • 28
  • 53
  • 2
    I think you can't create a toast from a non-ui thread. Try using runOnUiThread() – sknt Jun 01 '16 at 04:11
  • I'll try this, but this doesn't seem consistent with the behavior i'm seeing because when I comment out the while(true) loop I do see all the Toast messages from this thread's class. – Cody Jun 01 '16 at 04:14
  • Confirmed that this is NOT the problem. – Cody Jun 01 '16 at 04:18
  • 1
    @Cody - just because it does not fail outright does not mean that it is not a violation of the API to try to create a toast from other than the UI thread - *unpredictable* results are a valid outcome of such a violation, too. – Chris Stratton Jun 01 '16 at 04:54
  • 1
    http://stackoverflow.com/questions/3134683/android-toast-in-a-thread – Chris Stratton Jun 01 '16 at 04:55
  • @ChrisStratton Surely enough, you were right. After posting a toast messages to a handler in my main activity they are displaying as expected! Your link was helpful. I ended up implementing this using the style found in the accepted answer here http://stackoverflow.com/questions/14996560/how-to-use-runonuithread-without-getting-cannot-make-a-static-reference-to-the – Cody Jun 02 '16 at 02:47

1 Answers1

0

How about using HandlerThreads, Handlers and Runnables instead? Makes your code a lot cleaner and easier to maintain.

In your onCreate() just create a couple of them:

HandlerThread usbThread = new HandlerThread("USBController");
usbThread.start();
usbHandler = new Handler(usbThread.getLooper());

HandlerThread sensorThread = new HandlerThread("SensorDataBuffer");
sensorThread.start();
sensorHandler = new Handler(sensorThread.getLooper());

Then you create your Runnables and post them to the Handlers

usbHandler.post(new Runnable(){
    run(){
        //....
        numBytesRead = port.read(buffer, 100);
            if (numBytesRead > 0) {
               sensorHandler.post(new Runnable(){run(){//doSomething}});
            }
        //....
        if(isStillRunning)
            usbHandler.post(this);
    }
});

You can let the runnable post itself and it will run forever. From within you can post runnables to other handlers (like the Main Thread Handler) to show your Toasts.

xxtesaxx
  • 6,175
  • 2
  • 31
  • 50
  • Oh and instead of using anonymous runnables, you can have your own ones which do all the necessary stuff you need to initiate in their constructor and use the run() method as your infinite loop – xxtesaxx Jun 01 '16 at 04:53
  • Doesn't this make the code less clean and harder to maintain since i'll now have a huge jumble of logic in my onCreate() method? I thought my code was pretty readable before by having each thread in its own class and .java file – Cody Jun 02 '16 at 00:16