More Efficient Way of Updating UI from Service Than Intents

More efficient way of updating UI from Service than intents?

UPDATE 2015:

This question/answer still gets a little bit of activity, but it is over 5 yrs old and things have changed quite a bit. 5 years ago, the answer below was how I would have handled it. Later I wrote a very lightweight dependency injection solution that I was using for a while (which I mentioned in the comments). Nowadays, I would answer this question using Dagger and RxAndroid. Dagger to inject a "mediator" class into both the Service and all Activities that need to be notified, the Service would push the status update to the mediator class, and the mediator class would expose an observable for the activities to consume the status update (in place of the OP's broadcast receiver).

Original answer

I usually subclass Application and let my in-app communication go through this class (or have a mediator owned by the Application do the work...regardless, the Application being the entry point for the service to communicate with). I have a bound service that needs to update the UI as well (much simpler than yours, but the same idea) and it basically tells the app its new state and the app can then pass this information along in one way or another to the currently active activity. You can also maintain a pointer to the currently active activity (if there is more than one), and make decisions whether or not to simply update the current activity, broadcast the intent to launch a different activity, ignore the message, etc. I would also subclass Activity and have your new activity base class tell the Application that it is currently the active one in onResume and that it is being paused in onPause (for cases where your service is running in the background and the activities are all paused).

EDIT:

In response to the comment, here's more specifics.

Your application currently consists of Activity-derived and Service-derived classes for the most part. Inherently, you get functionality from an instance of the android.app.Application class. This is declared in your manifest (by default) with the following line:

<application android:icon="@drawable/icon" android:label="@string/app_name">

The application element in your manifest doesn't use the android:name attribute, so it just creates an instance of the default android.app.Application class to represent your global application context.

In my apps, I create a subclass of Application (ApplicationEx, for example) and I tell my app through the manifest that this is the class to instantiate as MY global application context. For example:

<application
android:name="com.mycompany.myapp.app.ApplicationEx"
android:icon="@drawable/app_icon"
android:label="@string/app_name">

I can now add methods to ApplicationEx for activities and services to use to communicate. There is always a single instance of your global application context, so this is your starting point if anything needs to be global for your app.

A second piece of this is that instead of deriving my services and activities from Service and Activity, I create a subclass of each with a getAppContext method that casts the return value of getApplicationContext (which exists already in both of these classes because they derive from Context) to my ApplicationEx class.

So........

All that being said, you add a CurrentActivity property to your ApplicationEx class of type Activity (or ActivityBase if you subclass it as I do). In ActivityBase's onResume method, you pass yourself to ApplicationEx for it to set CurrentActivity to that activity. Now, you can expose methods on ApplicationEx to pass information directly to the current activity instead of relying on the Intent mechanisms.

That's about as clear as I can make it

A more efficient way to update the UI from a service?

If you only want to run it when the main activity is visible, then implement a method to do that. Example: How to detect when an Android app goes to the background and come back to the foreground

Then for the "static list of server objects" I would be careful with static stuff, it's usually not a good idea (and it's "technically slower" to access static members).

For the "timer" don't do that. Instead have your Service broadcast an intent (or if you bind it, have your activity implement a custom interface that the server can talk to in a typical observer/listener pattern).

E.g.:

public interface ServerStatusListener {
void onServerIsUp();
void onServerIsDown();
}

If you go the Broadcast Intent route, have a small Broadcast Receiver in your base activity that listens to this:

public class ServerStatusReceiver extends BroadcastReceiver {
public void onReceive(final Context context, final Intent intent) {
// analyze the intent and determine what happened
// pseudo code
if (intent.getBooleanExtra("com.your.app.EXTRA_NETWORK_IS_UP", false) ) {
serviceIsUp();
} else {
serviceIsDown();
}
}
}

You get the idea. Both approaches are ok. Different concepts. Remember that the onReceive method is already running on the UI thread, don't perform expensive stuff there.

Read the Android documentation about services, broadcast receivers, etc. It's good material and you will want to know this if you're going to be an Android developer.

Have fun!

Android update activity UI from service

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

Update UI from service

My suggestion to you is, create a handler for the UI part which updates the text field or UI components.

Secondly, have notifications from the service to the activity by way of interface class.

Updating Activity UI from Intent Service?

Is there anyother possible way of exchanging data between IntentService and Activity which is efficient?

Use an event bus, like LocalBroadcastManager, greenrobot's EventBus, or Square's Otto. Have the IntentService post events as needed (e.g., when a file is done downloading). Your activities/fragments can register and unregister for events as they come and go from the foreground. If they are in the foreground, they will receive the event and be able to update the UI. You can even detect if the event was not picked up by the foreground UI and have the service display a Notification, if desired.

This directory contains three sample apps demonstrating this for the three event bus implementations that I cited. And, FWIW, here is the PDF of slides that I used in recent webinars on using event buses.



Related Topics



Leave a reply



Submit