在做Android开发时,熟悉EventBus的人都知道,一个事件在发送的时候,可以选择是否由包含这个事件的父类形参的方法来接收,比如:
String event = "event";
//可由以下两个方法接收
public void subscribeString(String event) {
System.out.println("this is subscribeString:" + event);
}
public void subscribeObject(Object event) {
System.out.println("this is subscribeObject:" + event);
}
之所以EventBus能做到这一点,得益于java强大的反射机制,在发送事件时,会通过反射获取事件参数的所有父类,进而通过这些类型来找到所有匹配的方法进行分发。
但是在flutter中,为了最大化地缩减包体积,直接屏蔽了dart语言的反射包“ dart:mirrors”。
而我们要做到事件向父类分发,得要知道这个事件的父类都有什么,而去除反射之后,留给flutter关于类型信息的只有Type
这个类,这个类能干啥呢?这个类基本啥都干不了-_-,能应用的场景基本等于无:
//判断类型是否相等
Type type1 = String;
Type type2 = '1'.runtimeType;
print(type1 == type2); //true
//输出类型让我们查看
print('1'.runtimeType);
///当做映射的key值
Map<Type, dynamic> map = {};
除此之外,基本没啥用途了,而且这个Type不能用于is关键字:
Type type = String;
print('1' is String); //true
print('1' is type); //编译出错
所以,我们获取不了一个变量的所有父类型,得换个思路,什么思路呢?可以这样:在分发事件的时候,找到可以传这个事件参数的所有方法不就行了吗?
但说起来简单,做起来并没有我们想象中那么容易。
先由最基本的来,首先,我们可以用一个Map来根据事件类型保存相应方法:
Map<Type, Set<Function>> map = {};
注册方法:
Map<Type, Set<Function>> map = {};
void register<T>(Function(T event) fun) {
var funSet = map[T];
if (funSet == null) {
funSet = map[T] = <T>{};
}
funSet.add(fun);
}
要注意的是,我们不能用Set<Function(dynamic event)>
来保存方法,一是我们并不知道事件的具体类型,二是Function(dynamic event)
并不兼容Function(String event)
,所以只能用Set<Function>
来涵盖所有Function(event)
,在注册的时候限定方法的参数就行了。
接着是发送事件:
Map<Type, Set<Function>> map = {};
void sendEvent<T>(T event) {
var set = map[T];
if (set != null) {
for (var fun in set) {
fun.call(event);
}
}
}
以上是不需要找到父类型的事件分发步骤,接着我刚才所说的思路,我们应该如何找到可以传这个事件参数的所有方法?
我们先遍历整个map集合:
Map<Type, Set<Function>> map = {};
void sendEvent<T>(T event) {
for (var entry in map.entries) {
Type type = entry.key;
var set = entry.value;
//如何判断event的类型是type?
}
}
这样,如何找到可以传这个事件参数的所有方法,转化为了如何判断event
的类型是type
,但是在上边我们说过,Type
不能用于is
关键字:
Type type = String;
print('1' is type); //编译出错
用Type
来判断这条路断了,那么还能用什么来判断?没错,就是泛型!dart的泛型不像Java,没有类型擦除,这算是不幸中的万幸,所以,我们应该可以有一个方案,来使得思路可以达成,比如:
void callFun<T>(Function(T event) fun, dynamic event) {
if (event is T) {
fun.call(event);
}
}
直接通过泛型来判断,是不是很方便?哈哈哈,感觉是不是答案已经出来了?
看起来没错,判断类型的方案确实已经出来了,但是在执行这方案的前一个步骤我们却遇到了难题,还记得上边我说过只能用Set<Function>
来保存方法吗?是的,所以现在遇到冲突了,Function
没法转成Function(T event)
!
Map<Type, Set<Function>> map = {};
void sendEvent<T>(T event) {
for (var funSet in map.values) {
for (var fun in funSet) {
callFun(fun, event); //报错,编译不通过
}
}
}
void callFun<T>(Function(T event) fun, dynamic event) {
if (event is T) {
fun.call(event);
}
}
即使把Function
强制转成Function(dynamic event)
来绕过编译检查:
callFun(fun as Function(dynamic), event);
或是其他的类型如fun as Function(Object)
,在实际运行时也会报错,因为不兼容!
所以,我们没办法了吗?
实际上当然还是有的,不然我写这博客坑人吗?
既然我们没办法从Function
本身下手,那么干脆我们拔高一个度,从管理Function
的容器下手!
我们可以通过管理Function
的容器知道Function
所能接受的Type
呀!
但是管理Function
的Set
已经不能用了,因为Set
的泛型已经限定为Function
了,所以我们得再包一层,用包装类的泛型来揭示Function
所能接受的类型!
class EventFunSet<T> {
final Set<Function> _funSet = {};
bool get isEmpty => _funSet.isEmpty;
void add(Function(T e) fun) {
_funSet.add(fun);
}
void remove(Function fun) {
_funSet.remove(fun);
}
void callEventFun(dynamic event) {
if (_funSet.isNotEmpty && event is T) {
for (var fun in _funSet) {
try {
fun.call(event);
} catch (e, s) {
LogUtils.logException(e, s, 'EventUtils._callEventFun');
}
}
}
}
}
使用:
Map<Type, EventFunSet> map = {};
void register<T>(Function(T event) fun) {
//限定EventFunSet下Function类型为T
var funSet = map[T] as EventFunSet<T>?;
if (funSet == null) {
funSet = map[T] = EventFunSet<T>();
}
funSet.add(fun);
}
void sendEvent<T>(T event) {
for (var funSet in map.values) {
funSet.callEventFun(event);
}
}
上边流程关键就是在注册流程时限定包装类类型为T
:
map[T] = EventFunSet<T>();
发送事件时查找跟事件兼容的包装类类型T
:
if (_funSet.isNotEmpty && event is T) {
for (var fun in _funSet) {
try {
fun.call(event);
} catch (e, s) {
LogUtils.logException(e, s, 'EventUtils._callEventFun');
}
}
}
知道了核心步骤,无论你想要用StreamController
来分发还是别的,都能应用起来。
如此,向父类参数的方法发送事件也就大工告成,是不是有点绕哈哈哈。但是没办法,没有反射就是这么坑,效率也要低点,当事件类型过多的时候遍历整个Map
还是很有点耗时的,但是一般情况下也不会太耗。
最后,我强烈建议谷歌把dart的部分反射特性弄回来,起码得让我们知道一个已知变量的类型都有哪些,不然做项目真的不方便。