架构师修炼之路-站在架构师的角度如何妙用自定义注解

108 阅读6分钟
今天要跟大家分享一个造轮子的案例。

EventBus我相信大家都有使用过,虽然现在已经过时,有更好的框架来替代。但是EventBus刚出来的时候,还是受到了很多开发者的青睐和追捧。而EventBus的事件订阅/发布思想是非常值得学习的。

EventBus事件膨胀

我们在使用 EventBus 的时候,要发送一个事件往往都需要创建对应的事件类来标明事件。如果项目中事件多了,这样的事件类的数量也就跟着膨胀了。那么我们何不打造一个类似的框架,用特定标签来代替事件类进行发送与接收,在发送事件的时候指定标签,那么只有声明了该标签的方法才会接受到此事件。当有大量的事件的时候,我们也只需要维护好标签表就行了,而不需要创建大量的事件类。话不多说,下面开始表演。

使用范例

首先,可以在 Activity 或 Fragment 初始化的时候进行注册,ICCLib 是一个单例类,调用其 register() 方法将要接收事件的类对象传入其中。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 注册(是不是跟EventBus长得很像)
    ICCLib.getDefault().register(this);
}

然后,定义一个方法,使用 @ICCTag 自定义注解修饰,并传入一个字符串数组作为注解的值,表示一组标签。方法也可以定义参数来接收发送事件时传入的参数。

@ICCTag({"login"})
public void login(String name) {
    Log.e(TAG, "login:" + name);
}

注册与定义好方法后,我们可以在任何需要的地方进行事件的发送,调用 ICCLibpost()方法,第一个参数为tag,即标签,当发送事件后,定义了此标签的方法才会接收到该事件,第二个参数为一个可变参数,里面放的是需要传递给接收的方法的参数。

ICCLib.getDefault().post("tag","param1","param2");

最后,需要在 Activity 或 Fragment 销毁的时候进行注销,同样传入注册时的对象。

ICCLib.getDefault().unregister(this);

就是这么灵性,使用起来跟 EventBus 一样简便。

原理解析

从上面的使用可以看到,我们需要使用 ICCLib 对象来进行类的注册、注销以及事件的发送,需要使用 @ICCTag 注解来修饰方法并给注解传入标签组,从而接收相应的事件。

首先,我们来定义一个自定义注解: ICCTag

@Target(ElementType.METHOD) //表示该注解修饰的是方法
@Retention(RetentionPolicy.RUNTIME) //表示该注解可以在运行时通过反射获取解析
public @interface ICCTag {
    String[] value();   //表示传入一组标签,用来定义方法要接收那些事件
}

自定义注解定义完后,我们来编写核心类 ICCLib

由于 ICCLib 类会在很多地方都需要用到,所以,我们将它定义为单例类,避免对象的重复创建。

public class ICCLib {
    private static final ICCLib ourInstance = new ICCLib();
    private ICCLib() { }
    public static ICCLib getDefault() {
        return ourInstance;
    }

代码下面讲解。

之后,我们需要在 ICCLib 类里定义三个缓存表。缓存表的作用是将我们第一次反射得到的各种数据缓存起来,再次使用的时候直接从缓存表里拿,从而减少多次反射带来的性能消耗。

第一张,方法缓存表

/**
 * 方法缓存表
 * key: 当前注册类的Class对象
 * value: 方法信息封装类的集合
 */
private HashMap<Class, List<ICCMethod>> mMethodCaches = new HashMap();

第二张,方法执行缓存表

/**
 * 方法执行缓存表
 * key: 标签
 * value:方法执行信息封装类的集合
 */
private HashMap<String, List<ICCMethodInvoke>> mMethodInvokeCaches = new HashMap();

第三张,标签(小精灵) 缓存表

/**
 * 标签缓存表
 * key: 当前注册类的Class对象
 * value: 需要注销的标签集合
 */
private HashMap<Class, List<String>> mTagCaches = new HashMap();

方法缓存表:当调用 ICCLibregister() 方法进行注册的时候,会传入一个实例对象,然后通过反射拿到这个对象所有使用@ICCTag 注解修饰的方法,然后解析这些方法将其封装到 ICCMethod 类。

public class ICCMethod {
    // 标签
    private String tag;
    // 方法反射类
    private Method method;
    ...Getter/Setter
}

方法执行表:同样是在调用 register() 方法的时候,当拿到了使用 @ICCTag 注解修饰的所有方法后,需要根据方法定义的标签来对方法进行分组缓存,并将 ICCMethod 与当前注册的对象封装到 ICCMethodInvoke 类。

当调用post()方法发送事件时,我们就可以通过post()方法传入的标签来拿到相应的需要执行的方法来执行。

public class ICCMethodInvoke {
    // 方法信息封装类
    private ICCMethod mnMethod;
    // 执行方法的对象
    private Object object;
    ...Getter/Setter
}

方法注销表:当调用 register() 方法的时候,会构建方法执行表,同时会将每个注册对象对应的要执行的方法的标签缓存到方法注销表中。当调用 unregister() 方法的时候,就可以传入对象的 Class 类,然后拿出要注销的方法的标签集合遍历,通过标签拿到对应的要执行的所有的方法封装类,如果封装类里面的对象与要注销的对象相同,就将此方法封装类移出方法执行表。

下面首先要做的事情就是在 register() 方法中实现三张表的初始化赋值。

/**
 * 注册
 *
 * @param object
 */
public void register(Object object) {
    if (object == null) return;
    Class<?> objectClass = object.getClass();
    List<ICCMethod> iccMethods = findTaggedMethods(objectClass);
    //从标签缓存表中获取标签集合
    List<String> tags = mTagCaches.get(objectClass);
    if (tags == null) {
        tags = new ArrayList<>();
    }
    for (ICCMethod iccMethod : iccMethods) {
        String tag = iccMethod.getTag();
        if (!tags.contains(tag)) {
            tags.add(tag);
        }

        List<ICCMethodInvoke> iccMethodInvokes = mMethodInvokeCaches.get(tag);
        if (iccMethodInvokes == null) {
            iccMethodInvokes = new ArrayList<>();
        }
        iccMethodInvokes.add(new ICCMethodInvoke(object, iccMethod));
        mMethodInvokeCaches.put(tag, iccMethodInvokes);
    }
    mTagCaches.put(objectClass, tags);
}

/**
 * 查找被ICCTag标记的方法
 * 如果缓存表中不存在,则加入缓存表
 *
 * @param objectClass
 * @return
 */
private List<ICCMethod> findTaggedMethods(Class<?> objectClass) {
    List<ICCMethod> iccMethods = mMethodCaches.get(objectClass);
    if (iccMethods == null) {
        iccMethods = new ArrayList<>();
        Method[] methods = objectClass.getDeclaredMethods();
        for (Method method : methods) {
            //获取方法的注解,只处理 ICCTag
            ICCTag iccTag = method.getAnnotation(ICCTag.class);
            if (iccTag != null) {
                //设置method的权限
                method.setAccessible(true);
                String[] tags = iccTag.value();
                for (String tag : tags) {
                    iccMethods.add(new ICCMethod(tag, method));
                }
            }
        }
        mMethodCaches.put(objectClass, iccMethods);
    }
    return iccMethods;
}

接下来是 unregister() 方法的实现:

/**
 * 反注册
 *
 * @param object
 */
public void unregister(Object object) {
    if (object == null) return;
    //从标签缓存表中获取标签集合
    List<String> tags = mTagCaches.get(object.getClass());
    if (tags == null || tags.size() == 0) return;
    for (String tag : tags) {
        List<ICCMethodInvoke> iccMethodInvokes = mMethodInvokeCaches.get(tag);
        if (iccMethodInvokes != null && iccMethodInvokes.size() > 0) {
            Iterator<ICCMethodInvoke> iterator = iccMethodInvokes.iterator();
            while (iterator.hasNext()) {
                ICCMethodInvoke iccMethodInvoke = iterator.next();
                if (iccMethodInvoke.getObject() == object) {
                    iterator.remove();
                }
            }
        }
    }
}

最后就剩下 post() 方法的实现了。

另外,我分享一份从网络上收录整理的 Android架构视频+BAT面试专题PDF+学习笔记,还有Android开发面试专题资料,高级进阶架构资料供大家学习进阶, 希望可以帮助到大家进入大厂、拿到高薪

如果你现在有需要的话,可以在 石墨文档 上查看《Android开发核心知识点笔记》最新版,路过别忘了点个Star

喜欢本文的话,不妨给我点个小赞、评论区留言或者转发支持一下呗~

《Android开发核心知识点笔记》