Android-Service组件

Service是一个android的四大组件之一,它没有UI界面,可以在后台执行长时间的操作。其他的组件可以start一个Service,service启动以后会在后台持续运行,不管用户是否切换到了其他的应用程序。此外,其他的组件还可以绑定到service上,并和service做交互,甚至还可以执行跨进程的操作(IPC)。比如说,Service可以在后台处理网络请求、播放音乐,执行文件i/o或者跟content provider交互。

有两种形式的service:
Started
当其他的组件通过调用startService()启动的service是“Started”。一旦启动以后,service就在后台无限的运行下去,就算是启动他的组件已经被销毁了。通常来说,Started service只会做单一的操作,并且不会返回给调用者返回值。比如:它可以通过网络下载或者是上传文件。当操作结束的时候,service要手动把自己停止掉。

Bound
当其他组件通过调用bindService()绑定到service的时候,service就是 "bound"的。bound service提供了客户端/服务端的接口,允许组件和service进行交互,发送请求,获取结果,甚至是做跨进程的操作。bound service只存活于其他组件绑定在它上面的时候,多个组件可以同时绑定到一个service,但是,当所有的组件同时解绑以后,service就被销毁了。

尽管本文档是分开讨论这两种类型的service的,但是你的service可以同时支持这两种类型,service可以是started(无限运行)同时还允许绑定。这仅仅取决于你是否实现了下面的回调:onStartCommand()允许其他的组件start一个service,onBind()允许绑定service。

不管你的service是started还是bound,应用的其他组件都可以使用这个service(甚至是别的应用)。就如同别的组件都可以使用一个Activity一样,通过Intent来启动它。但是,你也可以在manifest文件中把service声明成应用私有的,这就阻止了别的应用访问它。


更多信息请参考“在manifest文件中声明service”(http://developer.android.com/gui ... ices.html#Declaring)这一节。

注意:service是运行在宿主进程的主线程的,service并不会创建它自己的线程,并不会运行在单独的进程中,除非你指定了。这意味着,如果你的service要做一些耗CPU或者是阻塞的操作(比如:播放MP3或者是网络请求),你需要在service内部手动创建一个新的线程来做这样的操作。通过使用单独的线程,会降低ANR的风险,让主线程可以专注于UI操作。

service基础

要创建一个service,必须要创建Service(或者是Service子类)的子类。在你的实现中,你要重写一些回调方法,它们用来处理service的一些关键的生命周期,并且提供组件绑定到service的机制,如果合适的话。

要重写的最重要的回调是:

onStartCommand()

当别的组件通过调用startService()来启动service的时候,系统会调用这个方法。一旦这个方法开始执行,service就启动起来并在后台无限运行。如果你覆盖了这个方法,你还要负责在service的工作结束以后通过调用stopSelf()或者stopService()销毁掉service。(如果你只是想提供绑定就不需要实现这个方法)

onBind()
当其他的组件通过调用bindService()绑定到service(比如做RPC)的时候,系统会调用这个方法。实现这个方法的时候,必须要通过返回一个IBinder来给客户端提供一个用来和service进行交互的接口。一般都要实现这个方法,但是如果你不允许绑定的话,可以返回null。

onCreate()
当service首次启动的时候,系统会调用这个方法,只会执行一次(在调用onStartCommand()或者onBind()之前)。如果service已经在运行了,这个方法是不会被调用的。

onDestroy()
当service不再被使用或者是被销毁的时候系统会调用这个方法。你的service要实现这个方法来做一些清理资源的工作,比如:线程啊,监听啊,广播接收器啊等等。这是service收到的最后一个回调。

如果一个组件通过startService()启动一个service(会导致系统调用onStartCommand()),service会一直运行,一直到它调用stopSelf()或者是别的组件调用stopService()来把它停止掉。

如果一个组件通过bindService()启动一个service(onStartCommand()不会被调用),service只存活于绑定到它的组件上。一旦service跟所有的客户端解绑,它就会被系统结束掉。

Android系统只会在系统的内存不够用的时候才会强制杀掉service,系统必须能够给拥有用户焦点的activity提供系统资源。
如果service被绑定到了拥有用户焦点的activity上,它很可能不会被杀死。
如果service声明成了前台运行的,一般不会被杀死。
其他情况下,如果service是被started并且是长时间运行的,系统会随着时间的推移把它放到后台任务列表的靠后的位置,它就会变得易于被杀掉。
如果你的service是被started,你必须要让它能优雅的处理系统的restart。
如果系统杀掉了你的service,一旦资源可用,系统会立马restart被杀掉的service(当然这也取决于onStartCommand()的返回值)。
想了解更多关于系统可能会杀掉service的信息,参考:进程和线程(http://developer.android.com/gui ... es-and-threads.html)。

下面的章节,你会学到如何创建各种类型的service,还有如何在其他组件中使用service。

在manifest文件中声明service

跟Activity类似(还有其他组件),你必须要在manifest文件中声明所有的service。

要声明一个service,添加<service>元素作为<application>的子元素,比如:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

想了解更多关于在manifest声明service的信息,参考<service>元素大全(http://developer.android.com/gui ... ervice-element.html)。

<service>元素还有很多其他的属性,可以用来定义启动service的权限或者是service运行的进程。android:name属性是唯一必须的,它指定了service的类名。一旦你发布了应用以后,就不应该再修改这个名字了。假如你修改了,就会让用明确Intent来start或者是bind service的代码有crash的风险。

为了确保你的应用是安全的,当start或者是bind一个service的时候总是使用明确的intent,并且不要给service声明intent filter。如果允许不明确的方式来启动service非常重要,你可以给service提供intent filter,排除掉某些组件名字。但是,你必须要用setPackage()给intent设置pachage,这给要调用的目标service提供了足够的确定性。

此外,你也可以用过引入android:exported这个属性,并把它的值设置为false来确保你的service只是对你的应用可用。这就有效地防止了其他的应用启动你的service,就算是使用明确intent也没有用。

创建一个Started Service

如果别的组件通过调用startService()启动service的话,service就是一个started service,这会导致系统调用servicve的onStartCommand()方法。

当service启动以后,它的生命周期跟启动它的组件的生命周期是相互独立的,service可以在后台无限的运行,就算启动它的组件已经销毁了。这种情况下,当它的任务完成以后,service就需要调用stopSelf()来停掉自己,其他的组件可以调用stopService()来停掉service。

应用程序的组件比如Activity可以通过调用 startService()来启动一个service,并且给service传递一个Intent,Intent就指定了要启动的service和传递给service使用的数据。service会在onStartCommand()方法中收到这个Intent。

举个例子,假如一个Activity需要把数据保存到网络的数据库上,Activity可以启动一个service,通过intent传递给service要保存的数据。service在onStartCommand()方法中收到intent,连上网络,做数据库操作。当操作完成以后,service要停掉自己,然后servive就被销毁了。

注意:service是运行在应用的同一个进程中,默认是运行在应用的主线程的。因此,如果当用户跟应用进行交互的时候,service做一些耗时或者是阻塞的操作的话,service会拖慢Activity的性能。为了避免影响应用的性能,你要在service内部新开一个线程。

一般来说,可以通过继承两个类来创建started service:

一个是继承Service
Service是所有service的基类。当你继承这个类的时候,要在service内部新开一个线程做繁重的操作,因为service默认是运行在应用的主线程的,它会影响应用的性能。

还一个是继承IntentService
它是Service的子类,它内部使用了一个工作线程来处理所有的请求,一次一个。如果你的service不需要同步与处理多个请求的话,这将是最佳的选择。你所要做的仅仅是实现onHandleIntent()方法,它会接收请求的intent,因此你就可以做后台任务。

下面的章节讲述了如何用这两个类实现service。

继承IntentService类

因为大多数的started service都不需要处理并发的请求,因此使用IntentService可能是最优的方案。

IntentService会做如下的事情:

(1)创建一个默认的工作线程,它用来执行传递到onStartCommand()的intent,并且是跟应用的主线程独立的。
(2)创建一个工作队列。每次只传递一个intent到onHandleIntent()方法,因此就不需要担心多线程的问题了。
(3)当所有的请求都处理完以后,service会自动停止掉,因此不需要调用stopSelf()。
(4)提供了onBind()的默认实现,方法会返回null。
(5)提供了 onStartCommand()的默认实现,会把intent发送到工作队列,然后发送给onHandleIntent()。

所有这些以后,你所需要做的仅仅是实现onHandleIntent()来完成客户的工作(当然你也可以给service提供构造函数)。

下面是一个实现了IntentService的例子:

public class HelloIntentService extends IntentService {  /**   * A constructor is required, and must call the super IntentService(String)   * constructor with a name for the worker thread.   */  public HelloIntentService() {      super("HelloIntentService");  }  /**   * The IntentService calls this method from the default worker thread with   * the intent that started the service. When this method returns, IntentService   * stops the service, as appropriate.   */  @Override  protected void onHandleIntent(Intent intent) {      // Normally we would do some work here, like download a file.      // For our sample, we just sleep for 5 seconds.      long endTime = System.currentTimeMillis() + 5*1000;      while (System.currentTimeMillis() < endTime) {          synchronized (this) {              try {                  wait(endTime - System.currentTimeMillis());              } catch (Exception e) {              }          }      }  }}
这就是你要做的全部的事情:一个构造函数和实现onHandleIntent()。

如果你想覆盖别的回调比如onCreate(), onStartCommand(),或者onDestroy(),一定要记得调用父类的实现,这样IntentService才可以正确的处理工作线程的生命周期。

比如下面,onStartCommand()必须要返回默认的实现(跟intent被传递到onHandleIntent()一样)。

@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();    return super.onStartCommand(intent,flags,startId);}
除了onHandleIntent(),唯一个不需要调用父类的函数是onBind()(只有不允许绑定的时候才需要实现这个方法)。

下一节中,你会看到如何继承不同的基类来实现同样的功能,代码有点多,但是如果你要处理并发的请求的话可能会很有用。

继承Service类

正如在上节中看到的那样,使用IntentService让实现一个started service变得很简单。但是,如果你的service需要处理多线程(不是通过工作队列来处理请求),那么你可以继承Service类来处理intent。

作为一个对比,下面的例子是一个实现了Service类来完成上面使用IntentService同样的功能,也就是,给每一个请求,都使用工作线程来完成工作,一次只处理一个请求。

public class HelloService extends Service {  private Looper mServiceLooper;  private ServiceHandler mServiceHandler;  // Handler that receives messages from the thread  private final class ServiceHandler extends Handler {      public ServiceHandler(Looper looper) {          super(looper);      }      @Override      public void handleMessage(Message msg) {          // Normally we would do some work here, like download a file.          // For our sample, we just sleep for 5 seconds.          long endTime = System.currentTimeMillis() + 5*1000;          while (System.currentTimeMillis() < endTime) {              synchronized (this) {                  try {                      wait(endTime - System.currentTimeMillis());                  } catch (Exception e) {                  }              }          }          // Stop the service using the startId, so that we don‘t stop          // the service in the middle of handling another job          stopSelf(msg.arg1);      }  }  @Override  public void onCreate() {    // Start up the thread running the service.  Note that we create a    // separate thread because the service normally runs in the process‘s    // main thread, which we don‘t want to block.  We also make it    // background priority so CPU-intensive work will not disrupt our UI.    HandlerThread thread = new HandlerThread("ServiceStartArguments",            Process.THREAD_PRIORITY_BACKGROUND);    thread.start();    // Get the HandlerThread‘s Looper and use it for our Handler    mServiceLooper = thread.getLooper();    mServiceHandler = new ServiceHandler(mServiceLooper);  }  @Override  public int onStartCommand(Intent intent, int flags, int startId) {      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();      // For each start request, send a message to start a job and deliver the      // start ID so we know which request we‘re stopping when we finish the job      Message msg = mServiceHandler.obtainMessage();      msg.arg1 = startId;      mServiceHandler.sendMessage(msg);      // If we get killed, after returning from here, restart      return START_STICKY;  }  @Override  public IBinder onBind(Intent intent) {      // We don‘t provide binding, so return null      return null;  }  @Override  public void onDestroy() {    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();  }}
可以看出来,与使用IntentService相比要做很多额外的工作。

但是,因为你在onStartCommand()中处理请求,所以你可以并发处理多个请求。这个就超出这个例子的范围了。但是如果你需要这么做,你可以给每个请求创建一个线程然后立即执行(而不是等待前一个请求执行结束)。

要注意的是,onStartCommand()必须要返回一个整数返回值。返回值描述了service被系统杀掉以后又被restart的时候,系统如何处理service(正如之前讨论的那样,IntentService默认会帮你做这个事,当然你也可以修改)。返回值必须是下面的几个常量:

START_NOT_STICKY
如果onStartCommand()返回以后系统杀掉了service,restart的时候不会重新创建service,除非有pending intent要发送。这是最安全的方式来避免运行不必要的service,并且应用可以简单地重新开始任何没完成的任务。

START_STICKY
如果onStartCommand()返回以后系统杀掉了service,restart的时候会重新创建service,然后调用onStartCommand(),但是不会再次发送最后的intent。相反,系统会用null的intent来调用onStartCommand(),除非是有pending intent来启动service,这种情况下,这些intent是会传递进去的。这对媒体播放器(或者类似的service)来说是非常合适的,因为它们不执行命令,只是在无限的运行,等待新的任务。

START_REDELIVER_INTENT

如果onStartCommand()返回以后系统杀掉了service,restart的时候会重新创建service,然后调用onStartCommand(),并且会再次发送最后的intent。pending intents会被依次发送,这适合于那种需要立即被唤醒的情况,比如:下载文件。

了解更多关于返回值的信息,参考每一个常量的文档。

启动Service

你可以在Activity中后者是其他组件中通过给startService()传递intent(指定要启动的service)来启动service。Android系统会调用service的onStartCommand()方法然后把Intent传递进去(禁止直接调用onStartCommand())。

比如,Activity可以在startService()中用明确的intent启动前面章节中的例子service(HelloService)。

Intent intent = new Intent(this, HelloService.class);startService(intent);
startService()方法会立即返回,Android系统会调用service的onStartCommand()方法。如果service还没有执行过,系统会首先调用onCreate()然后再调用onStartCommand()。

如果service不提供绑定,startService()方法传递进来的intent是唯一的跟外部组件进行通讯的方式。但是,如果你想让service发送结果出去,启动service的客户端可以创建一个做广播用的PendingIntent,(用getBroadcast()),把PendingIntent传递给service。service就可以使用广播来传递结果。

多个启动service的请求会导致相应的对service的多个onStartCommand()调用。但是,只需要一个停止service的请求就可以停掉service。

停掉service

started service必须要自己管理生命周期。也就是说,系统不会停止或者销毁service除非是为了释放系统资源,service在onStartCommand()返回以后会继续运行。因此,service必须要调用stopSelf()停掉自己或者是其他组件调用stopService()来停掉service。

一旦用stopSelf()或者stopService()请求停掉service,系统就会尽快的将service销毁掉。

但是,如果你的service在onStartCommand()中并发的处理请求,当你处理完一个请求以后你不应该就停掉service,因为你可能又收到了一个新的请求(第一个请求结束后停掉service也会停掉第二个请求)。为了避免这样的事情,你可以使用stopSelf(int)来确保停止service的请求总是基于最近的请求。也就是说,当你调用stopSelf(int)的时候,你要传递对应停止请求的启动请求ID(startId会被传递到onStartCommand()中)。然后,如果service在调用stopSelf(int)之前,又收到了一个启动请求,ID匹配不上,service就不会停止。

注意:在service工作完成的时候把service停掉对于避免浪费系统资源和节省电量是很重要的。如果需要的话,其他的组件可以调用stopService()来停掉service。启用了binding的service,如果收到了onStartCommand()调用的话,也要手动停掉service。

了解更多关于service生命周期的知识,查看“管理Service的生命周期”下面的章节。

创建Bound Service

bound service允许应用的组件调用bindService()绑定到service上,为了创建长时间存在的连接(一般不允许组件调用startService()来启动)。

当你想从Activity或者是其他组件跟service进行交互,或者是通过IPC暴漏应用的某些功能给其他应用的时候,你应该创建bound service。

要创建一个bound service,必须要实现onBind()回调,并且返回IBinder,它定义了跟service通讯的接口。其他的组件可以调用bindService()来检索出接口,然后调用service的方法。service只存活于它绑定到的组件上,所以,当没有组件绑定的时候,系统会销毁它(你不需要像停掉started service那样来停掉bound service)。

要创建一个bound service,首先要做的是定义客户端跟service交互的接口。service和客户端之间的接口必须是IBinder的实现,并且service必须要在onBind()回调中返回,一旦客户端收到了IBinder,它就可以通过接口跟service交互。

多个客户端可以同时绑定到同一个service上,当一个客户端跟service交互完成以后,它会调用unbindService()来解绑。如果没有客户端绑定到service上,系统就会把service销毁掉。

有很多种实现bound service的方式,一般实现要比started service更复杂,所以bound service会在单独的文章(http://developer.android.com/guide/components/bound-services.html)中做讨论。

给用户发送Notification

一旦运行以后,service可以使用弹出Notification或者是状态栏Notification给用户发送提醒事件。

弹出提醒是出现在当前窗口之上停留一段时间然后消失的消息提示,状态栏提醒在状态栏的消息中提供了一个icon,用户可以选中做一些操作(比如启动一个Activity)。

一般来说,当后台任务完成以后,用状态栏提醒是最佳的方式(比如:文件下载完成),然后用户可以做一些动作。当用户在展开的视图中选择一个提醒以后,提醒可以开启一个Activity(比如跳转到下载文件的view)。

前台运行service


前台service是被认为是用户可感知的,当系统内存低的时候不是候选被杀掉的service。前台service必须在状态栏有提醒,放在“正在进行”的头部之下,也就是说,提醒不会消失除非service被停止或者被从前台移除。

比如说,service的音乐播放器应该运行在前台,因为用户明确感知到它的操作,状态栏的提醒可以表明当前正在播放的歌曲,并且允许用户跳转到音乐播放器的Activity做一些交互。

让service运行在前台需要调用startForeground()方法。这个方法接受2个参数,一个唯一标识提醒的整数,一个是状态栏的提醒。比如:

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_ID, notification);
注意: ID一定不能为0.

要从前台移除service,需要调用stopForeground()。这个方法接收一个boolean参数,用以表明是否移除状态栏的提醒。这个方法不会停掉service,但是,如果你想在service运行在前台的时候停掉它,提醒也会被移除掉。

管理service的生命周期

service的生命周期要远比Activity的生命周期简单。但是,你需要更加注意service的创建和销毁,因为service可以在用户不知晓的情况下在后台运行。

service生命周期-从创建到销毁-遵循下面的2中不同的形式:

(1)started service
当其他组件调用startService()的时候service被创建出来,然后就无限的运行,它必须要手动调用stopSelf()来停掉自己。其他的组件可以调用stopService()来停掉它。当service停掉以后,系统会销毁它。


(2)bound service
当其他组件(客户端)调用bindService()时候service被创建出来,然后客户端通过IBinder接口跟service进行交互。客户端可以调用unbindService()来关掉与service的连接。多个客户端可以同时连接到一个service上,当所有的客户端都解绑以后,系统会销毁掉service(service不需要自己手动去停掉)。

这两种形式并不是完全独立的。也就是说,你可以绑定到一个用startService()启动的started service上。比如:后台的音乐service可以通过startService()传递要播放的歌曲的intent来启动起来,然后,当用户希望对播放器做一些操作或者是获取当前歌曲的信息的时候,可以把Activity通过bindService()绑定到service上。这种情况下,stopService()或者stopSelf()并不会停掉service,直到所有的客户端都解绑以后。

实现生命周期回调

类似于Activity,service也有生命收起回调函数可以让你来监控service的状态,并在适当的时候做一些事情。


下面的例子展示了每一个生命周期函数:

public class ExampleService extends Service {    int mStartMode;       // indicates how to behave if the service is killed    IBinder mBinder;      // interface for clients that bind    boolean mAllowRebind; // indicates whether onRebind should be used    @Override    public void onCreate() {        // The service is being created    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        // The service is starting, due to a call to startService()        return mStartMode;    }    @Override    public IBinder onBind(Intent intent) {        // A client is binding to the service with bindService()        return mBinder;    }    @Override    public boolean onUnbind(Intent intent) {        // All clients have unbound with unbindService()        return mAllowRebind;    }    @Override    public void onRebind(Intent intent) {        // A client is binding to the service with bindService(),        // after onUnbind() has already been called    }    @Override    public void onDestroy() {        // The service is no longer used and is being destroyed    }}
注意:跟Activity的生命周期回调不同的是,这些回调并不要求调用父类的实现。

通过实现这些方法,你可以监控service生命周期内部的2个循环:

(1)service的完整的生命周期是从调用onCreate()到onDestroy()返回。跟activity类似,service也是在onCreate()中做初始化,在onDestroy()中释放资源。比如,音乐播放器service可以在onCreate()中创建播放音乐的线程,在onDestroy()中停掉这个线程。所有的service都会调用onCreate()和onDestroy(),不管service是用startService()还是bindService()创建出来。

(2)service的活动的时间是从调用onStartCommand()或者onBind()开始的.与此对应,这两个方法会处理通过startService()或者bindService()传递进去的intent。如果service是被started,当service的生命周期结束的时候也就是活动时间结束的时候(从onStartCommand()返回以后,service仍然是活动的)。如果service是bound的,onUnbind()返回的时候,service的生命也就结束了。

注意:如果一个started service是通过调用stopSelf()或者stopService()被停掉的,这时候并没有与之对应的回调(没有onStop()回调)。
因此,除非service是被绑定到了一个客户端上,否则一旦service停掉,系统就会去销毁它,onDestroy()是service收到的唯一的回调。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。