Android开发之Service
Service简介
Android中有四种重要的组件在开发中频繁使用,适用于四种不同的开发场景,它们被我们称之为Android的四大组件,分别是Activity、Service、BroadcastReceiver、ContentProvider。Activity可以说是Android开发中使用最频繁的组件,它提供一个窗口用于界面展示和与用户进行一系列的交互操作,是一个优秀的前台工作者。那么有了前台就会有默默付出的幕后,四大组件中Service就充当着幕后默默劳动的角色。
Service用于实现程序后台运行解决方案,它适用于去执行那些不需要和用户交互并且长期运行的任务。Service自身没有展示界面,有些时候也不依赖于任何用户界面,当程序被切换到后台或用于打开了另外一个应用Service仍然能保持正常运行。
Service默认是运行在主线程(UI线程)中的,不会自动开启新的工作线程。在一定的场景下我们需要在Service内部自发手动去创建工作线程(子线程)并在这里执行具体的任务,否则很有可能出现UI线程被阻塞导致ANR的情况发生。
Service不是运行在一个独立的进程中的,而是依赖于创建Service时所在的应用进程,当某个应用进程被Kill的时候,所有依赖于该进程的Service也会停止运行。
Android进程 & 优先级
Android中的进程是托管的,当系统进程空间紧张的时候会依照优先级进行进程回收。Android的进程分为6个等级,他们的优先级由高到低依次是前台进程( FOREGROUND_APP) 、可视进程(VISIBLE_APP ) 、次要服务进程(SECONDARY_SERVER ) 、后台进程 (HIDDEN_APP) 、内容供应节点(CONTENT_PROVIDER) 、空进程(EMPTY_APP) 。
当Service运行在低内存的环境时,很有可能会Kill掉一些存在的低优先级进程。因此进程的优先级将会很重要,Service可以通过startForeground将Service放到设置成前台服务,提升它的优先级,这样在低内存时才不容易被Android系统默认回收。
Service的种类
Service的种类按运行分类分为前台服务、后台服务;按使用分类分为远程服务、本地服务。
前台服务
服务的系统优先级比较低,当系统出现内存不足的情况时,有可能会回收掉正在后台运行的服务。如果希望服务可以一直保持运行状态,不会由于系统内存不足的原因导致被回收, 可以考虑使用前台服务。
前台服务和普通服务最大的区别就在于它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,类似于通知(Notification)的效果。
有时候也可能不仅仅是为了防止服务被回收掉才使用前台服务的,有些项目由于特殊的需求会要求必须使用前台服务。可以调用startForeground()方法把我们的服务设置为前台服务,该方法有唯一标识通知的整数值、状态栏通知Notification对象两个参数。
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("content title")
.setContentText("content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher))
.setContentIntent(pi)
.build();
startForeground(1,notification);
可以调用stopForeground()方法移除前台服务,这个方法接收一个布尔值,标识是否同时移除状态栏通知。此方法不会终止服务,如果服务在前台运行时被终止,那么通知也会同时被移除。
后台服务
普通服务就是后台服务,后台服务可以设置为前台服务防止被系统Kill掉;
远程服务
用于系统内部应用之间,可被其他应用复用,比如天气预报服务,其他应用不需要再去写这样的服务,直接调用已有的服务即可。
可以定义接口并把接口暴露出来以便其他应用进行操作。客户端建立到服务对象的连接,通过连接来调用服务。调用Context.bindService()方法建立连接并启动,调用Context.unbindService()关闭连接。多个客户端可以绑定同一个服务,如果服务此时还没有加载,bindService()会先加载它。
本地服务
用于应用内部,实现一些耗时任务,不占用应用所属线程,单开线程后台执行。
通过调用Context.startService()启动,调用Context.stopService()结束。
在内部可以调用Service.stopSelf()或Service.stopSelfResult()来自己停止。
Service的启动方式
Service的启动方式有startService()和bindService();
startService()启动服务
调用startService()方法并传入一个Intent,Start服务必须自行管理生命周期。
系统不会终止或销毁这类服务,除非必须恢复系统内存并且服务返回后一直维持运行。因此服务必须通过调用stopSelf()自行终止,或者其他组件可以通过调用stopService()来终止它。
如果希望Service一启动就立刻去执行某个动作,这时候就可以将逻辑写在onStartCommand()方法中。
当Service销毁的时候我们应该在onDestroy()方法中去回收那些不再使用的资源。
public class MyService extends Service{
@Override
public IBinder onBind(Intent intent){
return null;
}
@Override
public void onCreate(){
super.onCreate();
}
@Override
public int onStartCommand(Intent intent,int flags,int startId){
return super.onStartCommand(intent,flags,startId);
}
@Override
public void onDestroy(){
super.onDestroy();
}
}
//启动Service
Intent startIntent= new Intent(this,MyService.class);
startService(startIntent);
//停止Service
Intent stopIntent= new Intent(this,MyService.class);
stopService(stopIntent);
每一个Service都需要在AndroidManifest.xml清单文件中进行注册才能生效,这一点是Android四大组件的共有特点,其中Activity、Service、ContentProvider必须在清单文件中注册才能生效。
<service android:name=".MyService"/>
bindService()启动服务
Activity或者其他组件需要与服务进行交互,或者应用的某些功能需要暴露给其他应用时应该创建一个Bind服务,并通过进程间通信(IPC)来完成。
在进行服务绑定的时候flags有如下选择;
Context.BIND_AUTO_CREATE
表示收到绑定请求的时候如果服务尚未创建则立刻创建,在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程称为被摧毁对象时,服务才被摧毁;
Context.BIND_DEBUG_UNBIND
通常用于调试场景中判断绑定的服务是否正确,容易引起内存泄漏,因此非调试目的的时候不建议使用;
Context.BIND_NOT_FOREGROUND
表示系统将组织驻留该服务的进程具有前台优先级,仅在后台运行;
Service注册
Service无论什么类型的Service都必须在清单文件中进行注册声明。Service在AndroidManifest.xml中的元素如下:
android:name:服务类名,是唯一必需的属性,定义了服务的类名。服务既可以定义intent过滤器,使得其他组件能用隐式intent来调用服务。如果想让服务职能内部使用,就不必提供任何intent过滤器。android:exported属性并且设置为"false",可以确保该服务是应用的私有服务。即使服务提供了intent过滤器,本属性依然生效。
android:label:服务的名字,不设置默认显示服务名为类名;
android:icon:服务的图标;
android:permission:服务的权限,意味着只有提供该权限的应用才能控制或连接此服务;
android:process:服务是否运行在另外一个进程,设置了此项,那么将会在包名后面加伤这段字符串表示另一进程的名字;
android:enabled:设置为true时Service将会默认被系统启动,不设置默认此项为false;
android:exported:服务是否能够被其他应用所控制或连接,不设置默认此项为false;
Service能否执行耗时操作
Service默认情况下是在主线程中(UI线程),如果没有开启子线程的情况下是不能在Service中进行耗时操作的,会引发ANR。
Service是否跟Activity在同一个线程
同一App默认情况下都是在主线程中,是同一线程。
Service是否可以可以弹吐司
Toast得有一个Context才可以弹出,Service是Context的子类,因此可以在Service中弹Toast。比如我们在Service中完成下载任务可以弹一个Toast通知用户。
Activity中启动Service
在Activity中通过startService()和bindService()启动Service;
一般情况如果获取Service的服务对象需要通过bindService方法;
如果仅仅是为了开启一个后台任务那么可以使用startService()方法;
Activity如何跟Service绑定
Activity通过bindService(Intent,ServiceConnection,int)跟Service进行绑定,当绑定成功的时候Service将代理对象通过回调的形式传递给ServiceConnection,这样我们就拿到了Service提供的服务代理对象。
同时调用startService()和 bindService()的Service如何销毁?
一个Service只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用 stopService() 和 unbindService() 方法,onDestroy()方法才会执行。
Service和Thread的区别
Service是一个组件,即使用户不与应用进行交互它仍然能在后台运行。所以应该在需要的时候才创建一个服务。
当需要在主线程之外执行一些工作,但只在用户与应用交互时才用到,就创建一个新的线程而不是创建服务。
由于无法在不同的Activity中对同一Thread进行控制,这个时候就考虑服务实现。如果使用了服务就默认运行于应用的主线程中。所以如果服务执行密集计算或者阻塞操作,仍然应该在服务中创建一个新的线程来完成以避免ANR。
Activity、Intent、Service的关系
Activity和Service是四大组件之一,都是Android开发中使用频率最高的类,都是Context子类ContextWrapper的子类,同级关系,负责的内容不一样,Activity负责界面展示和交互,Service负责后台任务的处理。
Activity和Service之间可以通过Intent传递数据。