Member variable sometimes turns null in long-running Android service - android

I have an Android service that collects some data in a member variable of that service which occassionally (far from always) turns into null. I myself could never produce this no matter how long this service was running on an array of Android devices, so therefore I wonder if somebody sees any mistake in following (be aware this is a stripped down example that just illustrates the issue):
public class CollectionService extends Service {
private final CollectionServiceBinder binder = new CollectionServiceBinder();
private PowerManager.WakeLock wakeLock;
private UserData userData; // this is the object that sometimes becomes null
#Override
public IBinder onBind(Intent intent) {
return binder;
}
#Override
public void onCreate() {
super.onCreate();
wakeLock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AppId");
}
#Override
public void onDestroy() {
super.onDestroy();
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
public void startCollecting() {
userData = new UserData();
Notification notification = new Notification();
// initializing notification variable
startForeground(0xABCD, notification);
// trigger logic that collects relevant data here, just imagine some Runnable that's triggered on a certain interval and adds data to the UserData value object.
}
public UserData finishCollecting() {
try {
userData.setFinishDate(new Date()); // throws NullPointerException every now and then. return userData;
} finally {
if (wakeLock.isHeld()) {
wakeLock.release();
}
stopForeground(true);
userData = null;
}
}
public boolean isCollecting() {
return userData != null;
}
public class CollectionServiceBinder extends Binder {
public CollectionService getService() {
return CollectionService.this;
}
}
}
The service is started in an activity using...
Intent i = new Intent(this, CollectionService.class);
getApplicationContext().startService(i);
getApplicationContext().bindService(i, serviceConnection, BIND_AUTO_CREATE); // just so the activity has a handle and can call startCollecting()/finishCollecting()
... and is unbound in onDestroy() using ...
getApplicationContext.unbindService(serviceConnection);
The ServiceConnection class looks like this:
public class MyServiceConnection implements ServiceConnection {
private CollectionService service;
#Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
service = ((CollectionService.CollectionServiceBinder) binder).getService();
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
service = null;
}
public CollectionService getCollectionService() {
return service;
}
}
And startCollecting is trigger in an OnClickListener like this to prevent starting twice:
if (!serviceConnection.getCollectionService().isCollecting()) {
serviceConnection.getCollectionService().startCollecting();
}
I believe this should all be ok but sometimes userData (as commented in the code example) will be null. Again, this happens extremely rarely (of 50.000+ active device installs on Google Play I have just received 50 reports over the course of a year, but still, I want everyone to be able to enjoy the app).
Any ideas?

I don't see the place where you call startCollecting which is relevant as that's where you're initializing userData, but what's probably happening is that your service is getting killed for whatever reason and then restarted. You need to do something to persist/reload data as necessary or at the very least lazy load userData so they'll start fresh.
You don't have all your code here, but it might also be relevant to note that you only acquire your wake lock in onCreate but you release it in finishCollecting. If it's possible that you call startCollecting, finishCollecting, then startCollecting again on the same Service, you're going to end up with the Service running without a wake lock held.

Related

Android: Service is killed and restarted after a while

I know that is a well known subject, but I have tried lot of things. I have an simple application, dedicated to a specific user, application has an mainActivity which is displaying some status on screen and it's starting two services, one is making request from a server (at every 5 minutes) and one which is sending sms and replay to server (at every ten minutes).
The application is running on a Samsung pocket 2 with Android 4.4.2, this device is used only for this application. While the device is connected to ADB the services are working just fine, but if I disconnect the phone and let it running normally, the services are killed repeatable and restarted after a while. The messaged are send with very much delay. I would be thankful for any suggestions.
Here is my code:
Main activity:
public class MainActivity extends Activity {
private TextView _internet;
private TextView _signal;
private TextView _server;
private BroadcastReceiver receiver;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
IntentFilter intentFilter = new IntentFilter(Constants.SS);
receiverWorker();
registerReceiver(receiver, intentFilter);
startService(new Intent(this, RefreshDBService.class));
startService(new Intent(this, SmsService.class));
}
private void receiverWorker() {
receiver = new BroadcastReceiver() {
public void onReceive(Context arg0, Intent arg1) {
checkState();
}};
}
public void refreshButonClicked(View v) {
checkState();
}`
Here is my first service:
public class RefreshDBService extends Service {
private Thread _backgroundWork;
private ScheduledExecutorService scheduleTaskExecutor = Executors.newScheduledThreadPool(1);
private DataBaseOperations _dataSource;
#Override
public void onCreate() {
super.onCreate();
_dataSource = new DataBaseOperations(this);
_backgroundWork = new Thread(new Runnable() {
#Override
public void run() {
if(Checks.checkInternetConnection(getApplicationContext())){
if(ServerOperations.isServerAvailable(getApplicationContext())){
String inputData = ServerOperations.makeRequest(Constants.GET_DATA_ROUTE, ServerOperations.getMessagesFromServer(getApplicationContext()));
ArrayList<DataSmsObj> dataFromServer=null;
if(inputData!=null && !inputData.isEmpty()){
dataFromServer = ServerOperations.fromJsonToObjects(inputData);
if(dataFromServer.size()>0){
_dataSource.open();
_dataSource.insertDataFromServer(dataFromServer);
_dataSource.close();
}
}
System.out.println("check server for messages in pending status, received -> "+ dataFromServer.size());
}else{
System.out.println("no server");
sentErrorToUI(Constants.NO_SERVER);
}
}else{
System.out.println("no internet");
sentErrorToUI(Constants.NO_INTERNET);
}
}
});
}
public int onStartCommand(Intent intent, int flags, int startId) {
scheduleTaskExecutor.scheduleWithFixedDelay(_backgroundWork, 0, Constants.NEXT_CYCLE/2, TimeUnit.MINUTES);
return START_REDELIVER_INTENT;
}
#Override
public void onDestroy() {
super.onDestroy();
scheduleTaskExecutor.shutdownNow();
}
public IBinder onBind(Intent intent) {
return null;
}
private void sentErrorToUI(String message){
Intent intent = new Intent(Constants.SS);
intent.putExtra(Constants.SS, message);
System.out.println("trimit" +message);
sendBroadcast(intent);
}
}
And this is the second one:
public class SmsService extends Service {
private Thread _backgroundWork;
private ScheduledExecutorService scheduleTaskExecutor = Executors.newScheduledThreadPool(1);
private DataBaseOperations _dataSource;
#Override
public void onCreate() {
super.onCreate();
_dataSource = new DataBaseOperations(this);
_backgroundWork = new Thread(new Runnable() {
#Override
public void run() {
sendFeedbackToServer();
List<DataSmsObj> dataToSent = new ArrayList<DataSmsObj>();
_dataSource.open();
dataToSent = _dataSource.getDataToSent();
_dataSource.close();
System.out.println("messages to sent: "+ dataToSent.size());
for (int i = 0; i < dataToSent.size(); i++) {
//here the messages are send, the code is to long to put it here, but if is need i can do it afterwards
}
}
});
}
public int onStartCommand(Intent intent, int flags, int startId) {
scheduleTaskExecutor.scheduleWithFixedDelay(_backgroundWork, 0, Constants.NEXT_CYCLE, TimeUnit.MINUTES);
return START_REDELIVER_INTENT;
}
#Override
public void onDestroy() {
super.onDestroy();
scheduleTaskExecutor.shutdownNow();
public IBinder onBind(Intent intent) {
return null;
}
If you are using a background Service with a scheduled task, it could be killed by the system. The only way to prevent the killing is a foreground Service. Quoting the documentation:
A foreground service is a service that the user is actively aware of and is not a candidate for the system to kill when low on memory.
You have to call the method startForeground() inside your Service using a Notification to show it. For further information you can check: https://developer.android.com/guide/components/services.html#Foreground
By the way, I recommend you to use the new JobScheduler api above api 21.
https://developer.android.com/reference/android/app/job/JobScheduler.html
Android kills service based on priority stack.
Android: keeping a background service alive (preventing process death)
What is START_STICKY,START_NOT_STICKY and START_REDELIVER_INTENT Service
Above links might help you.
Your devices will sleeps if it is unplugged from computer . So, the solutions :
Use startForeground method to prevent service to be killed and/or use AlarmManager in order to charge event.
It is possible to use start_stiky flag but it just restarts the process if it killed by system.

Android asynchronous UI Updating from service fails

I have a very strange problem for updating UI. I have a foreground started bounded service which my main process in background. When I start app, I like to check if service is already running and change state of a toggle button. For this problem, I bind to my started service when starting app in OnResume() and service sends a value back to my app which shows running status of service and I update UI based on this value. But the problem is that UI is not updated in this situation.
Because this bug is shown in a very complex situation, I have written a sample code that reproduce this problem. Here are these codes (sorry for bad names and missing a lot of error checks, I have quickly written this code just to reproduce problem). I have discussed each code a little as an overview.
activity_main layout:
<ToggleButton
android:id="#+id/ui_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOff="Off State"
android:textOn="On State"
android:checked="false" />
<Button
android:id="#+id/start_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start"/>
MyTestService.java
At first, this is my sample foreground started bounded service. As you see, when we start service, we create a foreground service which just runs a small thread that toggles a mStatus variable every 10 seconds for 10 times and then stops. Whenever we bind to this service, we use ResultReceiver which is sent through binding intent in order to send mStatus to app. We also allow rebinding, because app may be closed several times and reopened again.
public class MyTestService extends Service {
private volatile boolean mStatus = false;
private MyThread mTh = new MyThread();
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
mTh.start();
Intent notintent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notintent, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentText("Test").setContentIntent(pendingIntent).setContentTitle("title").setSmallIcon(R.drawable.ic_launcher);
Notification notification = builder.build();
startForeground(100, notification);
return START_STICKY;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
if (intent != null && intent.getAction().equals("checkstatus")) {
ResultReceiver recv = (ResultReceiver)intent.getParcelableExtra("myrecvextra");
Bundle data = new Bundle();
data.putBoolean("status", mStatus);
recv.send(0, data);
}
return null;
}
#Override
public boolean onUnbind(Intent intent) {
return true;
}
#Override
public void onRebind(Intent intent) {
if (intent != null && intent.getAction().equals("checkstatus")) {
ResultReceiver recv = (ResultReceiver)intent.getParcelableExtra("myrecvextra");
Bundle data = new Bundle();
data.putBoolean("status", mStatus);
recv.send(0, data);
}
}
public class MyThread extends Thread {
#Override
public void run() {
try {
for (int i = 0; i < 10; i++ ) {
Thread.sleep(10000);
mStatus = !mStatus;
Log.i("ASD", String.format("%d", mStatus? 1 : 0));
}
}catch (Exception e) {
}
stopSelf();
}
}
}
MyServiceAccessClass.java
This class is used for accessing service. start() starts service, bind() and unbind() are using for binding and unbinding service. mRecv is the ResultReceiver which send to service while binding and is used for getting status. When status is received after binding, ResultReceiver updates UI via a callback.
public class MyServiceAccessClass {
private MyResultRecv mRecv = new MyResultRecv(new Handler(Looper.getMainLooper()));
private OnUpdateRequest mCallback = null;
private Context mCtx = null;
private ServiceConnection mCon = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {}
#Override
public void onServiceDisconnected(ComponentName name) {}
};
public MyServiceAccessClass(Context ctx) {
mCtx = ctx;
mCallback = (OnUpdateRequest)ctx;
}
public void bind() {
Intent intent = new Intent(mCtx, MyTestService.class);
intent.setAction("checkstatus");
intent.putExtra("myrecvextra", mRecv);
mCtx.bindService(intent, mCon, Context.BIND_AUTO_CREATE);
}
public void unbind() {
mCtx.unbindService(mCon);
}
public void start() {
Intent intent = new Intent(mCtx, MyTestService.class);
mCtx.startService(intent);
}
private class MyResultRecv extends ResultReceiver {
public MyResultRecv(Handler handler) {
super(handler);
}
#Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == 0) {
mCallback.updateUi(resultData.getBoolean("status"));
}
}
}
}
MainActivity.java
This is main class of test app. Start button starts service. and this class binds in OnResume() and unbinds in OnPause(). If app is run when service is already running and its mStatus is true, then updateUi will be called with true value and sets status of toggle button.
interface OnUpdateRequest {
public void updateUi(boolean state);
}
public class MainActivity extends AppCompatActivity implements OnUpdateRequest{
private MyServiceAccessClass mTest = new MyServiceAccessClass (this);
private ToggleButton mBtn = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtn = (ToggleButton)findViewById(R.id.ui_btn);
((Button)findViewById(R.id.start_btn)).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mTest.start();
}
});
}
#Override
protected void onResume() {
super.onResume();
mTest.bind();
}
#Override
protected void onPause() {
super.onPause();
mTest.unbind();
}
#Override
public void updateUi(boolean state) {
mBtn.setChecked(state);
}
}
Ok, now in theory everything is all right. But if you try to use this code, when service is started and mStatus is true, toggle button's setChecked() will be called with true (which is correct till now) but UI will not be updated to show correct text and status. Funny part is that if you run isChecked for this toggle button, it will return true, but UI shows something else.
Any idea why this happens? Sorry for a lot of codes, this problem occured is this complex situation.
Update
I noticed something that I should mention. if I use isChecked right after setCheck, I get true which is correct. But if I use isChecked again some time later (for example in another button event handler), it returns false while I have not called setChecked anymore. I think this situation is related to my problem but I don't know how this is happened.
In addition, I think this problem is related to updating UI when you are in binding process to a service. because if I try to update app main UI with same ResultReceiver when I'm not in binding process, everything works correctly.
Possibly need to call View.requestLayout() or View.forceLayout() on the buttons View to refresh the buttons state.
I finally found the problem with my code. It took me a lot of time to solve this problem, so I post it here for other android developers.
Sending result back from a service is somehow obvious via ResultReceiver. But most of examples in internet does not show service rebinding, and I never found sending result back after rebinding service.
OK, now what is the problem? look at the following part of code from my service:
#Nullable
#Override
public IBinder onBind(Intent intent) {
if (intent != null && intent.getAction().equals("checkstatus")) {
ResultReceiver recv = (ResultReceiver)intent.getParcelableExtra("myrecvextra");
Bundle data = new Bundle();
data.putBoolean("status", mStatus);
recv.send(0, data);
}
return null;
}
#Override
public boolean onUnbind(Intent intent) {
return true;
}
#Override
public void onRebind(Intent intent) {
if (intent != null && intent.getAction().equals("checkstatus")) {
ResultReceiver recv = (ResultReceiver)intent.getParcelableExtra("myrecvextra");
Bundle data = new Bundle();
data.putBoolean("status", mStatus);
recv.send(0, data);
}
}
This is a common approach for making a rebinding service, based on simple binding to service that you find in internet. This has been done with returning true in onUnbind() and using onRebind(). But this approach is completely wrong.
Why? Because of a strange design in android. In Service OnRebind(), there is a small 18 word comment:
Note that any extras that were included with the Intent at that point
will not be seen here.
Now What this means? It means that extra which carries ResultReceiver will not be available on rebinding which in turn means that results will not be sent back after rebinding. But for unknown reason, this code does not make any exception and you even see result back in app while debugging, so it is super ambiguous why this code does not work.
Now what is the solution? Never send ResultReceiver when you bind to a service with bindService() intent. Even though this is correct for a non-rebinding service, but I highly suggest to avoid it. Send ResultReceiver via a separate message to service when onServiceConnected is called and then everything works like a piece of cake. Here are my modifications for code:
MyTestService.java
public static int SERVICE_SET_RECV = 1;
public static String SERVICE_RECV = "SERVICE_RECV";
private Messenger mMessenger = new Messenger(new MyHandler(this));
private ResultReceiver mRecv = null;
#Nullable
#Override
public IBinder onBind(Intent intent) {
if (intent != null && intent.getAction().equals("checkstatus")) {
return mMessenger.getBinder();
}
return null;
}
#Override
public boolean onUnbind(Intent intent) {
mRecv = null;
return true;
}
#Override
public void onRebind(Intent intent) {}
public void setRecv(ResultReceiver recv) {
mRecv = recv;
// Example to send some result back to app
Bundle data = new Bundle();
data.putBoolean("status", mStatus);
mRecv.send(0, data);
}
private static class MyHandler extends Handler {
private final WeakReference<MyTestService> mService;
public MyHandler(MyTestService service) {
mService = new WeakReference<>(service);
}
#Override
public void handleMessage(Message msg) {
MyTestService service = mService.get();
Bundle data = msg.getData();
switch (msg.what) {
case SERVICE_SET_RECV: {
ResultReceiver recv = data.getParcelable(SERVICE_RECV);
service.setRecv(recv);
break;
}
default:
super.handleMessage(msg);
}
}
}
MyServiceAccessClass.java
private ServiceConnection mCon = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
Messenger messenger = new Messenger(service);
Bundle data = new Bundle();
data.putParcelable(MyTestService.SERVICE_RECV, mRecv);
Message msg = Message.obtain(null, MyTestService.SERVICE_SET_RECV, 0, 0);
msg.setData(data);
messenger.send(msg);
}
#Override
public void onServiceDisconnected(ComponentName name) {}
};
public void bind() {
Intent intent = new Intent(mCtx, MyTestService.class);
mCtx.bindService(intent, mCon, Context.BIND_AUTO_CREATE);
}
Finding this ridiculous problem took me a lot of time. I wish everyone likes this solution and solves a lot of problems for rebinding a service.

Persistent Service on Android

I am trying to write a service that comes with a MediaPlayer. I have different Activities accessing it, so I thought it would be best to peruse a Service.
It works fine so far, I have also added a call to startForeground, as described here. The notification shows up.
But when I now press the home or back button on the device, the service is stopped and onDestroy is called, and the notification icon disappears. When I return, the service seems to reBind just fine.
I stop the music playback on onDestroy, so of course it stops. But I would like to keep the notification and service alive even when the user is on another app.
EDIT: I hope this is the relevant part:
public class MediaPlayerService extends Service {
private static class PlayerMessageHandler extends Handler {
private final MediaPlayerService owner;
public PlayerMessageHandler(MediaPlayerService owner) {
this.owner = owner;
}
#Override
public void handleMessage(Message msg) {
// Handle
}
}
private static final int NOTIFICATION_ID = 13138;
private final Messenger messenger = new Messenger(new PlayerMessageHandler(
this));
private MediaPlayer player;
private Notification notification;
#Override
public IBinder onBind(Intent intent) {
startNotification();
return messenger.getBinder();
}
#Override
public void onCreate() {
super.onCreate();
Log.v(TAG, "Media player service created.");
player = new AudiobookPlayer(this);
new Thread(seekerUpdate).start();
isRunning = true;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "Received start id " + startId + ": " + intent);
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
Log.v(TAG, "Media player service destroyed.");
if (player.isPlaying()) {
player.pause();
}
sendMessageToUI(MSG_PLAYER_HAS_PAUSED);
isRunning = false;
}
private void sendMessageToUI(int msg) {
Log.v(TAG, "Sending " + msg);
sendMessage(Message.obtain(null, msg));
}
private void sendMessage(final Message message) {
// Send
}
private void startNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(
this);
builder.setSmallIcon(R.drawable.notification);
builder.setContentTitle(getString(R.string.app_name));
notification = builder.build();
startForeground(NOTIFICATION_ID, notification);
}
}
EDIT2: Methods from the activity, taken from here
#Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MediaPlayerService.class),
playerServiceConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (bound) {
unbindService(playerServiceConnection);
bound = false;
}
}
You should make your service sticky. In fact, this is what the tutorial uses:
public class HelloService extends Service {
...
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
...
// If we get killed, after returning from here, restart
return START_STICKY;
}
...
}
EDIT: from the discussion that transpired since, turns out that my suspicion was correct, and Hugo provided a spot-on diagnosis. I guess you now need to add builder.setOngoing(true); in startNotification().
Extracted from http://developer.android.com/guide/components/services.html
Bound
A service is "bound" when an application component binds to it by
calling bindService(). A bound service offers a client-server
interface that allows components to interact with the service, send
requests, get results, and even do so across processes with
interprocess communication (IPC). A bound service runs only as long as
another application component is bound to it. Multiple components can
bind to the service at once, but when all of them unbind, the service
is destroyed.
You are binding your Activities on onStart and unbinding on onStop. When you press Home or Back, your last foreground Activity may call onStop, unbinding the last Activity from the Service and killing it.
An alternative solution would be call startService so the onStartCommand will be called, then calling the bindService to bind the Activities.

Sticky Service Management

I've got a Sticky Service (returns START_STICKY from onStartCommand) which executes some code in an AsyncTask, but I'm having some problems with how and when to start, bind, stop, unbind. I only want the service around whilst the parent activity is alive, I don't want it hanging around in the background when the app has been closed, but I need the service to survive an orientation change. I currently don't need the service to be active for the entire duration of the activity being active, so I call stopSelf() after the main work is done in my AsyncTask in the Service and then start the Service again when needed. Sometimes I'll need to interrupt the work the service is doing, cancel the AsyncTask and start again with different data. The problem is that no matter what I do - I can't seem to get it solid throughout all the different possible scenarios. Can anyone have a look through and tell me what I'm doing wrong?
My Service is :
public class ChordCalculatorService extends Service {
private final IBinder mBinder = new LocalBinder();
private AsyncTask<SearchData, SearchStatusData, List<Item>> currentTask;
#Override
public void onCreate() {}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
/**
* Class for clients to access. Because we know this service always runs in
* the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public ChordCalculatorService getService() {
return ChordCalculatorService.this;
}
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public SearchData getSearchData() {
return searchData;
}
public void startWork() {
if (currentTask != null && currentTask.getStatus() == Status.RUNNING) {
currentTask.cancel(true);
}
if(searchData != null) {
Worker task = new Worker();
currentTask = task.execute(new SearchData[] { searchData });
} else {
Message msg = handler.obtainMessage(ERROR, "No search data set");
handler.sendMessage(msg);
}
}
class Worker extends AsyncTask<SearchData, SearchStatusData, List<Item>> {
// ... code ...
#Override
protected void onPostExecute(List<Item> result) {
Message msg = handler.obtainMessage(COMPLETE, new StatusData(Status.STATUS_FINISHED, result));
handler.sendMessage(msg);
stopSelf();
}
}
}
Currently I have the Service being started when my custom View is created:
public class MyCustomView extends BasicFretBoardView {
private ServiceConnection conn;
private MyService myService;
private boolean isServiceStarted;
private boolean isServiceBound;
public MyCustomView(Context context, AttributeSet attr) {
super(context, attr);
startService();
}
public void startService() {
Intent serviceIntent = new Intent(getContext(), MyService.class);
conn = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
myService = ((LocalBinder) service).getService();
myService.registerHandler(serviceHandler);
}
#Override
public void onServiceDisconnected(ComponentName name) {
myService = null;
}
};
// Explicitly start the service. Don't use BIND_AUTO_CREATE, since it
// causes an implicit service stop when the last binder is removed.
getContext().startService(serviceIntent);
getContext().bindService(serviceIntent, conn, 0);
isServiceStarted = true;
isServiceBound = true;
}
public void stopService() {
if (isServiceStarted) {
Intent serviceIntent = new Intent(getContext(), MyService.class);
getContext().stopService(serviceIntent);
isServiceStarted = false;
}
unBindService();
}
public void unBindService() {
if(isServiceBound) {
getContext().unbindService(conn);
isServiceBound = false;
}
}
// gets called based on some user interaction
private void startServiceWork() {
if(!isServiceStarted) {
startService();
} else {
myService.cancelCalcalation();
}
myService.setData(data);
myService.startWork();
}
}
and stopping the service is handled in the Activity:
public class CustomChordActivity extends Activity {
// ... code ...
#Override
public void onBackPressed() {
super.onBackPressed();
}
#Override
protected void onPause() {
if(isFinishing()) {
chordsView.stopService();
}
super.onPause();
}
#Override
protected void onStop() {
super.onStop();
}
#Override
protected void onDestroy() {
chordsView.unBindService();
super.onDestroy();
}
#Override
protected void finalize() throws Throwable {
super.finalize();
}
}
It seems that you want your task to run on demand, maybe an IntentService would be a more suitable option. When you need work to be done, (startServiceWork()), you just start the service and that kicks off your AsyncTask. The service will then finish after the task has finished.
Now, regarding orientation changes, you would have to implement a Broadcast Receiver whose intent filter is "android.intent.action.CONFIGURATION_CHANGED". (I assume that you want the service to do work when the orientation changes) Place the Broadcast Receiver, within your activity/main ui thread. This will in effect make the hosting process of your Broadcast Receiver to be the main application process making it safer to start the service from within the Broadcast Receiver.

How to have Android Service communicate with Activity

I'm writing my first Android application and trying to get my head around communication between services and activities. I have a Service that will run in the background and do some gps and time based logging. I will have an Activity that will be used to start and stop the Service.
So first, I need to be able to figure out if the Service is running when the Activity is started. There are some other questions here about that, so I think I can figure that out (but feel free to offer advice).
My real problem: if the Activity is running and the Service is started, I need a way for the Service to send messages to the Activity. Simple Strings and integers at this point - status messages mostly. The messages will not happen regularly, so I don't think polling the service is a good way to go if there is another way. I only want this communication when the Activity has been started by the user - I don't want to start the Activity from the Service. In other words, if you start the Activity and the Service is running, you will see some status messages in the Activity UI when something interesting happens. If you don't start the Activity, you will not see these messages (they're not that interesting).
It seems like I should be able to determine if the Service is running, and if so, add the Activity as a listener. Then remove the Activity as a listener when the Activity pauses or stops. Is that actually possible? The only way I can figure out to do it is to have the Activity implement Parcelable and build an AIDL file so I can pass it through the Service's remote interface. That seems like overkill though, and I have no idea how the Activity should implement writeToParcel() / readFromParcel().
Is there an easier or better way? Thanks for any help.
EDIT:
For anyone who's interested in this later on, there is sample code from Google for handling this via AIDL in the samples directory: /apis/app/RemoteService.java
The asker has probably long since moved past this, but in case someone else searches for this...
There's another way to handle this, which I think might be the simplest.
Add a BroadcastReceiver to your activity. Register it to receive some custom intent in onResume and unregister it in onPause. Then send out that intent from your service when you want to send out your status updates or what have you.
Make sure you wouldn't be unhappy if some other app listened for your Intent (could anyone do anything malicious?), but beyond that, you should be alright.
Code sample was requested:
In my service, I have this:
// Do stuff that alters the content of my local SQLite Database
sendBroadcast(new Intent(RefreshTask.REFRESH_DATA_INTENT));
(RefreshTask.REFRESH_DATA_INTENT is just a constant string.)
In my listening activity, I define my BroadcastReceiver:
private class DataUpdateReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(RefreshTask.REFRESH_DATA_INTENT)) {
// Do stuff - maybe update my view based on the changed DB contents
}
}
}
I declare my receiver at the top of the class:
private DataUpdateReceiver dataUpdateReceiver;
I override onResume to add this:
if (dataUpdateReceiver == null) dataUpdateReceiver = new DataUpdateReceiver();
IntentFilter intentFilter = new IntentFilter(RefreshTask.REFRESH_DATA_INTENT);
registerReceiver(dataUpdateReceiver, intentFilter);
And I override onPause to add:
if (dataUpdateReceiver != null) unregisterReceiver(dataUpdateReceiver);
Now my activity is listening for my service to say "Hey, go update yourself." I could pass data in the Intent instead of updating database tables and then going back to find the changes within my activity, but since I want the changes to persist anyway, it makes sense to pass the data via DB.
There are three obvious ways to communicate with services:
Using Intents
Using AIDL
Using the service object itself (as singleton)
In your case, I'd go with option 3. Make a static reference to the service it self and populate it in onCreate():
void onCreate(Intent i) {
sInstance = this;
}
Make a static function MyService getInstance(), which returns the static sInstance.
Then in Activity.onCreate() you start the service, asynchronously wait until the service is actually started (you could have your service notify your app it's ready by sending an intent to the activity.) and get its instance. When you have the instance, register your service listener object to you service and you are set. NOTE: when editing Views inside the Activity you should modify them in the UI thread, the service will probably run its own Thread, so you need to call Activity.runOnUiThread().
The last thing you need to do is to remove the reference to you listener object in Activity.onPause(), otherwise an instance of your activity context will leak, not good.
NOTE: This method is only useful when your application/Activity/task is the only process that will access your service. If this is not the case you have to use option 1. or 2.
Use LocalBroadcastManager to register a receiver to listen for a broadcast sent from local service inside your app, reference goes here:
http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html
You may also use LiveData that works like an EventBus.
class MyService : LifecycleService() {
companion object {
val BUS = MutableLiveData<Any>()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
val testItem : Object
// expose your data
if (BUS.hasActiveObservers()) {
BUS.postValue(testItem)
}
return START_NOT_STICKY
}
}
Then add an observer from your Activity.
MyService.BUS.observe(this, Observer {
it?.let {
// Do what you need to do here
}
})
You can read more from this blog.
I am surprised that no one has given reference to Otto event Bus library
http://square.github.io/otto/
I have been using this in my android apps and it works seamlessly.
Using a Messenger is another simple way to communicate between a Service and an Activity.
In the Activity, create a Handler with a corresponding Messenger. This will handle messages from your Service.
class ResponseHandler extends Handler {
#Override public void handleMessage(Message message) {
Toast.makeText(this, "message from service",
Toast.LENGTH_SHORT).show();
}
}
Messenger messenger = new Messenger(new ResponseHandler());
The Messenger can be passed to the service by attaching it to a Message:
Message message = Message.obtain(null, MyService.ADD_RESPONSE_HANDLER);
message.replyTo = messenger;
try {
myService.send(message);
catch (RemoteException e) {
e.printStackTrace();
}
A full example can be found in the API demos: MessengerService and MessengerServiceActivity. Refer to the full example for how MyService works.
The other method that's not mentioned in the other comments is to bind to the service from the activity using bindService() and get an instance of the service in the ServiceConnection callback. As described here http://developer.android.com/guide/components/bound-services.html
Binding is another way to communicate
Create a callback
public interface MyCallBack{
public void getResult(String result);
}
Activity side:
Implement the interface in the Activity
Provide the implementation for the method
Bind the Activity to Service
Register and Unregister Callback when the Service gets bound and unbound with
Activity.
public class YourActivity extends AppCompatActivity implements MyCallBack{
private Intent notifyMeIntent;
private GPSService gpsService;
private boolean bound = false;
#Override
public void onCreate(Bundle sis){
// activity code ...
startGPSService();
}
#Override
public void getResult(String result){
// show in textView textView.setText(result);
}
#Override
protected void onStart()
{
super.onStart();
bindService();
}
#Override
protected void onStop() {
super.onStop();
unbindService();
}
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
GPSService.GPSBinder binder = (GPSService.GPSBinder) service;
gpsService= binder.getService();
bound = true;
gpsService.registerCallBack(YourActivity.this); // register
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
bound = false;
}
};
private void bindService() {
bindService(notifyMeIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private void unbindService(){
if (bound) {
gpsService.registerCallBack(null); // unregister
unbindService(serviceConnection);
bound = false;
}
}
// Call this method somewhere to start Your GPSService
private void startGPSService(){
notifyMeIntent = new Intent(this, GPSService.class);
startService(myIntent );
}
}
Service Side:
Initialize callback
Invoke the callback method whenever needed
public class GPSService extends Service{
private MyCallBack myCallback;
private IBinder serviceBinder = new GPSBinder();
public void registerCallBack(MyCallBack myCallback){
this.myCallback= myCallback;
}
public class GPSBinder extends Binder{
public GPSService getService(){
return GPSService.this;
}
}
#Nullable
#Override
public IBinder onBind(Intent intent){
return serviceBinder;
}
}
Another way could be using observers with a fake model class through the activity and the service itself, implementing an MVC pattern variation. I don't know if it's the best way to accomplish this, but it's the way that worked for me. If you need some example ask for it and i'll post something.
Besides LocalBroadcastManager , Event Bus and Messenger already answered in this question,we can use Pending Intent to communicate from service.
As mentioned here in my blog post
Communication between service and Activity can be done using
PendingIntent.For that we can use
createPendingResult().createPendingResult() creates a new
PendingIntent object which you can hand to service to use and to send
result data back to your activity inside onActivityResult(int, int,
Intent) callback.Since a PendingIntent is Parcelable , and can
therefore be put into an Intent extra,your activity can pass this
PendingIntent to the service.The service, in turn, can call send()
method on the PendingIntent to notify the activity via
onActivityResult of an event.
Activity
public class PendingIntentActivity extends AppCompatActivity
{
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PendingIntent pendingResult = createPendingResult(
100, new Intent(), 0);
Intent intent = new Intent(getApplicationContext(), PendingIntentService.class);
intent.putExtra("pendingIntent", pendingResult);
startService(intent);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 100 && resultCode==200) {
Toast.makeText(this,data.getStringExtra("name"),Toast.LENGTH_LONG).show();
}
super.onActivityResult(requestCode, resultCode, data);
}
}
Service
public class PendingIntentService extends Service {
private static final String[] items= { "lorem", "ipsum", "dolor",
"sit", "amet", "consectetuer", "adipiscing", "elit", "morbi",
"vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam",
"vel", "erat", "placerat", "ante", "porttitor", "sodales",
"pellentesque", "augue", "purus" };
private PendingIntent data;
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
data = intent.getParcelableExtra("pendingIntent");
new LoadWordsThread().start();
return START_NOT_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
super.onDestroy();
}
class LoadWordsThread extends Thread {
#Override
public void run() {
for (String item : items) {
if (!isInterrupted()) {
Intent result = new Intent();
result.putExtra("name", item);
try {
data.send(PendingIntentService.this,200,result);
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
SystemClock.sleep(400);
}
}
}
}
}
My method:
Class to manage send and receive message from/to service/activity:
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class MessageManager {
public interface IOnHandleMessage{
// Messages
int MSG_HANDSHAKE = 0x1;
void onHandleMessage(Message msg);
}
private static final String LOGCAT = MessageManager.class.getSimpleName();
private Messenger mMsgSender;
private Messenger mMsgReceiver;
private List<Message> mMessages;
public MessageManager(IOnHandleMessage callback, IBinder target){
mMsgReceiver = new Messenger(new MessageHandler(callback, MessageHandler.TYPE_ACTIVITY));
mMsgSender = new Messenger(target);
mMessages = new ArrayList<>();
}
public MessageManager(IOnHandleMessage callback){
mMsgReceiver = new Messenger(new MessageHandler(callback, MessageHandler.TYPE_SERVICE));
mMsgSender = null;
mMessages = new ArrayList<>();
}
/* START Getter & Setter Methods */
public Messenger getMsgSender() {
return mMsgSender;
}
public void setMsgSender(Messenger sender) {
this.mMsgSender = sender;
}
public Messenger getMsgReceiver() {
return mMsgReceiver;
}
public void setMsgReceiver(Messenger receiver) {
this.mMsgReceiver = receiver;
}
public List<Message> getLastMessages() {
return mMessages;
}
public void addMessage(Message message) {
this.mMessages.add(message);
}
/* END Getter & Setter Methods */
/* START Public Methods */
public void sendMessage(int what, int arg1, int arg2, Bundle msgData){
if(mMsgSender != null && mMsgReceiver != null) {
try {
Message msg = Message.obtain(null, what, arg1, arg2);
msg.replyTo = mMsgReceiver;
if(msgData != null){
msg.setData(msgData);
}
mMsgSender.send(msg);
} catch (RemoteException rE) {
onException(rE);
}
}
}
public void sendHandshake(){
if(mMsgSender != null && mMsgReceiver != null){
sendMessage(IOnHandleMessage.MSG_HANDSHAKE, 0, 0, null);
}
}
/* END Public Methods */
/* START Private Methods */
private void onException(Exception e){
Log.e(LOGCAT, e.getMessage());
e.printStackTrace();
}
/* END Private Methods */
/** START Private Classes **/
private class MessageHandler extends Handler {
// Types
final static int TYPE_SERVICE = 0x1;
final static int TYPE_ACTIVITY = 0x2;
private IOnHandleMessage mCallback;
private int mType;
public MessageHandler(IOnHandleMessage callback, int type){
mCallback = callback;
mType = type;
}
#Override
public void handleMessage(Message msg){
addMessage(msg);
switch(msg.what){
case IOnHandleMessage.MSG_HANDSHAKE:
switch(mType){
case TYPE_SERVICE:
setMsgSender(msg.replyTo);
sendHandshake();
break;
case TYPE_ACTIVITY:
Log.v(LOGCAT, "HERE");
break;
}
break;
default:
if(mCallback != null){
mCallback.onHandleMessage(msg);
}
break;
}
}
}
/** END Private Classes **/
}
In Activity Example:
public class activity extends AppCompatActivity
implements ServiceConnection,
MessageManager.IOnHandleMessage {
[....]
private MessageManager mMessenger;
private void initMyMessenger(IBinder iBinder){
mMessenger = new MessageManager(this, iBinder);
mMessenger.sendHandshake();
}
private void bindToService(){
Intent intent = new Intent(this, TagScanService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
/* START THE SERVICE IF NEEDED */
}
private void unbindToService(){
/* UNBIND when you want (onDestroy, after operation...)
if(mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
/* START Override MessageManager.IOnHandleMessage Methods */
#Override
public void onHandleMessage(Message msg) {
switch(msg.what){
case Constants.MSG_SYNC_PROGRESS:
Bundle data = msg.getData();
String text = data.getString(Constants.KEY_MSG_TEXT);
setMessageProgress(text);
break;
case Constants.MSG_START_SYNC:
onStartSync();
break;
case Constants.MSG_END_SYNC:
onEndSync(msg.arg1 == Constants.ARG1_SUCCESS);
mBound = false;
break;
}
}
/* END Override MessageManager.IOnHandleMessage Methods */
/** START Override ServiceConnection Methods **/
private class BLEScanServiceConnection implements ServiceConnection {
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
initMyMessenger(iBinder);
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
mMessenger = null;
mBound = false;
}
}
/** END Override ServiceConnection Methods **/
In Service Example:
public class Blablabla extends Service
implements MessageManager.IOnHandleMessage {
[...]
private MessageManager mMessenger;
#Nullable
#Override
public IBinder onBind(Intent intent) {
super.onBind(intent);
initMessageManager();
return mMessenger.getMsgReceiver().getBinder();
}
private void initMessageManager(){
mMessenger = new MessageManager(this);
}
/* START Override IOnHandleMessage Methods */
#Override
public void onHandleMessage(Message msg) {
/* Do what you want when u get a message looking the "what" attribute */
}
/* END Override IOnHandleMessage Methods */
Send a message from Activity / Service:
mMessenger.sendMessage(what, arg1, arg2, dataBundle);
How this works:
on the activity you start or bind the service.
The service "OnBind" methods return the Binder to his MessageManager, the in the Activity through the "Service Connection" interface methods implementation "OnServiceConnected" you get this IBinder and init you MessageManager using it.
After the Activity has init his MessageManager the MessageHandler send and Handshake to the service so it can set his "MessageHandler" sender ( the "private Messenger mMsgSender;" in MessageManager ). Doing this the service know to who send his messages.
You can also implement this using a List/Queue of Messenger "sender" in the MessageManager so you can send multiple messages to different Activities/Services or you can use a List/Queue of Messenger "receiver" in the MessageManager so you can receive multiple message from different Activities/Services.
In the "MessageManager" instance you have a list of all messages received.
As you can see the connection between "Activity's Messenger" and "Service Messenger" using this "MessageManager" instance is automatic, it is done through the "OnServiceConnected" method and through the use of the "Handshake".
Hope this is helpful for you :) Thank you very much!
Bye :D
To follow up on #MrSnowflake answer with a code example.
This is the XABBER now open source Application class. The Application class is centralising and coordinating Listeners and ManagerInterfaces and more. Managers of all sorts are dynamically loaded. Activity´s started in the Xabber will report in what type of Listener they are. And when a Service start it report in to the Application class as started. Now to send a message to an Activity all you have to do is make your Activity become a listener of what type you need. In the OnStart() OnPause() register/unreg. The Service can ask the Application class for just that listener it need to speak to and if it's there then the Activity is ready to receive.
Going through the Application class you'll see there's a loot more going on then this.
As mentioned by Madhur, you can use a bus for communication.
In case of using a Bus you have some options:
Otto event Bus library (deprecated in favor of RxJava)
http://square.github.io/otto/
Green Robot’s EventBus
http://greenrobot.org/eventbus/
NYBus (RxBus, implemented using RxJava. very similar to the EventBus)
https://github.com/MindorksOpenSource/NYBus

Categories

Resources