美文网首页
Android kotlin service使用简析

Android kotlin service使用简析

作者: 水天滑稽天照八野滑稽石 | 来源:发表于2020-08-18 23:45 被阅读0次

    前言

    模拟器教程的前奏之一

    什么是service

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


    通过StartService启动Service

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

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

    • onCreate()
      1.如果service没被创建过,调用startService()后会执行onCreate()回调;
      2.如果service已处于运行中,调用startService()不会执行onCreate()方法。
      也就是说,onCreate()只会在第一次创建service时候调用,多次执行startService()不会重复调用onCreate(),此方法适合完成一些初始化工作。
    • onStartCommand()
      如果多次执行了Context的startService()方法,那么Service的onStartCommand()方法也会相应的多次调用。onStartCommand()方法很重要,我们在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等。
    • onBind()
      Service中的onBind()方法是抽象方法,Service类本身就是抽象类,所以onBind()方法是必须重写的,即使我们用不到。
    • onDestory()
      在销毁的时候会执行Service该方法。

    创建一个Service


    • Exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。
    • Enabled:是否可以被系统实例化,默认为 true因为父标签 也有 enable 属性,所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。

    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
    • 调用bindService之后
    1. client执行bindService()
    2. 如果Service不存在,则Service执行onCreate(),onBind()
    3. client实例ServiceConnection执行onServiceConnected()方法
    • 调用unbindService之后
    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的保活

    • onStartCommand方式中,返回START_STICKY
      不同FLAG有什么用途前文已提及
    • 提高Service的优先级
      在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。
    • 提升Service进程的优先级
      当系统进程空间紧张时,会依照优先级自动进行进程的回收。
      Android将进程分为6个等级,按照优先级由高到低依次为:
      • 前台进程foreground_app
      • 可视进程visible_app
      • 次要服务进程secondary_server
      • 后台进程hiddena_app
      • 内容供应节点content_provider
      • 空进程empty_app

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

    相关文章

      网友评论

          本文标题:Android kotlin service使用简析

          本文链接:https://www.haomeiwen.com/subject/fokfjktx.html