【问题标题】:Android/Java Thread sync: while(true){} causing blockingAndroid/Java 线程同步:while(true){} 导致阻塞
【发布时间】:2016-09-30 06:21:31
【问题描述】:

我正在尝试更好地了解我的 Android 应用程序中线程的行为。出于某种原因,当我在我的一个工作线程中使用 while(true) 时,该线程的 run 方法中在 while(true) 循环之前顺序存在的代码永远不会执行。需要明确的是,我不确定代码(toast 消息)是否实际上没有执行,或者 Android 操作系统处理线程同步的方式是否导致我的 Toast 消息不显示。这种行为似乎是某种阻塞,但我无法弄清楚为什么会发生这种情况。

我的应用程序使用 3 个线程:UI 线程(Android 应用程序中的默认/主线程)、一个在运行时从设备的 USB 端口无限读取数据的线程,以及一个通过来自 USB 的消息处理这些数据的线程-阅读线程。问题似乎出现在我的 USBController 类中。当我注释掉我的无限 while 循环时,循环开始之前的所有 Toast 消息都显示得很好。 当我不注释掉我的 while(true) 时,不会显示任何 TOAST 消息!我对此感到很困惑,我认为我误解了 Android 操作系统线程处理的一些基本知识。即使while循环会导致阻塞,我不认为这是因为它驻留在工作线程中,为什么不会触发while循环之前发生的toast消息?这是同步问题?我是否误用了 Android 的 Handler-Looper 系统?

代码如下。注意:我已经包含了主要活动的相关部分和整个 USBController 类。我对这个类的实现很大程度上依赖于mik3y/usb-serial-for-android 中的 USB 转串行库。我认为没有必要,但我已经包含了包含我的第三个线程 SensorDataBuffer 的类,它从线程 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();
    }
}

【问题讨论】:

  • 我认为你不能从非 ui 线程创建 toast。尝试使用 runOnUiThread()
  • 我会试试这个,但这似乎与我看到的行为不一致,因为当我注释掉 while(true) 循环时,我确实看到了来自该线程类的所有 Toast 消息.
  • 确认这不是问题。
  • @Cody - 仅仅因为它没有完全失败并不意味着尝试从 UI 线程以外的地方创建 toast 并不违反 API - 不可预测 结果也是此类违规的有效结果。

标签: java android multithreading while-loop


【解决方案1】:

改用 HandlerThreads、Handlers 和 Runnables 怎么样?使您的代码更简洁,更易于维护。

在你的 onCreate() 中创建几个:

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

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

然后你创建你的 Runnables 并将它们发布到处理程序

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

您可以让可运行的帖子本身,它会永远运行。您可以在内部将 runnables 发布到其他处理程序(如主线程处理程序)以显示您的 Toast。

【讨论】:

  • 哦,您可以拥有自己的可运行对象,而不是使用匿名可运行对象,它们会在构造函数中执行您需要启动的所有必要工作,并使用 run() 方法作为无限循环
  • 这是否会降低代码的简洁性和维护难度,因为我现在的 onCreate() 方法中有大量的逻辑混乱?我认为我的代码之前通过将每个线程都放在自己的类和 .java 文件中而非常易读
猜你喜欢
  • 1970-01-01
  • 2017-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-18
  • 1970-01-01
相关资源
最近更新 更多