Service的使用。
1. 启动方式 Service有两种启动方式,startService和bindService。
2. 生命周期 这里应该放一张官网的图,但是加图有些麻烦,还是用文字代替吧。
Service在通过startService启动时:onCreate -> onStartCommand。一个Service系统只会创建一个实例,多次调用start方法时,onCreate不会重复调用,只在第一次创建时调用一次,而onStartCommand会被多次调用。停止时调用stopService,此时会调用onDestroy
Service在通过bindService启动时:onCreate -> onBind,onCreate在创建时调用,Service是用来通过服务的server,想要bind Service的是client。一个server可以同时给多个client提供服务。只有在client第一次调用bindService时,才会回调onBind方法。当client调用unbindService时,此时会回调Service的onUnbind方法,当此server的client数量为0时,会调用onDestroy进行销毁。
混合调用时,即创建一个Service实例后,既调用过startService,也调用过bindService,那么这时即便bind的client为0时,Service也不会停止,只有通过stopService才能停止该Service,可以理解为start的等级更高些。
3. onStartCommand(intent: Intent?, flags: Int, startId: Int): Int 这个方法有3个参数1个返回值,摘点官方文档的介绍
intent:启动时,启动组件传递过来的Intent,如Activity可利用Intent封装所需要的参数并传递给Service
flags: 表示启动请求时是否有额外数据,可选值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY,0代表没有,它们具体含义如下:
START_FLAG_REDELIVERY: 这个值代表了onStartCommand方法的返回值为START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf方法停止服务。其中START_REDELIVER_INTENT意味着当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),此时Intent时有值的。
START_FLAG_RETRY: 该flag代表当onStartCommand调用后一直没有返回值时,会尝试重新去调用onStartCommand()。
startId: 指明当前服务的唯一ID,与stopSelfResult (int startId)配合使用,stopSelfResult 可以更安全地根据ID停止服务。
onStartCommand的返回值int类型,它有三种可选值, START_STICKY,START_NOT_STICKY,START_REDELIVER_INTENT,它们具体含义如下:
START_STICKY: 当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。
START_NOT_STICKY: 当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
START_REDELIVER_INTENT: 当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
首次startService时,flags的值是0,其他情况启动时,flags的值根据其返回值决定。
4. startService示例 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 class SampleService : Service () { val TAG = SampleService::class .java.canonicalName override fun onCreate () { super .onCreate() Log.d(TAG, "on create invoked" ) } override fun onStartCommand (intent: Intent ?, flags: Int , startId: Int ) : Int { Log.d(TAG, "on start command invoked, flag:${flags} " ) return super .onStartCommand(intent, flags, startId) } override fun onDestroy () { super .onDestroy() Log.d(TAG, "on destroy invoked" ) } } val intent = Intent(this , SampleService::class .java)startService(intent) var intent = Intent(this , SampleService::class .java)stopService(intent)
5. bindService示例,同一进程 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 54 55 56 57 58 59 60 61 62 63 64 65 class BindService : Service () { val TAG = BindService::class .java.canonicalName private lateinit var runner: Thread inner class ServerBinder : Binder () { fun getService () : BindService { return this @BindService } } override fun onCreate () { super .onCreate() Log.d(TAG, "on create invoked" ) } override fun onBind (intent: Intent ?) : IBinder? { Log.d(TAG, "on bind invoked" ) return ServerBinder() } fun getData () : Int = 4 override fun onUnbind (intent: Intent ?) : Boolean { Log.d(TAG, "on unbind invoked" ) return super .onUnbind(intent) } override fun onDestroy () { super .onDestroy() Log.d(TAG, "on destroy invoked" ) runner.interrupt() } } private val conn = object : ServiceConnection { override fun onServiceConnected (name: ComponentName ?, service: IBinder ?) { Log.d(TAG, "on service connected" ) val binder = service as BindService.ServerBinder val server = binder.getService() val data = server.getData() } override fun onServiceDisconnected (name: ComponentName ?) { Log.d(TAG, "on service disconnected" ) } } val intent = Intent(this , BindService::class .java)bindService(intent, conn, BIND_AUTO_CREATE) val intent = Intent(this , BindService::class .java)unbindService(conn)
6. bindService示例,不同进程 想要实现client和server的双向通信,那么就需要双方都有发送/接收消息,即Handler+Messenger
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 class MessengerService : Service () { val TAG = MessengerService::class .java.canonicalName private val handler = object : Handler(Looper.myLooper()!!) { override fun handleMessage (msg: Message ) { Log.d(TAG, "service handle message:${msg.what} " ) val reply = Message.obtain() reply.what = 45456 msg.replyTo.send(reply) } } private val messenger = Messenger(handler) override fun onCreate () { super .onCreate() Log.d(TAG, "messenger service on create" ) } override fun onBind (intent: Intent ?) : IBinder? { Log.d(TAG, "messenger service on bind" ) return messenger.binder } override fun onUnbind (intent: Intent ?) : Boolean { Log.d(TAG, "messenger service on unbind" ) return super .onUnbind(intent) } override fun onDestroy () { super .onDestroy() Log.d(TAG, "messenger service on destroy" ) } } private val handler = object : Handler(Looper.getMainLooper()) { override fun handleMessage (msg: Message ) { Log.d(TAG, "client handle message:${msg.what} " ) } } private val messenger = Messenger(handler)private val ipcConn = object : ServiceConnection { override fun onServiceConnected (name: ComponentName ?, service: IBinder ?) { val server = Messenger(service) val msg = Message.obtain() msg.what = 12123 msg.replyTo = messenger server.send(msg) } override fun onServiceDisconnected (name: ComponentName ?) { } } val intent = Intent(this , MessengerService::class .java)bindService(intent, ipcConn, BIND_AUTO_CREATE) val intent = Intent(this , MessengerService::class .java)unbindService(ipcConn)
7. ForegroundService前台服务 通俗理解,后台任务多是用来做一些不需要用户看到的任务,比如静默下载,而前台任务则是做些需要给用户展示一些提示的任务,比如下载文件的进度条、播放音乐的进度条,等。摘一段官方的说明:
前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知,状态栏位于“正在进行”标题下方,这意味着除非服务停止或从前台删除,否则不能清除通知。例如将从服务播放音乐的音乐播放器设置为在前台运行,这是因为用户明确意识到其操作。 状态栏中的通知可能表示正在播放的歌曲,并允许用户启动 Activity 来与音乐播放器进行交互。如果需要设置服务运行于前台, 我们该如何才能实现呢?Android官方给我们提供了两个方法,分别是startForeground()和stopForeground()。
可以通过onStartzCommand方法传递命令,来回切换控制服务是否置于前台,这里只演示了如何切换。
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 54 55 56 57 class ForegroundService : Service () { val TAG = ForegroundService::class .java.canonicalName override fun onCreate () { super .onCreate() Log.d(TAG, "on service create" ) val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channelId = "default" val channel = NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_DEFAULT) val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (nm.getNotificationChannel(channelId) == null ) { nm.createNotificationChannel(channel) } NotificationCompat.Builder(this , channelId) } else { NotificationCompat.Builder(this ) } builder.setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)) .setOngoing(true ) .setAutoCancel(false ) .setShowWhen(true ) .setContentTitle("Foreground Service" ) val notification = builder.build() startForeground(1 , notification) } override fun onStartCommand (intent: Intent ?, flags: Int , startId: Int ) : Int { Log.d(TAG, "on service start command" ) return super .onStartCommand(intent, flags, startId) } override fun onBind (intent: Intent ?) : IBinder? { return null } override fun onDestroy () { super .onDestroy() Log.d(TAG, "on service destroy" ) stopForeground(true ) } } val intent = Intent(this , ForegroundService::class .java)startForegroundService(intent) startService(intent) val intent = Intent(this , ForegroundService::class .java)stopService(intent)
8. IntentService 和Service相比,特点是自带一个工作线程,可以用来做一些阻塞线程的任务,阻塞任务需要写在onHandleIntent里。现在已经显示弃用了,推荐使用androidx里的WorkManager
1 2 3 4 5 6 7 class DIntentService : IntentService ("download" ) { override fun onHandleIntent (intent: Intent ?) { } }
9. 显示启动和隐式启动 以上通过指定Service类的启动方式,均为显示启动。隐式启动指定Intent的action为全类名即可,此时需要service在Manifest中的exported属性为true。同一应用中两种方式均可,不同的应用只能用隐式。
1 2 val intent = Intent(ForegroundService::class .java.canonicalName)startService(intent)
5.0之前隐式启动只会发送一个警告,5.0之后官方出于安全考虑禁止了隐式启动,直接报错:Service Intent must be explicit,意思就是指定的Service必须精确
1 2 3 4 5 6 7 8 9 10 private validateServiceIntent (Intent service) { if (service.getComponent() == null && service.getPackage() == null ) { if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) { IllegalArgumentException ex = new IllegalArgumentException("Service Intent must be explicit: " + service); throw ex; } else { Log.w(TAG, "Implicit intents with startService are not safe: " + service + " " + Debug.getCallers(2 , 3 )); } } }
解决方式,很简单,给Intent指定component,或者package,或者两者都指定,便可通过检查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 val intent = Intent(ForegroundService::class .java.canonicalName)intent.setPackage(getPackageName()) startService(intent) fun getExplicitServiceIntent (context: Context , intent: Intent ) : Intent? { val pm = context.packageManager val resolveInfo = pm.queryIntentServices(intent, 0 ) if (resolveInfo == null || resolveInfo.size != 1 ) { return null } val serviceInfo = resolveInfo[0 ] val packageName = serviceInfo.serviceInfo.packageName val className = serviceInfo.serviceInfo.name val component = ComponentName(packageName, className) val explicitIntent = Intent(intent); explicitIntent.component = component return explicitIntent; }