四大组件之全面了解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类型,我们将在下一篇继续。

相关推荐
CheungChunChiu28 分钟前
Android10 rk3399 以太网接入流程分析
android·framework·以太网·eth·net·netd
木头没有瓜1 小时前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp
键盘侠0071 小时前
springboot 上传图片 转存成webp
android·spring boot·okhttp
江上清风山间明月2 小时前
flutter bottomSheet 控件详解
android·flutter·底部导航·bottomsheet
Crossoads3 小时前
【汇编语言】外中断(一)—— 外中断的魔法:PC机键盘如何触发计算机响应
android·开发语言·数据库·深度学习·机器学习·计算机外设·汇编语言
sunphp开发者4 小时前
黑客攻击网站,篡改首页问题排查修复
android·js
我又来搬代码了4 小时前
【Android Studio】创建新项目遇到的一些问题
android·ide·android studio
ggs_and_ddu9 小时前
Android--java实现手机亮度控制
android·java·智能手机
zhangphil14 小时前
Android绘图Path基于LinearGradient线性动画渐变,Kotlin(2)
android·kotlin
watl015 小时前
【Android】unzip aar删除冲突classes再zip
android·linux·运维