Android update activity UI from service - android

I have a service which is checking for new task all the time. If there is new task, I want to refresh the activity UI to show that info.
I did find https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/ this example. Is that a good approch ? Any other examples?
Thanks.

See below for my original answer - that pattern has worked well, but recently I've started using a different approach to Service/Activity communication:
Use a bound service which enables the Activity to get a direct
reference to the Service, thus allowing direct calls on it, rather
than using Intents.
Use RxJava to execute asynchronous operations.
If the Service needs to continue background operations even when no
Activity is running, also start the service from the Application
class so that it does not get stopped when unbound.
The advantages I have found in this approach compared to the startService()/LocalBroadcast technique are
No need for data objects to implement Parcelable - this is particularly important to me as I am now sharing code between Android and iOS (using RoboVM)
RxJava provides canned (and cross-platform) scheduling, and easy composition of sequential asynchronous operations.
This should be more efficient than using a LocalBroadcast, though the overhead of using RxJava may outweigh that.
Some example code. First the service:
public class AndroidBmService extends Service implements BmService {
private static final int PRESSURE_RATE = 500000; // microseconds between pressure updates
private SensorManager sensorManager;
private SensorEventListener pressureListener;
private ObservableEmitter<Float> pressureObserver;
private Observable<Float> pressureObservable;
public class LocalBinder extends Binder {
public AndroidBmService getService() {
return AndroidBmService.this;
}
}
private IBinder binder = new LocalBinder();
#Nullable
#Override
public IBinder onBind(Intent intent) {
logMsg("Service bound");
return binder;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
#Override
public void onCreate() {
super.onCreate();
sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
if(pressureSensor != null)
sensorManager.registerListener(pressureListener = new SensorEventListener() {
#Override
public void onSensorChanged(SensorEvent event) {
if(pressureObserver != null) {
float lastPressure = event.values[0];
float lastPressureAltitude = (float)((1 - Math.pow(lastPressure / 1013.25, 0.190284)) * 145366.45);
pressureObserver.onNext(lastPressureAltitude);
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}, pressureSensor, PRESSURE_RATE);
}
#Override
public Observable<Float> observePressure() {
if(pressureObservable == null) {
pressureObservable = Observable.create(emitter -> pressureObserver = emitter);
pressureObservable = pressureObservable.share();
}
return pressureObservable;
}
#Override
public void onDestroy() {
if(pressureListener != null)
sensorManager.unregisterListener(pressureListener);
}
}
And an Activity that binds to the service and receives pressure altitude updates:
public class TestActivity extends AppCompatActivity {
private ContentTestBinding binding;
private ServiceConnection serviceConnection;
private AndroidBmService service;
private Disposable disposable;
#Override
protected void onDestroy() {
if(disposable != null)
disposable.dispose();
unbindService(serviceConnection);
super.onDestroy();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.content_test);
serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
logMsg("BlueMAX service bound");
service = ((AndroidBmService.LocalBinder)iBinder).getService();
disposable = service.observePressure()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(altitude ->
binding.altitude.setText(
String.format(Locale.US,
"Pressure Altitude %d feet",
altitude.intValue())));
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
logMsg("Service disconnected");
}
};
bindService(new Intent(
this, AndroidBmService.class),
serviceConnection, BIND_AUTO_CREATE);
}
}
The layout for this Activity is:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.controlj.mfgtest.TestActivity">
<TextView
tools:text="Pressure"
android:id="#+id/altitude"
android:gravity="center_horizontal"
android:layout_gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
If the service needs to run in the background without a bound Activity it can be started from the Application class as well in OnCreate() using Context#startService().
My Original Answer (from 2013):
In your service: (using COPA as service in example below).
Use a LocalBroadCastManager. In your service's onCreate, set up the broadcaster:
broadcaster = LocalBroadcastManager.getInstance(this);
When you want to notify the UI of something:
static final public String COPA_RESULT = "com.controlj.copame.backend.COPAService.REQUEST_PROCESSED";
static final public String COPA_MESSAGE = "com.controlj.copame.backend.COPAService.COPA_MSG";
public void sendResult(String message) {
Intent intent = new Intent(COPA_RESULT);
if(message != null)
intent.putExtra(COPA_MESSAGE, message);
broadcaster.sendBroadcast(intent);
}
In your Activity:
Create a listener on onCreate:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.copa);
receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String s = intent.getStringExtra(COPAService.COPA_MESSAGE);
// do something here.
}
};
}
and register it in onStart:
#Override
protected void onStart() {
super.onStart();
LocalBroadcastManager.getInstance(this).registerReceiver((receiver),
new IntentFilter(COPAService.COPA_RESULT)
);
}
#Override
protected void onStop() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
super.onStop();
}

for me the simplest solution was to send a broadcast, in the activity oncreate i registered and defined the broadcast like this (updateUIReciver is defined as a class instance) :
IntentFilter filter = new IntentFilter();
filter.addAction("com.hello.action");
updateUIReciver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//UI update here
}
};
registerReceiver(updateUIReciver,filter);
And from the service you send the intent like this:
Intent local = new Intent();
local.setAction("com.hello.action");
this.sendBroadcast(local);
don't forget to unregister the recover in the activity on destroy :
unregisterReceiver(updateUIReciver);

I would use a bound service to do that and communicate with it by implementing a listener in my activity. So if your app implements myServiceListener, you can register it as a listener in your service after you have bound with it, call listener.onUpdateUI from your bound service and update your UI in there!

I would recommend checking out Otto, an EventBus tailored specifically to Android. Your Activity/UI can listen to events posted on the Bus from the Service, and decouple itself from the backend.

Clyde's solution works, but it is a broadcast, which I am pretty sure will be less efficient than calling a method directly. I could be mistaken, but I think the broadcasts are meant more for inter-application communication.
I'm assuming you already know how to bind a service with an Activity.
I do something sort of like the code below to handle this kind of problem:
class MyService extends Service {
MyFragment mMyFragment = null;
MyFragment mMyOtherFragment = null;
private void networkLoop() {
...
//received new data for list.
if(myFragment != null)
myFragment.updateList();
}
...
//received new data for textView
if(myFragment !=null)
myFragment.updateText();
...
//received new data for textView
if(myOtherFragment !=null)
myOtherFragment.updateSomething();
...
}
}
class MyFragment extends Fragment {
public void onResume() {
super.onResume()
//Assuming your activity bound to your service
getActivity().mMyService.mMyFragment=this;
}
public void onPause() {
super.onPause()
//Assuming your activity bound to your service
getActivity().mMyService.mMyFragment=null;
}
public void updateList() {
runOnUiThread(new Runnable() {
public void run() {
//Update the list.
}
});
}
public void updateText() {
//as above
}
}
class MyOtherFragment extends Fragment {
public void onResume() {
super.onResume()
//Assuming your activity bound to your service
getActivity().mMyService.mMyOtherFragment=this;
}
public void onPause() {
super.onPause()
//Assuming your activity bound to your service
getActivity().mMyService.mMyOtherFragment=null;
}
public void updateSomething() {//etc... }
}
I left out bits for thread safety, which is essential. Make sure to use locks or something like that when checking and using or changing the fragment references on the service.

Callback from service to activity to update UI.
ResultReceiver receiver = new ResultReceiver(new Handler()) {
protected void onReceiveResult(int resultCode, Bundle resultData) {
//process results or update UI
}
}
Intent instructionServiceIntent = new Intent(context, InstructionService.class);
instructionServiceIntent.putExtra("receiver", receiver);
context.startService(instructionServiceIntent);

My solution might not be the cleanest but it should work with no problems.
The logic is simply to create a static variable to store your data on the Service and update your view each second on your Activity.
Let's say that you have a String on your Service that you want to send it to a TextView on your Activity. It should look like this
Your Service:
public class TestService extends Service {
public static String myString = "";
// Do some stuff with myString
Your Activty:
public class TestActivity extends Activity {
TextView tv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tv = new TextView(this);
setContentView(tv);
update();
Thread t = new Thread() {
#Override
public void run() {
try {
while (!isInterrupted()) {
Thread.sleep(1000);
runOnUiThread(new Runnable() {
#Override
public void run() {
update();
}
});
}
} catch (InterruptedException ignored) {}
}
};
t.start();
startService(new Intent(this, TestService.class));
}
private void update() {
// update your interface here
tv.setText(TestService.myString);
}
}

You could use android Jetpack's LiveData
As per the documentation:
You can extend a LiveData object using the singleton pattern to wrap
system services so that they can be shared in your app. The LiveData
object connects to the system service once, and then any observer that
needs the resource can just watch the LiveData object. For more
information, see Extend
LiveData.
Below is what I did for communication between Service and Activity and also Service and Fragment.
In this example, I have:
a class SyncLogLiveData extending LiveData that contains a SpannableStringBuilder
a service SyncService
a fragment SyncFragment
The Fragment "observes" the LiveData (ie SyncLogLiveData) and performs an action when the LiveData changes.
The LiveData is updated by the Service.
I could also update the LiveData from the Fragment in the same way but don't show it here.
class SyncLogLiveData
public class SyncLogLiveData extends LiveData<SpannableStringBuilder> {
private static SyncLogLiveData sInstance;
private final static SpannableStringBuilder log = new SpannableStringBuilder("");
#MainThread
public static SyncLogLiveData get() {
if (sInstance == null) {
sInstance = new SyncLogLiveData();
}
return sInstance;
}
private SyncLogLiveData() {
}
public void appendLog(String text) {
log.append(text);
postValue(log);
}
public void appendLog(Spanned text) {
log.append(text);
postValue(log);
}
}
in class SyncService
This line of code will update the content of the LiveData
SyncLogLiveData.get().appendLog(message);
You could also make direct use of setValue(...) or postValue(...) methods of LiveData
SyncLogLiveData.get().setValue(message);
class SyncFragment
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
//...
// Create the observer which updates the UI.
final Observer<SpannableStringBuilder> ETAObserver = new Observer<SpannableStringBuilder>() {
#Override
public void onChanged(#Nullable final SpannableStringBuilder spannableLog) {
// Update the UI, in this case, a TextView.
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
textViewLog.setText(spannableLog);
}
});
}
};
// Observe the LiveData, passing in this activity/fragment as the LifecycleOwner and the observer.
SyncLogLiveData.get().observe(getViewLifecycleOwner(), ETAObserver);
//...
}
From within an activity it works the same way, but for .observe(...), you may use this instead
SyncLogLiveData.get().observe(this, ETAObserver);
You could also fetch the current value of the LiveData this way at anytime in your code.
SyncLogLiveData.get().getValue();
Hopefully this will help someone. There wasn't any mention of LiveData in this answer yet.

Related

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.

How to keep polling a bound service?

I looked up on the internet, but couldn't find an example covering my scenario. What I am trying to do is:
1) To start and bind to a service as soon as my activity starts (done)
2) The service then binds itself to another service looking for a user
input from a connected device, and saves a string a string to a variable (done)
3) I would like to send back this string to the activity, so I can check what it
is and based on it to make a network call.
Now number 3) is my challenge. I managed to do it with a Timer that runs for one second and then checks the value written in the service, but somehow this doesn't seem to be the right way and I think that there might be a more mature solution. However, I can't seem to figure it out.
I've taken the code from the documentation and only added the timer. It is just one service in this example that just generates a random number (this will normally be replaced by my second service).
This is the code for the service:
public class LocalService extends Service {
private final IBinder mBinder = new LocalBinder();
private final Random mGenerator = new Random();
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
And this is the code in my activity:
public class MainActivity extends AppCompatActivity {
LocalService mService;
boolean mBound = false;
Timer timer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
timer = new Timer();
}
#Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
timer.schedule(new MyTimerTask(new Handler(), this), 1000, 1000); // run on every second
}
#Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mConnection);
mBound = false;
}
timer.cancel();
timer.purge();
}
private class MyTimerTask extends TimerTask {
Handler handler;
MainActivity ref;
public MyTimerTask(Handler handler, MainActivity ref) {
super();
this.handler = handler;
this.ref = ref;
}
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
if (mBound) {
int num = ref.mService.getRandomNumber();
// just as an example, raise a toast to see if it works
// but otherwise the value will be handled
Toast.makeText(ref, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
});
}
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
My question is: is this a good approach (it works) or is it bad and what's the alternative?
You can use LocalBroadcastManager to send broadcasts from your Service to your Activity. For example, in your Service declare:
public static final String BROADCAST_INTENT = "broadcast_intent";
public static final String BROADCAST_VALUE = "broadcast_value";
private LocalBroadcastManager broadcastManager;
public void onCreate() {
super.onCreate();
broadcastManager = LocalBroadcastManager.getInstance(this);
}
Now whenever you want to send a String to your Activity you can do so like this:
private void sendBroadcast(String value) {
Intent intent = new Intent(BROADCAST_INTENT);
intent.putExtra(BROADCAST_VALUE, value);
broadcastManager.sendBroadcast(intent);
}
In your Activity declare a BroadcastReceiver:
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
handleIntent(intent);
}
};
Register the receiver when you bind to your Service:
IntentFilter broadcastIntentFilter = new IntentFilter();
broadcastIntentFilter.addAction(StreamService.BROADCAST_INTENT);
LocalBroadcastManager.getInstance(context).registerReceiver((broadcastReceiver), broadcastIntentFilter);
And unregister where you unbind from your Service:
LocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver);
Now when your service sends the broadcast you can handle it in your Activity:
private void handleIntent(Intent intent) {
if (intent.getAction().equals(StreamService.BROADCAST_INTENT)) {
String value = intent.getStringExtra(StreamService.BROADCAST_VALUE, "default");
}
}
I would like to send back this string to the activity, so I can check what it is and based on it to make a network call.
Use LocalBroadcastManager, greenrobot's EventBus, Square's Otto, or some other in-process event bus implementation. Raise an event when you have changed data. Have the activity register with the bus to find out about the event. Have the activity use the changed data when the change occurs.
is this a good approach
No.

Calling activity class method from Service class

I have seen many posts in SO regarding this but could not get the exact and most easy way to call an activity method from service class. Is broadcast receiver only the option? No easy way out ? I just need to call the following method in Activity class after the media player is prepared in Service class .
Activity class:
public void updateProgress() {
// set Progress bar values
songProgressBar.setProgress(0);
songProgressBar.setMax(100);
// Updating progress bar
updateProgressBar();
}
Service class:
#Override
public IBinder onBind(Intent intent) {
Log.d(this.getClass().getName(), "BIND");
return musicBind;
}
#Override
public boolean onUnbind(Intent intent) {
return false;
}
#Override
public void onPrepared(MediaPlayer mp) {
try {
mp.start();
} catch (IllegalStateException e) {
e.printStackTrace();
}
// updateProgress();// Need to call the Activity method here
}
Define an interface your Service will use to communicate events:
public interface ServiceCallbacks {
void doSomething();
}
Write your Service class. Your Activity will bind to this service, so follow the sample shown here. In addition, we will add a method to set the ServiceCallbacks.
public class MyService extends Service {
// Binder given to clients
private final IBinder binder = new LocalBinder();
// Registered callbacks
private ServiceCallbacks serviceCallbacks;
// Class used for the client Binder.
public class LocalBinder extends Binder {
MyService getService() {
// Return this instance of MyService so clients can call public methods
return MyService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return binder;
}
public void setCallbacks(ServiceCallbacks callbacks) {
serviceCallbacks = callbacks;
}
}
Write your Activity class following the same guide, but also make it implement your ServiceCallbacks interface. When you bind/unbind from the Service, you will register/unregister it by calling setCallbacks on the Service.
public class MyActivity extends Activity implements ServiceCallbacks {
private MyService myService;
private boolean bound = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(...);
}
#Override
protected void onStart() {
super.onStart();
// bind to Service
Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from service
if (bound) {
myService.setCallbacks(null); // unregister
unbindService(serviceConnection);
bound = false;
}
}
/** Callbacks for service binding, passed to bindService() */
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
// cast the IBinder and get MyService instance
LocalBinder binder = (LocalBinder) service;
myService = binder.getService();
bound = true;
myService.setCallbacks(MyActivity.this); // register
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
bound = false;
}
};
/* Defined by ServiceCallbacks interface */
#Override
public void doSomething() {
...
}
}
Now when your service wants to communicate back to the activity, just call one of the interface methods from earlier. Inside your service:
if (serviceCallbacks != null) {
serviceCallbacks.doSomething();
}
Use Broadcast receiver with service for updating your view from the service class.
For example:
In my activity class
public class ServiceDemoActivity extends Activity {
Intent intent;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final TextView notification = (TextView) findViewById(R.id.notification);
if (CheckIfServiceIsRunning()) {
} else {
startService(new Intent(this, MyService.class));
}
}
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
updateDate(intent);
}
};
private void updateDate(Intent intent) {
String time = intent.getStringExtra("time");
Toast.makeText(getApplicationContext(), "Yea!!! Service called", Toast.LENGTH_SHORT).show();
TextView date = (TextView) findViewById(R.id.date);
date.setText(time);
}
#Override
public void onResume() {
super.onResume();
registerReceiver(broadcastReceiver, new IntentFilter(
MyService.BROADCAST_ACTION));
}
}
And in my service class I am calling my update ui after a few interval of time which updates my UI.
public class MyService extends Service {
public static final String
BROADCAST_ACTION = "com.mukesh.service";
private final Handler handler = new Handler();
#Override
public void onCreate() {
intent = new Intent(BROADCAST_ACTION);
}
#Override
public void onDestroy() {
stopService(intent);
}
#Override
public void onStart(Intent intent, int startid) {
int i = 0;
while (i <= 2) {
if (i > 1) {
i++;
this.onDestroy();
} else {
counter = i;
i++;
handler.removeCallbacks(sendUpdatesToUI);
handler.postDelayed(sendUpdatesToUI, 1 * 1000); // 1 sec
}
}
}
private Runnable sendUpdatesToUI = new Runnable() {
public void run() {
DisplayLoggingInfo();
handler.postDelayed(this, 7 * 1000); // 7 sec
}
};
private void DisplayLoggingInfo() {
intent.putExtra("time", new Date().toLocaleString());
intent.putExtra("counter", String.valueOf(counter));
sendBroadcast(intent);
stopService(intent);
}
}
For complete code check this link
I created a general class called Delegate (it's not a special name, you can name it John) and passed MainActivity class into it as a static field. Then I can access it from the service since its global now. I am not sure if it is cost-effective but it solved the problem for me simple.
My service:
package com.some.package;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;
public class FirebaseInstanceIDService extends FirebaseInstanceIdService {
#Override
public void onTokenRefresh() {
String token = FirebaseInstanceId.getInstance().getToken();
Delegate.theMainActivity.onDeviceTokenChange(token);
}
}
Delegate class:
package com.some.package;
public class Delegate {
static MainActivity theMainActivity;
}
What I did in MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Delegate.theMainActivity = this;
//rest of the code...
}
public void onDeviceTokenChange(String token){
Log.e("updated token:", token);
}
You can't call your sevices method direcly from your activity or vise versa. There are 3 ways to communicate with a service; using broadcasters and receivers, using Messenger or binding to the service. For further information look at http://developer.android.com/guide/components/bound-services.html
You can call from your service
getContentResolver().notifyChange(uri, null);
and in your activity you set up a
getContentResolver().registerContentObserver(uri, false, new ContentObserver(getHandler())
{
public void onChange(boolean selfChange)
{
updateProgress()
}
};
the onChange method will ba called on the UI thread
You can call a method of activity from service by implementing your own listener like this
https://stackoverflow.com/a/18585247/5361964
You might consider running your activity method in runOnUiThread like this:
// method will be called from service
override fun callback(activity: Activity, result: String) {
runOnUiThread{
Toast.makeText(activity, result, Toast.LENGTH_LONG).show()
}
}
I would prefer to use some very easy and cleaner solution provided by
EventBus

Communication between Activity and Service

I am trying to make my own MusicPlayer for android. Where i came to a problem is running some things in background. Main activity manages GUI and up to now all the songs are playing. I wanted to separate GUI and music playing classes. I want to put music managing part in Service and leave other things as they are now.
My problem is that i can't organize communication between Activity and Service as lot of communication is happening between them including moving objects in both directions. I tried many techniques that I searched here on Stack Overflow but every time I had problems. I need Service to be able to send objects to Activity and vice versa. When I add widget i also want it to be able to communicate with Service.
Any tips are appreciated, if you need source code place comment bellow but now in this transition it became chaotic.
Is there any more advanced tutorial on this than calling one method that returns random number from service? :P
EDIT: Possible solution is to use RoboGuice library and move objects with injection
I have implemented communication between Activity and Service using Bind and Callbacks interface.
For sending data to the service I used Binder which retruns the Service instace to the Activity, and then the Activity can access public methods in the Service.
To send data back to the Activity from the Service, I used Callbacks interface like you are using when you want to communicate between Fragment and Activity.
Here is some code samples for each:
The following example shows Activity and Service bidirectional relationship:
The Activity has 2 buttons:
The first button will start and stop the service.
The second button will start a timer which runs in the service.
The service will update the Activity through callback with the timer progress.
My Activity:
//Activity implements the Callbacks interface which defined in the Service
public class MainActivity extends ActionBarActivity implements MyService.Callbacks{
ToggleButton toggleButton;
ToggleButton tbStartTask;
TextView tvServiceState;
TextView tvServiceOutput;
Intent serviceIntent;
MyService myService;
int seconds;
int minutes;
int hours;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
serviceIntent = new Intent(MainActivity.this, MyService.class);
setViewsWidgets();
}
private void setViewsWidgets() {
toggleButton = (ToggleButton)findViewById(R.id.toggleButton);
toggleButton.setOnClickListener(btListener);
tbStartTask = (ToggleButton)findViewById(R.id.tbStartServiceTask);
tbStartTask.setOnClickListener(btListener);
tvServiceState = (TextView)findViewById(R.id.tvServiceState);
tvServiceOutput = (TextView)findViewById(R.id.tvServiceOutput);
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
Toast.makeText(MainActivity.this, "onServiceConnected called", Toast.LENGTH_SHORT).show();
// We've binded to LocalService, cast the IBinder and get LocalService instance
MyService.LocalBinder binder = (MyService.LocalBinder) service;
myService = binder.getServiceInstance(); //Get instance of your service!
myService.registerClient(MainActivity.this); //Activity register in the service as client for callabcks!
tvServiceState.setText("Connected to service...");
tbStartTask.setEnabled(true);
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
Toast.makeText(MainActivity.this, "onServiceDisconnected called", Toast.LENGTH_SHORT).show();
tvServiceState.setText("Service disconnected");
tbStartTask.setEnabled(false);
}
};
View.OnClickListener btListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
if(v == toggleButton){
if(toggleButton.isChecked()){
startService(serviceIntent); //Starting the service
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); //Binding to the service!
Toast.makeText(MainActivity.this, "Button checked", Toast.LENGTH_SHORT).show();
}else{
unbindService(mConnection);
stopService(serviceIntent);
Toast.makeText(MainActivity.this, "Button unchecked", Toast.LENGTH_SHORT).show();
tvServiceState.setText("Service disconnected");
tbStartTask.setEnabled(false);
}
}
if(v == tbStartTask){
if(tbStartTask.isChecked()){
myService.startCounter();
}else{
myService.stopCounter();
}
}
}
};
#Override
public void updateClient(long millis) {
seconds = (int) (millis / 1000) % 60 ;
minutes = (int) ((millis / (1000*60)) % 60);
hours = (int) ((millis / (1000*60*60)) % 24);
tvServiceOutput.setText((hours>0 ? String.format("%d:", hours) : "") + ((this.minutes<10 && this.hours > 0)? "0" + String.format("%d:", minutes) : String.format("%d:", minutes)) + (this.seconds<10 ? "0" + this.seconds: this.seconds));
}
}
And here is the service:
public class MyService extends Service {
NotificationManager notificationManager;
NotificationCompat.Builder mBuilder;
Callbacks activity;
private long startTime = 0;
private long millis = 0;
private final IBinder mBinder = new LocalBinder();
Handler handler = new Handler();
Runnable serviceRunnable = new Runnable() {
#Override
public void run() {
millis = System.currentTimeMillis() - startTime;
activity.updateClient(millis); //Update Activity (client) by the implementd callback
handler.postDelayed(this, 1000);
}
};
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//Do what you need in onStartCommand when service has been started
return START_NOT_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
//returns the instance of the service
public class LocalBinder extends Binder{
public MyService getServiceInstance(){
return MyService.this;
}
}
//Here Activity register to the service as Callbacks client
public void registerClient(Activity activity){
this.activity = (Callbacks)activity;
}
public void startCounter(){
startTime = System.currentTimeMillis();
handler.postDelayed(serviceRunnable, 0);
Toast.makeText(getApplicationContext(), "Counter started", Toast.LENGTH_SHORT).show();
}
public void stopCounter(){
handler.removeCallbacks(serviceRunnable);
}
//callbacks interface for communication with service clients!
public interface Callbacks{
public void updateClient(long data);
}
}
Update: July 10 2016
IMO I think using BroadcastReceiver for custom events is better way
as the Messengers mentioned don't handle activity recreation on device
rotation as well as possible memory leaks.
You may create custom BroadCast Receiver for events in the activity, Then you may also use Messengers.
In your Activity
create a MessageHandler class as
public static class MessageHandler extends Handler {
#Override
public void handleMessage(Message message) {
int state = message.arg1;
switch (state) {
case HIDE:
progressBar.setVisibility(View.GONE);
break;
case SHOW:
progressBar.setVisibility(View.VISIBLE);
break;
}
}
}
Now you can have it's instance as
public static Handler messageHandler = new MessageHandler();
Start your Service with this Handler object as an extra data as
Intent startService = new Intent(context, SERVICE.class)
startService.putExtra("MESSENGER", new Messenger(messageHandler));
context.startService(startService);
In your Service you receive this object from the intent and initialize the Messenger variable in Service as
private Messenger messageHandler;
Bundle extras = intent.getExtras();
messageHandler = (Messenger) extras.get("MESSENGER");
sendMessage(ProgressBarState.SHOW);
And then write a method sendMessage to send messages to activity.
public void sendMessage(ProgressBarState state) {
Message message = Message.obtain();
switch (state) {
case SHOW :
message.arg1 = Home.SHOW;
break;
case HIDE :
message.arg1 = Home.HIDE;
break;
}
try {
messageHandler.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
The sample code above shows and hides a ProgressBar in Activity as messages are received from Service.
Intents are good solution for communication between Activitiy and Service.
A fast solution for receive intents in your service is subclassing IntentService class. It handles asynchronous requests expressed as Intents using a queue and worker thread.
For communication from service to Activity you can broadcast the intent but instead of using normal sendBroadcast() from Context, a more efficent way is to use LocalBroadcastManager from support library.
Example service.
public class MyIntentService extends IntentService {
private static final String ACTION_FOO = "com.myapp.action.FOO";
private static final String EXTRA_PARAM_A = "com.myapp.extra.PARAM_A";
public static final String BROADCAST_ACTION_BAZ = "com.myapp.broadcast_action.FOO";
public static final String EXTRA_PARAM_B = "com.myapp.extra.PARAM_B";
// called by activity to communicate to service
public static void startActionFoo(Context context, String param1) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_FOO);
intent.putExtra(EXTRA_PARAM1, param1);
context.startService(intent);
}
public MyIntentService() {
super("MyIntentService");
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_FOO.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM_A);
// do something
}
}
}
// called to send data to Activity
public static void broadcastActionBaz(String param) {
Intent intent = new Intent(BROADCAST_ACTION_BAZ);
intent.putExtra(EXTRA_PARAM_B, param);
LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
bm.sendBroadcast(intent);
}
}
Example Activity
public class MainActivity extends ActionBarActivity {
// handler for received data from service
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(MyIntentService.BROADCAST_ACTION_BAZ)) {
final String param = intent.getStringExtra(EXTRA_PARAM_B);
// do something
}
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter filter = new IntentFilter();
filter.addAction(MyIntentService.BROADCAST_ACTION_BAZ);
LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
bm.registerReceiver(mBroadcastReceiver, filter);
}
#Override
protected void onDestroy() {
LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
bm.unregisterReceiver(mBroadcastReceiver);
super.onDestroy();
}
// send data to MyService
protected void communicateToService(String parameter) {
MyIntentService.startActionFoo(this, parameter);
}
}
I think there is a problem with the correct answer. I have not enough reputation to comment on it.
Right in the answer:
Activity call bindService() to get pointer to Service is ok. Because service context is maintained when connection is maintained.
wrong in the answer:
service pointer to Activity class to call back is bad way. Activity instance maybe not null during Activity context is being Release => exception here.
solution for the wrong in the answer:
service send intent to Activity. and Activity receiver intent via BroadcastReceiver.
Note:
in this case, Service and Activity in the same Process, you should use LocalBroadcastManager to send intent. It make performance and security better
This is a simple example of communication between activity and service
Activity
MyReceiver myReceiver; //my global var receiver
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layourAwesomexD);
registerReceiver();
}
//When the activity resume, the receiver is going to register...
#Override
protected void onResume() {
super.onResume();
checkStatusService(); // verficarStatusServicio(); <- name change
registerReceiver();
}
//when the activity stop, the receiver is going to unregister...
#Override
protected void onStop() {
unregisterReceiver(myReceiver); //unregister my receiver...
super.onStop();
}
//function to register receiver :3
private void registerReceiver(){
//Register BroadcastReceiver
//to receive event from our service
myReceiver = new MyReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(MyService.SENDMESAGGE);
registerReceiver(myReceiver, intentFilter);
}
// class of receiver, the magic is here...
private class MyReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context arg0, Intent arg1) {
//verify if the extra var exist
System.out.println(arg1.hasExtra("message")); // true or false
//another example...
System.out.println(arg1.getExtras().containsKey("message")); // true or false
//if var exist only print or do some stuff
if (arg1.hasExtra("message")) {
//do what you want to
System.out.println(arg1.getStringExtra("message"));
}
}
}
public void checkStatusService(){
if(MyService.serviceStatus!=null){
if(MyService.serviceStatus == true){
//do something
//textview.text("Service is running");
}else{
//do something
//textview.text("Service is not running");
}
}
}
Service
public class MyService extends Service {
final static String SENDMESAGGE = "passMessage";
public static Boolean serviceStatus = false;
#Override
public void onCreate() {
super.onCreate();
serviceStatus=true;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {return null;}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//you service etc...
passMessageToActivity("hello my friend this an example of send a string...");
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
passMessageToActivity("The service is finished, This is going to be more cooler than the heart of your ex...");
System.out.println("onDestroy");
serviceStatus=false;
}
private void passMessageToActivity(String message){
Intent intent = new Intent();
intent.setAction(SENDMESAGGE);
intent.putExtra("message",message);
sendBroadcast(intent);
}
}
if we don't unregister BroadcastReceiver we will have an error, you need to unregister when the activity go onPause, onStop, onDestroy...
if you don't register BroadcastReceiver when you back to activity, it will not listen anything from the service... the service will send information to BroadcastReceiver but it will not receive anything because it isn't registered.
When you create more than one service, the following services are going to begin in onStartCommand.
You can pass information to service with intent and you get it in onStartCommand
Difference about return in onStartCommand: Difference between START_STICKY and START_REDELIVER_INTENT? and check the official website of google: Services
The best way in this case is to communicate by doing broadcasting from your service for different actions and receiving it in your activity. You can create a custom broadcast and send some codes defining specific events like complete, change, prepare etc...
Most easy and efficient way will be using EventBus from GreenRobot.
Use simple 3 steps:
1 Define events
public static class MessageEvent { /* Additional fields if needed */ }
2 Prepare subscribers: Declare and annotate your subscribing method, optionally specify a thread mode:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
Register and unregister your subscriber. For example on Android, activities and fragments should usually register according to their life cycle:
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
3 Post events:
EventBus.getDefault().post(new MessageEvent());
Very easy yet powerful way is to use EventBus you can add it to your gradle build and enjoy the easy publisher/subscriber pattern .

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