Android学习笔记39:Android四大组件之Service

  Android应用程序的四大组件分别是Activity、Service、BroadcastReceiver和ContentProvider。其中,有关Activity的介绍可以参阅博文《Android学习笔记38:Android四大组件之Activity》。有关ContentProvider的使用方法可以参阅博文《Android学习笔记37:使用Content Providers方式共享数据

  本文将主要对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       @Override
10       public void onCreate() {
11           super.onCreate();
12           Log.i(TAG, "MyService-->onCreate()");
13       }
14 
15       @Override
16       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       @Override
22       public IBinder onBind(Intent intent) {
23           return null;
24       }
25       
26       @Override
27       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:                    //停止Service
13                 Intent intent_stopService = new Intent(MainActivity.this, MyService.class);
14                 stopService(intent_stopService);
15                 break;
16             }
17         }
18     };

  最后,我们还需要在AndroidManifest.xml文件中注册我们自定义的MyService组件,即在<application></application>标签中添加如下代码即可:

1   <!-- 注册MyService组件 -->
2     <service 
3         android:name=".MyService"    >
4     </service>

  至此,我们便完成了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         //断开Service
16         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         @Override
14         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       @Override
15       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中。

posted @ 2013-04-25 23:14  依旧淡然  阅读(2617)  评论(3编辑  收藏  举报