Service
Service 是 Android 中实现程序后台运行的解决方案,它非常适合执行那些不需要和用户交互而且还要求长期运行的任务。
Service 的运行不依赖任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,Service 依然能够保持运行。Service 并不是运行在一个独立的进程当中,而是依赖于创建 Service 时所在的应用程序。当某个应用程序进程被杀掉时,所有依赖于该进程的 Service 也会停止运行。
另外,也不要被 Service 的后台概念所迷惑,实际上 Service 并不会自动开启线程,所有的代码都是默认运行在主线程中的。也就是说,需要在 Service 的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况。
Android 8.0 开始,应用的后台功能被大幅消减。现在只有当应用保持在前台可见状态的情况下,Service 才能保证稳定运行,一旦应用进入后台之后,Service 随时可能被系统回收。之所以做这样的改动,是为了防止许多恶意的应用程序长期在后台占用手机资源,从而导致手机变得越来越卡。(可使用前台 Service 或者 WorkManager 来长期在后台执行一些任务,如果想在应用程序中使用 WorkManager,实现后台作业,可以去读一下 http://dontkillmyapp.com 上的所有内容,并且要关注一下与 WorkManager 相关的 Google 问题列表。)
分类
服务
按运行地点分
本地服务,远程服务
按运行类型分
前台服务,后台服务
按功能分
可通信服务,不可通信服务
本地服务(LocalService) 特点:运行在主进程中。主进程终止后,服务也会终止。它有两种启动方式:
通过start方式开启服务 使用service的步骤:
定义一个类继承service
manifest.xml文件中配置service
使用context的startService(Intent)方法启动service
不在使用时,调用stopService(Intent)方法停止服务
特点:一旦服务开启就跟调用者(开启者)没有任何关系了。开启者退出或者挂了,服务还在后台长期的运行,开启者不能调用服务里面的方法。
通过bing方式开启服务 使用service的步骤:
定义一个类继承Service
在manifest.xml文件中注册service
使用context的bindService(Intent,ServiceConnection,int)方法启动service
不再使用时,调用unbindService(ServiceConnection)方法停止该服务
特点:bind的方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。绑定者可以调用服务里面的方法。
生命周期
通过 Context 的 startService() 启动相应的 Service,并回调 onStartCommand()。如果第一次创建,会先调用 onCreate()。Service 启动后会一直保持运行状态,直到 stopService() 或 stopSelf() 被调用,或者被系统回收。
调用 Context 的 bindService() 来获取一个 Service 的持久连接,并回调 Service 中的 onBind()。如果第一次创建,会先调用 onCreate()。之后,调用方可以获取到 onBind() 里返回的 IBinder 对象的实例,这样就可以自由地和 Service 通信了。只要调用方和 Service 之间的连接没有断开,Service 就会一直保持运行状态,直到被系统回收。
当调用了 startService() 后,再调用 stopService(),这时 Service 中的 onDestory() 会执行,表示 Service 已经销毁。类似地,当调用 bindService() 后,再调用 unbindService() ,onDestory() 也会执行,表示取消绑定。但如果对一个 Service 既调用了 startService() ,又调用了 bindService(),根据 Android 系统的机制,一个 Service 只要被启动或者被绑定之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service 才能被销毁。所以,在这种情况下,要同时调用 stopService() 和 unbindService() ,onDestory() 才会执行。
远程服务() 特点:调用者和service不在同一个进程中,service在单独的进程中的main线程,是一种垮进程通信方式 绑定远程服务的步骤:
在服务的内部创建一个内部类,提供一个方法,可以间接调用服务的方法
把暴露的接口文件的扩展名改为.aidl文件 去掉访问修饰符
实现服务的onbind方法,继承Bander和实现aidl定义的接口,提供给外界可调用的方法
在activity 中绑定服务。bindService()
在服务成功绑定的时候会回调 onServiceConnected方法 传递一个 IBinder对象
aidl定义的接口.Stub.asInterface(binder) 调用接口里面的方法
IntentService IntentService是Service子类,特征:
会创建独立的worker线程来处理所有的Intent请求;
会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;
所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service;
为Service的onBind()提供默认实现,返回null;
为Service的onStartCommand提供默认实现,将请求Intent添加到队列中;
基本用法
示例(Kotlin):创建 Service
New -> Service -> Service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class MyService : Service () { override fun onBind (intent: Intent ) : IBinder { TODO("Return the communication channel to the service." ) } override fun onCreate () { super .onCreate() Log.d("TAG" ,"onCreate" ) } override fun onStartCommand (intent: Intent ?, flags: Int , startId: Int ) : Int { Log.d("TAG" ,"onStartCommand" ) thread { stopSelf() } return super .onStartCommand(intent, flags, startId) } override fun onDestroy () { super .onDestroy() Log.d("TAG" ,"onDestroy" ) } }
1 2 3 4 5 6 7 8 9 10 11 12 private fun initView () { btnStartService.setOnClickListener{ val intent = Intent(this ,MyService::class .java) startService(intent) } btnStopService.setOnClickListener{ val intent = Intent(this ,MyService::class .java) stopService(intent) } }
AndroidManifest.xml 1 2 3 4 5 <service android:name =".service.MyService" android:enabled ="true" android:exported ="true" > </service >
示例(Kotlin):Activity 与 Service 通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class MyService : Service () { ... private val mBinder = DownloadBinder() class DownloadBinder : Binder (){ fun startDownload () { Log.d("TAG" ,"startDownload executed" ) } fun getProgress () :Int { Log.d("TAG" ,"getProgress executed" ) return 0 } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class ServiceActivity :BaseActivity () { lateinit var downloadBinder: MyService.DownloadBinder private val connection = object :ServiceConnection{ override fun onServiceConnected (name: ComponentName ?, service: IBinder ?) { downloadBinder = service as MyService.DownloadBinder downloadBinder.startDownload() downloadBinder.getProgress() } override fun onServiceDisconnected (name: ComponentName ?) { } } ... private fun initView () { ... btnBindService.setOnClickListener{ val intent = Intent(this ,MyService::class .java) bindService(intent,connection,Context.BIND_AUTO_CREATE) } btnUnBindService.setOnClickListener{ val intent = Intent(this ,MyService::class .java) unbindService(connection) } } }
使用前台 Service
前台 Service 和 普通 Service 最大的区别在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。
由于状态栏中一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台 Service。另外,用户也可以通过下拉状态栏清除地知道当前什么应用正在运行,因此也不存在某些恶意应用长期在后台偷偷占用手机资源的情况。
示例(Kotlin):
从 Android 9.0 开始,前台 Service 必须进行权限声明:
1 <uses-permission android:name ="android.permission.FOREGROUND_SERVICE" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class MyService : Service () { ... override fun onCreate () { super .onCreate() Log.d("TAG" ,"onCreate" ) val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ val channel = NotificationChannel("my_service" ,"前台 Service 通知" ,NotificationManager.IMPORTANCE_DEFAULT) manager.createNotificationChannel(channel) } val intent = Intent(this ,ServiceActivity::class .java) val pi = PendingIntent.getActivity(this ,0 ,intent,0 ) val notification = NotificationCompat.Builder(this ,"my_service" ) .setContentTitle("This is content title" ) .setContentText("This is content text" ) .setSmallIcon(R.drawable.small_icon) .setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.large_icon)) .setContentIntent(pi) .build() startForeground(1 ,notification) } ... }
1 2 3 4 btnStartService.setOnClickListener{ val intent = Intent(this ,MyService::class .java) startService(intent) }
使用 IntentService
一个异步的、会自动停止的 Service。
示例(Kotlin):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class MyIntentService : IntentService ("MyIntentService" ) { override fun onHandleIntent (intent: Intent ?) { Log.d("TAG" ,"Thread id is ${Thread.currentThread().name} " ) } override fun onDestroy () { super .onDestroy() Log.d("TAG" ,"onDestroy executed" ) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 private fun initView () { btnStartIntent.setOnClickListener{ Log.d("TAG" ,"Thread id is ${Thread.currentThread().name} " ) val intent = Intent(this ,MyIntentService::class .java) startService(intent) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 <application android:allowBackup ="true" android:icon ="@mipmap/ic_launcher" android:label ="@string/app_name" android:roundIcon ="@mipmap/ic_launcher_round" android:supportsRtl ="true" android:theme ="@style/AppTheme" > <service android:name =".service.MyIntentService" android:enabled ="true" android:exported ="true" > </service > </application >
备注
参考资料:
第一行代码(第3版)
Android 开发艺术探索