116 votes

Mise à jour de l'interface utilisateur de l'activité Android à partir du service

J'ai un service qui vérifie en permanence les nouvelles tâches. S'il y a une nouvelle tâche, je veux rafraîchir l'interface utilisateur de l'activité pour afficher cette information. J'ai trouvé https://github.com/commonsguy/cw-andtutorials/tree/master/18-LocalService/ cet exemple. Est-ce une bonne approche ? D'autres exemples ?

Gracias.

240voto

Clyde Points 1521

Voir ci-dessous ma réponse initiale - ce modèle a bien fonctionné, mais j'ai récemment commencé à utiliser une approche différente pour la communication Service/Activité :

  • Utilisez un service lié qui permet à l'activité d'obtenir une référence directe au service, permettant ainsi de l'appeler directement, plutôt que d'utiliser plutôt que d'utiliser des Intents.

  • Utilisez RxJava pour exécuter des opérations asynchrones.

  • Si le service doit continuer à fonctionner en arrière-plan même lorsqu'il n'y a pas d'interruptions de service, il peut être nécessaire de le faire. aucune activité n'est en cours, démarrez également le service à partir de la classe de façon à ce qu'il ne soit pas arrêté en cas de déliaison.

Les avantages que j'ai trouvés dans cette approche par rapport à la technique startService()/LocalBroadcast sont les suivants

  • Pas besoin d'objets de données pour implémenter Parcelable - ceci est particulièrement important pour moi car je partage maintenant le code entre Android et iOS (en utilisant RoboVM).
  • RxJava fournit un ordonnancement fixe (et multiplateforme) et une composition facile d'opérations asynchrones séquentielles.
  • Cela devrait être plus efficace que l'utilisation d'un LocalBroadcast, bien que la surcharge de l'utilisation de RxJava puisse l'emporter.

Quelques exemples de code. D'abord le 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);
    }
} 

Et une activité qui se lie au service et reçoit les mises à jour de l'altitude pression :

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

La mise en page de cette activité est la suivante :

<?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 le service doit fonctionner en arrière-plan sans activité liée, il peut être lancé à partir de la classe Application également dans OnCreate() en utilisant Context#startService() .


Ma réponse originale (de 2013) :

A votre service : (en utilisant le COPA comme service dans l'exemple ci-dessous).

Utilisez un LocalBroadCastManager. Dans le onCreate de votre service, configurez le diffuseur :

broadcaster = LocalBroadcastManager.getInstance(this);

Lorsque vous voulez informer l'interface utilisateur de quelque chose :

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

Dans votre activité :

Créer un écouteur sur 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.
        }
    };
}

et l'enregistrer dans 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();
}

37voto

Eran Katsav Points 81

Pour moi, la solution la plus simple était d'envoyer une diffusion, dans l'activité oncreate j'ai enregistré et défini la diffusion comme ceci (updateUIReciver est défini comme une instance de classe) :

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

Et depuis le service, vous envoyez l'intention comme ceci :

Intent local = new Intent();

local.setAction("com.hello.action");

this.sendBroadcast(local);

n'oubliez pas de désenregistrer la récupération dans l'activité sur la destruction :

unregisterReceiver(updateUIReciver);

12voto

psykhi Points 1110

J'utiliserais un service lié pour le faire et communiquer avec lui en implémentant un écouteur dans mon activité. Ainsi, si votre application implémente myServiceListener, vous pouvez l'enregistrer en tant qu'auditeur dans votre service après l'avoir lié, appeler listener.onUpdateUI depuis votre service lié et mettre à jour votre interface utilisateur !

9voto

SeanPONeil Points 2586

Je vous recommande de vérifier Otto un EventBus spécialement conçu pour Android. Votre activité/UI peut écouter les événements postés sur le bus à partir du service, et se découpler du backend.

5voto

Reynard Points 331

La solution de Clyde fonctionne, mais il s'agit d'une diffusion, qui, j'en suis sûr, sera moins efficace que l'appel direct d'une méthode. Je peux me tromper, mais je pense que les diffusions sont plutôt destinées à la communication inter-applications.

Je suppose que vous savez déjà comment lier un service avec une activité. Je fais un peu comme le code ci-dessous pour gérer ce genre de problème :

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

J'ai laissé de côté des éléments pour la sécurité du fil, qui est essentielle. Assurez-vous d'utiliser des verrous ou quelque chose de ce genre lors de la vérification et de l'utilisation ou de la modification des références de fragments sur le service.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X