I have an android app designed to pair with other phones and send/receive data over bluetooth. Specifically, when a user clicks a button, it will start discovery, connect to a phone, open a socket etc. In general this works, however, I also want to set it up so that during this whole process, if the user clicks the button again, it will correctly close/stop this process. The problem I'm having is that when the user clicks to stop, sometimes it will close a socket prematurely while the app is still trying to check for incoming data because this is all happening across multiple threads. I'm trying to find the correct way to handle this
So when the user clicks to start the process, discovery will happen and then it will try to connect to the paired device over a socket:
private class ConnectThread extends Thread {
private BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
mmDevice = device;
}
public void run() {
// Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();
while(!this.isInterrupted()){
BluetoothSocket tmp = null;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// SERVICE_UUID is the app's UUID string, also used by the server code
tmp = mmDevice.createRfcommSocketToServiceRecord(SERVICE_UUID);
} catch (IOException e) {
e.printStackTrace();
}
mmSocket = tmp;
if(mmSocket != null){
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
mmSocket.connect();
Log.d(TAG, "Socket connected");
// Do work to manage the connection (in a separate thread)
connectedThread = new ConnectedThread(mmSocket);
connectedThread.start();
break;
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
Log.d(TAG, "Unable to connect socket: " + connectException.getMessage());
}
}
}
}
/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
Log.d(TAG, "Closing socket");
mmSocket.close();
this.interrupt();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Once the socket has connected, I start a different thread to set up the datastreams:
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (!this.isInterrupted()) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI activity
mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
/* Call this from the main activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
/* Call this from the main activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
this.interrupt();
} catch (IOException e) {
e.printStackTrace();
}
}
}
So this all works as far as sending/receiving data goes. But when the user clicks the button a second time to stop the entire process (or kill the connection if it already exists) I get an error. On pressing Stop the following code gets executed:
if(btHelper.discoveryStarted){
this.discoveryStarted = false;
mBluetoothAdapter.cancelDiscovery();
}
//Stop socket connection
if(connectThread != null & connectedThread != null){
connectedThread.cancel();
connectThread.cancel();
}
The error I get is an ioexception: bt socket closed, read return: -1 and its pointing to the line where I'm trying to read from the datastream:
bytes = mmInStream.read(buffer)
So the problem is that its trying to read from the stream, but on button click I have already told the socket to close, therefore theres no socket to read from. Whats the correct way to interrupt these threads to avoid a race condition like this?
Furthermore, how do I set this up so that regardless of where the user is in this process the appropriate threads/sockets/connections will be closed correctly? (for example, sometimes the socket will be opened, but data streams have not been set up yet, vs both/neither have been created)
Related
I'm doing some stuff with bluetooth on android and I would like to connect to one of the discovered devices and open a socket connection towards it.
I've granted all of the needed permissions: Bluetooth, Bluetooth_Admin, Access_Fine_Location and Access_Coarse_Location and ask for them before I do anything with bluetooth.
Now, I've discovered some devices with adapter.startDiscovery(); and activity.registerReceiver(receiver, filter);
In the receiver finds a device of a certain name, I try connecting to it like this:
adapter.cancelDiscovery();
Log.d(TAG, "Create Bond");
device.createBond();
try {
socket = device.createRfcommSocketToServiceRecord(uuid);
Log.d(TAG, "Sleep 10");
sleep(10000);
Log.d(TAG, "Create Socket");
//socket = device.createInsecureRfcommSocketToServiceRecord(uuid);
Log.d(TAG, "Connect socket");
socket.connect();
Log.d(TAG, "Connecting Done");
} catch (Exception e) {
Log.d(TAG, "Failed to connect to device", e);
try {
socket.close();
} catch (Exception e2) {
Log.d(TAG, "Failed to close socket", e2);
}
}
This is a test code with which I'm trying to create a socket and open a connection.
I get the following Exception on .connect():
java.io.IOException: read failed, socket might closed or timeout, read
ret: -1
at android.bluetooth.BluetoothSocket.readAll(BluetoothSocket.java:684)
at android.bluetooth.BluetoothSocket.readInt(BluetoothSocket.java:696)
at android.bluetooth.BluetoothSocket.connect(BluetoothSocket.java:373)
What am I doing wrong.
The bluetooth device I connect to is a Android mobile device, but I plan on using others when I manage to get the connection.
Update1:
Android version is 7.0
use fetchUuidsWithSdp() and getUuids() to find all the published services and their associated UUID values.
You don't need to call device.createBond(); to connect to a Bluetooth device.
Try removing this line. Also check, that your phone is not already paired with the device you're trying to connect to.
You can check that on the Bluetooth settings screen (open it with a long press on the Bluetooth icon on your smartphone.
Here is a sample code to initiates a Bluetooth connection :
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket
// because mmSocket is final.
BluetoothSocket tmp = null;
mmDevice = device;
try {
// Get a BluetoothSocket to connect with the given BluetoothDevice.
// MY_UUID is the app's UUID string, also used in the server code.
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
Log.e(TAG, "Socket's create() method failed", e);
}
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it otherwise slows down the connection.
bluetoothAdapter.cancelDiscovery();
try {
// Connect to the remote device through the socket. This call blocks
// until it succeeds or throws an exception.
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and return.
try {
mmSocket.close();
} catch (IOException closeException) {
Log.e(TAG, "Could not close the client socket", closeException);
}
return;
}
// The connection attempt succeeded. Perform work associated with
// the connection in a separate thread.
manageMyConnectedSocket(mmSocket);
}
// Closes the client socket and causes the thread to finish.
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close the client socket", e);
}
}
}
This code is from Android official doc:
https://developer.android.com/guide/topics/connectivity/bluetooth#ConnectAsAClient
I wrote another code instead of what I was using for server side.
Log.d(TAG,"Start server");
BluetoothServerSocket serverSocket = null;
try {
serverSocket = adapter.listenUsingRfcommWithServiceRecord("ime", uuid);
} catch (Exception e) {
e.printStackTrace();
}
while (true) {
try {
serverSocket.accept();
} catch (Exception e) {
e.printStackTrace();
}
}
I used this code inside of a thread which starts instead of calling the code from the question.
Installing the App with server code on one app and calling "connect" on socket did the trick.
I used the same UUID (previous was random generated, new one was static from string).
stuck in One Issue ,
I am using BluetoothSocket class, I am sending and receiving data with the help of input and output streams.
when App receives large amount of data from input stream, I am killing my app forcefully and after it I am again restarting my app, but InputStream returns me previous data, which is not needed anymore.how to discard that old data?
has Anyone Some Solution for this Issue?
Following is my source code:
public class MyBluetoothService {
private static final String TAG = "MY_APP_DEBUG_TAG";
private Handler mHandler; // handler that gets info from Bluetooth service
// Defines several constants used when transmitting messages between the
// service and the UI.
private interface MessageConstants {
public static final int MESSAGE_READ = 0;
public static final int MESSAGE_WRITE = 1;
public static final int MESSAGE_TOAST = 2;
// ... (Add other message types here as needed.)
}
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private byte[] mmBuffer; // mmBuffer store for the stream
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams; using temp objects because
// member streams are final.
try {
tmpIn = socket.getInputStream();
} catch (IOException e) {
Log.e(TAG, "Error occurred when creating input stream", e);
}
try {
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "Error occurred when creating output stream", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
mmBuffer = new byte[1024];
int numBytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs.
while (true) {
try {
// Read from the InputStream.
numBytes = mmInStream.read(mmBuffer);
// Send the obtained bytes to the UI activity.
Message readMsg = mHandler.obtainMessage(
MessageConstants.MESSAGE_READ, numBytes, -1,
mmBuffer);
readMsg.sendToTarget();
} catch (IOException e) {
Log.d(TAG, "Input stream was disconnected", e);
break;
}
}
}
// Call this from the main activity to send data to the remote device.
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
// Share the sent message with the UI activity.
Message writtenMsg = mHandler.obtainMessage(
MessageConstants.MESSAGE_WRITE, -1, -1, mmBuffer);
writtenMsg.sendToTarget();
} catch (IOException e) {
Log.e(TAG, "Error occurred when sending data", e);
// Send a failure message back to the activity.
Message writeErrorMsg =
mHandler.obtainMessage(MessageConstants.MESSAGE_TOAST);
Bundle bundle = new Bundle();
bundle.putString("toast",
"Couldn't send data to the other device");
writeErrorMsg.setData(bundle);
mHandler.sendMessage(writeErrorMsg);
}
}
// Call this method from the main activity to shut down the connection.
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close the connect socket", e);
}
}
}
}
// Keep listening to the InputStream until an exception occurs
The problem is here. You should keep reading from the stream until end of stream or an exception occurs. You need to break out of the read loop if read() returns -1.
At present you are reading beyond end of stream, and ignoring the condition altogether, so of course the data that was in the buffer on the last successful read is still there.
For your application to keep seeing that data, you must also be ignoring the read count and assuming the buffer was filled, which also is invalid.
I think you should close the socket to manage the bug.
I recommend you to do this in finalizer like the code below.
private class ConnectedThread extends Thread
{
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private byte[] mmBuffer; // mmBuffer store for the stream
#override
protected void finalize() throws Throwable
{
try
{
cancel();
}
finally
{
super.finalize();
}
}
...
Also as I mentioned in comment, it is safer to close every streams before closing the socket.
So, try this cancel() method.
// Call this method from the main activity to shut down the connection.
public void cancel()
{
try {
mmInStream.close();
} catch( NullPointerException | IOException e) {}
try {
mmOutStream.close();
} catch( NullPointerException | IOException e) {}
try
{
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close the connect socket", e);
}
}
And more information about finalize method.
EDIT: bold ones are important than other suggestions.
Reading comments of EJP I understood why your app stops when you get large data : you maybe have to clear buffers before calling read(). And he says that finalizer can happen not to be called by system (I don't know why).
How about breaking the loop when the read() returned -1?
And just now I found a helpful link about proper method to read a stream. I hope it helped.
Code cited from the link
private String extract(InputStream inputStream) throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int read = 0;
while ((read = inputStream.read(buffer, 0, buffer.length)) != -1) {
baos.write(buffer, 0, read);
}
baos.flush();
return new String(baos.toByteArray(), "UTF-8");
}
Also
Though the finalizer may be able not to be called by system closing streams before closing sockets is safer (I read some SO threads before).
I have a frustrating issue, that being my serial Bluetooth receiving code does not always receive the data from it's connection. Most of the time, it works perfectly, but sometimes after connection, it does not receive any data even though I can verify the connection is there, and the other device is sending the data. My receiving code is below:
/**
* Created by tvanderpuy on 12/1/2016.
* Handles communication with BT device
*/
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private final BufferedReader bufferedReader;
public ConnectedThread(BluetoothSocket socket) {
Log.d(TAG, "create ConnectedThread");
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "temp sockets not created", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
// Set up buffered reader
bufferedReader = new BufferedReader(new InputStreamReader(mmInStream));
}
public void run() {
Log.i(TAG, "BEGIN mConnectedThread");
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
String cmd;
// Keep listening to the InputStream until an exception occurs
while (mState == STATE_CONNECTED) {
try {
// Wait for new line for buffered reader
cmd = bufferedReader.readLine();
// Send the obtained bytes to the UI activity
if (!cmd.isEmpty())
mHandler.obtainMessage(MESSAGE_READ, cmd.getBytes().length, -1, cmd.getBytes())
.sendToTarget();
Log.v(TAG, "Command: " + cmd);
} catch (IOException e) {
Log.e(TAG, "disconnected", e);
connectionLost();
break;
}
}
}
/* Call this from the main activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) {
}
}
/* Call this from the main activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
}
}
}
}
Please let me know if you have any insight into this.
Thank you,
Tom
I need to send a list of commands to OBD port with some delay because the ELM327 can't manage all commands together...
I'm trying with this code but not work
public void repeatCommand(){
for (final String command : commandArray){
Log.d(TAG, "Giro for");
final Handler handlerTimed = new Handler();
handlerTimed.postDelayed(new Runnable() {
#Override
public void run() {
//Do something after 100ms
sendMessage(command);
}
}, 1000);
}
/*String message = "010C\r";
sendMessage(message);*/
}
It's only send the first command after 1 sec but the other commands nope.
How can i send all comands delayed for let the write to manage all commands sended to the OBD?
Ok i use the suggested method that send the first command and wait for response.... when get response, send the next message.
private synchronized void manage(BluetoothSocket socket, BluetoothDevice
device) {
Log.d(TAG, "connected, Socket Type:");
// Cancel the thread that completed the connection
if (mConnectThread != null) {
mConnectThread.cancel();
mConnectThread = null;
}
// Cancel any thread currently running a connection
if (mConnectedThread != null) {
mConnectedThread.cancel();
mConnectedThread = null;
}
// Cancel any thread currently managing connections
if (mManageThread != null) {
mManageThread.cancel();
mManageThread = null;
}
// Start the thread to manage the connection and perform transmissions
mManageThread = new ManageDataThread(socket);
mManageThread.start();
// Send the name of the connected device back to the UI Activity
Message msg = mHandler.obtainMessage(Constants.MESSAGE_DEVICE_NAME);
Bundle bundle = new Bundle();
bundle.putString(Constants.DEVICE_NAME, device.getName());
msg.setData(bundle);
mHandler.sendMessage(msg);
// Update UI title
updateUserInterfaceTitle();
}
Here the Thread that manage connection..
public class ManageDataThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private boolean wait_response = false;
public ManageDataThread(BluetoothSocket socket) {
Log.d(TAG, "create ManageDataThread: ");
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the BluetoothSocket input and output streams
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "temp sockets not created", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
mState = STATE_CONNECTED;
}
public void run() {
while(true) {
for (final String command : commandArray) {
byte[] send = command.getBytes();
write(send);
//mState = STATE_WAIT_RESPONSE;
byte[] buffer = new byte[1024];
int bytes;
// Keep listening to the InputStream while connected
while (wait_response) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
//TODO devo gestire l'arrivo di bytes
ObdCommand obc = new ObdCommand();
obc.readResult(mmInStream);
formattedMessage = obc.getResult();
//buffer = (byte) obc.getBuffer();
// Send the obtained bytes to the UI Activity
mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, formattedMessage)
.sendToTarget();
wait_response = false;
} catch (IOException e) {
Log.e(TAG, "disconnected", e);
connectionLost();
break;
}
}
}
}
}
It's little bit imperfect but now it's work....
I will open a new post for stop it and update the array list of commands because if i change the list of commands, the loop keep the old array list, so i need to notify the thread that the arraylist has change
EDIT
Don't use while(true) inside thread, better to use a variable set to True e when need to stop thread set the variable to false, or problem occurs when stop thread....
The proper way to work with OBD2 – which is a serial protocol – is to implement something like a command queue with command-specific callbacks that deliver the response to the command you have requested to it. The command queue should work in a background thread, operating the queue and listening to the serial port. Don't even start w/ delays or similar approaches.
One thing to consider is that when you create a handler and don't provide it with a looper it will use the looper of it's current thread.
So my best guess, since we don't have your code, is that you do in fact run it in a thread which is not the main one. make sure your thread doesn't get killed somewhere along the way and it should work.
Also, a few coding tips:
First, I must say that it would be more efficient to set the handler outside of the loop.
That way you don't waste memory creating a handler in each iteration.
So it should look something like that:
public void repeatCommand()
{
final Handler handlerTimed = new Handler();
for (final String command : commandArray){
Log.d(TAG, "Giro for");
handlerTimed.postDelayed(new Runnable() {
#Override
public void run() {
//Do something after 100ms
sendMessage(command);
}
}, 1000);
}
}
Second, I strongly recommend that you will set the consts of your class, like that 1000 value.
I'm trying to create bluetooth application, using this tutorial:
https://developer.android.com/guide/topics/connectivity/bluetooth.html
There is class ConnectedThread:
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI activity
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}
/* Call this from the main activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/* Call this from the main activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
Comment says I need to call write() method from main activity, but there is actually no instance of ConnectedThread class, it is just started from another class. How can I use this method?
EDIT1:
I see why I can't use it that way.
It doesn't work, because ConnectedThread is defined and declared at the point in the program and when program leaves it, it no longer exists, right? So, should I define object of ConnectedThread visible in whole activity and just define it in Connect thread. Instead:
ConnectedThread mConnectedThread = new ConnectedThread(mmSocket);
mConnectedThread.start();
I could use:
mConnectedThread = new ConnectedThread(mmSocket);
mConnectedThread.start();
Right?