一、Service种类
按运行地点分类:
- 本地服务:运行在主进程中
- 远程服务:运行在独立的进程中
按运行类型分类:
- 前台服务:会有通知栏显示
- 后台服务:默认的服务,没有通知栏显示
按启动方式分类:
- startService启动:停止使用stopService
- bindService启动:停止使用unbindService
- 使用startService和bindService启动:同时使用stopService和unbindService停止
二、生命周期
onCreate()
系统在service第一次创建时执行此方法,来执行只运行一次的初始化工作。
onStartCommand()
每次客户端调用startService()方法启动该Service都会回调该方法(多次调用)。一旦这个方法执行,service就启动并且在后台长期运行。通过调用stopSelf()或stopService()来停止服务。
onBind()
当组件调用bindService()想要绑定到service时系统调用此方法(一次调用,一旦绑定后,下次再调用bindService()不会回调该方法),提供一个返回一个IBinder来以使客户端能够使用它与service通讯,因为该方法在Service类中是抽象方法,所以你必须总是实现这个方法,但是如果你不允许绑定,那么你应返回null。
onUnbind()
当前组件调用unbindService(),想要解除与service的绑定时系统调用此方法(一次调用,一旦解除绑定后,下次再调用unbindService()会抛出异常)。
onDestory()
系统在service不再被使用并要销毁时调用此方法(一次调用)。service应在此方法中释放资源,比如线程,已注册的侦听器,接收器等等.这是service收到的最后一个调用。
注意点:
1.startService / stopService
生命周期顺序:onCreate->onStartCommand->onDestroy
①第一次 startService 会触发 onCreate 和 onStartCommand,以后在服务运行过程中,每次 startService 都只会触发 onStartCommand
②不论 startService 多少次,stopService 一次就会停止服务
2.bindService / unbindService
生命周期顺序:onCreate->onBind->onUnBind->onDestroy
如果一个Service在某个Activity中被调用bindService方法启动,不论bindService被调用几次,Service的onCreate方法只会执行一次,同时onStartCommand方法始终不会调用。
当建立连接后,Service会一直运行,除非调用unbindService来接触绑定、断开连接或调用该Service的Context不存在了(如Activity被Finish——即通过bindService启动的Service的生命周期依附于启动它的Context),系统在这时会自动停止该Service。
第一次 bindService 会触发 onCreate 和 onBind,以后在服务运行过程中,每次 bindService 都不会触发任何回调
3.混合型
当一个Service在被启动(startService)的同时又被绑定(bindService),该Service将会一直在后台运行,并且不管调用几次,onCreate方法始终只会调用一次,onStartCommand的调用次数与startService调用的次数一致(使用bindService方法不会调用onStartCommand)。同时,调用unBindService将不会停止Service,必须调用stopService或Service自身的stopSelf来停止服务。
A bound service
被绑定的service是当其他组件(一个客户)调用bindService()来创建的。客户可以通过一个IBinder接口和service进行通信。客户可以通过 unbindService()方法来关闭这种连接。一个service可以同时和多个客户绑定,当多个客户都解除绑定之后,系统会销毁service。context.bindService()->onCreate()->onBind()->Service running-->onUnbind() -> onDestroy() ->Service stop,onBind将返回给客户端一个IBind接口实例,IBind允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,当解除绑定时,只有完全没有Client与它绑定时才会调用onUnbind和onDestroy,否则都不会调用。
注意:由于TestService已经处于运行状态,所以ActivityB调用bindService时,不会重新创建TestService的实例,所以也不会执行TestService的onCreate回调方法,由于在ActivityA执行bindService的时候就已经执行了TestService的onBind回调方法而获取IBinder实例,并且该IBinder实例在所有的client之间是共享的,所以当ActivityB执行bindService的时候,不会执行其onBind回调方法,而是直接获取上次已经获取到的IBinder实例!!!并将其作为参数传入ActivityB的ServiceConnection的onServiceConnected方法中,标志着ActivityB与TestService建立了绑定连接,此时有两个客户单client(ActivityA和ActivityB)与TestService绑定。
三、前台服务
前台服务创建很简单,其实就在Service的基础上创建一个Notification,然后使用Service的startForeground()方法即可启动为前台服务。
大体流程:创建通知--》设置跳转--》获取通知服务--》显示通知--》启动前台服务
通知可以自定义视图,使用的是RemoteViews,其中只支持部分控件,使用IPC通信和反射实现,这里不多说
这里需要注意的一点就是如果你要给通知添加点击跳转,你最好为其指定一个父Activity,这样当从通知跳转到指定界面中后,按返回不会回到桌面而是应用的主视图,这样用户体验比较好
public class NotificationService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Notification.Builder mBuilder = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText("点击跳转");
Intent intent2 = new Intent(this, SecondActivity.class);
PendingIntent pendingIntent=PendingIntent.getActivity(this, 0, intent2, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(pendingIntent);
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = mBuilder.build();
nm.notify(0, notification);
startForeground(0, notification);
return Service.START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
你还要在AndroidManifest中注册声明
<activity
android:name=".SecondActivity"
android:parentActivityName=".MainActivity"/>
<service android:name=".NotificationService" />
android:parentActivityName 的作用,就是为了左上角给子Activity加一个返回button
如果父 Activity 不在当前栈内(没有在当前栈有实例),这个属性不起效
如果父 Activity 在当前栈(在当前有实例),设置了该属性,父 Activity 会经历先销毁后创建的过程
如果设置父 Activity启动模式为 singleTask 或者 singleTop (两者效果一样的),那么父 Activity 就具有与 singTask 一样清理栈的 作用(清除在父 Activity 之上的那些Activity ),使得 父 Activity 得以重新独占设置与用于交互(可以走 onRsume 方法)
四、怎么保证Service不被杀死
1)进程生命周期:
官方文档告诉我们,Android系统会尽量保持拥有service的进程运行,只要在该service已经被启动(start)或者客户端连接(bindService)到它。当内存不足时,需要保持,拥有service的进程具有较高的优先级。
1. 如果service正在调用onCreate,onStartCommand或者onDestory方法,那么用于当前service的进程则变为前台进程以避免被killed。
2. 如果当前service已经被启动(start),拥有它的进程则比那些用户可见的进程优先级低一些,但是比那些不可见的进程更重要,这就意味着service一般不会被killed.
3. 如果客户端已经连接到service (bindService),那么拥有Service的进程则拥有最高的优先级,可以认为service是可见的。
4. 如果service可以使 用startForeground(int, Notification)方法来将service设置为前台状态,那么系统就认为是对用户可见的,并不会在内存不足时killed。
5. 如果有其他的应用组件作为Service,Activity等运行在相同的进程中,那么将会增加该进程的重要性。
解决方法:
方法一:onStartCommand中返回START_STICKY,如果内存不足被杀死,那么等内存足够是系统会自动重启Service;
方法二:Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是:
1.前台进程( FOREGROUND_APP)
2.可视进程(VISIBLE_APP )
3. 次要服务进程(SECONDARY_SERVER )
4.后台进程 (HIDDEN_APP)
5.内容供应节点(CONTENT_PROVIDER)
6.空进程(EMPTY_APP)
当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以使用startForeground将service放到前台状态。这样在低内存时被kill的几率会低一些。
白色方法:放一个可见的Notification,使用startForeground
灰色方法:它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。
方法四:监听锁屏事件或者主Activity被关闭时,显示一个1像素的透明Activity,让进程成为前台进程。
方法五:守护进程(Native层或者Java层),互相监听。