Android Service使用拾遗[阿里工程师分享]
Service作为android的四大组件之一常用来帮助我们完成一些需要放在后台处理的任务,通过startService和bindService两种方式被调用。因为Service也是在主线程中运行的,所以如果处理耗时任务,一般在Service里再单独创建工作线程去执行耗时任务。使用Service的另一个用处是可以减少业务逻辑与界面的耦合,在产品演进中具备快速迭代的能力。
有的应用有服务需要一直常驻在内存中,如果UI和service在同一进程中,当按home键退到后台时,因为有常驻的service,整个进程的状态是service,即使在低内存时LMK也不会杀掉这个进程,使内存难以回收。这样就得把需要常驻内存的、(几乎)没有界面显示的业务逻辑单独拆出来放在一个进程里,有界面显示的放在另一个进程里。但这么做会占用更多的内存,只有当应用位于后台时也要处理大量任务时才应该考虑让app运行在多个进程中。
除此之外,在使用过程中还会碰到低内存时的情形,有一些service的知识平时可能接触的不多,在这里我做了一些简单的总结。
一、startService
1.1 onStartCommand的返回值
常用的返回值有3种,START_STICKY、START_NOT_STICKY和START_REDELIVER_INTENT。其中START_STICKY和START_REDELIVER_INTENT在service没有执行完就被系统杀掉后的一段时间内会被系统重启,被系统杀掉的情形可能是在系统内存不足或者某些ROM定制了管理后台任务的策略,比如锁屏一段时间后,不在白名单中的应用会被杀掉以释放内存。如果是service本身的错误导致在没有执行完就crash退出,是不会被系统重启的。
1)START_NOT_STICKY
如果onStartCommand返回START_NOT_STICKY,那即使service没有执行完,被杀掉后也不会被系统重启。如果这个service是用来为界面的Activity处理数据用的,那它不是必须一定要执行完的,大部分的情形是service所在的应用进程都已经被系统杀掉,这时没必要重启再次执行service。
2)START_STICKY
被杀掉后系统会重启service,并且onStartCommand一定会被调用,如果在重启期间没有任何启动命令被传递到service,那么参数Intent将为null。这里说的重启期间是指系统杀掉service后到系统再次启动该service的时间间隔,那么这段时间有多长呢,frameworks有专门处理重启service的代码。在ActiveServices.java的scheduleServiceRestartLocked函数里,
这个分支是处理非persistent的情况,只有系统应用才有权限把自己设为persistent,即在AndroidManifest.xml里设了android:persistent="true"。deliveredStarts中存放的是已经传递给service的启动参数,pendingStarts中存放的是还没有传递给service的启动参数。minDuration是被系统杀掉后被系统重启的时间间隔,resetTime是重置这个重启时间的间隔。如果Intent不为null,会更新这两个值,
然后重新计算service下一次被重启的时间。如果之前没被重启过,restartDelay为0,则把restartDelay设为minDuration。如果之前重启过,当前时间距上次重启的时间已经超过了resetTime,则把restartCount置为1,restartDelay设为minDuration;如果距上次重启时间还不到resetTime,则调大restartDelay。这是为了防止service被在内存不足的情况下被频繁重启,第一次内存不足时杀掉service,1s后重启该service,重启后又消耗了一部分内存造成内存再次不足再次杀掉service,这时1s后就不应该重启了,要往后推迟一段时间再尝试重启。
nextRestartTime就是下次重启service的时间了,然后postAtTime在nextRestartTime这个时间点重启service,并且更新nextRestartTime。
因为START_STICKY类型默认传入的Intent为null,所以在使用时我们要仔细考虑。如果service需要使用Intent里的参数,那很有可能被重启时并没有调用者能传入这个参数。比如,该service是在某场景下才会被本应用的其他组件所调用启动,那么有可能整个应用都被杀掉了,重启该service时,只会经过Application的onCreate和该service的onCreate、onStartCommand,没有经过调用启动的上下文。或者是收到broadcast而触发该service,则重启期间可能不会收到broadcast。只有当service是必须要完成的,并且不依赖于传入的Intent才需要把返回值设为START_STICKY。
3)START_REDELIVER_INTENT
在重启时会重传被杀时未完成的Intent。比如该startService调用了4次,第1、2次的任务已经被service处理完(比如调用了stopSelf或stopService),第3、4次还未被处理时就被杀掉了,重启时会按顺序传入第3、4个Intent。重启后调用stopSelf的顺序要注意startId的顺序。因为第3、4次任务可能会被service 交给不同线程去执行,可能4先被执行完,如果4执行完后调用stopSelf(startId4)的话,那么3会被立即停止,即使它还没被执行完。所以stopSelf的顺序要严格按照收到onStartCommand中的startId来执行。
1.2.IntentService
在这里推荐使用IntentService,它有一个工作线程和一个Handler,可以通过回调函数onHandleIntent依次处理onStartCommand收到的Intent,在onHandleIntent调完后会自己调stopSelf。
1.3. startForeground
为了防止处于后台的service在低内存时被系统杀掉,service可以调用startForeground()把自己放在前台进程中,但最好在完成任务后及时调用stopForeground把优先级调回来。
二、bindService
2.1 bindService的flag
bindService的第三个参数flags一般都会传0或BIND_AUTO_CREATE,跨进程调用bindService会在引起依赖,比如A进程的Activity中bindService调用B进程service,则B进程的service的oom_adj值依赖于A进程Activity的oom_adj值。如果activity在前台,它的oom_adj值为0,service的值为1,两者都难以被系统杀掉。但如果把flags设为一些“弱连接”类型,比如设为BIND_WAIVE_PRIORITY,则即使Activity位于前台,oom_adj为0,service的oom_adj值为15,也可以很容易被杀掉。其他一些flags还有:
BIND_ABOVE_CLIENT:调用bindService的应用的oomAdj的值比service本身的oom_adj更高,比如activity在后台时,oom_adj为10,service的oom_adj为9,调用者activity更容易被杀掉。
BIND_ADJUST_WITH_ACTIVITY:service的重要性跟调用它的activity一样。比如activity在前台时,oom_adj为0,service的oom_adj也为0。
2.2 DeadObjectException和RemoteException
Service异常终止或者被系统杀掉后会抛出DeadObjectException,binder的IPC过程中如果在server端发生异常抛出,client端这边也会有RemoteException,客户端在调用服务端的接口的过程中,在需要时要注意捕获这两个异常。捕获后一般意味着远程对象已经不可用了,died或异常无法继续运行下去,因此在catch后一般会重新启动服务,或重新再调一遍接口来保证高可用性。
2.3 利用bindService实现进程间通信
前台(Foreground)和后台(Background)进程要实现双向通信,即相互传输一些数据或命令,在Android上并无现成的拿来可用的框架。一个解决方案是利用service,在前台和后台进程中各创建一个service,它们两个之间互相bind。同时,前台和后台进程各自有一个transfer和handler,用来发送和接收数据。
流程简述如下:
1)前台进程的Application中bindService启动BackService。
2)在onServiceConnected中ForeTransfer发送一个启动后台服务的命令START。
3)后台进程的BackHandler通过BackService收到该命令后,bindService启动ForeService。这样前后台进程都有一个service bind到对方上。
4)后台进程的模块A要向前台进程的模块B发送数据,就通过BackTransferàBackService àForeService àForeHandler,被ForeHandler收到,模块B在ForeHandler中实现自己收到数据的处理函数即可。
5)前后进程的Handler中也可以注册回调函数,告知Transfer数据是否已发送处理完,这是因为binder调用是同步的,所以整个通信过程也是同步的。
- 嵌入式企鹅圈原创团队由阿里、魅族、nvidia、龙芯、炬力、拓尔思等资深工程师组成。百分百原创,每周两篇,分享嵌入式、Linux、物联网、GPU、Android、自动驾驶等技术。欢迎扫码关注微信公众号:嵌入式企鹅圈,实时推送原创文章!
Android Service使用拾遗[阿里工程师分享]的更多相关文章
- 阿里资深工程师分享支付宝热补丁技术—— AndFix原理
本文由嵌入式企鹅圈原创团队成员.阿里资深工程师Hao分享. 上次我们介绍了用dexposed方案实施热补丁的原理,它本质上就是hook要修改的函数,这样一来在正式版本发布时就不能直接拿热补丁的代码集成 ...
- 阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路
本文原始内容由作者“阳振坤”整理发布于OceanBase技术公众号. 1.引言 OceanBase 是蚂蚁金服自研的分布式数据库,在其 9 年的发展历程里,从艰难上线到找不到业务场景濒临解散,最后在双 ...
- 转:android service总结2
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11952435 相信大多数朋友对Service这个名词都不会陌生,没错,一个老练的A ...
- android service两种启动方式
android service的启动方式有以下两种: 1.Context.startService()方式启动,生命周期如下所示,启动时,startService->onCreate()-> ...
- 1、Android Studio集成极光推送(Jpush) 报错 java.lang.UnsatisfiedLinkError: cn.jpush.android.service.PushProtoco
Android studio 集成极光推送(Jpush) (华为手机)报错, E/JPush: [JPushGlobal] Get sdk version fail![获取sdk版本失败!] W/Sy ...
- Android Service完全解析,关于服务你所需知道的一切(下)
转载请注册出处:http://blog.csdn.net/guolin_blog/article/details/9797169 在上一篇文章中,我们学习了Android Service相关的许多重要 ...
- Android Service完全解析,关于服务你所需知道的一切(上)
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11952435 相信大多数朋友对Service这个名词都不会陌生,没错,一个老练的A ...
- android service 的各种用法(IPC、AIDL)
http://my.oschina.net/mopidick/blog/132325 最近在学android service,感觉终于把service的各种使用场景和用到的技术整理得比较明白了,受益颇 ...
- Android service介绍和启动方式
1.Android service的作用: service通常是用来处理一些耗时操作,或后台执行不提供用户交互界面的操作,例如:下载.播放音乐. 2.Android service的生命周期: ser ...
随机推荐
- 自己动手搭建 Redis 环境,并建立一个 .NET HelloWorld 程序测试
关于 Redis ,下面来自百度百科: redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set( ...
- 下载安装与配置 Java JDK 7
1. 去 Oracle 的官网下载 JDK,我下载的是:jdk-7u25-windows-x64.exe 大小为:90.6M 2. 双击它安装. 3. 安装完后,JDK 配置如下: 01 02 - ...
- JS基础回顾,小练习(克隆对象,数组)
对象的克隆: var srcObj = { a: 1, b: { b1: ["hello", "hi"], b2: "JavaScript" ...
- sqlserver -- 学习笔记(七)获取同组数据的前两条记录
不啰嗦,直接上图,大概实现效果如下: 有上面这样一份数据,将他们按照userAccount和submitTime进行分组,然后提前每组数据的前两条记录 提取后数据如下: 实现的SQL如下: selec ...
- get新技能: 如何设置元素高度与宽度成特定比例。宽度改变,高度自动按比例改变。 例如设置宽高比16:9。
设置宽高比在很多时候是有用的. 下面的栗子,我们设置一个容器的宽高比为16:9 //HTML代码片段 <div class="container"> <div c ...
- redis实现有序的消息队列
redis是什么东西就不多说了,网上文章一搜一大堆. 首先来说一下我要实现的功能: 类似一个消息中转站吧,如果有人要发送消息,先将消息发到我这里来,然后我这边进行转发,为的就是有一个统一的管理和修改时 ...
- Django--models连表构建
需求 models多表(一对多.多对多.一对一)的构建 速查 1.一对多 1 2 3 class User(models.Model): name = models.CharField(max ...
- 登陆mysql时提示异常的解决方法
[root@host2 ~]# mysql -uroot -p Enter password: ERROR (HY000): Can't connect to local MySQL server t ...
- nodejs+express+jade给我baby做个小相册
去年年底迎来了my little star.从此人生多了一个最重要的牵挂.生了宝宝全家人都太忙了.最近宝宝稍微大点了,终于有空可以研究下技术了.这是14年第一帖.废话不多了.开始吧 1.安装NTVS ...
- C#写文本日志帮助类(支持多线程)
代码: using System; using System.Configuration; using System.IO; using System.Threading.Tasks; namespa ...