java Android 上的实时蓝牙 SPP 数据流仅工作 5 秒
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27229813/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Real-time Bluetooth SPP data streaming on Android only works for 5 seconds
提问by jpo38
I have a home-made bluetooth device measuring ECG at 500Hz: every 2 ms the device sends 9 bytes of data (header, ECG measurment, footer). So this is roughly a 9*500=4.5kbytes/s data stream.
我有一个自制的蓝牙设备,以 500Hz 的频率测量心电图:设备每 2 毫秒发送 9 个字节的数据(页眉、心电图测量、页脚)。所以这大概是一个 9*500=4.5kbytes/s 的数据流。
I have a C++ Windows program able to connect the device and retrieve the data stream (displaying it with Qt/qwt). In this case, I use Windows control panel to bond the device and I connect it via a virtual COM port using boost serial_port interface. This works perfectly and I'm receiving my data stream in real time: I get a measurment point every 2ms or so.
我有一个 C++ Windows 程序能够连接设备并检索数据流(用 Qt/qwt 显示它)。在这种情况下,我使用 Windows 控制面板来绑定设备,并使用 boost serial_port 接口通过虚拟 COM 端口连接它。这非常有效,我正在实时接收我的数据流:我每 2 毫秒左右得到一个测量点。
I ported the whole C++ program on Android via QtCreator (Qt 5.3.2). I had real-time issues. Data stream was in "real-time" for the first 5 seconds, and then performance would dramatically slow down (see How to do good real-time data streaming using Java Android SDK).
我通过 QtCreator (Qt 5.3.2) 在 Android 上移植了整个 C++ 程序。我有实时问题。数据流在前 5 秒处于“实时”状态,然后性能会显着降低(请参阅如何使用 Java Android SDK 进行良好的实时数据流传输)。
Because I thougth the problem could be due to C++/Qt, I wrote a completely blank pure Java/Android project using Eclipse. And it has the same problem!!!
因为我认为问题可能是由 C++/Qt 引起的,所以我使用 Eclipse 编写了一个完全空白的纯 Java/Android 项目。它有同样的问题!!!
Questions are: Is there something wrong with this code? Why am I receiving data in real-time for only the 5 first seconds? What happens after 5 seconds of intensive BT usage on Android platform and why does it slow down the BT data reception?
问题是:这段代码有什么问题吗?为什么我只在前 5 秒实时接收数据?在Android平台上密集使用BT 5秒后会发生什么,为什么会减慢BT数据接收?
Here is my Java program:
这是我的 Java 程序:
BluetoothHelper.java (with functions to connect/disconnect/read and write data:
BluetoothHelper.java(具有连接/断开/读取和写入数据的功能:
package com.example.helloworld;
import android.util.Log;
import android.content.Context;
import android.os.Bundle;
import java.util.Locale;
import java.util.concurrent.Semaphore;
import java.lang.String;
import java.lang.Thread;
import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.lang.InterruptedException;
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothManager;
import android.util.SparseArray;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import java.util.UUID;
import java.util.Date;
import java.util.Calendar;
import java.util.Vector;
import java.util.Set;
import java.util.Arrays;
public class BluetoothHelper
{
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothDevice mDevice;
private BluetoothSocket mSocket;
private OutputStream mOutputStream;
private InputStream mInputStream;
private BroadcastReceiver mReceiver;
private Activity myActivity;
private Vector<BluetoothDevice> mDevices;
private byte[] mHeader;
private byte[] mFrame;
public BluetoothHelper(Activity a)
{
myActivity = a;
mHeader = new byte[3];
mFrame = new byte[256];
mDevices = new Vector();
}
/* Check bluetooth is enabled, return "" if OK, else, return error string */
public String initializeBluetooth(){
String error = "";
System.out.println("Initializing bluetooth...");
mBluetoothManager = (BluetoothManager) myActivity.getSystemService(Context.BLUETOOTH_SERVICE);
if ( mBluetoothManager == null )
{
error = "Bluetooth manager is not found";
}
else
{
mBluetoothAdapter = mBluetoothManager.getAdapter();
if( mBluetoothAdapter == null )
{
error = "Bluetooth adapter is not found";
}
else if( ! mBluetoothAdapter.isEnabled() )
{
error = "Bluetooth adapter is off";
}
else
{
System.out.println("Bluetooth successfully initialized");
return "";
}
}
return error;
}
private void addDevice( final BluetoothDevice device )
{
mDevices.add(device);
}
public Vector<BluetoothDevice> getDevices() { return mDevices; }
/* Clear previously detected device list */
public boolean clearDeviceList(){
// Clear old list
mDevices.clear();
return true;
}
/* Fill local device list with paired devices */
public boolean addPairedDevices(){
//System.out.println("Entering addPairedDevices");
if( mBluetoothAdapter == null )
{
System.out.println("No bluetooth adapter");
return false;
}
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0)
{
//System.out.println("Found paired devices");
// Loop through paired devices
for (BluetoothDevice device : pairedDevices)
{
addDevice( device );
}
}
return true;
}
public String connectToDevice(final BluetoothDevice device)
{
if ( mDevice != null )
disconnectDevice();
if( mBluetoothAdapter == null || myActivity == null )
return "System not initialized or bluetooth not active";
if ( device.getBondState() != BluetoothDevice.BOND_BONDED )
{
// TODO: find a way to do a synchronized bounding operation
return "Device is not bonded";
}
final boolean[] the_result = new boolean[1];
the_result[0] = false;
final Semaphore mutex = new Semaphore(0);
Runnable connectRunnable = new Runnable() {
@Override
public void run() {
UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
try
{
mSocket = device.createInsecureRfcommSocketToServiceRecord( MY_UUID );
System.out.println("Created RFcomm socket");
mSocket.connect();
if ( mSocket.isConnected() )
{
System.out.println("Connected RFcomm socket");
mOutputStream = mSocket.getOutputStream();
mInputStream = mSocket.getInputStream();
System.out.println("Retrieved output stream");
the_result[0] = true;
}
else
{
System.out.println("Failed to connect RFcomm socket");
}
}
catch (IOException e)
{
System.out.println("Failed to open RFcomm socket (createRfcommSocketToServiceRecord)");
System.out.println(e.toString());
}
mutex.release();
}
};
myActivity.runOnUiThread( connectRunnable );
// waiting for thread to be completed...
try {
mutex.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
if ( the_result[0] )
{
System.out.println("Connection succeeded");
return "";
}
else
{
System.out.println("Connection failed");
return "Failed to connect device";
}
}
/* Request to disconnect the device */
public boolean disconnectDevice(){
System.out.println("Disconnecting device...");
if ( mSocket != null )
{
// block read/write
mOutputStream = null;
mInputStream = null;
try
{
mSocket.close();
}
catch( IOException e )
{
e.printStackTrace();
return false;
}
mSocket = null;
}
mDevice = null;
return true;
}
/* Send bytes to the connected device */
public boolean writeData( byte[] buffer )
{
if( mOutputStream == null )
{
System.out.println("No connection, can't send data");
}
else
{
try
{
mOutputStream.write( buffer );
return true;
}
catch (IOException e)
{
System.out.println( "Failed to send data" );
e.printStackTrace();
}
}
return false;
}
public static String byteArrayToHex(byte[] a, int size) {
StringBuilder sb = new StringBuilder(size * 5);
for( int i = 0; i != size; ++i )
sb.append(String.format("0x%02x ", a[i] & 0xff));
return sb.toString();
}
public int getBytesPending()
{
try
{
return mInputStream.available();
}
catch (IOException e)
{
return 0;
}
}
/* Non blocking read function. Read bytes from the connected device.
* Return number of bytes read
* return 0 if not enough bytes available
* return -1 in case of error
*/
public int readData( byte[] buffer, int size, boolean blocking )
{
if ( mInputStream == null )
{
System.out.println("No connection, can't receive data");
}
else
{
try
{
final boolean verbose = false;
if ( blocking )
{
if ( verbose )
System.out.println( "Blocking request of " + buffer.length + " byte(s)" );
int res = 0;
int temp = 0;
while ( true )
{
temp = mInputStream.read( buffer, res, size - res );
res += temp;
if ( res >= size )
{
break;
}
else
{
if ( verbose )
System.out.println( "Received " + res + " byte(s) to far : " + byteArrayToHex(buffer,size) );
}
try {
Thread.sleep(10);
} catch(InterruptedException ex) {
}
}
if ( verbose )
System.out.println( "Received " + res + " byte(s) : " + byteArrayToHex(buffer,size) );
return res;
}
else
{
int available = mInputStream.available();
if ( verbose && available != 0 )
{
Calendar c = Calendar.getInstance();
Date date = new Date();
c.setTime(date);
c.get(Calendar.MILLISECOND);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String currentTime = sdf.format(date);
System.out.println( currentTime + ":" + c.get(Calendar.MILLISECOND) + " - " + available + " bytes available, requested " + buffer.length );
}
if ( available >= size )
{
int res = mInputStream.read( buffer, 0, size ); // only call read if we know it's not blocking
if ( verbose )
System.out.println( "Received " + res + " byte(s) : " + byteArrayToHex(buffer,size) );
return res;
}
else
{
return 0;
}
}
}
catch (IOException e)
{
System.out.println( "Failed to read data...disconnected?" );
//e.printStackTrace();
}
}
return -1;
}
public byte[] readNextFrame( boolean blocking )
{
if ( readData( mHeader, mHeader.length, blocking ) == mHeader.length )
{
int size = mHeader[2];
if ( size < 0 )
size = -size;
if ( readData( mFrame, size, blocking ) == size )
{
byte[] res = new byte[mHeader.length + size];
System.arraycopy(mHeader, 0, res, 0, mHeader.length);
System.arraycopy(mFrame, 0, res, mHeader.length, size);
return res;
}
}
return null;
}
*/ read frame but without allocating any memory, does not retur condumed bytes */
public boolean eatNextFrame( boolean blocking )
{
if ( readData( mHeader, mHeader.length, blocking ) == mHeader.length )
{
int size = mHeader[2];
if ( size < 0 )
size = -size;
if ( readData( mFrame, size, blocking ) == size )
{
return true;
}
}
return false;
}
public boolean startECG()
{
// some code sending instructions to configure my device
}
}
main Java file, connecting and doing a 10sec acquisition:
主 Java 文件,连接并执行 10 秒获取:
// Here is the code for Medoc:
BluetoothHelper helper = new BluetoothHelper(this);
String error = helper.initializeBluetooth();
if ( error.isEmpty() )
{
if ( helper.addPairedDevices( ) )
{
if ( !helper.getDevices().isEmpty() )
{
if ( helper.getDevices().size() == 1 )
{
BluetoothDevice device = helper.getDevices().firstElement();
error = helper.connectToDevice( device );
if ( error.isEmpty() )
{
if ( helper.startECG() )
{
// acquiere data for 10 seconds
Date start = new Date();
Date end = new Date();
Date empty = null;
int lastMinute = 0;
int maxBufferSize = 0;
boolean receivedData = false;
while ( end.getTime() - start.getTime() < 10 * 1000 )
{
int currentMinute = (int) (( end.getTime() - start.getTime() ) / 1000);
if ( currentMinute != lastMinute )
{
if ( receivedData )
System.out.println( "During second #" + lastMinute + " max buffer size was : " + maxBufferSize );
else
System.out.println( "During second #" + lastMinute + " no data was received!" );
maxBufferSize = 0;
receivedData = false;
lastMinute = currentMinute;
}
if ( helper.eatNextFrame(false) )
{
receivedData = true;
}
if ( helper.getBytesPending() == 0 )
{
if ( empty == null )
{
empty = new Date();
}
}
else
{
if ( empty != null )
{
Date now = new Date();
int elapsed = (int) ( now.getTime() - empty.getTime() );
if ( elapsed > 100 )
System.out.println( "No pending data, during " + elapsed + "ms" );
empty = null;
}
}
maxBufferSize = Math.max( helper.getBytesPending(), maxBufferSize );
end = new Date();
}
AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
dlgAlert.setMessage( "Done" );
dlgAlert.setPositiveButton("Ok",null);
dlgAlert.create().show();
}
else
{
error = "Failed to start ECG";
}
helper.disconnectDevice();
}
}
else
{
error = "Too many devices found";
}
}
else
{
error = "No device found";
}
}
else
{
error = "Failed to scan for devices";
}
}
if ( !error.isEmpty() )
{
AlertDialog.Builder dlgAlert2 = new AlertDialog.Builder(this);
dlgAlert2.setMessage( error );
dlgAlert2.setPositiveButton("Ok",null);
dlgAlert2.create().show();
}
And here, the output of the program:
在这里,程序的输出:
12-01 14:12:51.755: I/System.out(15940): During second #0 max buffer size was : 63
12-01 14:12:52.755: I/System.out(15940): During second #1 max buffer size was : 133
12-01 14:12:53.755: I/System.out(15940): During second #2 max buffer size was : 66
12-01 14:12:54.755: I/System.out(15940): During second #3 max buffer size was : 61
12-01 14:12:55.755: I/System.out(15940): During second #4 max buffer size was : 129
12-01 14:12:56.705: I/System.out(15940): No pending data, during 501ms
12-01 14:12:56.755: I/System.out(15940): During second #5 max buffer size was : 939
12-01 14:12:57.755: I/System.out(15940): During second #6 max buffer size was : 980
12-01 14:12:58.755: I/System.out(15940): During second #7 max buffer size was : 1008
12-01 14:12:59.195: I/System.out(15940): No pending data, during 488ms
12-01 14:12:59.695: I/System.out(15940): No pending data, during 489ms
12-01 14:12:59.755: I/System.out(15940): During second #8 max buffer size was : 990
12-01 14:13:00.185: I/System.out(15940): No pending data, during 490ms
12-01 14:13:01.205: I/System.out(15940): Disconnecting device...
As you can see, during the 5 first seconds, read buffer remains prettry small and there is no moment when buffer is empty for more than 100ms (see code outputing "No pending data"). Then, from the fifth second we :
如您所见,在前 5 秒内,读取缓冲区仍然很小,并且缓冲区空的时间不会超过 100 毫秒(请参阅输出“无挂起数据”的代码)。然后,从第五秒我们:
- start having long periods (~500ms) where the read buffer remains empty (InputStream::available() returns 0) even if my device is permanently sending data to Android.
- can see the buffer max size grows significantly.
- 即使我的设备永久向 Android 发送数据,读取缓冲区仍为空(InputStream::available() 返回 0)的时间段开始较长(~500 毫秒)。
- 可以看到缓冲区最大大小显着增长。
After the 5 first seconds of data acquisition, it's as if data are getting bufferized somewhere and are made available for reading in the InputStream by blocks of ~500ms.....
在数据采集的前 5 秒之后,就好像数据在某处被缓冲,并且可以通过大约 500 毫秒的块在 InputStream 中读取.....
Sometimes, it could be even worst, there is no data being received at all after 5sec:
有时,情况可能更糟,5 秒后根本没有收到任何数据:
12-01 14:35:54.595: I/System.out(16386): During second #0 max buffer size was : 22
12-01 14:35:55.595: I/System.out(16386): During second #1 max buffer size was : 93
12-01 14:35:56.595: I/System.out(16386): During second #2 max buffer size was : 108
12-01 14:35:57.595: I/System.out(16386): During second #3 max buffer size was : 61
12-01 14:35:58.595: I/System.out(16386): During second #4 max buffer size was : 64
12-01 14:35:59.595: I/System.out(16386): During second #5 max buffer size was : 63
12-01 14:36:00.595: I/System.out(16386): During second #6 no data was received!
12-01 14:36:01.595: I/System.out(16386): During second #7 no data was received!
12-01 14:36:02.595: I/System.out(16386): During second #8 no data was received!
Note: I tried to sleep some seconds before creating BluetoothHelper
and before calling startECG()
. Same behaviour (acquisition slows down or stops after 5 seconds).
注意:我尝试在创建BluetoothHelper
之前和调用startECG()
. 相同的行为(采集速度减慢或在 5 秒后停止)。
Edit: I'm experiencing that on:
编辑:我正在经历:
- Nexus 5 phone, Android 4.4.2
- Nexus 7 tablet, Android 4.4.2
- Galaxy S4 with Android 4.4.2
- Nexus 5 手机,安卓 4.4.2
- Nexus 7 平板电脑,安卓 4.4.2
- 搭载 Android 4.4.2 的 Galaxy S4
But not on a Galaxy S3 with custom CyanogenMod 11 Android 4.4.2: data streaming seems perfect, no freezing after 5sec and data are arriving in real-time...
但不是在带有自定义 CyanogenMod 11 Android 4.4.2 的 Galaxy S3 上:数据流看起来很完美,5 秒后没有冻结,数据实时到达......
Edit December 15th:
12 月 15 日编辑:
As proposed, moved read to a separate thread:
Made BluetoothHelper
implement Runnable
and added those methods/attributes to the class:
按照建议,将 read 移至单独的线程:BluetoothHelper
实现Runnable
并将这些方法/属性添加到类中:
private int mFramesReceived;
private long mLongestPause;
public void clearReceived()
{
mFramesReceived = 0;
mLongestPause = 0;
}
public int received()
{
return mFramesReceived;
}
public long longestPause()
{
return mLongestPause;
}
@Override
public void run() {
System.out.println( "Started thread" );
int lastSeconde = 0;
long currentTimeMillis = System.currentTimeMillis();
long started = System.currentTimeMillis();
// Keep listening to the InputStream until an exception occurs
while (true) {
if ( eatNextFrame( true ) )
{
//System.out.println( "Got some data" );
mLongestPause = Math.max( mLongestPause, System.currentTimeMillis() - currentTimeMillis );
currentTimeMillis = System.currentTimeMillis();
mFramesReceived++;
int currentSeconde = (int) (( System.currentTimeMillis() - started ) / 1000);
if ( currentSeconde != lastSeconde )
{
if ( mFramesReceived != 0 )
System.out.println( "During second #" + lastSeconde + " max pause was : " + mLongestPause );
else
System.out.println( "During second #" + lastSeconde + " no data was received!" );
clearReceived();
lastSeconde = currentSeconde;
}
}
else
{
System.out.println( "Failed to get some data, connection closed?" );
break;
}
}
}
Then changed caller to:
然后将来电者更改为:
if ( helper.startECG() )
{
new Thread(helper).start();
try {
Thread.sleep(10000); // wait 10 seconds
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
dlgAlert.setMessage( "Done" );
dlgAlert.setPositiveButton("Ok",null);
dlgAlert.create().show();
}
else
{
error = "Failed to start ECG";
}
helper.disconnectDevice();
And it did not fix the problem, here is the output:
它没有解决问题,这是输出:
During second #0 max pause was : 48
During second #1 max pause was : 45
During second #2 max pause was : 33
During second #3 max pause was : 35
During second #4 max pause was : 58
During second #5 max pause was : 498
During second #6 max pause was : 477
During second #7 max pause was : 480
During second #8 max pause was : 986
During second #9 max pause was : 497
采纳答案by jpo38
This problem is apparently similar to the one reported here.
此问题显然与此处报告的问题相似。
After 5 seconds, I had either a connection lost, either real-time streaming being dramatically slow down.
5 秒后,我要么失去了连接,要么实时流传输速度大大减慢。
As said hereAndroid >4.3 apparently does not like one-way communication exceeding 5 secondes. So I'm now sending a dummy command to the device every 1 seconde (kind of "keep-alive" command) and now Android is happy because it's not a one-way communication anymore...and so data streaming is as good after the fifth second than before!
正如这里所说,Android > 4.3 显然不喜欢超过 5 秒的单向通信。所以我现在每 1 秒向设备发送一个虚拟命令(一种“保持活动”命令),现在 Android 很高兴,因为它不再是单向通信......所以数据流也一样好比之前的第五秒!
回答by kaushik parmar
Use threading concept for simultaneously Read and Write Bytes on peripheral devices. Use android Bluetooth data transfer example to solve problem. You are using normal java class to send and receive data to another device that is not valid approach. You should use Threading concept to send and receive data over Bluetooth.
使用线程概念在外围设备上同时读取和写入字节。使用android蓝牙数据传输示例解决问题。您正在使用普通的 java 类向另一个设备发送和接收数据,这是无效的方法。您应该使用线程概念通过蓝牙发送和接收数据。
please refer below link to read and write data over Bluetooth.
请参考以下链接通过蓝牙读取和写入数据。
http://developer.android.com/guide/topics/connectivity/bluetooth.html
http://developer.android.com/guide/topics/connectivity/bluetooth.html
回答by stealth
You should not rely on InputStream.available()
to tell how many bytes are available in the stream (see https://developer.android.com/reference/java/io/InputStream.html#available()for the details).
Since you know the exact size of your data package (9 bytes), read 9 bytes each time into the buffer: mInputStream.read(buffer, 0, 9)
.
您不应依赖于InputStream.available()
判断流中有多少可用字节(有关详细信息,请参阅https://developer.android.com/reference/java/io/InputStream.html#available())。既然你知道你的数据包(9个字节)的确切大小,每次读取到缓冲区9字节:mInputStream.read(buffer, 0, 9)
。
With Bluetooth it is hard to guarantee real-time binary delivery, as there might be many reasons for delays (e.g. increased distance between devices, obstacles, etc.). Thus, it is usually better to constantly call read
and forward retrieved data portions to handling components. For instance, in one of my projects I implemented Android Service
waiting for the data packages from Bluetooth and notifying UI with received data. You can implement a Service
or an AsyncTask
for this.
使用蓝牙很难保证实时二进制传输,因为延迟的原因可能有很多(例如,设备之间的距离增加、障碍物等)。因此,通常最好不断调用read
检索到的数据部分并将其转发给处理组件。例如,在我的一个项目中,我实现了 AndroidService
等待来自蓝牙的数据包并使用接收到的数据通知 UI。您可以为此实现 aService
或 an AsyncTask
。
Another recommendation: avoid making unnecessary memory allocations in methods you call often (such as readData
). You can measure elapsed time by using System.currentTimeMillis()
. Garbage collection could be one of the reasons you experience degrading performance.
另一个建议:避免在您经常调用的方法(例如readData
)中进行不必要的内存分配。您可以使用 来测量经过的时间System.currentTimeMillis()
。垃圾收集可能是您遇到性能下降的原因之一。