Android Service生命周期,本地服务远程服务,前台服务后台服务,启动方式,与IntentService区别全面解析

脑子里记了太多,最近感觉不太够用了,还是决定来CSDN给存起来,方便自己以后翻阅也便利别人,一点知识总结,如有错误各位过客可在评论区指出。

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类,如下

public class ServiceDemo extends Service{

    private String TAG = "ServiceDemo";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"onBind");
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate ");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy");
    }

}

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

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <TextView
        android:id="@+id/start"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:gravity="center"
        android:background="@color/colorAccent"
        android:text="启动服务"/>

    <TextView
        android:id="@+id/stop"
        android:layout_marginTop="20dp"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:gravity="center"
        android:background="@color/colorAccent"
        android:text="停止服务"/>

</LinearLayout>

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

public class MainActivity extends AppCompatActivity {

    private String TAG = "MainActivity";

    @Bind(R.id.start)TextView start;
    @Bind(R.id.stop)TextView stop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }

    @OnClick(R.id.start)
    public void start() {
        Intent intent = new Intent(this, ServiceDemo.class);
        startService(intent);
    }

    @OnClick(R.id.stop)
    public void stop() {
        Intent intent = new Intent(this, ServiceDemo.class);
        stopService(intent);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ButterKnife.unbind(this);
    }
}
Intent intent = new Intent(this, ServiceDemo.class);
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

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

Intent intent = new Intent(this, ServiceDemo.class);
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类

public class ServiceDemo2 extends Service{

    private String TAG = "ServiceDemo";

    private MyBind myBind;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate");
        myBind = new MyBind();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"onBind");
        return myBind;
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        Log.e(TAG,"onRebind");
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG,"onUnbind");
        return super.onUnbind(intent);
    }

    public class MyBind extends Binder {

        public void beginDown() {
            Log.e(TAG,"begin");
            down();
        }
    }

    private void down() {
        Log.e(TAG,"down");
    }

}

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

public class MainActivity extends AppCompatActivity {

    private String TAG = "MainActivity";

    @Bind(R.id.bind)TextView bind;
    @Bind(R.id.unBind)TextView unBind;

    private ServiceDemo2.MyBind myBind;

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

    @OnClick(R.id.bind)
    public void bind() {
        bind.setText("绑定成功");
        unBind.setText("解绑服务");
        Intent intent = new Intent(this, ServiceDemo2.class);
        bindService(intent,connection,BIND_AUTO_CREATE);
    }

    @OnClick(R.id.unBind)
    public void unBind() {
        bind.setText("绑定服务");
        unBind.setText("解绑成功");
        unbindService(connection);
    }


    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myBind = (ServiceDemo2.MyBind) service;
            //这里就可以调用Service类的方法了
            myBind.beginDown();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ButterKnife.unbind(this);
    }
}

看启动方式:

Intent intent = new Intent(this, ServiceDemo2.class);
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,

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

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

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

再启动Service,在onCreate里加个log

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

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

Intent intent = new Intent(this, ServiceDemo2.class);
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:

public class ServiceDemo3 extends Service{

    private String TAG = "ServiceDemo3";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"onBind");
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate ");
        //添加下列代码将后台Service变成前台Service
        //构建点击通知栏后打开MainActivity的Intent对象
        // 在API11之后构建Notification的方式
        Intent nfIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = getActivity(this,0,nfIntent,0);
        Notification.Builder builder = new Notification.Builder(getApplicationContext()); //获取一个Notification构造器
        builder .setContentIntent(pendingIntent) // 设置PendingIntent
                .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher)) // 设置下拉列表中的图标(大图标)
                .setContentTitle("前台服务") // 设置下拉列表里的标题
                .setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
                .setContentText("显示的内容") // 设置上下文内容
                .setWhen(System.currentTimeMillis()); // 设置该通知发生的时间

        Notification notification = builder.build(); // 获取构建好的Notification
        notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
        // 参数一:唯一的通知标识;参数二:通知消息。
        startForeground(110, notification);// 让Service变成前台Service,并在系统的状态栏显示出来
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        // 停止前台服务--参数:表示是否移除之前的通知
        stopForeground(true);
        super.onDestroy();
        Log.e(TAG,"onDestroy");
    }

}

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

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

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

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

 

IntentService:

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

 

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

先新建一个类ServiceDemo4

public class ServiceDemo4 extends IntentService{

    private String TAG = "ServiceDemo4";
    
    public ServiceDemo4() {
        //调用父类的构造函数 构造函数参数=工作线程的名字
        super("ServiceDemo4");
        Log.e(TAG,"ServiceDemo4");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate");
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        Log.e(TAG,"onHandleIntent intent = " + intent.getStringExtra("params") + "  thread id = " + Thread.currentThread().getId() + ",name = "+Thread.currentThread().getName());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy");
    }
}

在配置文件里注册服务

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

在MainActivity里启动服务

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

我们先来看下日志:

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,再次发一个耗时请求过去:

public class ServiceDemo4 extends IntentService{

    private String TAG = "ServiceDemo4";

    private LocalBroadcastManager mBroadcastManager;

    private boolean isRunning = true;
    private boolean isRunning_again = true;
    private int progress = 0;
    private int progress_again = 0;

    public ServiceDemo4() {
        //调用父类的构造函数 构造函数参数=工作线程的名字
        super("ServiceDemo4");
        Log.e(TAG,"ServiceDemo4");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate");
        //切记不要在构造方法里实例化这个本地广播,因为获取不到context作为参数
        mBroadcastManager = LocalBroadcastManager.getInstance(this);
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        Log.e(TAG,"onHandleIntent intent = " + intent.getStringExtra("params") + "  thread id = " + Thread.currentThread().getId() + ",name = "+Thread.currentThread().getName());
        String parma = intent.getStringExtra("params");
        if (!TextUtils.isEmpty(parma)) {

            switch (parma) {
                case "thread" :
                    //当请求为 thread 类型时,模拟下载进度回调,每隔2s加1,然后通过本地广播发出去;
                    isRunning = true;
                    while (isRunning) {
                        progress++;
                        if (progress>=5) {
                            isRunning = false;
                        }
                        sendThreadStatus(ACTION_THREAD,progress);
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                case "thread_again" :
                    //当请求为 thread_again 类型时,模拟下载进度回调,每隔2s加1,然后通过本地广播发出去;
                    isRunning_again = true;
                    while (isRunning_again) {
                        progress_again++;
                        if (progress_again>=5) {
                            isRunning_again = false;
                        }
                        sendThreadStatus(ACTION_THREAD_AGAIN,progress_again);
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
            }
        }

    }

    // 发送线程结果
    private void sendThreadStatus(String action, int progress) {
        Log.e(TAG,"action="+action+",progress="+progress);
        Intent intent = new Intent(action);
        intent.putExtra("progress", progress);
        mBroadcastManager.sendBroadcast(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy");
    }
}

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

public class MainActivity2 extends AppCompatActivity {

    private static String TAG = "MainActivity2";

    public final static String ACTION_THREAD = "com.mango.thread";
    public final static String ACTION_THREAD_AGAIN = "com.mango.thread_again";

    @Bind(R.id.msg)TextView msg;
    @Bind(R.id.msg_again)TextView msg_again;

    private LocalBroadcastManager mBroadcastManager;
    private MyBoradCast myBoradCast;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        ButterKnife.bind(this);
        Log.e(TAG,"onCreate thread id = " + Thread.currentThread().getId()+ ",name = "+Thread.currentThread().getName());

        mBroadcastManager = LocalBroadcastManager.getInstance(this);
        myBoradCast = new MyBoradCast();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION_THREAD);
        intentFilter.addAction(ACTION_THREAD_AGAIN);
        mBroadcastManager.registerReceiver(myBoradCast,intentFilter);

    }

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

    @OnClick(R.id.start_again)
    public void startAgain() {
        Intent intent = new Intent(this, ServiceDemo4.class);
        intent.putExtra("params","thread_again");
        startService(intent);
    }

    @OnClick(R.id.stop)
    public void stop() {
        Intent intent = new Intent(this, ServiceDemo4.class);
        stopService(intent);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ButterKnife.unbind(this);
        mBroadcastManager.unregisterReceiver(myBoradCast);
    }

    public class MyBoradCast extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {

            String action = intent.getAction();
            if (action != null) {
                switch (action) {
                    case ACTION_THREAD :
                        int progress = intent.getIntExtra("progress",0);
                        msg.setText("ACTION_THREAD 当前进度 = "+progress);
                        break;
                    case ACTION_THREAD_AGAIN :
                        int progress_again = intent.getIntExtra("progress",0);
                        msg_again.setText("ACTION_THREAD_AGAIN 当前进度 = "+progress_again);
                        break;
                }
            }

        }

    }
}

这里先在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

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

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的源码中

public abstract class IntentService extends Service {

    private volatile Looper mServiceLooper;//这是一个消息队列,存放着发过来的请求Intent
    private volatile ServiceHandler mServiceHandler;//就是自定义一个Handler,用来从消息队        
    列取消息和放消息到消息队列中
    private String mName;//工作线程名字
    private boolean mRedelivery;//是否重启服务的标志,默认flase

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

       //从onCreate里可以知道ServiceHandler的创建是依附于HandlerThread线程的,也就是在线程里创建一个Handler
       //所以在这里处理请求是在HandlerThread进行,不会阻塞主线程
       //每处理一个请求就调用一次stopSelf(msg.arg1),这个参数是onStartCommand方法第三个参数,并不是真的销毁自己,会判断
       //如果mServiceLooper的队列里还有消息,就不会销毁自己
        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    /**
     *创建一个IntentService。 由你的子类的构造函数调用。
     * 参数name是工作线程名字
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    /**
     * 
     * 设置true,如果onStartCommand在返回之前IntentService被系统销毁了,系统会重启该服务
     * 如果有多个Intent已经被发送,则只有最近的一个保证被重新递交。
     * 设置flase 如果onStartCommand在返回之前IntentService被系统销毁了,系统不会重启该服务
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //构建一个线程,HandlerThread是继承自Thread
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
//将HandlerThread的消息队列拿出来,传给ServiceHandler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    
/**
     *可以看到每startService一次,就会调用一次onStartCommand,在onStartCommand会调用onStart,
     * 在onStart里将发过来的请求Intent放到Message里,
     * 这个startId是叠加的,表示请求顺序
     */
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    /**
     * 建议不用重写这个方法,没有用,因为有onHandleIntent方法被回调
     */
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

//只要调用stopService后,后续请求就不会得到执行,因为消息队列清空了mServiceLooper.quit()
    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    /**
     * 除非您为您的服务提供绑定,否则不需要实现此方法,因为默认实现返回null。
     * 所以建议用startService启动服务,不用bindService启动服务。
     */
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 这是一个抽象方法,在工作线程去处理请求,不会阻塞主线程,一次只处理一个请求,如果一个请求耗时较长,会将接下来的请求处理往后延,
     * 所有请求结束后,IntentService会自动销毁,开发者不要调用stopSelf
     */
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}


整体流程来说就是:

  • 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又比线程优先级高,相对而言不容易被系统杀死来保证服务运行。

 

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值