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.
Related
I have an android app that receives values from a Bluetooth device. Everything works smooth and nice the first time I open the Activity. The value from the Bluetooth is shown in a TextView.
The app consists of two activities --> Main and Details(details includes a few fragments).
In Main activity, the user enters an ID and presses okay button. Then I start the Bluetooth thread and directly connect to a Bluetooth device using the MAC address. And then I open the Details activity.
In Details activity, I've made a Handler class that puts the value from the Bluetooth device to the text view. Then the users has yes and no buttons that save the value (or don't) in the database, when pressed and he is redirected back to the Main activity.
The first time this happens, everything works great. But the second time the Details activity is opened, the handle doesn't update the UI. I cannot understand why, if I try to debug, I can see that it works as it should -> the handler gets the value and updates the text views, but for some reason I can't see them in the UI.
Does anybody have any ideas what the problem might be?
The Handle looks like that:
static class MyHandler extends Handler {
private final WeakReference<DetailsFragment> mFragment;
public MyHandler(DetailsFragment fragment) {
mFragment = new WeakReference<>(fragment);
}
#Override
public void handleMessage(Message msg) {
DetailsFragment fragment = mFragment.get();
if (fragment != null) {
String tempValue = (String) msg.obj;
if (tempValue != null && !tempValue.equals("")) {
fragment.updateTextViewValue(tempValue.trim());
}
}
}
}
This is cancel button click in the Details activity:
public void cancelButtonClick() {
Intent intent = new Intent(getActivity(), MainActivity.class);
startActivity(intent);
getActivity().finish();
}
This is the Bluetooth ConnectedThread:
public class ConnectedThread extends Thread {
private final int INCOMMING = 1;
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private final Handler mHandler;
public ConnectedThread(BluetoothSocket socket, Handler handler) {
mmSocket = socket;
mHandler = handler;
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 exception
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the streams
int bytes = 0; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
buffer[bytes] = (byte) mmInStream.read();
if ((buffer[bytes] == '\n') || (buffer[bytes] == '\r')) {
String value = new String(buffer, 0, bytes);
// Send the obtained bytes to the UI activity
mHandler.obtainMessage(INCOMMING, value).sendToTarget();
bytes = 0;
} else
bytes++;
} catch (IOException ex) {
//log the exception
break;
}
}
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
// log the exception
}
}
}
I am wondering if maybe when I open the Details activity for second time, the ConnectedThread and Handler are updating the old TextView, but is that possible, since I am using a WeakReference ?
Your description makes me think you may have a leaked activity, your thread would then update a fragment which is not displayed anymore.
You may try to launch the following command line:
adb shell dumpsys meminfo <your.app.package>
You will find a good explanation of the result here.
What is interesting for you is found in the "Objects" section, where you will find an "Activities" number.
You may go that your Details activity, come back to the Main one, go again in Details, etc...
The activites number should stay stable. If it increases, you may have a leak on your activity.
From there, you should look for retained views / fragments (I could see you had a WeakReference in your handler, so you know how to fix it)
Here, the profiling tools such as the Memory Monitor and the Allocation Tracker will surely help, but they take some time fto be understood.
Assuming that you don't actually update the Handler of the ConnectedThread, the question isn't "why doesn't the UI update?" but rather, "how could the UI update?"
You'll need to update the thread's mHandler member:
public void setHandler( MyHandler newHandler ) {
mHandler = newHandler;
}
and then add
connectedThread.setHandler( new MyHandler( this ) );
to your fragment's onActivityCreated() event.
Depending on how much traffic you have coming across, an event bus might be a better implementation.
Okay, I think I got it right, I just had to cancel the ConnectThread and ConnectedThread when I redirect back to the MainActivity. This fixed my problem. Embarrassing, but I am still a beginner.
However, I guess I still have a problem with the leaked activities. But that's another topic that I'll continue fighting with.
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;
}
}
}
}
}
}
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.
I'm looking to make a service which I can use to make calls to a web-based REST API.
Basically I want to start a service on app init then I want to be able to ask that service to request a url and return the results. In the meantime I want to be able to display a progress window or something similar.
I've created a service currently which uses IDL, I've read somewhere that you only really need this for cross app communication, so think these needs stripping out but unsure how to do callbacks without it. Also when I hit the post(Config.getURL("login"), values) the app seems to pause for a while (seems weird - thought the idea behind a service was that it runs on a different thread!)
Currently I have a service with post and get http methods inside, a couple of AIDL files (for two way communication), a ServiceManager which deals with starting, stopping, binding etc to the service and I'm dynamically creating a Handler with specific code for the callbacks as needed.
I don't want anyone to give me a complete code base to work on, but some pointers would be greatly appreciated.
Code in (mostly) full:
public class RestfulAPIService extends Service {
final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
}
public IBinder onBind(Intent intent) {
return binder;
}
public void onCreate() {
super.onCreate();
}
public void onDestroy() {
super.onDestroy();
mCallbacks.kill();
}
private final IRestfulService.Stub binder = new IRestfulService.Stub() {
public void doLogin(String username, String password) {
Message msg = new Message();
Bundle data = new Bundle();
HashMap<String, String> values = new HashMap<String, String>();
values.put("username", username);
values.put("password", password);
String result = post(Config.getURL("login"), values);
data.putString("response", result);
msg.setData(data);
msg.what = Config.ACTION_LOGIN;
mHandler.sendMessage(msg);
}
public void registerCallback(IRemoteServiceCallback cb) {
if (cb != null)
mCallbacks.register(cb);
}
};
private final Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
// Broadcast to all clients the new value.
final int N = mCallbacks.beginBroadcast();
for (int i = 0; i < N; i++) {
try {
switch (msg.what) {
case Config.ACTION_LOGIN:
mCallbacks.getBroadcastItem(i).userLogIn( msg.getData().getString("response"));
break;
default:
super.handleMessage(msg);
return;
}
} catch (RemoteException e) {
}
}
mCallbacks.finishBroadcast();
}
public String post(String url, HashMap<String, String> namePairs) {...}
public String get(String url) {...}
};
A couple of AIDL files:
package com.something.android
oneway interface IRemoteServiceCallback {
void userLogIn(String result);
}
and
package com.something.android
import com.something.android.IRemoteServiceCallback;
interface IRestfulService {
void doLogin(in String username, in String password);
void registerCallback(IRemoteServiceCallback cb);
}
and the service manager:
public class ServiceManager {
final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
public IRestfulService restfulService;
private RestfulServiceConnection conn;
private boolean started = false;
private Context context;
public ServiceManager(Context context) {
this.context = context;
}
public void startService() {
if (started) {
Toast.makeText(context, "Service already started", Toast.LENGTH_SHORT).show();
} else {
Intent i = new Intent();
i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
context.startService(i);
started = true;
}
}
public void stopService() {
if (!started) {
Toast.makeText(context, "Service not yet started", Toast.LENGTH_SHORT).show();
} else {
Intent i = new Intent();
i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
context.stopService(i);
started = false;
}
}
public void bindService() {
if (conn == null) {
conn = new RestfulServiceConnection();
Intent i = new Intent();
i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
context.bindService(i, conn, Context.BIND_AUTO_CREATE);
} else {
Toast.makeText(context, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
}
}
protected void destroy() {
releaseService();
}
private void releaseService() {
if (conn != null) {
context.unbindService(conn);
conn = null;
Log.d(LOG_TAG, "unbindService()");
} else {
Toast.makeText(context, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show();
}
}
class RestfulServiceConnection implements ServiceConnection {
public void onServiceConnected(ComponentName className, IBinder boundService) {
restfulService = IRestfulService.Stub.asInterface((IBinder) boundService);
try {
restfulService.registerCallback(mCallback);
} catch (RemoteException e) {}
}
public void onServiceDisconnected(ComponentName className) {
restfulService = null;
}
};
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
public void userLogIn(String result) throws RemoteException {
mHandler.sendMessage(mHandler.obtainMessage(Config.ACTION_LOGIN, result));
}
};
private Handler mHandler;
public void setHandler(Handler handler) {
mHandler = handler;
}
}
Service init and bind:
// this I'm calling on app onCreate
servicemanager = new ServiceManager(this);
servicemanager.startService();
servicemanager.bindService();
application = (ApplicationState)this.getApplication();
application.setServiceManager(servicemanager);
service function call:
// this lot i'm calling as required - in this example for login
progressDialog = new ProgressDialog(Login.this);
progressDialog.setMessage("Logging you in...");
progressDialog.show();
application = (ApplicationState) getApplication();
servicemanager = application.getServiceManager();
servicemanager.setHandler(mHandler);
try {
servicemanager.restfulService.doLogin(args[0], args[1]);
} catch (RemoteException e) {
e.printStackTrace();
}
...later in the same file...
Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case Config.ACTION_LOGIN:
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
try {
...process login results...
}
} catch (JSONException e) {
Log.e("JSON", "There was an error parsing the JSON", e);
}
break;
default:
super.handleMessage(msg);
}
}
};
If your service is going to be part of you application then you are making it way more complex than it needs to be. Since you have a simple use case of getting some data from a RESTful Web Service, you should look into ResultReceiver and IntentService.
This Service + ResultReceiver pattern works by starting or binding to the service with startService() when you want to do some action. You can specify the operation to perform and pass in your ResultReceiver (the activity) through the extras in the Intent.
In the service you implement onHandleIntent to do the operation that is specified in the Intent. When the operation is completed you use the passed in ResultReceiver to send a message back to the Activity at which point onReceiveResult will be called.
So for example, you want to pull some data from your Web Service.
You create the intent and call startService.
The operation in the service starts and it sends the activity a message saying it started
The activity processes the message and shows a progress.
The service finishes the operation and sends some data back to your activity.
Your activity processes the data and puts in in a list view
The service sends you a message saying that it is done, and it kills itself.
The activity gets the finish message and hides the progress dialog.
I know you mentioned you didn't want a code base but the open source Google I/O 2010 app uses a service in this way I am describing.
Updated to add sample code:
The activity.
public class HomeActivity extends Activity implements MyResultReceiver.Receiver {
public MyResultReceiver mReceiver;
public void onCreate(Bundle savedInstanceState) {
mReceiver = new MyResultReceiver(new Handler());
mReceiver.setReceiver(this);
...
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, QueryService.class);
intent.putExtra("receiver", mReceiver);
intent.putExtra("command", "query");
startService(intent);
}
public void onPause() {
mReceiver.setReceiver(null); // clear receiver so no leaks.
}
public void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case RUNNING:
//show progress
break;
case FINISHED:
List results = resultData.getParcelableList("results");
// do something interesting
// hide progress
break;
case ERROR:
// handle the error;
break;
}
}
The Service:
public class QueryService extends IntentService {
protected void onHandleIntent(Intent intent) {
final ResultReceiver receiver = intent.getParcelableExtra("receiver");
String command = intent.getStringExtra("command");
Bundle b = new Bundle();
if(command.equals("query") {
receiver.send(STATUS_RUNNING, Bundle.EMPTY);
try {
// get some data or something
b.putParcelableArrayList("results", results);
receiver.send(STATUS_FINISHED, b)
} catch(Exception e) {
b.putString(Intent.EXTRA_TEXT, e.toString());
receiver.send(STATUS_ERROR, b);
}
}
}
}
ResultReceiver extension - edited about to implement MyResultReceiver.Receiver
public class MyResultReceiver implements ResultReceiver {
private Receiver mReceiver;
public MyResultReceiver(Handler handler) {
super(handler);
}
public void setReceiver(Receiver receiver) {
mReceiver = receiver;
}
public interface Receiver {
public void onReceiveResult(int resultCode, Bundle resultData);
}
#Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (mReceiver != null) {
mReceiver.onReceiveResult(resultCode, resultData);
}
}
}
Developing Android REST client applications has been an awesome resource for me. The speaker does not show any code, he just goes over design considerations and techniques in putting together a rock solid Rest Api in android. If your a podcast kinda person or not, I'd recommend giving this one at least one listen but, personally I've listened to it like 4 or five times thus far and I'm probably going to listen to it again.
Developing Android REST client applications
Author: Virgil Dobjanschi
Description:
This session will present architectural considerations for developing RESTful applications on the Android platform. It focuses on design patterns, platform integration and performance issues specific to the Android platform.
And there are so many considerations I really hadn't made in the first version of my api that I've had to refactor
Also when I hit
the post(Config.getURL("login"),
values) the app seems to pause for a
while (seems weird - thought the idea
behind a service was that it runs on a
different thread!)
No you have to create a thread yourself, a Local service runs in the UI thread by default.
I know #Martyn does not want full code, but I think this annotation its good for this question:
10 Open Source Android Apps which every Android developer must look into
Foursquared for Android is open-source, and have an interesting code pattern interacting with the foursquare REST API.
I would highly recommend the REST client Retrofit.
I have found this well written blog post extremely helpful, it also contains simple example code.
The author uses Retrofit to make the network calls and Otto to implement a data bus pattern:
http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html
Just wanted to point you all in the direction of an standalone class I rolled that incorporates all of the functionality.
http://github.com/StlTenny/RestService
It executes the request as non-blocking, and returns the results in an easy to implement handler. Even comes with an example implementation.
Lets say I want to start the service on an event - onItemClicked() of a button. The Receiver mechanism would not work in that case because :-
a) I passed the Receiver to the service (as in Intent extra) from onItemClicked()
b) Activity moves to the background. In onPause() I set the receiver reference within the ResultReceiver to null to avoid leaking the Activity.
c) Activity gets destroyed.
d) Activity gets created again. However at this point the Service will not be able to make a callback to the Activity as that receiver reference is lost.
The mechanism of a limited broadcast or a PendingIntent seems to be more usefull in such scenarios- refer to Notify activity from service
Note that the solution from Robby Pond is somehow lacking: in this way you only allow todo one api call at a time since the IntentService only handles one intent at a time. Often you want to perform parallel api calls. If you want todo this you have to extend Service instead of IntentService and create your own thread.
Also when I hit the post(Config.getURL("login"), values) the app seems to pause for a while (seems weird - thought the idea behind a service was that it runs on a different thread!)
In this case its better to use asynctask, which runs on a different thread and return result back to the ui thread on completion.
Robby provides a great answer, though I can see you still looking for more information. I implemented REST api calls the easy BUT wrong way. It wasn't until watching this Google I/O video that I understood where I went wrong. It's not as simple as putting together an AsyncTask with a HttpUrlConnection get/put call.
There is another approach here which basically helps you to forget about the whole management of the requests. It is based on an async queue method and a callable/callback based response.
The main advantage is that by using this method you'll be able to make the whole process (request, get and parse response, sabe to db) completely transparent for you. Once you get the response code the work is already done. After that you just need to make a call to your db and you are done.
It helps as well with the problematic of what happens when your activity is not active.
What will happen here is that you'll have all your data saved in your local database but the response won't be processed by your activity, that's the ideal way.
My app does the following:
Activity1 starts Activity2.
Acitivity2 starts a Service.
The Service uses a AsyncTask to download a file.
In the AsyncTask I have a piece of code like this:
while ((status == 0)) {
byte buffer[];
if (size - downloaded > MAX_BUFFER_SIZE) {
buffer = new byte[MAX_BUFFER_SIZE];
} else {
buffer = new byte[size - downloaded];
}
int read = stream.read(buffer);
if (read == -1)
break;
file.write(buffer, 0, read);
downloaded += read;
}
Everything works like expected. With the status variable I can start and stop my download depending on its value.
BUT, when I close Activity2 and start it again (the service keeps running), I cannot stop the download, which means the variable status is not read correctly. I checked the variable, the value is OK but the Asynctask does not recognize it.
How can I get back control over my AsyncTask?
I made some more tests but this time with a thread, to make sure its not a failure in how I handle the AsyncTask. I did it this way:
Activity2 starts the Service (I did not change any code here).
The Service creates an Download Object what downloads the file using a Thread.
The structure looks like this:
in the Service
private Download dl = new Download();
private final DMInterface.Stub mBinder = new DMInterface.Stub() {
public void downloadFile() throws DeadObjectException {
try {
dl.start(url) // This starts a thread and the download
} catch (IndexOutOfBoundsException e) {
Log.e(getString(R.string.app_name), e.getMessage());
}
}
public void stop() throws DeadObjectException {
dl.cancel(); //This stops the download
}
};
And again, everything works until I disconnect from the service. Why am I only able to control the thread when I don't disconnect from the service?
Here is the code where I start/bind the service to Activity2 (only they important parts):
public class Activity2 extends ListActivity {
private DMInterface dmInterface;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.bindService(new Intent(Activity2.this, DMService.class), mConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
dmInterface = DMInterface.Stub.asInterface(service);
//do some stuff
}
public void onServiceDisconnected(ComponentName className) {
dmInterface = null;
}
};
}
There are two scenarios. In the first one I get an error in the second one not (but nothing else happens).
When an error is raised depends on, where i initialize the Thread e.g. the Object that starts the Thread.
Scenario 1:
When I do it like described above, I get no error but nothing happens.
Scenario 2:
In the Service:
private Download dl;
private final DMInterface.Stub mBinder = new DMInterface.Stub() {
public void downloadFile() throws DeadObjectException {
try {
dl = new Download();
dl.start(url) // This starts a thread and the download
} catch (IndexOutOfBoundsException e) {
Log.e(getString(R.string.app_name), e.getMessage());
}
}
public void stop() throws DeadObjectException {
dl.cancel(); //This stops the download
}
};
When I try to reach other parts of the service (setting a variable or something like that) everything works OK.
A Service lives in its own world, being a Service it is remote from the rest of your program.
A Binder can be used to communicate with your service. Defining a aidl interface with a setStatus method allows you to communicate the status to the service.