架構師修煉之路-站在架構師的角度如何妙用自定義註解

今天要跟大家分享一個造輪子的案例。

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開發核心知識點筆記》

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章