Как проверить, работает ли фоновая служба?
Я хочу, чтобы активность Android изменяла состояние службы - она позволяла мне включать его, если он выключен, и выключен, если он включен.
Как проверить, работает ли фоновая служба?
Я хочу, чтобы активность Android изменяла состояние службы - она позволяла мне включать его, если он выключен, и выключен, если он включен.
У меня была такая же проблема недавно. Поскольку моя служба была локальной, я просто использовал статическое поле в классе службы для переключения состояния, как описано hackbod здесь
EDIT (для записи):
Вот решение, предлагаемое 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 events или Binders или статические переменные, не будут работать надежно, потому что, как разработчик, которого вы никогда не знаете, когда 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, лучше всего иметь статическое поле в классе службы, чтобы узнать, запущена ли служба или нет.
Чтобы сделать его еще более чистым, я предлагаю преобразовать службу в одноэлементном режиме с очень очень ленивым выбором: т.е. не существует экземпляра singleton через статические методы. Статический метод getInstance вашего сервиса /singleton просто возвращает экземпляр 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
Это решение является элегантным, но оно имеет смысл только в том случае, если у вас есть доступ к классу услуг, и только для классов есть приложение/пакет службы. Если ваши классы находятся за пределами приложения/пакета службы, вы можете запросить ActivityManager с ограничениями, подчеркнутыми Pieter-Jan Van Robays.
Вы можете использовать это (я еще не пробовал, но надеюсь, что это работает):
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).
Это не похоже на проверку, я думаю, потому что она запускает сервис, поэтому вы можете добавить 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;
}
Выдержка из документов Android :
Как и sendBroadcast (Intent), но если есть приемники для Намерение эта функция заблокирует и немедленно отправит их перед возвращением.
Думайте об этом взломе как о "пингующем" Service
. поскольку мы можем транслировать синхронно, мы можем транслировать и получать результат синхронно в потоке пользовательского интерфейса.
Service
@Override
public void onCreate() {
LocalBroadcastManager
.getInstance(this)
.registerReceiver(new ServiceEchoReceiver(), new IntentFilter("ping"));
//do not forget to deregister the receiver when the service is destroyed to avoid
//any potential memory leaks
}
private class ServiceEchoReceiver extends BroadcastReceiver {
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;
}
}
Я немного изменил одно из приведенных выше решений, но передал класс вместо общего имени строки, чтобы, конечно же, сравнить строки, исходящие из того же метода 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()
not Called: выберите "Настройки" → "Приложение" → "Управление приложениями" → "Выбрать" и "принудительно остановить" приложение, в котором работает ваша служба. Однако, поскольку ваше приложение остановлено здесь, так что определенно экземпляры службы также будут остановлены.
Наконец, я хотел бы упомянуть, что подход, упомянутый здесь, используя статическую переменную в классе singleton, работает для меня.
onDestroy
не всегда вызывается в службе, поэтому это бесполезно!
Например: просто запустите приложение снова с одним изменением с Eclipse. Приложение принудительно завершено с использованием SIG: 9.
Правильный способ проверить, работает ли служба, - это просто спросить об этом. Внедрите 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 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;
}
}
};
}
Прежде всего, вы не пытаетесь обратиться к службе с помощью ActivityManager. (Обсуждаем здесь)
Сервисы могут запускаться самостоятельно, быть связанными с Activity или обоими. Способ проверки активности, если ваша Служба работает или нет, заключается в создании интерфейса (который расширяет Binder), где вы объявляете методы, которые понимают как Activity, так и Service. Вы можете сделать это, создав свой собственный интерфейс, где вы объявляете, например, "isServiceRunning()". Затем вы можете привязать свою активность к своей службе, запустить метод isServiceRunning(), Служба будет проверять себя, если она запущена или нет, и возвращает логическое значение вашей активности.
Вы также можете использовать этот метод для остановки своего Сервиса или взаимодействия с ним по-другому.
Я использовал этот учебник, чтобы узнать, как реализовать этот сценарий в моем приложении.
Xamarin С# версия:
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;
}
Ниже представлен элегантный хак, который охватывает все 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(...);
}
В приведенном здесь прецеденте мы можем просто использовать возвращаемое значение метода 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
- константа, которую вы определяете конфиденциально в своем классе для определения ожидающих действий, связанных с вашей службой.
Ответ geekQ, но в классе Kotlin. Благодаря 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)
В своем подклассе службы Используйте статическое логическое значение, чтобы получить состояние службы, как показано ниже.
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)
}
}
}
В kotlin вы можете добавить логическую переменную в объект-компаньон и проверить ее значение в любом классе, который вам нужен:
companion object{
var isRuning = false
}
Измените его значение при создании и уничтожении службы
override fun onCreate() {
super.onCreate()
isRuning = true
}
override fun onDestroy() {
super.onDestroy()
isRuning = false
}
Может быть несколько служб с тем же именем класса.
Я только что создал два приложения. Имя пакета для первого приложения 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;
}
Для kotlin вы можете использовать приведенный ниже код.
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;
}
Это относится скорее к отладке Intent Service, поскольку они порождают поток, но могут работать и для обычных сервисов. Я нашел эту тему благодаря Binging
В моем случае я играл с отладчиком и нашел вид потока. Это похоже на значок точки пули в MS Word. В любом случае вам не обязательно находиться в режиме отладчика, чтобы использовать его. Нажмите на процесс и нажмите на эту кнопку. Любые Intent Services будут отображаться во время работы, по крайней мере, на эмуляторе.
Если служба принадлежит другому процессу или APK использует решение на основе ActivityManager.
Если у вас есть доступ к его источнику, просто используйте решение на основе статического поля. Но вместо этого, используя логическое значение, я бы предложил использовать объект Date. Пока служба работает, просто обновите ее значение до "сейчас", и когда она закончит, установите значение null. Из действия вы можете проверить, является ли его значение null или дата слишком старым, что будет означать, что он не работает.
Вы также можете отправить широковещательное уведомление из своей службы, указав, что работает дальше, как прогресс.
Внутри TheServiceClass определите:
public static Boolean serviceRunning = false;
Затем In onStartCommand (...)
public int onStartCommand(Intent intent, int flags, int startId) {
serviceRunning = true;
...
}
@Override
public void onDestroy()
{
serviceRunning = false;
}
Затем вызовите if(TheServiceClass.serviceRunning == true)
из любого класса.
public static boolean isServiceRunning;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
isServiceRunning = true;
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
isServiceRunning = false;
}
ИЛИ
если вы не хотите использовать static, тогда пользователь SharedPreferences
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
PrefManager.getPref().saveBoolean(AppConstants.Pref.IS_SERVICE_RUNNING, true);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
PrefManager.getPref().saveBoolean(AppConstants.Pref.IS_SERVICE_RUNNING, false);
}
PrefManager - мой одиночный класс для управления всеми настройками.
простое использование bind с не создавайте автоматически - см. ps. и обновить...
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.
Примечание. Этот метод предназначен только для отладки или реализации пользовательских интерфейсов типа управления сервисом.
пс. Документация по android вводит в заблуждение, я открыл проблему на google tracker, чтобы устранить любые сомнения:
https://issuetracker.google.com/issues/68908332
так как мы видим, что служба привязки фактически вызывает транзакцию через связывание ActivityManager через связывание кэша службы - я dint track, какая служба отвечает за привязку, но поскольку мы можем видеть, что результат для bind:
int res = ActivityManagerNative.getDefault().bindService(...);
return res != 0;
транзакция производится через связующее:
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, так что, пока я не разобью вызов отдыха, я приостановил свой ответ.
но лучший способ проверить, работает ли служба, - создать привязку (если привязка не создана, сервис не существует) и запросить службу о ее состоянии через привязку (используя сохраненный внутренний флаг на нем).
я нашел интересные:
/**
* 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;
}
вкратце :)
Msgstr "Предоставить привязку к уже связанному сервису. Этот метод является синхронным и не запускает целевую услугу, если он отсутствует."
public IBinder peekService (Intent service, String resolvedType, String callPackage) выбрасывает 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;
}
*
Мой kotlin преобразование ответов на основе ActivityManager::getRunningServices
. Поместите эту функцию в activity-
private fun isMyServiceRunning(serviceClass: Class<out Service>) =
(getSystemService(ACTIVITY_SERVICE) as ActivityManager)
.getRunningServices(Int.MAX_VALUE)
?.map { it.service.className }
?.contains(serviceClass.name) ?: false
Пожалуйста, используйте этот код.
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;
}
Другой подход с использованием котлина. Вдохновленные другими пользователями ответы
fun isMyServiceRunning(serviceClass: Class<*>): Boolean {
val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
return manager.getRunningServices(Integer.MAX_VALUE)
.any { it.service.className == serviceClass.name }
}
это работает для меня
if(startService(new Intent(this, YourService.class)) != 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();
}