I'm developing an GPS Bluetooth App (NMEA parsing from a 10 Hz GPS modul) and I have the following problem:
Some devices/Android versions receive the bluetooth NMEA data not like every ~100ms (in a 10Hz GPS modul case) but more like ~200-300ms and I get the data for many timestamps at once. This is ok for postprocessing but for a real time view where I want to update the activity with the data this is not great.
Bad Example: Jerky Bluetooth Data Receive
Here is an example from a console output of the NMEA data over the readLine() function, where it's not working properly (just compare the output console time to the UTC time of the GPS):
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 <--- in a TextView this would be not visible
13:35:11.805 NMEA UTC 123618.577 <--- in a TextView this would be visible for only 5ms
13:35:11.810 NMEA UTC 123618.677 <--- this value would be visible in a TextView because there is a long pause (245ms)
13:35:12.055 NMEA UTC 123618.777 <--- not visible
13:35:12.055 NMEA UTC 123618.877 <--- visible for 250 ms
13:35:12.305 NMEA UTC 123618.977
So you see the console time i.e. "13:35:11.805" twice and a very short time later "13:35:11.810". This mean if I use this data for a TextView I will just see the last line "123618.677" in it and then there is a long pause and I would see "123618.877". Basically the readLine() function will be called like 2-3 times very fast. Then there is a pause of 200-300ms and again the same thing happen. No data in between is visible. It is a jerky updating on a TextView.
Good Example: Uniform Bluetooth Data Receive
This is a good example:
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 <--- All NMEA sentences were received about equally in ~100ms distances (uniform textView updating)
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
In this case the data will be received about every 100ms and updating a TextView with this is great. It looks uniform.
The good example work every time on Galaxy S Plus(Android 4.3.1). On the Galaxy S3(Android 4.3) and Huawai P40 Pro (Android 10) it's like in the bad example. But the very interesting thing is when I connect, disconnect and connect again fast in my App it's often switches to the fast and steady transfer on the Huawai P40 Pro but not so often on the Galaxy S3. Or if I decouple the bluetooth device from the phone and reconnect it over my App (with pin input) it's also work sometimes good. After reconnecting again the most time it's bad again. I tested some other Bluetooth GPS Apps and they behave the same => unsteady transmission frequency. Has anyone experienced the same behavior? How can I solve this?
Code Example
This code leads to the bad console output. You have to change the MAC address for your GPS modul and add an "activity_logging_extern_gps_layout_test.xml" file with the "txtTime" TextView:
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);
}
}
}
}


It's not possible to give a definitive answer here without more diagnostic info but try the following:
Put a breakpoint on the following line:
if((line = mmBuffReader.readLine()) != null)Keep hitting the 'continue' button on your debugger and look at the formatting of your incoming $GPGGA lines - are there parts missing or do eg 2 lines joint together or do they all look perfect.
Is there a checksum at the end of the NMEA sentence on your input stream, and if so (and you can find out how it is created from the manual of your GPS receiver) then consider checking against it in your code & displaying a count of any failed values.
Corrupted incoming data (at the readLine() level) will indicate either a hardware issue or a lower level OS issue and will be unlikely to be in your own code structure.
A hardware issue (and I've had this problem) might be an incompatibility between the GPS receiver and your variant of device which could cause interruptions in the comms between GPS and tablet, and if the GPS has an output buffer this will start to fill - the result will be a delay on the tablet screen followed by a 'rush' of data when the data finally gets through.
This would explain variations between devices in your case.
In my case the issue was resolved by the GPS manufacturer making a hardware change to their receiver.
Having said that, I'm now having a similar issue after an update from Android 11 to 13 using hardware that previously was working & which I haven't resolved yet.
If all your NMEA data is coming in OK then try changing your display method and instead of using a handler try just adding each incoming NMEA sentence to eg an arrayList and wrapping that up within a separate thread to display on your screen - or at least something to test whether the handler is the sticking point.