四大组件之全面了解Service(上)

前言

服务是一种在后台运行的组件,用于执行长时间运行的操作或为远程进程执行作业。 服务不提供用户界面。 例如,当用户位于其他应用中时,服务可能在后台播放音乐或者通过网络获取数据,但不会阻断用户与 Activity 的交互。诸如 Activity 等其他组件可以启动服务,让其运行或与其绑定以便与其进行交互。虽然现在官方都是强烈推荐使用WorkManager API 替换Service,但是我个人觉得还是有必要好好学习一下的,弄清楚不迷糊。

Service 的类型

前台服务

前台服务会执行一些用户可察觉到的操作。例如,音频应用将使用前台服务来播放音轨。前台服务必须显示 Notification。即使用户没有与应用互动,前台服务也会继续运行。除非服务停止或从前台移除,否则无法关闭此通知。

注意WorkManager API 提供了一种灵活的任务调度方法,并能够在需要时将这些作业作为前台服务运行。在许多情况下,使用 WorkManager 比直接使用前台服务更可取。

后台服务

后台服务执行用户不会直接注意到的操作。例如,如果应用使用服务来压缩其存储空间,则该服务通常是后台服务。

注意 :如果您的应用以 API 级别 26 或更高级别为目标平台,那么当应用本身不在前台运行时,系统会对运行后台服务施加限制。例如,在大多数情况下,您不应在后台访问位置信息。请改为使用 WorkManager 调度任务

绑定服务

当应用组件通过调用 bindService() 绑定到服务时,服务即处于绑定状态。绑定服务提供了一个客户端-服务器接口,该接口允许组件与服务进行交互、发送请求、接收结果,甚至使用进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

Service基础知识

常用回调

以下是一些需要重写的重要回调方法:

  • onStartCommand()

    当另一个组件(如 activity)请求启动服务时,系统会通过调用 startService() 来调用此方法。执行此方法时,服务会启动并且可以在后台无限期运行。如果您实现此方法,则需负责在服务工作完成后通过调用 stopSelf()stopService() 来停止服务。如果您只想提供绑定,则无需实现此方法。

  • onBind()

    当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用 bindService() 来调用此方法。 在此方法的实现中,您必须通过返回 IBinder 提供一个接口,以供客户端用来与服务通信。您必须始终实现此方法;但是,如果您不想允许绑定,则应返回 null。

  • onCreate()

    首次创建服务时(在调用 onStartCommand()onBind() 之前),系统会调用此方法来执行一次性设置过程。如果服务已在运行,则不会调用此方法。

  • onDestroy()

    当服务不再使用并且即将被销毁时,系统会调用此方法。您的服务应实现此机制以清理所有资源,例如线程、注册的监听器或接收器。这是服务接收的最后一个调用。

清单文件声明

Service与Activity一样如果要使用的话必须在AndroidMainifest.xml声明,如下:

xml 复制代码
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
        <application
            ......
            >

            <service android:name=".MyServices"/>
            ......
        </application>

    </manifest>

android:name 属性是唯一的必需属性,用于指定服务的类名称。发布应用后,请勿更改此名称,以免因依赖显式 intent 来启动或绑定服务而破坏代码

注意

为确保您的应用安全无虞,请务必在启动 Service 时使用显式 intent,并且不要为您的服务声明 intent 过滤器。使用隐式 intent 启动服务存在安全隐患,因为您无法确定响应 intent 的服务,而且用户看不到启动的是哪项服务。从 Android 5.0(API 级别 21)开始,如果使用隐式 intent 调用 bindService(),系统会抛出异常。

Service生命周期

上图描述了Service的生命周期,左边描述了startService创建的生命周期,右边描述了bindService创建的生命周期。

Service的整个声明周期介于介于onCreate()onDestroy() 之间。Service的"有效生命周期"从调用 onStartCommand()onBind() 开始。系统会将每个方法传递给 startService()bindService()Intent。如果是startService启动的服务,有效生命周期与整个生命周期同时结束,如果是bindService创建的则有效生命周期在 onUnbind() 返回时结束。

对于bindService创建的服务,当所有绑定解除的时候会自动销毁(除非是startService启动的),所以如果知识bindService启动的服务不需要管理其生命周期。

此外,如果您的服务已启动并接受绑定 ,那么当系统调用您的 onUnbind() 方法时,如果您想在客户端下次绑定到服务时收到对 onRebind() 的调用,可以选择返回 trueonRebind() 返回空值,但客户端仍会在其 onServiceConnected() 回调中接收 IBinder。下图说明了这种生命周期的逻辑。

后台服务

后台服务是另一个组件通过startService()启动的服务,这回导致其调用onStartCommand方法。服务启动之后生命周期独立于启动它的组件,就算启动它的组件销毁了也不影响。可以通过调用 stopSelf() 自行停止运行,或者由其他组件通过调用 stopService() 来停止。组件可以通过startService()传递数据,服务会在onStartCommand中接收intent

Android 框架还提供了 ServiceIntentService 子类,该类使用工作器线程逐一处理所有启动请求。不建议 新应用使用此类,因为它从 Android 8 Oreo 开始将无法正常工作,原因是引入了后台执行限制。此外,从 Android 11 开始,它将被废弃。您可以使用 JobIntentService(最新版本也即将废弃推荐使用WorkManager) 替代与更高版本的 Android 兼容的 IntentService。 通过前面的介绍,相信我们已经对Service有了一个基本的了解,接下来通过实例,一步一步创建一个基础的后台服务。

1.创建一个Service类

kotlin 复制代码
class MyServices : Service() {
    companion object {
        const val TAG = "MyServices"
    }

    override fun onCreate() {
        Log.i(TAG, "onCreate")
        super.onCreate()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i(TAG, "onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {
        Log.i(TAG, "onDestroy")
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
}

上面我们创建了一个Service类并复写了前面介绍的几个常用回调,为了方便调试我们还添加了几行日志。

2.在AndroidMainifest.xml申明

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
        <application
            ......
            >

            <service android:name=".MyServices"/>
            ......
        </application>

    </manifest>

3.创建操作布局按钮

新建一个Activity#xml文件,内容如下:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".activity.MainActivity">

    <Button
        android:id="@+id/start_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Service" />

    <Button
        android:id="@+id/stop_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Stop Service" />

</LinearLayout>

4.服务启动与销毁

这里我们定义了两个按钮一个用于启动Service,一个用于停止Service

kotlin 复制代码
startService.setOnClickListener {
    //启动Service
    val intent = Intent(this@MainActivity, MyServices::class.java)
    startService(intent)
}

stopService.setOnClickListener {
    //停止Service
    val intent = Intent(this@MainActivity, MyServices::class.java)
    stopService(intent)
}

这样一个简单的Service程序就写完了,接下来我们运行一下程序,点击startService并打开LogCat查看日志,如果正常的情况下会得到如下日志:

我们可以看到,启动时Service的onCreateonStartCommand方法被调用了,再次点击以下startService按钮。

仔细观察我们可以发现,再次点击启动按钮之后Service的onCreate没用被调用了,这是因为onCreate方法只会在Service第一次被创建的时候被调用。 点击停止按钮,我们就可以将Service踢掉了,查看日志如下:

以上,我们就完成了一个简单的后台Service创建流程。

绑定服务

绑定服务是客户端-服务器接口中的服务器。它允许 activity 等组件绑定到服务、发送请求、接收响应,以及执行进程间通信 (IPC)。绑定服务通常只在为其他应用组件提供服务时处于活动状态,不会无限期在后台运行。

绑定服务是 Service 类的实现,可让其他应用与其绑定并进行交互。如需为服务提供绑定,需要实现 onBind() 回调方法。此方法会返回一个 IBinder 对象,该对象定义的编程接口可供客户端用来与服务进行交互。

在实现绑定服务的过程中,最重要的环节是定义 onBind() 回调方法所返回的接口。总共有三种定义服务 IBinder 接口的方法。

  • 扩展IBinder类
  • 使用Messenger
  • 使用AIDL

对于大多数应用而言,AIDL方式不是创建绑定服务的好方法,因为其使用过于复杂,所以这里我们只介绍另外两种方法,下面还是通过例子来说明。

扩展Binder类

如果没有跨进程的需求,使用这种方法。如果只是本应用使用当前Service,需要优先使用这种方式。

1.创建一个Service类

具体代码如下代码:

kotlin 复制代码
class MyServices : Service() {
    //2.创建Binder实例
    private val mBinder = BinderImp()
    fun doSomething() {
        Log.i(TAG,"start doing something")
    }
    ······
    override fun onBind(intent: Intent?): IBinder {
        //3.返回Binder实例给客户端
        return mBinder
    }
    //1.扩展Binder类
    inner class BinderImp :Binder() {
        //4.获取Service实例进而获取其公有方法
        fun getService():MyServices {  
            return this@MyServices  
        }
    }
}

以上代码我们创建了一个内部类扩展Binder类并创建了一个它的实例,接下来从onBind()返回其实例来创建接口,客户端收到Binder之后(onServiceConnected()回调接收),可通过它直接访问Binder实例或Service中提供的共有方法。

2.在AndroidMainifest.xml申明

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
        <application
            ......
            >

            <service android:name=".MyServices"/>
            ......
        </application>

    </manifest>

3.添加操作布局按钮

xml 复制代码
    <Button
        android:id="@+id/bind_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Bind Service" />
    
    <Button 
        android:id="@+id/unbind_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Unbind Service"/>

4.服务绑定与解绑

然后在MainActivity中添加bind和unBind代码,调用 bindService() 来检索该接口,并开始对服务调用方法。Service的生命周期与与其绑定的应用组件挂钩,因此,如果没有任何组件绑定到该Service,系统将销毁该Service

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    companion object {
        const val TAG = "MainActivity"
    }

    private val binding by binding(ActivityMainBinding::inflate)
    private val connection = object :ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.i(TAG,"onServiceConnected")
            if (service is MyServices.BinderImp) {
                val binderImp = service as MyServices.BinderImp
                binderImp.getService().doSomething()
            }
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            Log.i(TAG,"onServiceDisconnected")
        }

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        .......
        binding.bindService.setOnClickListener {
            val intent = Intent(this@MainActivity, MyServices::class.java)
            bindService(intent,connection, BIND_AUTO_CREATE)
        }
        binding.unbindService.setOnClickListener {
            unbindService(connection)
        }
    }
}
关于绑定

binService方法有三个参数

  • 第一个参数是intent对象,用于显式命名要绑定的服务
  • 第二个参数是我们创建的ServiceConnection实例
  • 指示绑定选项(通常为 BIND_AUTO_CREATE),用于在服务尚未处于活动状态时创建服务。其他可能的值为 BIND_DEBUG_UNBINDBIND_NOT_FOREGROUND0(表示无)。

另外两个参数好理解,这里讲一下第二个参数,我们创建了一个ServiceConnection对象,重写了它的onServiceConnectedonServiceDisconnected方法,这两个方法分别在Service与Activity建立联系和解除联系的时候调用。一切准备就绪,点击bindService按钮之后日志如下:

可以看到,MyServices中的onCreateonBind方法被调用,这时Service已经和MainActivity组件绑定上了并触发了onServiceConnected回调。在onServiceConnected中我们通过向下转型获取到了MyServices#BinderImp的实例,通过这个实例,我们就可以调用MyServices或者MyServices#BinderImp中的任何public方法,上例代码我们就调用了MyService的doSomething方法。

解除绑定

当我们通过binService的方式启动Service的时候想要销毁这个Service,点击stopService的时候发现Service的onDestroy方法没有调用,也就是说Service没有被销毁,那么怎么销毁bind方式启动的service呢?很简单,调用Activity的unbindService方法,传入我们创建的ServiceConnection对象。日志如下:

但细心的同学可能就会发现,销毁的时候onServiceDisconnected方法并没有调用,因为这个方法只有在Service异常销毁的时候才会被调用(例如内存的资源不足时)。

注意:

当用户即点击了startService又点击了bindService时,由于bindService启动标识时BIND_AUTO_CREATE,销毁的时候单独点击stopService或者unbinService都不会销毁Service,只有两个都调用时Service才会被销毁。

使用Messenger

如果我们想让Service与远程进程同行,可以优先考虑使用这个方法而不是去使用AIDL,原因有二

  • 对于大多数应用,服务不需要执行多线程处理,因此使用 Messenger 可让服务一次处理一个调用。
  • 使用简单

如果您的服务必须执行多线程处理,请使用 AIDL 定义接口。

1.创建一个Service类

kotlin 复制代码
/** Command to the service to display a message.  */
const val MSG_SAY_HELLO = 1

class MessengerService : Service() {
    companion object {
        const val TAG = "MessengerService"
    }

    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    private lateinit var mMessenger: Messenger

    /**
     * Handler of incoming messages from clients.
     */
    internal class IncomingHandler(
        context: Context,
        private val applicationContext: Context = context.applicationContext
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO -> {
                    Log.i(TAG,"hello${ Thread.currentThread().name}")
                }
                else -> super.handleMessage(msg)
            }
        }
    }

    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    override fun onBind(intent: Intent): IBinder? {
        Log.i(TAG,"onBind")
        mMessenger = Messenger(IncomingHandler(this))
        return mMessenger.binder
    }
}

以上首先我们创建了一个MessengerService类继承自Service;其次实现了一个Handler(IncomingHandler)用来处理从客户端发送过来的消息;然后在onBind方法中通过Messenger创建了一个IBinder并将其返回给客户端;最后定义了一个消息TAG用于在客户端创建消息。

2.在AndroidMainifest.xml申明

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
        <application
            ......
            >

            <service android:name=".MessengerService"/>
            ......
        </application>

    </manifest>

3.添加发送消息按钮

xml 复制代码
    <Button
        android:id="@+id/send_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Bind Service" />

4.客户端(应用组件例如Activity)发送消息

kotlin 复制代码
import com.abiao.services.MSG_SAY_HELLO

class SecondActivity : AppCompatActivity() {
    private var mService:Messenger? = null
    private var mIsBind = false
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.i(TAG,"onServiceConnected:${Thread.currentThread().name}")
            mService = Messenger(service)
            mIsBind = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            mService = null
            mIsBind = false
            Log.i(TAG,"onServiceDisconnected")
        }
    }
    companion object {
        const val TAG = "SecondActivity"
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
       
        sendMessage.setOnClickListener {
            Log.i(TAG,"sendMessage")
            if (!mIsBind)return@setOnClickListener
            val message = Message.obtain(null,MSG_SAY_HELLO,0,0)
            try{
                mService?.send(message)
            }catch (e:RemoteException) {
                e.printStackTrace()
            }
        }
    }

    override fun onStart() {
        super.onStart()
        Intent(this,MessengerService::class.java).also { intent ->
            bindService(intent,connection,Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        if (mIsBind) {
            unbindService(connection)
            mIsBind = false
        }
    }
}

以上首先我们创建了一个ServiceConnection对象,区别是在我们在onServiceConnected中根据服务中回调过来的IBinder创建了一个Messenger实例,在onServiceDisconnected释放Messenger实例;然后分别在onStartonStop方法中绑定服务和解绑服务;最后在点击事件里将Message发送给服务。点击按钮我们可以看到如下日志:

到这里我们就创建好了一个Messenger类型的Service了。

总结

以上就是关于Service的一些常用用法了,另外还有前台服务和绑定服务的AIDL类型,我们将在下一篇继续。

相关推荐
AFinalStone6 小时前
Android 7系统异常问题排查(四)Framework层(上)—System Server Watchdog
android·系统异常
宝杰X78 小时前
Android Room3 多平台数据库
android·数据库
huibin14785236910 小时前
热缓解学习记录
android·学习
小田的博客13 小时前
SAP MM 供应商银行主数据更新报错!message R1228!
android·java·服务器
一笑的小酒馆14 小时前
Android智能猫砂盆视频加载慢卡顿问题分析
android
只会cv的小前端16 小时前
七巧低代码服务端脚本使用方法
android·低代码·rxjava
16 小时前
Android 自定义 View 实战:从零打造工业级全向摇杆控件(OmniJoystickView)
android·kotlin·自定义view·摇杆控件
雨白16 小时前
深入理解 Kotlin 协程 (十一):以逸待劳,探秘 select 多路复用与并发安全策略
android·kotlin
hunterandroid19 小时前
[Android 从零到一] Android 深度链接与 App Links:从 URI Scheme 到可验证的应用跳转
android
我命由我1234521 小时前
Jetpack Compose - Material Design 断点范围、WindowSizeClass、针对不同屏幕尺寸创建预览、四类导航栏
android·java·开发语言·java-ee·kotlin·android jetpack·android runtime