Android中的Service,潜在的服务者。
作者:互联网
前言
年前因为疫情以及个人原因辞职以后,很久没有工作了。终于在休息了大半年后,准备回到手敲代码的生活中去,开始着手准备自己的简历和面试等等。在面试过程中,被面试官问到了最基本的问题:“介绍一下Service作用和使用方式”,然而在回答的过程中,发现自己对于这部分东西有一些朦胧的感觉,竟然有一丝丝逐渐淡化的倾向。于是乎,有了这篇文章,Android中的Service,潜在的服务者。
正文
一,什么是Service?
Service是Android中实现程序后台运行的解决方案,适用于去执行那些不需要和用户交互而且还要求长期运行的任务。Service是android 系统中的四大组件之一(Activity、Service、BroadcastReceiver、ContentProvider),它跟Activity的级别差不多,但不能自己运行,只能后台运行,并且可以和其他组件进行交互。
Service并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。
虽然它是Android系统中四大组件之一,但是它并不能自己运行。那么问题来了,它到底是如何运行的呢?答案是———虽然无法自己运行,但是它可以和其他组件进行联系,通过别的组件来帮助它运行在进程中。其中运行方式又有不同,分为两种:
运行条件 | 启动方式 | 停止方式 | service与启动它的组件之间的通信方式 | service的生命周期 |
---|---|---|---|---|
startService | 在其他组件中调用startService()方法后,服务即处于运行状态 | service中调用stopSelf()方法,或者其他组件调用stopService()方法后,service将停止运行 | 没有提供默认的通信方式,启动service后,service就处于独立运行的状态 | 一旦启动,service即可在后台无限期运行,即使启动service的组件已被销毁也不受其影响,直到其被停止 |
bindService | 在其他组件中调用bindService()方法后,服务即处于运行状态 | 所有与service绑定的组件都被销毁,或者它们都调用了unbindService()方法后,service将停止运行 | 可以通过 ServiceConnection进行通信,组件可以与service进行交互、发送请求、获取结果,甚至是利用IPC跨进程执行这些操作 | 当所有与其绑定的组件都取消绑定(可能是组件被销毁也有可能是其调用了unbindService()方法)后,service将停止 |
PS:
1.上述中的“其他组件”不包括Broadcast receiver,其无法直接启动或绑定service。
2.startService()与bindService()并不冲突,同一个service可能既有组件调用了startService()启动它,又有组件与它进行了绑定。当同一个service与其他组件同时存在这两种联系时,其生命周期会发生变化,必须从两种方法的角度看service均停止才能真正停止。
ok,通过这个表格我们可以知道,这两种启动service的方式各有自己的特点,我们可以根据自己的项目需要选择合适的方式。
2,如何创建一个Service?
关于这个话题,其实在百度、google,又或者任何一本Android入门的书本上都有这个问题的答案:
- 创建一个类继承自Service(或它的子类,如IntentService),重写里面的一些关键的回调方法,如onStartCommand(),onBind()等
- 在Manifests文件里面为其声明,并根据需要配置一些其他属性。
其实和Activity非常相似,包括了其中的一些生命周期方法。
- onCreate(),在每个service的生命周期中这个方法会且仅会调用一次,并且它的调用在onStartCommand()以及onBond()之前,我们可以在这个方法中进行一些一次性的初始化工作。
- onStartCommand(),当其他组件通过startService()方法启动service时,此方法将会被调用。
- onBind(),当其他组件通过bindService()方法与service相绑定之后,此方法将会被调用。这个方法有一个IBinder的返回值,这意味着在重写它的时候必须返回一个IBinder对象,它是用来支撑其他组件与service之间的通信的——另外,如果你不想让这个service被其他组件所绑定,可以通过在这个方法返回一个null值来实现。
- onDestroy(),这是service一生中调用的最后一个方法,当这个方法被调用之后,service就会被销毁。所以我们应当在这个方法里面进行一些资源的清理,比如注册的一些监听器什么的。
在Manifests文件里进行声明的时候,只有android:name属性是必须要有的,其他的属性都可以没有。但是有的时候适当的配置可以让我们的开发进行地更加顺利,所以了解一下注册一个service可以声明哪些属性也是很有必要的。
<service
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
</service>
-
android:enabled : 如果为true,则这个service可以被系统实例化,如果为false,则不行。默认为true
-
android:exported : 如果为true,则其他应用的组件也可以调用这个service并且可以与它进行互动,如果为false,则只有与service同一个应用或者相同user ID的应用可以开启或绑定此service。它的默认值取决于service是否有intent filters。如果一个filter都没有,就意味着只有指定了service的准确的类名才能调用,也就是说这个service只能应用内部使用——其他的应用不知道它的类名。这种情况下exported的默认值就为false。反之,只要有了一个filter,就意味着service是考虑到外界使用的情况的,这时exported的默认值就为true
-
android:icon : 一个象征着这个service的icon
-
android:isolatedProcess : 如果设置为true,这个service将运行在一个从系统中其他部分分离出来的特殊进程中,我们只能通过Service API来与它进行交流。默认为false。
-
android:label : 显示给用户的这个service的名字。如果不设置,将会默认使用
<application>
的label属性。 -
android:name : 这个service的路径名,例如“com.lypeer.demo.MyService”。这个属性是唯一一个必须填的属性。
-
android:permission : 其他组件必须具有所填的权限才能启动这个service。
-
android:process : service运行的进程的name。默认启动的service是运行在主进程中的。
下边来举个栗子:
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
class MyService : Service() {
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
//只在service创建的时候调用一次,可以在此进行一些一次性的初始化操作
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand")
//当其他组件调用startService()方法时,此方法将会被调用
//在这里进行这个service主要的操作
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(intent: Intent): IBinder? {
Log.d(TAG, "onBind")
//当其他组件调用bindService()方法时,此方法将会被调用
//如果不想让这个service被绑定,在此返回null即可
return null
}
override fun onDestroy() {
Log.d(TAG, "onDestroy")
//service调用的最后一个方法
//在此进行资源的回收
super.onDestroy()
}
companion object {
private const val TAG = "MyService"
}
}
<!--in manifests -->
<service android:name=".ServiceDemo"/>
3,如何启动Service?
开始我们说了两种启动方式,但都是笼统的概括了一下,下面来具体的说说这两种方式。
3.1,startService()
另一个组件通过调用startService()方法,就可以启动一个特定的service,并且这将导致service中的onStartCommand()方法被调用。在调用startService()方法的时候,其他组件需要在方法中传递一个intent参数,然后service将会在onStartCommand()中接收这个intent,并获取一些数据。比如此时某个Activity要将一些数据存入数据库中,我就可以通过intent把数据传入service,然后让service去进行连接数据库,存储数据等操作,而此时用户可以执行其他的任何操作——甚至包括销毁那个Activity——这并不会影响service存储数据这件事。
当一个service通过这种方式启动之后,它的生命周期就已经不受启动它的组件影响了,它可以在后台无限期的运行下去,只要service自身没有调用stopSelf()并且其他的组件没有调用针对它的stopService()。
另外,如果确定了使用这种方式启动service并且不希望这个service被绑定的话,那么也许除了传统的创建一个类继承service之外我们有一个更好的选择——继承IntentService。
如果是扩建Service类的话,通常情况下我们需要新建一个用于执行工作的新线程,因为默认情况下service将工作于应用的主线程,而这将会降低所有正在运行的Activity的性能。而IntentService就不同了。它是Service的子类,它使用工作线程来注意的处理所有的startService请求。如果你不要求这个service要同时处理多个请求,那么继承这个类显然要比直接继承Service好到不知道哪里去了——IntentService已经做了这些事:
- 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent
- 创建工作队列,用于将一个 Intent 逐一传递给 onHandleIntent() 实现,这样的话就永远不必担心多线程问题了
- 在处理完所有启动请求后停止服务,从此妈妈再也不用担心我忘记调用 stopSelf() 了
- 提供 onBind() 的默认实现(返回 null)
- 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现
因此我们只需要实现onHandleIntent()方法来完成具体的功能逻辑就可以了。
import android.content.Intent
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AppCompatActivity
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
val intent = Intent(this, MyService::class.java)
startService(intent)
}
}
import android.app.IntentService
import android.content.Intent
class MyIntentService
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
(name: String?) : IntentService(name) {
override fun onHandleIntent(intent: Intent?) {
//在这里根据intent进行操作
}
}
相比上面的继承service实现,这个确实要简单许多。但是要注意的是,如果需要重写其他的方法,比如onDestroy()方法,一定不要删掉它的超类实现!因为它的超类实现里面也许包括了对工作线程还有工作队列的初始化以及销毁等操作,如果没有了的话很容易出问题。
但是,如果你有让service同时处理多个请求的需求,这个时候就只能去继承Service了。这个时候就要自己去处理工作线程那些事。下面是一个官方的栗子:
import android.app.Service
import android.content.Intent
import android.os.*
import android.widget.Toast
class HelloService : Service() {
private var mServiceLooper: Looper? = null
private var mServiceHandler: ServiceHandler? = null
private val lock = java.lang.Object()
private inner class ServiceHandler(looper: Looper?) : Handler(looper) {
override fun handleMessage(msg: Message) {
val endTime = System.currentTimeMillis() + 5 * 1000
while (System.currentTimeMillis() < endTime) {
synchronized(lock) {
try {
lock.wait(endTime - System.currentTimeMillis())
} catch (e: Exception) {
}
}
}
stopSelf(msg.arg1)
}
}
override fun onCreate() {
val thread = HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND)
thread.start()
mServiceLooper = thread.looper
mServiceHandler = ServiceHandler(mServiceLooper)
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()
val msg = mServiceHandler!!.obtainMessage()
msg.arg1 = startId
mServiceHandler!!.sendMessage(msg)
return START_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
}
}
比起IntentService来,显然做的工作要多的多的多。但是,由于是自己处理的对onStartCommand的调用,它可以同时执行多个请求——虽然官方的栗子里没有这样做。但是如果你想这样做,就可以为每一个请求创建一个线程,然后立即运行这些请求。
另外,我们注意到onStartCommand()的返回值是一个很奇怪的值START_STICKY,这是个什么呢?或者说这个方法的返回值是用来干嘛的呢?事实上,它的返回值是用来指定系统对当前线程的行为的。它的返回值必须是以下常量之一:
- START_NOT_STICKY : 如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
-
START_STICKY : 如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但绝对不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。
-
START_REDELIVER_INTENT : 如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
除此之外,继承service来实现的service,一定要记得,停止这个service。因为除非系统必须回收内存资源,否则系统不会停止或销毁service。事实上,如果这个service是与可见组件绑定,其几乎永远不会被停止或销毁。在这种情况下,如果你忘记了在其工作完成之后将其停止,势必会造成系统资源的浪费以及电池电量的消耗,而这应当是我们要极力避免的。
3.2,bindService()
这是一种比startService更复杂的启动方式,同时使用这种方式启动的service也能完成更多的事情,比如其他组件可向其发送请求,接受来自它的响应,甚至通过它来进行IPC等等。我们通常将绑定它的组件成为客户端,而称它为服务器。
如果要创建一个支持绑定的service,我们必须要重写它的onBind()方法。这个方法会返回一个IBinder对象,它是客户端用来和服务器进行交互的接口。而要得到IBinder接口,我们通常有三种方式:继承Binder类,使用Messenger类,使用AIDL。
这三种方式也是各有各的好处以及缺点,更多的讲解将我会放在后边的文章中。
标签:调用,Service,service,服务者,组件,Intent,Android,android 来源: https://blog.csdn.net/elank0521/article/details/111320632