面試常客:Intent 能傳遞多大 Size 的數據?| 付阿里的建議方案!

1、序

做爲 Android 開發,平常 Coding 時,最頻繁的操做應該就是操做 App 內的一系列 Activity。而在 Activity 間傳遞數據,就須要藉助 Intent。html

很多資料中寫到,Intent 在 Activity 間傳遞基礎類型數據或者可序列化的對象數據。可是 Intent 對數據大小是有限制的,當超過這個限制後,就會觸發 TransactionTooLargeException 異常。android

那麼今天就來聊聊 Intent 傳遞大數據時,爲何會拋異常,以及如何解決它。面試

2、爲何會出現異常?

2.1 異常緣由

Intent 傳遞大數據,會出現 TransactionTooLargeException 的場景,這自己也是一道面試題,常常在面試中被問到。緩存

其實這個問題,若是遇到了,查查文檔就知道了。安全

在 TransactionTooLargeException(https://developer.android.com... 的文檔中,其實已經將觸發緣由詳細說明了。post

簡單來講,Intent 傳輸數據的機制中,用到了 Binder。Intent 中的數據,會做爲 Parcel 被存儲在 Binder 的事務緩衝區(Binder transaction buffer)中的對象進行傳輸。大數據

而這個 Binder 事務緩衝區具備一個有限的固定大小,當前爲 1MB。你可別覺得傳遞 1MB 如下的數據就安全了,這裏的 1MB 空間並非當前操做獨享的,而是由當前進程所共享。也就是說 Intent 在 Activity 間傳輸數據,自己也不適合傳遞太大的數據。優化

2.2 Bundle 的鍋?

這裏再補充一些細節,Intent 使用 Bundle 存儲數據,究竟是值傳遞(深拷貝)仍是引用傳遞?spa

Intent 傳輸的數據,都存放在一個 Bundle 類型的對象 mExtras 中,Bundle 要求全部存儲的數據,都是可被序列化的。線程

在 Android 中,序列化數據須要實現 Serializable 或者 Parcelable。對於基礎數據類型的包裝類,自己就是實現了 Serializable,而咱們自定義的對象,按需實現這兩個序列化接口的其中一個便可。

那是否是隻要經過 Bundle 傳遞數據,就會面臨序列化的問題?

並非,Activity 之間傳遞數據,首先要考慮跨進程的問題,而 Android 中又是經過 Binder 機制來解決跨進程通訊的問題。涉及到跨進程,對於複雜數據就要涉及到序列化和反序列化的過程,這就註定是一次值傳遞(深拷貝)的過程。

這個問題用反證法也能夠解釋,若是是引用傳遞,那傳遞過去的只是對象的引用,指向了對象的存儲地址,就只至關於一個 Int 的大小,也就根本不會出現 TransactionTooLargeException 異常。

傳輸數據序列化和 Bundle 沒有關係,只與 Binder 的跨進程通訊有關。

爲何要強調這個呢?

在 Android 中,使用 Bundle 傳輸數據,並不是 Intent 獨有的。例如使用彈窗時,DialogFragment 中也能夠經過 setArguments(Bundle) 傳遞一個 Bundle 對象給對話框。

Fragment 自己是不涉及跨進程的,這裏雖然使用了 Bundle 傳輸數據,可是並無經過 Binder,也就是不存在序列化和反序列化。和 Fragment 數據傳遞相關的 Bundle,其實傳遞的是原對象的引用。

有興趣能夠作個試驗,彈出 Dialog 時傳遞一個對象,Dialog 中修改數據後,在 Activity 中檢查數據是否被修改了。

3、如何解決這個異常?

3.1 解決思路

知道異常的緣由,就好解決了。

既然緣由在於 Binder 傳輸限制了數據的大小,那咱們不走 Binder 通訊就行了。

能夠從數據源上來考慮。

例如 Bitmap,自己就已經實現了 Parcelable 是能夠支持序列化的。用 Intent 傳輸,稍微大一點的圖必定會出現 TransactionTooLargeException。固然真是業務場景,確定不存在傳遞 Bitmap 的狀況。

那就先看看這個圖片的數據源。Drawable?本地文件?線上圖片?不管數據源在哪裏,咱們只須要傳遞一個 drawable_id、路徑、URL,就能夠還原這張圖片,無需將這個 Bitmap 對象傳遞過去。大數據總有數據源,從數據源還原數據,對咱們而言只是調用一個方法而已。

此前阿里發佈的《Android 開發者手冊》中,就提到了這個問題的解決建議。

image

阿里給出的方案,是經過 EventBus 來傳遞數據。

3.2 EventBus 的 粘性事件

不少商業項目其實都用到了 EventBus,這裏就簡單介紹如何使用 EventBus 的粘性事件來完成數據在 Activity 間的傳遞。

EventBus 是一個 Android 端優化的 Publish/subscribe 消息總線,簡化了應用程序內各個組件間、組件與後臺線程間的通訊。

在 Activity 中使用 EventBus,須要根據 Activity 的生命週期,成對調用register() 和 unregister() 方法。普通的事件,只會發生在 register() 以後,在註冊前發生的事件,通通都收不到。

這裏利用的 EventBus 的粘性事件(Sticky Event)來實現,EventBus 內部維護了一個 Map 對象 stickyEvents,用於緩存粘性事件。

粘性事件使用 postSticky() 方法發送,它會將事件緩存到 stickyEvents 這個 Map 對象中,以待下次註冊時,將這個事件取出,拋給註冊的組件。以此來達到一個粘性的滯後事件發送和接收。

接下來咱們看看 EventBus 粘性事件的使用細節。

1. 註解的區別

粘性事件的註解和普通事件的註解略有區別,須要添加 threadMode 和 sticky 參數。

@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
public void onStickyEvent(MyStickyEvent event){
    //...
}

注意,這兩個額外的參數是必須的。

2. 調用方法的區別

在發送消息的時候,須要使用 postSticky() 來替換掉 post() 方法。

須要注意的是,粘性事件是使用 Map 結構緩存的,而且是使用事件對象類型當 Key 進行緩存,因此對於同類型的數據,它只會緩存最後發送的數據。

3. 注意清理事件

前面也提到,粘性事件是存儲在一個 Map 對象中的,它是不會主動清理其中存儲的對象的,須要開發者手動清理。

EventBus 提供了兩類方法 removeStickyEvent() 和 removeAllStickyEvents()方法,分別用來清理固定數據以及所有數據。

咱們須要在合適的時機,手動的調用這兩類方法,清理粘性事件。若是不對粘性事件進行清理,每次 register() 的時候,都會收到粘性事件。

4. EventBus 粘性事件的問題

粘性事件自己是脫離了 Android Intent 數據傳遞的這一套機制的,要知道 Activity 會在一些特殊狀況下被銷燬重建,在此狀況下,經過 Intent 傳遞的數據,是能夠繼續從 Intent 中獲取恢復到上一次頁面傳遞的數據。

而經過 EventBus 的粘性事件,則可能在銷燬重建時,形成數據丟失。

若是想要使用 EventBus 的粘性事件,來在頁面間傳遞大數據,仍是有很多細節,須要根據業務來調整的。

4、小結時刻

今天咱們聊到了在 Activity 間,經過 Intent 傳遞大數據會觸發 TransactionTooLargeException 異常的緣由,以及如何解決它,最後再簡單總結一下。

  1. Intent 沒法傳遞大數據是由於其內部使用了 Binder 通訊機制,Binder 事務緩衝區限制了傳遞數據的大小。
  2. Binder 事務緩衝區的大小限定在 1MB,可是這個尺寸是共享的,也就是並非傳遞 1MB 如下的數據就絕對安全,要視當前的環境而定。
  3. 不要挑戰 Intent 傳遞數據大小的極限,對於大數據,例如長字符串、Bitmap 等,不要考慮 Intent 傳遞數據的方案。
  4. 解決大數據傳遞問題,能夠從數據源出發,根據數據的標識,還原數據,或者先持久化再還原。也可使用 EventBus 的粘性事件來解決。

reference:https://developer.android.com...

https://www.wanandroid.com/bl...

相關文章
相關標籤/搜索