语音识别时使用蓝​​牙关闭套接字

我已经在我的APP上实现了这个tutorial,但是我做了很多更改….我创建了TabLayout,所以我做了(我不认为这是个好主意,不是因为它不起作用:))在每个片段上我都复制粘贴了本教程的代码(我创建了套接字以连接到我的蓝牙,我创建了与设备的连接…),当我仅使用一个Activity测试它时,它工作得很好…但是当我添加TabLayout时,它开始不起作用.我想我可以在Activity上完成蓝牙的所有代码,然后使用该Activity的对象(从我的意思是片段…开始),问题是onPause()上有这个:

@Override
public void onPause() {
    super.onPause();
    Toast.makeText(getActivity(), "onPause", Toast.LENGTH_SHORT).show();
        try {
            btSocket.close();
        } catch (IOException e2) {

        }
}

每次我使用这个:

private void startVoiceRecognitionActivity(){
    Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
            RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
    intent.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.VoiceControllerText));
    startActivityForResult(intent, REQUEST_CODE);
}

它进入onPause(),然后关闭套接字,并且我无法向蓝牙发送信息,我试图注释该行btSocket.close();.但是错误表明套接字已关闭,我没有注释另一个Tab的行(我只有2个),我是否也应该注释另一个Tab的socket.close()?

我正在寻找一种解决方案,该解决方案可以帮助我实现/指导如何将所有蓝牙代码实现为另一个类或其他东西,如果我从一个选项卡输入onPause(),则套接字不会关闭.

顺便说一句,我不确定是否复制粘贴蓝牙的代码(它们在一个Fragment中与另一个Fragment是相同的….)是个好主意…相同的UUID都一样…

如果你们需要更多代码来签出,请告诉我,我将其发布.

谢谢.

编辑

首先,我有第一个活动,该活动已发送给MainActivity MAC地址,如下所示:

Intent i = new Intent(DeviceListActivity.this, MainActivity.class);
i.putExtra(EXTRA_DEVICE_ADDRESS, address);
i.putExtra("name", name);
startActivity(i);

这是我的DeviceListActivity中最重要的代码…

我拥有的第二件事是MainActivity,但是那里没有关于蓝牙的任何东西,因为我在它的Fragments上做了一些处理…

我有一个完美的片段(这是第一个):

Atributes

//Sending info
Handler bluetoothIn;
private ConnectedThread mConnectedThread;
final int handlerState = 0;                        //used to identify handler message
private BluetoothAdapter btAdapter = null;
private BluetoothSocket btSocket = null;
// SPP UUID service - this should work for most devices
private static final UUID BTMODULEUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
// String for MAC address
private static String address="";

在onCreate()中,我称之为:

btAdapter = BluetoothAdapter.getDefaultAdapter();// get Bluetooth adapter
    if (btAdapter == null) {
        Toast.makeText(getActivity(), getString(R.string.BtNotSupported), Toast.LENGTH_SHORT).show();
    }
checkBTState();

我有创建套接字的方法

private BluetoothSocket createBluetoothSocket(BluetoothDevice device) throws IOException {
    return device.createRfcommSocketToServiceRecord(BTMODULEUUID);
}

这是我的onResume()

@Override
public void onResume() {
    super.onResume();
    //Get MAC del intent
    Intent intent = getActivity().getIntent();
    address = intent.getStringExtra(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
    //Creates a device with the MAC from DeviceListActivity
    if(btAdapter!=null) {
        BluetoothDevice device = btAdapter.getRemoteDevice(address);

        try {
            btSocket = createBluetoothSocket(device);
        } catch (IOException e) {
            ShowSnack(getString(R.string.SocketCreationFailed), Color.RED);
        }
        //Trying to connect
        try {
            btSocket.connect();
        } catch (IOException e) {
            try {
                btSocket.close();
            } catch (IOException e2) {
            }
        }
        mConnectedThread = new ConnectedThread(btSocket);
        mConnectedThread.start();
    }  
    else{
        ShowSnack(getString(R.string.toast_bt_unavailable), Color.RED);
    }
}

这是我的onPause()

  @Override
public void onPause() {
    super.onPause();
    try {
        //Close socket if leaves the Activity
        btSocket.close();
    } catch (IOException e2) {

    }
}

这是我用来查看是否启用蓝牙的方法.

private void checkBTState() {

    if (btAdapter == null) {
        ShowSnack(getString(R.string.toast_bt_unavailable), Color.RED);
    } else {
        if (btAdapter.isEnabled()) {
        } else {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, 1);
        }
    }
}

这是我的ConnectedThread类,用于从蓝牙发送和接收内容.

private class ConnectedThread extends Thread {
 private final InputStream mmInStream;
 private final OutputStream mmOutStream;
    public ConnectedThread(BluetoothSocket socket) {
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) {
        }
        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }
    public void run() {
        byte[] buffer = new byte[256];
        int bytes;
        while (true) {
            try {
                bytes = mmInStream.read(buffer);            //read bytes from input buffer
                String readMessage = new String(buffer, 0, bytes);
                bluetoothIn.obtainMessage(handlerState, bytes, -1, readMessage).sendToTarget();
            } catch (IOException e) {
                break;
            }
        }
    }

    //Send stuff to Bluetooth
    public void write(char input) {
        try {
            mmOutStream.write(input);
        } catch (IOException e) {

        }
    }
}

好了,现在,当我在第二个Fragment上遇到问题时,这里的代码相同.这就是为什么我想在尝试使用语音识别时崩溃的原因……当我尝试向蓝牙发送东西时,好..很抱歉,如果代码太多,但这是我唯一希望您理解我的问题的地方.

解决方法:

您面临的一个问题是,您似乎正在尝试从活动中全部管理蓝牙连接的生命周期.如您所见,当Activity的生命周期功能(例如onPause()和onResume())与连接的生命周期不完全匹配时,这可能会导致问题.为了解决这个问题,您可以创建一个服务来处理所有与该蓝牙连接的连接,发送和接收以及与之断开连接的服务.服务的生命周期与活动无关,因此,即使您的用户在活动和片段之间切换,您也可以保持蓝牙连接打开.

要设置服务,请创建一个扩展服务的新类,并将所有蓝牙处理对象放入其中.

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();
        }
    }
}

首次启动服务时,这将设置mSocket对象.在那之后,您将可以通过简单地调用mSocket.getInputStream()和mSocket.getOutputStream()并使用它们读取/写入数据来与远程蓝牙设备进行交互.但是,如果您不熟悉使用服务,那么如何从“活动”中获取数据以及从“服务”中获取数据来传输数据可能会有些困惑.这是一种使用Intents的方法.

在同一BluetoothService类中,重写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;
    }
}

这将为您提供一种使用Intent将活动中的数据发送到服务,但尚未接收该数据的方法.稍后再说.请注意,我已经使用LocalBroadcastReceiver来注册意图.这意味着,我们注册的BroadcastReceiver只会获得既从您的应用程序内部广播又具有匹配动作的意图.我只是用它来简化意图交互,但是在将来,如果您想允许外部应用程序使用您的服务发送数据(可能不太可能),则需要进行更改.无论如何,请从“活动”中执行以下操作,以通过“服务”发送数据:

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);
    }
}

不幸的是,我几分钟后就上课了,现在无法完成这篇文章,但是我将在几个小时内讲解如何设置接收部分.同时,请随时从我的一个项目中检出this code来解决这些问题.查看TransferManager类以及它如何使用线程提供一种非阻塞方式来从BluetoothSocket的InputStream接收数据.

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

好的,现在让我们看看如何使用服务从远程蓝牙设备接收数据.关于服务的一件事是,它们不是在与“活动”不同的线程上运行.尽管它们保持其状态并且其生命周期功能与Activity的状态分离,但是它们仍然都在主UI线程上执行.这意味着,如果将缓慢或阻塞的代码放入服务中,则会分别减慢或冻结活动的UI.我们绝对希望避免这种行为,因此,当我们考虑从Bluetooth设备接收数据时(阻止操作),我们需要通过在自定义Service类中创建新的Thread来处理该操作.让我们定义一个自定义类,将Thread扩展为我们的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;
                }
            }
        }
    }
}

好的,现在您可以通过在Service中onStartCommand()的末尾添加几行来启动一个新的ReceiveThread:

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

最后一步是将这些数据实际放入您的活动中.为此,您将创建一个BroadcastReceiver,以侦听ReceiveThread发送的广播.在您的Activity类中,将其放在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));
}

每当您的BluetoothService的ReceiveThread从您的远程蓝牙设备读取新行时,就会调用onReceive()方法.根据您的实际应用,这可能不适合您(例如,如果您的程序不是基于文本/命令的,并且其中没有换行符).您可以通过用另一种类型的Reader换出ReceiveThread中的BufferedReader来更改该行为.

编辑:

在您的代码段中,您已经构建了一个名为write的存根方法,您似乎已将其固定于此.拥有这样的方法将需要您直接从Activity中执行它,而这并不是您想要的.如果您在这篇文章中查找,您会发现我放置了一些代码,这些代码本来是要从您的Activity中调用的,该代码使用意图将数据传递给要编写的服务.看一下以公共类MyActivity扩展Activity开头的代码片段.使用意图的要点是,Android框架将负责将“额外”数据传递到Service,然后将其解包到Service中onStartCommand()的onReceive()方法中,您可以在其中看到OutputStream是被写入.

唯一的另一件事是我确实忘记了Service的onStartCommand()方法的返回Service.START_STICKY.您想在您的代码段中放置的写方法的任何地方,放置有关使用LocalBroadcastManager创建和发送Intent的代码.

上一篇:Git学习系列之Windows上安装Git之后的一些配置(图文详解)


下一篇:java-查找和配对蓝牙设备