Ok, I am new to android, I'm trying to create an app that interfaces with an arduino via bluetooth. I've seen the sample BluetoothChat and seen how it's using an Handler to communicate between the "service", the threads spawned by it and the MainActivity.
My problem is that I have more than one Activity that needs to use the Bluetooth Service.
For each Activity I have an Handler like this:
mHandler = new Handler(){
#Override
public void handleMessage(Message message) {
switch (message.what){
case BtService.CHANGE_STATE:
if (message.arg1 == BtService.STATE_CONNECTING){
Intent i = new Intent (MainActivity.this,ConnectedActivity.class);
startActivity(i);
}
break;
}
}
};
and in the service constructor I've got this:
private BtService(){
btm = BluetoothAdapter.getDefaultAdapter();
mHandler= new Handler(Looper.getMainLooper());
}
and when I need to send a message I do this:
private synchronized void setState(int state){
mHandler.obtainMessage(CHANGE_STATE, state, -1).sendToTarget();
mState = state;
}
but the messages aren't received in the various other Handlers.
In here is stated that "all of the Handler objects for a particular thread receive the same message." so I can't understand the problem.
Do I need, every time an activity is started, to pass to the service the Handler declared in that Activity to have it receive messages? This seems to work, but it dosen't seem to be a good practice for me.
If you want send the message in all application you should use BroadcastReceiver, I this this is the best way in your case.
Intent intent = new Intent(ApplicationConstants.MY_MESSAGE);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
Receive message in any activity(you cand use this in more then one activity)
BroadcastReceiver connectionUpdates = new BroadcastReceiver() {
#Override
public void onReceive(Context arg0, Intent intent) {
...//TODO here
}
};
LocalBroadcastManager.getInstance(this).registerReceiver(
connectionUpdates ,
new IntentFilter(ApplicationConstants.MY_MESSAGE));
Hope this is helpfull
Cheers,
Instead of having each activity connect through bluetooth, you can extend the Application layer and use that to maintain thread(s) to retrieve and manage the data collected over the bluetooth connection. Then just use a handler in each activity to have them refresh against the data gathered in the Application layer, if needed.
My only Activity with the btAdapter and socket is the first activity to actually need bluetooth information (after menus and bt config activities).
In my first activity onRusume() looks something like this with comments explaining..:
#Override
public void onResume() {
super.onResume();
Log.d(TAG, "...onResume - try connect...");
// Set up a pointer to the remote node using it's address.
BluetoothDevice device = btAdapter.getRemoteDevice(address);
// Two things are needed to make a connection:
// A MAC address, which we got above.
// A Service ID or UUID. In this case we are using the
// UUID for SPP.
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
errorExit("Fatal Error", "In onResume() and socket create failed: " + e.getMessage() + ".");
}
// Discovery is resource intensive. Make sure it isn't going on
// when you attempt to connect and pass your message.
btAdapter.cancelDiscovery();
// Establish the connection. This will block until it connects.
Log.d(TAG, "...Connecting...");
try {
btSocket.connect();
Log.d(TAG, "....Connection ok...");
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
errorExit("Fatal Error", "In onResume() and unable to close socket during connection failure" + e2.getMessage() + ".");
}
}
// Create a data stream so we can talk to server.
Log.d(TAG, "...Create Socket...");
/**
* **Here I am kicking off the thread in the application that retrieves all data
* needed by all my activities. Then it stores the information in its member
* variables. Each activity then refreshes as often as needed, gets the data from
* the application layer it needs and does some logic on it.**
*/
if(mConnectedThread == null) {
mConnectedThread = app.new ConnectedThread(btSocket);
mConnectedThread.start();
}
// This kicks off the handler for this activity that refreshes the activity every
// xxxx ms and checks the data retrieved from bt in the application layer.
startUpdatingTicketView();
}
That is pretty much the core of how i got it to work for me.
Just an additional note... I also tried doing this with the bt communication managed in a background service and could not get it working well. I forget exactly what the issues I was running into were and it is quite possible using a service would work as well, but I did not end up going this route.
Good luck.
Related
I've implemented this tutorial on my APP, but I did many changes.... I've created a TabLayout so what I did (I don't think that's the good idea, well it is not since it doesn't work :)) on each fragment I copy pasted the code of the tutorial (I created the sockets to connect to my Bluetooth, I create connection to the device...) and when I tested it only with one Activity it worked well... but when I added the TabLayout it started to don't work. I think I could do all of the code of the Bluetooth on the Activity and then work with the objects of that Activity (from the Fragment I mean...) the problem is that on onPause() I have this :
#Override
public void onPause() {
super.onPause();
Toast.makeText(getActivity(), "onPause", Toast.LENGTH_SHORT).show();
try {
btSocket.close();
} catch (IOException e2) {
}
}
And everytime I use this :
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);
}
It enters on onPause() and then the socket is closed, and I can't send info to Bluetooth I tried to comment that line btSocket.close(); but the error says that socket is closed, I didn't commented the line of the other Tab (I only have 2) should I comment also the socket.close() of the other Tab?....
I'm looking for a solution that helps to me to implement / guide how to implement all of the code of Bluetooth as another class or something, that if I enter on the onPause() from one Tab the socket doesn't close..
And by the way I'm not sure that copying pasting the code of the Bluetooth (They are the same in one Fragment than the other....) it's a good idea... same UUID same all...
If you guys need more code to check it out, let me know and I'll post it.
Thanks.
EDIT
First I have the first Activity which I sent to the MainActivity the MAC address as follows :
Intent i = new Intent(DeviceListActivity.this, MainActivity.class);
i.putExtra(EXTRA_DEVICE_ADDRESS, address);
i.putExtra("name", name);
startActivity(i);
This is the most important code of my DeviceListActivity...
The second thing that I have is MainActivity but there I don't have anything about Bluetooth because I do stuff with it on Fragments inside of it...
I have this Fragment which works perfect (it's the first one) :
Atributes
//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="";
in onCreate() I call this :
btAdapter = BluetoothAdapter.getDefaultAdapter();// get Bluetooth adapter
if (btAdapter == null) {
Toast.makeText(getActivity(), getString(R.string.BtNotSupported), Toast.LENGTH_SHORT).show();
}
checkBTState();
I have the method to create the socket
private BluetoothSocket createBluetoothSocket(BluetoothDevice device) throws IOException {
return device.createRfcommSocketToServiceRecord(BTMODULEUUID);
}
This is my onResume()
#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);
}
}
This is my onPause()
#Override
public void onPause() {
super.onPause();
try {
//Close socket if leaves the Activity
btSocket.close();
} catch (IOException e2) {
}
}
And this is the method that I call to see if Bluetooth is enabled or not.
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);
}
}
}
This is my ConnectedThread class to send and recieve stuff from Bluetooth.
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) {
}
}
}
Well and now, when I'm having problems is on the second Fragment where I have THE SAME code as here... that's why I guess that crashes when trying to use Voice recognision... when I try to send something to Bluetooth, well.. I'm sorry if that's too much code but that's the only thing that I have hope you understand my problem.
One problem that you're facing is that it seems that you're trying to manage the lifecycle of your Bluetooth connection all from within your activity. As you've seen, this can cause problems when the Activity's lifecycle functions (such as onPause() and onResume()) don't perfectly align with the lifetime of your connection. To solve this, you can create a Service that handles all of your connecting, sending and receiving, and disconnecting from that Bluetooth connection. The Service's lifetime is independent from the Activity, so even if your user is switching between Activities and Fragments, you can keep the Bluetooth connection open.
To set up your Service, make a new class that extends Service and put all of your Bluetooth handling objects in it.
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();
}
}
}
This sets up the mSocket object when the Service is first launched. After that point, you'll be able to interact with the remote bluetooth device by simple calls to mSocket.getInputStream() and mSocket.getOutputStream() and reading/writing data using those. However, if you're not familiar with using Services, it can be a little confusing as to how to get your data from the Activity to and from the Service to transfer your data. Here's a way to do it using Intents.
Inside the same BluetoothService class, override 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;
}
}
This will give you a way to use Intents to send your data from an Activity to the Service, but not yet to receive that data. I'll get to that later. Note that I've used LocalBroadcastReceiver to register the intent. This means that the BroadcastReceiver that we register will only be given intents that were both broadcast from within your app and have a matching action. I just used that to simplify the intent interactions, but in the future if you want to allow external apps to send data using your service (probably unlikely), then you'll need to change that. Anyway, from your Activity, do the following to send the data through your Service:
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);
}
}
Unfortunately I have class in a few minutes and can't finish this post right now, but I'll be on in a few hours to cover how to set up the receiving part. In the meantime, feel free to check out this code from a project of mine that solves exactly these problems. Look at the TransferManager class and how it uses Threads to provide a non-blocking way to receive data from the InputStream of the BluetoothSocket.
==========================================================================
Ok, now let's look at how you can use your Service to receive data from your remote Bluetooth device. One thing to know about Services is that they are not run on separate threads from your Activities. While they maintain their state and their lifecycle functions are decoupled from those of Activities, they are still both executed on the main UI thread. This means that if you put code in your Service that is slow or blocking, it will respectively slow down or freeze your Activity's UI. This is behavior that we definitely want to avoid, so when we consider receiving data from a Bluetooth device (a blocking operation), we need to handle that operation by creating a new Thread within the custom Service class. Let's define a custom class that extends Thread as an inner class of our 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;
}
}
}
}
}
Ok, now you can spin up a new ReceiveThread by throwing a few lines onto the end of onStartCommand() in the Service:
ReceiveThread receiver = new ReceiveThread(mSocket.getInputStream());
receiver.start();
The last step is to actually get that data into your Activity. To do that, you'll create a BroadcastReceiver that listens for the broadcasts sent out by the ReceiveThread. In your Activity class, put this at the end of 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));
}
The onReceive() method gets called every time your BluetoothService's ReceiveThread reads a new line from your remote bluetooth device. Depending on your actual application, this may or may not be suitable for you (for example, if your program isn't text/command based and has no newline characters in it). You can change that behavior by swapping out the BufferedReader in the ReceiveThread with another type of Reader.
EDIT:
In your snippet you've built a stub method called write that you seem to be fixated on having. Having a method like this would require you to execute it as a direct call from the Activity, which isn't what you want. If you look up in this post, you'll see that I've put some code that was meant to be called from your Activity which uses intents to deliver your data to the Service to be written. Look at the snippet beginning with public class MyActivity extends Activity. The point of using intents is that the Android framework will take care of carrying the "extra" data over to the Service, which is then unpackaged in the onReceive() method in onStartCommand() in the Service, where you can see the OutputStream is being written to.
The only other thing is that I did forget the return Service.START_STICKY for the onStartCommand() method of the Service. Everywhere you would want to put that write method that you made in your snippet, put the code about creating and sending the Intent using the LocalBroadcastManager.
I've searched for the relationship between thread and looper, and I just know what the two words's literal meaning now. When come to specific conditions, I'm still a little confused.
I came across this issue when I try to build an app about communicating with a bluetooth device. I got problems in the connect thread.
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
private String mSocketType;
public ConnectThread(BluetoothDevice device, boolean secure) {
mmDevice = device;
BluetoothSocket tmp = null;
mSocketType = secure ? "Secure" : "Insecure";
// Get a BluetoothSocket for a connection with the
// given BluetoothDevice
try {
if (secure) {
tmp = device.createRfcommSocketToServiceRecord(
MY_UUID_SECURE);
} else {
tmp = device.createInsecureRfcommSocketToServiceRecord(
MY_UUID_INSECURE);
}
} catch (IOException e) {
tip("Socket Type: " + mSocketType + "create() failed");
}
mmSocket = tmp;
}
public void run() {
Looper.prepare();
setName("ConnectThread" + mSocketType);
// Always cancel discovery because it will slow down a connection
if(mBluetoothAdapter.isDiscovering())
mBluetoothAdapter.cancelDiscovery();
// Make a connection to the BluetoothSocket
try {
// This is a blocking call and will only return on a
// successful connection or an exception
mmSocket.connect();
} catch (IOException e) {
// Close the socket
try {
mmSocket.close();
} catch (IOException e2) {
e2.printStackTrace();
}
connectionFailed();
return;
}
// Reset the ConnectThread because we're done
synchronized (BluetoothThreads.this) {
mConnectThread = null;
}
// Start the connected thread
connected(mmSocket, mmDevice, mSocketType);
Looper.loop();
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
}
}
}
this code is download from the Android Developers's sample. and the Looper.prepare(), Looper.loop() are added by myself. Withou calling these two methods, the app will crash down. And I got a warning from the android studio:Can't create handler inside thread that has not called Looper.prepare(). That's why I add the two methods.
I want to ask, do I call the two methods in the right way?
Why I must call them while I have not used Toast or Handler as others do?
I want to ask, do I call the two methods in the right way?
Yes
Why I must call them while I have not used Toast or Handler as others do?
Looper.loop() and Looper().prepare() are used to create a MessageQueue and to handle this MessageQueue android recommends to use Handler , so if you use Looper.loop() and Looper.prepare() then you should use Handler also .
Well, finally I figure out what's wrong with my own project. There is nothing to do with the build.gradle, but thank Android Dev all the same.
Actually it's myself to blame. In the sample project BluetoothChat, methods like Log.d() is called to indicate the debug information. But I didn't know where to find Log.d()'s result, so I replaced them with Toast. I thought that would be fine. But I find out that Toast cannot be used in ConnectThread, otherwise it will throw an Exception:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
And I used Toast in method connectionFailed(), that's where the problem is.
Actually, you do not need Looper.prepare() and Looper.loop(). Those are only required when you would like to attach a Handler to the thread, which in your case, I cannot see such requirement.
Essentially, Once you call Looper.prepare(), It assigns a message queue to this thread such that all subsequent messages passed by handlers will be handled one by one in a queue manner.
Note: For debugging always use Log.d() / Log.w() / Log.e() and avoid using toasts.
I have created an application that does the following steps very well -
Connects with the Remote Device (SPP) using Bluetooth socket .
Then listens for the stream coming from the remote bluetooth device
in a separate thread.
Then when data stream comes, it passes the data stream to the handler to parse it.
When data is parsed, a broadcast is sent and the records are created
into the database.
Now I want to add a new functionality -
When the application is in the back ground and is "connected" to remote device, it needs to continue to process the data stream and create records.
So once I get the socket connected, I am passing the result "connected" to the onPostExecute() method.
IMPORTANT NOTE:
1) My all socket related work ( socket connection, socket data parse, data handler ) is in the fragment.
2) Once the connection is established, the private class (Thread - ConnectedThread.java) in the fragment is keep listening to the InputStream
public class EntryFragment extends Fragment{
//More fragment code here then this
public class ConnectedThread extends Thread {
public ConnectedThread(BluetoothSocket socket) {
//code initialization stuff
}
public void run() {
// Keep listening to the InputStream until an exception occurs
while (true)
{
// Read from the InputStream
if(mmInStream.available() > 0)
{
bytes = mmInStream.read(buffer);
mHandler.obtainMessage(MESSAGE_READ,
bytes, -1, buffer).sendToTarget();
}
}
}
}
3) My handler that handles the Read of step 2
case MESSAGE_READ:
//Call to AsyncTask to do background processing of data
new parseStream(getActivity()).execute();
break;
4) I am connected so do something from onPostExecute() of AsyncTask parseStream
#Override
protected void onPostExecute(Void result) {
//Database related work here
//Result is connected so listen to data if app goes to background after this state
if(result.equals("connected"))
{
Log.i(TAG, "CONNECTED TO Remote Device!");
Toast.makeText(getActivity(),"CONNECTED TO Remote
Device!",Toast.LENGTH_SHORT).show();
//Do something when connected
setSetting("STATUS", "Connected");
// Start the thread to manage the connection and perform transmissions
mConnectedThread = new ConnectedThread(socket);
mConnectedThread.start();
//Do I need to call Service here to handle data ?????
Intent serviceIntent= new Intent(context, DataProcessService.class);
getActivity().startService(serviceIntent);
}
}
5) I called service in step 4 with the intention that it will execute when app will go background
and process data. But then how will it communicate with the Fragment because my whole work of
data processing is in the fragment. Do I really need it to process data OR should I call
broadcast receiver here as it can also process in the background ?
I've worked on this quite a bit. I'll share with you what works best in my experience.
-Dedicated bluetooth service, running in it's own separate process, for handling continuous streams of data.
-Avoiding broadcasts when getting & manipulating data; specially if it's fast&large streams of data. This is a mistake that i've made myself in the past when handling bt streams. What i realised is that, it might work OK for small amounts or slow streams, but, broadcasts are very expensive and i saw HUGE performance improvements when replacing them with IPC (UI Thread <- Bluetooth service) to pass the data to be handled in the UI thread.
-IPC, as mentioned previously, definitely the best method to if you'll go for a dedicated bluetooth service. The idea is that you want to bind your Context to the service, in order to send&receive messages. Documentation : http://developer.android.com/guide/components/bound-services.html#Messenger
-When an activity/context bounds to the running service, set an interface so that the activity is registered as to whom the service should reply to. So you will pass any incoming data from the bluetooth radio and send messages to the activity with the new data, avoiding the nasty, unnecessary broadcasts.
-I'm writing a quick, basic example (untested and not compiled) based on my thoughts and online examples (from docs). Hope this is useful
public class BluetoothService extends Service {
//
// API keys for the messages being passed across ui thread <-> service
//
static final int REGISTER_CLIENT = 3;
static final int NEW_DATA = 2;
static final int WRITE_DATA = 1;
static final int CONNECT_BT = 0;
// manages actual connection
private BluetoothManager btManager;
// who is activity and ready to receive messages?
private Messenger clientToReply;
public int onStartCommand(Intent intent, int flags, int startId) {
btManager = new BluetoothManager(this);
return START_STICKY;//makes sure the service keeps running and get's back up if it gets terminated
}
//
// Send data back to your activity
//
public void sendDataToBoundClient(byte[] bytes) {
Message msgToClient = new Message();
msgToClient.what = NEW_DATA;
Bundle bNewData = new Bundle();
bNewData.putByteArray("newData", bytes);
msgToClient.setData(bNewData);
try {
clientToReply.send(msgToClient); //send
} catch (RemoteException e) {
e.printStackTrace(); //couldn't send
}
}
/**
* Handles messages received from a bound Context
*/
public class MessageHandler extends Handler {
/* (non-Javadoc)
* #see android.os.Handler#handleMessage(android.os.Message)
*/
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case REGISTER_CLIENT:
// now we know to whom to reply with our messages, callbacks or new data
clientToReply = msg.replyTo;
break;
case WRITE_DATA:
break;
case CONNECT_BT:
// launches Connect & Connected Threads
// would follow the same pattern as in http://developer.android.com/guide/topics/connectivity/bluetooth.html#ConnectingAsAClient
btManager.connect();
break;
}
}
}
}
//
// Check examples in http://developer.android.com/guide/topics/connectivity/bluetooth.html#ConnectingAsAClient
//
public class BluetoothManager {
private ConnectThread connectThread; //thread to connect
private ConnectedThread connectedThread; //thread manages connection
private BluetoothService service;
public BluetoothManager(BluetoothService service) {
this.service = service;
}
//
// stuff omitted...
//
public void connect() {
connectThread = new ConnectThread();
connectThread.start();
}
public void writeData(byte[] bytes) {
connectedThread.write(bytes);
}
public void onDataRead(byte[] bytes) {
// service knows how to forward this to the client (bound activity, for example)
this.service.sendDataToBoundClient(bytes);
}
}
//
// Based on the example from http://developer.android.com/guide/components/bound-services.html#Messenger
//
public class ActivityMessenger extends Activity {
/** Messenger for communicating with the service. */
Messenger mService = null;
// handle incoming messages
protected Messenger messagesFromService = new Messenger(new IncomingHandler());
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
public void registerAsClient() {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = new Message();
msg.what = BluetoothService.REGISTER_CLIENT;
msg.replyTo = messagesFromService; // reply to "me"!
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
#Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
public class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case BluetoothService.NEW_DATA:
Bundle data = msg.getData;
// handle your new data!
break;
}
}
}
}
}
}
I modified the standard Bluetoothchat example to send 4 bytes of data at a time to a bluetooth device every half a second. It works fine if I start the App fresh. However, there is a problem if I reconnect as follows:
While Bluetooth is connected, I click the connect button again on the menu and select the same device. This disconnects the bluetooth (not sure whether this is the right procedure to disconnect). Then, I connect again by selecting the device, and it will be reconnected. After reconnection, a very strange problem appears: instead of sending the data every half a second, it will send the data every quarter a second. If I go through the process again and reconnect, the time interval will become even shorter. It gets to a point that the bluetooth device on the receiving end can't keep up with the data. At this point, the only way out is to kill the app and restart again. Then everything becomes normal, till next time I try to reconnect again.
I have tried different things but nothing appear to fix this. For example, I made sure the thread sending the data is killed when disconnected so no multiple threads are sending the data. I was wondering whether the baud rate changed when reconnected, but then why would the baud rate affect the Thread.sleep(500); statement (which is responsible for controlling the half a second data send). Any help would be greatly appreciated.
Here is the code, the SendClass is created under the MainActivity:
class SendClass implements Runnable {
public void run() {
bytearr[0]=0;bytearr[1]=0;bytearr[2]=0;bytearr[3]=0;
while (!Thread.currentThread().isInterrupted()) {
if (mChatService==null || mChatService.getState()
!=BluetoothChatService.STATE_CONNECTED) {
continue;
} else {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mChatService.write(bytearr);
}
}//end of run
}//end of runnable
Then under STATE_CONNECTED:
case BluetoothChatService.STATE_CONNECTED:
setStatus(getString(R.string.title_connected_to,mConnectedDeviceName));
/*
if(sendingThread!=null){
//sendingThread.stop();
sendingThread.interrupt();
if(D) Log.i(TAG, "after sendingThread");
sendingThread = null;
}*/
sendingThread = new Thread(new SendClass());
sendingThread.start();
break;
As you can see, I tried to kill the thread before creating a new one but that didn't make any difference. Any suggestions?
You are creating a thread that never actually stops, even after you create a new thread and assign to the same variable that particular thread wont stop running.
You need to make sure that the thread will stop after it disconnects.
Here is my suggestion
Change your SendClass to:
class SendClass implements Runnable {
private boolean stopped = false;
public void setStopped(boolean s){
this.stopped = s;
}
public void run() {
bytearr[0]=0;bytearr[1]=0;bytearr[2]=0;bytearr[3]=0;
while (!Thread.currentThread().isInterrupted() && !stopped) {
if (mChatService==null || mChatService.getState() !=BluetoothChatService.STATE_CONNECTED) {
continue;
} else {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mChatService.write(bytearr);
}
}//end of run
}//end of runnable
Then when you start your thread keep the reference to the Runnable so you can call the setStopped(true); like this
SendClass sc = new SendClass();
sendingThread = new Thread(sc);
sendingThread.start();
When you disconnect the bluetooth dont forget to call sc.setStopped(true); so your thread will finish by not going into the while.
My problem is probably going to be simple and awkward at the same time, but I am a little stuck.
I have a Main.java class, that extends Activity.
Inside that class, I do the following:
ringerServer = new Thread(new RingerServer());
ringerServer.start();
What I want to do is have the RingerServer thread running continously.
Inside that thread, I listen for a TCP connection. If I get one, I start another class, which sends and receives UDP packet.
public class RingerServer implements Runnable {
public static final int SERVERPORT = 4445; // Default port to connect to
#Override
public void run() {
try {
// Create a socket for handling incoming requests
ServerSocket server = new ServerSocket(SERVERPORT);
while (!VoIPCall.onCall) {
// Wait for an incoming connection
Socket clientSocket = server.accept();
// TODO: Display a message for the user to accept or decline
// For now, automatically accept the call
Intent myIntent = new Intent(null, VoIPCall.class);
// Put the IP as a parameter
myIntent.putExtra("inetAddress", clientSocket.getInetAddress());
startActivity(myIntent);
}
} catch (IOException e) {
Log.e("TCP", "S: Error", e);
}
}
}
My problem has to do with the lines:
Intent myIntent = new Intent(null, VoIPCall.class);
myIntent.putExtra("inetAddress", clientSocket.getInetAddress());
startActivity(myIntent);
Those lines would work fine inside an Activity but it doesn't to complain as it is a Thread, it doesn't know about the Activity class, because it doesn't extend it, but implements Runnable.
I am not sure how I can make my program keep running the RingerServer but have the main thread go to VoIPCall class. Any ideas please?
I really appreciate your help.
Thank you very much,
Jary
You should move your thread into a Service rather than an Activity. I recommend starting off by reading the Processes and Threads section of the Android Dev Guide. Then checkout the API docs for Service, which will help you get started creating one.