Process data stream and create records in the background - android

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;
}
}
}
}
}
}

Related

Bluetooth clossing sockets when using voice recognition

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.

Android service for TCP Sockets

Based on a suggestion in a previous question I asked on here, I'm trying to push my socket connection for an application that I've written into a service. I spent the better part of the day yesterday researching services and actually mocked up a few (one remote, one local).
My question is in two parts:
1) after having played with both a local service and a remote service, I'm still not sure as to which one would be best for my situation. This is due in large part to the fact that I guess I still don't quite understand what advantages running in another 'process' is going to give me. I'm spawning a new thread for the socket connection no matter what so I won't have any thread contention with the UI. So what does putting the service in another process enable me to do? Will I potentially see better performance that way? My limited understanding is that by putting it in a different process, the service will run independently of whatever activity I have running on my app. I do have a few different activities, but only one of them requires the socket connection which I will rebuild everytime that activity is opened anyway. So would a local service be the way to go for me?
2) I'm going to have my socket "listener" (DataInputStream().readLine() inside a while loop) inside my service for any new data that gets passed down from the server. After the playing I did yesterday, I could not figure out how to pass the data that it reads to the actual "client" (either bound client by remote service, or local client itself) in "realtime".
Would greatly appreciate some suggestions for part 1, and some help with part 2 (code examples? :))
TIA
Edit: added code of my service - going with local service
Service Class:
public class SocketService extends Service {
Socket s;
PrintStream os;
#Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return myBinder;
}
private final IBinder myBinder = new LocalBinder();
public class LocalBinder extends Binder {
public SocketService getService() {
return SocketService.this;
}
}
#Override
public void onCreate() {
super.onCreate();
s = new Socket();
}
public void IsBoundable(){
Toast.makeText(this,"I bind like butter", Toast.LENGTH_LONG).show();
}
public void onStart(Intent intent, int startId){
super.onStart(intent, startId);
Toast.makeText(this,"Service created ...", Toast.LENGTH_LONG).show();
Runnable connect = new connectSocket();
new Thread(connect).start();
}
class connectSocket implements Runnable {
#Override
public void run() {
SocketAddress socketAddress = new InetSocketAddress("192.168.1.104", 4505);
try {
s.connect(socketAddress);
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onDestroy() {
super.onDestroy();
try {
s.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
s = null;
}
}
Activity that calls service:
public class SocketServiceController extends Activity {
private SocketService mBoundService;
private Boolean mIsBound;
public SocketServiceController ssc;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ssc = this;
setContentView(R.layout.main);
Button start = (Button)findViewById(R.id.serviceButton);
Button stop = (Button)findViewById(R.id.cancelButton);
start.setOnClickListener(startListener);
stop.setOnClickListener(stopListener);
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mBoundService = ((SocketService.LocalBinder)service).getService();
}
public void onServiceDisconnected(ComponentName className) {
mBoundService = null;
}
};
private void doBindService() {
bindService(new Intent(SocketServiceController.this, SocketService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
mBoundService.IsBoundable();
}
private void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
#Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
private OnClickListener startListener = new OnClickListener() {
public void onClick(View v){
startService(new Intent(SocketServiceController.this,SocketService.class));
doBindService();
}
};
private OnClickListener stopListener = new OnClickListener() {
public void onClick(View v){
stopService(new Intent(SocketServiceController.this,SocketService.class));
}
};
}
This is due in large part to the fact that I guess I still don't quite understand what advantages running in
another 'process' is going to give me.
Generally, none. You create a remote service if you are expecting other applications to communicate with the service. If it will only be used by your own application, use a local service.
Also, a remote service has nothing to do with creating a separate process within your application.
Will I potentially see better performance that way?
You will see worse performance that way, due to extra memory consumption.
My limited understanding is that by putting it in a different process, the service will run independently of
whatever activity I have running on my app.
Services have a lifecycle independent from activities regardless of whether it is local or remote.
So would a local service be the way to go for me?
Sounds likely.
After the playing I did yesterday, I could not figure out how to pass the data that it reads to the
actual "client" (either bound client by remote service, or local client itself) in "realtime".
Use the local binding pattern, and have the activity call an API on the service to register (and unregister) an event listener. Have the service pass the data to the activity via the listener.

Restful API service

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.

Android how do I wait until a service is actually connected?

I have an Activity calling a Service defined in IDownloaderService.aidl:
public class Downloader extends Activity {
IDownloaderService downloader = null;
// ...
In Downloader.onCreate(Bundle) I tried to bindService
Intent serviceIntent = new Intent(this, DownloaderService.class);
if (bindService(serviceIntent, sc, BIND_AUTO_CREATE)) {
// ...
and within the ServiceConnection object sc I did this
public void onServiceConnected(ComponentName name, IBinder service) {
Log.w("XXX", "onServiceConnected");
downloader = IDownloaderService.Stub.asInterface(service);
// ...
By adding all kinds of Log.xx I found that the code after if(bindService(...)) actually goes BEFORE ServiceConnection.onServiceConnected is being called - that is, when downloader is still null - which gets me into trouble. All the samples in ApiDemos avoid this timing problem by only calling services when triggered by user actions. But what should I do to right use this service after bindService succeeds? How can I wait for ServiceConnection.onServiceConnected being called reliably?
Another question related. Are all the event handlers: Activity.onCreate, any View.onClickListener.onClick, ServiceConnection.onServiceConnected, etc. actually called in the same thread (mentioned in the doc as the "main thread")? Are there interleaves between them, or Android would schedule all events come into being handled one-by-one? Or, When exactly is ServiceConnection.onServiceConnected actually going to be called? Upon completion of Activity.onCreate or sometime when A.oC is still running?
How can I wait for
ServiceConnection.onServiceConnected
being called reliably?
You don't. You exit out of onCreate() (or wherever you are binding) and you put you "needs the connection established" code in onServiceConnected().
Are all the event handlers:
Activity.onCreate, any
View.onClickListener.onClick,
ServiceConnection.onServiceConnected,
etc. actually called in the same
thread
Yes.
When exactly is
ServiceConnection.onServiceConnected
actually going to be called? Upon
completion of Activity.onCreate or
sometime when A.oC is still running?
Your bind request probably is not even going to start until after you leave onCreate(). Hence, onServiceConnected() will called sometime after you leave onCreate().
I had the same problem. I didn't want to put my bound service dependent code in onServiceConnected, though, because I wanted to bind/unbind with onStart and onStop, but I didn't want the code to run again every time the activity came back to the front. I only wanted it to run when the activity was first created.
I finally got over my onStart() tunnel vision and used a Boolean to indicate whether this was the first onServiceConnected run or not. That way, I can unbindService in onStop and bindService again in onStart without running all the start up stuff each time.
I ended up with something like this:
1) to give the auxiliary stuff some scope, I created an internal class. At least, the ugly internals are separated from the rest of the code. I needed a remote service doing something, therefore the word Something in class name
private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
//...
}
2) there are two things necessary to invoke a remote service method: the IBinder and the code to execute. Since we don't know which one becomes known first, we store them:
private ISomethingService mISomethingService;
private Runnable mActionRunnable;
Each time we write to one of these fileds, we invoke _startActionIfPossible():
private void _startActionIfPossible() {
if (mActionRunnable != null && mISomethingService != null) {
mActionRunnable.run();
mActionRunnable = null;
}
}
private void performAction(Runnable r) {
mActionRunnable = r;
_startActionIfPossible();
}
This, of course, assumes that the Runnable has access to mISomethingService, but this is true for runnables created within the methods of the RemoteSomethingHelper class.
It is really good that the ServiceConnection callbacks are called on the UI thread: if we are going to invoke the service methods from the main thread, we do not need to care about synchronization.
ISomethingService is, of course, defined via AIDL.
3) Instead of just passing arguments to methods, we create a Runnable that will invoke the method with these arguments later, when invocation is possible:
private boolean mServiceBound;
void startSomething(final String arg1) {
// ... starting the service ...
final String arg2 = ...;
performAction(new Runnable() {
#Override
public void run() {
try {
// arg1 and arg2 must be final!
mISomethingService.startSomething(arg1, arg2);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
4) finally, we get:
private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
private ISomethingService mISomethingService;
private Runnable mActionRunnable;
private boolean mServiceBound;
private void _startActionIfPossible() {
if (mActionRunnable != null && mISomethingService != null) {
mActionRunnable.run();
mActionRunnable = null;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
// the methods on this class are called from the main thread of your process.
#Override
public void onServiceDisconnected(ComponentName name) {
mISomethingService = null;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
mISomethingService = ISomethingService.Stub.asInterface(service);
_startActionIfPossible();
}
}
private void performAction(Runnable r) {
mActionRunnable = r;
_startActionIfPossible();
}
public void startSomething(final String arg1) {
Intent intent = new Intent(context.getApplicationContext(),SomethingService.class);
if (!mServiceBound) {
mServiceBound = context.getApplicationContext().bindService(intent, mServiceConnection, 0);
}
ComponentName cn = context.getApplicationContext().startService(intent);
final String arg2 = ...;
performAction(new Runnable() {
#Override
public void run() {
try {
mISomethingService.startSomething(arg1, arg2);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
context is a field in my class; in an Activity, you can define it as Context context=this;
I did not need queuing actions; if you do, you can implement it.
You likely will need a result callback in startSomething(); I did, but this is not shown in this code.
I did something similar before, the only different is I was not binding to service, but just starting it.
I would broadcast an intent from the service to notify the caller/activity about it is started.
I wanted to add some things you should or should not do:
bind the service not on create but onResume and unbind it onPause. Your app can go into pause (background) at any time by user interaction or OS-Screens.
Use a distinct try/catch for each and every service unbinding, receiver unregistering etc in onPause so if one is not bound or registered the exception doesn't prevent the others from being destroyed too.
I usually capsule binding in a public MyServiceBinder getService() Method. I also always use a blocking boolean variable so I don't have to keep an eye on all those calls using the servie in the activity.
Example:
boolean isBindingOngoing = false;
MyService.Binder serviceHelp = null;
ServiceConnection myServiceCon = null;
public MyService.Binder getMyService()
{
if(serviceHelp==null)
{
//don't bind multiple times
//guard against getting null on fist getMyService calls!
if(isBindingOngoing)return null;
isBindingOngoing = true;
myServiceCon = new ServiceConnection(
public void onServiceConnected(ComponentName cName, IBinder binder) {
serviceHelp = (MyService.Binder) binder;
//or using aidl: serviceHelp = MyService.Stub.AsInterface(binder);
isServiceBindingOngoing = false;
continueAfterServiceConnect(); //I use a method like this to continue
}
public void onServiceDisconnected(ComponentName className) {
serviceHelp = null;
}
);
bindService(serviceStartIntent,myServiceCon);
}
return serviceHelp;
}
Android 10 has introduced a new bindService method signature when binding to a service to provide an Executor (which can be created from the Executors).
/**
* Same as {#link #bindService(Intent, ServiceConnection, int)} with executor to control
* ServiceConnection callbacks.
* #param executor Callbacks on ServiceConnection will be called on executor. Must use same
* instance for the same instance of ServiceConnection.
*/
public boolean bindService(#RequiresPermission #NonNull Intent service,
#BindServiceFlags int flags, #NonNull #CallbackExecutor Executor executor,
#NonNull ServiceConnection conn) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
This allows to bind to the service in a thread and wait until it is connected. E.g. stub:
private final AtomicBoolean connected = new AtomicBoolean()
private final Object lock = new Object();
...
private void myConnectMethod() {
// bind to service
ExecutorService executorService = Executors.newSingleThreadExecutor();
context.bindService(new Intent(context, MyServiceClass.class), Context.BIND_AUTO_CREATE, executorService, new
ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder binder) {
synchronized (lock) {
// TODO: store service instance for calls in case of AIDL or local services
connected.set(true);
lock.notify();
}
});
synchronized (lock) {
while (!connected.get()) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
}
}
It is also necessary to run the service in a separate process:
<service
android:name=".MyServiceClass"
android:process=":service"
android:enabled="true"
android:exported="true" />
I figured out that these workarounds are only worth the effort and the wait only if your bound services are running in a different process than your application's main process.
For accessing data and methods in the same process (or application), I ended up implementing singleton classes. If the classes need a context for some methods, I leak the application context to the singleton classes. There is, of course, a bad consequence of it as it breaks the "instant run". But that is an overall better compromise, I think.
*The basic idea is same with #18446744073709551615, but I will share my code as well.
As a answer of main question,
But what should I do to right use this service after bindService succeeds?
[Original expectation (but not work)]
wait until service connected like below
#Override
protected void onStart() {
bindService(service, mWebServiceConnection, BIND_AUTO_CREATE);
synchronized (mLock) { mLock.wait(40000); }
// rest of the code continues here, which uses service stub interface
// ...
}
It won't work because both bindService() in onCreate()/onStart() and onServiceConnected() is called at same main thread.
onServiceConnected() is never called before wait finishes.
[Alternative solution]
Instead of "wait", define own Runnable to be called after Service Connected and execute this runnable after service connected.
Implement custom class of ServiceConnection as follows.
public class MyServiceConnection implements ServiceConnection {
private static final String TAG = MyServiceConnection.class.getSimpleName();
private Context mContext = null;
private IMyService mMyService = null;
private ArrayList<Runnable> runnableArrayList;
private Boolean isConnected = false;
public MyServiceConnection(Context context) {
mContext = context;
runnableArrayList = new ArrayList<>();
}
public IMyService getInterface() {
return mMyService;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.v(TAG, "Connected Service: " + name);
mMyService = MyService.Stub.asInterface(service);
isConnected = true;
/* Execute runnables after Service connected */
for (Runnable action : runnableArrayList) {
action.run();
}
runnableArrayList.clear();
}
#Override
public void onServiceDisconnected(ComponentName name) {
try {
mMyService = null;
mContext.unbindService(this);
isConnected = false;
Log.v(TAG, "Disconnected Service: " + name);
} catch(Exception e) {
Log.e(TAG, e.toString());
}
}
public void executeAfterServiceConnected(Runnable action) {
Log.v(TAG, "executeAfterServiceConnected");
if(isConnected) {
Log.v(TAG, "Service already connected, execute now");
action.run();
} else {
// this action will be executed at the end of onServiceConnected method
Log.v(TAG, "Service not connected yet, execute later");
runnableArrayList.add(action);
}
}
}
And then use it in the following way (in your Activity class or etc),
private MyServiceConnection myServiceConnection = null;
#Override
protected void onStart() {
Log.d(TAG, "onStart");
super.onStart();
Intent serviceIntent = new Intent(getApplicationContext(), MyService.class);
startService(serviceIntent);
myServiceConnection = new MyServiceConnection(getApplicationContext());
bindService(serviceIntent, myServiceConnection, BIND_AUTO_CREATE);
// Instead of "wait" here, create callback which will be called after service is connected
myServiceConnection.executeAfterServiceConnected(new Runnable() {
#Override
public void run() {
// Rest of the code comes here.
// This runnable will be executed after service connected, so we can use service stub interface
IMyService myService = myServiceConnection.getInterface();
// ...
}
});
}
It worked for me. But there may be more better way.

AsyncTask not accessible in Service

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.

Categories

Resources