Service之三种服务方式

时间:2020-12-22 07:23:51

(一)StartService

  运行Service的方法之一。任何继承于android.content.Context的Android组件(component)都可以使用一个Intent(android.content.Intent)来开启一个Service。Intent里面可以以类对象(Class<?>)或者action进行构造,使用action构造对于初学者来说不太直观,而且action构造的适用范围比类对象构造使用范围更广,所以action构造的方法将在本系列文章后面的内容进行介绍,现在就都使用类对象进行构造。

  如果Service并没有运行,则新建一个Service(这里涉及到Service指派Task的问题将在以后的内容中介绍),并运行之,相继Service的回调onCreate(),onStart()/onStartCommand()(onStartCommand()是在Android2.0及之后SDK加入推荐使用的,用来代替老版的onStart()方法)被调用;如果Service已经在运行了,则只有回调onStart()/onStartCommand()被调用。

  参看Android官方文档可以发现,onStart()和onStartCommand()均会传入一个Intent对象参数,这个Intent对象就是使用startService()时传入的同一个Intent对象(同一个表示使用“==”比较返回true)。因为Intent对象可以携带Extra数据,所以启用Service的组件可以随意的向Service传递Extra数据(使用putXXExtra()/putExtra()等方法,最常见的就是使用Java基本类型、String对象或者封装在Bundle对象中作为Extra数据,关于Extra数据的类型限制请自行参见文档)。当组件要传数据到Service时,只需要调用startService()并传入相应的携带了Extra数据的Intent对象,并在Service的onStart()/onStartCommand()中接收取出Extra数据,而不用理会Service是否已经运行,数据始终能够到达Service并进行处理。这样,就可以完成单向的IPC(进程间通信)功能(组件->Service)。

  当不想再让Service运行的时候,只需要(任一)组件调用stopService()并传入相应的Intent对象,如果Service正在运行,则会停止,如果Service没有运行,则系统会自动当什么事也没发生。如果Service自己不想再运行,可以在Service里使用stopSelf()自杀,可以看出,如果可以运行到自杀代码的话,那么Service肯定在运行。

  onStart()/onStartCommand()都会传入参数startId:int,用来标识Service的启用号。每次startService()系统会自动为开启的Service产生一个不同的startId,之前赋予它的startId(如果有)将会被覆盖,并且这个新产生的startId会成为这个Service的新的startId,无论Service是否正在运行。

  考虑如下情况,当多个组件启用了同一个Service,Service提供互斥的服务(使用synchronized关键字),且要保证在Service把所有工作完成之前不能自杀,这个时候,startId就相当有用了,在Service onStart()/onStartCommand()时把startId保存起来,因为互斥的使用服务,则Service是按顺序提供服务的,则Service自杀的时候只用检查当前Service的startId与保存的这个startId是否相同,不同则说明Service之后还有任务,不能自杀,相同则说明正在运行的任务已经是最后一个任务了,运行完后就可以自杀(使用stopSelf(int startId)方法)

启动服务:

Intent intent = new Intent(getApplicationContext(), MyService.class);

    startService(intent);

Service中代码:

    @Override

  public void onCreate() {

  }

@Override

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

  //接受传递过来的intent的数据等

  return START_STICKY;

  }

@Override

public void onDestroy() {

  }

(二)bindService

  绑定(bind)Service是开启Service的另一种方法,而且绑定Service几乎可以被认为是专门为IPC(进程间交互)准备的。绑定Serivce是通过Context.bindService()方法实现的,bindService和startService有一定的区别,首先就反应在生命周期上。bindService不会回调onStart()/onStartCommand()方法,而会回调onBind()方法;Service要停止绑定则需要要调用unbindService()方法,而不用stopService()或者stopSelf()方法。

  

/**
 * bindService
 *
 * @param service
 *            用显示的组件名(Class<?>方式)或者逻辑描述(action等)的Service的Intent
 * @param conn
 *            在Service开启或停止时接收信息的组件
 * @param flags
 *            绑定选项,可以是0,BIND_AUTO_CREATE,BIND_DEBUG_UNBIND,BIND_NOT_FOREGROUND,BIND_ABOVE_CLIENT,BIND_ALLOW_OOM_MANAGEMENT或者BIND_WAIVE_PRIORITY  
 * @return 绑定成功为true,否则为false
 */
    public abstract boolean bindService(Intent service, ServiceConnection conn,int flags);

  ServiceConnection可以监听服务的状态,在进行服务绑定的时,其标志位可以为以下几种(这里列出3种):

1).Context.BIND_AUTO_CREATE

  说明:表示收到绑定请求的时候,如果服务尚未创建,则即刻创建,在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧毁

2).Context.BIND_DEBUG_UNBIND

  说明:通常用于调试场景中判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用

3).Context.BIND_NOT_FOREGROUND

  说明:表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行,该标志位位于Froyo中引入。

Service的绑定方法bindService()中除了用了Service的Intent外,还使用到了ServiceConnection对象,这个对象除了可以为Service绑定者(caller)回调方法,还是解绑定(unbind)时需要提供的参数。bindService()方法中最后一个参数flag则是表明绑定Service时的一些设置,一般情况下可以直接使用0,有关这个问题将在本系列文章以后的内容中介绍。  android.content.ServiceConnection是一个接口,实现(implementate)这个接口有2个方法需要重写(Override)。一个是当Service成功绑定后会被回调的onServiceConnected()方法,另一个是当Service解绑定或者Service被关闭时被回调的onServiceDisconnected()。前者(onServiceConnected()方法)会传入一个IBinder对象参数,这个IBinder对象就是在Service的生命周期回调方法的onBind()方法中的返回值,它对Service的绑定式IPC起到非常重要的作用。  

以下代码是bindService()及unbindService()的惯常用法,代码如下:

boolean mIsBound = false;

/** 绑定服务 */

public void doBindService() {

bindService(new Intent(MainActivity.this, LocalService.class), mConnection,Context.BIND_AUTO_CREATE);

mIsBound = true;

    }

/** 解除绑定服务 */

public void doUnbindService() {

  if (mIsBound) {

  // Detach our existing connection.

unbindService(mConnection);

mIsBound = false;

     }

    }

private ServiceConnection mConnection = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

mBoundService = ((LocalService.LocalBinder) service).getService();

Toast.makeText(MainActivity.this, "服务连接", Toast.LENGTH_SHORT)

.show();

    }

@Override

public void onServiceDisconnected(ComponentName name) {

mBoundService = null;

Toast.makeText(MainActivity.this, "服务未连接", Toast.LENGTH_SHORT)

.show();

    }

  };

服务类中:

@Override

public void onCreate() {

  }

/** 绑定的IBinder */

private final IBinder mBinder = new LocalBinder();

 public class LocalBinder extends Binder {

  public LocalService getService() {

  return LocalService.this;

   }

  }

  @Override

 public IBinder onBind(Intent intent) {

  return mBinder;

  }

  @Override

 public boolean onUnbind(Intent intent) {

  // TODO Auto-generated method stub

  return super.onUnbind(intent);

  }

  需要注意的是,onServiceDisconnected()方法在Service被显示的(explicitly)unbind或者被停止时都会被回调。比如,当Android系统资源(主要是RAM)严重不足时,Service是很有可能被结束(kill)掉的,如果被kill掉,则onServiceDisconnected()方法会被回调,但这个时候Service是没有走完所有的生命周期的(比如不会回调onDestroy()方法)。当然,无论Service的开启是使用bind还是start,一旦当系统资源恢复之后,这些被kill掉的Service会以可能的最短的时间内被系统自动恢复(重新进行新的生命周期,从回调onCreate()方法开始)。

(三)ForegroundService

  Foreground Service(意译为前台服务)并不完全像其意译的意思那样是工作在前台的Service,因为Service实际上始终是工作在后台的。由于Service工作在后台的原因,使用者并不知道它在运行,有时候开发者需要使用者知道某个Service在运行时,就需要设计一种方案来解决这个问题,Foreground Service就出现了。Foreground Service说简单点其实就是在Service开启的时候使用通知(Notification),这也是Android官方推荐的方式,或者一些其它的方式(甚至可以是Activity,但使用Activity是基本没有必要的)来告知用户这个Service正在运行。

  在程序开启了Service,则使用一个“正在运行”的通知表明服务正在运行就可以了,也就是在Service的onCreate()回调或者onStart()/onStartCommand()回调中即可。虽然通知并不是一定需要的,或者说故意不提示用户有服务正在运行(稍稍流氓一点的程序就会这样),但是某些应用商场的应用审核就把通知提示做为了审核项目的。为了在Service周期(Life Cycle)结束的时候通知也能自动消失,所以需要在Service的onDestroy()回调里面写上取消通知的代码。以上就是配合通知自己实现的Foreground Service了。

  当然,除了自己处理通知的方法外,Google在Android 2.0(SDK level 5)以上的SDK提供了一个直接而简单的方法。直接使用Service.startForeground()和Service.stopForeground()进行处理(注意,这两个方法是Service类的)。下面看下Google提供的两个接口:

/**

 * 让service成为Foreground Service,并且产生一个“正在运行”

 * 的通知。默认情况下,service是后台的,这意味着service在系统

 * 回收内存(比如在浏览器里显示大图片)的时候可以被毫无顾忌的

 * kill掉。如果你比较在意这个service的挂掉,比如像后台音乐播放

 * 器这种突然挂了会影响用户的情况,就可以使用Foreground

 * Service来提示用户。

 *

 * 参数

 * id   The identifier for this notification as per

 * NotificationManager.notify(int, Notification).

 * notification The Notification to be displayed.

 */

publicfinalvoid startForeground (int id, Notification notification)

/**
 * 去掉service的foreground属性,允许在低内存时被kill掉
 *
 * Parameters
 * removeNotification  If true, the notification previously provided to startForeground(int,Notification)
 * will be removed. Otherwise it will remain until a later call removes
 * it (or the service is destroyed).
 */
publicfinalvoid stopForeground (boolean removeNotification)

  使用startForeground()之后,给出的Notification对象会发布,使用stopForeground()之后,通知会被撤销,当Service销毁(比如stopService()被调用)之后,通知也会被撤销。stopForeground()仅仅只是去掉service的foreground属性,并不会让service停止。

android官方描述如下:

Running a Service in the Foreground


A foreground service is a service that's considered to be something the
user is actively aware of and thus not a candidate for the system to
kill when low on memory. A foreground service must provide a
notification for the status bar, which is placed under
the "Ongoing" heading, which means that the notification cannot be
dismissed unless the service is either stopped or removed from the
foreground.

For example, a music player that plays music from a service should be
set to run in the foreground, because the user is explicitly aware of
its operation. The notification in the status bar might indicate the
current song and allow the user to launch an activity
to interact with the music player.

To request that your service run in the foreground, call startForeground().
This method takes two parameters: an integer that uniquely identifies the notification and the Notification for
the status bar. For example:

Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
System.currentTimeMillis());

Intent notificationIntent = new Intent(this, ExampleActivity.class);

PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

notification.setLatestEventInfo(this, getText(R.string.notification_title),getText(R.string.notification_message), pendingIntent);

startForeground(ONGOING_NOTIFICATION, notification);

To remove the service from the foreground, call stopForeground(). This method takes a boolean, indicating whether to remove the status bar notification as well. This method does not stop the service. However, if you stop the service while it's still running in the foreground, then the notification is also removed.

Note: The methods startForeground() and stopForeground() were introduced in Android 2.0 (API Level 5). In order to run your service in the foreground on older versions of the platform, you must use the previoussetForeground() method—see the startForeground() documentation for information about how to provide backward compatibility.

For more information about notifications, see Creating Status Bar Notifications.

  我们只需要在onStartCommand里面调用 startForeground方法让服务前台运行,然后再onDestroy里面调用stopForeground解除前台运行既可!

引申阅读:

1.检查Android后台服务是否正在运行?

通过系统级提供的方法ActivityManager.getRunningServices获取到当前正在运行的服务

private boolean isServiceRunning() {

ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);

  for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {

    if ("com.example.MyService".equals(service.service.getClassName())) {

        return true;

     }

    }

    return false;

  }

2.Service和Thread的区别?

  我们拿服务来进行一个后台长时间的动作,为了不阻塞线程,然而,Thread就可以达到这个效果,为什么我们不直接使用Thread去代替服务呢?

1). Thread:Thread 是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作。 
2). Service:Service
是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main
线程上的
。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是Remote
Service,那么对应的 Service 则是运行在独立进程的 main 线程上。因此请不要把 Service
理解成线程,它跟线程半毛钱的关系都没有!

  既然这样,那么我们为什么要用
Service 呢?其实这跟 android 的系统机制有关,我们先拿 Thread 来说。Thread 的运行是独立于 Activity
的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止
Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity
被 finish 之后,你不再持有该 Thread 的引用
。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制

  举个例子:如果你的
Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity
没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的
Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该
Thread,这样便解决了该问题(因为任何
Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例)。

  因此你可以把 Service 想象成一种消息服务,而你可以在任何有 Context
的地方调用
Context.startService、Context.stopService、Context.bindService,Context.unbindService,来控制它,你也可以在
Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread
做不到的。

最后感谢分享,转载于:https://www.juwends.com/tech/android/android-service-1.html(1-3)