秒懂Android Service组件(1)

导读:本篇文章讲解 秒懂Android Service组件(1),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

Android出来这么些年,发现国内程序员关于Android开发的文章写得越来越好了,有的真让人赞叹。不过再好也是别人的,自己只有亲自实践亲自总结,才能夯实基础,提升自我,尽力向大神的水平靠拢。

概述

真正做Android开发算来应该有两年多了,说实话自己写Service的时候还是比较少的。最近使用到了,刚好对其做个总结,我这边主要侧重于应用层面的,其实是因为我比较菜,深入原理的话怕讲不好。

首先我们应该清楚Service是Android四大组件之一,下面是官方网站对Service的定义,非常清楚

Service 是一个可以在后台执行长时间运行而不提供用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。

服务分为两种形式:

  • 启动

当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如,它可能通过网络下载或上传文件。 操作完成后,服务会自行停止运行。

  • 绑定

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

如何使用

对于一项技术首先要清楚什么场景下应该使用,这个技术是不是解决了我们的问题,然后才是如何使用

使用之前要明白Service有两种使用方式,启动模式绑定模式,如概述所示,这两种方式适用于不同的使用场景。

启动模式使用方法

这种模式启动的Service,一旦启动则与启动者毫无关系了,这个服务会一直运行在后台,直到服务完成任务后自己结束自己,或者其他组件主动结束它,或者操作系统强制结束它。

1.声明标签

AndroidManifest.xml文件中声明Service标签,如下面代码所示

        <service
            android:name=".your.services.name"
            android:enabled="true"
            android:exported="true">
        </service>

其实一个service只有name属性是必须的,其他都是可选的。
name: 这个Service 的唯一标识
enabled:此服务是否可以被系统实例化
exported:此服务是否可以被本应用以外的其他应用调起。

下面是其完整的代码清单,具体可参考 Service

<service android:description="string resource"
         android:directBootAware=["true" | "false"]
         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>

2.创建服务

创建一个Service子类(Kotlin代码)

package top.ss007.servicedemo.services
...
class Service1 : Service() {
    companion object {
        private val TAG = Service1::class.java.simpleName
    }

    //Service 的抽象方法,子类必须实现,虽然在启动模式下无需写具体实现代码
    override fun onBind(intent: Intent): IBinder? {
        Log.d(TAG, "onBind")
        return null
    }

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

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

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if (intent != null) {
            Log.d(TAG, "onStartCommand:" + intent.getStringExtra("param1") + " 开始id: " + startId)
        }
        Log.d(TAG,"Thread id:"+ Thread.currentThread().id+" Thread name:"+Thread.currentThread().name)
        return super.onStartCommand(intent, flags, startId)
    }
}

我们的服务必须继承Service类,并重写onBind方法,该方法是抽象方法必须重写,但是由于是启动模式可以不实现,返回null即可。此类还重写了onCreate、onStartCommand、onDestroy三个主要的生命周期方法,几个方法说明如下:

onBind()
在当前的启动模式下无需实现,直接返回null即可。 

onCreate()
  首次创建服务时,系统将调用此方法来执行初始化(在调用 onStartCommand() 或onBind() 之前)该方法只调用一次。

onStartCommand()
在当前启动模式下,当另一个组件(如 Activity)调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 我们需要在服务工作完成后,调用 stopSelf() 或 stopService() 来停止此服务。

onDestroy()
  当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。

3.启动服务

...
class ActMainActivity : AppCompatActivity() {

    companion object {
        private val TAG = ActMainActivity::class.java.simpleName
    }   

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.act_main)
        btnStartService1.setOnClickListener(clickListener)
        btnStopService1.setOnClickListener(clickListener)
        ...
    }

    private val clickListener = View.OnClickListener { v ->
        val intent = Intent(this@ActMainActivity, Service1::class.java)
        intent.putExtra("param1", "hello server")
        when (v.id) {
            R.id.btnStartService1 -> {
                startService(intent)
            }
            R.id.btnStopService1 -> {
                stopService(intent)
            }
            ...
            else -> {

            }
        }
    }

    ... 

}

如上代码是一个Activity,上面有两个按钮,一个启动服务,一个结束服务。点击启动服务按钮启动服务,Logcat输出如下

03-28 01:01:06.282 4485-4485/top.ss007.servicedemo D/Service1: onCreate
03-28 01:01:06.283 4485-4485/top.ss007.servicedemo D/Service1: onStartCommand:hello server 开始id: 1
    Thread id:2 Thread name:main

从上面的log可以获得如下几个信息

  1. 以启动模式开始服务不会执行onBind方法
  2. 会依次执行onCreateonStartCommand两个方法
  3. service是运行在自己的进程中的主线程中的,不会单开线程,所以不能做耗时操作。

多次点击启动服务按钮,会多次执行onStartCommandonCreate方法只会执行一次,如下log所示

03-28 01:04:47.321 4485-4485/top.ss007.servicedemo D/Service1: onCreate
03-28 01:04:47.322 4485-4485/top.ss007.servicedemo D/Service1: onStartCommand:hello server 开始id: 1
    Thread id:2 Thread name:main
03-28 01:10:37.857 4485-4485/top.ss007.servicedemo D/Service1: onStartCommand:hello server 开始id: 2
    Thread id:2 Thread name:main
03-28 01:10:50.372 4485-4485/top.ss007.servicedemo D/Service1: onStartCommand:hello server 开始id: 3
    Thread id:2 Thread name:main

从上面的log可以看出,startId是不断增加的,而所有的线程id都是一样的,都是主线程。

绑定模式使用方法

在此模式下,Service可以看做是服务端,绑定的组件,例如Activity,看做是客户端,他们之间是通过Binder来实现通信的。我们此处集中在同一进程间的通信,不涉及跨进程通信(IPC)的讨论,所以采用扩展 Binder 类 的方法即可

实现步骤:

  • 1.新建一个自定义的名为LocalService的类,此类继承至Service
  • 2.新建一个内部类LocalBinder,此类继承自Binder类,并在里面提供一个public方法给客户端调用,例如getService()方法。
 inner class LocalBinder : Binder() {
        fun getService(): LocalService {
            return this@LocalService
        }
    }
  • 3.在LocalService里面的onBind的方法里面将LocalBinder的实例返回。
  • 4.在客户端中,从 onServiceConnected() 回调方法接收 Binder实例对象,那样就可以调用绑定服务了。

下面具体实现上面的步骤:

1.声明标签

AndroidManifest.xml文件中声明Service标签,与前述一致

2.创建服务

创建一个Service子类(Kotlin代码)

...
class LocalService : Service() {

    companion object {
        private val TAG: String = LocalService::class.java.simpleName
    }

    private var quit: Boolean = false
    private var count: Int = 0

    override fun onBind(intent: Intent): IBinder? {
        Log.i(TAG, "Service is invoke onBind");
        return LocalBinder()
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.i(TAG, "Service is invoke onUnbind");
        return super.onUnbind(intent)
    }

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "Service is invoke Created");
        val thread = Thread {
            while (quit.not()) {
                Thread.sleep(1000)
                count++
                if (count > 1000) {
                    quit = true
                    break
                }
            }
        }
        thread.start()
    }

    fun getCount(): Int {
        return count
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i(TAG, "Service is invoke Destroyed");
        quit = true;
    }

    inner class LocalBinder : Binder() {
        fun getService(): LocalService {
            return this@LocalService
        }
    }
}

我们的服务必须继承Service类,并重写onBind方法,该方法是抽象方法必须重写,在当前绑定状态的情况下需要实现该方法并返回一个IBinder的实现类。此类还重写了onCreateonUnbindonDestroy三个主要的生命周期方法,几个方法说明如下:

onBind()
在当前绑定模式下,当其他组件通过调用bindService() 与服务绑定时系统将调用此方法。在此方法的实现中,必须返回 一个IBinder 接口的实现类,供客户端用来与服务进行通信。

onCreate()
  首次创建服务时,系统将调用此方法来执行初始化(在调用 onStartCommand() 或onBind() 之前)该方法只调用一次。

onUnbind
客户端可以通过调用 unbindService() 关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务。 (服务不必自行停止运行。)

onDestroy()
  当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。
  
这这个服务中,我们有一个公共方法 getCount(),外界通过这个方法可以获取到 count的值,而这个count在服务创建之初会每秒自增1直到1000

3.绑定服务

package top.ss007.servicedemo
...
class ActMainActivity : AppCompatActivity() {

    companion object {
        private val TAG = ActMainActivity::class.java.simpleName
    }

    var localService: LocalService? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.act_main)
        ...
        btnStartServiceB1.setOnClickListener(clickListener)
        btnStopServiceB1.setOnClickListener(clickListener)
        btnShowCounter.setOnClickListener(clickListener)
        ...
    }

    private val clickListener = View.OnClickListener { v ->
        ...

        val bindIntent = Intent(this@ActMainActivity, LocalService::class.java)
        when (v.id) {
            ...
            R.id.btnStartServiceB1 -> {
                Log.d(TAG, "绑定调用:bindService");
                bindService(bindIntent, serviceCon, Service.BIND_AUTO_CREATE)
            }
            R.id.btnStopServiceB1 -> {
                Log.d(TAG, "解除绑定调用:unbindService");
                if (localService != null) {
                    localService = null;
                    unbindService(serviceCon);
                }
            }
            R.id.btnShowCounter -> {
                btnShowCounter.text = localService?.getCount().toString()
                Log.d(TAG, "服务中的计数器:"+localService?.getCount().toString());
            }
            ...
            else -> {
            }
        }
    }

    private val serviceCon: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.d(TAG, "绑定成功调用:onServiceConnected "+" "+name?.className)
            // 获取Binder
            val binder = service as LocalService.LocalBinder
            localService = binder.getService()
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            localService = null
        }
    }
}

如上代码是一个Activity,上面有3个按钮,一个启动服务,一个结束服务,一个获取服务中count的值。点击绑定服务按钮绑定服务,Logcat输出如下

03-28 02:33:31.576 6631-6631/top.ss007.servicedemo D/ActMainActivity: 绑定调用:bindService
03-28 02:33:31.580 6631-6631/top.ss007.servicedemo D/LocalService: Service is invoke Created
03-28 02:33:31.581 6631-6631/top.ss007.servicedemo I/LocalService: Service is invoke onBind
03-28 02:33:31.597 6631-6631/top.ss007.servicedemo D/ActMainActivity: 绑定成功调用:onServiceConnected  top.ss007.servicedemo.services.LocalService

可见,当绑定成功后,activity中的ServiceConnection 里面的onServiceConnected就会收到回调,从回调里面就获取到了服务端传过来的Binder实例,进而可以通过其调用服务里的方法与服务交互了。

点击获取服务中计数的按钮,输出log如下

03-28 02:57:44.871 6631-6631/top.ss007.servicedemo D/ActMainActivity: 服务中的计数器:3
03-28 02:57:54.127 6631-6631/top.ss007.servicedemo D/ActMainActivity: 服务中的计数器:12

可见从Activity里面确实调用到了服务里面的函数。

总结

上面就是在同进程下,服务Service的两种使用方法。相关的话题还涉及到运行在前台的服务IntentService 等,大家可以搜索相关内容学习。

本文源代码下载地址

参考文章
Google官方网站
关于Android Service真正的完全详解,你需要知道的一切

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/14792.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!