1、简介
作为一个android开发者进程间通信,开发一个产品或多或少都会遇到;能用系统自带的Intent,handler实现是最好的;如果不能,大家应该都会想到一个第三方库EventBus;这个库确实简单、好用;
虽然是手撸,还是按照EventBus核心代码思路来搞的,参考版本3.2.0;
2、基础准备
我们需要准备注解知识、线程切换知识、观察者模式等即可;
2.1 注解
注解本质是一个继承了 Annotation 的特殊接口,其具体实现类是 Java 运行时生成的动态代理类。下面就是EventBus中定义的一个注解类
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
其实就是定义一种接口;下面就先解释以下这个注释
- @interface表示这是一个注释
- @Document表示注释被包含在javadoc中
- @Retention表示注释保留的生命周期可选的RetentionPolicy参数包括:
- SOURCE:注解将被编译器丢弃
- CLASS:注解在class文件中可用,但会被VM丢弃
- RUNTIME:VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。
- @Target 该注解可以用于什么地方,可能的ElementType参数有:
- CONSTRUCTOR:构造器的声明
- FIELD:域声明(包括enum实例)
- LOCAL_VARIABLE:局部变量声明
- METHOD:方法声明
- PACKAGE:包声明
- PARAMETER:参数声明
- TYPE:类、接口(包括注解类型)或enum声明
使用此注解时只需要在方法上方加入下面代码即可
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
注解的获取,是通过反射来获取,比如上述代码需要,先获取Method对象,再调用Method.getAnnotation(Class var1)方法即可获取Subscribe对象,通过相应方法就可以获得配置信息了
其实上面解释的都是元注解,这是系统已经规定好,1-4是可以定义注解的注解,也就是自定义会用到上述注解;自定义注解还有一个 @Inherited允许子类继承父类中的注解
其它一些常见元注解
-
@Override,表示当前的方法定义将覆盖超类中的方法。
-
@Deprecated,使用了注解为它的元素编译器将发出警告,因为注解@Deprecated是不赞成使用的代码,被弃用的代码。
-
@SuppressWarnings,关闭不当编译器警告信息。
@Retention:很重要,决定了你要如何获取注解信息,如果是RUNTIME,直接通过反射获取;如果是SOURCE,则通过java注解器+反射+字节码生成
2.2 线程切换
其实线程切换并没有那么复杂,主线程切换用Handler机制来处理,子线程用线程池来处理即可
2.3 观察者模式
观察者模式(Observer),又叫发布-订阅模式(Publish/Subscribe),定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。
一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
3、开始手撸
既然是观察者模式,那么就有发布者,和接收者,而且要有一套接收者能够正确接收消息的机制;
3.1 使用注解建立接收者信息
比如下面,可以配置接收者执行线程和优先级的方法注解,方法即为接收者,方法传参类型作为接收者接收消息类型
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
int priority() default 0;
}
既然注解已经处理好了,那么我们就定义一个包含接收者信息的类吧;那么这类应该包括
- 反射调用方法所包含的信息:调用者对象,方法对象
- 注解信息:优先级、执行线程信息
- 调用方法的参数信息
消息类
final class Subscription {
Object subscriber;
Method method;
Class<?> type;
int priority;
ThreadMode mode;
public Subscription(Object subscriber, Method method, Class<?> type, int priority, ThreadMode mode) {
this.subscriber = subscriber;
this.method = method;
this.type = type;
this.priority = priority;
this.mode = mode;
}
public void doMethod(Object event) {
try {
method.invoke(subscriber, event);
} catch (Throwable e) {}
}
}
3.2 建立发送者-接收者信息传递机制
需要建立消息类型到消息接收者的集合映射,使用全局模式;比如下面的接收者信息集合:以方法参数的class为索引,以方法等信息为value来存储
private HashMap<Class<?>, CopyOnWriteArrayList<Subscription>> maps;
注册消息:
public void register(Object obj) {
if (obj != null) {
Method[] methods = obj.getClass().getDeclaredMethods();
if (methods != null && methods.length > 0) {
if (maps == null) {
maps = new HashMap<>();
}
Subscription subscription;
for (Method method: methods) {
Class<?>[] params = method.getParameterTypes();
if (params == null || params.length != 1) {
continue;
}
Subscribe subscribe = method.getAnnotation(Subscribe.class);
if (subscribe == null) {
continue;
}
subscription = new Subscription(obj, method, params[0], subscribe.priority(), subscribe.threadMode());
CopyOnWriteArrayList<Subscription> subscriptions = maps.get(params[0]);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
maps.put(params[0], subscriptions);
}
if (subscriptions.isEmpty()) {
subscriptions.add(subscription);
} else {
int index = 0;
for (; index < subscriptions.size(); index++) {
if (subscription.priority < subscriptions.get(index).priority) {
break;
}
}
subscriptions.add(index, subscription);
}
}
}
}
}
发送消息:
public void post(final Object event) {
if (event != null && maps != null && !maps.isEmpty()) {
CopyOnWriteArrayList<Subscription> subscriptions = maps.get(event.getClass());
if (subscriptions == null) {
return;
}
for (final Subscription subscription : subscriptions) {
switch (subscription.mode) {
case POSTING:
subscription.doMethod(event);
break;
case MAIN:
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
subscription.doMethod(event);
}
});
break;
default:
service.submit(new Runnable() {
@Override
public void run() {
subscription.doMethod(event);
}
});
}
}
}
}
接收者:
@Subscribe(priority = 1, threadMode = ThreadMode.MAIN)
public void onMessage(String str) {
view.setText(str);
}
发送消息:
EventBus.get().post("nihao event bus!");
解注:
public void unRegister(Object obj) {
if (obj != null) {
Method[] methods = obj.getClass().getDeclaredMethods();
if (methods != null && methods.length > 0) {
if (maps == null) {
maps = new HashMap<>();
return;
}
for (Method method: methods) {
if (method.getAnnotation(Subscribe.class) == null) {
continue;
}
Class<?>[] params = method.getParameterTypes();
if (params == null || params.length != 1) {
continue;
}
CopyOnWriteArrayList<Subscription> subscriptions = maps.get(params[0]);
if (subscriptions == null) {
return;
}
int index = 0;
for (; index < subscriptions.size(); index++) {
if (subscriptions.get(index).subscriber == obj) {
break;
}
}
subscriptions.remove(index);
}
}
}
}
通过上面两步骤,已经完成最简单暴力的版本;其中仍然可以增加class-method的映射缓存等,还可以做一些粘性消息等新增功能
4、 EventBus中的线程切换处理
首先定义了一个Poster接口;仅有一个方法
interface Poster {
void enqueue(接收者信息 Subscription subscription, 消息 Object event);
}
实现中均使用了单列表的先进先出的队列PendingPostQueue,节点PendingPost封装了接收者、消息信息,并使用了池技术
定义了三种实现:
- 异步处理 AsyncPoster:实现了Runnable接口,通过enqueue方法加入接收者-消息,调用线程池执行当前对象
- 后台处理 BackgroundPoster:实现了Runnable接口,通过enqueue方法加入接收者-消息,如果当前run方法未执行,则调用线程池执行; run方法采用死循环方式,尽可能尝试从队列中读取,1s后仍然未有处理,则run方法执行结束
- 主线程处理HandlerPoster,继承Handler,通过enqueue方法加入接收者-消息,如果当前handleMessage方法不再运行状态,则发送消息,通知执行;执行时,也使用了死循环,跳出的条件,执行时长小于某个值,默认10ms
为什么处理的过程中基本都使用了死循环呢,而又给处理跳转条件呢?
- 死循环是为了,更及时的把消息处理掉
- 结束条件,是为了不阻塞主线程或者,节约线程资源
5、小结
自己手撸EventBus,根据所需功能来定,上面小结中手撸的代码60%的都够用了,而且很简单;那么我们在具体阻止代码时,应该注意什么呢?我认为至少要注意以下3点:
- 注意线程安全
- 注意不要内存泄漏,注册和解注要成对实现(也可利用弱引用+加弱引用队列来处理)
- 建立一套触发机制,保证消息得到处理
技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!