提问



如何检查后台服务(在Android上)是否正在运行?


我想要一个能够切换服务状态的AndroidActivity - 它可以让我打开它,如果它打开则关闭。

最佳参考


不久前我遇到了同样的问题。由于我的服务是本地的,我最终只是在服务类中使用静态字段来切换状态,正如hackbod这里[75]所描述的那样。


编辑(记录):


这是hackbod提出的解决方案:



  如果您的客户端和服务器代码是同一个.apk的一部分,那么您就是
  使用具体的Intent绑定到服务(一个指定
  确切的服务类),然后你可以简单地设置你的服务
  客户端可以检查运行的全局变量。

  
  我们故意没有API来检查服务是否是
  因为,几乎没有失败,当你想要做某事时
  就像你最终在代码中遇到竞争条件一样。


其它参考1


我在Activity中使用以下内容:


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提供的有关运行服务的信息。[76]


使用onDestroy或onSometing事件或Binders或静态变量的所有方法都无法可靠地工作,因为作为开发人员,您永远不知道,当Android决定终止您的进程或者调用哪些回调时。请注意Android文档中生命周期事件表中的killable列。[77]

其它参考2


得到它了!


必须调用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;
     }
}

其它参考3


一个小补充是:


我的目标是知道一个服务正在运行,如果它没有运行,没有实际运行它。


调用bindService或调用可以被服务捕获的intent并不是一个好主意,因为如果它没有运行它将启动服务。


因此,正如miracle2k建议的那样,最好的方法是在服务类中使用静态字段来了解服务是否已经启动。


为了使它更清晰,我建议使用一个非常非常懒的读取来转换单个服务中的服务:也就是说,通过静态方法在所有单例实例中都没有实例化。 service/singleton的静态getInstance方法只返回单例的实例(如果已创建)。但它并没有实际启动或实现单身自身。服务只能通过正常的服务启动方法启动。[78]


然后,修改单例设计模式以将混淆的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


这个解决方案很优雅,但只有在您可以访问服务类时才有意义,并且只有服务的应用程序/包中的类才有用。如果您的类在服务app/package之外,那么您可以查询ActivityManager,其中包含Pieter-Jan Van Robays强调的限制。

其它参考4


你可以使用它(我还没试过,但我希望这有效):


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。


请参阅公共抽象ComponentName startService(意图服务)。[79]


这不像我想的那样,因为它启动了服务,所以你可以在代码下添加stopService(someIntent);

其它参考5


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

其它参考6


我稍微修改了上面提到的解决方案之一,但是传递了类而不是通用字符串名称,以确保比较来自同一方法的字符串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);

其它参考7


我只想在@Snicolas的答案中添加一个注释。以下步骤可用于在有/不调用onDestroy()的情况下检查停止服务。



  1. onDestroy()调用:转到设置 - >应用程序 - >运行服务 - >选择并停止服务。

  2. onDestroy()未调用:转到设置 - >应用程序 - >管理应用程序 - >选择并强制停止运行服务的应用程序。但是,由于您的应用程序在此处停止,因此服务实例也将被停止。



最后,我想提一下,在单例类中使用静态变量提到的方法对我有用。

其它参考8


onDestroy并不总是在服务中调用,所以这没用!


例如:只需从Eclipse进行一次更改即可再次运行应用程序。使用SIG:9强制退出应用程序。

其它参考9


这是 Android 文档的摘录



  有序广播(与 Context.sendOrderedBroadcast 一起发送)是
  一次送到一个接收器。当每个接收器执行时
  转,它可以将结果传播到下一个接收器,或者它可以
  完全中止广播,以免它被传递给其他人
  接收器。



认为这个hack是pingService ,因为我们可以同步广播,我们可以在UI线程上同步广播并获得结果。


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

其它参考10


首先,你试图通过使用ActivityManager来尝试达到服务。(在这里讨论)[80]


服务可以独立运行,绑定到Activity或两者。如果您的服务正在运行,则检入Activity的方法是创建一个接口(扩展Binder),您可以在其中声明Activity和Service都能理解的方法。您可以通过在声明isServiceRunning()的位置创建自己的接口来完成此操作。
然后,您可以将Activity绑定到您的Service,运行方法isServiceRunning(),如果它正在运行,Service将检查自身并为您的Activity返回一个布尔值。


您还可以使用此方法停止服务或以其他方式与其进行交互。


我使用本教程学习如何在我的应用程序中实现此场景。[81]

其它参考11


检查服务是否正在运行的正确方法是简单地询问它。在您的服务中实现BroadcastReceiver,以响应您的Activity中的ping。在服务启动时注册BroadcastReceiver,并在销毁服务时注销它。从您的Activity(或任何组件),向服务发送本地广播意图,如果它响应,您知道它正在运行。请注意下面代码中ACTION_PING和ACTION_PONG之间的细微差别。[82]


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

其它参考12


对于这里给出的用例,我们可以简单地使用stopService()方法的返回值。如果存在指定的服务则返回true并且它被杀死。否则它返回false如果结果是false,你可以重新启动服务,否则确保当前服务已经停止。:)如果你看一下这会更好。[83]

其它参考13


同样,如果他们使用待定意图,人们可能会发现更清除的另一种选择(例如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是您在类中私下定义的常量,用于标识与您的服务关联的待定意图。

其它参考14


Xamarin C#verison。


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

其它参考15


下面是一个优雅的黑客,涵盖了所有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(...);
    }

其它参考16


可以有多个具有相同类名的服务。


我刚刚创建了两个应用程序。第一个应用程序的包名是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个实例,因为ComponentNameequals()比较包名和类名。并且不能在设备上安装两个具有相同软件包名称的应用程序。


ComponentName的equals()方法。


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


组件名[84]

其它参考17


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

其它参考18



  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)

其它参考19


这更适用于Intent Service调试,因为它们会产生一个线程,但也可能适用于常规服务。感谢Binging,我找到了这个帖子


就我而言,我玩了调试器并找到了线程视图。它有点像MS Word中的项目符号点图标。无论如何,你不必在调试器模式下使用它。单击该过程并单击该按钮。任何Intent Services将在它们运行时显示,至少在模拟器上。

其它参考20


如果服务属于另一个进程或APK,则使用基于ActivityManager的解决方案。


如果您可以访问其源,只需使用基于静态字段的解决方案。但是使用布尔值我建议使用Date对象。当服务正在运行时,只需将其值更新为now,并在完成时将其设置为null。从Activity中,您可以检查它的null或日期是否太旧,这意味着它没有运行。


您还可以从您的服务发送广播通知,指示正在运行的进一步信息。

其它参考21


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


要么


如果不想使用静态然后用户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是我用于管理所有首选项的单例类。

其它参考22


简单使用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.


注意:此方法仅用于调试或实现服务管理类型用户界面。





PS。 android文档有误导性我在Google跟踪器上打开了一个问题以消除任何疑问:


https://issuetracker.google.com/issues/68908332[85]


正如我们所看到的,绑定服务实际上是通过ActivityManager绑定器通过服务缓存绑定器调用事务 - 我跟踪哪个服务负责绑定,但正如我们可以看到绑定的结果是:


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() {


但没有Activity只有窗口包和报警..


所以我们需要回电话:


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


我现在没有时间去挖掘,所以直到我解剖休息电话我暂停我的回答。


但检查服务是否正在运行的最佳方法是创建绑定(如果未创建绑定服务不存在) - 并通过绑定查询服务的状态(使用存储的内部标志对其进行状态) 。


更新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,
                String 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;
}



  *


其它参考23


放轻松的家伙...... :)


我认为最合适的解决方案是在SharedPreferences中保存一个关于服务是否正在运行的键值对。


逻辑很直接;在服务类的任何所需位置;放置一个布尔值,它将作为一个标志,告诉您服务是否正在运行。然后在您的应用程序中随时读取此值。


我在我的应用程序中使用的示例代码如下:


在我的Service类(音频流服务)中,我在服务启动时执行以下代码;


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


然后在我的应用程序的任何Activity中,我正在使用以下代码检查服务的状态;


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


没有特殊权限,没有循环...简单的方法,干净的解决方案:)


如果您需要更多信息,请参阅链接[86]


希望这可以帮助。