servicios - IU de actividad de actualización de Android del servicio
api 28 android (7)
Tengo un servicio que busca nuevas tareas todo el tiempo. Si hay una tarea nueva, quiero actualizar la IU de actividad para mostrar esa información. Encontré https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/ este ejemplo. ¿Es eso un buen enfoque? ¿Algún otro ejemplo?
Gracias.
La solución de Clyde funciona, pero es una transmisión, y estoy seguro de que será menos eficiente que llamar directamente a un método. Podría estar equivocado, pero creo que las transmisiones son más para la comunicación entre aplicaciones.
Supongo que ya sabes cómo enlazar un servicio con una Actividad. Hago algo así como el siguiente código para manejar este tipo de problema:
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... }
}
Dejé bits para la seguridad del hilo, que es esencial. Asegúrese de usar bloqueos o algo así cuando verifique y use o cambie las referencias de fragmentos en el servicio.
Mi solución podría no ser la más limpia, pero debería funcionar sin problemas. La lógica es simplemente crear una variable estática para almacenar sus datos en el Service
y actualizar su vista cada segundo en su Activity
.
Digamos que tiene un String
en su Service
que desea enviar a un TextView
en su Activity
. Debe tener un aspecto como este
Tu servicio:
public class TestService extends Service {
public static String myString = "";
// Do some stuff with myString
Su actividad:
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);
}
}
Recomiendo ver Otto , un EventBus diseñado específicamente para Android. Su Actividad / IU puede escuchar los eventos publicados en el Bus desde el Servicio, y desacoplarse del back-end.
Usaría un servicio enlazado para hacer eso y comunicarme con él implementando un oyente en mi actividad. Entonces, si su aplicación implementa myServiceListener, puede registrarlo como un oyente en su servicio después de haberlo enlazado, llame a listener.onUpdateUI desde su servicio encuadernado y actualice su UI allí.
Vea a continuación mi respuesta original: ese patrón ha funcionado bien, pero recientemente comencé a utilizar un enfoque diferente para la comunicación de Servicio / Actividad:
- Utilice un servicio enlazado que permita a la Actividad obtener una referencia directa al Servicio, lo que le permite realizar llamadas directas, en lugar de utilizar Intentos.
Use RxJava para ejecutar operaciones asincrónicas.
Si el Servicio necesita continuar operaciones en segundo plano incluso cuando no se está ejecutando ninguna Actividad, también inicie el servicio desde la clase de Aplicación para que no se detenga cuando se libere.
Las ventajas que he encontrado en este enfoque en comparación con la técnica startService () / LocalBroadcast son
- No es necesario que los objetos de datos implementen Parcelable: esto es particularmente importante para mí ya que ahora comparto código entre Android e iOS (usando RoboVM)
- RxJava proporciona programación enlatada (y multiplataforma) y una fácil composición de operaciones asincrónicas secuenciales.
- Esto debería ser más eficiente que usar un LocalBroadcast, aunque la sobrecarga de usar RxJava puede superar eso.
Un código de ejemplo. Primero el servicio:
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);
}
}
Y una actividad que se une al servicio y recibe actualizaciones de altitud de presión:
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);
}
}
El diseño de esta actividad es:
<?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>
Si el servicio necesita ejecutarse en segundo plano sin una actividad enlazada, puede iniciarse desde la clase de aplicación también en OnCreate()
usando Context#startService()
.
Mi respuesta original (desde 2013):
En su servicio: (utilizando COPA como servicio en el ejemplo a continuación).
Use un LocalBroadCastManager. En el servicio onCreate, configure la emisora:
broadcaster = LocalBroadcastManager.getInstance(this);
Cuando desea notificar a la IU de algo:
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);
}
En tu actividad:
Crea un oyente en 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.
}
};
}
y registrarlo en 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();
}
para mí, la solución más simple fue enviar una transmisión, en la actividad oncreate registré y definí la emisión de esta manera (updateUIReciver se define como una instancia de clase):
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);
Y desde el servicio envía la intención de esta manera:
Intent local = new Intent();
local.setAction("com.hello.action");
this.sendBroadcast(local);
no olvides anular el registro de la recuperación en la actividad de destruir:
unregisterReceiver(updateUIReciver);
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);