最近在 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
的文档中有提到 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 的介绍後,很直觉的就是想到 LiveData 或只是 RxJava 之类的工具,他有Publisher 以及 Subscriber,Subscriber 和 Publisher 之间不用知道彼此的存在,只要 Subscriber 先跟 EventBus Manager 注册要收哪类型的讯息,当 Publisher 发送相同类型的讯息到 EventBus Manager 时, Manager 就会负责转交给那些有注册的 Subscriber,而这个过程是不用 Binder 的介入的
根据上面的基础,我们只要在 Activity 之间设定 Publisher 以及 Subscriber 就可以了,但在使用时有一个地方要注意,在这种 Event Base 的架构下,如果 Subcriber 在 Publisher 发送讯息後才去注册,是没办法拿到资料的
例如:
- A Activity 发送讯息
- A Activity 退到背景,并启动 B Activity
- B Activity 去注册要收到 Event
- B Activity 并不会收到
那这是个满常见的操作,那 EventBus 非常贴心的提供了一个叫作 Sticky Events 的方法,透过这个方法,可以让比较晚注册的 Activity 也可以收到,我觉得非常赞!!!
那实作上分成 3 个部份
我是建立了一个叫作 MessageEvent
的 sealed class,并在其中建立多个 data class 来区分讯息的类型
// MessageEvent.kt
sealed class MessageEvent {
data class MessageTrack(val track: Track) : MessageEvent()
data class MessageSearch(val text: String) : MessageEvent()
}
这边可以看到我建立了两个 data class,分别表示两种类型的讯息
那 Subscriber 就可以像 EventBus 注册需要收到上面哪些类型的讯息,那这边有一个重点需要注意, Subscriber 的 Register
和 Unregister
需要由开发者自己控管,依照我的状况我是在 onStart()
的时候 Register 并在 onStop()
的时候 Unregister
在需要收 Event 的 Method 需要加上 @Subscribe
告诉 EventBus,并且加上 sticky = true
才能收到已经被发过的 event
,而需要在 Method 中透 when
指定要收到哪种讯息,以及收到之後行为
// 要收讯息的 Activity
class 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 -> {
// 指定收到的讯息以及之後的行为
...
}
}
}
...
}
最後就可以透过 Publisher 发送消息了,透过 postSticky
可以让发送被保留住,因此在发送讯息後才启动的 Activity 也可以收到 event 喔!
另外 Publisher 的 Activity 如果没有要接收其他 Activity 的资料,是不需要在 onStart()
以及 onStop()
中向 EventBus 注册的喔~
// 要发讯息的 Activity
class 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 这样的机制,在程序撰写上就可以做到
对於应用上也是有很大的弹性,虽然很方便,但这种 Event Base 撰写上还是要做到尽量单一一点,否则一堆 Event 互相触发、打来打去,在之後 Debug 也会感觉很困扰的!(曾经被 LiveData 循环触发 Event 残害过的人应该都有感...)
最後如果各位大大有更好的解法也欢迎留言分享喔~
<<: 【IntelliJ IDEA 入门指南】Java 开发者的神兵利器
>>: 非线性回归-多项式回归 (polynomial regression in r)
离实习结束还有30天 实习生小光第一天到新公司实习,什麽都不懂的他到底会遇到什麽事情呢,让大家想想第...
前言 认为整体环境看多,优秀的个股不会太差;大环境不好,优秀的股票也会被拖累。有这个概念後我们取得三...
进入到尾声,范例的最後一片拼图,马上开始吧!!! 地图的部分使用了leaflet JS和OpenSt...
前面我们已经完成了 Create 和 Details , 今天就来实作 List 页面 List 页...
DAY4 Python基础教学(二) 前言 今天要介绍一些基础运算式,不过最简单的加减乘除就不赘述了...