How to Update Information in an Android Activity from a Background Service

How can I update information in an Android Activity from a background Service

How can I set up my Activity to be
listening to the Service? Is this the
best way to approach this problem?

You have three major options, as I see it:

  1. Polling. The Activity periodically asks the Service for the latest data. IMHO, this option sucks, but it's certainly possible.

  2. Callbacks. Per jax's answer, the Activity registers a callback object ("observer") with the Service. The Service invokes a method on the callback when the data changes, which in turn updates the UI. You can see an example of using that with a Service here.

  3. Broadcast Intents. The Service broadcasts an Intent via sendBroadcast() on a data change. The Activity registers a BroadcastReceiver using registerReceiver(), and that BroadcastReceiver is notified of an incoming broadcast. This triggers the Activity to load the latest data from the Service, or possibly just to get the latest data out of extras in the broadcast Intent. You can see an example of using that technique with a Service here.

update textview in an Activity from a background service even when application is closed

You should probably save the last response in persistant storage (file, sharedprefernce) and simply read it once the app is opened and show the last response.

Can I update the UI of an activity which is in background?

once the download is over , while my Activity A is still in background, my UI should be updated while Activity is still in background. This way the user feels he gets the data before he switches to Activity A.

It isn't possible. You see, the Activity has to pass through the onCreate() and onResume() callbacks for the UI to be changed.

Also, you should be using a bound Service to update the Activity when it returns to the foreground.

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

How can I update UI in several Android Activity from a background Service

Instead of updating directly from Service, I think the Listener Pattern would be a better choice. You can do this in two ways:

  1. Classic Listener Pattern of Java. You define a listener interface for your Activities, and update is called from the Service.

  2. Android Specific. Use Broadcast to broadcast such an update intent from your Service class, and your Activity create an object to receive the broadcast.

Since your update period is measured in seconds, second approach could be easier and more scalable, so it is preferred.

How to update activity UI when app is in background/closed

When your app starts, it should ask the Service for the current state of the player and show that.

While the app is running and in the foreground, it can listen for the broadcast events and update the UI (or its own internal state) accordingly.

When your app goes to the background, it doesn't need to do anything. When it comes again to the foreground (in onResume()) it can again ask the Servicefor the current state of the player.

You can have the Activity bind to the Service and use AIDL to get the current state OR you can just call startService() with an Intent that contains an ACTION or an EXTRA that indicates that you want to know the current state, and the Service can ract to that by sending a broadcast Intent containing the current state, which your Activity can listen for.



Related Topics



Leave a reply



Submit