怎樣寫一個相似ROS的易用的android機器人框架(1)node
接下來,咱們一步步來實現這個幾個目標android
參考項目源碼git
相關代碼實現位於 ai.easy.robot.framework
包內網絡
首先,咱們須要開啓一個常駐運行的 MasterService,在機器人運行的這個生命週期內保持活動,每一個節點(無論進程內仍是進程外)均可以鏈接此Service,向其發送消息,MasterService負責將這些消息轉發給已經訂閱了該消息主題的節點。多線程
咱們選用 Messenger:框架
MasterService 類在 onCreate()
以後開啓一個線程運行消息循環socket
thread(start=true,name = "master"){ try { Looper.prepare() //TODO h = MyHandler(this, Looper.myLooper()) // messenger = Messenger(h) mtx.release() // logd("start loop") Looper.loop() logd("stop loop") }catch (e:Exception){ e.printStackTrace() } }
這裏,MyHandler負責全部消息的接收,解析,轉發,messenger則經過 onBind()
暴露給鏈接的外界節點ide
override fun onBind(intent: Intent?): IBinder = synchronized(this){ if(!this::messenger.isInitialized){ //mtx.wait() mtx.acquire() } messenger.binder }
MasterConnector 是負責普通節點和 MasterService 鏈接的中介,其connect()
函數經過bindService
方式獲取 MasterService 的 messenger,從而能夠想MasterService發送 Message函數
private val conn = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) { logd("service disconnected") isConnected = false messenger = null } override fun onServiceConnected(name: ComponentName?, service: IBinder?) { logd("service connected") messenger = Messenger(service) isConnected = true onConnected?.invoke() } }
fun connect(servClass:Class<*>) { if (!isConnected && !isBounded) { val i = Intent() i.setClass(ctx, servClass) ctx.bindService(i, conn, Context.BIND_AUTO_CREATE) isBounded = true } }
每一個Message的 what
屬性說明消息的做用,obj
屬性指明節點名稱或者消息主題,data
屬性封裝消息數據oop
what
的取值定義爲
const val NODE_MGR_REG = 1 //節點註冊指令消息 const val NODE_MGR_UN_REG = 2 //節點註銷指令消息 const val NODE_MGR_LIST = 3 //查詢節點列表指令消息 const val NODE_MGR_CLR = 4 //清除全部節點指令 const val NODE_MSG_PUB = 5 //節點發布消息 const val NODE_MSG_SUB = 6 //節點訂閱消息 const val NODE_MSG_UN_SUB = 7 //取消訂閱 const val NODE_MSG_ON_REG = 8 //註冊成功事件 const val NODE_MSG_ON_NODE_CHANGED = 9 //節點列表發生變化事件 const val NODE_MSG = 16 //MasterService轉發的消息
表示有外部節點要加入整個節點消息通信體系,該消息中有個 replyTo
屬性,傳入的是該節點接收 MasterService發送數據的Messenger對象,MaserService就是經過他再給目標節點發送消息的。這個Messenger對象須要放入nodeMap
中保存。
刪除nodeMap
中存放的Messenger對象,釋放其內存。
表示節點想接收某個主題的消息。將節點名稱和訂閱主題存入nodeSubMap
擁有消息主題和消息數據,經過nodeSubMap
查詢訂閱了該消息的節點的Messenger對象,再經過該Messenger對象發送該消息。實現消息轉發。
查詢已經註冊的節點列表
強迫全部節點註銷
BaseNode
是全部普通節點的基類,實現消息的收發
fun register() { val m = Message.obtain() m.what = MasterService.NODE_MGR_REG m.obj = name m.replyTo = receiver connector.messenger?.send(m) }
註冊須要將自身的Messenger對象 receiver
傳遞給 MasterService,其中
private val receiver = Messenger( object : Handler() { override fun handleMessage(msg: Message?) { logd("msg>>") when (msg?.what) { MasterService.NODE_MSG -> onMessageReceive("${msg.obj}", msg?.data) MasterService.NODE_MSG_ON_REG -> onNodeRegisted() else -> { } } } })
receiver
接收到 MasterService 的消息後調用響應的回調函數
fun unregister()
fun subscribe(topic: String)
fun publish(topic: String, msg: Bundle)
全部節點的數據消息封裝在Bundle對象中。
至此,android版的相似ROS通信框架就實現了
系統中只有一個進程運行MasterService 跟MasterService不在同一個進程的節點的消息收發不須要改變,只是其connect
方法作些改動
fun connect(masterPackageName:String,masterServiceName:String) { if (!isConnected && !isBounded) { val i = Intent() i.setComponent(ComponentName(masterPackageName,masterServiceName)) ctx.bindService(i, conn, Context.BIND_AUTO_CREATE) isBounded = true } }
多主板分佈的節點須要經過網絡進行通信了。考慮到這種應用場景很少。因此MasterService暫時還不支持這樣的操做,所以還須要實現一個適配器,以便可以註冊和管理網絡上的節點。