Как проверить, если служба работает на Android?
Как проверить, работает ли фоновая служба (на Android)?
Я хочу Android-активность, которая переключает состояние службы - это позволяет мне включить его, если он выключен и выключен, если он включен.
23 ответа:
У меня была такая же проблема не так давно. Поскольку моя служба была локальной, я просто использовал статическое поле в классе обслуживания для переключения состояния, как описано hackbod здесь
редактировать (для записи):
вот решение, предложенное hackbod:
Если ваш клиентский и серверный код является частью одного и того же .APK и вы не привязка к службе с конкретным намерением (тот, который определяет точное обслуживание класса), то вы можете просто иметь свой сервис установить глобальная переменная, когда она работает, что ваш клиент может проверить.
У нас намеренно нет 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 или вызов intent, который может быть пойман службой, не является хорошей идеей, так как он запустит службу, если она не запущена.
Итак, как предложил miracle2k, лучше всего иметь статическое поле в классе обслуживания, чтобы знать, запущена ли служба или нет.
сделать это еще чище, я предлагаю превратить сервис в синглтон с очень очень ленивой выборкой: то есть, нет экземпляра вообще синглтон экземпляр через статические методы. Статический метод getInstance вашего сервиса / синглтона просто возвращает экземпляр синглтона, если он был создан. Но на самом деле он не запускает и не создает экземпляр самого синглтона. Служба запускается только с помощью обычных методов запуска службы.
Она будет даже чище, чтобы изменить шаблон дизайна синглтона, чтобы переименовать запутанный метод 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.
посмотреть public abstract ComponentName 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. Следующие шаги могут быть использованы для проверки остановки службы с / без вызова
onDestroy()
.
onDestroy()
вызывается: перейдите в настройки - > приложение - > запущенные службы - > выберите и остановите службу.
onDestroy()
не вызывается: перейдите в настройки - > приложение - > Управление приложениями - > выберите и "принудительно остановите" приложение, в котором работает ваша служба. Однако, как ваше приложение остановлено здесь, поэтому, безусловно, экземпляры службы также будут остановлены.наконец, я хотел бы упомянуть, что упомянутый там подход с использованием статической переменной в классе singleton работает для меня.
onDestroy
не всегда вызывается в службе, так что это бесполезно!например: просто запустите приложение снова с одним изменением от Eclipse. Приложение принудительно выходит с помощью SIG: 9.
это выдержка из Android docs
заказанные трансляции (отправлено с контексте.sendOrderedBroadcast) являются поставленный к одному приемнику одновременно. Как каждый приемник выполняет в поверните, он может распространить результат к следующему приемнику, или он может полностью прервать трансляцию, чтобы она не передавалась другим электроприемники.
подумайте об этом рубить как "пинг"
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), где вы объявляете методы, которые понимают как действие, так и Служба. Вы можете сделать это, сделав свой собственный интерфейс, где вы объявляете, например, "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()
на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)
Это больше относится к отладке намеренных служб, поскольку они порождают поток, но могут работать и для обычных служб. Я нашел эту нить благодаря Binging
в моем случае я поиграл с отладчиком и нашел представление потока. Это похоже на значок маркера в MS Word. В любом случае, вы не должны быть в режиме отладки, чтобы использовать его. Нажмите на процесс и нажмите на эту кнопку. Любые намеренные службы будут отображаться во время их работы, по крайней мере, на эмулятор.
Если сервис принадлежит другому процессу или 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)
из любого класса.
простое использование привязки с не создавать авто- см. ПС. и обновление...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); }
никаких специальных разрешений, никаких петель... Простой способ, чистое решение:)
Если вам нужна дополнительная информация, пожалуйста, обратитесь к ссылке
надеюсь, что это помогает.