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退出 , 是不会被系统重启的 。
【Android Service的使用】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.2DeadObjectException和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调用是同步的 , 所以整个通信过程也是同步的 。
本文到此结束,希望对大家有所帮助!
猜你喜欢
- 苹果入驻抖音,完成官方认证
- 城里人去农村采槐花,需要注意什么?
- 宝宝咬人招来一顿打?知道真相后妈妈眼泪都掉下来
- 三明市十大教育培训机构排名 童蕴学堂上榜 第一以理念著称
- 抖音林客服务商入驻要多少钱
- 什么加速器好用?
- 使用婴儿推车也有讲究,使用不当容易伤害宝宝,宝爸宝妈别不重视
- 电脑开机一直蓝屏怎么办,错误代码分析和解决
- 入驻抖音林客服务商要投资多少钱