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

導讀

本文作者: 承香墨影(微信公衆號: 承香墨影)

發佈時間:2019-04-09

原文地址:https://mp.weixin.qq.com/s?__biz=MzIxNjc0ODExMA==&mid=2247486120&idx=1&sn=2e40a6ab873d33678dcbf0bded2c46e2&chksm=97851389a0f29a9f4fa1637f4b44380921a5daec2934cb63d3236dc34c720f21e519ecbd3bd2&mpshare=1&scene=23&srcid=0513idV15QWbe1SBR6Dn4tWr&sharer_sharetime=1589339674753&sharer_shareid=0f0d47c02c883223a76d8cef2f65e487#rd

Intent 話題是面試常客啦!這篇文章講解得挺詳細的,面試照着答,基本不會被面試官捶啦!


1. 序

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

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

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

2. 爲什麼會出現異常?

2.1 異常原因

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

其實這個問題,如果遇到了,查查文檔就知道了。

在 TransactionTooLargeException(https://developer.android.com/reference/android/os/TransactionTooLargeException.html)的文檔中,其實已經將觸發原因詳細說明了。

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

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

2.2 Bundle 的鍋?

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

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 開發手冊》中,就提到了這個問題的解決建議。

阿里給出的方案,是通過 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 粘性事件的使用細節。

3.2.1 註解的區別

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

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

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

3.2.2 調用方法的區別

在發送消息的時候,需要使用 postSticky() 來替換掉 post() 方法。需要注意的是,粘性事件是使用 Map 結構緩存的,並且是使用事件對象類型當 Key 進行緩存,所以對於同類型的數據,它只會緩存最後發送的數據。

3.2.3 注意清理事件

前面也提到,粘性事件是存儲在一個 Map 對象中的,它是不會主動清理其中存儲的對象的,需要開發者手動清理。EventBus 提供了兩類方法 removeStickyEvent() 和 removeAllStickyEvents() 方法,分別用來清理固定數據以及全部數據。我們需要在合適的時機,手動的調用這兩類方法,清理粘性事件。如果不對粘性事件進行清理,每次 register() 的時候,都會收到粘性事件。

3.2.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 的粘性事件來解決。

 

參考資料:

https://developer.android.com/reference/android/os/TransactionTooLargeException.html

https://www.wanandroid.com/blogimgs/a2609aed-1000-4039-93c3-7541aaa2013b.pdf

 

如果想進一步交流和學習的同學,可以加一下QQ羣哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章