主頁 > 移動端開發 > 用觀察者模式 手寫一個解耦的在Acticity之間傳遞資訊的方案 借鑒EventBus Android kotlin

用觀察者模式 手寫一個解耦的在Acticity之間傳遞資訊的方案 借鑒EventBus Android kotlin

2021-02-17 10:09:32 移動端開發

看代碼直接到最后,不過建議從頭開始看

一. 起因(廢話)

參加了一個android程式員招聘的面試,被問到廣播相關的內容,我根據作業經驗,做出了一些畫蛇添足的回答(不一定對):

開始回答

廣播可以作為行程之間的通信,也可以在Activity/Fragment之間傳遞內容,但是鑒于Activity/Fragment之間傳遞內容的各個方法都有比較明顯的缺點,

1. 比如Bundle無法傳遞大容量資料,且傳遞復雜資訊操作繁瑣;

2. Handler回產生一個佇列,且如果需要重繪UI會有執行緒之間切換帶來的比較難以控制的效果;

3. 介面好使但比較難寫(對于我來說,經驗不足);

4. 使用Application傳遞訊息在用戶翻轉,切換的時候,可能會導致記憶體泄露(這點不確定,沒用過);

5. 單例的話,專案禁止使用,用的多了,會產生一些奇怪的單例,畢竟下次維護可能得半年后了;

6. 通過永固化存盤雖然完全解決了物件復雜度的問題,但是其需要通過I/O速度太慢了,

所以原來組里通常使用EventBus來傳遞資訊,因為它完成了Acticity之間的解耦,

回答結束

面試官突然來了興趣,問道:“EventBus的原始碼有鉆研過嗎?

好家伙,我直接好家伙,我就去應聘個一年作業經歷的android碼農,居然問我原始碼,說實在的,我感覺如果我能扯出點什么說不定會給我發個Offer,可惜沒有如果,

回頭看了好幾天的原始碼,橫豎睡不著,滿眼都是注解框架,回呼,細細琢磨,字里行間寫著再來過,

But,天無絕人之路,還是能夠搜到EventBus用的是 觀察者模式 ,掏出大學時期買來卻從沒看過的《設計模式》一頓翻閱,決定自己寫一個類似功能的,省的下次面試被問到說不出什么內容,

二.原理

(1. 2. 3.抄書,有書自己看,《設計模式 可復用面對物件軟體的基礎 機械工業出版社》 第五章 行為模式 OBSERVER(觀察者)——物件行為型模式)

1.為什么要使用觀察者模式

安卓系統在開發中被分割為一些列相互協作的,Activity/Fragment,需要維護相關物件的一致性,我們不希望萎了維護一致性而使各個Activity/Fragment緊密耦合,這增加了開發維護的難度,舉個例子,一個區分普通用戶和會員的app,其功能對非付費用戶閹割,如果付費用戶恰好會員過期了,就需要及時通知所有的Activity/Fragment頁面更改顯示,雖然可以通過強制關閉應用重啟,但這樣對用戶體驗極差,各個Activity/Fragment可以作為觀察者,等待資訊的更改,

2.觀察者模式適用以下場景:

1. 當一個抽象模型有兩個方面,其中一個方面依賴于另一方面,將這兩者封裝在獨立的物件中以使它們可以各自獨立地改變和復用,

2. 當一個物件的改變需要同時改變其他物件,而不知道具體又多少物件有待改變,

3. 當一個獨享必須通知其他物件,而它又不能假定其他物件是誰,換言之,你不希望這些物件是緊密耦合的,

在應用層面,把一個個Activity/Fragment看作是一個個物件,顯然其符合第三適用場景,

3.簡易觀察者模式:

Subject(目標)

——目標知道它的觀察者,可以有任意多個觀察者觀察同一個目標

——提供注冊和洗掉觀察者物件的介面

Observer(觀察者)

——為那些在目標發生改變時需要獲得通知的物件定義一個更新介面

ConcreteSubject(具體目標)

——將有關狀態存入各個ConcreteObserver物件

——當它的狀態發生改變時,向它的各個觀察者發出通知

ConcreteObserver(具體觀察者)

——維護一個指向ConcreteSubject物件的參考

——存盤有關狀態,這些狀態應與目標的狀態保持一致

——實作Observer的更新介面以使自身狀態和目標的狀態保持一致

4.具體到代碼層面:(實在看不懂原理的可以直接粘貼創建一個專案進行測驗)

//觀察者抽象類
abstract class Observer {
    abstract fun Update(msg:Any?)
}


//被觀察者抽象類
abstract class Subject {
    abstract fun Attach(observer: Observer)
    abstract fun Detach(observer:Observer)
    abstract fun Notify(msg:Any?)
}


//以下是隨便定義的物體觀察者和被觀察者

//一號觀察者,對型別為Message的物件有特殊處理,否則直接列印
class FirstObserver : Observer() {
    override fun Update(msg: Any?) {
        if(msg is Message){
            println("1號觀察者收到Message訊息,編號為"+msg.num+"內容為"+msg.msg )
        }else{
            println("1號觀察者收到訊息:內容======"+msg.toString())
        }
    }
}

//二號觀察者,無論如何直接列印
class SecondObserver : Observer() {
    override fun Update(msg: Any?) {
        println("2號觀察者收到訊息:內容======"+msg.toString())
    }
}

//目標物體
class FirstSubject : Subject() {
    //存放觀察者
    private  var observers =  ArrayList<Observer>()
    override fun Attach(observer: Observer) {
        observers.add(observer)
    }
    override fun Detach(observer: Observer) {
        observers.remove(observer)
    }
    override fun Notify(msg:Any?) {
        for (observe in observers){
            observe.Update(msg)
        }
    }
}

//Message類,在一號觀察者中有特殊處理
class Message(var msg:String,var num:Int)

//實際測驗代碼
fun main(args: Array<String>){
    val firstSubject = FirstSubject()//被觀察者物體
    val firstObserver = FirstObserver()//一號觀察者
    val secondObserver = SecondObserver()//二號觀察者

    firstSubject.Attach(firstObserver)//一號觀察者訂閱被觀察者
    firstSubject.Attach(secondObserver)//二號觀察者訂閱被觀察者
    firstSubject.Notify("來一份通知")//被觀察者發送String資訊

    firstSubject.Notify(123)//被觀察者發送Int資訊

    val msg = Message("說話",2)//被觀察者發送Message物件資訊
    firstSubject.Notify(msg)

    firstSubject.Detach(firstObserver)//撤銷二號觀察者對被觀察者的查看
    firstSubject.Notify(null)//被觀察者發送空
}

//測驗結果
1號觀察者收到訊息:內容======來一份通知
2號觀察者收到訊息:內容======來一份通知
1號觀察者收到訊息:內容======123
2號觀察者收到訊息:內容======123
1號觀察者收到Message訊息,編號為2內容為說話
2號觀察者收到訊息:內容======com.example.eventbusapplication.example.Message@7e0ea639
2號觀察者收到訊息:內容======null

5.從簡易觀察者模式進化到具有更改管理器的觀察者模式(誰才是觀察目標

為什么不能讓Activity/Fragment繼承被觀察者來發布訊息

1. 在使用kotlin開發的時候第一個問題就是我們的Activity/Fragment是繼承自安卓系統的Activity/Fragment的,只有繼承了Activity/Fragment才能被認作是Activity/Fragment,由于kotlin和JAVA的單繼承特性,是不能直接使用抽象類的方式進行繼承,

2. 考慮實際開發程序中,各個Activity/Fragment之間是平級的,只有人為規定其為主Activity/Fragment或者起始Activity/Fragment才會有所區別,所以并不存在某一被觀察Activity/Fragment知曉其觀察者Activity/Fragment,破壞了"目標知道它的觀察者,可以有任意多個觀察者觀察同一個目標"這一條件,

3. 在開發程序中,比如需要向后一Activity/Fragment傳遞一個資訊,那么本Activity/Fragment作為觀察物件沒有很好的辦法提前知道后一Activity/Fragment是什么,甚至后一Activity/Fragment不一定存在,破壞了“目標提供注冊和洗掉觀察者物件的介面”這一條件,

綜上所述,目標必不能是一個Activity/Fragment;而觀察者應該是一個Activity/Fragment,至此引入一個單例,更改管理器,作為目標,

設計具有更改管理器的Activity/Fragment間使用的觀察者模式

改抽象類為介面實作

Subject(目標)

——目標知道它的觀察者,可以有任意多個觀察者觀察同一個目標

——提供注冊和洗掉觀察者物件的介面

Observer(觀察者)

——為那些在目標發生改變時需要獲得通知的物件定義一個更新介面

——在向更改管理器注冊觀察者時,應主動查看更改管理器的延遲通知佇列

ChangeManager(更改管理器) 唯一的Subject

——行程中用于資訊傳遞的唯一的目標,是單例

——當接收到目標發來的通知,提醒各個觀察者更新

——維護延遲通知訊息佇列,并在觀察者注冊時,拿延遲訊息對觀察者試探

6. 代碼實作

//觀察者介面
interface Subject {
    fun Notify(msg:Any?)
    fun LateNotify(msg:Any?)
    fun Attach(observer: Observer)
    fun Detach(observer:Observer)
}


//觀察者介面
interface Observer {
    fun Update(msg:Any?)
}

//唯一的觀察物件
class ChangeManager:Subject {
    //觀察者鏈表
    private val ObserverList = ArrayList<Observer>()
    //粘性事件鏈表
    private val LateMessageList = ArrayList<Any?>()

    //對某一個觀察者遍歷粘性事件
    fun checkLateMessageList(observer: Observer){
        for (latemessage in LateMessageList){
            observer.Update(latemessage)
        }
    }

    //添加粘性事件
    private fun addLateMessage (lateMessage: Any?){
        LateMessageList.add(lateMessage)
    }

    //添加粘性事件(對外抽象)
    override fun LateNotify(msg: Any?) {
        addLateMessage(msg)
    }

    //粘性事件需要手動解除,這點和EventBus是一樣的
    fun removeLateMessage(lateMessage: Any?){
        LateMessageList.remove(lateMessage)
    }

    //通知所有觀察者更新資訊
    override fun Notify(msg: Any?) {
        for (observer in ObserverList){
            observer.Update(msg)
        }
    }

    //注冊新的觀察者
    override fun Attach(observer: Observer) {
        checkLateMessageList(observer)
        ObserverList.add(observer)
    }

    //注銷某個觀察者
    override fun Detach(observer: Observer) {
        ObserverList.remove(observer)
    }

}

//個性化一個Application,在里面單例化一個ChangeManager
class MyApp : Application() {
    companion object{
        var instance:MyApp  by Delegates.notNull()
        var changeManager: ChangeManager by Delegates.notNull()
    }

    override fun onCreate() {
        super.onCreate()
        instance = this
        changeManager = ChangeManager()
    }
}

如何在Activity中使用(實體):

//資訊類
class EventMessage (var num:Int, var str:String)

//第一個Activity
class FirstActivity : AppCompatActivity(), Observer {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        MyApp.changeManager.Attach(this)
        btn_first.setOnClickListener {
            //發布粘性事件
            MyApp.changeManager.LateNotify("來試試")
            val intent = Intent(this,SecondActivity().javaClass)
            startActivity(intent)
        }
    }
    override fun Update(msg: Any?) {
        if(msg is EventMessage) {
            Log.i("tofu", "FirstActivity收到了Message物件的訊息,訊息內容為===${msg.num}===${msg.str}")
        } else{
            Log.i("tofu", "FirstActivity收到了其他訊息")
        }
    }
}

//第二個Activity
class SecondActivity : AppCompatActivity(),Observer {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        //注冊監聽(觀察者)
        MyApp.changeManager.Attach(this)
        btn_second.setOnClickListener {
            val intent = Intent(this,ThirdActivity().javaClass)
            startActivity(intent)
        }
    }

    //觀察者更新的事
    override fun Update(msg: Any?) {
        Log.i("tofu", "SecondActivity收到了資訊")
    }
}

//第三個Activity
class ThirdActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_third)

        MyApp.changeManager.Notify(EventMessage(11,"來,更新"))
    }
}


///
//第一第二個Activity各自有一個按鈕跳轉到下一個Activity

//結果
I/tofu: SecondActivity收到了資訊
I/tofu: FirstActivity收到了Message物件的訊息,訊息內容為===11===來,更新
I/tofu: SecondActivity收到了資訊

三. 技術總結

重點使用了觀察者模式 和 單例模式 兩種重要的設計模式,與一般觀察者模式用法不一樣的是,對于在Activity/Fragment間傳遞資訊的觀察者模式,只有一個被觀察者,且不是某一個Activity/Fragment,

顯然在實體中可以看出,遠不如原版的EventBus,比方說,需要手動在Update中判定msg物件是什么型別,

手寫不易,如果有幫助轉載標明:https://blog.csdn.net/zxy7311074/article/details/113803571,歡迎大佬指正

感謝大佬指正,歡迎進開發交流群討論問題:200409033

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/260276.html

標籤:其他

上一篇:拍照顯示圖片時出現個空指標,沒頭緒

下一篇:二十四、系結服務

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more