【Android官方文档】翻译Android官方文档-Services(二)

来源:互联网 发布:windows xp字体大小 编辑:程序博客网 时间:2024/05/24 15:41

Service是运行在后台的组件,它不能直接给用户提供服务。其他组件可以启动Service,即使是切换至别的应用,Service仍然可以在后台运行,组件可以与Service绑定并且与之交互,甚至可以跨进程通信(IPC)。如,Service可以在后台进行网络请求,播放音乐,或者进行文件读写操作,又或者和Content Provider交互等等。

这篇博客将介绍Service的定义,创建,启动,绑定等内容。

Service的两种方式启动:
Started:组件通过调用startService()方法启动Service,启动后,Service会在后台一直运行,就算是唤起Service的启动者被销毁,Service也依然可以在后台运行。一般情况下,一个被start的Service会在后台执行单独的操作,也并不会给启动者返回结果。例如:一个start的Service在后台执行下载或者是上传文件的操作,完成之后,Service应当自己停止。
Bound:其他组件调用bindService()方法绑定一个Service。绑定启动的Service是Service-Client的结构,也就是说Service可以与多个组件进行交互。一个bound Service只在有组件绑定时才会运行,无组件绑定时,服务被销毁。

Service也可以同时以上面两种方式下启动。这里涉及到serivice中的两个方法:onStartCommand(),onBind()

无论是哪种方式启动Service(单独start , 单独 bind , 同时 bind 和 start),任何组件都可以唤起 Service,通过Intent对象传递参数,不过,我们也可以将Service在配置文件(manifest.xml)中配置为私有,这样不允许其他应用访问它。

注:Service运行在主线程中,所以说Service不是个新的线程,也不是新的进程。也就是说,如果我们需要在Service中执行比较耗时的操作时(如音乐播放,网络请求等等),这就需要在Service中另外创建一个新的线程。这么做可以防止异常,保证主线程的流畅操作。

那么Service和Thread处理,哪个更优呢?

Service 是运行在后台的组件,并不能直接与用户交互。我们只需要在需要用上的时候才创建Service.

当用户与UI进行交互时,如果要执行一个主线程无法完成的操作时,就应该创建一个线程。如:当Activity在运行,而我们需要播放音乐,这时候就应该创建线程。最后在Activity消耗时,别忘了销毁这个线程。我们也可以选择AsyncTask或者是HandlerThread来替代Thread。


Service基础内容
我们为了创建新的Service,就要继承Service。并且重写一些方法。这些方法就代表了Service的生命周期,并且这些方法中提供了绑定Service的机制。如下:

  • onStartCommand(): 当其他组件调用StartService()唤起Service时,这个方法会被调用。只要Service启动,Service便会在后台一直独立的运行。在Service执行完后,应该调用stopSelf()或者是stopService()去销毁Service。
  • onBind(): 当其他组件调用bindService()绑定Service时,这个方法会被调用,并且会返回一个IBinder接口,IBinder接口是Service与其绑定者进行交互的桥梁。如果Service没有绑定者,则返回null。
  • onCreate(): 在Service首次创建时,启动这个方法。方法只被触发一次, 并且是在上述两种方法启动前被调用。如果Service已经在运行,那么该方法不会执行。
  • onDestory() : 在service 消耗时调用,在消耗的方法中,我们应当要释放之前占用的资源。比如:停止线程。解除绑定的监听器或者是广播接收器等等。这个方法是Service的最后一个调用方法。

如果有组件调用startService()启动Service(那么系统会调用onStartCommand()方法),直到调用stopSelf()或者是调用stopService()方法,Service才会被停止。
如果有组件调用bindService()绑定了Service(这里系统并不会调用onStartCommand()方法),只要有组件绑定着Service,那么这个Service就会一直运行,当无组件绑定Service时,Service会被消耗。

在系统内存不够时,系统将强制销毁Service。如果Service绑定了Activity,而这个Activity正在和用户交互,那么Service将有可能不会被系统清理掉。假如创建的是前台Service,那么Service几乎不会被杀死。当创建的Service运行了很长时间后,那么系统将可能会降低它在栈中的级别–从而使得它更容易被销毁掉。因此,在做Service时,应当让Service时常被restart,那么这样就可以保持Service的级别。


注册Service
在manifest.xml文件中:

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

另外,在标签中还可以配置别的属性,例如:需要启动Service所需要的权限,该Service应在哪个进程中运行等等。这里 android:name是不能缺少的,这是指定Service的类名。一旦发布了应用,类名将不可更改。

注:为了保证应用的安全性,推荐使用显式的Intent启动或者绑定Service,不要在中配置 intent-filter。

将android:exported设置为false时,表示不允许别的应用启动本应用的组件,即使是显示的Intent也是不可以的。


继承IntentService
在很多情况下,启动Service 并不会去同时处理多个请求,因为处理多个线程是非常危险的,这个时候选择继承IntentService就是不错的解决方案。
使用IntentService要注意以下几点:

  • 默认在子线程中处理回传到onStartCommand()方法中的Intent;
  • 在OnHanleIntent()中处理按时间排序的Intent队列,因此不必担心多线程问题。
  • 当请求处理后,停止Service,无需手动调用stopSelf()
  • 默认是实现了onBind()方法,返回为空、
  • 默认是实现了onStartCommand方法,并且将回传的Intent以序列的形式发送给onHandleIntent(),只需要重写该方法并且处理Intent就可以了。

综上,我们只需要重写OnhandleIntent()就可以了,不过,还少不了一个构造方法,如下:

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

如果你想在IntnetService的继承类中重写其他生命周期方法,如:onCreate(),onstartCommand(),onDestroy(),那么先调用各自父类方法来保证子线程能够正常启动。
比如,要实现onStartCommand()方法,需要返回其父类方法,如:

@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

如果你需要在Service中执行多线程而不是处理一个请求队列,那么就需要继承Service类,分别处理每个Intent。

在Service进行操作时,每个请求都应当开启一个线程,并且在同一时刻,一个线程只处理一个请求。如:

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

注意OnStartCommand返回一个整形变量,该变量必须是下列常量之一:

  • START_NOT_STICKY:如果执行完了onStartCommand后,系统就会消耗Service,不用再重新创建Service,除非系统回传了一恶搞pending intent。这样可以避免在不必要的时候运行Service。

  • START_STICKY:如果系统在onStartCommand()执行并返回后kill了service,那么service会被recreate并回调onStartCommand()。dangerous不要重新传递最后一个Intent(do not redeliver the last intent)。相反,系统回调onStartCommand()时会回传一个空的Intent,除非有 pending intents传递,否则Intent将为null。该模式适合做一些类似播放音乐的操作。

  • START_REDELIVER_INTENT:如果系统在onStartCommand()执行并返回后kill了service,那么service会被recreate并回调onStartCommand()并将最后一个Intent回传至该方法。任何 pending intents都会被轮流传递。该模式适合做一些类似下载文件的操作。


启动Service
若需要启动Service,如:

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

startService(intent)方法将会立即返回,并且回调onStartCommand()(请不要手动调用该方法),如果该Service未处于运行状态,那么系统将会首先回调onCreate(),接着再回调onStartCommand()。若希望Service可以返回结果,那么需要通过调用getBroadcast返回的PendingIntent启动Service(将PendingIntent包装为Intent),service可使用broadcast 传递结果。

多个启动Service的请求可能导致onStartCommand()多次调用,但只需调用stopSelf() 、 stopService()这两个方法之一,就可停止该服务。


停止Service
一个启动的Service必须管理自己的生命周期。系统不会主动stop或destroy一个正在运行的Service,除非系统内存紧张,不然的话,执行完onStartCommand()方法后,Service会一直运行。停止Service必须手动调用stopSelf()(在Service中)或调用stopService()(在启动组件中)。

一旦调用了上述两种方法之一,系统会尽快destroy该Service。

若系统正在处理多个调用onStartCommand()请求,那么在启动一个请求时,您不应当在此时停止该Service。为了避免这个问题,可以调用stopSelf()方法,以确保请求停止的Service时最新的启动请求。当调用stopSelf()方法时,传入的ID表示启动请求(该ID会传递至onStartCommand()),该ID与请求停止的ID一致。则如果在调用stopSelf()之前,Service收到一个新的Start请求,ID将无法匹配,Service并不会停止。

为了节省内存和电量,当Service完成其工作后将其stop很有必要。如有必要,可以在其他组件中调用stopService()方法,即便Service处于绑定状态,只要它回调过onStartCommand(),也应当主动停止该Service。


绑定Service
通过其他组件调用bindService()方法可以绑定一个Service以保持长连接,这时一般不允许其他组件调用startService()启动Service。

当其他组件需要与Service交互或者需要跨进程通信时,可以创建一个bound Service。

为创建一个bound Service,必须重写onbind回调,该方法返回一个IBinder接口。该接口时组件与Service通信的桥梁。组件调用bindService()与Service绑定,该组件可获取IBinder接口,一旦获取该接口,就可以调用Service中的方法。一旦没有组件与Service绑定,系统将destroy它,我们不必手动停止它。

为创建一个bound Service,必须定义一个接口 ,该接口指定组件与Service如何通信。定义的接口在组件与Service之间,且必须实现IBinder接口。这正是onBind()的返回值。一旦组件接收了IBinder,组件与Service便可以开始通信。

多个组件可同时与Service绑定,当组件与Service交互结束后,可调用unbindService()方法解绑。bound Service比start Service要复杂,故我将在后续单独翻译。


发通知
运行中的Service可以通过Toast Notifications 或 Status Bar Notifications 向用户发送通知。Toast是一个可以短时间弹出的提醒框。Status Bar是顶部状态栏中出现的太有图标的信息,用户可以通过下拉状态栏获得具体信息并执行某些操作(如启动Activity)。

通常,Status Bar用于通知某些操作已经完成,如下载文件完成。当用户下拉状态栏后,点击该通知,可获取详细内容,如查看该下载的文件。


前台Service
前台Service用于动态通知消息,如天气预报。该Service不易被kill。前台Service必须提供status bar,只有前台Service被destroy后,status bar才能消失。

举例来说,一个播放音乐的Service必须是前台Service,只有这样用户才能确知其运行状态。为前台Service提供的status bar可以显示当前音乐的播放状态,并可以启动播放音乐的Activity。

调用startForeground()可以启动前台Service。该方法接收两个参数,参数一是一个int型变量,用户指定该通知的唯一性标识,而参数而是一个Notification用于配置status bar,示例如下:

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

调用stopForeground()来移除(remove)前台Service。该方法需传入一个boolean型变量,表示是否也一并清除status bar上的notification(indicating whether to remove the status bar notification as well)。该方法并不停止Service,如果停止正在前台运行的Service,那么notification 也一并被清除。


管理Service的生命周期
从Service的启动到销毁,有两种流程:
A started service:需手动停止
A bound service:可自动停止

如下图所示 :
这里写图片描述
这两条流程并不是毫无关系的:当调用startService()启动一个Service后,我们仍可以bind该Service。例如,当播放音乐时,需调用startService()启动指定播放的音乐,当需要获取该音乐的播放进度时,又需要调用bindService(),在这种情况下,知道Service被unbind ,调用stopService() 或stopSelf()都不能停止该Service。


以上是Android官方文档-Service的翻译,翻译了个大概,部分参考了别人的解读方式。
附:Android官方文档-Service

0 0
原创粉丝点击