Service详解
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Service详解
⼀什么是Service
⼀个Service是没有界⾯且能长时间运⾏于后台的应⽤组件.其它应⽤的组件可以启动⼀个服务运⾏于后台,即使⽤户切换到另⼀个应⽤也会继续运⾏.另外,⼀个组件可以绑定到⼀个service来进⾏交互,即使这个交互是进程间通讯也没问题.例如,⼀个aservice可能处理⽹络事物,播放⾳乐,执⾏⽂件I/O,或与⼀个内容提供者交互,所有这些都在后台进⾏.
⼀个service本质上可有两种表现形式:
尽管这个⽂档是把这两种service分开讲的,但你的service可以在这两种⽅式下⼯作.—它可以是started(⽆限期运⾏)同时也允许绑定.唯⼀的简单问题是你是否实现了⼀对回调⽅法:onStartCommand()允许组件启动它并且onBind()允许绑定.
不论你是应⽤是否启动,或绑定到⼀个服务或两者都做了,任何应⽤组件都可以使⽤service(即使从另⼀个应⽤),跟任何组件都可以使⽤activity⼀样—通过⼀个Intent启动它.然⽽,你可以在manifest⽂件中声明服务为私有,并且阻⽌另外的应⽤访问它.这在讲如何于manifest⽂件中声明service时会详细讲解.
注意:⼀个service是运⾏在它所在进程的主线程中的—service不会创建它⾃⼰的thread也不会运⾏于单独的进程(除⾮你另外指定).这表⽰,如果你
的service想做⼀些狂耗CPU的⼯作或阻塞型的操作(⽐如MP3播放或⽹络通讯),你必须在service中创建⼀个新的线程来做那些⼯作.通过使⽤⼀个分离的线程,你将减少"应⽤没有反应"(ANR)错误并且应⽤的主线程可以保持activity对⽤户操作的快速反应.
基础
你应使⽤⼀个service还是线程?
⼀个service是⼀个在⽤户不与你的应⽤交互时依然可以运⾏于后台的简单组件.所以,只有你需要这样做时才创建⼀个service.
如果你需要执⾏的⼯作不在主线程中,但是只有⽤户在与你的应⽤交互时才进⾏,那么你可能应该创建⼀个新的线程⽽不是⼀个service.例如,如果你想播放⼀些⾳乐,但是只在你的activity运⾏时才播放,你应该在onCreate()中创建⼀个线程,在onStart()运⾏这个线程,然后在onStop()中停⽌它.也可以考虑使⽤AsyncTask或HandlerThread,来代替传统的线程类.
记住,如果你使⽤了service,它默认会固定运⾏于你的应⽤的主线程,所以你应该在其中创建⼀个线程来执⾏耗时或阻塞的操作
要创建⼀个service,你必须创建⼀个Service类(或某个已存在的⼦类)的⼦类.在你的实现中,你应覆写⼀些处理有关service⽣命期的关键⽅⾯的回调⽅法并且提供⼀个能让组件绑定到service的机制(如果需要).你应覆写的最重要的回调⽅法是:
如果⼀个组件通过调⽤startService()启动⼀个service(最终导致onStartCommand()被调⽤),之后service会保持运⾏,直到它通过stopSelf()停⽌⾃⼰或另外的组件调⽤stopService()停⽌它.
如果⼀个组件调⽤bindService()来创建service(onStartCommand()不会被调⽤),那么service只是运⾏在绑定期间.⼀旦service从所有的客户端解除绑定,系统就会杀了它.
Android系统只在内存很少并且必须为具有⽤户焦点的actvity釋放资源时才会强制停⽌⼀个service.如果service是绑定到具有⽤户焦点的activity上,那么它很难被杀死,并且如果service被声明为运⾏于前台(后⾯将讨论),那么它将永不被杀死,除⾮,如果这个service启动并且长期运⾏,那么系统将会降低它在后台任务超时列表中的位置然后这个将变成⾼度易被杀对象—如果你的service被启动,那么它必须被设计为能优雅地处理被系统重启的操作.如果系统杀死了你的service,它会在资源重新可⽤时⽴马重启它(但是依赖于你在onStartCommand()中的返回值).
⼆如何创建⼀个Service
在manifest中声明⼀个service
跟activity以及其它组件⼀样,你必须在你的应⽤的manifest⽂件中声明所有的service们.
要声明你的service,添加⼀个<service>元素作为<application>元素的⼉⼦.例如:
[java]
01. <manifest ... >
02. ...
03. <application ... >
04. <service android:name=".ExampleService" />
05. ...
06. </application>
07. </manifest>
有许多属性你可以包含在<service>元素中,⽐如启动service的权限和service运⾏所在的进程.android:name属性是哇⼀必须的—它指定了service类的名字.⼀旦你发布了你的应⽤,你不应再改变这个名字,因为如果你改了,你可能使⼀些通过明确的intent来引⽤你
的service的功能⽆法运⾏.
就像⼀个activity,⼀个service可以定义intent过滤器来使得其它组件使⽤明确的intent调⽤⾃⼰.通过声明intent过滤器,你设备上的任意应⽤中的组件都可以通过给startService()传递匹配的intent来启动你的sevice.
如果你打算只在本应⽤内使⽤⾃⼰的service,那么你不需指定任何intent过滤器.不使⽤intent过滤器,你必须使⽤⼀个明确指
定service的类名的intent来启动你的service.
另外,你也可以通过包含android:exported属性,并指定其值为”false”来保证你的service是私有的.即使你的service使⽤了intent过滤器,也会起作⽤.
创建⼀个"启动的"Service
针对Android1.6或更早的版本:
如果你创建的应⽤是针对Android1.6或更早版本的,你需要实现onStart()⽽不是onStartCommand()(在Android2.0中,onStart()被废弃代替之以onStartCommand()).
更多关于如何兼容2.0之前版本的知识,请看onStartCommand()⽂档.
⼀个启动的service,在被其它组件调⽤startService()来启动时,会导致service的onStartCommand()⽅法被调⽤.
当⼀个service被启动后,它的⽣命期就不再依赖于启动它的组件并且可以独⽴运⾏于后台,即使启动它的组件死翘翘了.所
以,service应该⼯作完成后调⽤stopSelf()⾃⼰停⽌掉,或者其它组件也可以调⽤stopService()停⽌service.
⼀个应⽤组件,⽐如⼀个activity可以通过调⽤startService()启动service同时传递⼀个指定service和service所⽤的数据
的Intent,service在⽅法onStartCommand()中接收这个Intent.
事物完成后,service停⽌⾃⼰然后被销毁.
⼩⼼:service默认运⾏在声明它的应⽤进程的主线程中.所以,如果你的service执⾏密集运算或阻塞操作并且与跟⽤户交互的activity位于相同的应⽤中,这个service将会拉低activity的性能.要避免影响应⽤的性能,你必须在service中启动⼀个线程.
传统上,有两个类你可以从它派⽣来创建"启动的"service:
从IntentService类派⽣
因为⼤多数"启动的"service不需要同时处理多个请求,可能从IntentService实现你的service是最好的选择.
IntentService做了以下⼯作:
以上实现使得你可以仅仅实现onHandleIntent()来做要做的⼯作即可.(当然,你还是要实现⼀个⼩⼩的构造函数).
下⾯是⼀个实现IntentService的例⼦:
01. public class HelloIntentService extends IntentService {
02.
03. /**
04. * ⼀个构造函数是必须的,并且你必须调⽤⽗类的IntentService(String)以传⼊⼯作线程的名字.
05. */
06. public HelloIntentService() {
07. super("HelloIntentService");
08. }
09.
10. /**
11. * IntentService在默认的⼯作线程中调⽤这个⽅法<p> *当这个⽅法返回后,IntentService停⽌服务,如果能停⽌的
话.
12. */
13. @Override
14. protected void onHandleIntent(Intent intent) {
15. // Normally we would do some work here, like download a file.
16. // For our sample, we just sleep for 5 seconds.
17. long endTime = System.currentTimeMillis() + 5*1000;
18. while (System.currentTimeMillis() < endTime) {
19. synchronized (this) {
20. try {
21. wait(endTime - System.currentTimeMillis());
22. } catch (Exception e) {
23. }
24. }
25. }
26. }
27. }</p>
以上就是你所有需要做的:⼀个构造函数和⼀个onHandleIntent()的实现.
如果你决定重写其它的⽅法,⽐如onCreate(),onStartCommand(),oronDestroy(),要保证调⽤⽗类的对应实现,这样IntentService才能正确地处理⼯作线程的⽣命期.
⽐如,onStartCommand()必须返回默认的实现(其中实现了intent被传送到onHandleIntent()的逻辑):
[java]
01. @Override
02. public int onStartCommand(Intent intent, int flags, int startId) {
03. Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
04. return super.onStartCommand(intent,flags,startId);
05. }
除onHandleIntent()外,唯⼀不需调⽤⽗类实现的⽅法是onBind()(但是你只需在你的service允许绑定时才实现它).
在下⼀节,你将看到同样的service类,从类Service派⽣时是如何实现的.这需要写更多的代码,但是当你需要处理同时发⽣的请求时(⾮序列化)这就是合适的做法了
使⽤类IntentService使得你实现⼀个"开始的"service⾮常容易.然⽽,如果你需要你的service以多线程⽅式执⾏(⽽不是使⽤⼯作队列),那么你需要从类Service派⽣来处理每个intent.
相⽐之下,下⾯的例⼦从类Service派⽣并实现了与上⾯使⽤IntentService例⼦完全相同的⼯作.也就是在⼀个线程中序列化的处理每个"开始"请求.
01. <span style="font-size:18px;">public class HelloService extends Service {
02. private Looper mServiceLooper;
03. private ServiceHandler mServiceHandler;
04.
05. // 处理从线程收到的消息们
06. private final class ServiceHandler extends Handler {
07. public ServiceHandler(Looper looper) {
08. super(looper);
09. }
10. @Override
11. public void handleMessage(Message msg) {
12. // 通常我们在这⾥做⼀些⼯作⽐如下载⼀个⽂件
13. // 在我们的例⼦中,仅仅是睡5秒钟.
14. long endTime = System.currentTimeMillis() + 5*1000;
15. while (System.currentTimeMillis() < endTime) {
16. synchronized (this) {
17. try {
18. wait(endTime - System.currentTimeMillis());
19. } catch (Exception e) {
20. }
21. }
22. }
23. // 使⽤startId停⽌服务,从⽽使我们不会在处理
24. // 另⼀个⼯作的中间停⽌service
25. stopSelf(msg.arg1);
26. }
27. }
28.
29. @Override
30. public void onCreate() {
31. // 启动运⾏service的线程.注意我创建了⼀个
32. // 分离的线程,因为service通常都是在进程的
33. // 主线程中运⾏,但我们不想让主线程阻塞.我们还把新线程
34. // 搞成后台级的优先级,从⽽减少对UI线程(主线程的影响).
35. HandlerThread thread = new HandlerThread("ServiceStartArguments",
36. Process.THREAD_PRIORITY_BACKGROUND);
37. thread.start();
38.
39. // Get the HandlerThread's Looper and use it for our Handler
40. mServiceLooper = thread.getLooper();
41. mServiceHandler = new ServiceHandler(mServiceLooper);
42. }
43.
44. @Override
45. public int onStartCommand(Intent intent, int flags, int startId) {
46. Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
47.
48. // 对于每个开始请求,发送⼀消息来开始⼀次⼯作,并且把
49. // start ID也传过去,所以当完成⼀个⼯作时,我们才知道要停⽌哪个请求.
50. Message msg = mServiceHandler.obtainMessage();
51. msg.arg1 = startId;
52. mServiceHandler.sendMessage(msg);
53.
54. // 如果我们在这⾥返回后被被杀死了,重启之.
55. return START_STICKY;
56. }
57.
58. @Override
59. public IBinder onBind(Intent intent) {
60. // We don't provide binding, so return null
61. return null;
62. }
63.
64. @Override
65. public void onDestroy() {
66. Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
67. }
68. }
69. </span>
如你所见,要做的⼯作⽐使⽤IntentService时多⼀些.
然⽽,因为你⾃⼰处理每次对onStartCommand()的调⽤,你可以同时执⾏多个请求.这个例⼦并没有那样做,但是如果那是你所需要的,那么你可以为每个请求创建⼀个新的线程并且⽴即运⾏它们(⽽不是等待上⼀个请求完成).
注意⽅法onStartCommand()必须返回⼀个整数.这个整数描述了在系统杀死它的事件中系统如何继续这个服务(如前⾯所述,IntentService的默认实现为你处理这些,当然你也能够去改写它).onStartCommand()也返回值必须是下⾯常量之⼀:
开始⼀个Service
你可以从⼀个activity或从其它应⽤的组件通过传递⼀个Intent(指定了要启动的服务)给startService()启动⼀个服务.Android系统然后调⽤service的onStartCommand()⽅法并且把Intent传递给它.(你永远不能直接调⽤onStartCommand().)
例如,⼀个activity可以在调⽤startService()时使⽤⼀个明确的intent开始前⽂的例⼦中的service(HelloSevice):
Intentintent = new Intent(this, HelloService.class);
startService(intent);
startService()⽅法会⽴即返回然后Android系统调⽤service的onStartCommand()⽅法.但是如果service尚没有运⾏,系统会先调
⽤onCreate(),然后调⽤onStartCommand().
如果service没有提供绑定功能,传给startService()的intent是应⽤组件与service之间唯⼀的通讯⽅式.然⽽,如果你希望service回发⼀个结果,那么启动这个service的客户端可以创建⼀个⽤于⼴播(使⽤getBroadcast())的PendingIntent然后放在intent中传
给service,service然后就可以使⽤⼴播来回送结果.
不同的启动请求导致对service的onStartCommand()的不同调⽤,但停⽌service的请求只有⼀个(使⽤stopSelf()或stopService()).
停⽌⼀个service
⼀个"启动的"service必须管理其⾃⼰的⽣命期.这表⽰,系统不会停⽌或销毁这种service,除⾮内存不够⽤了并
且service在onStartCommand()返回后会继续运⾏.所以,service必须调⽤stopSelf()停⽌⾃⼰或由另⼀个组件调⽤stopService()来停⽌它.
⼀旦通过stopSelf()或stopService()发出了停⽌请求,系统就会尽可能快地销毁service.
然⽽,如果你的service同时处理多个对onStartCommand()的请求,那么你不应在处理完⼀个请求之后就停⽌service,因为你可能已经⼜收到了新的启动请求(在第个完成后停⽌将会结束掉第⼆个).要避免这个问题,你可以使⽤stopSelf(int)来保证你的停⽌请求对应于你最近的开始请求.也就是,当你调⽤stopSelf(int)时,你传递开始请求的ID(传递给onStartCommand()的startId)给service,如果service在你调
⽤stopSelf(int)之前收到⼀了个新的开始请求,发现ID不同,于是service将不会停⽌.
注意:你的应⽤在完成⼯作后停⽌它所有的service是⾮常重要的.这可以避免浪费系统资源和消耗电量.如果需要,其它的组件可以调⽤stopService()停⽌service.即使你为service启⽤了绑定,你也必须⾃⼰停⽌service,甚⾄它收到了对onStartCommand()的调⽤也这样.
创建⼀个绑定的Service
⼀个绑定的service是允许应⽤的组件通过调⽤bindService()来绑定它以创建⼀个能长期存在的连接(并且⼀般不允许组件调
⽤startService()来启动它).
当你的activity或其它组件想与service交互或你的应⽤想基于IPC的向其它应⽤提供功能时,你应该创建⼀个绑定的service.
要创建⼀个绑定的service,你必须实现回调⽅法onBind(),还要在其中返回⼀个IBinder,这个IBinder定义了与service通讯的接⼝.其它应⽤组件就可以在之后调⽤bindService()来接收这个接⼝并开始调⽤service的⽅法.service只在有应⽤组件绑定到它时才活着,所以当没有组件绑定到它时,系统就会宰了它(你不需去停⽌⼀个绑定的service,跟⽤onStartCommand()启动的service不⼀样).
要创建⼀个绑定的service,⾸先要做的就是定义客户端如何与service通讯的接⼝.这个接⼝必须是IBinder的⼀个实现,并且必须被回调⽅法onBind()返回.⼀旦客户端接收到IBinder,它就可以开始与service进⾏交互.
多个客户端可以⼀起绑定到⼀个service.当⼀个客户端完成与service的交互,它调⽤unbindService()来解除绑定.⼀旦不再有任何客户端绑定到service,系统就宰了这个service.
有很多⽅法来实现⼀个绑定的service并且这些实现要⽐"开始的"service难懂得多.
发送通知给⽤户
⼀旦开始运⾏,⼀个service可以通过Toast通知或状态栏通来通知⽤户⼀些事件.
⼀个toast通知是⼀个出现在当前窗⼝表⾯上并过⼀会就消失的消息.当⼀个状态栏通知提供⼀个带有消息的图标到状态栏,⽤就可以先定它来执⾏⼀些动作(⽐如启动⼀个activity).
通常,⼀个状态栏通知是当⼀些后台⼯作(⽐如⼀个⽂件下载完成了)完成后通知⽤户可以对它进⾏动作的最佳⽅式.当⽤户选择这个通知时,它可以开始⼀个activity(⽐如可以查看下载的⽂件).
在前台运⾏Service
⼀个前台的service是被⽤户强烈关注的从⽽不会在内存低时被系统杀死.前台service必须在状态栏上提供⼀个通知,这个通知被放在"正在进⾏"区域中,这表⽰这个通知不能被解除,除⾮服务停⽌了或者从前台移除了.
例如,⼀个从service播放⾳乐的⾳乐播放器,应被设置为前台运⾏,因为⽤户会明确地注意它的运⾏.在状态栏中的通知可能会显⽰当前的歌曲并且允许⽤户启动⼀个activity来与⾳乐播放器交互.
[java]
01. Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
02. System.currentTimeMillis());
03. Intent notificationIntent = new Intent(this, ExampleActivity.class);
04. PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
05. notification.setLatestEventInfo(this, getText(R.string.notification_title),
06. getText(R.string.notification_message), pendingIntent);
07. startForeground(ONGOING_NOTIFICATION, notification);
要请求你的service运⾏于前台,调⽤startForeground().此⽅法有两个参数:⼀个整数唯⼀的标识⼀个通知,和这个⽤于状态栏的通知,例如:
要从前台移除service,调⽤stopForeground().这个⽅法有boolean型参数,表明是否也从状态栏删除对应的通知.这个⽅法不会停掉service.然⽽,如果你停⽌了正在前台运⾏的service,这个通知也会被删除.
注意:⽅法startForeground()和⽅法stopForeground()是从Android2.0 (API Level 5)引⼊的.为了在早期版本是于前台运⾏你的service,你必须使⽤以前的那个setForeground()⽅法—见startForeground()的API⽂档查看如何提供与旧版本的兼容性.
管理Service的⽣命期
⼀个service的⽣命期⽐⼀个activity要简单得多.然⽽,你依然需要密切关注你的service是如何被创建⼜是如何被销毁的,因为⼀个service可以运⾏于后台⽽⽤户看不到它.
service的⽣命期—从它被创建到它被销毁—有两条路可⾛:
这两条路并不是完全分离的.也就是,你是可以绑定到⽤startService()启动的service的.例如,⼀个后台⾳乐service在通过传⼊指明要播放的⾳乐
的intent来调⽤startService()后启动.之后,当⽤户想对播放器进⾏⼀些操作或要获取当前歌曲的信息时,⼀个activity可以通过调⽤bindService()绑定
到service.在此情况下,stopService()或stopSelf()不会真正的停⽌service,除⾮所有的客户端都取消绑定了.
实现⽣命期回调⽅法
就像activity,service也具有⽣命期回调⽅法,⽤它们你可以监视service的状态的变化并且在合适的时机做⼀些⼯作.下⾯的框架代码演⽰了每个⽣命期⽅法的实现:
[java]
01. public class ExampleService extends Service {
02. int mStartMode; // 表明在service被杀后的⾏为
03. IBinder mBinder; // 客户端绑定到的接⼝
04. boolean mAllowRebind; // 表明onRebind是否应被使⽤
05.
06. @Override
07. public void onCreate() {
08. // The service is being created
09. }
10. @Override
11. public int onStartCommand(Intent intent, int flags, int startId) {
12. // service 正在启动,在调⽤startService()期间被调⽤
13. return mStartMode;
14. }
15. @Override
16. public IBinder onBind(Intent intent) {
17. // ⼀个客户端通过bindService()绑定到这个service
18. return mBinder;
19. }
20. @Override
21. public boolean onUnbind(Intent intent) {
22. // 所有的客户端使⽤unbindService()解除了绑定
23. return mAllowRebind;
24. }
25. @Override
26. public void onRebind(Intent intent) {
27. // ⼀个客户端在调⽤onUnbind()之后,正使⽤bindService()绑定到service
28. }
29. @Override
30. public void onDestroy() {
31. // service不再被使⽤并将被销毁
32. }
33. }
注:不像activity的⽣命期回调⽅法们,你不需要调⽤⽗类的相应实现.
图 2.service的⽣命期.左图显⽰了⽤startService()创建的service的⽣命期,右图显⽰了⽤bindService()创建的service的⽣命期.
通过实现这些⽅法们,你可以监视service⽣命期的两个嵌套循环:
注:尽管⼀个"启动的"service在调⽤stopSelf()或stopService()时结束,但并没有单独的回调对应这些停⽌⽅法(没有类似于onStop()的回调).所以,除
⾮service被绑定到⼀个客户端,系统就会在停⽌时销毁service—onDestroy()是唯⼀收到的回调.
图 2演⽰了service的典型回调.尽管图⽰分开了通过startService()和bindService()创建的service,但记住任何service,不管它是怎样启动的,都是可能允许绑定的.所以⼀个从onStartCommand()启动的service(客户端调⽤了startService())仍可以接收onBind()调⽤(当客户端调⽤bindService()时).。