Как проверить, работает ли служба на Android?

Как проверить, работает ли фоновая служба (на Android)?

Я хочу Android-активность, которая переключает состояние службы - это позволяет мне включить его, если он выключен и выключен, если он включен.

23 ответов


У меня была такая же проблема не так давно. Поскольку моя служба была локальной, я просто использовал статическое поле в классе службы для переключения состояния, как описано hackbod здесь

EDIT (для записи):

вот решение, предложенное hackbod:

Если ваш код клиента и сервера является частью одного и того же .apk и вы привязка к службе с конкретным намерением (которое определяет точное обслуживание class), тогда вы можете просто установить свой сервис a глобальная переменная, когда она запущена, что ваш клиент может проверить.

У нас намеренно нет API, чтобы проверить, является ли служба бег, потому что, почти безошибочно, когда вы хотите что-то сделать как это вы в конечном итоге с условиями гонки в вашем коде.


Я использую следующее изнутри действия:

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

и я называю это использованием:

isMyServiceRunning(MyService.class)

это работает надежно, потому что он основан на информации о запущенных службах, предоставляемых операционной системой Android через ActivityManager#getRunningServices.

все подходы с использованием onDestroy или onSometing событий или связующих или статических переменных не будут работать надежно, потому что как разработчик вы никогда не знаете, когда Android решает убить ваш процесс или какой из упомянутых обратных вызовов вызывается или нет. Обратите внимание на столбец "killable" в таблица событий жизненного цикла в документации Android.


понял!

вы должны вызов startService() для вашей службы, чтобы быть должным образом зарегистрированы и передачи BIND_AUTO_CREATE не хватит.

Intent bindIntent = new Intent(this,ServiceTask.class);
startService(bindIntent);
bindService(bindIntent,mConnection,0);

и теперь класс 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;
     }
}

небольшое дополнение:

моя цель-знать, работает ли служба без фактического запуска, если она не работает.

вызов bindService или вызов намерения, которое может быть поймано службой, не является хорошей идеей, поскольку он запустит службу, если она не запущена.

Итак, как предложил miracle2k, лучше всего иметь статическое поле в классе обслуживания, чтобы знать, запущена ли служба или нет.

сделать это еще чище, я предлагаю преобразовать сервис в синглтон с очень очень ленивой выборкой: то есть нет никакого экземпляра вообще синглтон экземпляр через статические методы. Статический метод getInstance вашего сервиса / singleton просто возвращает экземпляр singleton, если он был создан. Но на самом деле он не запускает и не создает экземпляр самого синглтона. Служба запускается только с помощью обычных методов запуска службы.

Она будет еще чище, чтобы изменить шаблон одноэлементного дизайна, чтобы переименовать запутанный метод getInstance во что-то вроде isInstanceCreated() : boolean метод.

код будет выглядеть как:

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

это решение является элегантным, но оно актуально только в том случае, если у вас есть доступ к классу сервиса и только для классов iside приложение/пакет сервиса. Если ваши классы находятся вне приложения/пакета службы, вы можете запросить ActivityManager с ограничениями, подчеркнутыми Питером-Яном Ваном Робейс.


вы можете использовать это (я еще не пробовал, но надеюсь, что это сработает):

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

метод startService возвращает объект ComponentName, если уже запущена служба. Если нет, null будет возвращен.

посмотреть общедоступное абстрактное имя компонента startService (Intent service).

это не похоже на проверку, я думаю, потому что он запускает службу, поэтому вы можете добавить stopService(someIntent); под кодом.


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

Я немного изменил одно из решений, представленных выше, но передал класс вместо общего имени строки, чтобы быть уверенным в сравнении строк, выходящих из того же метода 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;
    }
}

а то

Boolean isServiceRunning = ServiceTools.isServiceRunning(
                    MainActivity.this.getApplicationContext(),
                    BackgroundIntentService.class);

Я просто хочу добавить примечание к ответу @Snicolas. Следующие шаги можно использовать для проверки службы stop с / без вызова onDestroy().

  1. onDestroy() вызывается: перейдите в настройки - > приложение - > запущенные службы - > выберите и остановите службу.

  2. onDestroy() не вызывается: перейдите в настройки - > приложение - > Управление приложениями - > выберите и" принудительно остановите " приложение, в котором работает ваша служба. Однако, поскольку ваше приложение остановлено здесь, поэтому определенно экземпляры службы также будут остановлены.

наконец, я хотел бы упомянуть, что подход, упомянутый там, используя статическую переменную в одноэлементном классе, работает для меня.


onDestroy не всегда вызывается в службе, так что это бесполезно!

например: просто запустите приложение снова с одним изменением из Eclipse. Приложение принудительно выходит с помощью SIG: 9.


это выдержка из Android docs

заказанные трансляции (отправлено с контексте.sendOrderedBroadcast) являются доставляется одному получателю за один раз. Как каждый приемник выполняет в поверните, он может распространить результат к следующему приемнику, или он может полностью прервать трансляцию, чтобы она не передавалась другим электроприемники.

подумайте об этом взломе как "pinging"Service так как мы можем трансляция синхронно мы можем транслировать и получать результат синхронно в потоке пользовательского интерфейса.

Service

BroadcastReceiver .

@Override
public void onCreate() {
   LocalBroadcastManager
     .getInstance(this)
     .registerReceiver(new ServiceEchoReceiver(), IntentFilter("echo");
}

private class ServiceEchoReceiver{
    public void onReceive (Context context, Intent intent) {
      LocalBroadcastManager
         .getInstance(this)
         .sendBroadcastSync(new Intent("echo"));
    }
}

Activity

    bool serviceRunning = false;

    protected void onCreate (Bundle savedInstanceState){
        LocalBroadcastManager.getInstance(this).registerReceiver(echo);
        LocalBroadcastManager.getInstance(this).sendBroadcastSync(new Intent("echo"));
        if(!serviceRunning){
           //try and run the service
        }
    }

    private BroadcastReceiver echo = new BroadcastReceiver(){
        public void onReceive (Context context, Intent intent) {
          serviceRunning = true;   
        }
    }

прежде всего, вы не должны пытаться связаться с сервисом с помощью ActivityManager. (Обсуждается здесь)

службы могут работать самостоятельно, быть привязаны к деятельности или и то и другое. Способ проверки действия, если ваша служба запущена или нет, заключается в создании интерфейса (который расширяет Binder), где вы объявляете методы, которые оба, Activity и Service, понимают. Вы можете сделать это, сделав свой собственный интерфейс, где вы объявляете, например, "isServiceRunning()". Вы затем можно привязать вашу активность к вашей службе, запустить метод isServiceRunning (), служба сама проверит, запущена она или нет, и вернет логическое значение вашей активности.

вы также можете использовать этот метод, чтобы остановить службу или взаимодействовать с ней другим способом.

Я использовал этот учебник чтобы узнать, как реализовать этот сценарий в моем приложении.


правильный способ проверить, запущена ли служба, - просто спросить ее. Реализуйте BroadcastReceiver в своем сервисе, который реагирует на пинги от ваших действий. Зарегистрируйте BroadcastReceiver при запуске службы и отмените регистрацию при ее уничтожении. Из вашей активности (или любого компонента) отправьте местный эфир намерение службы, и если он отвечает, Вы знаете, что он работает. Обратите внимание на тонкую разницу между ACTION_PING и ACTION_PONG в коде под.

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

версия 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;
}

для случаев, приведенных здесь, мы можем просто использовать stopService() возвращаемое значение метода. Он возвращается true если существует указанный сервис и он убит. Иначе он вернется false. Таким образом, вы можете перезапустить службу, если результат false иначе он уверен, что текущая служба была остановлена. :) Было бы лучше, если бы вы взглянули на этой.


опять же, еще одна альтернатива, которую люди могут найти чище, если они используют ожидающие намерения (например, с 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);
}

здесь CODE - константа, которую вы определяете в своем классе для идентификации ожидающих намерений, связанных с вашей службой.


ниже элегантный хак, который охватывает все Ifs. Это только для местных служб.

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

и потом:

    if(AService.isServiceCreated()){
        ...
    }else{
        startService(...);
    }

может быть несколько служб с одинаковым именем класса.

Я создал два приложения. Имя пакета первого приложения -com.example.mock. Я создал подпакет под названием lorem в приложении и сервис под названием Mock2Service. Таким образом, его полное имя com.example.mock.lorem.Mock2Service.

затем я создал второе приложение и сервис под названием Mock2Service. Имя пакета приложения com.example.mock.lorem. Полное название услуги -com.example.mock.lorem.Mock2Service тоже.

здесь мой вывод 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

лучше сравнить ComponentName экземпляров, так как equals() of ComponentName сравнивает имена пакетов и классов. И не может быть двух приложений с одинаковым именем пакета, установленных на устройстве.

метод equals ()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;
}

имя компонента


ответ geekQ, но в классе Котлина. Спасибо 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
}

вызов

isMyServiceRunning(NewService::class.java)

Это больше относится к отладке службы Intent, так как они порождают поток, но могут работать и для обычных служб. Я нашел эту нить благодаря Binging

в моем случае я поиграл с отладчиком и нашел представление потока. Это похоже на значок точки пули в MS Word. В любом случае, вам не нужно быть в режиме отладчика, чтобы использовать его. Нажмите на процесс и нажмите на эту кнопку. Любые службы Intent будут отображаться во время их работы, по крайней мере, на эмулятор.


Если служба принадлежит другому процессу или APK, используйте решение на основе ActivityManager.

Если у вас есть доступ к его источнику, просто используйте решение на основе статического поля. Но вместо использования логического я бы предложил использовать объект Date. Пока служба запущена, просто обновите ее значение до "сейчас", а когда она закончит, установите значение null. Из действия вы можете проверить, является ли его null или дата слишком старой, что будет означать, что он не работает.

вы можете также отправить широковещательное уведомление от вашей службы, указывающее, что работает вдоль дальнейшей информации, как прогресс.


внутри TheServiceClass определите:

 public static Boolean serviceRunning = false;

затем в onStartCommand(...)

 public int onStartCommand(Intent intent, int flags, int startId) {

    serviceRunning = true;
    ...
}

 @Override
public void onDestroy()
{
    serviceRunning = false;

} 

затем вызов if(TheServiceClass.serviceRunning == true) из любого класса.


простое использование bind with don't create auto - см. ПС. и обновление...

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

пример :

    Intent bindIntent = new Intent(context, Class<Service>);
    boolean bindResult = context.bindService(bindIntent, ServiceConnection, 0);

почему не используете? getRunningServices()

List<ActivityManager.RunningServiceInfo> getRunningServices (int maxNum)
Return a list of the services that are currently running.

Примечание: этот метод предназначен только для отладки или реализации пользовательских интерфейсов типа управления службами.


ps. документация android вводит в заблуждение, я открыл проблему в google трекер для устранения любых сомнений:

https://issuetracker.google.com/issues/68908332

как мы видим, служба bind фактически вызывает транзакцию через activitymanager binder через привязки кэша службы - я отслеживаю, какая служба отвечает за привязку, но, как мы видим, результат для bind:

int res = ActivityManagerNative.getDefault().bindService(...);
return res != 0;

транзакция осуществляется через binder:

ServiceManager.getService("activity");

следующий:

  public static IBinder getService(String name) {
    try {
        IBinder service = sCache.get(name);
        if (service != null) {
            return service;
        } else {
            return getIServiceManager().getService(name);

это установить в ActivityThread через:

 public final void bindApplication(...) {

        if (services != null) {
            // Setup the service cache in the ServiceManager
            ServiceManager.initServiceCache(services);
        }

это вызывается в ActivityManagerService в методе:

 private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid) {
    ...
    thread.bindApplication(... , getCommonServicesLocked(),...)

затем:

 private HashMap<String, IBinder> getCommonServicesLocked() {

но нет "активности" только пакет окна и сигнализация..

поэтому нам нужно вернуться, чтобы позвонить:

 return getIServiceManager().getService(name);

    sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());

это делает вызов через:

    mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);

что приводит к :

BinderInternal.getContextObject()

и это родной способ....

  /**
     * 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();

у меня нет времени теперь закопался в c, поэтому, пока я не рассеку rest call, я приостанавливаю свой ответ.

но лучший способ проверить, работает ли служба создать привязку (если привязка не создана, служба не существует) - и запросить службу о ее состоянии через привязку (используя сохраненный внутренний флаг в ее состоянии).

обновление 23.06.2018

я нашел эти интересные:

/**
 * 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;
}

короче :)

" снабдите связыватель уже-связанный услуга. Этот метод является синхронным и не запускает целевую службу, если она отсутствует."

public IBinder peekService(Intent service, String resolvedType, Строка callingPackage) вызывает 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;
}

*


спокойно, ребята... :)

Я думаю, что наиболее подходящим решением является проведение пары ключ-значение в SharedPreferences о том, запущена служба или нет.

логика очень прямая; в любой желаемой позиции в вашем классе обслуживания; поместите логическое значение, которое будет действовать как флаг для вас о том, работает служба или нет. Затем прочитайте это значение там, где вы хотите в своем приложении.

пример кода, который я использую в мое приложение ниже:

в моем классе обслуживания (сервис для аудиопотока) я выполняю следующий код, когда служба запущена;

private void updatePlayerStatus(boolean isRadioPlaying)
{
        SharedPreferences sharedPref = this.getSharedPreferences(getString(R.string.str_shared_file_name), Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPref.edit();
        editor.putBoolean(getString(R.string.str_shared_file_radio_status_key), isRadioPlaying);
        editor.commit();
}

затем в любой деятельности моего приложения я проверяю статус службы с помощью следующего кода;

private boolean isRadioRunning() {
        SharedPreferences sharedPref = this.getSharedPreferences(getString(R.string.str_shared_file_name), Context.MODE_PRIVATE);

        return sharedPref.getBoolean(getString(R.string.str_shared_file_radio_status_key), false);
}

нет специальных разрешений, нет петель... Простой способ, чистое решение:)

Если вам нужна дополнительная информация, пожалуйста, обратитесь к ссылке

надеюсь, что это помогает.