android隐式绑定服务,Android service (一) startService vs bindService

service 简介

service 是Android的4大组件之一, 用于在后台(这里指的是service不提供UI, 用户不可见)执行任务.

service 是在UI线程上被创建, 并且在UI线程上运行的, 如果service需要执行耗时操作,那么需要自己开启线程或线程池,然后在子线程中执行.

service 有2种启动方式: startService & bindService (也可以同时使用这2种方式)

使用service VS 在其他组件中开启子线程

首先要说明的是service和thread是2个并不对等的概念, 这里之所以将它们放在一起比较, 是因为不少人在实际开发中会遇到这样的抉择: 是创建一个service去执行一个操作呢, 还是直接开启一个子线程?

service 和 thread 的区别

service

service是Android提供的一个基础组件, 当进程中有存活的service时, 那么这个进程至少拥有Service process级别的优先级(Android定义了5个级别的进程优先级). 因此这个进程退到后台后, 也不容易被回收.

当系统负载过高杀死该进程之后, 该service依然会在系统资源变得充足之后再次运行(如果onStartCommand方法返回START_STICKY或者START_REDELIVER_INTENT的话). 这就意味着即使包含service的进程可能会被系统杀死, 但相应的任务依然是有机会恢复执行的,

service具有高可复用性, 可以为不同的进程, 不同的应用提供服务

thread

thread并不是Android提供的概念, 只是一个单独的执行单元, 可以用于实现负载均衡. 在Android中使用thread的一个常见情形是避免ANR.

何时使用service, 何时使用thread

如果该任务的结果只在用户与其交互时有效(例如界面显示时有效, 界面隐藏或销毁之后就无效了), 那么适合用thread去执行.(例如在界面上显示一张远程图片, 就可以在子线程中去下载图片)

如果该任务在用户退到后台后仍然希望执行, 或者改任务并不直接影响到用户的交互, 那么适合使用service(通常在service中也要创建thread去执行).(例如下载服务)

startService VS bindService

startService 和 bindService 的简介

startService

通过startService启动的service将在后台持续运行,直到某个组件调用stopService,或者该service自己调用stopSelf,或者因为内存不足而被系统销毁. 这意味着调用方通常需要管理service的生命周期

通过这种方式启动的Service和启动它的组件之间没有绑定关系(可以理解为游离的),因此启动它的组件被销毁了,并不会影响到service的运行

onStartCommand的返回值对service的生命周期的影响.(仅当service没有正常终止而是被系统销毁,此方法的返回值才会有效)

START_STICKY: 系统会重新创建该service,并调用onStartCommand方法,传入的intent为null.(适用于需要长期存在的一些任务)

START_NOT_STICKY: 系统不会再创建该service, 除非其他组件start它. (通常建议使用它)

START_REDELIVER_INTENT: 系统会重新创建该service,并调用onStartCommand方法,传入的intent为service销毁之前的最后一个intent. (下载服务适用于此方式)

注意: 在Android 2.0以后, onStartCommand()方法默认返回START_STICKY, 通常情况下, 我们需要考虑我们的服务是否应该使用START_STICKY, 如果不是,则需要重写此方法.

bindService

bind 这种方式使得发起绑定的组件和被绑定的service形成了一个client-server模式.当发起绑定的组件被销毁后, 被绑定的service也会随之销毁.(前提是没有其他的组件和它绑定, 并且该service没有被start, 或者虽然被start过, 但已经执行了stopService/stopSelf 方法)

当绑定service的组件C被销毁后,service也会被销毁(service的onDestroy会被调用),不管组件C在销毁前是否调用了unbindService.(如果没有调用unbindService,那么Logcat中会打印出android.app.ServiceConnectionLeaked异常, 不过通过MAT分析内存dump文件,可以看出 绑定service的组件以及service自身都是可以被回收的)

通过bindService的方式, client可以得到一个IBinder的接口, 进而可以将其转换为IInterface接口, 用于和远程service进行通信.(如果client和server在同一个进程内, 那么service返回的IBinder直接提供一个返回Service引用的方法, 使用起来可能会更方便一些)

当被绑定的service被销毁之后,ServiceConnection.onServiceDisconnected会被调用,当系统重新创建该service之后,ServiceConnection.onServiceConnected会再次被触发

注意:要支持bind, service的onBind方法必需返回一个IBinder对象, 如果返回null, 那么要绑定该service的组件提供的ServiceConnection.onServiceConnected将不会被调用,也就是说绑定会失败.(注意:该service仍然会被创建,也会一直运行,直到要绑定它的那个组件销毁的时候一起销毁). 如果希望某个service不允许被任何其他组件绑定, 那么在其onBind方法中返回null就好了.

重要:通过相同的Intent调用bindService方法, service的onBind方法只会被调用一次(即第一次会被调用), 对于后续的bindService调用, 系统将会直接返回上一此的IBinder. // 这里比较 Intent 是否相同, 使用的是: android.content.Intent.FilterComparison.equals() 方法

// 每次 onBind 方法成功执行之后, 对应的 IntentBindRecord 会保存到 ServiceRecord 之中

// com.android.server.am.ServiceRecord

final ArrayMap bindings

= new ArrayMap();

// 对于bindService(Intent service, ServiceConnection conn, int flags)

// 如果参数 Intent 之前使用过, 那么对应的service的onBind方法不会再调用, 而是直接返回之前的IBinder

只有当所有绑定到该service的组件都与其解绑了,onUnbind方法才会被调用.(如果onUnbind返回true,那么下次绑定service,系统创建后将不会调用onBind,而是调用onRebind,并返回之前的IBinder)

何时使用startService, 何时使用bindService

如果只是要启动service去执行一个任务, 则使用startService比较合适.

如果启动service之后要与它进行交互(例如调用服务的方法, 甚至是client与server的双向通行), 则使用bindService比较合适.

既start又bind

通常这样做的目的是:

需要service能长时间的运行, 而不要关心启动它的组件是否被销毁. start

需要和service进行交互 以调用service的一些方法. bind

例如:实现一个DownloadService

1. startService目的: 使得 DownloadService 可以长久的在后台运行

2. bindService目的: 使用 DownloadService 提供的下载进度等Api

// 顺便说一句, 像DownloadService这样的服务, onStartCommand()方法建议返回START_REDELIVER_INTENT

此时要终止该service,需要同时具备下面2个条件:

service调用了stopSelf或者某个组件调用了stopService

所有绑定到该service的组件都已解绑或者被销毁

隐式Intent的风险

之所以说通过隐式Intent启动service存在安全风险,是因为:

通常调用者无法确定通过隐式Intent启动的service是哪一个(这个service甚至可能来自其他应用)

service在后台运行,没有用户界面.因此很难感知到这个service的存在以及它在干什么

通常建议startService和bindService使用显示Intent.并且在Manifest文件中定义service时,不应该提供, export设置为false(如果没有提供,那么export默认就是false)

**注意:**如果targetSdkVersion>=21的话,startService,bindService不能使用隐式Intent,否则会抛出IllegalArgumentException

ContextImpl.java

private void validateServiceIntent(Intent service) {

if (service.getComponent() == null && service.getPackage() == null) {

if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {

IllegalArgumentException ex = new IllegalArgumentException("Service Intent must be explicit: " + service);

throw ex;

} else {

Log.w(TAG, "Implicit intents with startService are not safe: " + service

+ " " + Debug.getCallers(2, 3));

}

}

}

tips

使用隐式Intent, 但是限定被启动的组件在指定app中的方法:

//在使用隐式Intent的时候, 可以通过 Intent.setPackage(packagename);

//来将组件的匹配限制在某个应用内.

//限制该隐式intent只匹配自己应用内的某个组件

intent.setPackage("my package name");

BroadcastReceiver.onReceive(Context context, Intent intent)方法中的context是不能用来绑定service的, 因为该context是:android.app.ReceiverRestrictedContext重写了bindService方法, 并抛出异常.

@Override

public boolean bindService(Intent service, ServiceConnection conn, int flags) {

throw new ReceiverCallNotAllowedException("BroadcastReceiver components are not allowed to bind to services");

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值