Bluetooth clossing sockets when using voice recognition

后端 未结 1 1186
独厮守ぢ
独厮守ぢ 2021-01-24 02:11

I\'ve implemented this tutorial on my APP, but I did many changes.... I\'ve created a TabLayout so what I did (I don\'t think that\'s the good idea, well it is not

相关标签:
1条回答
  • 2021-01-24 02:55

    One problem that you're facing is that it seems that you're trying to manage the lifecycle of your Bluetooth connection all from within your activity. As you've seen, this can cause problems when the Activity's lifecycle functions (such as onPause() and onResume()) don't perfectly align with the lifetime of your connection. To solve this, you can create a Service that handles all of your connecting, sending and receiving, and disconnecting from that Bluetooth connection. The Service's lifetime is independent from the Activity, so even if your user is switching between Activities and Fragments, you can keep the Bluetooth connection open.

    To set up your Service, make a new class that extends Service and put all of your Bluetooth handling objects in it.

    public class BluetoothService extends Service {
        public static final String BLUETOOTH_SERIAL_UUID = "00001101-0000-1000-8000-00805F9B34FB";
        private BluetoothSocket mSocket;
        private String mAddress = "bluetooth_mac_address_here";
    
        public void onCreate() {
            //Set up Bluetooth socket.
            BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
            if(btAdapter.isEnabled()) {
                BluetoothDevice btDevice = btAdapter.getRemoteDevice(mAddress);
                mSocket = btDevice.createRfcommSocketToServiceRecord(BLUETOOTH_SERIAL_UUID);
                btAdapter.cancelDiscovery();
                mSocket.connect();
            }
        }
    }
    

    This sets up the mSocket object when the Service is first launched. After that point, you'll be able to interact with the remote bluetooth device by simple calls to mSocket.getInputStream() and mSocket.getOutputStream() and reading/writing data using those. However, if you're not familiar with using Services, it can be a little confusing as to how to get your data from the Activity to and from the Service to transfer your data. Here's a way to do it using Intents.

    Inside the same BluetoothService class, override onStartCommand():

    public class BluetoothService extends Service {
    ...
    public static final String ACTION_SEND_DATA = "send_data";
    public static final String ACTION_RECEIVED_DATA = "received_data";
    public static final String EXTRA_BLUETOOTH_DATA = "bluetooth_data";
    
    public int onStartCommand(Intent intent, int flags, int startId) {
        //Register a BroadcastReceiver to handle "send" requests.
        LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                //Parse your data to send from the intent.
                if(intent.getAction().equals(ACTION_SEND_DATA)) {
                    byte[] data = intent.getByteArrayExtra(EXTRA_BLUETOOTH_DATA);
                    //Send the data over the Bluetooth Socket.
                    try {
                        mSocket.getOutputStream().write(data);
                    } catch(IOException ioe) {
                        //This might happen if you try to write to a closed connection.
                        ioe.printStackTrace();
                    }
                }
            }
            return Service.START_STICKY;
        }
    }
    

    This will give you a way to use Intents to send your data from an Activity to the Service, but not yet to receive that data. I'll get to that later. Note that I've used LocalBroadcastReceiver to register the intent. This means that the BroadcastReceiver that we register will only be given intents that were both broadcast from within your app and have a matching action. I just used that to simplify the intent interactions, but in the future if you want to allow external apps to send data using your service (probably unlikely), then you'll need to change that. Anyway, from your Activity, do the following to send the data through your Service:

    public class MyActivity extends Activity {
        public void onCreate(Bundle savedInstanceState) {
            ...
            String myString = "This is some data I want to send!";
            //Create an intent with action saying to send data
            //with the byte[] of data you want to send as an extra.
            Intent sendIntent = new Intent(BluetoothService.ACTION_SEND_DATA);
            sendIntent.putExtra(BluetoothService.EXTRA_BLUETOOTH_DATA, myString.getBytes());
            //Sends the intent to any BroadcastReceivers that have registered receivers for its action.
            LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
        }
    }
    

    Unfortunately I have class in a few minutes and can't finish this post right now, but I'll be on in a few hours to cover how to set up the receiving part. In the meantime, feel free to check out this code from a project of mine that solves exactly these problems. Look at the TransferManager class and how it uses Threads to provide a non-blocking way to receive data from the InputStream of the BluetoothSocket.

    ==========================================================================

    Ok, now let's look at how you can use your Service to receive data from your remote Bluetooth device. One thing to know about Services is that they are not run on separate threads from your Activities. While they maintain their state and their lifecycle functions are decoupled from those of Activities, they are still both executed on the main UI thread. This means that if you put code in your Service that is slow or blocking, it will respectively slow down or freeze your Activity's UI. This is behavior that we definitely want to avoid, so when we consider receiving data from a Bluetooth device (a blocking operation), we need to handle that operation by creating a new Thread within the custom Service class. Let's define a custom class that extends Thread as an inner class of our BluetoothService:

    public class BluetoothService extends Service {
        ...
        public void onCreate() {...}
        public int onStartCommand(...) {...}
    
        public static class ReceiveThread extends Thread {
            private boolean isRunning;
            private InputStream mBluetoothInputStream;
    
            public ReceiveThread(InputStream bluetoothInputStream) {
                mBluetoothInputStream = bluetoothInputStream;
                isRunning = true;
            }
    
            @Override
            public void run() {
                BufferedReader bufferedReader = new BufferedReader(
                        new InputStreamReader(mBluetoothInputStream));
                String line;
                while(isRunning) {
                    try {
                        //This is the line that blocks until a newline is read in.
                        line = bufferedReader.readLine();
                    } catch(IOException ioe) {
                        //This happens if the InputStream is closed.
                        ioe.printStackTrace();
                        //Stop the thread from looping.
                        isRunning = false;
                    }
    
                    //Make sure our line in isn't null or blank.
                    if(line == null || line.equals("") {
                        continue; //Start again at top of while loop.
                    }
    
                    //Notify your Activity about the new data.
                    Intent receivedIntent = new Intent(BluetoothService.this, MyActivity.class);
                    receivedIntent.setAction(ACTION_RECEIVED_DATA);
                    receivedIntent.putExtra(EXTRA_BLUETOOTH_DATA);
                    LocalBroadcastManager.getInstance(BluetoothService.this).sendBroadcast(receivedIntent);
    
                    try {
                        //This is an arbitrary sleep time just to prevent
                        //this from looping without any restriction.
                        Thread.sleep(20);
                    } catch(InterruptedException e) {
                        //This happens if the Thread is interrupted for any reason.
                        e.printStackTrace();
                        isRunning = false;
                    }
                }
            }
        }
    }
    

    Ok, now you can spin up a new ReceiveThread by throwing a few lines onto the end of onStartCommand() in the Service:

    ReceiveThread receiver = new ReceiveThread(mSocket.getInputStream());
    receiver.start();
    

    The last step is to actually get that data into your Activity. To do that, you'll create a BroadcastReceiver that listens for the broadcasts sent out by the ReceiveThread. In your Activity class, put this at the end of onCreate():

    public void onCreate() {
        ...
        LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                //Get your data out of the intent.
                byte[] data = intent.getByteArrayExtra(BluetoothService.EXTRA_BLUETOOTH_DATA);
            }
        }, new IntentFilter(BluetoothService.ACTION_RECEIVED_DATA));
    }
    

    The onReceive() method gets called every time your BluetoothService's ReceiveThread reads a new line from your remote bluetooth device. Depending on your actual application, this may or may not be suitable for you (for example, if your program isn't text/command based and has no newline characters in it). You can change that behavior by swapping out the BufferedReader in the ReceiveThread with another type of Reader.

    EDIT:

    In your snippet you've built a stub method called write that you seem to be fixated on having. Having a method like this would require you to execute it as a direct call from the Activity, which isn't what you want. If you look up in this post, you'll see that I've put some code that was meant to be called from your Activity which uses intents to deliver your data to the Service to be written. Look at the snippet beginning with public class MyActivity extends Activity. The point of using intents is that the Android framework will take care of carrying the "extra" data over to the Service, which is then unpackaged in the onReceive() method in onStartCommand() in the Service, where you can see the OutputStream is being written to.

    The only other thing is that I did forget the return Service.START_STICKY for the onStartCommand() method of the Service. Everywhere you would want to put that write method that you made in your snippet, put the code about creating and sending the Intent using the LocalBroadcastManager.

    0 讨论(0)
提交回复
热议问题