kotlin實現android 訊息機制
首先需要搞清楚android訊息機制的原理
大部分人應該都明白,簡單過一下。
- 線上程中有looper
- looper.loop方法,通過死迴圈來遍歷訊息佇列(MessageQueue),沒有訊息的話,執行緒睡眠,避免cpu壓力過大(cpu空轉也是非常耗電的)
- 有訊息的話,通過handler.sendMesage 方法,在looper的訊息佇列中插入一條訊息(Message),同時喚醒執行緒。(注意哈,這個執行緒是通過其他執行緒喚醒的,他自己已經睡眠了)
來上程式碼實現一下
首先是Message
class Message( var what: Int, var obj: Object? = null, var runnable: Runnable? = null ) { // 標記了是從哪個handler發出的 lateinit var target: Handler }
Handler
open class Handler(private val looper: Looper = Looper.myLooper()!!) { /** * 處理訊息 */ open fun handleMessage(message: Message) { if (message.runnable != null) { message.runnable!!.run() } } /** * 傳送訊息 */ fun send(message: Message) { message.target = this looper.messageQueue.enqueue(message) } }
訊息佇列
class MessageQueue { // 訊息佇列 private val queue: Deque<Message> = LinkedList<Message>() private var quit = false // 執行緒鎖 private val lock = Object() fun enqueue(message: Message) { val empty = queue.isEmpty() queue.add(message) // 如果之前訊息佇列,為空,則喚醒執行緒 if (empty) { synchronized(lock) { println("有新訊息,已經解鎖") lock.notifyAll() } } } fun next(): Message? { if (queue.isEmpty()) { if (quit) { println("已退出") return null } println("訊息佇列為空,已經鎖定") synchronized(lock) { lock.wait() } } return queue.poll() } fun quit() { quit = true } }
Looper
這個稍微複雜一點
class Looper private constructor() { // 訊息佇列 var messageQueue = MessageQueue() fun quit() { messageQueue.quit() } companion object { // 使用ThreadLocal 儲存所有的Looper private val looperThreadLocal = ThreadLocal<Looper>() // 主執行緒的looper,也就是第一個初始化的 private var mainLooper: Looper? = null @JvmStatic fun myLooper(): Looper? { return looperThreadLocal.get() } @JvmStatic fun mainLooper(): Looper { return mainLooper!! } @JvmStatic fun prepare() { if (looperThreadLocal.get() == null) { looperThreadLocal.set(Looper()) } else { throw IllegalStateException("looper exists in ${Thread.currentThread().name} thread") } } @JvmStatic fun loop() { val myLooper: Looper = myLooper() ?: throw IllegalStateException("looper not exists") while (true) { // 沒有訊息,是會睡眠的喲 val message = myLooper.messageQueue.next() ?: break // 注意這個target是誰 message.target.handleMessage(message) } } @JvmStatic fun prepareMainLooper() { if (mainLooper == null) { mainLooper = Looper() looperThreadLocal.set(mainLooper) } else { throw IllegalStateException("looper exists in main thread") } } } }
測試一下
fun main(args: Array<String>) { Looper.prepareMainLooper() val handler = Handler() Thread { println("請輸入內容(輸入q退出)") val scanner = Scanner(System.`in`) var quit = false while (scanner.hasNextLine() && !quit) { val line = scanner.nextLine() println("${Thread.currentThread().name}輸入的:$line") handler.send(Message(0, runnable = Runnable { println("${Thread.currentThread().name}接收到:$line") })) when (line) { "q" -> { Looper.mainLooper().quit() quit = true } } } }.also { it.name = "子執行緒" it.start() } Looper.loop() }

image.png
此demo有一點與android的不同,在沒有訊息時,android使用linux核心epoll機制 實現執行緒睡眠,而本demo使用的是執行緒鎖
demo傳送門 https://github.com/pokercc/message-demo
請注意工程結構基於idea,不是android studio