02、默默无闻的 Service (一):Service概述

Service.png

版权声明:本文为博主原创文章,未经博主允许不得转载

PS:转载请注明出处
作者: TigerChain
地址: http://www.jianshu.com/p/1443fa4036dc
本文出自 TigerChain 简书 Android 系列

教程简介

  • 1、阅读对象
    本篇教程适合新手阅读,老手直接略过
  • 2、教程难度
    初级
  • 3、Demo 地址
    稍后 提供

正文

Service 是一个神奇的东西,它可以执行长时间后台任务,并且在后台默默无闻的工作着,没有显示界面(也不露脸去表现自己),说到这里大家可能会想到,fk–Thread 也能干这样的事呀?那 Service 存在有必要吗?在这一节里这些问题都会一一解答

一、什么是 Service

在讲解 Service 之前我们先要知道什么是 Service,按照惯例,直接拿官方的解释来看

Service is an application component that can perform long-running operations in the 
background and does not provide a user interface ...

大体就是说 Service 是一个 Android 系统的组件可以在后台执行一些长时间操作并且没有用户界面

Service一个进程吗?是一个线程吗?

Service 既不是一个进程,也不是一个线程,而且它默认是工作在主线程中的,有的同鞋会想运行在主线程?我们都知道在主线程进行耗时操作会 ANR 的,Service 是用来执行耗时操作的难道不会 ANR 吗,如果不会 难道 ANR 的机制还有两套,如果会,那么 Service 为什么还能执行耗时操作,别急这些疑问慢慢解答

我们从官方描述可以知道 Service 是不依赖 UI 运行有后台用来执行耗时操作的一个的系统组件,我们知道 Thread 就是用来执行耗时操作的,那么 Android 有必要要有 Service 这个东西吗?Thread 就够了呀,答案是非常有必要有,因为存在就有必要(靠~~ 说了等于没有说 _ )

  • Service 和 Thread 的关系
    Service 和 Thread 没有半毛钱关系,如果非要说有关系,也就是组合使用的关系,我们对Service 和 Thread 产生混淆的主要一点是由于官方说了 Service 是后台任务来处理一些长时间的操作,这和 Thread 的功能非常类似,懂Handler的朋友尤其明白。其实 Thread 和 Service 的后台意思不太一样,前者是指不依赖UI后者运行在一个工作线程的后台任务,而且 Service 是运行在主线程中的,一个主线程一个子线程程肯定两者之间没有半毛钱关系(除非用于线程间通信),至于为什么 Service 运行在主线程,我们后面再说

  • Service 的特点
    (1)、优先级高于 Acitivity,一般情况下即使 Activity 销毁了 Service 任然可以运行,Service 优先级也高于 Activity 所创建的 Thread,优先级高就不会轻易被系统杀死,除非非常有必要
    (2)、Service 有自己的生命周期,这样的话就很好控制,而 Thread 的生命周期一般是依赖它被启动的环境中

二、Service 的创建和启动方式

Service 的创建

/**
 * Created by TigerChain.
 */
public class MyService extends Service {
    private static final String TAG = MyService.class.getSimpleName();
    @Override
    public void onCreate() {
        Log.e(TAG,"onCreate") ;
        super.onCreate();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand") ;
        return super.onStartCommand(intent, flags, startId);
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onDestroy() {
        Log.e(TAG,"onDestroy") ;
        super.onDestroy();
    }
}

Service 是系统组件,既然是系统四大组件之一,那么就要在 Mainfest 中去声明

<service android:name="com.jun.servicedemo.MyService"></service>

在MainActivity中声明一个按钮并用绑定事件

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private static final String TAG = MainActivity.class.getSimpleName();
    private Button start_service ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView() ;
    }
    private void initView() {
        start_service = (Button) this.findViewById(R.id.start_service) ;
        start_service.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
           case  R.id.start_service:
               Intent startServiceIntent = new Intent(MainActivity.this,MyService.class) ;
               startService(startServiceIntent) ;
               break ;
        }
    }
}

然后通过startService()来启动Service

Intent startServiceIntent = new Intent(MainActivity.this,MyService.class) ;
startService(startServiceIntent) ;

然后我们运行并查看结果,如下图所示

start_service_first.png

我们清楚的看到了,首次点击 startService 按钮的时候会依次调用 MyService 的 onCreate–>onStartCommand 方法

我们再点击 startService 按钮,图如下

start_service_secondandmore.png

以后不管再调用多少次 StartService 方法都只会调用 onStartCommand 方法

我们在 Myservice 和 MainActivity 的 onCreate 中分别加入一条 Log 信息

//用来获取当前类所在线程
 Log.e(TAG,Thread.currentThread().getId()+"") ;

再次运行,查看 Log

service_thread.png

神奇吧,居然 Service 和 Activity 是在同一个线程中,Activity 是在 UI 线程(主线程中),所以Service 默认也是在主线程中的,这就回答了上面的 Service 不是线程,也不是进程,它运行在主线程中,所以它执行耗时操作肯定会 ANR ,如何解决,答案是在 Service 中开启一个 Thread 来执行耗时操作,当然还可以有别的办法,我们后面再说

Service 的 ANR

我们来模拟一个 Service ANR 的效果,我们在 MyService 的 onStartCommand() 方法中添加如入代码

try {
 //模拟耗时操作
    Thread.sleep(80000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

当我们点击 startService 按钮的时候会发现先卡顿一小会作,然后就会报 ANR,典型的主线程进行耗时操作所带来的问题,如下图:

service_anr.png

那么如何解决呢,肯定要开子线程去处理耗时操作,代码修改

@Override
   public int onStartCommand(Intent intent, int flags, int startId) {
       Log.e(TAG,"onStartCommand") ;
      //这里启用一个子线程用来处理耗时操作
       new Thread(new Runnable() {
           @Override
           public void run() {
               try {
                   //模拟耗时操作
                   Thread.sleep(80000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }).start();
       return super.onStartCommand(intent, flags, startId);
   }

这样处理以后,就永远不会 ANR 了

总结:Service 运行在主线程中,如果想要操作耗时操作必须在 Service 中开启子线程去处理。

调用 startService 以后,如果想要停止 Service 一定要手动调用 stopService 来停止,我们可看看 Service 的启动方式

Service的启动方式

  • 1、 startService
    通过上面的例子,我们已经了解了如何使用 startService 来启动一个 Service 了

  • 2、bindService
    按照官网的说法就是通过 bindService 可以创建一个客户端接口来和 Service 交互,并且还可以通过 aidl 来实现进程间通讯。

下面我们我们用实例来看看 bindService

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private static final String TAG = MainActivity.class.getSimpleName();
    private Button start_service ,stop_service ,bind_service,unbind_service;
    private MyService.MyBinder myBinder ;
    //服务是否绑定的标志位
    private boolean isBind ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView() ;
        Log.e(TAG,"所在的线程id:"+Thread.currentThread().getId()+"") ;
    }
    private void initView() {
        start_service = (Button) this.findViewById(R.id.start_service) ;
        start_service.setOnClickListener(this);
        stop_service = (Button) this.findViewById(R.id.stop_service) ;
        stop_service.setOnClickListener(this);
        bind_service = (Button) this.findViewById(R.id.bind_service) ;
        bind_service.setOnClickListener(this);
        unbind_service = (Button) this.findViewById(R.id.unbind_service) ;
        unbind_service.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
           case  R.id.start_service:
               Intent startServiceIntent = new Intent(MainActivity.this,MyService.class) ;
               startService(startServiceIntent) ;
               break ;
            case  R.id.stop_service:
                Intent stopServiceIntent = new Intent(MainActivity.this,MyService.class) ;
                stopService(stopServiceIntent) ;
                break ;
            case  R.id.bind_service:
                Intent bindServiceIntent = new Intent(MainActivity.this,MyService.class) ;
                bindService(bindServiceIntent,myServiceConnection, BIND_AUTO_CREATE) ;
                break ;
            case  R.id.unbind_service:
                if(isBind){
                    unbindService(myServiceConnection);
                    isBind = false;
                }
                break ;
        }
    }
    private ServiceConnection myServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //和服务连接的时候调用
            myBinder = (MyService.MyBinder) service ;
            myBinder.progressLongTimeTask();
            isBind = true ;
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            //服务断开的时候调用(由于异常时断开) Service被停止或被系统杀死的时候调用
            Log.e(TAG,"service 断开") ;
            myBinder = null ;
        }
    } ;
}

从代码中可以看到,我们添加了两个按钮 bind_service 和 unbind_service 并添加相应的点击事件,我们在调用 bindService(Intent service, ServiceConnection conn,int flags) 的时候需要传入三个参数,第一个是 Intent,第二个是服务的连接类,第三个是标志位,这里传入 BIND_AUTO_CREATE 表示 Activty 和 Service 建立关联后自动创建 Service

相应的我们的 MyService 也要添加代码

/**
 * @author TigerChain
**/
public class MyService extends Service {
    private static final String TAG = MyService.class.getSimpleName();
    private MyBinder myBinder = new MyBinder() ;
    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate") ;
        Log.e(TAG,"所在的线程id:"+Thread.currentThread().getId()+"") ;
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand") ;
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模拟耗时操作
                    Thread.sleep(80000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"IBinder") ;
        return myBinder;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy") ;
    }
     
    class  MyBinder extends Binder{
          //这里模拟耗时任务
        public void progressLongTimeTask(){
            Log.e(TAG,"处理耗时任务") ;
        }
    }
    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG,"onUnbind") ;
        return true ;
    }
    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
        Log.e(TAG,"unbindService") ;
    }
    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        Log.e(TAG,"onRebind") ;
    }
}

先来大概解释一下,在这里我们定义了一个 MyBinder 类来继承自 Binder,Binder 是 IBinder 的一个实现类,然后在 MyService 的 onBind() 方法中返回这个类的实例,当我们调用 bindService 的方法的时候就会触发 onBind() 方法把这个 MyBinder 类的实例返回去,返回到那里呢?就是MainActivity 中的 onServiceConnected 中的 IBinder 中,然后我们就可以在 Activity 中拿到 Binder 了,然后就可以为所欲为了…

private ServiceConnection myServiceConnection = new ServiceConnection() {
      @Override
      public void onServiceConnected(ComponentName name, IBinder service) {
          //和服务连接的时候调用
          myBinder = (MyService.MyBinder) service ;
          myBinder.progressLongTimeTask();
          isBind = true ;
      }
      @Override
      public void onServiceDisconnected(ComponentName name) {
          //服务断开的时候调用(由于异常时断开) Service被停止或被系统杀死的时候调用
          Log.e(TAG,"service 断开") ;
          myBinder = null ;
      }
  } ;

废话少说,我们点击 bindService 和 unbindService 按钮来看看 Log 信息

bind_service.gif

从 gif 图中我们清楚的看到了点击 bindservice 按钮依次调用 MyService 的 oncreate()->onBind()方法,而点击 unbindservice 按钮会依次调用 MyService 的 onUnbind()-> onDestroy() 方法,unbindservice 按钮如果我们点击多次就会报错,说没有注册 Service,我们是程序员当然对这种异常是0容忍的,解决办法上面代码中已经体现,在 MainActivity 中添加一个标志位 isBind 然后判断一下即可,当然你也可以有自己的解决方案

细心的朋友们发现我们重写了 Service 的 onRebind 方法,那么这个方法有什么卵用,何时调用,首先 onRebind 方法的调用必须满足两个条件,我们来看这个方法注释中杂说

/**
   * Called when new clients have connected to the service, after it had
   * previously been notified that all had disconnected in its
   * {@link #onUnbind}.  This will only be called if the implementation
   * of {@link #onUnbind} was overridden to return true.
   * 
   * @param intent The Intent that was used to bind to this service,
   * as given to {@link android.content.Context#bindService
   * Context.bindService}.  Note that any extras that were included with
   * the Intent at that point will <em>not</em> be seen here.
   */
  public void onRebind(Intent intent) {
  }

尼玛,啥求意思呀,总结起来就两点,也就是 onRebind() 调用满足的条件

  • 服务被绑定后没有销毁
  • onUnbind 方法必须返回值为 true

从上面的代码中可知,我们第二个条件是满足了,我们给 onUnbind 方法手动的返回了 true,第一种情况就要配合 startService 了,我们来看这种情况,为了清楚的看日志信息,我把无关的 Log 注释掉了,如下:

onrebind_service.gif

如 gif 图我们依次调用了 bindService->startService,然后再调用 unBindService 然后再调用 bindService,这样就调用了 ServiceonRebind 方法,以后只要不调用 stopService 方法,重复调用 unBindService 和 bindService 都会执行 onRebind 方法。在这里我们获取到了一个重要信息,就是当调用了 startServcie 再调用 bindService 的时候,如果再调用 unBindService 是没有销毁 SerVice 的,不然的话 onRebind 方法是不会调用的,关于 startServicebindService调用同一个 Service 的情况我们后面讨论

三、当 bindService 遇上 startService

  • 1、先看先 startService->bindService->unbindService->stopServicestartService->bindService->stopService->unBindService 这两种情况

无图无直相,直接上图

startandbind_service.gif

图中我们把上面说的两种情况都实现了:

(1)、首先看 startService->bindService->unbindService->stopService这种情况:我们清楚的看到依次调用 Service 的
onCreateonStartCommandonBindonUnbindonDestory 方法,以下分别是 startServicebindService 对应的方法

startService: onCreate,onStartCommand onDestory
bindService:onBind,onUnbind

先调用 unbindService 再调用 stopService,会分别调用 ServiceonUnbind 方法和 onDestory 方法

(2)、其次看 startService->bindService->stopService->unBindService 这种情况:分别依次调用了 Service 的 onCreateonStartCommandonBindonUnbindonDestory 我肋个去和上面一毛一样,别急我们慢慢看,以下分别是 startServicebindService 对应的方法

startService: onCreate,onStartCommand
bindService:onBind,onUnbind onDestory

看到区别了没,如果先调用 stopService 再调用 unBindService 前者任何 log 都不打,只是把 Service 暂停了,再调用 unBindService 的时候会依次调用 Service 的 onUnbindonDestory 方法

  • 2、再看 bindService->startService->unbindService->stopServicebindService->startService->stopService->unBindService 这两种情况,小二,上图:
bindsandstart_service.gif

图中我们看到两种情况都实现了

(1)、首先行看 bindService->startService->unbindService->stopService 情况会依次调用 Service 的 onCreate->onBind->onStardCommand->onUnbind->onDestory

以下分别是 bindServicestartService 调用方法

bindService: onCreate, onBind ,onUnbind
startService: onStardCommand , onDestory

解释一下,先调用 bindService 会触发 Service 的 onCreate 方法和 onBind 方法,再调用 startService 会调用 onStardCommand 方法,再调用 unbindService 方法会调用 Service 的 onUnbind 方法,最后调用 stopService 会调用 Service 的 onDestory 方法

(2)、再看 bindService->startService->stopService->unBindService 情况会依次调用 Service 的 onCreate->onBind->onStardCommand->onUnbind->onDestory

以下分别是 bindServicestartService 调用方法

indService: onCreate, onBind ,onUnbind,onDestory
startService: onStardCommand

区别就是先调用 stopService 什么方法都没有调用没有日志信息,只是把 Service 暂停了,再调用 unBindService 方法会依次调用 onUnbindonDestory 方法

四、总结:

  • 1、startServicebindService 可以启动同一个 Service
  • 2、startServicebindService 启动同一个 Service 的时候如果想销毁 Service 就既要调用stopService 又要调用 unBindService 方法,先后顺序无关,但是最后成对调用

到此为止,我们就就把 Service 的基本用法说说完,一定要亲自试试哦

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容