Android輕量級事件通訊方案
開發過程中,總會遇到一些需要通訊的場景。
如果邏輯比較簡單,通過常規的傳參,回撥,返回值等即可實現。
而如果呼叫層次較深(如跨模組,跨執行緒等),光靠傳參和回撥等手段,耦合度較高,
對於需要主動通知,通知多個元件等場景,更是捉襟見肘。
為解耦事件的釋出與訂閱主體,簡化元件間通訊,可引入事件通訊機制。
事件通知包含哪些內容?
事件的定義,註冊/登出,通知。
事件框架如何實現?
一個介面,一個事件管理類,足矣。
程式碼不足50行,名副其實的“輕量級”。
方案實現
定義介面
interface Observer {
fun onEvent(event: Int, vararg args : Any?)
fun listEvents(): IntArray
}
介面定義了兩個方法:
listEvents: 返回關注的事件;
onEvent: 發生事件時回撥此介面並返回事件和引數(可預設)。
事件管理
object EventManager {
private val HANDLER = Handler(Looper.getMainLooper())
private val OBSERVER_ARRAY = SparseArray<LinkedList<Observer>>(16)
@Synchronized
fun register(observer: Observer?) {
observer?.listEvents()?.forEach { event ->
var observerList = OBSERVER_ARRAY.get(event)
if (observerList == null) {
observerList = LinkedList()
OBSERVER_ARRAY.put(event, observerList)
}
if (observer !in observerList) {
observerList.add(observer)
}
}
}
@Synchronized
fun unregister(observer: Observer?) {
observer?.listEvents()?.forEach { event ->
OBSERVER_ARRAY.get(event)?.removeLastOccurrence(observer)
}
}
@Synchronized
fun notify(event: Int, vararg args: Any?) {
OBSERVER_ARRAY.get(event)?.forEach { observer ->
HANDLER.post { observer.onEvent(event, *args) }
}
}
}
HANDLER:事件通知器,使事件介面統一在UI執行緒回撥;
OBSERVER_ARRAY:關聯事件和觀察者的容器。
SparseArray> 等價於 Map>,其key為事件, value為關注此事件的觀察者。
register: 將Observer放入其所關注的事件對應的list;
notify: 遍歷指定事件對應的list, 回撥Observer的onEvent()。
如下圖所示,將其展開,是一個多對多的十字結構:
簡而言之,就是:一個事件可被多個觀察者關注,一個觀察者可關注多個事件。
使用方法
以一個簡單的登入/登出場景為例:
1、定義事件
object Events {
const val LOGIN = 1
const val LOGOUT = 2
}
2、註冊/登出
abstract class BaseActivity : AppCompatActivity(), Observer {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
EventManager.register(this)
}
override fun onDestroy() {
super.onDestroy()
EventManager.unregister(this)
}
override fun onEvent(event: Int, vararg args: Any?) {
}
override fun listEvents(): IntArray {
return IntArray(0)
}
}
像 Activity 和 Fragment 這種有固定生命週期的類,可以在Base新增註冊和登出程式碼;
但並非每個子類都需要註冊事件,所以可以先實現空方法,需要註冊的子類自行過載即可。
3、訂閱事件&處理回撥
class MainActivity : BaseActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val switchAccountBtn: Button = findViewById(R.id.switch_account_btn)
switchAccountBtn.setOnClickListener {
if (!AccountManager.isLogin) {
startActivity(LoginActivity::class.java)
} else {
AccountManager.logout()
}
}
}
private fun setViews(account: String?) {
// update views
}
override fun listEvents(): IntArray {
return intArrayOf(Events.LOGIN, Events.LOGOUT)
}
override fun onEvent(event: Int, vararg args: Any?) {
when (event) {
Events.LOGIN -> setViews(args[0] as String?)
Events.LOGOUT -> setViews(null)
}
}
}
由於事件是在UI執行緒回撥,所以可以直接更新UI;若需要做耗時處理,需要另起執行緒。
4、傳送事件
object AccountManager {
fun login(account: String, password: String) {
// process login
EventManager.notify(Events.LOGIN, account)
}
fun logout() {
// process logout
EventManager.notify(Events.LOGOUT)
}
}
傳送事件可以只傳送事件,也可以同時攜帶引數。
引數型別,最初嘗試用Bundle, 但是Bundle傳參需要封裝和拆解;
後來換用vararg,所以可以新增任意引數,使用也相對方便。
原理分析
從各種事件框架,到系統廣播,到View的listener回撥,思路都是類似的,但有各種不同的形式,從各種各樣名字就可見一斑。
其中被對比的最多的是 觀察者模式 和 事件監聽模式。
若非要分類,該方案應該是後者。
作為對比,我們以 《JAVA與模式》之觀察者模式 中介紹的個例子為參考,其類圖如下:
其中,Subject為被觀察物件,有的地方也用Observable。
被觀察物件的狀態(state)變化改變時, attach到此物件的觀察者的update()會被回撥。
前面舉例的方案,結構圖如下:
兩者的共性在於,都是“訂閱->通知”的一種模式。
兩者的區別在於:一個關注物的變化,一個關注事的發生。
上面的例子中,大概流程如下:
整體還是比較簡單的,頁面建立時訂閱訊息,銷燬時取消訂閱;
從MainActivity到LoginActivity,再到AccountManager, 既跨頁面也跨執行緒,
但由於EventManager持有MainActivity引用,所以可以方便地通知。
不單是Activity, Fragment等UI元件,任何物件都可以通過實現Observer介面成為觀察者,然後通過EventManager訂閱自己感興趣的事件。
其他事項
生命週期
引用觀察者的是個靜態物件,所以觀察者生命週期結束時需要確保取消訂閱,以免記憶體洩漏。
如果觀察者也是靜態物件,則不用取消訂閱。
如果不確定物件生命週期結束前是否可以取消訂閱,可藉助弱應用來防止記憶體洩漏:
class WeakObserver(target: Observer) : Observer {
private val reference: WeakReference<Observer> = WeakReference(target)
private val events: IntArray = target.listEvents()
override fun onEvent(event: Int, vararg args: Any?) {
reference.get()?.onEvent(event, *args)
}
override fun listEvents(): IntArray {
return events
}
}
WeakObserver和WeakHandle類似,都是通過實現介面以及弱應用來進行包裝。
重複檢查
當事件定義變多後,有可能一個不小心,事件的value就重複了。
object Events {
const val LOGIN = 1
const val LOGOUT = 2
// ...
const val SOMETHING = 2
}
若重複定義,可能會相互干擾。
為此,可編寫單元測試檢查事件的value是否重複:
fun testDuplicate() {
val fields = Events::class.java.declaredFields
val events = fields.filter { it.type == Int::class.java }
val eventSet = events.map { it.getInt(Events::class.java) }.toSet()
Assert.assertEquals(events.size, eventSet.size)
}
如上定義, 單元測試會報錯:
junit.framework.AssertionFailedError: expected:<3> but was:<2>
檢視事件
通過“Find Usages”快捷鍵,可以檢視所有訂閱者和事件傳送的地方。
結語
說到事件框架,大家可能會想到EventBus。
相比而言,EventBus有粘性事件,可指定回撥執行緒等特性;
而此方案則是輕量,以及清晰的事件管理。
本文主要是介紹事件通訊的思想和一些實現技巧,旨在拋磚引入,歡迎各位讀者批評指正。
附上演示Demo, 感興趣的讀者可以下載回來Run一下。
github地址: LigntEvent
相關文章
- iOS 輕量級 HTML 解析方案iOSHTML
- 輕量通訊協議 --- MQTT協議MQQT
- 輕量級日誌收集方案LokiLoki
- azeroth-event輕量級事件驅動框架事件框架
- 輕量級非侵入式埋點方案
- EasyBarrage——Android平臺輕量級彈幕效果Android
- 輕量級超級 css 工具CSS
- TDengine 助力西門子輕量級數字化解決方案
- Golang wails2 輕量級跨端桌面解決方案GolangAI跨端
- 學習筆記(2):go輕量級分散式與微服務-實現程式的訊號通訊2筆記Go分散式微服務
- Redis 訊息中介軟體 ServiceStack.Redis 輕量級Redis
- lima 輕量級虛擬機器docker替代方案 (macos平臺)虛擬機DockerMac
- Android Socket 通訊Android
- 騰訊通停更後最佳升級方案:輕鬆應對2027信創國產化全面替代
- 滴滴開源 DroidAssist : 輕量級 Android 位元組碼編輯外掛Android
- 一個輕量級的iOS皮膚切換方案(內附Demo)iOS
- python輕量級效能工具-LocustPython
- 輕量級Web框架Flask(二)Web框架Flask
- Spring的輕量級實現Spring
- Flutter路由輕量級框架FRouterFlutter路由框架
- 輕量級orm框架——gzero指南ORM框架
- JAVA輕量級鎖簡介Java
- 輕量級配置中心Nodejs版NodeJS
- Android RxLife 一款輕量級別的RxJava生命週期管理庫AndroidRxJava
- Android跨程式通訊Android
- Android 串列埠通訊Android串列埠
- Android 多程式通訊Android
- 微服務架構下的輕量級定時任務解決方案微服務架構
- 元件間通訊--自定義事件元件事件
- Android RxLife 一款輕量級別的RxJava生命週期管理庫(二)AndroidRxJava
- 前端輕量級資料庫mongodb前端資料庫MongoDB
- Shottr for mac(輕量級截圖工具)Mac
- looter——超輕量級爬蟲框架爬蟲框架
- 輕量級API測試工具PandariaAPI
- 專案輕量級部署神器:Fabric
- Golang web filter 輕量級實現GolangWebFilter
- 私有化輕量級持續整合部署方案--01-環境配置(下)
- 私有化輕量級持續整合部署方案--01-環境配置(上)