IU de actividad de actualización de Android del servicio

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.

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 progtwigció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 pressureObserver; private Observable 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 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:

       

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

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í.

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.

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

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