为何部分Android设备无法按10Hz频率接收蓝牙GPS模块的NMEA数据?
我正在开发一款解析10Hz蓝牙GPS模块NMEA数据的App,遇到如下问题:部分Android设备/版本接收蓝牙NMEA数据的间隔并非10Hz模块应有的约100ms,而是200-300ms,且会一次性收到多个时间戳的数据。这对后处理无影响,但会导致实时界面(如TextView)更新卡顿。
异常示例:蓝牙数据接收卡顿
以下是readLine()函数输出的控制台日志,对比控制台输出时间与GPS UTC时间可看出异常:
Console Output Time GPS Time 13:35:11.555 NMEA UTC 123618.277 13:35:11.555 NMEA UTC 123618.377 13:35:11.805 NMEA UTC 123618.477 <--- 在TextView中无法显示 13:35:11.805 NMEA UTC 123618.577 <--- 在TextView中仅显示5ms 13:35:11.810 NMEA UTC 123618.677 <--- 因后续长停顿(245ms),该值会在TextView中显示 13:35:12.055 NMEA UTC 123618.777 <--- 无法显示 13:35:12.055 NMEA UTC 123618.877 <--- 显示250ms 13:35:12.305 NMEA UTC 123618.977
【异常情况界面演示:TextView更新卡顿,数据跳变明显,无法连续显示所有时间戳】
可见控制台时间如13:35:11.805出现两次,紧接着是13:35:11.810,这意味着TextView只会显示最后一行123618.677,随后长时间停顿后显示123618.877。readLine()会快速调用2-3次,然后停顿200-300ms,循环往复,导致TextView更新卡顿。
正常示例:蓝牙数据均匀接收
以下是正常接收的情况:
Console Output Time GPS Time 13:42:37.229 NMEA UTC 124239.073 13:42:37.335 NMEA UTC 124239.173 13:42:37.417 NMEA UTC 124239.273 <--- 13:42:37.522 NMEA UTC 124239.373 <--- 13:42:37.632 NMEA UTC 124239.473 <--- 所有NMEA语句接收间隔约100ms,TextView更新均匀 13:42:37.719 NMEA UTC 124239.573 <--- 13:42:37.826 NMEA UTC 124239.673 <--- 13:42:37.932 NMEA UTC 124239.773 13:42:38.013 NMEA UTC 124239.873 13:42:38.118 NMEA UTC 124239.973
【正常情况界面演示:TextView更新均匀流畅,每约100ms刷新一次时间戳】
此情况下数据约每100ms接收一次,TextView更新流畅均匀。
设备表现差异
该正常情况在Galaxy S Plus(Android 4.3.1)上始终出现,但在Galaxy S3(Android 4.3)和Huawei P40 Pro(Android 10)上则出现异常。有趣的是,在Huawei P40 Pro上快速断开并重新连接App后,常能切换到稳定快速传输,但Galaxy S3上很少出现此情况;通过App重新配对蓝牙设备(输入PIN码)有时也能恢复正常,但多数情况下重新连接后又回到异常状态。测试其他蓝牙GPS App也存在相同的传输频率不稳定问题。是否有人遇到过相同情况?该如何解决?
代码示例
以下代码会导致上述异常控制台输出,需替换GPS模块的MAC地址,并添加包含txtTime TextView的activity_logging_extern_gps_layout_test.xml文件:
package your.packages.activities; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.widget.TextView; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import your.packages.R; public class LiveViewBluetoothGPSActivityTEST extends Activity { public static final int MESSAGE_READ = 2; private TextView txtTime; private BluetoothAdapter mBluetoothAdapter = null; private BluetoothConnectionService mBluetConServ = null; private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch(msg.what) { case MESSAGE_READ: String readMessage = (String) msg.obj; if(readMessage.startsWith("$GPGGA")) { Log.d("NMEA UTC", readMessage); String timestamp = readMessage.split(",")[1]; txtTime.setText("Time: " + timestamp); } break; } } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_logging_extern_gps_layout_test); txtTime = (TextView) findViewById(R.id.txtTime); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); } @Override public void onStart() { super.onStart(); mBluetConServ = new BluetoothConnectionService(this, mHandler); } @Override public synchronized void onResume() { super.onResume(); mBluetConServ.start(); String deviceAddress = "00:11:22:33:44:55"; // put the mac address of your GPS modul here BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceAddress); mBluetConServ.connect(device); } } class BluetoothConnectionService { private static final String TAG = "BluetoothConnectionServ"; private final BluetoothAdapter mAdapter; private final Handler mHandler; private ConnectThread mConnectThread; private ConnectedThread mConnectedThread; public BluetoothConnectionService(Context context, Handler handler) { mAdapter = BluetoothAdapter.getDefaultAdapter(); mHandler = handler; } public synchronized void start() { if(mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if(mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } } public synchronized void connect(BluetoothDevice device) { if(mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } mConnectThread = new ConnectThread(device); mConnectThread.start(); } public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) { mConnectedThread = new ConnectedThread(socket); mConnectedThread.start(); } private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { mmDevice = device; BluetoothSocket tmp = null; try { Method m = device.getClass().getMethod("createRfcommSocket", int.class); tmp = (BluetoothSocket) m.invoke(device, 1); } catch(InvocationTargetException e) { throw new RuntimeException(e); } catch(NoSuchMethodException e) { throw new RuntimeException(e); } catch(IllegalAccessException e) { throw new RuntimeException(e); } mmSocket = tmp; } public void run() { setName("ConnectThread"); mAdapter.cancelDiscovery(); try { mmSocket.connect(); } catch(IOException e) { try { mmSocket.close(); } catch(IOException e2) { Log.e(TAG, "unable to close() socket during connection failure", e2); } BluetoothConnectionService.this.start(); return; } synchronized(BluetoothConnectionService.this) { mConnectThread = null; } connected(mmSocket, mmDevice); } public void cancel() { try { mmSocket.close(); } catch(IOException e) { Log.e(TAG, "close() of connect socket failed", e); } } } private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final BufferedReader mmBuffReader; public ConnectedThread(BluetoothSocket socket) { mmSocket = socket; InputStream tmpIn = null; try { tmpIn = socket.getInputStream(); } catch(IOException e) { Log.e(TAG, "temp sockets not created", e); } mmBuffReader = new BufferedReader(new InputStreamReader(tmpIn)); } public void run() { String line; while(true) { try { if((line = mmBuffReader.readLine()) != null) mHandler.obtainMessage(LiveViewBluetoothGPSActivityTEST.MESSAGE_READ, line).sendToTarget(); } catch(IOException e) { break; } } } public void cancel() { try { mmSocket.close(); } catch(IOException e) { Log.e(TAG, "close() of connect socket failed", e); } } } }
内容的提问来源于stack exchange,提问作者ValvernatoR

