Android应用程序的四大组件分别是Activity、Service、BroadcastReceiver和ContentProvider。其中,有关Activity的介绍可以参阅博文《》。有关ContentProvider的使用方法可以参阅博文《》
本文将主要对Service进行介绍。
1.Service简介
与Activity不同,Service没有提供与用户进行交互的用户界面。Service是运行在后台的一种Android组件,当应用程序需要进行某种不需要前台显示的计算或数据处理时,就可以启动一个Service来实现。
使用Service的目的通常有两个:后台运行和跨进程访问。通过启动一个Service,可以在不显示界面的前提下在后台运行指定的任务,这样可以不影响用户进行界面操作。通过AIDL(Android Interface Definition Language)服务实现不同进程之间的通信。
1.1Service的生命周期
Service一般由Activity或其他的Context对象来启动,启动方式有两种,对应的生命周期也不相同,具体如图1所示。
图1 Service生命周期示意图
由图1可以看出,通过startService()方法和通过bindService()方法启动Service是不一样的,下面就具体说说这两种方式的区别。
1.2通过startService()方法启动Service
一个Service从启动到销毁会经历3个阶段,分别是启动服务、服务执行和销毁服务。当Service经历上述3个阶段时,会分别调用Service类中的3个相应的事件方法:
(1)public void onCreate();
(2)public int onStartCommand(Intent intent, int flags, int startId);
(3)public void onDestroy();
其中,onCreate()方法用于创建Service;onStartCommand()方法用于开始Service;onDestroy()方法用于销毁服务。
需要注意的一点是,一个Service只会被创建一次,销毁一次,但可以开始多次,也就是说,onCreate()方法和onDestroy()只会被调用一次,而onStartCommand()方法却可以被多次调用。
下面就来简单的验证一下。
首先,我们需要自定义一个Service,让其继承android.app.Service类,并实现其中的onCreate()、onStartCommand()和onDestroy()方法。这里,我创建了一个名为“MyService”的Service类,具体代码如下:
1 /* 2 * 自定义的Service类 3 * 博客园-依旧淡然 4 */ 5 public class MyService extends Service { 6 7 private static final String TAG = "MyService"; 8 9 @Override10 public void onCreate() {11 super.onCreate();12 Log.i(TAG, "MyService-->onCreate()");13 }14 15 @Override16 public int onStartCommand(Intent intent, int flags, int startId) {17 Log.i(TAG, "MyService-->onStartCommand()");18 return super.onStartCommand(intent, flags, startId);19 }20 21 @Override22 public IBinder onBind(Intent intent) {23 return null;24 }25 26 @Override27 public void onDestroy() {28 super.onDestroy();29 Log.i(TAG, "MyService-->onDestroy()");30 }31 }
其中,onBind()方法是android.app.Service类的抽象方法,所以必须在子类MyService中实现。使用bindService()方法启动Service时,需要用到该方法,这里暂时未用到,所以直接返回null即可。
实现了自定义的MyService之后,我们便可以在Context对象(比如Activity)中通过调用startService(Intent intent)方法来启动Service,或是通过调用stopService(Intent intent)方法来终止Service。其中,startService()方法和stopService()方法中的参数intent可以用来指定想要启动和终止Service是哪一个。如下的代码,实现了通过Button按钮button_startService启动MyService,通过Button按钮button_stopService停止Service。
1 /* 2 * 实现事件监听器接口 3 * 博客园-依旧淡然 4 */ 5 private OnClickListener clickListenter = new OnClickListener() { 6 public void onClick(View view) { 7 switch(view.getId()) { 8 case R.id.button_startService: //启动Service 9 Intent intent_startService = new Intent(MainActivity.this, MyService.class);10 startService(intent_startService);11 break;12 case R.id.button_stopService: //停止Service13 Intent intent_stopService = new Intent(MainActivity.this, MyService.class);14 stopService(intent_stopService);15 break;16 }17 }18 };
最后,我们还需要在AndroidManifest.xml文件中注册我们自定义的MyService组件,即在<application></application>标签中添加如下代码即可:
1 24
至此,我们便完成了MyService的创建。
运行程序,点击Button按钮button_startService启动MyService,可以看到“MyService-->onCreate()”以及“MyService-->onStartCommand()”的Log输出信息,表明MyService已经启动。同时,我们也可以点击Menu,退出MainActivity,并依次选择Settings/Applications/Running Services,可以看到如图2所示的界面。
图2 MyService运行在后台
由图2可以看出,即使退出了MainActivity(MyService的启动者),MyService也不会停止,它仍然会在后台运行。
当我们再次运行程序,并再次点击Button按钮button_startService启动MyService时,可以看到“MyService-->onStartCommand()”的Log输出信息,表明当Service已经启动时,如果再次调用startService()方法,将只会调用Service类的onStartCommand()方法。
最后,当我们Button按钮button_stopService时,可以看到“MyService-->onDestroy()”的Log输出信息,表明MyService调用了onDestroy()方法停止了Service。
1.3通过bindService()方法启动Service
可以看到,通过startService()方式启动Service时,即使启动该Service的Context对象(比如Activity)关闭了,Service仍然会一直运行在后台(如果没有调用stopService()方法的话)。有时我们希望当启动Service的Context对象关闭时,Service也随之关闭,这时我们就可以通过使用bindService()方法来启动Service。通过bindService()方法启动Service可以将Activity和Service绑定。
下面同样以一个简单的实例来说明如何创建一个Service,并通过bindService()方法来启动该Service。
首先,我们需要自定义一个Service,让其继承android.app.Service类,并实现其中的onBind()、onRebind()和onUnbind()方法。这里,我创建了一个名为“BinderService”的Service类,具体代码如下:
1 /* 2 * 自定义的Service类 3 * 博客园-依旧淡然 4 */ 5 public class BinderService extends Service { 6 7 private MyBinder myBinder = new MyBinder(); //MyBinder对象,用于获得BinderService对象 8 private static final String TAG = "BinderService"; 9 10 //成功绑定时调用该方法11 public IBinder onBind(Intent intent) {12 Log.i(TAG, "-->onBind()");13 return myBinder;14 }15 16 //重新绑定时调用该方法17 public void onRebind(Intent intent) {18 super.onRebind(intent);19 Log.i(TAG, "-->onRebind()");20 }21 22 //解除绑定时调用该方法23 public boolean onUnbind(Intent intent) {24 Log.i(TAG, "-->onUnbind()");25 return super.onUnbind(intent);26 }27 28 /*29 * MyBinder内部类,扩展自Binder类30 */31 public class MyBinder extends Binder {32 33 //获取BinderService对象34 public BinderService getBinderService() {35 return BinderService.this;36 }37 38 }39 40 }
其中,当BinderService成功绑定时会调用onBind()方法;当BinderService重新绑定时会调用onRebind()方法;当BinderService解除绑定时会调用onUnbind()方法。MyBinder内部类继承自Binder类,并实现了getBinderService()方法,用于获取当前BinderService对象。
实现了自定义的BinderService之后,我们便可以在Context对象(比如Activity)中通过调用bindService(Intent service, ServiceConnection conn, int flags)方法启动Service,或是通过调用unbindService(ServiceConnection conn)方法来终止Service。
在bindService()方法中,第一个参数service表示与Service类相关联的Intent对象;第二个参数conn是一个ServiceConnection接口对象,负责连接Intent对象指定的Service,通过ServiceConnection对象可以获得连接Service成功或者失败的状态;第三个参数flags,一般设置为Context.BIND_AUTO_CREATE。
创建ServiceConnection接口对象时,需要实现ServiceConnection接口中的抽象方法onServiceConnected()和onServiceDisconnected()。其中,onServiceConnected()方法在连接Service成功时会被调用,onServiceDisconnected()方法在连接Service失败时会被调用。如下的代码实现了这两个方法。
1 /* 2 * 实现ServiceConnection接口 3 * 博客园-依旧淡然 4 */ 5 private ServiceConnection serviceConnection = new ServiceConnection() { 6 7 //连接Service 8 public void onServiceConnected(ComponentName name, IBinder iBinder) { 9 isBinderServiceConnected = true;10 MyBinder myBinder = (MyBinder)iBinder;11 BinderService binderService = myBinder.getBinderService();//获得BinderService对象12 //binderService.xxx(); //调用BinderService中的自定义方法,进行Service相关操作13 }14 15 //断开Service16 public void onServiceDisconnected(ComponentName name) {17 isBinderServiceConnected = false;18 }19 20 };
通过以上的代码可以看到,我们之前在BinderService类中定义的内部类MyBinder的作用就是获取BinderService对象的实例。获得了BinderService对象之后,便可以在Context对象中调用BinderService中的自定义方法,进行Service相关操作了。
需要注意的一点是,在Context对象中通过调用bindService()方法启动Service时,onCreate()方法和onRebind()方法会被调用,在Context对象中通过调用unbindService()方法来终止Service时,onUnbind()方法和onDestroy()方法会被调用。
2.IntentService的使用
上面分别介绍了如何通过startService()方式和bindService()方法来启动一个自定义的Service,在使用这两个方法时需要注意以下两个问题。
2.1 UI界面卡死
因为Service和启动它的Context对象(比如Activity)是在同一个线程里面,所以当我们直接在onStartCommand()方法中进行Service操作时,将会导致UI界面卡死。
我们可以通过Thread.currentThread().getId()方法来获得主线程以及Service线程的线程Id号,输出Log信息如图3所示。
图3 Log输出信息(1)由图3可以看出,Service和启动它的Activity确实是在同一个线程里。这样所带来的直接后果就是,当Service进行比较耗时的操作时UI界面卡死。比如,点击Button启动一个Service进行文件下载,那么在文件下载期间,UI界面将无法和用户进行交互。
2.2多进程调用
为了解决上面出现的问题,一种简单的解决方法就是在Service的onStartCommand()方法中重新启动一个线程,然后在新启动的线程中进行文件下载等耗时的操作。如下面的代码所示:
1 @Override 2 public int onStartCommand(Intent intent, int flags, int startId) { 3 new MyThread().start(); //启动一个新的线程 4 return super.onStartCommand(intent, flags, startId); 5 } 6 7 /* 8 * 内部类,用于启动一个新的线程 9 * 博客园-依旧淡然10 */ 11 private class MyThread extends Thread {12 13 @Override14 public void run() {15 try {16 Thread.sleep(2000); //模拟文件下载等耗时的操作17 Log.i(TAG, "MyService线程ID-->" + Thread.currentThread().getId());18 } catch (InterruptedException e) {19 e.printStackTrace();20 }21 }22 23 }
很显然,使用如上的方式,会比直接将文件下载等耗时的操作放在onStartCommand()方法中进行处理要好的多。
但是,当我们多次启动Service时,onStartCommand()方法将多次被调用,从而导致启动多个线程,如图4所示。
图4 Log输出信息(2)
由图4可以看出,使用改进后的方法,UI界面能够实时响应用户请求了,但是与此同时也引入了多线程调用的问题。
2.3 IntentService的使用
为了避免多线程调用的问题,Android提供了IntentService。
IntentService是Service的一个子类,主要用于处理异步请求。客户端通过startService(Intent intent)方法传递Intent请求给IntentService。在IntentService中,创建了一个与应用程序主线程分开的worker thread,用来处理所有传过来的Intent请求,当处理完所有的intent后停止Service。
实现IntentService的方法也很简单,覆写IntentService类的构造方法以及onHandleIntent()方法即可,在onHandleIntent()方法中可以完成比较耗时的操作。
如下的代码自定义了一个名为“MyIntentService”的类,该类继承自IntentService,并覆写IntentService类的构造方法以及onHandleIntent()方法。
1 /* 2 * MyIntentService类,继承自IntentService 3 * 博客园-依旧淡然 4 */ 5 public class MyIntentService extends IntentService { 6 7 private static final String TAG = "MyIntentService"; 8 9 //构造方法10 public MyIntentService(String name) {11 super("MyIntentService");12 }13 14 @Override15 protected void onHandleIntent(Intent intent) {16 try {17 Thread.sleep(2000); //模拟文件下载等耗时的操作18 Log.i(TAG, "MyIntentService线程ID-->" + Thread.currentThread().getId());19 } catch (InterruptedException e) {20 e.printStackTrace();21 } 22 }23 24 }
当我们多次启动MyIntentService时,可以看到如图5所示的Log输出信息。
图5 Log输出信息(3)
由图5可以看出,使用IntentService确实可以避免多线程调用。这是因为,在IntentService中默认创建了一个work queue,当IntentService收到多个Intent请求时,IntentService会将这些Intent请求依次放入work queue中,然后一次只传递一个Intent请求到onHandleIntent()方法中进行处理,从而避免了多线程调用。
需要注意的一点是,在IntentService中默认实现了onBind()方法,其返回值为null。同样,在IntentService中也默认实现了onStartCommand()方法,在这个方法中实现了将Intent请求放到work queue中。