Comment vérifier si un service d'arrière-plan (sous Android) est en cours d'exécution?
Je souhaite une activité Android qui modifie l'état du service. Elle me permet de l'activer si elle est désactivée et de l'activer si elle est activée.
J'ai eu le même problème il n'y a pas longtemps. Comme mon service était local, j'ai fini par utiliser un champ statique dans la classe de service pour basculer d’un état à l’autre, comme décrit par hackbod ici
EDIT (pour l'enregistrement):
Voici la solution proposée par hackbod:
Si vos codes client et serveur font partie du même .apk et que vous êtes liant au service avec une intention concrète (celle qui spécifie la classe de service exacte), vous pouvez simplement demander à votre service de définir un variable globale en cours d’exécution que votre client peut vérifier.
Nous n'avons délibérément pas d'API pour vérifier si un service est en cours d'exécution parce que, presque sans faute, lorsque vous voulez faire quelque chose. comme ça, vous vous retrouvez avec des conditions de concurrence dans votre code.
J'utilise les éléments suivants à l'intérieur d'une activité:
private boolean isMyServiceRunning(Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
Et je l'appelle en utilisant:
isMyServiceRunning(MyService.class)
Cela fonctionne de manière fiable, car il est basé sur les informations relatives à l'exécution de services fournis par le système d'exploitation Android via ActivityManager # getRunningServices .
Toutes les approches utilisant des événements onDestroy ou onSometing, des liants ou des variables statiques ne fonctionneront pas de manière fiable, car en tant que développeur, vous ne savez jamais, quand Android décide de tuer votre processus ou quels rappels mentionnés sont appelés ou non. Veuillez noter la colonne "killable" dans le tableau lifecycle events dans la documentation Android.
Je l'ai!
VousDEVEZappeler startService()
pour que votre service soit correctement enregistré et transmettre BIND_AUTO_CREATE
ne suffira pas.
Intent bindIntent = new Intent(this,ServiceTask.class);
startService(bindIntent);
bindService(bindIntent,mConnection,0);
Et maintenant la classe ServiceTools:
public class ServiceTools {
private static String LOG_TAG = ServiceTools.class.getName();
public static boolean isServiceRunning(String serviceClassName){
final ActivityManager activityManager = (ActivityManager)Application.getContext().getSystemService(Context.ACTIVITY_SERVICE);
final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
for (RunningServiceInfo runningServiceInfo : services) {
if (runningServiceInfo.service.getClassName().equals(serviceClassName)){
return true;
}
}
return false;
}
}
Un petit complément c'est:
Mon but est de savoir si un service est en cours d'exécution sans le lancer réellement s'il ne fonctionne pas.
Il n'est pas judicieux d'appeler bindService ou d'appeler une intention susceptible d'être capturée par le service, car il lancera le service s'il n'est pas en cours d'exécution.
Ainsi, comme le suggère miracle2k, le mieux est d’avoir un champ statique dans la classe de service pour savoir si le service a été démarré ou non.
Pour le rendre encore plus propre, je suggère de transformer le service en un singleton avec une extraction très très paresseuse: c’est-à-dire qu’il n’y a pas d’instanciation du singleton instance via des méthodes statiques. La méthode statique getInstance de votre service/singleton renvoie simplement l'instance du singleton si elle a été créée. Mais cela ne démarre ni n’instancie le singleton lui-même. Le service est uniquement démarré via les méthodes normales de démarrage du service.
Il serait alors encore plus simple de modifier le modèle de conception singleton pour renommer la méthode getInstance, qui prête à confusion, en quelque chose comme la méthode isInstanceCreated() : boolean
.
Le code ressemblera à ceci:
public class MyService extends Service
{
private static MyService instance = null;
public static boolean isInstanceCreated() {
return instance != null;
}//met
@Override
public void onCreate()
{
instance = this;
....
}//met
@Override
public void onDestroy()
{
instance = null;
...
}//met
}//class
Cette solution est élégante, mais elle n’est pertinente que si vous avez accès à la classe de service et uniquement aux classes iside de l’application/du package du service. Si vos classes sont en dehors de l'application/package de service, vous pouvez interroger ActivityManager avec les limitations soulignées par Pieter-Jan Van Robays.
Vous pouvez utiliser ceci (je ne l'ai pas encore essayé, mais j'espère que cela fonctionnera):
if(startService(someIntent) != null) {
Toast.makeText(getBaseContext(), "Service is already running", Toast.LENGTH_SHORT).show();
}
else {
Toast.makeText(getBaseContext(), "There is no service running, starting service..", Toast.LENGTH_SHORT).show();
}
La méthode startService renvoie un objet ComponentName s'il existe déjà un service. Sinon, null sera retourné.
Voir public abstract ComponentNameName startService (service Intent).
Ce n'est pas comme si je vérifiais, car il lance le service, vous pouvez donc ajouter stopService(someIntent);
sous le code.
public boolean checkServiceRunning(){
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE))
{
if ("com.example.yourpackagename.YourServiceName"
.equals(service.service.getClassName()))
{
return true;
}
}
return false;
}
J'ai légèrement modifié l'une des solutions présentées ci-dessus, mais en passant la classe à la place d'un nom de chaîne générique, afin de pouvoir comparer les chaînes issues de la même méthode class.getName()
public class ServiceTools {
private static String LOG_TAG = ServiceTools.class.getName();
public static boolean isServiceRunning(Context context,Class<?> serviceClass){
final ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
for (RunningServiceInfo runningServiceInfo : services) {
Log.d(Constants.TAG, String.format("Service:%s", runningServiceInfo.service.getClassName()));
if (runningServiceInfo.service.getClassName().equals(serviceClass.getName())){
return true;
}
}
return false;
}
}
et alors
Boolean isServiceRunning = ServiceTools.isServiceRunning(
MainActivity.this.getApplicationContext(),
BackgroundIntentService.class);
Ceci est un extrait de Android docs:
Comme sendBroadcast (Intent) , mais s’il existe des destinataires pour l’intention cette fonction bloquera et les enverra immédiatement avant de revenir.
_ {Pensez à ce hack comme à un "ping" de la Service
puisque nous pouvons diffuser de manière syncrone, nous pouvons diffuser et obtenir un résultat - de manière synchrone - sur le thread d'interface utilisateur.
Service
@Override
public void onCreate() {
LocalBroadcastManager
.getInstance(this)
.registerReceiver(new ServiceEchoReceiver(), IntentFilter("ping");
}
private class ServiceEchoReceiver{
public void onReceive (Context context, Intent intent) {
LocalBroadcastManager
.getInstance(this)
.sendBroadcastSync(new Intent("pong"));
}
}
Activity
bool serviceRunning = false;
protected void onCreate (Bundle savedInstanceState){
LocalBroadcastManager.getInstance(this).registerReceiver(pong, new IntentFilter("pong"));
LocalBroadcastManager.getInstance(this).sendBroadcastSync(new Intent("ping"));
if(!serviceRunning){
//run the service
}
}
private BroadcastReceiver pong = new BroadcastReceiver(){
public void onReceive (Context context, Intent intent) {
serviceRunning = true;
}
}
Je veux juste ajouter une note à la réponse de @Snicolas. Les étapes suivantes peuvent être utilisées pour vérifier l’arrêt du service avec/sans avoir appelé onDestroy()
.
onDestroy()
appelé: Accédez à Paramètres -> Application -> Services en cours d'exécution -> Sélectionnez et arrêtez votre service.
onDestroy()
not Called: Accédez à Paramètres -> Application -> Gérer les applications -> Sélectionnez et "Forcer l'arrêt" de votre application dans laquelle votre service est exécuté. Cependant, comme votre application est arrêtée ici, les instances de service le seront également.
Enfin, je voudrais mentionner que l’approche mentionnée ici, qui utilise une variable statique dans la classe singleton, fonctionne pour moi.
onDestroy
n'est pas toujours appelé dans le service, donc c'est inutile!
Par exemple: Il suffit de relancer l'application avec une modification d'Eclipse. L’application est abandonnée de force avec SIG: 9.
Pour vérifier si un service est actif, il suffit de le lui demander. Implémentez un BroadcastReceiver dans votre service qui répond aux pings de vos activités. Enregistrez BroadcastReceiver au démarrage du service et annulez l’enregistrement lorsque le service est détruit. À partir de votre activité (ou de tout composant), envoyez une diffusion locale intention au service et si celle-ci répond, vous savez que celle-ci est active. Notez la différence subtile entre ACTION_PING et ACTION_PONG dans le code ci-dessous.
public class PingableService extends Service
{
public static final String ACTION_PING = PingableService.class.getName() + ".PING";
public static final String ACTION_PONG = PingableService.class.getName() + ".PONG";
public int onStartCommand (Intent intent, int flags, int startId)
{
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(ACTION_PING));
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy ()
{
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
super.onDestroy();
}
private BroadcastReceiver mReceiver = new BroadcastReceiver()
{
@Override
public void onReceive (Context context, Intent intent)
{
if (intent.getAction().equals(ACTION_PING))
{
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getApplicationContext());
manager.sendBroadcast(new Intent(ACTION_PONG));
}
}
};
}
public class MyActivity extends Activity
{
private boolean isSvcRunning = false;
@Override
protected void onStart()
{
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getApplicationContext());
manager.registerReceiver(mReceiver, new IntentFilter(PingableService.ACTION_PONG));
// the service will respond to this broadcast only if it's running
manager.sendBroadcast(new Intent(PingableService.ACTION_PING));
super.onStart();
}
@Override
protected void onStop()
{
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
super.onStop();
}
protected BroadcastReceiver mReceiver = new BroadcastReceiver()
{
@Override
public void onReceive (Context context, Intent intent)
{
// here you receive the response from the service
if (intent.getAction().equals(PingableService.ACTION_PONG))
{
isSvcRunning = true;
}
}
};
}
Version Xamarin C #:
private bool isMyServiceRunning(System.Type cls)
{
ActivityManager manager = (ActivityManager)GetSystemService(Context.ActivityService);
foreach (var service in manager.GetRunningServices(int.MaxValue)) {
if (service.Service.ClassName.Equals(Java.Lang.Class.FromType(cls).CanonicalName)) {
return true;
}
}
return false;
}
Tout d'abord, vous ne devez pas essayer d'accéder au service en utilisant ActivityManager. (Discuté ici )
Les services peuvent fonctionner seuls, être liés à une activité ou aux deux. La façon de vérifier dans une activité si votre service est en cours d'exécution ou non consiste à créer une interface (qui étend le classeur) dans laquelle vous déclarez des méthodes que l'activité et le service comprennent. Vous pouvez le faire en créant votre propre interface dans laquelle vous déclarez, par exemple, "isServiceRunning ()" . Vous pouvez ensuite lier votre activité à votre service, exécuter la méthode isServiceRunning (), le service vérifie lui-même s’il est exécuté ou pas et renvoie un booléen à votre activité.
Vous pouvez également utiliser cette méthode pour arrêter votre service ou interagir avec celui-ci d'une autre manière.
J'ai utilisé ce tutorial pour apprendre à implémenter ce scénario dans mon application.
Pour le cas d'utilisation donné ici, nous pouvons simplement utiliser la valeur de retour de la méthode stopService()
. Il retourne true
s'il existe le service spécifié et s'il est tué. Sinon, il retourne false
. Vous pouvez donc redémarrer le service si le résultat est false
, sinon il est assuré que le service actuel a été arrêté. :) Ce serait mieux si vous jetez un oeil à this .
Encore une fois, une autre alternative que les gens pourraient trouver plus propre s’ils utilisent des intentions en attente (par exemple avec le AlarmManager
:
public static boolean isRunning(Class<? extends Service> serviceClass) {
final Intent intent = new Intent(context, serviceClass);
return (PendingIntent.getService(context, CODE, intent, PendingIntent.FLAG_NO_CREATE) != null);
}
Où CODE
est une constante que vous définissez en privé dans votre classe pour identifier les intentions en attente associées à votre service.
Ci-dessous se trouve un hack élégant qui couvre tous les Ifs
. Ceci est pour les services locaux seulement.
public final class AService extends Service {
private static AService mInstance = null;
public static boolean isServiceCreated() {
try {
// If instance was not cleared but the service was destroyed an Exception will be thrown
return mInstance != null && mInstance.ping();
} catch (NullPointerException e) {
// destroyed/not-started
return false;
}
}
/**
* Simply returns true. If the service is still active, this method will be accessible.
* @return
*/
private boolean ping() {
return true;
}
@Override
public void onCreate() {
mInstance = this;
}
@Override
public void onDestroy() {
mInstance = null;
}
}
Et puis plus tard:
if(AService.isServiceCreated()){
...
}else{
startService(...);
}
Il peut y avoir plusieurs services avec le même nom de classe.
Je viens de créer deux applications. Le nom de package de la première application est com.example.mock
. J'ai créé un sous-paquet appelé lorem
dans l'application et un service appelé Mock2Service
. Donc, son nom complet est com.example.mock.lorem.Mock2Service
.
Ensuite, j'ai créé la deuxième application et un service appelé Mock2Service
. Le nom de package de la deuxième application est com.example.mock.lorem
. Le nom complet du service est également com.example.mock.lorem.Mock2Service
.
Voici ma sortie logcat.
03-27 12:02:19.985: D/TAG(32155): Mock-01: com.example.mock.lorem.Mock2Service
03-27 12:02:33.755: D/TAG(32277): Mock-02: com.example.mock.lorem.Mock2Service
Une meilleure idée est de comparer les instances ComponentName
car equals()
de ComponentName
compare les noms de package et les noms de classe. Et il ne peut pas y avoir deux applications avec le même nom de package installées sur un appareil.
La méthode equals () de ComponentName
.
@Override
public boolean equals(Object obj) {
try {
if (obj != null) {
ComponentName other = (ComponentName)obj;
// Note: no null checks, because mPackage and mClass can
// never be null.
return mPackage.equals(other.mPackage)
&& mClass.equals(other.mClass);
}
} catch (ClassCastException e) {
}
return false;
}
Dans kotlin, vous pouvez ajouter une variable booléenne dans un objet compagnon et vérifier sa valeur dans la classe de votre choix:
companion object{
var isRuning = false
}
Changer la valeur lorsque le service est créé et détruit
override fun onCreate() {
super.onCreate()
isRuning = true
}
override fun onDestroy() {
super.onDestroy()
isRuning = false
}
La réponse de geekQ mais en classe Kotlin. Merci geekQ
fun isMyServiceRunning(serviceClass : Class<*> ) : Boolean{
var manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.name.equals(service.service.className)) {
return true
}
}
return false
}
L'appel
isMyServiceRunning(NewService::class.Java)
Dans TheServiceClass, définissez:
public static Boolean serviceRunning = false;
Puis dans onStartCommand (...)
public int onStartCommand(Intent intent, int flags, int startId) {
serviceRunning = true;
...
}
@Override
public void onDestroy()
{
serviceRunning = false;
}
Ensuite, appelez if(TheServiceClass.serviceRunning == true)
depuis n’importe quelle classe.
Dans votre sous-classe de services Utilisez un booléen statique pour obtenir l'état du service comme indiqué ci-dessous.
MyService.kt
class MyService : Service() {
override fun onCreate() {
super.onCreate()
isServiceStarted = true
}
override fun onDestroy() {
super.onDestroy()
isServiceStarted = false
}
companion object {
var isServiceStarted = false
}
}
MainActivity.kt
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val serviceStarted = FileObserverService.isServiceStarted
if (!serviceStarted) {
val startFileObserverService = Intent(this, FileObserverService::class.Java)
ContextCompat.startForegroundService(this, startFileObserverService)
}
}
}
Ma conversion en kotlin des réponses basées sur ActivityManager::getRunningServices
. Mettez cette fonction dans une activité
private fun isMyServiceRunning(serviceClass: Class<out Service>) =
(getSystemService(ACTIVITY_SERVICE) as ActivityManager)
.getRunningServices(Int.MAX_VALUE)
?.map { it.service.className }
?.contains(serviceClass.name) ?: false
Si le service appartient à un autre processus ou à un APK, utilisez la solution basée sur ActivityManager.
Si vous avez accès à sa source, utilisez simplement la solution basée sur un champ statique. Mais au lieu d'utiliser un booléen, je suggérerais d'utiliser un objet Date. Pendant que le service est en cours d'exécution, il suffit de mettre à jour sa valeur sur 'maintenant' et à la fin, définissez-le sur null. À partir de l’activité, vous pouvez vérifier si la valeur NULL ou la date est trop ancienne, ce qui signifie qu’elle ne fonctionne pas.
Vous pouvez également envoyer une notification de diffusion à partir de votre service indiquant qu'elle fonctionne avec d'autres informations telles que la progression.
S'il vous plaît utiliser ce code.
if (isMyServiceRunning(MainActivity.this, xyzService.class)) { // Service class name
// Service running
} else {
// Service Stop
}
public static boolean isMyServiceRunning(Activity activity, Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
utilisation simple lie avec ne pas créer auto - voir ps. et mise à jour ...
public abstract class Context {
...
/*
* @return {true} If you have successfully bound to the service,
* {false} is returned if the connection is not made
* so you will not receive the service object.
*/
public abstract boolean bindService(@RequiresPermission Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags);
exemple :
Intent bindIntent = new Intent(context, Class<Service>);
boolean bindResult = context.bindService(bindIntent, ServiceConnection, 0);
pourquoi ne pas utiliser? getRunningServices ()
List<ActivityManager.RunningServiceInfo> getRunningServices (int maxNum)
Return a list of the services that are currently running.
Remarque: cette méthode est uniquement destinée au débogage ou à la mise en œuvre d'interfaces utilisateur de type gestion de services.
ps. La documentation d'Android est trompeuse. J'ai ouvert un problème sur Google Tracker pour éliminer tout doute:
https://issuetracker.google.com/issues/68908332
comme nous pouvons le voir, le service de liaison appelle en fait une transaction via un classeur ActivityManager via des classeurs de cache de service - i dint le suivi du service responsable de la liaison, mais nous pouvons voir que le résultat de la liaison est le suivant:
int res = ActivityManagerNative.getDefault().bindService(...);
return res != 0;
la transaction est faite par le biais de liant:
ServiceManager.getService("activity");
suivant:
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
ceci est défini dans ActivityThread via:
public final void bindApplication(...) {
if (services != null) {
// Setup the service cache in the ServiceManager
ServiceManager.initServiceCache(services);
}
cela s'appelle dans ActivityManagerService dans la méthode:
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
...
thread.bindApplication(... , getCommonServicesLocked(),...)
puis:
private HashMap<String, IBinder> getCommonServicesLocked() {
mais il n'y a pas "activité" que le paquet de fenêtre et l'alarme ..
nous avons donc besoin de revenir pour appeler:
return getIServiceManager().getService(name);
sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
cela fait appel à travers:
mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
qui conduit à :
BinderInternal.getContextObject()
et c'est la méthode native ....
/**
* Return the global "context object" of the system. This is usually
* an implementation of IServiceManager, which you can use to find
* other services.
*/
public static final native IBinder getContextObject();
je n'ai pas le temps maintenant de creuser dans c, jusqu'à ce que je dissèque le repos, je suspends ma réponse.
mais le meilleur moyen de vérifier si le service est en cours d'exécution consiste à créer une liaison (si la liaison n'est pas créée, le service n'existe pas) - et interroger le service sur son état via la liaison (en utilisant un indicateur interne stocké sur cet état).
j'ai trouvé ces intéressants:
/**
* Provide a binder to an already-bound service. This method is synchronous
* and will not start the target service if it is not present, so it is safe
* to call from {@link #onReceive}.
*
* For peekService() to return a non null {@link Android.os.IBinder} interface
* the service must have published it before. In other words some component
* must have called {@link Android.content.Context#bindService(Intent, ServiceConnection, int)} on it.
*
* @param myContext The Context that had been passed to {@link #onReceive(Context, Intent)}
* @param service Identifies the already-bound service you wish to use. See
* {@link Android.content.Context#bindService(Intent, ServiceConnection, int)}
* for more information.
*/
public IBinder peekService(Context myContext, Intent service) {
IActivityManager am = ActivityManager.getService();
IBinder binder = null;
try {
service.prepareToLeaveProcess(myContext);
binder = am.peekService(service, service.resolveTypeIfNeeded(
myContext.getContentResolver()), myContext.getOpPackageName());
} catch (RemoteException e) {
}
return binder;
}
en bref :)
"Fournit un classeur à un service déjà lié. Cette méthode est synchrone et ne démarrera pas le service cible s'il n'est pas présent."
public IBinder peekService (Service Intent, String resolType, String callingPackage) lève RemoteException;
*
public static IBinder peekService(IBinder remote, Intent service, String resolvedType)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("Android.app.IActivityManager");
service.writeToParcel(data, 0);
data.writeString(resolvedType);
remote.transact(Android.os.IBinder.FIRST_CALL_TRANSACTION+84, data, reply, 0);
reply.readException();
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
return binder;
}
*
Pour kotlin, vous pouvez utiliser le code ci-dessous.
fun isMyServiceRunning(calssObj: Class<SERVICE_CALL_NAME>): Boolean {
val manager = requireActivity().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
if (calssObj.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
Cela s'applique davantage au débogage Intent Service car ils génèrent un thread, mais peuvent également fonctionner pour des services réguliers. J'ai trouvé ce fil grâce à Binging
Dans mon cas, j'ai joué avec le débogueur et trouvé la vue de fil. Cela ressemble en quelque sorte à l'icône représentant un point de balle dans MS Word. Quoi qu'il en soit, vous n'avez pas besoin d'être en mode débogueur pour l'utiliser. Cliquez sur le processus et cliquez sur ce bouton. Tous les services d'intention apparaîtront pendant leur exécution, au moins sur l'émulateur.