Service——基础详解

Service是一个可以在后台执行长时间运行的操作并且不提供用户界面的应用程序组件。如果应用程序组件开启一个了服务,即使用户切换到另一应用程序这个服务仍然在后台运行。此外,一个组件可以绑定一个Service与它进行交互,甚至进行进程间通信(IPC)服务。例如,一个服务可能处理网络交易,播放音乐,执行文件I / O,或与内容提供者进行交互,这些都是在后台运行的。

  • 启动方式

Service生命周期可以从两种启动Service的模式开始讲起,分别是context.startService()和context.bindService()。

(1).startService的启动模式下的生命周期:当我们首次使用startService启动一个服务时,系统会实例化一个Service实例,依次调用其onCreate和onStartCommand方法,然后进入运行状态,此后,如果再使用startService启动服务时,不再创建新的服务对象,系统会自动找到刚才创建的Service实例,调用其onStart方法;如果我们想要停掉一个服务,可使用stopService方法,此时onDestroy方法会被调用,需要注意的是,不管前面使用了多个次startService,只需一次stopService,即可停掉服务。

(2).bindService启动模式下的生命周期:在这种模式下,当调用者首次使用bindService绑定一个服务时,系统会实例化一个Service实例,并一次调用其onCreate方法和onBind方法,然后调用者就可以和服务进行交互了,此后,如果再次使用bindService绑定服务,系统不会创建新的Service实例,也不会再调用onBind方法;如果我们需要解除与这个服务的绑定,可使用unbindService方法,此时onUnbind方法和onDestroy方法会被调用。

两种模式有以下几点不同之处:startService模式下调用者与服务无必然联系,即使调用者结束了自己的生命周期,只要没有使用stopService方法停止这个服务,服务仍会运行;通常情况下,bindService模式下服务是与调用者生死与共的,在绑定结束之后,一旦调用者被销毁,服务也就立即终止,就像江湖上的一句话:不求同生,但愿同死。

当然,这两种方式不是互斥的,你可以同时用这两种方式,无论你用哪种方式启动,started、bind或是两种都用,任何程序组件都可以用通过Activity和Intent来启动Service。而且可以在manifest文件中设置其他组件对该Service的访问权限。

不过要注意的是:Service是运行在其宿主进程中的,它不能创建自己的线程,也无法在一个单独的进程中运行(除非另行指定)。这意味着,如果你的服务是打算做任何CPU密集型的工作或阻塞操作(例如MP3播放或网络I/O操作),你必须在该服务中创建一个新的线程来执行该工作。通过使用一个单独的线程,你会减少应用程序的不响应(ANR)错误,而且应用程序的主线程可以继续保持用户界面与用户的交互。

  • 声明Service

Service是在manifest中声明的,代码如下:

[html][/html]

view plaincopy在CODE上查看代码片派生到我的代码片

  1.   …
  2.   
  3.       
  4.       …
  5.   
  • 创建一个Service

通常创建一个Service要继承Service类或是IntentService类。先给大家介绍几个Service中的常用方法:

  • onStartCommand()
    系统在其它组件比如activity通过调用startService()请求service启动时调用这个方法.一旦这个方法执行,service就启动并且在后台长期运行.如果你实现了它,你需要负责在service完成任务时停止它,通过调用stopSelf()或stopService().(如果你只想提供绑定,你不需实现此方法).
  • OnBind()
    当组件调用bindService()想要绑定到service时(比如想要执行进程间通讯)系统调用此方法.在你的实现中,你必须提供一个返回一个IBinder来以使客户端能够使用它与service通讯,你必须总是实现这个方法,但是如果你不允许绑定,那么你应返回null.
  • OnCreate()
    系统在service第一次创建时执行此方法,来执行只运行一次的初始化工作(在调用它方法如onStartCommand()或onBind()之前).如果service已经运行,这个方法不会被调用.
  • OnDestroy()
    系统在service不再被使用并要销毁时调用此方法.你的service应在此方法中释放资源,比如线程,已注册的侦听器,接收器等等.这是service收到的最后一个调用.

    继承Service创建Service的代码例子如下:

    [java][/java]

    view plaincopy在CODE上查看代码片派生到我的代码片

    1. public class HelloService extends Service {
    2.   private Looper mServiceLooper;
    3.   private ServiceHandler mServiceHandler;
    4.   // Handler that receives messages from the thread
    5.   private final class ServiceHandler extends Handler {
    6.       public ServiceHandler(Looper looper) {
    7.           super(looper);
    8.       }
    9.       @Override
    10.       public void handleMessage(Message msg) {
    11.           // Normally we would do some work here, like download a file.
    12.           // For our sample, we just sleep for 5 seconds.
    13.           long endTime = System.currentTimeMillis() + 5*1000;
    14.           while (System.currentTimeMillis() < endTime) {
    15.               synchronized (this) {
    16.                   try {
    17.                       wait(endTime – System.currentTimeMillis());
    18.                   } catch (Exception e) {
    19.                   }
    20.               }
    21.           }
    22.           // Stop the service using the startId, so that we don’t stop
    23.           // the service in the middle of handling another job
    24.           stopSelf(msg.arg1);
    25.       }
    26.   }
    27.   @Override
    28.   public void onCreate() {
    29.     // Start up the thread running the service.  Note that we create a
    30.     // separate thread because the service normally runs in the process’s
    31.     // main thread, which we don’t want to block.  We also make it
    32.     // background priority so CPU-intensive work will not disrupt our UI.
    33.     HandlerThread thread = new HandlerThread(“ServiceStartArguments”,
    34.             Process.THREAD_PRIORITY_BACKGROUND);
    35.     thread.start();
    36.     // Get the HandlerThread’s Looper and use it for our Handler
    37.     mServiceLooper = thread.getLooper();
    38.     mServiceHandler = new ServiceHandler(mServiceLooper);
    39.   }
    40.   @Override
    41.   public int onStartCommand(Intent intent, int flags, int startId) {
    42.       Toast.makeText(this, “service starting”, Toast.LENGTH_SHORT).show();
    43.       // For each start request, send a message to start a job and deliver the
    44.       // start ID so we know which request we’re stopping when we finish the job
    45.       Message msg = mServiceHandler.obtainMessage();
    46.       msg.arg1 = startId;
    47.       mServiceHandler.sendMessage(msg);
    48.       // If we get killed, after returning from here, restart
    49.       return START_STICKY;
    50.   }
    51.   @Override
    52.   public IBinder onBind(Intent intent) {
    53.       // We don’t provide binding, so return null
    54.       return null;
    55.   }
    56.   @Override
    57.   public void onDestroy() {
    58.     Toast.makeText(this, “service done”, Toast.LENGTH_SHORT).show();
    59.   }
    60. }

    上面例子是在Service中建立子线程来执行耗时操作,代码是在官方文档中截取的,相信大家的英语水平,这些简单的英文注释应该能看懂吧。

IntentService

这是一个Service的子类,使用一个工作线程来处理所有的启动请求,一次处理一个.这是你不需你的service同时处理多个请求时的最好选择.你所有要做的就是实现onHandleIntent(),这个方法接收每次启动请求发来的intent,于是你可以做后台的工作.

因为大多数"启动的"service不需要同时处理多个请求,可能从IntentService实现你的service是最好的选择.

IntentService做了以下工作:

  • 创建一个默认的工作线程在主线程之外执行所有派发到onStartCommand()的intent.
  • 创建一个工作队列,某个时间只传递一个intent到你的onHandleIntent()实现中,于是你不必担心多线程的问题.
  • 当所有开始的请求都处理后,停止service,所以你永远不需调用stopSelf().
  • 提供onBind()的默认实现,返回null.
  • 提供一个onStartCommand()的默认实现,把intent加入到工作队列之后会传给你的onHandleIntent()实现.

以上实现使得你可以仅仅实现onHandleIntent()来做要做的工作即可.(当然,你还是要实现一个小小的构造函数).

继承Service创建Service的代码例子如下:

 
[java][/java]

view plaincopy在CODE上查看代码片派生到我的代码片

  1. public class HelloIntentService extends IntentService {
  2.   /**
  3.    * A constructor is required, and must call the super IntentService(String)
  4.    * constructor with a name for the worker thread.
  5.    */
  6.   public HelloIntentService() {
  7.       super(“HelloIntentService”);
  8.   }
  9.   /**
  10.    * The IntentService calls this method from the default worker thread with
  11.    * the intent that started the service. When this method returns, IntentService
  12.    * stops the service, as appropriate.
  13.    */
  14.   @Override
  15.   protected void onHandleIntent(Intent intent) {
  16.       // Normally we would do some work here, like download a file.
  17.       // For our sample, we just sleep for 5 seconds.
  18.       long endTime = System.currentTimeMillis() + 5*1000;
  19.       while (System.currentTimeMillis() < endTime) {
  20.           synchronized (this) {
  21.               try {
  22.                   wait(endTime – System.currentTimeMillis());
  23.               } catch (Exception e) {
  24.               }
  25.           }
  26.       }
  27.   }
  28. }

  •  启动一个Service
  • startService

本地服务可由Context.startService()启动,启动后这些服务将持续运行,直到客户端调用Context.stopService()或服务自己调用stopSelf()。

注意:如果调用Context.startService()时还未创建服务,系统将实例化服务并调用服务的onStartCommand()方法。如果在调用Context.startService()时服务已经启动,那么不会再创建一个实例,而是重新调用正在运行的服务的onStartCommand()方法。

Demo:我们在MainActivity中新建两个两个Button,一个用于启动服务,另外一个用于停止服务。建立一个MyService类继承于Service,当收到服务的时候在通知栏弹出通知,一直到我们的服务退出才清除通知,同时收到启动服务的消息时我们建立一个线程sleep 10秒。当我们退出MainActivity的时候停止服务,同时清除通知栏的通知。

MainActivity.xml:就两个Button用于启动和停止服务。

[html][/html]

view plaincopy在CODE上查看代码片派生到我的代码片

  1. <
  2. RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  3.     xmlns:tools=”http://schemas.android.com/tools”
  4.     android:layout_width=”match_parent”
  5.     android:layout_height=”match_parent”
  6.     android:paddingBottom=”@dimen/activity_vertical_margin”
  7.     android:paddingLeft=”@dimen/activity_horizontal_margin”
  8.     android:paddingRight=”@dimen/activity_horizontal_margin”
  9.     android:paddingTop=”@dimen/activity_vertical_margin”
  10.     tools:context=”.MainActivity” >
  11.     <Button
  12.         android:id=”@+id/btnStart”
  13.         android:layout_width=”wrap_content”
  14.         android:layout_height=”wrap_content”
  15.         android:text=”startService” >
  16.     </Button>
  17.     <Button
  18.         android:id=”@+id/btnStop”
  19.         android:layout_width=”wrap_content”
  20.         android:layout_height=”wrap_content”
  21.         android:text=”stopService”
  22.         android:layout_below=”@id/btnStart”>
  23.     </Button>
  24. </RelativeLayout>
然后是MainActivity,用于响应Button的点击事件,同时启动Service。
[html][/html]

view plaincopy在CODE上查看代码片派生到我的代码片

  1. public class MainActivity extends Activity implements OnClickListener{
  2.     private static final String TAG = “MainActivity”;
  3.     private int counter = 1;
  4.     private Button btnStart, btnStop;
  5.     @Override
  6.     protected void onCreate(Bundle savedInstanceState) {
  7.         super.onCreate(savedInstanceState);
  8.         setContentView(R.layout.activity_main);
  9.         btnStart = (Button)this.findViewById(R.id.btnStart);
  10.         btnStop = (Button)this.findViewById(R.id.btnStop);
  11.         btnStart.setOnClickListener(this);
  12.         btnStop.setOnClickListener(this);
  13.     }
  14.     @Override
  15.     public void onClick(View v) {
  16.         // TODO Auto-generated method stub
  17.         Log.v(TAG, “id:”+v.getId() + “btn:”+R.id.btnStart);
  18.         switch (v.getId()) {
  19.         case R.id.btnStart:
  20.             Log.v(TAG, “Starting Service…counter=” + counter);
  21.             Intent intent = new Intent(MainActivity.this, MyService.class);
  22.             intent.putExtra(“counter”, counter);
  23.             startService(intent);
  24.             break;
  25.         case R.id.btnStop:
  26.             Log.v(TAG, “Stopping Service…”);
  27.             if( stopService(new Intent(MainActivity.this, MyService.class)) ) {
  28.                 Log.v(TAG, “stopService successful”);
  29.             } else {
  30.                 Log.v(TAG, “stopService failed”);
  31.             }
  32.             break;
  33.         default:
  34.             break;
  35.         }
  36.     }
  37.     @Override
  38.     protected void onDestroy() {
  39.         // TODO Auto-generated method stub
  40.         stopService(new Intent(MainActivity.this, MyService.class));
  41.         super.onDestroy();
  42.     }
  43. }

  44. 
    
  45. 最后是我们的MyService,当收到服务的时候,在通知栏弹出通知,并启动一个sleep 10秒的线程。

  46. public class MyService extends Service {
  47.     private static final String TAG = “MyService”;
  48.     private NotificationManager notificationMgr;
  49.     private ThreadGroup threadGroup = new ThreadGroup(“ServiceWorkder”);
  50.     @Override
  51.     public void onCreate() {
  52.         // TODO Auto-generated method stub
  53.         super.onCreate();
  54.         Log.v(TAG, “in onCreate”);
  55.         notificationMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
  56.         Notification notification = new Notification(R.drawable.ic_launcher, “Service is running”, System.currentTimeMillis());
  57.         notification.flags = Notification.FLAG_NO_CLEAR;
  58.         PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
  59.         notification.setLatestEventInfo(this, TAG, “Service is running”, intent);
  60.         notificationMgr.notify(0, notification);
  61.     }
  62.     @Override
  63.     public int onStartCommand(Intent intent, int flags, int startId) {
  64.         // TODO Auto-generated method stub
  65.         super.onStartCommand(intent, flags, startId);
  66.         int counter = intent.getExtras().getInt(“counter”);
  67.         Log.v(TAG, “in onStartCommand, counter = “+counter+”,startId = “+startId);
  68.         new Thread(threadGroup, new ServiceWorker(counter)).start();
  69.         return START_STICKY;
  70.     }
  71.     class ServiceWorker implements Runnable {
  72.         private int counter = -1;
  73.         public ServiceWorker(int counter) {
  74.             this.counter = counter;
  75.         }
  76.         public void run() {
  77.             final String TAG = “ServiceWorker” + Thread.currentThread().getId();
  78.             try {
  79.                 Log.v(TAG, “Sleeping for 10 seconds.counter=”+counter);
  80.                 Thread.sleep(10000);
  81.                 Log.v(TAG, “…waking up”);
  82.             } catch (Exception e) {
  83.                 // TODO: handle exception
  84.                 Log.v(TAG, “…sleep interrupt”);
  85.             }
  86.         }
  87.     }
  88.     @Override
  89.     public void onDestroy() {
  90.         // TODO Auto-generated method stub
  91.         Log.v(TAG, “in onDestroy. Interrupt threads and canceling notifications”);
  92.         threadGroup.interrupt();
  93.         notificationMgr.cancelAll();
  94.         super.onDestroy();
  95.     }
  96.     @Override
  97.     public IBinder onBind(Intent intent) {
  98.         // TODO Auto-generated method stub
  99.         return null;
  100.     }
  101. }
  102. 最后不要忘了在AndroidManifest.xml中声明我们的Service:    

  103.     
  104.    

  105.     

点击startService按钮后通知栏运行效果如下:

 我们点击stopService按钮后看Log日志,Service从创建到销毁的流程都在这里:

注意:我们第一次点startService按钮后日志是如下:

当我们再点击一次startService后,日志如下:

我们看到MyService没有再次调用onCreate,因为服务已经创建了,并且startId=2,说明两次进入的是同一个服务。

  • bindService

本地服务也可由Context.bindService()启动,这样调用者和服务绑在一起,调用者一旦退出,服务也就终止了。客户端建立一个与Service连接,使用此连接与Service通信,通过Context.bindService()绑定服务,使用Context.unbindService()关闭服务。多个客户端可以绑定同一个服务,如果Service未启动,bindService()可以启动服务。

注意:上面的startService()和bindService()是完全独立的两种模式,你可以绑定一个已经通过startService()启动的服务。例如:一个后台播放音乐的服务可以通过startService()启动播放,然后activity可以通过调用bindService()方法建立于Service的联系,执行切换歌曲等操作。这种情况下:stopService()不会停止服务,直到最后一个unbindService()调用。

当创建一个能够提供绑定功能的服务时,我们必须提供一个IBinder对象,客户端能够使用这个对象与服务通信,Android中有三种方式:

(1)扩展Binder类

一般用于服务和Activity属于同一个进程的情况。类似上面的startService()我们在MainActivity中建立两个Button,一个用于bindService,另外一个unbindService()。在获得Service的IBinder接口之后就可以调用Service的内部方法了。

[java][/java]

view plaincopy在CODE上查看代码片派生到我的代码片

  1. public class MainActivity extends Activity implements OnClickListener{
  2.     private static final String TAG = “MainActivity”;
  3.     private int counter = 1;
  4.     private boolean isBindFlag = false;
  5.     private Button btnStart, btnStop,btnBind,btnUnbind;
  6.     @Override
  7.     protected void onCreate(Bundle savedInstanceState) {
  8.         super.onCreate(savedInstanceState);
  9.         setContentView(R.layout.activity_main);
  10.         btnStart = (Button)this.findViewById(R.id.btnStart);
  11.         btnStop = (Button)this.findViewById(R.id.btnStop);
  12.         btnBind = (Button)this.findViewById(R.id.btnBind);
  13.         btnUnbind = (Button)this.findViewById(R.id.btnUnbind);
  14.         btnStart.setOnClickListener(this);
  15.         btnStop.setOnClickListener(this);
  16.         btnBind.setOnClickListener(this);
  17.         btnUnbind.setOnClickListener(this);
  18.     }
  19.     @Override
  20.     public void onClick(View v) {
  21.         // TODO Auto-generated method stub
  22.         Log.i(TAG, “id:”+v.getId() + “btn:”+R.id.btnStart);
  23.         switch (v.getId()) {
  24.         case R.id.btnStart:
  25.             Log.i(TAG, “Starting Service…counter=” + counter);
  26.             Intent intent = new Intent(MainActivity.this, MyService.class);
  27.             intent.putExtra(“counter”, counter);
  28.             startService(intent);
  29.             break;
  30.         case R.id.btnStop:
  31.             Log.i(TAG, “Stopping Service…”);
  32.             if( stopService(new Intent(MainActivity.this, MyService.class)) ) {
  33.                 Log.i(TAG, “stopService successful”);
  34.             } else {
  35.                 Log.i(TAG, “stopService failed”);
  36.             }
  37.             break;
  38.         case R.id.btnBind:
  39.             Intent intent2 = new Intent(MainActivity.this, MyBindService.class);
  40.             bindService(intent2, serviceConnection, Context.BIND_AUTO_CREATE);
  41.             break;
  42.         case R.id.btnUnbind:
  43.             unbindService(serviceConnection);
  44.             break;
  45.         default:
  46.             break;
  47.         }
  48.     }
  49.     private ServiceConnection serviceConnection = new ServiceConnection() {
  50.         @Override
  51.         public void onServiceDisconnected(ComponentName name) {
  52.             // TODO Auto-generated method stub
  53.             isBindFlag = false;
  54.         }
  55.         @Override
  56.         public void onServiceConnected(ComponentName name, IBinder service) {
  57.             // TODO Auto-generated method stub
  58.             MyBindService.MyBinder binder = (MyBinder)service;
  59.             MyBindService bndService = binder.getService();
  60.             bndService.myMethod();
  61.             isBindFlag = true;
  62.         }
  63.     };
  64.     @Override
  65.     protected void onDestroy() {
  66.         // TODO Auto-generated method stub
  67.         if(isBindFlag == true) {
  68.             unbindService(serviceConnection);
  69.         }
  70.         super.onDestroy();
  71.     }

这里当绑定到MyBindService之后,就可以通过bndService实例调用其方法myMethod()了。下面MyBindService比较简单就是继承于Service,并实现其onBind()接口,返回一个MyBinder实例,客户端拿到这个MyBinder之后可以通过它获取到MyBindService实例,然后调用其提供的myMethod()方法了。

[java][/java]

view plaincopy在CODE上查看代码片派生到我的代码片

  1.  public class MyBindService extends Service {
  2.     private static final String TAG = “MyBindService”;
  3.     public void myMethod() {
  4.         Log.i(TAG, “myBindService->myMethod()”);
  5.     }
  6.     @Override
  7.     public IBinder onBind(Intent intent) {
  8.         // TODO Auto-generated method stub
  9.         return myBinder;
  10.     }
  11.     public class MyBinder extends Binder {
  12.         public MyBindService getService() {
  13.             return MyBindService.this;
  14.         }
  15.     }
  16.     private MyBinder myBinder = new MyBinder();
  17. }
最后不要忘了在AndroidManifest.xml中声明我们的Service:

[html][/html]

view plaincopy在CODE上查看代码片派生到我的代码片

  1. <service android:name=”.MyBindService” >
  2. </service>
我们看一下Log截图:

本文程序源码在这里:http://download.csdn.net/detail/avatarliyu/6760675。同理最后我们也需要在AndroidManifest.xml中声明我们的服务。关于Service的生命周期问题,请关注另一篇文章:Service——生命周期。

Android支持两种类型的服务:

本地服务:

本地服务只能由承载该服务的应用程序访问,无法供在设备上运行的其他应用程序访问。客户端调用Context.startService()启动该服务。我们在上面介绍的代码都是这种服务。

远程服务:

远程服务除了可从承载服务的应用程序访问,还可以从其他应用程序访问。远程服务使用AIDL向客户端定义。服务支持onBind()方法,客户端通过Context.bindService()进行调用。

想了解这种服务的朋友们可以看我的另一篇文章:Service——AIDL。

标签