service服务知识点总结

定义

service Android 中的服务,可以理解为一个没有界面的 activity,存在的意义就是运行本应该放在后台执行的代码,比如下载,音乐播放。

activity 也可以执行后台多线程代码,那为啥还要有 service 这个系统组件,就是因为进程优先级啊,运行 service 服务的进程优先级比运行 activity 的进程高,不容易被杀死,可以获得更久,这就是 service 存在的最大意义

注意服务默认是跑在 mian 主线程里的,使用 content 启动服务不用添加 newTask flag,因为服务没有界面的

进程优先级相关看这里:android 进程相关

service 的 xml 设置属性如下:

<service android:enabled=["true" | "false"]
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:isolatedProcess=["true" | "false"]
    android:label="string resource"
    android:name="string"
    android:permission="string"
    android:process="string" >
    . . .
</service>
  • android:exported
    是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。

  • android:name
    对应Service类名

  • android:permission
    是权限声明

  • android:process
    是否需要在单独的进程中运行,当设置为android:process=”:remote”时,代表Service在单独的进程中运行。注意“:”很重要,它的意思是指要在当前进程名称前面附加上当前的包名,所以“remote”和”:remote”不是同一个意思,前者的进程名称为:remote,而后者的进程名称为:App-packageName:remote。

  • android:isolatedProcess
    设置 true 意味着,服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。与其通信的唯一途径是通过服务的API(bind and start)。

  • android:enabled
    是否可以被系统实例化,默认为 true因为父标签 也有 enable 属性,所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。

生命周期

@Override
    public void onCreate() {
        super.onCreate();
    }

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

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

上面这5种就是 service 基本的5中生命周期了,根据 service 启动方式的不同,会走不同的生命周期,但是 onCreate / onDestroy是必会执行的,具体的下面跟着启动方式说。

service 全部的生命周期如下:


Snip20171021_2.png

服务的启动方式

服务的启动方式有2种:

  • startService:直接启动

    • 这样启动的服务生命周期比 app 还长,同启动该服务的 activity 没有任何关系了,只要不被系统回收,就会一直存在
    • startService 启动的服务所在进程理论上算是服务进程,但是若这个进程含有 activity 页面,那么实际上不算服务进程,只有启动的是前台服务的进程优先级才可以忽略 activity
    • 我们无法和 startService 启动的服务通信,因为 startService 方法没有相关的返回值
  • bindService:绑定启动

    • 绑定启动的服务生命周期是和启动该服务的 activity 的生命周期一致,activity 关闭了这个 service 服务也就销毁了
    • 还有绑定服务就解绑服务是会直接报错的,注意!
    • 系统建议我们在页面关闭时解绑这个页面使用绑定方式启动的服务
    • bindService 启动的服务,我们可以拿到一个 ibander 类型的对象,这是我们启动的 service 服务返回给我们的,这样就可以和启动的这个服务进行通讯

startService 直接启动方式

startService 启动方式的代码很简单,使用 intent 就可以启动和关闭这个服务,当然我们没法和这个服务通信。因为service 服务是没有界面的,所以使用 content 的 startService 时,不用加 newTask flag 标记。


Intent intent = new Intent(this, MyService.class);

// 启动服务
startService(intent);

// 关闭服务
 stopService(intent);

当然了,为啥代码这个简单就可以呢,因为我们启动服务的工作都不是我们来做的,都是我们去和 AMS(ActivityManageService) 通信,由 AMS 来实现的。

注意点:

  • 当我们多次 startService 同一个 service 服务,当这个服务已经启动时,我们再去启动同一个服务,那么会触发 服务的 onStartCommand 生命周期
 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand...");
        return super.onStartCommand(intent, flags, startId);
    }

所以 startService 方式启动的 service 生命周期如下:
onCreate(Client首次startService(..)) >> onStartCommand >> onStartCommand - optional ... >> onDestroy(Client调用stopService(..))

Started Service Client 与 Service 通信相关:

  • 当Client 调用 startService(Intent serviceIntent) 启动Service 时,Client可以将参数通过Intent直接传递给Service
  • Service 执行过程中,如果需要将参数传递给Client,一般可以通过借助于发送广播的方式(此时,Client需要注册此广播)

bindService 绑定启动方式

bindService 的方式启动服务有些繁琐,但是是必须的,因为 service 支持 IPC ,跨进程通讯的。

  1. 创建 MyServiceConnection 专用与于 service 通信的连接对象
// 这个链接类一般声明为内部类即可
public class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myControl = (MyService.IMyControl) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }

  1. 创建链接对象,声明为全局变量,因为我们 接触绑定还是需要这个链接对象的

// 声明全局变量
MyServiceConnection connection;

// 创建链接对象
connection = new MyServiceConnection();

// 开始绑定服务,flag是标记,标记这个 service 的重启模式,我们一般使用 BIND_AUTO_CREATE 这个值,表示系统在因为内存不足 kill 这个 service 后,在内存充足后会重启这个 service
bindService(intent, connection,flag );  
  1. 定义我们具体的 service 服务对外通信接口
  interface IMyControl {

        void huniyilian();

    }
  1. 在setvice 类中定义继承 Binder类的具体实现类,并实现我们定义的具体对外通信接口。Binder 内部实现了 IBinder这个接口,IBinder 就是 service 默认 IPC 通信的ALDL 接口,Binder 是这个默认的 ALDL 的具体实现类,Binder 内部主要实现了数据的跨进程通讯方法,这就是我们进行跨进程通许的基础。
 class MyControl extends Binder implements IMyControl {

        public void huniyilian() {
            Log.d("AAA", "唬你一脸,哈哈啊");
        }
    }
 
  1. 在 service 类的 onBind 生命周期中返回我们定义的 Binder 实现类
 @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind...");
        return new MyControl();
    }
  1. 在 activity 中接受这个 IBinder 对象,然后强转成我们定义的具体接口类型,注意这个 IBinder 对象是在这个我们定义的服务链接对象中获取的
 public class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myControl = (MyService.IMyControl) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }
  1. 使用这个链接对象接触绑定
 if (connection != null) {
            unbindService(connection);
        }

步奏比较多,涉及3个核心类:

  • ServiceConnection
  • IBinder
  • Binder

我来解释下:

这个 ServiceConnection 接口也是android 定义的默认 ALDL 接口,因为在写法上我们看着是和我们绑定的服务直接获得链接,进行通讯,实际上不是这样的。android 所有的组件启动,通讯都是通过 AMS(ActivityManageService) 来完成的,这是android 系统进程中的一个服务,因为4大组件都有复杂的生命周期和复杂的交互,不是我们直接 new 一个对象就好使的,所以有系统来统一管理。所以呢,这个 ServiceConnection 就是一个特殊的和 AMS 进行 IPC 通讯的接口,然后 AMS 会中转我们和目标服务的通讯,作为一个中间件存在,就和电话局管理所有的电话新路一样,我们没必要去记住每根线路都通向谁

IBinder 同上也是一个 android 默认的 ALDL 接口,是 service 服务和 AMS 进行 IPC 通讯的接口, Binder 是实现了这个 IBinder ALDL 接口的类,内部封装具体的数据进行 IPC 通许的方法,为啥要有这个 IBinder 对象呢,因为这个就是给我们用来继承的,利用类的多态性,就可以把这个具体的对象在进程间传递了,注意这个是 伪进程间传递,不是真正把对象引用传过去(对象引用在进程是无法传递的),而是在目标进程中创建一个和我们这个继承了 IBinder 类的相同类型的类,然后我们可以强转成我们定义的通讯类进行调用了,真是是此类非彼类,我们称之为代理类 proxy,详细的可以去看参考文档的文章或是开发艺术探索这个书

全部代码如下:

service 类:

public class MyService extends Service {

    String TAG = "AAA";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate..." + Thread.currentThread().getName());
    }

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

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

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind...");
        return new MyControl();
    }

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

    class MyControl extends Binder implements IMyControl {

        public void huniyilian() {
            Log.d("AAA", "唬你一脸,哈哈啊");
        }
    }

    interface IMyControl {

        void huniyilian();

    }
}

activity类:

public class MainActivity extends AppCompatActivity {

    Intent intent;
    MyServiceConnection connection;
    MyService.IMyControl myControl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("AAA", "pid:" + Process.myPid());

        intent = new Intent(this, MyService.class);
        connection = new MyServiceConnection();
    }

    public void startService(View view) {
        startService(intent);
    }

    public void stopService(View view) {
        stopService(intent);
    }

    public void bindService(View view) {
        bindService(intent, connection, BIND_AUTO_CREATE);
    }

    public void unbinfService(View view) {
        if (connection != null) {
            unbindService(connection);
        }
    }

    public void huniyilian(View view) {
        if (myControl != null) {
            myControl.huniyilian();
        }
    }

    public class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myControl = (MyService.IMyControl) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }
}

bindService 的生命周期:

  • onCreate -> onBind -> onUnbind -> onDestroy

onStartCommand 方法的返回值

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

可以看到 onStartCommand 方法的返回值的,返回值的不同表示不同的意思,约束的是系统在内存不足 kill 改 service 之后的操作

具体有4个返回值:

  • START_NOT_STICKY
    当Service因为内存不足而被系统kill后,接下来未来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。除非程序中Client明确再次调用startService(...)启动此Service
  • START_STICKY
    当Service因为内存不足而被系统kill后,接下来未来的某个时间内,当系统内存足够可用的情况下,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand(...)方法,但其中的Intent将是null,pendingintent除外
  • START_REDELIVER_INTENT
    与 START_STICKY 唯一不同的是,回调 onStartCommand(...) 方法时,其中的Intent将是非空,将是最后一次调用startService(...)中的intent
  • START_STICKY_COMPATIBILITY
    此值一般不会使用,所以注意前面三种情形就好

AndroidManifest.xml中Service元素常见属性

  • andorid:name
    服务类名。可以是完整的包名+类名。也可使用.代替包名。
  • adroid:exported
    其他应用能否访问该服务,如果不能,则只有本应用或有相同用户ID的应用能访问。默认为false。
  • android:enabled
    标识服务是否可以被系统实例化。true--系统默认启动,false--不启动。(默认值为true)
  • android:label
    显示给用户的服务名称。如果没有进行服务名称的设置,默认显示服务的类名。
  • android:process
    服务所运行的进程名。默认是在当前进程下运行,与包名一致。如果进行了设置,将会在包名后加上设置的集成名。
    如果名称设置为冒号 :开头,一个对应用程序私有的新进程会在需要时和运行到这个进程时建立。如果名称为小写字母开头,服务会在一个相同名字的全局进程运行,如果有权限这样的话。这允许不同应用程序的组件可以分享一个进程,减少了资源的使用。
  • android:icon
    服务的图标。
  • android:permission
    申请使用该服务的权限,如果没有配置下相关权限,服务将不执行,使用startService()、bindService()方法将都得不到执行。

service 的进程优先级

详细的去看 android 进程相关 这篇博客,这里只说下特别注意点:

  1. 一个进程要是activity 和 service 都有,进程的优先级要看 activity 的,除非这个 service 是前台 service ,典型例子就是主线程中,我们启动一个 service,然后 home / back,这个按理说应该是一个服务进程,但实际上这是一个后台进程,优先级低容易被系统kill。

service常用启动方式

在项目开发中,我们最常用的service 启动方式就是 先 startService 把服务按长生命周期启动起来,再 bindService 绑定服务,建立和这个服务的通讯链接。这就是最常用的方法了,代码上没有特殊之处,按照上面的代码写法即可


前台进程

一般 service 启动后,这个 service 所在的进程最多是 优先级5的服务进程,有可能随着机型的不同,优先级可能会是8,优先级是8的时候其实是和 activiyt 一样了,很容易就被杀死,优先级是5的时候其实也是会被杀死的,只是比较不容易罢了,这里有一种叫前台服务的服务,是绝对不会被系统 kill 的,除非你手动 kill

前台服务还是也给服务,核心就是在 service 启动后,启动一个 notifacation 通知和这个 service 关联,因为notifacation 是一直在通知栏可见的,所以这个服务就变成了前台服务了,进程优先级会提高至 1 或 3,小于5的都是不会被系统 kill 的。

代码写法:

public class MyService extends Service {

    String TAG = "AAA";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate..." + Thread.currentThread().getName());
        Notification.Builder builder = new Notification.Builder(this);
//        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
//                new Intent(this, MainActivity.class), 0);
//        builder.setContentIntent(contentIntent);
        builder.setSmallIcon(R.mipmap.ic_launcher);
        builder.setTicker("前台进程");
        builder.setContentTitle("前台进程......");
        builder.setContentText("我是前台进程");
        Notification notification = builder.build();
        startForeground(1, notification);
    }

在 service 的任何生命周期函数里面使用这个方法 startForeground(1, notification) 就可以了。

注意点:

  • 这样写,在 SDK 18之前,在通知栏不是显示通知, 18之后就会显示一条通知了,这是系统默认的对用户的友好提示,要是不想让用户看到,要再处理
  • SDK 21 之后,新添加了 startForegroundService(intent) 方法,可以直接启动服务为前台进程了。

带通知界面的前台服务和前台通知的交互看这里:

如何启动一个没有Notification的前台服务呢

这是利用系统的 bug 了,据说这个把bug 在7.1修复了,但是还是可以易用不是,具体步奏:

  1. 编写2个 service ,A/B,B 就是消除通知的,注意 A和B 要使用同一个 通知的 id,核心就在这里
  2. 先启动 A 服务
  3. 再启动 B 服务,然后B 再关闭自己,利用前台服务在关闭后会同步关闭通知的特性,来实现没有通知显示的前台服务,这样 A 就达到目的了

具体的代码看这里: 一个没有Notification的前台服务


使用Messenger 实现跨进程通信

使用 Messenger 可以实现跨进程通信,这样不用我们编写 ALDL

  1. 服务实现一个 Handler,由其接收来自客户端的每个调用的回调
  2. Handler 用于创建 Messenger 对象(对 Handler 的引用)
  3. Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端
  4. 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用Messenger将 Message 对象发送给服务
  5. 服务在其 Handler 中(在 handleMessage() 方法中)接收每个 Message

具体例子参考:


最后

Android 5.0之后google出于安全的角度禁止了隐式声明Intent来启动Service。如果使用隐式启动Service,会出没有指明Intent的错误

service 的包活除了设置成前台服务之外,还可以在 onDestory 方法中再把自己跑起来,或者2个服务相互守护

具体写法参考:


参考资料

SuperBigLw:

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

推荐阅读更多精彩内容