Android宝典|Service必考知识点总结

目录

  1. 思维导图
  2. 概述
  3. 启动方式及生命周期
    • startService 方式启动
    • bindService 方式启动
    • 混合启动
  4. 疑问
    • startCommand 返回值含义
    • bindService 第三个参数含义
  5. startForegroundService
    • FOREGROUND_SERVICE 权限申请
    • onStartCommand 必须调用 startForeground 构造通知栏
    • 非绑定服务
  6. 常见问题汇总
    • 如何保证 Service 不被杀死
  7. 参考

思维导图

image

概述

Service 是一种可以在后台长时间运行而没有用户界面的应用组件。它有两种启动方式,第一种方式是 startService 启动服务,服务启动后,会在后台无限期运行,直到通过 stopService 或者 stopSelf 停止服务。另一种方式是 bindService 绑定服务,组件与服务绑定在一起,服务的生命周期受组件影响,如果组件被销毁了,服务也就停止了。

Service 是运行在主线程中的,因此不能执行耗时任务,如果想执行耗时任务,可以使用 IntentService。

启动方式及生命周期

image
startService 方式启动

当应用组件通过 startService 方法来启动 Service 时,Service 则会处于启动状态,一旦服务启动,它就会在后台无限期的运行,生命周期独立于启动它的组件,即使启动它的组件已经销毁了也不受任何影响,由于启动的服务长期运行在后台,这会大量的消耗电量,因此,我们应该在任务执行完之后调用 stopSelf 来停止服务,或者通过其他应用组件调用 stopServcie 来停止服务。

通过这种方式启动 Service 生命周期是:

onCreate、onStartCommand、onDestory。

首次启动服务的时候,系统会调用 onCreate 方法,多次启动不会在调用 onCreate 方法,只会调用 onStartCommand,onStartCommand 被调用后,服务就启动起来了,将会在后台无限期的运行,直到通过 stopService 或者 stopSelf 方法来停止服务。当服务被销毁时,将会回调 onDestory 方法。

多次调用 startService(Intent) 会回调 onStartCommand 方法,而多次调用 stopService 只有第一次会回调 onDestory 方法。

bindService 方式启动

即将启动组件和服务绑定在一起,前面讲的通过 startService 方式启动的服务是与组件相独立的,即使启动服务的组件被销毁了,服务仍然在后台运行不受干扰,但是通过 bindService 方式绑定的服务就不一样了,它与绑定组件的生命周期是有关的。

多个组件可以绑定到同一个服务上,如果只有一个组件绑定服务,当绑定的组件被销毁时,服务也就会停止了。如果是多个组件绑定到一个服务上,当绑定到该服务的所有组件都被销毁时,服务才会停止。

通过这种方式启动 Service 生命周期是:

onCreate、onBind、onUnbind、onDestory

onBind():当其他组件想通过 bindService 与服务绑定时,系统将会回调这个方法,在实现中,必须返回一个 IBinder 接口,供客户端和服务进行通信,必须实现此方法,这个方法是 Service 的一个抽象方法,但是如果不允许绑定,返回 null 就好了。

onUnbind:当所有与服务绑定的组件都解除绑定时,就会调用此方法。

多次 bindService 并不会回调任何方法,多次 unBindService 则会 Crash。

实例:

  1. 创建服务

    其实就是在 onBind 方法中返回 IBinder 实例

    public class MyBindService extends Service {
    
        private static final String TAG = "MyBindService";
    
        @Override
        public void onCreate() {
            super.onCreate();
            Log.i(TAG, "onCreate: ");
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.i(TAG, "onStartCommand: ");
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            Log.i(TAG, "onBind: ");
            return new MyBinder();
        }
    
        @Override
        public boolean onUnbind(Intent intent) {
            Log.i(TAG, "onUnbind: ");
            return super.onUnbind(intent);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.i(TAG, "onDestroy: ");
        }
    
        public class MyBinder extends Binder{
            public String getInfo(){
                return "Info";
            }
        }
    }
    
  2. 绑定服务

    绑定服务需要提供一个 ServiceConnection 接口,在接口回调中获取 Binder 对象,与服务进行通信。

        private MyBindService.MyBinder mMyBinder;
        //绑定/解除绑定 Service 回调接口
        private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                //绑定成功后回调
                //1.获取 Binder 接口对象
                mMyBinder = (MyBindService.MyBinder) service;
                //2.从服务获取数据
                String content = mMyBinder.getInfo();
                //3.界面提示
                Toast.makeText(MainActivity.this, content, Toast.LENGTH_SHORT).show();
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                //解除绑定后回调
                mMyBinder = null;
            }
        };
        
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        unbindService(mConnection);
    
混合启动

如果一个服务被启动又被绑定,onCreate 方法只会执行一次,startService 调用多少次,onStartCommand 就会执行多少次,调用 stopService 并不会回调 onDestory,unBindService 可以。

疑问

onStartCommand 返回值

返回值的取值其实已经定义在了 Service 基类中了,常用的有:

  • START_NOT_STICKY

    如果系统在 onStartCommand 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。

  • START_STICKT

    如果系统在 onStartCommand 返回后终止服务,则会重建服务并调用 onStartCommadn,但绝对不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务,否则系统会通过空 Intent 调用 onStartCommand。这适用于不执行命令、但无限期运行并等待作业的媒体播放器等。

  • START_REDELIVER_INTENT

    如果系统在 onStartCommand 返回后终止服务,则会重建服务,并通过传递给服务等最后一个 Intent 调用 onStartCommand。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业的服务,例如下载文件。

bindService 参数
    public boolean bindService(Intent service, ServiceConnection conn,
            int flags) {
        return mBase.bindService(service, conn, flags);
    }

前两个参数就不用多说了,第三个参数一般传 Context.BIND_AUTO_CREATE。

它的值是一些枚举值,

startForegroundService

单独拿出来说,逼格高吧。

回到正题,最开始使用这个方法时,不是 Crash 就是 ANR,头大!

首先使用前台服务,必须申请 FOREGROUND_SERVICE 权限,这是普通权限,未申请则会引发 SecurityException。

ANR 的解决

使用前台服务,必须提供一个通知栏,不然五秒就会 ANR。

    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: ");
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        // 通知渠道的id
        String id = "my_channel_01";
        // 用户可以看到的通知渠道的名字.
        CharSequence name = "Demo";
        // 用户可以看到的通知渠道的描述
        String description = "Desc";
        int importance = NotificationManager.IMPORTANCE_HIGH;
        NotificationChannel mChannel = new NotificationChannel(id, name, importance);
        // 配置通知渠道的属性
        mChannel.setDescription(description);
        // 设置通知出现时的闪灯(如果 android 设备支持的话)
        mChannel.enableLights(true);
        mChannel.setLightColor(Color.RED);
        // 设置通知出现时的震动(如果 android 设备支持的话)
        mChannel.enableVibration(true);
        mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
        mNotificationManager.createNotificationChannel(mChannel);

        // 通知渠道的id
        String CHANNEL_ID = "my_channel_01";
        // Create a notification and set the notification channel.
        Notification notification = new Notification.Builder(this, CHANNEL_ID)
                .setContentTitle("New Message").setContentText("You've received new messages.")
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .build();
        startForeground(1, notification);
        return super.onStartCommand(intent, flags, startId);
    }

准确的来说,应该是必须调用 startForeground 方法。

既然我是写在 onStartCommand 方法里面,就说明这是一个启动服务非绑定服务,所以可以多次调用 startForegroundService 方法,调用一次 stopService 即可停止服务。

Crash 的解决

Crash 的报错如下:

android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord

这是由于我在 startForegroundService 之后就 stopService,在 Service 中并没有构造通知栏导致的。

总结

使用前台服务,有三点需要注意:

  1. 申请 FOREGROUND_SERVICE 权限,它是普通权限
  2. 在 onStartCommand 中必须要调用 startForeground 构造一个通知栏,不然 ANR
  3. 前台服务只能是启动服务,不能是绑定服务

常见问题汇总

  1. 如何保证 Service 不被杀死
    • 在 Service 的 onStartCommand 中返回 START_STICKY,该标志使得 Service 被杀死后尝试再次启动 Service
    • 提高 Service 优先级,比如设置成前台服务
    • 在 Activity 的 onDestory 发送广播,在广播接收器的 onReceiver 重启 Service

参考

Service 知识总结

Android Service和IntentService知识点详细总结

Context.startForegroundService() did not then call Service.startForeground?

developer服务概览

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

推荐阅读更多精彩内容