Android Update Activity UI from Service

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

Keep updating Android UI from service, when activity is closed / restarted

I solved using a LocalBroadcastManager as outlined in this question: Android update activity UI from service

How can i update UI from service using livedata?

Suppose you use have a service with live data as follows

class MusicService: Service() {
companion object {
const val STAT_PLAY = "playing"
const val STAT_PAUSE = "pause"
val playerStateLiveData = MutableLiveData<String>()
}
/*
* your code here to implement play, pause, etc
* */
private fun playAt(duration: Int) {
/*play music player logic*/
//update live data
playerStateLiveData.postValue(STAT_PLAY)
}
private fun pause() {
/*pause music player logic*/
//update live data
playerStateLiveData.postValue(STAT_PAUSE)
}
}

And then on your activity, you can cast the MutableLiveData as LiveData to get updates

class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)

val liveData: LiveData<String> = MusicService.playerStateLiveData

liveData.observe(this, { musicPlayerState ->
//update main activity view based on state changed
})
}
}

Im suggesting you to use viewmodel to get mutable live data from service

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.

How to update UI in real-time from service?

Use RxJava or EventBus to send your data to fragment and then in fragment update ui.

be careful in fragment you must use runOnUiThread.

How can Android service update the UI of the activity that started it?

Use an async task in your service to handle the work you need done in the background. When you need to update the UI use the progressUpdate method of async task to send a broadcast back to any interested activities.

Pseudo example.

Activity

onCreate -> startService and create new broadcastReceiver. Make sure to override the onReceive method and test for the specific intent.

    mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);

broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(yourActionType)) {
//do work here
}
}
};

onResume -> register as a broadcast receiver

    IntentFilter filter = new IntentFilter();
filter.addAction(yourActionType);
mLocalBroadcastManager.registerReceiver(broadcastReceiver, filter);

Service

onCreate -> create broadcast manager.

   mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);

onStartCommand -> create and execute a new async task if necessary. (onStart could be called multiple times)

Async Task

doInBackground -> Start whatever background task you need. In this case playing music.
Make periodic calls to publishProgress

onProgressUpdate -> sendBroadcast indicating updated status

    Intent broadcastIntent = new Intent(yourActionType);
broadcastIntent.putExtra(whateverExtraData you need to pass back);
mLocalBroadcastManager.sendBroadcast(broadcastIntent);

onPostExecute -> sendBroadcast indicating task ended

updating ui of activity from service using handler not call oncreate() and start()Command of service

inside the Handler or whatever you take, when you need to update Ui than Use,

runOnUiThread(new  Runnable()
{
public void run()
{
//Code for ui update
}
});


Related Topics



Leave a reply



Submit