android - 使用语音识别时蓝牙关闭 socket

标签 android sockets android-fragments bluetooth

我已经在我的 APP 上实现了这个 tutorial,但我做了很多改变......我已经创建了一个 TabLayout 所以我做了什么(我认为这不是个好主意,因为它不起作用,所以不是:)) 在我复制的每个 fragment 上粘贴了教程的代码(我创建了套接字以连接到我的 Bluetooth ,我创建了与设备的连接......)并且当我只用一个 Activity 测试它时它运行良好......但是当我添加 TabLayout 时它开始不起作用。我想我可以在 Bluetooth 上完成 Activity 的所有代码,然后使用 Activity 的对象(从 Fragment 我的意思是......)问题是在 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() 然后套接字关闭,我无法向 Bluetooth 发送信息) 我还应该评论另一个 btSocket.close();socket is closed 吗?....

我正在寻找一种解决方案来帮助我实现/指导如何将 Tab 的所有代码实现为另一个类或其他类,如果我从一个 socket.close() 输入 Tab 套接字不会关闭..

顺便说一句,我不确定复制粘贴 Bluetooth 的代码(它们在 onPause() 中与另一个相同......)这是一个好主意......相同的 Tab 全部相同......

如果你们需要更多代码来检查它,请告诉我,我会发布它。

谢谢。

编辑

首先,我将第一个 Bluetooth 发送到 Fragment UUID 如下:
Intent i = new Intent(DeviceListActivity.this, MainActivity.class);
i.putExtra(EXTRA_DEVICE_ADDRESS, address);
i.putExtra("name", name);
startActivity(i);

这是我的 Activity 最重要的代码...

我拥有的第二件事是 MainActivity 但在那里我没有任何关于 MAC address 的信息,因为我在里面的 DeviceListActivity 上使用它...

我有这个 MainActivity 完美运行(这是第一个):

属性
//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="";

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

我有创建 Fragments 的方法
private BluetoothSocket createBluetoothSocket(BluetoothDevice device) throws IOException {
    return device.createRfcommSocketToServiceRecord(BTMODULEUUID);
}

这是我的 Fragment
@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);
    }
}

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

    }
}

这是我调用以查看 socket 是否启用的方法。
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);
        }
    }
}

这是我的 onResume() 类,用于发送和接收来自 onPause() 的东西。
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) {

        }
    }
}

好吧,现在,当我在第二个 Bluetooth 上遇到问题时,我有 与此处相同的 代码……这就是为什么我猜想在尝试使用 ConnectedThread 时会崩溃……当我尝试向 067104 发送某些内容时,好吧..如果代码太多,我很抱歉,但这是我唯一希望你理解我的问题。

最佳答案

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

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

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()并使用这些读取/写入数据。但是,如果您不熟悉使用服务,那么对于如何从 Activity 获取数据和从服务获取数据以传输数据可能会有些困惑。这是一种使用 Intent 的方法。

在同一个 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 将数据从 Activity 发送到服务的方法,但尚未接收该数据。稍后我会谈到这一点。请注意,我使用了 LocalBroadcastReceiver注册 Intent 。这意味着 BroadcastReceiver我们注册的将只获得从您的应用程序中广播并具有匹配操作的 Intent 。我只是用它来简化 Intent 交互,但是将来如果您希望允许外部应用程序使用您的服务发送数据(可能不太可能),那么您需要更改它。无论如何,从您的 Activity 中,执行以下操作以通过您的服务发送数据:
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 类以及它如何使用 Threads 提供一种非阻塞方式来接收来自 BluetoothSocket 的 InputStream 的数据。

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

好的,现在让我们看看如何使用 Service 从远程蓝牙设备接收数据。关于服务需要了解的一件事是它们不是在与您的 Activity 不同的线程上运行的。虽然它们维护它们的状态并且它们的生命周期功能与 Activity 的那些分离,但它们仍然在主 UI 线程上执行。这意味着如果您在 Service 中放置缓慢或阻塞的代码,它会分别减慢或卡住您的 Activity 的 UI。这是我们绝对想要避免的行为,因此当我们考虑从蓝牙设备接收数据(阻塞操作)时,我们需要通过在自定义服务类中创建一个新线程来处理该操作。让我们定义一个自定义类,将 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;
                }
            }
        }
    }
}

好的,现在您可以通过在 onStartCommand() 的末尾添加几行来启动一个新的 ReceiveThread在服务中:
ReceiveThread receiver = new ReceiveThread(mSocket.getInputStream());
receiver.start();

最后一步是将这些数据实际放入您的 Activity 中。为此,您将创建一个 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));
}
onReceive()每次您的 BluetoothService 的 ReceiveThread 从您的远程蓝牙设备读取新行时,都会调用该方法。根据您的实际应用程序,这可能适合您,也可能不适合您(例如,如果您的程序不是基于文本/命令的并且其中没有换行符)。您可以通过将 ReceiveThread 中的 BufferedReader 替换为另一种类型的 Reader 来更改该行为。

编辑:

在您的代码段中,您构建了一个名为 write 的 stub 方法。你似乎着迷于拥有。拥有这样的方法需要您将其作为来自 Activity 的直接调用来执行,这不是您想要的。如果您查看这篇文章,您会看到我已经放置了一些代码,这些代码旨在从您的 Activity 中调用,这些代码使用 Intent 将您的数据传送到要编写的服务。查看以 public class MyActivity extends Activity 开头的 fragment .使用 Intent 的要点是 Android 框架会负责将“额外”数据传送到服务,然后在 onReceive() 中解包。 onStartCommand() 中的方法在 Service 中,您可以在其中看到正在写入的 OutputStream。

唯一的另一件事是我确实忘记了 return Service.START_STICKYonStartCommand()服务的方法。你想把它放在任何地方 write您在代码 fragment 中创建的方法,使用 LocalBroadcastManager 放置有关创建和发送 Intent 的代码。

关于android - 使用语音识别时蓝牙关闭 socket ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35944939/

相关文章:

python - 在Python上使用套接字进行通信

android - 使用 Intent.ACTION_CREATE_DOCUMENT 选择文件后保存从 WebView 生成的 PDF 文件

java - fragment 提交时出现 IllegalStatementException

android - EditText 值被 backpress 上的另一个编辑文本替换

java - Android (Java) 中 MediaRecorder 的 LocalSocket(Unix 域)客户端-服务器数据流问题

java - 从 AKKA 等 Actor 模型框架调用基于线程的 API

android - 重复类 kotlin 类 kotlin 版本 1.3.70

android - 仅滑动 Android 中 View 寻呼机 View 的半 View

android - Edittext输入类型对齐额外空间

android - 如何在 Android Studio 2.2 上刷新预览?