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:
Polling. The
Activity
periodically asks theService
for the latest data. IMHO, this option sucks, but it's certainly possible.Callbacks. Per jax's answer, the
Activity
registers a callback object ("observer") with theService
. TheService
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 aService
here.Broadcast
Intents
. TheService
broadcasts anIntent
viasendBroadcast()
on a data change. TheActivity
registers aBroadcastReceiver
usingregisterReceiver()
, and thatBroadcastReceiver
is notified of an incoming broadcast. This triggers theActivity
to load the latest data from theService
, or possibly just to get the latest data out of extras in the broadcastIntent
. You can see an example of using that technique with aService
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 whileActivity
is still in background. This way the user feels he gets the data before he switches toActivity
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:
Classic Listener Pattern of Java. You define a listener interface for your Activities, and update is called from the Service.
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 Service
for 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
Calculating the Angle Between a Line and the X-Axis
Runtime.Exec():Reboot in Android
Android - Save Image from Url Onto Sd Card
Multiple Dex Files Define Landroid/Support/Annotation/Animres
Open Gallery App from Android Intent
Android Studio Mailto Intent Doesn't Show Subject and Mail Body
How to Declare on UI Component in Android with Kotlin
Read Data from SQLite Where Column Name Contains Spaces
How Get Value from Linkedhashmap Based on Index Not on Key
How to Remove Only Trailing Spaces of a String in Java and Keep Leading Spaces
Google Signin API Exception 10
How Many Ways to Convert Bitmap to String and Vice-Versa