欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android创建服务之started service详细介绍

程序员文章站 2023-11-08 14:53:16
创建started service        应用组件(例如activity)调用startservice...

创建started service

       应用组件(例如activity)调用startservice()来启动一个service,将需要的参数通过intent传给service,service将会在onstartcommand函数中获得intent。

有两种方式可以创建started service,一种是扩展service类,另外一种是扩展intentservice类

扩展service
       这是所有服务的基类。扩展这个类的时候,特别重要的一点是,需要创建一个新的线程来做服务任务,因为service默认是运行在你的主线程(ui线程)中的,它会使你的主线程运行缓慢。

扩展intentservice
       这是一个service的子类,他可以在一个工作线程中处理所有的启动请求。如果你不需要同一时刻出来所有的服务请求,使用intentservice是一个很好的选择。你需要做的仅仅是实现onhandlerintent()方法,通过这个函数处理接受的每一个启动请求。


下面我们学习如何扩展intentservice类和service类

扩展intentservice类

intentservice做了什么?
1.创建一个独立于主线程的工作线程,用于执行通过onstartcommand()传来的所有的intent。
2.创建一个工作队列,将接受的所有intent一个一个的传给onhandlerintent(),所以同一时间内你只处理一个intent,不用担心多线程的问题。
3.当所有的请求都处理完后,停止服务,所以你不需要手动调用stopself()。
4.提供onbind()函数的默认实现,返回null
5.提供onstartcommand()函数的默认实现,它把intent发送到工作队列中去,然后工作队列再发送到你的onhandlerintent()函数中。

有了上面的基础,你仅仅要做的就是实现onhandlerintent()。并且实现一个小小的构造函数。

参考下面的例子:

复制代码 代码如下:

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

如果你要实现其他的回调函数,例如 oncreate , onstartcommand ,ondestroy 一定记得调用父类相应的函数,这样intentservice才能正确的处理工作线程。

例如,你需要在onstartcommand函数中弹出一个提示,那么你可以这样写:

复制代码 代码如下:

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

有一点例外的是,如果你需要其他组件绑定服务,那么你的onbind函数不需要调用父类的onbind。

在下一节中,你将会看到通过扩展service类来实现与本节相同的服务,所不同的是代码会更多,但是同样意味着更灵活,特别是你需要同时处理多个请求时,比较适合直接扩展service。

扩展service类

如同你在上一节看到的一样,使用intentservice来实现一个started 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();
  }
}

如同你看到的,比intentservice的例子多了好多代码。

因为你自己处理每次的onstartcommand(),所以你可以在同一时间处理多个请求。这个例子没有这样做,但是如果你愿意,你完全可以每收到一个请求的时候,创建一个新线程并且立即运行,而不是等到上一个请求处理完成后再运行。

注意,onstartcommand()函数必须返回一个整数。这个整数描述了当系统杀死该服务时将要如何处理该服务。返回值必须是下面几个值:
start_not_sticky
在onstartcommand()返回后,系统杀死服务,服务将不会重启,除非有pending intents(目前笔者还不懂什么是pending intents)要投递。这比较适合非常容易就可以重新启动未完成任务的情况。

start_sticky
        在系统杀死服务后,重启服务并且调用onstartcommand(),但是不重新投递上一次的intent。系统会传给onstartcommand()函数null,除非有pending intent来启动服务,会传给onstartcommand()相应的intent。这种做法比较适合媒体播放服务,不需要执行命令,但是独立运行,常处于等待任务的服务。

start_redeliver_intent
在系统杀死服务后,重启服务并且调用onstartcommand(),参数传入上一次的intent,然后接下来是pending intent传入onstartcommand()。这比较适合于正在执行一项工作,它不能被立刻恢复,例如下载文件。

启动service

你可以从一个activity或者其他组件调用startservice(intent)来启动服务。android系统会调用服务的onstartcommand()函数并且传给它intent。

用上一节的helloservice来做个例子:
intent intent = new intent(this, helloservice.class);
startservice(intent);
startservice()会立刻返回,android系统会调用服务的onstartcommand()函数。如果这是服务没有在运行,系统会首先调用oncreate()函数,然后会紧接着调用onstartcommand()函数。

如果服务没有提供绑定,那么通过startservice()函数传入的intent就是应用组件和服务进行交互的唯一途径。如果你想服务发送结果给客户组件,那么客户组件需要在启动服务时,创建一个用于广播的pendingintent,通过intent投递给服务。这样,服务就可以利用广播把结果发送给客户组件。

多个请求会导致服务的onstartcommand()被调用多次。但是,只需要一个停止请求(stopself() 或 stopservice())就会使服务停止。

停止服务

一个启动的服务必须管理自己的生命周期。因为除非系统需要回收资源的时候,系统不会停止或者销毁服务。所以,服务必须通过调用stopself()来停止自己,或者有其他组件调用stopservice()。

一担收到stopself()或stopservice()的停止请求,系统会立刻销毁服务。

如果你的服务同时处理多个服务请求,当某个请求完成时就不能马上停止服务,因为你可能已经又接受了一个新的请求(在第一个请求完成时结束服务会终止第二个请求)。为了解决这个问题,你可以调用stopself(int)函数来保证你的停止请求总是基于最近的一次服务请求。这是因为,调用stopself(int)函数
会把onstartcommand()函数传入的startid传给停止请求。当你的startid和最近一次接受的服务请求不匹配时,服务不会结束。

注意:stopself(int)函数只是简单的与最近一次收到的startid比较,如果你的服务处理过程是多线程的,可能后收到的服务请求先完成,那stopself(int)的方案不适合你,你应该手动管理接受到的服务请求和完成的服务,比如在onstartcommand()函数中把startid记录到一个表中,在完成服务任务时在表中记录完成状态,在确保表中任务都完成的情况下直接调用stopself()来停止服务。

在前台运行服务

一个前台服务是用户能够很明显意识到的服务,当可用内存比较低的时候,系统不会杀掉前台服务。一个前台服务必须提供一个状态栏通知,放在正在运行条目里,这意味着只要服务没有停止或者一直是前台服务,这个通知就不会被消失。

举个例子,一个音乐播放器服务应该被设置为前台运行,因为用户非常明显知道他在运行。这个状态栏通知可以显示正在播放的歌曲,并且可以允许用户点击进入音乐播放界面。

要使服务运行在前台只要调用startforeground()。这个方法有两个参数:一个通知的整数id和一个状态栏通知。代码片段:

复制代码 代码如下:

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()函数可以把服务从前台移除。这个函数带一个布尔值参数,来表示是否从状态栏移除通知。这个函数不会使服务停止。如果把前台服务停止掉,那么通知也会同时被移除。