透过 EventBus 解决 TransactionTooLargeException 问题

最近在 App 里面加上了纪录使用者登山轨迹的功能后,上线的第一个週末 Firebase Crashlytics 就冒出来 20 个以上的 crash log,尿都快吓出来了...

看了一下 log 后发现虽然引发的位置不同,但是全部都指向同一个问题

Caused by android.os.TransactionTooLargeException: data parcel size 2764576 bytes       at android.os.BinderProxy.transactNative(BinderProxy.java)       at android.os.BinderProxy.transact(BinderProxy.java:510)       ... 以下省略

TransactionTooLargeException 的原因

官方在 TransactionTooLargeException 的文档中有提到 Binder transaction buffer 有 1MB 的限制


[color=#79bf18]

其中提到的 Binder 是 Android OS 中负责处理 Process 之间通信的机制(IPC, Inter-Process Communication), 当 Activity、Service、Broadcast Receiver 和 Content Provide 需要沟通时,就会需要 Binder 参与到其中

而我所碰到的状况是纪录了太多的轨迹点,当他需要切换到其他 Activity 时,我会透过 Intent + Parcelable 来传递轨迹点资料,而当 Intent 内容超过 1MB 时, OS 就会丢出 TransactionTooLargeException,因此必须找个方法让资料可以完整的传递,同时不会引起 TransactionTooLargeException 的方法

解法

而这个问题其实分析过后就是捨弃 Intent 传递资料,只需要有一个地方可以暂存这些资料,而需要的 Activity 可以取得,那这样就可以解决上述的问题

而最多人使用的是 EventBus 来解,其中包括阿里巴巴的开发手册都推荐使用 EventBus 来解大量数据传递的问题,因此应该可以很放心的使用 EventBus 来解吧...

EventBus 是啥?

其实在这之前我都没有用过 EventBus 的经验,但在了解过后发现如果只是用基础功能,那其实还满好上手的

在我看完 EventBus 的介绍后,很直觉的就是想到 LiveData 或只是 RxJava 之类的工具,他有Publisher 以及 Subscriber,Subscriber 和 Publisher 之间不用知道彼此的存在,只要 Subscriber 先跟 EventBus Manager 注册要收哪类型的讯息,当 Publisher 发送相同类型的讯息到 EventBus Manager 时, Manager 就会负责转交给那些有注册的 Subscriber,而这个过程是不用 Binder 的介入的

用 EventBus 怎么解?

根据上面的基础,我们只要在 Activity 之间设定 Publisher 以及 Subscriber 就可以了,但在使用时有一个地方要注意,在这种 Event Base 的架构下,如果 Subcriber 在 Publisher 发送讯息后才去注册,是没办法拿到资料的

例如:

A Activity 发送讯息A Activity 退到背景,并启动 B ActivityB Activity 去注册要收到 EventB Activity 并不会收到

那这是个满常见的操作,那 EventBus 非常贴心的提供了一个叫作 Sticky Events 的方法,透过这个方法,可以让比较晚注册的 Activity 也可以收到,我觉得非常讚!!!

实作

那实作上分成 3 个部份

建立讯息类型Subscriber 向 EventBus 注册Publisher 向 EventBus 发送讯息

1. 建立讯息类型

我是建立了一个叫作 MessageEvent 的 sealed class,并在其中建立多个 data class 来区分讯息的类型

// MessageEvent.ktsealed class MessageEvent {    data class MessageTrack(val track: Track) : MessageEvent()    data class MessageSearch(val text: String) : MessageEvent()}

这边可以看到我建立了两个 data class,分别表示两种类型的讯息

2. Subscriber 向 EventBus 注册

那 Subscriber 就可以像 EventBus 注册需要收到上面哪些类型的讯息,那这边有一个重点需要注意, Subscriber 的 RegisterUnregister 需要由开发者自己控管,依照我的状况我是在 onStart() 的时候 Register 并在 onStop() 的时候 Unregister

在需要收 Event 的 Method 需要加上 @Subscribe 告诉 EventBus,并且加上 sticky = true 才能收到已经被发过的 event ,而需要在 Method 中透 when 指定要收到哪种讯息,以及收到之后行为

// 要收讯息的 Activityclass HikeStatisticsActivity : AppCompatActivity() {    ...    override fun onStart() {        super.onStart()        // 向 EventBus 注册        EventBus.getDefault().register(this)    }    override fun onStop() {        super.onStop()        // 结束注册        EventBus.getDefault().unregister(this)    }    // 告诉 EventBus 可以收到之前发出来的讯息,以及跑在 MAIN theread 上    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)    fun onResultReceived(event: MessageEvent) {        when (event) {            is MessageEvent.MessageHike -> {                // 指定收到的讯息以及之后的行为                ...            }        }    }    ...}

3. Publisher 向 EventBus 发送讯息

最后就可以透过 Publisher 发送消息了,透过 postSticky 可以让发送被保留住,因此在发送讯息后才启动的 Activity 也可以收到 event 喔!

另外 Publisher 的 Activity 如果没有要接收其他 Activity 的资料,是不需要在 onStart() 以及 onStop() 中向 EventBus 注册的喔~

// 要发讯息的 Activityclass TrackingActivity : AppCompatActivity() {    ...    private inner class StatisticsClickListener : View.OnClickListener {        override fun onClick(v: View?) {            val intent = Intent(context, HikeStatisticsActivity::class.java)            EventBus.getDefault().postSticky(MessageEvent.MessageHike(trackingData))            startActivity(intent)        }    }    ...}

结论

透过 EventBus 这样的机制,在程式撰写上就可以做到

一个 Publisher 让多个 Subscriber 收到 Event多个 Publisher 让一个 Subscriber 收到 Event

对于应用上也是有很大的弹性,虽然很方便,但这种 Event Base 撰写上还是要做到儘量单一一点,否则一堆 Event 互相触发、打来打去,在之后 Debug 也会感觉很困扰的!(曾经被 LiveData 循环触发 Event 残害过的人应该都有感...)

最后如果各位大大有更好的解法也欢迎留言分享喔~

Reference

https://developer.android.com/reference/android/os/TransactionTooLargeExceptionhttps://greenrobot.org/eventbus/documentation/configuration/sticky-events/

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章