Android kotlin service使用简析

2020-08-18  本文已影响0人  水天滑稽天照八野滑稽石

前言

模拟器教程的前奏之一

什么是service

android 4大组件之一,可以理解成是没有页面的Activity,用来做一些在后台的业务,其中service有2中启动方式


通过StartService启动Service

通过startService启动后,service会一直无限期运行下去,只有外部调用了stopService()或stopSelf()方法时,该Service才会停止运行并销毁。

要创建一个这样的Service,你需要让该类继承Service类,然后重写以下方法:

创建一个Service


startService代码实例

class MyService : Service() {

    override fun onCreate() {
        Log.i("xiao","onCreate - Thread ID = " + Thread.currentThread().id)
        super.onCreate()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i("xiao", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id)
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onBind(p0: Intent?): IBinder? {
        Log.i("xiao", "onBind - Thread ID = " + Thread.currentThread().id)
        return null
    }

    override fun onDestroy() {
        Log.i("xiao", "onDestroy - Thread ID = " + Thread.currentThread().id)
        super.onDestroy()
    }
}

在MainActivity中三次startService,之后stopService

class MyService : Service() {

    override fun onCreate() {
        Log.i("xiao","onCreate - Thread ID = " + Thread.currentThread().id)
        super.onCreate()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i("xiao", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id)
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onBind(p0: Intent?): IBinder? {
        Log.i("xiao", "onBind - Thread ID = " + Thread.currentThread().id)
        return null
    }

    override fun onDestroy() {
        Log.i("xiao", "onDestroy - Thread ID = " + Thread.currentThread().id)
        super.onDestroy()
    }
}


总结:

  1. 主线程打印出是1,所有回调方法中打印出的执行线程ID都是1,证明回调方法都是在主线程中执行的。
  2. 三次调用startService,只触发一次onCreate回调,触发了三次onStartCommand回调,且startId分别为1,2,3。证明 多次startService不会重复执行onCreate回调,但每次都会执行onStartCommand回调。

通过bindService启动Service

bindService启动服务特点:
1.bindService启动的服务和调用者之间是典型的client-server模式。调用者是client,service则是server端。service只有一个,但绑定到service上面的client可以有一个或很多个。这里所提到的client指的是组件,比如某个Activity。
2.client可以通过IBinder接口获取Service实例,从而实现在client端直接调用Service中的方法以实现灵活交互,这在通过startService方法启动中是无法实现的。
3.bindService启动服务的生命周期与其绑定的client息息相关。当client销毁时,client会自动与Service解除绑定。当然,client也可以明确调用Context的unbindService()方法与Service解除绑定。当没有任何client与Service绑定时,Service会自行销毁。

bindService代码实例

activity_a.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".socket.AActivity">

    <Button
        android:id="@+id/btn_bind_service_a"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="A BindService"
        android:textAllCaps="false"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginBottom="16dp"/>

    <Button
        android:id="@+id/btn_unbind_service_a"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="A UnBindService"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="16dp"
        android:textAllCaps="false"
        android:layout_marginBottom="16dp"/>

    <Button
        android:id="@+id/btn_a_start_b"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="A StartActivity B"
        android:layout_marginLeft="16dp"
        android:textAllCaps="false"
        android:layout_marginRight="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginBottom="16dp"/>

    <Button
        android:id="@+id/btn_finish_a"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:textAllCaps="false"
        android:text="A Finish"
        android:layout_marginRight="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginBottom="16dp"/>


</LinearLayout>
activity_b.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".socket.BActivity">

    <Button
        android:id="@+id/btn_bind_service_b"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="B BindService"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="16dp"
        android:textAllCaps="false"
        android:layout_marginBottom="16dp"/>

    <Button
        android:id="@+id/btn_unbind_service_b"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="B UnBindService"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="16dp"
        android:textAllCaps="false"
        android:layout_marginBottom="16dp"/>

    <Button
        android:id="@+id/btn_finish_b"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="B Finish"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="16dp"
        android:textAllCaps="false"
        android:layout_marginBottom="16dp"/>

</LinearLayout>
交互说明

其实从见面上看已经很清楚了:

  1. AActivity可以绑定解绑service(MyService),跳转到BActivity及关闭自己;
  2. BActivity可以绑定解绑service(MyService)及关闭自己
MySerivce

要想让Service支持bindService调用方式,需要做以下事情:

  1. 在Service的onBind()方法中返回IBinder类型的实例
  2. onBind()方法返回的IBinder的实例需要能够返回Service实例本身。通常,最简单的方法就是在service中创建binder的内部类,加入类似getService()的方法返回Service,这样绑定的client就可以通过getService()方法获得Service实例了。
class MyService : Service() {

    //client 可以通过Binder获取Service实例
    inner class MyBinder : Binder() {
        val service: MyService
        get() = this@MyService
    }

    //通过binder实现调用者client与Service之间的通信
    private val binder = MyBinder()

    private val generator: Random = Random()

    override fun onCreate() {
        Log.i("xiao", "MyService - onCreate - Thread = " + Thread.currentThread().name)
        super.onCreate()
    }

    /**
     *  @param intent 启动时,启动组件传递过来的Intent,如Activity可利用Intent封装所需要的参数并传递给Service
     *  @param flags 表示启动请求时是否有额外数据,可选值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY
     *  0: 在正常创建Service的情况下,onStartCommand传入的flags为0。
     *
     *  START_FLAG_REDELIVERY:
     *  这个值代表了onStartCommand()方法的返回值为 START_REDELIVER_INTENT,
     *  而且在上一次服务被杀死前会去调用stopSelf()方法停止服务。
     *  其中START_REDELIVER_INTENT意味着当Service因内存不足而被系统kill后,
     *  则会重建服务,并通过传递给服务的最后一个 Intent调用 onStartCommand(),此时Intent时有值的。
     *
     *  START_FLAG_RETRY
     *  该flag代表当onStartCommand()调用后一直没有返回值时,会尝试重新去调用onStartCommand()。
     *
     *  @param startId 指明当前服务的唯一ID,与stopSelfResult(int startId)配合使用,stopSelfResult()可以更安全地根据ID停止服务。
     *
     *  @return
     *  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。
     *  这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
     */
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i("xiao", "MyService - onStartCommand - startId = $startId, Thread = " + Thread.currentThread().name)
        return START_NOT_STICKY
    }

    override fun onBind(intent: Intent): IBinder{
        Log.i("xiao", "MyService - onBind - Thread = " + Thread.currentThread().name)
        return binder
    }

    override fun onUnbind(intent: Intent): Boolean {
        Log.i("xiao", "MyService - onUnbind - from = " + intent.getStringExtra("from"))
        return false
    }

    override fun onDestroy() {
        Log.i("xiao", "MyService - onDestroy - Thread = " + Thread.currentThread().name)
        super.onDestroy()
    }

    //getRandomNumber是Service暴露出去供client调用的公共方法
    fun getRandomNumber(): Int {
        return generator.nextInt()
    }
}

client端要做的事情:
1.创建ServiceConnection类型实例,并重写onServiceConnected()方法和onServiceDisconnected()方法。
2.当执行到onServiceConnected回调时,可通过IBinder实例得到Service实例对象,这样可实现client与Service的连接。
3.onServiceDisconnected回调被执行时,表示client与Service断开连接,在此可以写一些断开连接后需要做的处理。

AAcitivy
class AActivity : AppCompatActivity() {

    private var service: MyService? = null
    private var isBind = false

    private var conn = object : ServiceConnection{
        override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
            isBind = true
            val myBinder = p1 as MyService.MyBinder
            service = myBinder.service
            Log.i("xiao", "ActivityA - onServiceConnected")
            val num = service!!.getRandomNumber()
            Log.i("xiao", "ActivityA - getRandomNumber = $num");
        }

        override fun onServiceDisconnected(p0: ComponentName?) {
            isBind = false
            Log.i("xiao", "ActivityA - onServiceDisconnected")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_a)

        Log.i("xiao", "ActivityA - onCreate - Thread = " + Thread.currentThread().name)

        btn_bind_service_a.setOnClickListener {
            val intent = Intent(this,MyService::class.java)
            intent.putExtra("from","ActivityA")
            Log.i("xiao", "----------------------------------------------------------------------")
            Log.i("xiao", "ActivityA 执行 bindService");
            bindService(intent, conn, Context.BIND_AUTO_CREATE)
        }
        btn_unbind_service_a.setOnClickListener {
            if(isBind){
                Log.i("xiao", "----------------------------------------------------------------------")
                Log.i("xiao", "ActivityA 执行 unbindService");
                unbindService(conn)
            }
        }
        btn_a_start_b.setOnClickListener {
            val intent = Intent(this,BActivity::class.java)
            Log.i("xiao", "----------------------------------------------------------------------")
            Log.i("xiao", "ActivityA 启动 ActivityB");
            startActivity(intent)
        }
        btn_finish_a.setOnClickListener {
            Log.i("xiao", "----------------------------------------------------------------------")
            Log.i("xiao", "ActivityA 执行 finish");
            finish()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i("xiao", "ActivityA - onDestroy")
    }

}
BAcitivy
class BActivity : AppCompatActivity() {

    private var service: MyService? = null
    private var isBind = false

    private var conn = object : ServiceConnection {
        override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
            isBind = true
            val myBinder = p1 as MyService.MyBinder
            service = myBinder.service
            Log.i("xiao", "ActivityB - onServiceConnected")
            val num = service!!.getRandomNumber()
            Log.i("xiao", "ActivityB - getRandomNumber = $num");
        }

        override fun onServiceDisconnected(p0: ComponentName?) {
            isBind = false
            Log.i("xiao", "ActivityB - onServiceDisconnected")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_b)

        btn_bind_service_b.setOnClickListener {
            val intent = Intent(this,MyService::class.java)
            intent.putExtra("from","ActivityB")
            Log.i("xiao", "----------------------------------------------------------------------")
            Log.i("xiao", "ActivityB 执行 bindService");
            bindService(intent, conn, Context.BIND_AUTO_CREATE)
        }
        btn_unbind_service_b.setOnClickListener {
            if(isBind){
                Log.i("xiao", "----------------------------------------------------------------------")
                Log.i("xiao", "ActivityB 执行 unbindService");
                unbindService(conn)
            }
        }
        btn_finish_b.setOnClickListener {
            Log.i("xiao", "----------------------------------------------------------------------")
            Log.i("xiao", "ActivityB 执行 finish");
            finish()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i("xiao", "ActivityB - onDestroy")
    }
}
测试步骤1
  1. 点击ActivityA的bindService按钮
  2. 再点击ActivityA的unbindService按钮
总结1
  1. client执行bindService()
  2. 如果Service不存在,则Service执行onCreate(),onBind()
  3. client实例ServiceConnection执行onServiceConnected()方法
  1. client执行unbindService()
  2. client与Service解除绑定连接状态
  3. Service检测是否还有其他client与其连接,如果没有Service执行onUnbind()和onDestroy()
测试步骤2
  1. 点击ActivityA的bindService按钮
  2. 再点击ActivityA的Finish按钮
总结2

如果client销毁,那么client会自动与Service解除绑定。

测试步骤3
  1. 点击ActivityA的bindService按钮
  2. 点击ActivityA的startActivity B按钮,切换到ActivityB
  3. 点击ActivityB中的bindService按钮
  4. 点击ActivityB中的unbindService按钮
  5. 点击ActivityB中的Finish按钮
  6. 点击ActivityA中的unbindService按钮
总结3
  1. 点击ActivityA的bindService按钮
    第一次调用bindService会实例化MyService,然后执行其onBind()方法,得到IBinder类型的实例,将其作为参数传入ActivityA的ServiceConnection的onServiceConnected方法中,标志着ActivityA与MyService建立了绑定。
  2. 点击ActivityB中的bindService按钮
    由于MyService已处于运行状态,所以再次调用bindService不会重新创建它的实例,所以也不会执行MyService的onCreate()方法和onBind()方法。ActivityB与ActivityA共享IBinder实例。此时有两个client与TestTwoService绑定。
  3. 点击ActivityB中的unbindService按钮
    ActivityB与MyService解除了绑定,当没有任何client与Service绑定时,才会执行Service的onUnbind()方法。此时,ActivityA还在绑定连接中,所以不会执行Service的解绑方法。
  4. 点击ActivityA中的unbindService按钮
    ActivityA执行unbindService之后,ActivityA与MyService就解除绑定了,这样就没有client与TestTwoService绑定,这时候Android会销毁TestTwoService,在销毁前会先执行MyService的onUnbind()方法,然后才会执行其onDestroy()方法,这样TestService就销毁了。

Service的保活

可以使用startForeground将service放到前台状态,这样低内存时,被杀死的概率会低一些

上一篇下一篇

猜你喜欢

热点阅读