Service

不贴英文了,直接翻译生气主要是加深对Service的理解


Service是一个可以在后台处理耗时操作且没有用户界面的一个应用组件。一个Service可以被其它的组件启动,并一直在后台运行即使用户转向去使用别的app了。此外,一个组件可以通过绑定(bind)的方式和Service交互,甚至执行跨进程通信(IPC)。比如,一个服务可以在后台完成很多操作,诸如 操作网络事务,播放音乐,操作文件I/O,和content provider 交互等等。


Service本质上有两种形式。

方式一 (Start型):一个组件(比如activity)通过startService()来启动,一旦以这种方式启动,Service会无限期地在后台运行,即使是启动它的组件被销毁了。通常情况下,此方式启动的服务会执行单一的操作并且不会返回任何结果给调用者。比如,Service执行了一个操作从网络上下载或者上传文件,操作完成了服务就该结束了(方式一不会自动结束)。

方式二(Bind型):一个组件通过bindService()的方式是自身与Service绑定来启动Service。绑定型Service提供了一个C/S(客户端/服务器)接口来允许自身与绑定的组件进行交互通信,发送请求,返回结果,甚至是跨进程通信(IPC)。一个绑定型Service只要有其它组件与其绑定就会运行,并且可以和多个组件同时绑定,当所有绑定的组件都与其解除绑定关系(unbind,即不再有与之绑定的组件)时自动销毁。

尽管本文档大体上分开讲述了这两种启动方式,但是你的Service可以同时以这两种形式存在——它既可以无限期后台运行,也可以运行其它组件绑定。关键在于你是否实现了对应的接口 ——onStartCommand()来允许组件start方式启动,onBind()来允许组件进行绑定。

和用Intent启动activity一样,你可以启动一个Service无论你用的哪种Service形式,Start、Bind异或Start+Bind。此外,你可以在manifest文件中将你的Service定义成私有的来阻止其他应用调用。

注意:Service运行于它的宿主进程的主线程中——Service并不会为自己创建新进程,也不会运行于另一个进程中(除非你指定)。这就意味着,如果你的Service想要执行消耗cpu的工作或者耗时操作(比如播放mp3或者网络操作),那么你应该创建一个新的线程去执行这些操作。新建一个线程执行这些操作可以帮助减少“Application No Response”(ANR)错误,并且能够让应用主线程专注于和activity交互。

基础

应该用Service还是Thread?(用服务还是线程,如何选择?)

Service 是一个可以一直运行在后台组件,即使用户关了app也可以运行。如果你有这个需求就需要创建一个Service。

如果你需要在主线程之外执行操作,并且仅仅是在用户在使用app过程中,那么你就应该使用Thread而不是Service。

比如,如果你只想在应用界面运行时播放音乐,那么你只需要在activity的onCreate()中创建一个线程,在onStart()中启动它,在onStop()中停止它。也是可以考虑用AsyncThread 或者HandlerThread 而不是传统的Thread类。

记着,如果你用Service,它是默认运行于你的应用主线程里的,因此如果你要进行密集或者耗时操作你应该创建一个新thread并在里面执行。


创建一个Service,你需要创建一个类继承Service(或者是一个现有Service的子类)。实现过程中,你需要重写一些处理的服务生命周期的关键方面和提供绑定服务与组件的回调方法,如果合适的话。最重要的回调方法你需要覆写如下:

onStartCommand()

当另一个组件如activity启动Service通过调用startService时这个方法会被系统调用。一旦这方法被执行,服务就被启动起来并且长期运行于后台。一旦你实现了它,你就有责任去停止Service当它的工作完成时通过调用stopSelf()或者stopService().(如果你想要提供Bind方式,那么你可以不实现这个方法)

onBind()

当另一个组件想要绑定Service(比如要执行RPC操作)通过调用bindService(),该方法会被系统调用。这个方法的实现中,你需要提供一个可以让客户端和Service交互的接口,通过返回一个IBinder。你必须实现这个方法,但是如果你不想允许绑定,那么就返回null。

onCreate()

当Service第一次被创建系统会调用这个方法,这个方法运来执行第一次创建服务操作(该方法早于系统调用onStartCommand()或onBind())。但是如果服务已经运行了,启动服务该方法不会被调用(只用来创建服务,服务已经存在就会调用)

onDestroy()

当Service不再需要运行了被销毁时系统会调用这个方法。你的Service需要实现这个方法去清理如线程、注册的监听器、接收器等等资源。这是服务的最后一个可被调用的生命周期方法。

如果一个组件通过startService()创建并启动一个服务,onStartCommand()会被调用,服务将会一直运行,在服务中调用stopSelf()或者其它组件中调用stopService()可以停止服务。

如果一个组件通过bindService()创建并启动一个服务,onStartCommand()不会被调用,只要有组件和服务绑定,服务就会一直运行,直到所有与服务绑定的组件都解除了绑定,服务才会被系统自动销毁。

当系统必须为用户正在使用的activity提供资源,并且内存不足时,系统会强制停止Service。如果一个服务和activity绑定,那么它比较不容易被系统杀死;如果一个服务被声明为前台服务,那么它几乎不会被系统杀死。当服务长期在后台运行,随着时间的流逝,系统会自动降低服务的等级,服务会变得越来越可能被系统杀死——如果你的服务启动了,你需要优雅地为你的服务设计重启机制。如果系统杀死了你的服务,一旦系统资源充裕了系统会重启你的服务(尽管这依赖于onStartCommand()的返回值)


在manifest中声明Service

和activity一样,你必须在你的应用manifest中注册声明Service。

<manifest ...= "" >
   ...
   
       <service android:name= ".ExampleService" >
       ...
   </service></application>
</manifest>
 声明中还有很多其它元素可以定义比如运行服务需要的权限 和 服务运行于哪个进程(默认主线程)。"name"是唯一必须制定的元素属性,它详细说明了服务的类名。一旦你发布了你的应用,你就不能再更改服务名了,一旦你这样做了,就会对那些用显性意图启动或绑定服务的代码带来风险。

为了保证app的安全,应该总是用一个显性intent去启动或者绑定你的服务而不要用启动服务的intent filter。如果你必须允许一定量的模糊性去启动服务,您可以为您的服务提供intent filters 和排除component name的Intent,但同时你也必须为你的intent用 setPackage() 设置能够消除歧义的包名。

同时,你可是设置android:exported=false去确保你的Service只对你的app可用,这有效地停止从其他应用程序启动你的服务,甚至使用一个明确的Intent时。

启动一个已经启动的服务

一个started Service 是一个被其它组件通过调用startService()方法启动了的服务,会调用onStartCommand()方法。

一旦一个服务被启动,它就拥有一个生命周期不依赖于启动它的组件,且可以一直运行下去,即使启动它的组件被销毁了。照此,服务应该完成工作后自己销毁通过stopSelf()或者被其它组件通过stopService()方法销毁。

  一个组件如activity  startService()启动一个服务会传递一个Intent ,这个Intent可以包含所有Service需要使用的数据,Service会在onStartCommand()中接收到这个intent。

举个例子,设想一个activity需要保存一些数据到云数据库上,那么可以为activity创建一个服务,activity把数据封装到intent传给服务 startService().服务在onStartCommand()接收到intent后连接网络完成数据库操作事务,然后自己停止退出。

注意:服务默认运行于应用主线程,如果你的服务要执行密集的或者阻塞操作当用户在和activity交互时,服务就有可能阻塞activity使其表现卡顿。避免这种情况,那你应该在服务中创建一个thread来执行这些任务。

 通常,有两个类你可以继承去创建一个服务。

Service: 这是所有服务的基类。注意在Service中创建一个thread来完成耗时操作。

IntentService:这是Service的一个子类,它会创建一个工作thread来完成启动时的任务请求,一次完成一个。如果你不需要你的服务同时完成多线程操作,那么它是最好的选择。你只需要实现 HandleIntent(),这个方法会接收每次启动请求的intent来按顺序后台完成任务。

继承IntentService类

服务中不需要处理并发线程任务时,继承IntentService最好。它会创建一个默认的工作线程(主线程之外)来处理onStartCommand()传递来的Intents任务。onStartCommand()会创建一个工作队列一次传递一个Intent到onHandleIntent()中,所以你永远不用担心多线程问题,(单线程顺序执行intent),一旦发现没有可以执行的intent任务,它会自己终止退出,所以你也不用手动停止服务。它提供默认实现的onBind()方法,并返回null。提供onStartCommand()方法默认发送intent到intent队列并且把它们传递给onHandleIntent()。

事实上你需要做的事加起来就是实现onHandleIntent()去实现客户端传递来的任务。(当然,你也需要提供一个小构造函数)。

如下是一个例子:

public class HelloIntentService extends IntentService {
 
   /**
    * A constructor is required, and must call the super <code>IntentService(String)</code>
    * 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(),那么一定要确保调用super,从而IntentService可以正确处理自身工作线程的生命周期。

比如,onStartCommand()必须返回默认的实现(它会完成intent的获取和传递给HandleIntent())。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
     Toast.makeText( this , service starting, Toast.LENGTH_SHORT).show();
     return super .onStartCommand(intent,flags,startId);
}
除了onHandleIntent()之外,唯一覆写不需要super的方法是onBind()(但是也只有你运行你的服务可以被绑定时才需要覆写这个方法)。

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

如你所见,完成相同的工作任务,继承Service类做了更多的工作。

然而,因为你每次调用onStartCommand()都自己处理,所以你可以同时处理多线程任务。例子没有实现这种功能,但是如果你有这个需求,你可以为每次请求都创建一个新thread来执行操作不需要等待前面的任务完成。

注意onStartCommand()必须要返回一个integer。这个integer 描述了当系统杀死了它时如何处理(之前讨论过,IntentService默认处理了这个,尽管你可以修改它)。onStartCommand()返回值必须是如下之一:

START_NOT_STICKY : 如果系统杀死了服务当掉用过onStartCommand()的返回值之后,不会重启服务,除非有没有传递的Intents需要传递,这是最安全的选项去避免你的服务运行在不是那么必要运行或者你的app可以轻松重启服务来完成未完成任务的情况下。

START_STICKY:如果系统杀死了服务当掉用过onStartCommand()的返回值之后,重启服务并调用onStartCommand(),但是不会重新传递上一个Intent,而是调用一个null的Intent,除非有没有传递的Intents需要启动服务,这种情况下,那些intents就被传递了。这适用于多媒体播放器(或类似的服务),他们并不是一直在处理命令,而是无限地运行下去等待任务执行。

START_REDELIVER_INTENT:如果系统杀死了服务当掉用过onStartCommand()的返回值之后,重启服务并调用onStartCommand(),会重新传递上一个Intent,所有未传递的Intents会依次被传递。这适用于那些一直在执行并且需要被立即重启的操作,比如下载一个文件。


启动一个Service

你可以通过activity或者其它组件通过传递一个Intent的方式startService()。Android系统会回调onStartCommand()方法并且把Intent传递给它。(千万不要直接调用onStartCommand())。

比如,activity可以用显式Intent的启动HelloService.

Intent intent = new Intent( this , HelloService. class );
startService(intent);

startService()方法会立即返回结果并且android系统会回调服务的onStartCommand()方法,如果服务还没有创建运行,会先调用onCreate(),然后调用onStartCommand()。

如果服务没有允许绑定(bind),那么通过startService()传递intent是activity和Service通信的唯一一种形式。然而,如果你想要服务将执行结果返回给activity,那么startService的客户端可以为一个broadcast(广播,通过getBroadcast())创建一个PendingIntent并且将它传递给对应的服务。那么服务就可以用这个广播来把执行结果传递回来。

多个请求启动服务startService()会导致多次调用onStartCommand(),但是终止Service只需要调用一次stopService()或stopSelf()。


停止一个应用

一个started service 必须自己料理生命周期,也就是说系统不会去杀死服务除非内存不足,并且会在onStartCommand()返回后重启服务继续执行。所以服务必须自己终止自己调用stopSelf()或stopService()。

一旦调用上面的两个方法,系统会尽可能地杀死服务。

然而,如果你的服务正在onStartCommand()方法中处理多种请求任务,那么你就不应该终止服务当你刚刚完成一个intent请求,因为你可能已经收到了一个新的intent启动请求(第一个任务完成后的终止命令会终止第二个刚收到但未执行的任务)。为了避免这一问题,你应该用stopSelf(int)来确保你的终止服务请求是建立在当前所有intent请求都完成的基础上。即是说,当你调用stopSelf(int)的时候,你将会将start请求的ID(传递到onStartCommand()的start ID)传递给终止请求的来匹配。然后,如果服务接收到一个新的start请求在你能够调用stopSelf(int)之前,那么就会发现ID不匹配,那么Service就不会终止。

注意:应用自己终止创建的服务很重要,这样可以避免系统资源的消耗和电量消耗。如果必要的话,其它组件也可以用stopService()来终止服务,即使你允许服务被绑定,你也必须总是对应地终止服务如果曾经通过startService方式启动。

创建一个绑定式Service

一个绑定式服务是一种允许其它应用组件通过调用bindService()绑定的服务,以便可以创建一种长期存在的连接形式。(通常不允许组件通过startService()方式来启动它)

当你想要activity 或者应用中的其他组件和Service交互 或者想显示你应用中的一些功能给其它应用通过进程间通信(IPC)时,你应该创建一个绑定式Service。

创建一个绑定式Service,你必须实现onBind()方法,并且返回一个IBinder。这个IBinder定义了和服务交互的接口。其它应用组件可以通过调用bindService()来获取这个接口并且调用Service中的方法。Service只为与之绑定的组件服务,所以一旦Service没有任何与之绑定的组件,系统就会销毁它(bind方式启动不需要你去手动终止服务,但是通过onStartCommand()方式启动的需要手动终止)。

创建一个绑定式Service,第一件需要做的事是必须定义一个详细描述终端如何与服务本身交互的接口。这个接口必须是IBinder的一个实现并且在服务的onBind()中返回。一旦终端接收到IBinder,它就可以通过这个接口来和Service交互。

多个终端可以同时和Service绑定。当一个终端完成了交互后,调用unbindService()来解除绑定,直到没有绑定终端时,系统就销毁了这个服务。

实现一个绑定式Service有很多方式且比started Service要复杂得多,所以绑定式服务有单独的文档讨论。


给用户发送通知

一旦运行,服务可以通知用户事件通过toast通知或者状态栏通知。

Toast通知是可以在当前窗口显示一段时间然后自动消失的一种信息。而状态栏通知可以在用户的状态栏显示一个图标和文字信息,这种状态下用户可以选择是否要进行下一步操作(比如启动一个activity)。

通常,用状态栏通知的方式当后台任务完成时(比如下载完了一个文件)是最好的一种手法,这样用户可以现在就操作它。当用户下拉通知栏时,状态栏通知可以启动一个activity(比如去看下载好的文件)

运行一个前台服务

一个前台服务被系统认为是用户关注的服务并且当系统内存不足时系统不会选择去杀死。一个前台应用必须提供一个通知在窗口顶部的状态栏上,那意味着这个通知不能被取消除非是服务挂了或者被从前台移除。

比如,一个音乐播放器通过服务播放音乐应该被设置为前台服务,因为用户很明显可以意识到这个播放行为。状态栏通知可能显示着当前播放的歌曲并且允许用户启动一个activity来和音乐播放器交互。

使你的服务允许与前台,需要调用startForeground()。这个方法需要两个参数:一个integer唯一定义通知,另一个是状态栏通知本身。举例:

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);
注意:传入startForeground()的整数ID必须不为0
从前台移除调用stopForeground()。需要传入一个boolean值,定义是否同时移除状态栏通知,这个方法不会终止服务。然而如果你终止了服务,状态栏通知就会被移除无论你服务是否还在前台运行。

管理服务的生命周期


服务生命周期——从创建到消亡有两种不同路径:

Started Service

其它组件或终端 startService()创建服务,然后服务无限运行必须自行终止stopSelf(),或者其它组件stopService()。服务被终止,系统销毁它。

bound Service(绑定式服务)

其它组件或终端 bindService()创建服务,可以被多个终端绑定。终端unBind()解除绑定,当没有绑定对象时,系统自动销毁它。

两种路径并非完全分离的。可以绑定 startedService(必须实现onBind()允许被绑定)。比如,一个后台音乐播放服务,被创建启动通过startService()通过一个Intent。之后,用户想要执行一些控制操作通过播放器界面或者获取一些当前播放信息,一个activity可以bindService()绑定服务.在这种场景下,stopSelf()和stopService()并不能完全终止服务本身直到没有绑定对象了。

实现生命周期的回调方法

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 <code>onCreate</code>() {
         // The service is being created
     }
     @Override
     public int <code>onStartCommand</code>(Intent intent, int flags, int startId) {
         // The service is starting, due to a call to <code>startService()</code>
         return <em>mStartMode</em>;
     }
     @Override
     public IBinder <code>onBind</code>(Intent intent) {
         // A client is binding to the service with <code>bindService()</code>
         return <em>mBinder</em>;
     }
     @Override
     public boolean <code>onUnbind</code>(Intent intent) {
         // All clients have unbound with <code>unbindService()</code>
         return <em>mAllowRebind</em>;
     }
     @Override
     public void <code>onRebind</code>(Intent intent) {
         // A client is binding to the service with <code>bindService()</code>,
         // after onUnbind() has already been called
     }
     @Override
     public void <code>onDestroy</code>() {
         // The service is no longer used and is being destroyed
     }
}

注意:和activity不同,并不是所有回调方法都要super超类的回调。

























  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值