Android 四大組件(二) —— Service 知識體系

生命週期

startService和stopService方式

使用startService啓動:

startService(Intent(this@MainActivity, MyService::class.java))

使用stopService停止:

stopService(Intent(this@MainActivity, MyService::class.java))

生命週期如下

Created with Raphaël 2.2.0onCreateonStartCommandonDestroy

啓動時的生命週期如下:

Created with Raphaël 2.2.0onCreateonStartCommand

Service啓動後多次調用startService,只會執行onStartCommand,不會再次執行onCreate。生命週期如下:

Created with Raphaël 2.2.0onCreateonStartCommandonStartCommandonStartCommand...

bindService和unbindService方式

使用bindService啓動,unbindService停止:

class MainActivity : AppCompatActivity() {
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.d("~~~",Thread.currentThread().stackTrace[2].methodName)
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            Log.d("~~~",Thread.currentThread().stackTrace[2].methodName)
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btnBindService.setOnClickListener {
            bindService(Intent(this@MainActivity, MyService::class.java), connection, BIND_AUTO_CREATE)
        }
        btnUnbindService.setOnClickListener {
            unbindService(connection)
        }
    }
}

生命週期如下:

Created with Raphaël 2.2.0onCreateonBindonServiceConnectedonUnbindonDestroy

Service啓動後多次執行bindService,不會執行任何回調。

如果一個Service既被startService啓動了,又被bindService綁定了,那麼單獨調用stopService或unbindService都不會使Service銷燬,直到stopService和unbindService都被調用後Service纔會被銷燬。
也就是說,點擊Stop Service按鈕只會讓Service停止,點擊Unbind Service按鈕只會讓Service和Activity解除關聯,一個Service必須要在既沒有和任何Activity關聯又處於停止狀態的時候纔會被銷燬。

onRebind回調

Service中還有個onRebind回調,如果Service未被銷燬,並被同一個Activity多次綁定,並且onUnbind方法中返回的是true,則會執行onRebind回調。
例如:在MyService中編寫以下代碼:

class MyService : Service() {
    val myBinder = MyBinder()

    override fun onCreate() {
        super.onCreate()
        Log.d("~~~", Thread.currentThread().stackTrace[2].methodName)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d("~~~", Thread.currentThread().stackTrace[2].methodName)
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onBind(intent: Intent?): IBinder? {
        Log.d("~~~", Thread.currentThread().stackTrace[2].methodName)
        return myBinder
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.d("~~~",Thread.currentThread().stackTrace[2].methodName)
        return true
    }

    override fun onRebind(intent: Intent?) {
        super.onRebind(intent)
        Log.d("~~~",Thread.currentThread().stackTrace[2].methodName)
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("~~~", Thread.currentThread().stackTrace[2].methodName)
    }

    inner class MyBinder : Binder() {

        fun startTask() {
            Log.d("~~~", Thread.currentThread().stackTrace[2].methodName)
        }
    }
}

在MainActivity中編寫如下代碼:

class MainActivity : AppCompatActivity() {
    private lateinit var myBinder:MyService.MyBinder
    private val connection = object : ServiceConnection {

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.d("~~~",Thread.currentThread().stackTrace[2].methodName)
            myBinder = service as MyService.MyBinder
            myBinder.startTask()
        }

        override fun onServiceDisconnected(name: ComponentName?) {
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initView()
    }

    private fun initView() {
        btnStartService.setOnClickListener {
            startService(Intent(this@MainActivity, MyService::class.java))
        }
        btnStopService.setOnClickListener {
            stopService(Intent(this@MainActivity, MyService::class.java))
        }
        btnBindService.setOnClickListener {
            bindService(Intent(this@MainActivity, MyService::class.java), connection, BIND_AUTO_CREATE)
        }
        btnUnbindService.setOnClickListener {
            unbindService(connection)
        }
    }
}

運行程序,查看Log控制檯。
點擊startService:

~~~: onCreate
~~~: onStartCommand

點擊bindService:

~~~: onBind
~~~: onServiceConnected
~~~: startTask

點擊unbindService:

~~~: onUnbind

再點擊bindService:

~~~: onServiceConnected
~~~: startTask
~~~: onRebind

再點擊unbindService:

~~~: onUnbind

點擊stopService:

~~~: onDestroy

Service 和 Thread

Service作爲四大組件之一,本身和Thread是沒有任何關係的。Service就像是一個沒有界面的Activity,默認在主線程中運行,如果要在Service中執行耗時任務的話,需要在Service中開一個子線程。例如:

class MyService : Service() {
    private val myBinder = MyBinder()
    private val timerTask by lazy {
        object : TimerTask() {
            override fun run() {
                Log.d("~~~", "${System.currentTimeMillis()}")
            }
        }
    }

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

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 執行耗時任務,這裏開啓了一個定時器模擬耗時任務
        Thread(Runnable {
            Timer().schedule(timerTask, 0, 1000)
        }).start()
        return super.onStartCommand(intent, flags, startId)
    }

    inner class MyBinder : Binder() {
        fun startTask() {
            // 執行耗時任務,這裏開啓了一個定時器模擬耗時任務
            Thread(Runnable {
                Timer().schedule(timerTask, 0, 1000)
            }).start()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        timerTask.cancel()
    }
}

在Service中開啓子線程的好處是Service可以全局共享,多個
Activity綁定Service時,獲取到的是同一個binder實例。

前臺 Service

由於Service默默地在後臺運行,優先級比較低,在系統內存不足時,系統會根據進程優先級從低到高來殺進程。如果想要Service不被系統輕易殺死,可以使用前臺Service。
前臺Service最明顯的區別就在於他會在通知欄顯示當前Service正在運行,優先級較高。來看一張效果圖:

使用前臺Service需要添加權限:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

Android8.0之後,通知欄需要添加channelId,使用前臺Service示例如下:

class MyService : Service() {
    private val myBinder = MyBinder()

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

    override fun onCreate() {
        super.onCreate()
        val notificationIntent = Intent(this, MainActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0)
        val channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel("my_service", "My Background Service")
        } else {
            // If earlier version channel ID is not used
            ""
        }
        val builder = NotificationCompat.Builder(this, channelId)
        val notification = builder.setOngoing(true)
            .setContentTitle("content title")
            .setContentText("content text")
            .setContentIntent(pendingIntent)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setPriority(PRIORITY_MIN)
            .build()
        startForeground(1, notification)
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private fun createNotificationChannel(channelId: String, channelName: String): String {
        val chan = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE)
        chan.lightColor = Color.BLUE
        chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
        val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        service.createNotificationChannel(chan)
        return channelId
    }

    inner class MyBinder : Binder() {
        fun startTask() {
        }
    }
}

pendingIntent用於設置通知欄點擊事件,channelID是Android8.0之後需要設置的參數,使用前要先調用NotificationManager的createNotificationChannel創建渠道。builder中可以設置通知欄的標題、內容、圖標等屬性。

參考文章

Android Service完全解析,關於服務你所需知道的一切(上)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章