Service、IntentService生命周期总结

Service内容基本会在这章总结到,总结顺序如下:

  1. Service概述
  2. Service生命周期概述
  3. Service分类:

           * 按启动方式分类:通过Context.startService()或Context.bindService启动(顺带讲到Service与Thread区别)

           * 按寄存方式分类:本地服务,远程服务(讲到使用AIDL进行进程间通信)

           * 按运行方式分类:前台服务,后台服务

      4.IntentService使用和源码分析

 

Service概述

什么是Service,相信大家应该不会陌生了,Service是Android四大组件之一,是一个可以在后台长时间运行操作而没有用户界面的应用组件,服务可由其他应用组件启动(如Activity),服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行,即使我们的APP已经退出桌面了。Service没有界面与用户交互,但是也可以为Service添加界面,后面娓娓道来。

 

Service知识总结(相关代码请移步https://github.com/Mangosir/ServiceReview/tree/master)

 

Service生命周期概述

onCreate:当用Context.startService,Context.bindService启动服务时,会回调onCreate,在整个生命周期中,只会回调一次。也是生命周期方法中第一个被回调的,系统将调用次方法来执行一次性设置程序

onStartCommand:用Context.startService启动Service会回调这个方法,每次启动都会回调,Context.bindService启动Service不会回调这个方法。

onBindContext.bindService启动Service回调这个方法,启动者可通过该方法返回对象来对Service对象进行操控。

onReBind当使用startService启动Service,又调用bindService启动Service,且 onUnbind 返回值为 true 时,下次再次调用 Context.bindService 将触发方法。

onUnBind用 Context.unbindService 触发此方法,默认返回 false, 当返回值 true 后,再次调用 Context.bindService 时将触发 onRebind 方法。

onDestory:1.以Context.startService启动service,调用Context.stopService停止服务回调此方法;2.以Context.bindService启动service,以Context.unbindService停止服务回调此方法;3.先以Context.startService 启动服务,再用Context.bindService绑定服务,结束时必须先调用Context.unbindService解绑再使用Context.stopService停止service才会回调此方法,否则会报错。这里做一些资源释放操作

(onStart方法已经过时,这里就不再赘述)

 

Service的分类:

1.按启动方式分两类

第一类:通过Context.startService()启动,调用Context.stopService()停止服务。

第二类:通过Context.bindService启动service,以Context.unbindService停止服务。

第一类:

新建一个ServiceDemo类继承Service类,如下


    
    
    
    
  1. public class ServiceDemo extends Service{
  2. private String TAG = "ServiceDemo";
  3. @Nullable
  4. @Override
  5. public IBinder onBind(Intent intent) {
  6. Log.e(TAG,"onBind");
  7. return null;
  8. }
  9. @Override
  10. public void onCreate() {
  11. super.onCreate();
  12. Log.e(TAG,"onCreate ");
  13. }
  14. @Override
  15. public int onStartCommand(Intent intent, int flags, int startId) {
  16. Log.e(TAG,"onStartCommand");
  17. return super.onStartCommand(intent, flags, startId);
  18. }
  19. @Override
  20. public void onDestroy() {
  21. super.onDestroy();
  22. Log.e(TAG,"onDestroy");
  23. }
  24. }

然后在MainActivity的布局中添加两个按钮,一个启动服务,一个停止服务


    
    
    
    
  1. <LinearLayout
  2. xmlns:android= "http://schemas.android.com/apk/res/android"
  3. android:layout_width= "match_parent"
  4. android:layout_height= "match_parent"
  5. android:orientation= "vertical"
  6. android:gravity= "center">
  7. <TextView
  8. android:id= "@+id/start"
  9. android:layout_width= "100dp"
  10. android:layout_height= "50dp"
  11. android:gravity= "center"
  12. android:background= "@color/colorAccent"
  13. android:text= "启动服务"/>
  14. <TextView
  15. android:id= "@+id/stop"
  16. android:layout_marginTop= "20dp"
  17. android:layout_width= "100dp"
  18. android:layout_height= "50dp"
  19. android:gravity= "center"
  20. android:background= "@color/colorAccent"
  21. android:text= "停止服务"/>
  22. LinearLayout>

然后在监听方法中启动服务:


    
    
    
    
  1. public class MainActivity extends AppCompatActivity {
  2. private String TAG = "MainActivity";
  3. @Bind(R.id.start)TextView start;
  4. @Bind(R.id.stop)TextView stop;
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_main);
  9. ButterKnife.bind(this);
  10. }
  11. @OnClick(R.id.start)
  12. public void start() {
  13. Intent intent = new Intent(this, ServiceDemo.class);
  14. startService(intent);
  15. }
  16. @OnClick(R.id.stop)
  17. public void stop() {
  18. Intent intent = new Intent(this, ServiceDemo.class);
  19. stopService(intent);
  20. }
  21. @Override
  22. protected void onDestroy() {
  23. super.onDestroy();
  24. ButterKnife.unbind(this);
  25. }
  26. }

    
    
    
    
  1. Intent intent = new Intent(this, ServiceDemo.class);
  2. startService(intent);

这里用到了ButterKnife注解框架,这里先不做过多解释,以后再出一篇使用详述。

这里可以看到当点击按钮启动Service时,log日子打印结果是

11-06 11:03:58.192 6941-6941 E/ServiceDemo: onCreate
11-06 11:03:58.192 6941-6941 E/ServiceDemo: onStartCommand

当多次点击启动按钮,日志打印结果是

11-06 11:03:58.192 6941-6941 E/ServiceDemo: onCreate
11-06 11:03:58.192 6941-6941 E/ServiceDemo: onStartCommand
11-06 11:05:20.231 6941-6941 E/ServiceDemo: onStartCommand
11-06 11:05:20.690 6941-6941 E/ServiceDemo: onStartCommand
11-06 11:05:20.958 6941-6941 E/ServiceDemo: onStartCommand
11-06 11:05:21.262 6941-6941 E/ServiceDemo: onStartCommand
11-06 11:05:21.529 6941-6941 E/ServiceDemo: onStartCommand

启动总结:

从这可以看出,一个Service是可以多次启动的,但是onCreate只会回调一次,而onStartCommand是启动一次就回调一次。所以如果你的Service有多次启动的可能性的话,有一些初始化的操作就放在onCreate里,避免无故开辟内存。但是onStartCommand方法却可以回调多次,我们先看onStartCommand这个方法是有返回值的并且是我们开发者可以控制返回什么值。我们知道当系统内存是有限的,当系统内存资源不足,Service是会被销毁的,如果你在Service里做了什么重要事情,那被销毁显然是你不愿意看到的,所以要有一种方法让系统帮我们重启该服务,那要不要重启就由这个返回值决定了,看下方:

  • START_STICKY:如果service进程被kill掉,系统会尝试重新创建Service,如果在此期间没有任何启动命令被传递到Service,那么参数intent将为null。
  • START_NOT_STICKY:使用这个返回值时,如果在执行完onStartCommand()后,服务被异常kill掉,系统不会自动重启该服务。
  • START_REDELIVER_INTENT:使用这个返回值时,如果在执行完onStartCommand()后,服务被异常kill掉,系统会自动重启该服务,并将intent的值传入。
  • START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

如果把返回值设置成START_STICKY和START_REDELIVER_INTENT,这样就可以处于不死状态了(这样做其实是不好的)还有一种情况是用户主动在设置界面把你的Service给杀死,但是这会回调onDestroy,你可以在这里发送广播重新启动。

onStartCommand()第二个输入参数flags正是代表此次onStartCommand()方法的启动方式,正常启动时,flags默认为0,被kill后重新启动,参数分为以下两种:

  • START_FLAG_RETRY:代表service被kill后重新启动,由于上次返回值为START_STICKY,所以参数 intent 为null
  • START_FLAG_REDELIVERY代表service被kill后重新启动,由于上次返回值为START_REDELIVER_INTENT,所以带输入参数intent

好,我们继续往下说,当我们点击按钮停止服务时


    
    
    
    
  1. Intent intent = new Intent(this, ServiceDemo.class);
  2. stopService(intent);

此时log打印为

11-06 11:20:48.120 6941-6941 E/ServiceDemo: onDestroy

也就是回调了onDestroy,在这里做一些释放内存的事情。

注意了,服务可以多次启动,但是停止只能停一次,还有服务必须在AndroidManifest.xml里注册:

<service android:name="com.mangoer.servicereview.Service.ServiceDemo">service>
    
    
    
    

如果以这种方式启动服务,服务就会在后台无限期运行,即使启动的Activity被销毁了,也不会对Service有任何影响;除非手动调用stopService,服务才会停止

总结使用步骤:

  • 定义一个类继承Service
  • 在Manifest.xml中注册该Service
  • 使用Context.startService启动服务
  • 不使用时使用stopService停止服务

第二类:

新建ServiceDemo2类继承Service类


    
    
    
    
  1. public class ServiceDemo2 extends Service{
  2. private String TAG = "ServiceDemo";
  3. private MyBind myBind;
  4. @Override
  5. public void onCreate() {
  6. super.onCreate();
  7. Log.e(TAG,"onCreate");
  8. myBind = new MyBind();
  9. }
  10. @Override
  11. public int onStartCommand(Intent intent, int flags, int startId) {
  12. Log.e(TAG,"onStartCommand");
  13. return super.onStartCommand(intent, flags, startId);
  14. }
  15. @Override
  16. public void onDestroy() {
  17. super.onDestroy();
  18. Log.e(TAG,"onDestroy");
  19. }
  20. @Nullable
  21. @Override
  22. public IBinder onBind(Intent intent) {
  23. Log.e(TAG,"onBind");
  24. return myBind;
  25. }
  26. @Override
  27. public void onRebind(Intent intent) {
  28. super.onRebind(intent);
  29. Log.e(TAG,"onRebind");
  30. }
  31. @Override
  32. public boolean onUnbind(Intent intent) {
  33. Log.e(TAG,"onUnbind");
  34. return super.onUnbind(intent);
  35. }
  36. public class MyBind extends Binder {
  37. public void beginDown() {
  38. Log.e(TAG,"begin");
  39. down();
  40. }
  41. }
  42. private void down() {
  43. Log.e(TAG,"down");
  44. }
  45. }

然后在布局里加两个按钮一个绑定服务,一个解绑服务,然后在MainActivity里进行操作


    
    
    
    
  1. public class MainActivity extends AppCompatActivity {
  2. private String TAG = "MainActivity";
  3. @Bind(R.id.bind)TextView bind;
  4. @Bind(R.id.unBind)TextView unBind;
  5. private ServiceDemo2.MyBind myBind;
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. setContentView(R.layout.activity_main);
  10. ButterKnife.bind(this);
  11. // thread id = " + Thread.currentThread().getId() + ",thread name = " + Thread.currentThread().getName()
  12. Log.e(TAG,"onCreate");
  13. }
  14. @OnClick(R.id.bind)
  15. public void bind() {
  16. bind.setText("绑定成功");
  17. unBind.setText("解绑服务");
  18. Intent intent = new Intent(this, ServiceDemo2.class);
  19. bindService(intent,connection,BIND_AUTO_CREATE);
  20. }
  21. @OnClick(R.id.unBind)
  22. public void unBind() {
  23. bind.setText("绑定服务");
  24. unBind.setText("解绑成功");
  25. unbindService(connection);
  26. }
  27. private ServiceConnection connection = new ServiceConnection() {
  28. @Override
  29. public void onServiceConnected(ComponentName name, IBinder service) {
  30. myBind = (ServiceDemo2.MyBind) service;
  31. //这里就可以调用Service类的方法了
  32. myBind.beginDown();
  33. }
  34. @Override
  35. public void onServiceDisconnected(ComponentName name) {
  36. }
  37. };
  38. @Override
  39. protected void onDestroy() {
  40. super.onDestroy();
  41. ButterKnife.unbind(this);
  42. }
  43. }

看启动方式:


    
    
    
    
  1. Intent intent = new Intent(this, ServiceDemo2.class);
  2. bindService(intent,connection,BIND_AUTO_CREATE);

我们先介绍下这三个参数:

  • Intent:构建的连接Activity和Service的对象
  • ServiceConnection:在MainActivity创建的匿名内部类connection,重写两个方法,就是通过这个类的onServiceconnected方法获得MyBind实例,从而让Activity可以调用Service类的公共方法;onServiceDisconnected是服务意外中断的时候调用的,不是开发者主动停止服务调用的
  • flags:这是一个int型数,一般填入Context.BIND_AUTO_CREATE,表示Activity和Service建立关联后自动创建Service,可以让Service的onCreate执行,但是onStartCommand不会执行

就这样Service就运行起来了,

看下log日志:

11-06 14:24:55.982 7123-7123 E/ServiceDemo: onCreate
11-06 14:24:55.985 7123-7123 E/ServiceDemo: onBind
11-06 14:24:55.987 7123-7123 E/MainActivity: onServiceConnected
11-06 14:24:55.987 7123-7123 E/ServiceDemo: begin
11-06 14:24:55.987 7123-7123 E/ServiceDemo: down

也就是服务一绑定,会执行onCreate,再回调onBind,最后会走到ServiceConnection类的onServiceConnected中。

再调用解绑服务:

11-06 14:37:55.575 14517-14517 E/ServiceDemo: onUnbind
11-06 14:37:55.576 14517-14517 E/ServiceDemo: onDestroy

总结使用步骤:

  • 自定义Service类,继承自Service,创建一个继承Binder类的内部类
  • 在Service类的onBind方法中返回上述Binder实例
  • 在客户端中,使用bindService绑定服务端,从onServiceConnected方法中接收Binder对象并调用该对象的方法
  • 使用unbindService使客户端与服务端解除绑定

注意:

  1. 不管是startService还是bindService,最后都要stopService和unBindService与之对应,要不然容易造成内存泄漏。
  2. bindService后调用unBindService成功后,如果再调用unBindService将会报异常,可以加一个boolean变量判断。
  3. 这里有一种特殊情况是先startService,此时回调onCreate->onStartCommand,然后再bindService,回调onBind->onServiceConnected;这时候要想销毁Service必须调用unBindService,再调用stopService。
  4. bindService成功后,Service就与当前Activity绑定了,它就跟随Activity生命周期走了,如果服务没有销毁,而与之绑定的Activity销毁了,那这个绑定的Service也会被销毁,但是Service里启动的子线程不会被销毁。
  5. 服务是运行在后台,没错,但是不能进行耗时操作,否则会ANR,页面卡死情况;如果有耗时操作一定要新建一个子线程,放在里进行,这里也说明Service和Thread不是同一种东西,就跟猫和鱼一样,是两个不同物种。但是通常情况下这两个是组合起来使用。
  6. 有人会问了,既然Service不能做耗时操作,就是做也得放在子线程里去做,那为啥不直接在Activity里开一个子线程去做呢,反而弄Service这么麻烦。有道理,但是Activity是很难对线程去操作的,当线程所在的Activity销毁了,那这个线程就是脱缰的野马,没人能管的了了,你没办法再获取这个线程的实例了,除非把进程给杀了;但是如果线程放在Service里,就算Activity销毁了,以后只要重新与这个Service进行绑定,在onServiceConnected获取binder实例,这样就可以继续操作线程了,要它GG还是要它继续与你作伴,就看你高兴了。
  7. 通过startService启动服务后,服务就与启动它的组件没有联系了,组件销毁了,服务还是继续运行;通过bindService启动服务,与之绑定的组件就可以控制Service的具体执行逻辑了。

 

2.按Service寄存方式分两类:

1.本地服务 (Local Service): 寄存于当前的进程当中,当前进程结束后 Service 也会随之结束;因为处于同一个进程当中,所以Service 可以随时与 Activity 等多个部件进行通信,不需要IPC和AIDL;Service服务不会自动启动线程,如果没有人工调用多线程方式进行启动,Service将寄存于主线程当中。

2.远程服务 (Remote Service ): 独立寄存于另一进程中, 服务常驻后台,通过 AIDL (Android Interface Definition Language)接口定义语言,实现Android设备上的两个进程间通信(IPC)。

本地服务:

我们先打开MainActivity,在onCreate里打一个显示当前线程的log,


    
    
    
    
  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. setContentView(R.layout.activity_main);
  5. ButterKnife.bind(this);
  6. Log.e(TAG,"onCreate thread id = " + Thread.currentThread().getId() + ",thread name = " + Thread.currentThread().getName() );
  7. }

再看日志,表明当前Activity运行在主线程

11-06 15:31:22.269 32438-32438 E/MainActivity: onCreate thread id = 1,thread name = main

再启动Service,在onCreate里加个log


    
    
    
    
  1. @Override
  2. public void onCreate() {
  3. super.onCreate();
  4. Log.e(TAG,"onCreate thread id = " + Thread.currentThread().getId() + ",thread name = " + Thread.currentThread().getName());
  5. }

再在MainActivity里启动服务(用bindService是一样的)


    
    
    
    
  1. Intent intent = new Intent(this, ServiceDemo2.class);
  2. startService(intent);

看日志

11-06 15:31:23.645 32438-32438 E/ServiceDemo2: onCreate thread id = 1,thread name = main
11-06 15:31:23.647 32438-32438 E/ServiceDemo2: onStartCommand

这就可以看出来Service与Activity运行在同一个main线程,这就是本地服务了。

 

远程服务:

讲远程服务的时候先讲一下Service在Androidmanifest里的android:process配置属性,这个是表示这个Service是否在另一个进程中运行,不设置默认为本地服务,设置为:remote就为远程服务。那如何将远程服务用起来呢,请看下方:

由于远程服务涉及到AIDL进程间通信,以及两个进程间简单数据和复杂类型数据传递,篇幅较大,就不在这叙述了,请看博主另一篇文章http://blog.csdn.net/qq_30993595/article/details/78481716

 

3.按Service运行方式分两类:前台服务和后台服务。

前台Service和后台Service(普通)最大的区别就在于:

  • 前台Service在下拉通知栏有一条显示通知(主要做一些需要用户知道的事,但退出APP还能继续做,像音乐播放器类似的应用,在下拉栏有一些当前播放歌曲的信息和进行一些简单操作;有些下载功能放在Service里也会在下拉栏显示当前下载进度),但后台Service没有(主要是默默的做一些用户不知道的事,像收集定位数据,更新天气数据)
  • 前台Service优先级较高,不会由于系统内存不足而被回收;后台Service优先级较低,当系统出现内存不足情况时,很有可能会被回收

来看看前台Service什么样,新建一个类ServiceDemo3继承Service:


    
    
    
    
  1. public class ServiceDemo3 extends Service{
  2. private String TAG = "ServiceDemo3";
  3. @Nullable
  4. @Override
  5. public IBinder onBind(Intent intent) {
  6. Log.e(TAG,"onBind");
  7. return null;
  8. }
  9. @Override
  10. public void onCreate() {
  11. super.onCreate();
  12. Log.e(TAG,"onCreate ");
  13. //添加下列代码将后台Service变成前台Service
  14. //构建点击通知栏后打开MainActivity的Intent对象
  15. // 在API11之后构建Notification的方式
  16. Intent nfIntent = new Intent(this, MainActivity.class);
  17. PendingIntent pendingIntent = getActivity(this,0,nfIntent,0);
  18. Notification.Builder builder = new Notification.Builder(getApplicationContext()); //获取一个Notification构造器
  19. builder .setContentIntent(pendingIntent) // 设置PendingIntent
  20. .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher)) // 设置下拉列表中的图标(大图标)
  21. .setContentTitle("前台服务") // 设置下拉列表里的标题
  22. .setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
  23. .setContentText("显示的内容") // 设置上下文内容
  24. .setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
  25. Notification notification = builder.build(); // 获取构建好的Notification
  26. notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
  27. // 参数一:唯一的通知标识;参数二:通知消息。
  28. startForeground(110, notification);// 让Service变成前台Service,并在系统的状态栏显示出来
  29. }
  30. @Override
  31. public int onStartCommand(Intent intent, int flags, int startId) {
  32. Log.e(TAG,"onStartCommand");
  33. return super.onStartCommand(intent, flags, startId);
  34. }
  35. @Override
  36. public void onDestroy() {
  37. // 停止前台服务--参数:表示是否移除之前的通知
  38. stopForeground(true);
  39. super.onDestroy();
  40. Log.e(TAG,"onDestroy");
  41. }
  42. }

在onCreate方法中构建一个通知Notification,这样在MainActivity里就可以启动服务了


    
    
    
    
  1. Intent intent = new Intent(this, ServiceDemo3.class);
  2. startService(intent);

这样就是一个前台服务了,当然这个显示通知这个代码你可以放在你自己具体的逻辑里。

后台服务就不多叙述了,把通知这段代码去掉再启动这个服务就是一个后台服务了。

 

IntentService:

IntentService是Service的一个子类,它可以自己处理异步请求,在它内部有一个工作线程来处理耗时请求,可以启动IntentService多次,每个耗时操作会以队列的方式在onHandlerIntent方法中回调处理,每次只会执行一个工作线程,全部处理完,IntentService会自动结束,不需要开发者去结束。

 

先来个IntentService的简单使用,然后再分析源码:

先新建一个类ServiceDemo4


    
    
    
    
  1. public class ServiceDemo4 extends IntentService{
  2. private String TAG = "ServiceDemo4";
  3. public ServiceDemo4() {
  4. //调用父类的构造函数 构造函数参数=工作线程的名字
  5. super("ServiceDemo4");
  6. Log.e(TAG,"ServiceDemo4");
  7. }
  8. @Override
  9. public void onCreate() {
  10. super.onCreate();
  11. Log.e(TAG,"onCreate");
  12. }
  13. @Override
  14. public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
  15. Log.e(TAG,"onStartCommand");
  16. return super.onStartCommand(intent, flags, startId);
  17. }
  18. @Override
  19. protected void onHandleIntent(@Nullable Intent intent) {
  20. Log.e(TAG,"onHandleIntent intent = " + intent.getStringExtra("params") + " thread id = " + Thread.currentThread().getId() + ",name = "+Thread.currentThread().getName());
  21. }
  22. @Override
  23. public void onDestroy() {
  24. super.onDestroy();
  25. Log.e(TAG,"onDestroy");
  26. }
  27. }

在配置文件里注册服务

<service android:name=".Service.ServiceDemo4">service>
    
    
    
    

在MainActivity里启动服务


    
    
    
    
  1. @OnClick(R.id.start)
  2. public void start() {
  3. Intent intent = new Intent(this, ServiceDemo4.class);
  4. intent.putExtra("params","thread");
  5. startService(intent);
  6. }

我们先来看下日志:

11-07 11:17:12.657 18996-18996 E/MainActivity2: onCreate thread id = 1,name = main
11-07 11:17:35.215 18996-18996 E/ServiceDemo4: ServiceDemo4
11-07 11:17:35.217 18996-18996 E/ServiceDemo4: onCreate
11-07 11:17:35.217 18996-18996 E/ServiceDemo4: onStartCommand
11-07 11:17:35.218 18996-19347 E/ServiceDemo4: onHandleIntent intent = thread  thread id = 613,name = IntentService[ServiceDemo4]
11-07 11:17:35.222 18996-18996 E/ServiceDemo4: onDestroy

当我再次点击启动任务按钮,日志如下:

11-07 11:21:50.631 18996-18996 E/ServiceDemo4: ServiceDemo4
11-07 11:21:50.632 18996-18996 E/ServiceDemo4: onCreate
11-07 11:21:50.633 18996-18996 E/ServiceDemo4: onStartCommand
11-07 11:21:50.633 18996-21742 E/ServiceDemo4: onHandleIntent intent = thread  thread id = 614,name = IntentService[ServiceDemo4]
11-07 11:21:50.634 18996-18996 E/ServiceDemo4: onDestroy

可以得出

  • IntentService的工作线程(613/614)与MainActivity(1)不在同一个线程
  • 我这时候只在onHandleIntent打印了一个log,所以很快就结束任务,然后做完就执行onDestory;但我并没有手动调用stopService,所以IntentService是任务结束就自动销毁
  • onHandleIntent里如何判断每次过来的请求要做什么事?答案在于startService时构建的intent里的putExtra()参数
  • 每次IntentService结束后再startService都会开启一个与上次不同的线程去处理请求(613/614)

再来写个模拟耗时操作例子,第一次startService,IntentService还在onHandleIntent异步处理请求,没处理完再次startService,再次发一个耗时请求过去:


    
    
    
    
  1. public class ServiceDemo4 extends IntentService{
  2. private String TAG = "ServiceDemo4";
  3. private LocalBroadcastManager mBroadcastManager;
  4. private boolean isRunning = true;
  5. private boolean isRunning_again = true;
  6. private int progress = 0;
  7. private int progress_again = 0;
  8. public ServiceDemo4() {
  9. //调用父类的构造函数 构造函数参数=工作线程的名字
  10. super("ServiceDemo4");
  11. Log.e(TAG,"ServiceDemo4");
  12. }
  13. @Override
  14. public void onCreate() {
  15. super.onCreate();
  16. Log.e(TAG,"onCreate");
  17. //切记不要在构造方法里实例化这个本地广播,因为获取不到context作为参数
  18. mBroadcastManager = LocalBroadcastManager.getInstance(this);
  19. }
  20. @Override
  21. public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
  22. Log.e(TAG,"onStartCommand");
  23. return super.onStartCommand(intent, flags, startId);
  24. }
  25. @Override
  26. protected void onHandleIntent(@Nullable Intent intent) {
  27. Log.e(TAG,"onHandleIntent intent = " + intent.getStringExtra("params") + " thread id = " + Thread.currentThread().getId() + ",name = "+Thread.currentThread().getName());
  28. String parma = intent.getStringExtra("params");
  29. if (!TextUtils.isEmpty(parma)) {
  30. switch (parma) {
  31. case "thread" :
  32. //当请求为 thread 类型时,模拟下载进度回调,每隔2s加1,然后通过本地广播发出去;
  33. isRunning = true;
  34. while (isRunning) {
  35. progress++;
  36. if (progress>=5) {
  37. isRunning = false;
  38. }
  39. sendThreadStatus(ACTION_THREAD,progress);
  40. try {
  41. Thread.sleep(2000);
  42. } catch (InterruptedException e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. break;
  47. case "thread_again" :
  48. //当请求为 thread_again 类型时,模拟下载进度回调,每隔2s加1,然后通过本地广播发出去;
  49. isRunning_again = true;
  50. while (isRunning_again) {
  51. progress_again++;
  52. if (progress_again>=5) {
  53. isRunning_again = false;
  54. }
  55. sendThreadStatus(ACTION_THREAD_AGAIN,progress_again);
  56. try {
  57. Thread.sleep(2000);
  58. } catch (InterruptedException e) {
  59. e.printStackTrace();
  60. }
  61. }
  62. break;
  63. }
  64. }
  65. }
  66. // 发送线程结果
  67. private void sendThreadStatus(String action, int progress) {
  68. Log.e(TAG,"action="+action+",progress="+progress);
  69. Intent intent = new Intent(action);
  70. intent.putExtra("progress", progress);
  71. mBroadcastManager.sendBroadcast(intent);
  72. }
  73. @Override
  74. public void onDestroy() {
  75. super.onDestroy();
  76. Log.e(TAG,"onDestroy");
  77. }
  78. }

这里是在onHandleIntent里模拟耗时请求,获得结果后通过本地广播发出去,然后在MainActivity接受广播进行更新UI


    
    
    
    
  1. public class MainActivity2 extends AppCompatActivity {
  2. private static String TAG = "MainActivity2";
  3. public final static String ACTION_THREAD = "com.mango.thread";
  4. public final static String ACTION_THREAD_AGAIN = "com.mango.thread_again";
  5. @Bind(R.id.msg)TextView msg;
  6. @Bind(R.id.msg_again)TextView msg_again;
  7. private LocalBroadcastManager mBroadcastManager;
  8. private MyBoradCast myBoradCast;
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState) {
  11. super.onCreate(savedInstanceState);
  12. setContentView(R.layout.activity_main2);
  13. ButterKnife.bind(this);
  14. Log.e(TAG,"onCreate thread id = " + Thread.currentThread().getId()+ ",name = "+Thread.currentThread().getName());
  15. mBroadcastManager = LocalBroadcastManager.getInstance(this);
  16. myBoradCast = new MyBoradCast();
  17. IntentFilter intentFilter = new IntentFilter();
  18. intentFilter.addAction(ACTION_THREAD);
  19. intentFilter.addAction(ACTION_THREAD_AGAIN);
  20. mBroadcastManager.registerReceiver(myBoradCast,intentFilter);
  21. }
  22. @OnClick(R.id.start)
  23. public void start() {
  24. Intent intent = new Intent(this, ServiceDemo4.class);
  25. intent.putExtra("params","thread");
  26. startService(intent);
  27. }
  28. @OnClick(R.id.start_again)
  29. public void startAgain() {
  30. Intent intent = new Intent(this, ServiceDemo4.class);
  31. intent.putExtra("params","thread_again");
  32. startService(intent);
  33. }
  34. @OnClick(R.id.stop)
  35. public void stop() {
  36. Intent intent = new Intent(this, ServiceDemo4.class);
  37. stopService(intent);
  38. }
  39. @Override
  40. protected void onDestroy() {
  41. super.onDestroy();
  42. ButterKnife.unbind(this);
  43. mBroadcastManager.unregisterReceiver(myBoradCast);
  44. }
  45. public class MyBoradCast extends BroadcastReceiver{
  46. @Override
  47. public void onReceive(Context context, Intent intent) {
  48. String action = intent.getAction();
  49. if (action != null) {
  50. switch (action) {
  51. case ACTION_THREAD :
  52. int progress = intent.getIntExtra("progress",0);
  53. msg.setText("ACTION_THREAD 当前进度 = "+progress);
  54. break;
  55. case ACTION_THREAD_AGAIN :
  56. int progress_again = intent.getIntExtra("progress",0);
  57. msg_again.setText("ACTION_THREAD_AGAIN 当前进度 = "+progress_again);
  58. break;
  59. }
  60. }
  61. }
  62. }
  63. }

这里先在onCreate里实例化广播MyBoradcast,然后通过本地广播注册(广播这个组件在Service讲完后再写一遍文章具体分析),然后在第一个按钮里启动服务,记得传一个params参数,看日志:

11-07 15:31:29.488 13558-13558 E/MainActivity2: onCreate thread id = 1,name = main
11-07 15:31:31.861 13558-13558 E/ServiceDemo4: ServiceDemo4
11-07 15:31:31.863 13558-13558 E/ServiceDemo4: onCreate
11-07 15:31:31.863 13558-13558 E/ServiceDemo4: onStartCommand
11-07 15:31:31.864 13558-13703 E/ServiceDemo4: onHandleIntent intent = thread  thread id = 868,name = IntentService[ServiceDemo4]
11-07 15:31:31.865 13558-13703 E/ServiceDemo4: action=com.mango.thread,progress=1
11-07 15:31:33.867 13558-13703 E/ServiceDemo4: action=com.mango.thread,progress=2

当耗时操作计数到2时,再点击第二个按钮再次启动服务,看日志:

11-07 15:31:34.638 13558-13558 E/ServiceDemo4: onStartCommand
11-07 15:31:35.869 13558-13703 E/ServiceDemo4: action=com.mango.thread,progress=3
11-07 15:31:37.870 13558-13703 E/ServiceDemo4: action=com.mango.thread,progress=4

这里只回调了onStartCommad方法,并没有进入onHandleIntent方法,继续看日志:

11-07 15:31:39.872 13558-13703 E/ServiceDemo4: action=com.mango.thread,progress=5
11-07 15:31:41.873 13558-13703 E/ServiceDemo4: onHandleIntent intent = thread_again  thread id = 868,name = IntentService[ServiceDemo4]
11-07 15:31:41.874 13558-13703 E/ServiceDemo4: action=com.mango.thread_again,progress=1
11-07 15:31:43.874 13558-13703 E/ServiceDemo4: action=com.mango.thread_again,progress=2
11-07 15:31:45.875 13558-13703 E/ServiceDemo4: action=com.mango.thread_again,progress=3

这里可以看到第一个请求计数到5结束后,就回调了onHandleIntent方法处理第二个请求,此时点击停止按钮停止服务,看日志:

11-07 15:31:46.696 13558-13558 E/ServiceDemo4: onDestroy
11-07 15:31:47.876 13558-13703 E/ServiceDemo4: action=com.mango.thread_again,progress=4
11-07 15:31:49.877 13558-13703 E/ServiceDemo4: action=com.mango.thread_again,progress=5

虽然服务停止了,但是服务里开启的子线程并没有结束,依然在计数,依然在发广播。

Service、IntentService生命周期总结_第1张图片

IntentService使用步骤:

  1. 自定义类继承IntentService,并重写构造方法和onHandleIntent两个方法
  2. 在Activity里通过startService方法启动它,可以通过Intent携带参数

总结:

  • 如果第一次启动后,onhandleIntent没处理完,继续startService,不会再重新实例化这个Service了,而是将请求放到请求队列里,等待第一个处理完再处理第二个。这种情况下,只有一个线程在运行,看日志的thread id 都是868就明白了。
  • 假如现在有A,B,C三个请求,那就startService3次,如果在处理A请求调用了stopService,那A请求会继续执行完,但是B和C请求就不会执行;如果在执行C请求时候调用了stopService,C请求还是会执行完。具体原因待会看源码解释。
  • IntentService处理任务时是按请求顺序处理的,也就是一个接一个处理,优点是使用方便代码简洁,比较适用于单线程操作不耗时任务,但是并不符合我们大多数使用情况,没法进行并发操作。

 

IntentService源码解析

Service有的特性IntentService都有,但是有一些自身具有的属性,它在内部封装了一个消息队列和HnadlerThread。我们进到IntentService的源码中


    
    
    
    
  1. public abstract class IntentService extends Service {
  2.     private volatile Looper mServiceLooper; //这是一个消息队列,存放着发过来的请求Intent
  3.     private volatile ServiceHandler mServiceHandler; //就是自定义一个Handler,用来从消息队
  4. 列取消息和放消息到消息队列中
  5.     private String mName; //工作线程名字
  6.     private boolean mRedelivery; //是否重启服务的标志,默认flase
  7.     private final class ServiceHandler extends Handler {
  8.         public ServiceHandler(Looper looper) {
  9.             super(looper);
  10.         }
  11. //从onCreate里可以知道ServiceHandler的创建是依附于HandlerThread线程的,也就是在线程里创建一个Handler
  12. //所以在这里处理请求是在HandlerThread进行,不会阻塞主线程
  13. //每处理一个请求就调用一次stopSelf(msg.arg1),这个参数是onStartCommand方法第三个参数,并不是真的销毁自己,会判断
  14. //如果mServiceLooper的队列里还有消息,就不会销毁自己
  15.         @Override
  16.         public void handleMessage(Message msg) {
  17.             onHandleIntent((Intent)msg.obj);
  18.             stopSelf(msg.arg1);
  19.         }
  20.     }
  21.     /**
  22.      *创建一个IntentService。 由你的子类的构造函数调用。
  23.      * 参数name是工作线程名字
  24.      */
  25.     public IntentService(String name) {
  26.         super();
  27.         mName = name;
  28.     }
  29.     /**
  30.      * 
  31.      * 设置true,如果onStartCommand在返回之前IntentService被系统销毁了,系统会重启该服务
  32.      * 如果有多个Intent已经被发送,则只有最近的一个保证被重新递交。
  33.      * 设置flase 如果onStartCommand在返回之前IntentService被系统销毁了,系统不会重启该服务
  34.      */
  35.     public void setIntentRedelivery(boolean enabled) {
  36.         mRedelivery = enabled;
  37.     }
  38.     @Override
  39.     public void onCreate() {
  40.         super.onCreate();
  41.         //构建一个线程,HandlerThread是继承自Thread
  42.         HandlerThread thread = new HandlerThread( "IntentService[" + mName + "]");
  43.         thread.start();
  44. //将HandlerThread的消息队列拿出来,传给ServiceHandler
  45.         mServiceLooper = thread.getLooper();
  46.         mServiceHandler = new ServiceHandler(mServiceLooper);
  47.     }
  48.     
  49. /**
  50.      *可以看到每startService一次,就会调用一次onStartCommand,在onStartCommand会调用onStart,
  51.      * 在onStart里将发过来的请求Intent放到Message里,
  52.      * 这个startId是叠加的,表示请求顺序
  53.      */
  54.     @Override
  55.     public void onStart(@Nullable Intent intent, int startId) {
  56.         Message msg = mServiceHandler.obtainMessage();
  57.         msg.arg1 = startId;
  58.         msg.obj = intent;
  59.         mServiceHandler.sendMessage(msg);
  60.     }
  61.     /**
  62.      * 建议不用重写这个方法,没有用,因为有onHandleIntent方法被回调
  63.      */
  64.     @Override
  65.     public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
  66.         onStart(intent, startId);
  67.         return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
  68.     }
  69. //只要调用stopService后,后续请求就不会得到执行,因为消息队列清空了mServiceLooper.quit()
  70.     @Override
  71.     public void onDestroy() {
  72.         mServiceLooper.quit();
  73.     }
  74.     /**
  75.      * 除非您为您的服务提供绑定,否则不需要实现此方法,因为默认实现返回null。
  76.      * 所以建议用startService启动服务,不用bindService启动服务。
  77.      */
  78.     @Override
  79.     @Nullable
  80.     public IBinder onBind(Intent intent) {
  81.         return null;
  82.     }
  83.     /**
  84.      * 这是一个抽象方法,在工作线程去处理请求,不会阻塞主线程,一次只处理一个请求,如果一个请求耗时较长,会将接下来的请求处理往后延,
  85.      * 所有请求结束后,IntentService会自动销毁,开发者不要调用stopSelf
  86.      */
  87.     @WorkerThread
  88.     protected abstract void onHandleIntent(@Nullable Intent intent);
  89. }


整体流程来说就是:

  • startService后,IntentService内部调用onCreate方法,在这里创建了一个HandleThread,也就是创建了一个线程,并取出HandleThread中的消息队列Looper,接着创建一个ServiceHandler(本质就是一个Handler),并将获取到的Looper的引用传给ServiceHandler;这个用法其实就跟我们平时使用HandleThread的过程是一样的,只不过google把这个使用放到Service里去了就变成了一个IntentService。
  • 然后就会调用onStartCommand,将发过来的请求Intent和请求id传过去调用onStart方法,
  • 在onstart方法里将这两个参数构造成Message,通过mServiceHandler发到消息队列。
  • 然后在mServiceHandler类的handleMessage方法里调用onHandleIntent方法,然后调用stopSelf,如果消息队列没有消息了,那就真的停止自己了。
  • onHandleIntent如果没执行完,再次startService,就不会再走onCreate,而是继续走第二步,第三步,第四步,以此循环。
  • stopSelf执行完了,开发者再次startService,就继续从第一步开始继续往下走

Service与IntentService

  • Service是依附于主线程的(远程服务除外),直接做耗时操作会阻塞主线程;而IntentService是工作在工作线程,对主线程没影响
  • 使用IntentService不需要像在Service里自己手动开启线程去处理耗时请求
  • Service是需要手动调用stopService来销毁,而IntentService是自动销毁
  • IntentService内部采用了HandleThread和Handler的实现,IntentService又比线程优先级高,相对而言不容易被系统杀死来保证服务运行。

 


你可能感兴趣的:(安卓基础篇)