Service详解(基础篇)

如需转载请评论或简信,并注明出处,未经允许不得转载

目录

前言

Service是Android四大组件之一,但是由于实际开发中可能没有Activity使用的频繁,有的同学对Service的一些使用和某些细节可能不是特别清楚。没关系,这篇文章将带你更加全面的了解Service

为什么要使用Service

从字面意思上看来,Service是服务的意思,我们很容易联想到它是一个帮助我们在后台完成一些任务的组件,那么它和Thread有什么区别呢?

Thread的定义

Thread:程序执行的最小单元,它是分配CPU的基本单位

Thread的生命周期

  1. 新建,new Thread()

  2. 就绪,排队等候cpu资源

  3. 运行,run()

  4. 死亡,执行完毕或被杀死

  5. 阻塞,由于某种原因,正在运行的线程让出cpu,并暂停执行

Thread的特点(重要)

Thread子线程独立于ActivityActivity很难对线程进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作

当我们需要使用Thread连续不停地每隔一段时间就要连接服务器一次做某种同步,那么这个Thread就需要在Activity退出后也运行,同时需要对这个Thread进行控制,这时候我们要怎么办?

没错!使用Service就可以解决了

什么是Service?

  1. Service既不是一个线程,Service通常运行在当成宿主进程的主线程中,所以在Service中进行一些耗时操作就需要在Service内部开启线程去操作,否则会引发ANR异常
  2. 也不是一个单独的进程,除非在清单文件中声明时指定进程名,否则Service所在进程就是application所在进程
  3. Service可以不需要UI就在后台运行,不用管开启它的页面是否被销毁,只要进程还在就可以在后台运行
  4. Service是Android中的四大组件,使用它一定要在AndroidManifest.xml中声明

Service的启动方式与生命周期

Service有三种启动方式

  1. startService

  2. bindService(类似于C/S结构)

  3. startService&bindService同时调用

service生命周期

startService

startService步骤

  1. 定义一个类继承Service
  2. Manifest.xml文件中配置该Service
  3. 使用ContextstartSerice(intent)方法启动该Service
  4. 不再使用时,调用stopService(intent)方法停止该服务
private Intent mStartIntent;

public void btnStartService(View view) {
    mStartIntent = new Intent(this, MyService.class);
    startService(mStartIntent);
}

public void btnStopService(View view) {
    if (mStartIntent != null) {
        stopService(mStartIntent);
    }
}

startService特点

  1. startService方式启动Service不会调用到onBind()

  2. startService可以多次调用,但是只有第一次调用才会执行onCreate(),之后每次调用都会直接执行onStartCommand()onStartCommand()执行次数与startService调用次数一致

  3. 不管调用多少次startService,只需要调用一次stopService就结束

  4. 如果startService后没有调用stopSelf或者stopService,则Service一直存活并运行在后台,直到进程被杀死

  5. Service中onStartCommand方法返回值

bindService

bindService步骤

  1. 定义一个类继承Service
  2. Manifest.xml文件中配置该Service
  3. 创建Binder子类,返回给客户端即Activity使用,提供数据交换的接口
  4. ServiceonBind()中返回创建的Binder子类对象
  5. Activity中定义用于接收BinderServiceConnection
  6. onServiceConnected()中拿到Service对象后,就可以调用Service内部的公有方法
  7. 使用ContextbindSerice(intent,ServiceConnection,flags)方法启动该Service
  8. Activity被销毁,或者主动掉调用unbindService(),Service的生命周期结束
    //创建Binder子类,返回给客户端即Activity使用,提供数据交换的接口
    public class MyBinder extends Binder {
        //声明一个方法,getService。(提供给客户端调用)
        MyService getService() {
            return MyService.this;
        }
    }

        //在Service的onBind()中返回创建的Binder子类对象
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind");
        return new MyBinder();
    }
        
        //在Activity中定义用于接收Binder的ServiceConnection
    mConn = new ServiceConnection() {
        /**
         * 与服务端交互的接口方法,绑定服务的时候被回调,单这个方法获得绑定Service传递过来的IBinder对象
         * 通过IBinder对象,实现宿主和Service交互
         * */
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.i(TAG, "绑定成功调用:onServiceConnected");
            //获取Binder
            MyService.MyBinder binder = (MyService.MyBinder) iBinder;
            //拿到Service对象后,就可以调用Service内部的公有方法
            mService = binder.getService();
        }

        /**
         * 正常情况下是不被调用的,他的调用时机是当Service服务被意外销毁时
         * 进程crash或被kill
         * */
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.i(TAG, "服务被销毁:onServiceDisconnected");
        }
    };

        //bindService
    public void btnBindService(View view) {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mConn, Service.BIND_AUTO_CREATE);
    }

        //unbindService
    public void btnUnBindService(View view) {
        if (mService != null) {
            mService = null;
            unbindService(mConn);
        }
    }


bindService特点

  1. 由于bindService是异步执行的,所以需要额外构建一个ServiceConnection对象用与接收bindService的状态,同时还要指定bindService的类型
  2. bindService()也可以调用多次,与startService()不同,多次调用bindService()onBind()onServiceConnected()只执行一次
  3. 主动调用unbindService()并不会回到onServiceDisconnected(),只有当Service服务被意外销毁时(如进程crash或者内存资源不足被kill),这个方法才被自动调用。可以用adb shell kill pidkill service来测试
  4. 通过bindService()方式启动ServiceActivity可以拿到Service对象,所以可以调用Service内部的公有方法

startService&bindService

针对同一个Service,我们既可以是启动服务的状态(即启动后无限运行下去直到进程被杀死),也可以绑定服务状态(即绑定对象销毁,服务也销毁),那么就可能会出现启动服务和绑定服务的先后顺序问题

  1. startService(),后bindService()

启动服务状态不会转化为绑定服务状态,即使调用unbindService()后,服务依然会以启动服务状态在后台运行,直到收到stopService()指令

  1. bindService(),后startService()

绑定服务状态转化为启动服务状态,即使之前绑定的Activity销毁了,服务还是会继续运行下去,直到收到stopService()指令

小结

  1. 启动服务状态的优先级会高于绑定服务端状态,因为绑定服务是要依托于我们的Activity的,而启动服务只需要依靠Service本身以及内存的一些情况

  2. 不管先后顺序如何,Android系统仅仅会为一个Service创建一个Service对象,所有操作的都是同一个Service对象

  3. 当同时调用startService()bindService()后,需要分别调用stopService()unbindService()Service才会走onDestroy()

一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁

异步Service — IntentService

  1. Service通常运行在当成宿主进程的主线程中,所以在Service中进行一些耗时操作就需要在Service内部开启线程去操作,否则会引发ANR异常
  2. 通过startService形式开启Service时,如果不主动调用stopServiceService将在后台一直运行

为了解决这个两个问题,Android系统帮我们封装了IntentService类。下面我们通过源码来看看IntentService是怎么做的

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

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

        @Override
        public void handleMessage(Message msg) {
            //子类重写onHandleIntent执行异步操作
            onHandleIntent((Intent)msg.obj);
            //执行完成后
            stopSelf(msg.arg1);
        }
    }

    public IntentService(String name) {
        super();
        mName = name;
    }
  
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //HanderThread是Thread类的子类,内部封装了Looper,方便我们在子线程中使用Handler
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        mServiceLooper = thread.getLooper();
        //把HandlerThread持有的looper对象转交给mServiceHandler
        //即 mServiceHandler就可以执行异步任务了
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    //onStartCommand之后执行
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        //发送消息
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

IntentService内部有一个工作线程HanderThread,(有关HanderThread的知识可以看HandlerThread使用及源码解析
),并通过这个HandlerThread中的looper创建了一个Handler对象,然后将Intent以Message的方式从主线程发送到子线程,这样onHandleIntent()就在子线程中执行,可以执行耗时操作

相同点:

  1. 继承自Service,与Service的启动方式是一样的,具有Service各种特性

  2. IntentService也可以多次启动,但是每次启动会将任务添加到工作队列中,每次只会执行一个工作线程

不同点:

  1. 在使用时将不再需要实现onStartCommand(),同时需要实现onHandleIntent()来执行异步任务
  2. 当消息队列中所有任务执行完成后,IntentService会在内部自动调用stopSelf()关闭自己,不用手动调用stopService()

总结

本文讲了ServiceThread的区别与应用场景,Service的启动方式以及不同启动方式分别对应的生命周期,同时也介绍了用于异步任务处理的IntentService类。对于Service原理感兴趣的可以继续看Service详解(原理篇)

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

推荐阅读更多精彩内容