Android組件化開發實踐(四):組件間通信問題

記得第一次實施項目組件化時,遇到的最大困擾就是,組件之間的通信問題。例如:

  • 怎么從這個組件跳轉到另一個組件的頁面;
  • 組件之間怎么傳遞數據;
  • 怎么獲取其他組件的數據或服務;
  • 組件怎么通知其他組件響應某個事件;

1. 頁面跳轉統一采用路由

在Android中,頁面跳轉都是通過startActivity來實現的。但是我們組件化之后,上層的業務組件之間是不能相互依賴的,也就是說現在無法通過startActivity來進行頁面跳轉了。

組件化之后,所有頁面跳轉都必須采用路由來實現。現在已經有很多成熟的路由框架了,具體什么是路由、路由的作用都講的很清楚,我這里不再贅述了,比較成熟的有:

路由框架的核心原理都是一樣,這里我來說說我自己的路由框架,以及這樣設計的原因何在。

1.1 路由URI格式

路由實質上都是將一個URI映射到某一個具體的界面,通過URI跳轉時,路由框架內部找到該URI對應的Activity頁面,進而實現頁面跳轉。首先我們來看一張圖,明白一個完整的URI是怎么定義的。


圖片引用自美團

是不是特別復雜,但實際上我們并不需要這么復雜,以我的一個路由uri為例:

hmiou://www.54jietiao.com/webview/index?title=*&url=*

這是一個打開WebView頁面的路由地址定義,具體來說只采用了URI的幾個部分:

  • scheme
    這個是必須的,我定義為hmiou,這個根據項目來自定義即可。
  • host
    www.54jietiao.com,通常定義為你項目的主站域名。
  • path
    /webview/index,也就是路徑,根據你的業務來區分即可。
  • query
    title=*&url=*,查詢參數

我的路由定義里面,只采用了scheme、host、path、query這4部分,能滿足我的需求即可。

1.2 路由映射文件

我們沒有采用注解,而是定義了一份全局的路由映射json文件,應用啟動時讀取配置文件進行路由初始化。

{
  "test": [
    {
      "url": "hmiou://www.54jietiao.com/test/test1?title=*",
      "iclass": "Test1ViewController",
      "aclass": "com.hm.iou.router.demo.TestActivity1"
    },
    {
      "url": "hmiou://www.54jietiao.com/test/test2",
      "iclass": "Test2ViewController",
      "aclass": "com.bwton.router.demo.MainActivity"
    }
  ],
  "main": [
    {
      "url": "hmiou://www.54jietiao.com/main/index?url=*",
      "iclass": "MainViewController",
      "aclass": "com.hm.iou.router.demo.MainActivity"
    }
  ]
}

每個配置項都包含“url、iclass、aclass”3個選項,url就是路由定義,iclass是對應的iOS里面該頁面的類名,aclass是對應的Android里面該頁面的類名,這么做的目的是為了保持2個平臺的路由統一。

我根據功能將路由進行了分組,從上面配置文件中可以看到有2個分組:test、main,然后每個路由url的path都以該分組名開頭,所以每個路由url至少應該包含2級路徑。

以路由“hmiou://www.54jietiao.com/test/test1?title=標題”為例,來看看內部是怎樣實現頁面跳轉的。
1.路由框架首先解析出這個url的scheme、host、path、query這4部分;
2.檢查scheme是否應用能支持的scheme,如果不是則不允許跳轉或跳轉失敗;
3.檢查host是否應用支持;
4.前面檢查通過后,取出path的第一級路徑,這里為“test”,然后框架查找路由配置表,找到“test”這個分組;
5.在“test”分組下的路由配置里遍歷匹配,找到與當前url一致的路由配置項數據來;
6.找到對應的配置項之后,找到該url對應的aclss,這里為“com.hm.iou.router.demo.TestActivity1”;
7.框架通過反射調用startActivity來進行頁面跳轉;
8.如果第1步解析出的查詢參數里有值,則將參數放到Intent里面傳遞過去,這里我們會傳遞一個key為“title”,value為“標題”的數據傳遞過去,類似于intent.putExtra("title", "標題")。
9.路由表里的查詢參數都定義成類似于title=*,這里*只是一個占位符,僅僅是為了便于開發人員理解,知道該路由接收一個參數,名為“title”。

這里對路由進行分組,是因為做url匹配時,需要遍歷整個路由表,分組可以提高查找匹配url的速度。配置文件里的url甚至可以設置一些自定義的正則匹配規則,你可以設置一些通配符,讓若干個不同的url都能跳轉到同一個頁面。

當然還有很多細節需要處理,比如:

  • 支持頁面間跳轉動畫;
  • 支持startActivityForResult;
  • 支持設置intent的flag;
  • 使用路由url進行跳轉時,查詢參數的值必須進行encode,否則會導致解析失敗;
  • 通過Intent傳遞參數時,不能知道查詢參數里的數據類型,統一定義為字符串類型;

還有些功能可能實現不了,比如說頁面之間怎么傳遞對象,Android里可以傳遞Parceable、Serializable對象,在我這里就不能支持。不過我并不推薦頁面間傳遞對象,這樣會帶來比較高的耦合度,同時不利于組件化開發。

1.3 動態更新路由文件

通常安裝包里會包含一份初始的配置文件,但是當應用發布之后,某個頁面出現嚴重bug,或者我們想改變某個入口點擊后的跳轉目標頁面,這時可以通過動態更新路由配置文件來實現。

新的配置文件里,只需要把原本配置里的aclass、iclass替換成新的目標頁面類名即可,而不用重新發布app版本。

1.4 外部路由分發器

現在很多應用有這么個功能:在外部第三方應用里,或者H5網頁里,直接通過路由url能打開我們的應用,并跳轉到指定的目標頁面。

public class RouteDispatchActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onResume() {
        super.onResume();
        Intent data = getIntent();
        if(data != null && data.getData() != null) {
            Uri uri = data.getData();
            //-------通過路由來跳轉-------
            1.判斷uri是否合法;
            2.判斷uri是否在白名單內;
            3.判斷通過,則采用路由跳轉;
            4.不通過或跳轉失敗,則僅僅打開應用而已;
        }
        finish();
    }
}

在AndroidManifest.xml里配置:

<activity
    android:name=".RouteDispatchActivity"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:screenOrientation="portrait"
    android:theme="@android:style/Theme.Translucent.NoTitleBar"
    >
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:scheme="hmiou" android:host="www.54jietiao.com" />
    </intent-filter>
</activity>

該Activity沒有任何業務邏輯,它只是一個接收外部uri跳轉的入口Activity。注意這里的 <intent-filter />配置,這樣該Activity就能響應所有 hmiou://www.54jietiao.com 這種格式的uri跳轉了。

該Activity被設置成透明的樣式,用戶感知不到,它的作用就是一個外部路由的分發器,這樣我們就能在外部應用里跳轉到任意頁面了。這里有個路由白名單是個什么鬼,且繼續看下去。

1.5 路由白名單配置

前面講到可以從外部跳轉到任意指定頁面,這顯然是極度危險的操作,如果你的應用里有錢包的話,這意味著任何應用都可以打開你的錢包頁面進行付錢。所以對外部應用的路由跳轉,我們必須設置白名單,在白名單內的路由url,能跳轉到指定的目標頁面,不在其內的僅僅只是打開應用進入首頁而已。

[
  "hmiou://www.54jietiao.com/main/index"
]

2. 數據服務共享

像美團的WMRouter框架,主要提供了URI分發、ServiceLoader兩大功能。ServiceLoader通俗點說就是組件間服務共享、數據共享,我在路由框架里沒有實現,而是換了個方式來實現這些需求。

2.1 維護好全局共享數據

一般應用里都需要用戶登錄,登錄之后我們會本地保存用戶信息,而用戶信息可能在所有的組件都會使用。例如注冊登錄組件服務里,用戶登錄后需要保存登錄信息到本地;用戶在個人中心組件服務里,需要讀取用戶登錄信息進行展示。

通常這類數據我稱之為全局共享數據,我通常的做法是,將這類數據下沉到底層模塊里,所有業務組件可依賴,這樣就解決了組件之間數據共享的問題。

不要盲目的將共享數據下沉到底層組件里,否則隨著業務的擴張,會造成難以維護的地步。一旦數據下沉之后,以后想從底層組件里剝離出,將會是一件非常困難的事情。

2.2 采用EventBus

除了數據共享之外,還有一個是服務調用,例如A組件想調用B組件的某個操作。還是以登錄為例,當用戶登錄成功之后,在個人中心這個組件里,需要及時展示用戶的個人信息。

我引入了EventBus庫,通過EventBus發送事件通知,其他組件接收自己感興趣的事件,通過訂閱通知的模式,來實現組件之間的通信。

public void post(Object event)

采用EventBus有個問題,它發送的事件必須是一個對象,但我們不可能在底層模塊定義很多event class,所以我定義了一個通用的事件。

public class CommBizEvent {
    public String key;
    public String content;

    public CommBizEvent(String key, String content) {
        this.key = key;
        this.content = content;
    }
}

通過key來區分事件,然后組件文檔維護好這些事件名。

3. 小結

組件之間通信是組件化開發首先要解決的問題,我們必須先解決該問題,后面才能實施下去。在資源緊張、時間緊迫的情況下,可以借鑒成熟的方案,沒必要重新造輪子。我的方案,前期實施比較容易,也很容易理解,但是問題其實還很多。

系列文章
Android組件化開發實踐(一):為什么要進行組件化開發?
Android組件化開發實踐(二):組件化架構設計
Android組件化開發實踐(三):組件開發規范
Android組件化開發實踐(四):組件間通信問題
Android組件化開發實踐(五):組件生命周期管理
Android組件化開發實踐(六):老項目實施組件化
Android組件化開發實踐(七):開發常見問題及解決方案
Android組件化開發實踐(八):組件生命周期如何實現自動注冊管理
Android組件化開發實踐(九):自定義Gradle插件
Android組件化開發實踐(十):通過Gradle插件統一規范

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容