IOC實(shí)現(xiàn)一個(gè)簡(jiǎn)單的注解處理器

首先看一下用法和效果圖:
點(diǎn)擊按鈕 然后處理注入的事件

image.png

具體使用方法類似ButterKnife:

@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @ViewInject(R.id.testTv)
    private TextView testTv;

    @ViewInject(R.id.btn)
    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 注入
        InjectUtils.inject(this);
        testTv.setText("這是一段很有意思的文字~~~");
        btn.setText("點(diǎn)擊有驚喜");
    }
    
    @OnClick({R.id.btn})
    private void btnClickListener(View v){
        Toast.makeText(this, "哎呀扮叨,差一點(diǎn)就拿到100w獎(jiǎng)金了台夺,再試一次吧~", Toast.LENGTH_SHORT).show();
    }
}

具體實(shí)現(xiàn)

1群发、首先先定義幾個(gè)想要注入事件的注解文件

  • 定義一個(gè)ContentView注解
@Target(ElementType.TYPE) // (Target表示注解用在什么地方) ElementType.TYPE 表示是用在類上的注解
@Retention(RetentionPolicy.RUNTIME) // 存在于運(yùn)行期
public @interface ContentView  {
    int value();
}
  • 定義一個(gè)view的初始化注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
    int value();
}
  • 然后準(zhǔn)備定義一個(gè)onClick事件的注解辕羽,但是不能像上面那樣寫了妥色,因?yàn)榭赡苓€有別的行為事件租冠,如果按上面寫法鹏倘,那么在處理注解的時(shí)候就需要手動(dòng)判斷然后再對(duì)應(yīng)處理是點(diǎn)擊事件還是長(zhǎng)按事件等等了,耦合度較高顽爹,所以這里需要定義一個(gè)行為事件的父級(jí)注解纤泵,仔細(xì)看會(huì)發(fā)現(xiàn)一般行為事件分為三部分:
簡(jiǎn)單行為事件的結(jié)構(gòu)

所以父級(jí)注解需要有三個(gè)字段,以方便后續(xù)處理注解的事件:

@Target(ElementType.ANNOTATION_TYPE) // 用于注解上的注解
@Retention(RetentionPolicy.RUNTIME) // 存在于運(yùn)行期
public @interface EventBase {

    /**
     * 設(shè)置監(jiān)聽的方法
     * @return
     */
    String listenerSetter();

    /**
     * 設(shè)置監(jiān)聽的類型
     * @return
     */
    Class<?> listenerType();

    /**
     * 事件觸發(fā)之后的回調(diào)方法
     * @return
     */
    String callbackMethod();

}
  • 然后再定義自己想要處理的行為事件镜粤,這里定義一個(gè)onClick事件
@Target(ElementType.METHOD) // 用于方法上的注解
@Retention(RetentionPolicy.RUNTIME) // 存在于運(yùn)行期
@EventBase(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class, callbackMethod = "onClick")
public @interface OnClick {
    int[] value();
}

2捏题、接下來實(shí)現(xiàn)一個(gè)InvocationHandler接口
為什么處理注入事件不直接用InvocationHandler呢,這里做一個(gè)優(yōu)化:

public class EventInvocationHandler implements InvocationHandler {

    private Activity activity;
    private final Map<String ,Method> methodMap;

    public EventInvocationHandler(Activity activity,Map<String ,Method> methodMap){
        this.activity=activity;
        this.methodMap=methodMap;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
        Method mtd = methodMap.get(method.getName());
        if(mtd!=null){
            // 如果這個(gè)方法不是被public修飾的 就強(qiáng)制改成可被使用的(忽略檢查)
            if (!Modifier.isPublic(mtd.getModifiers())) {
                AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
                    mtd.setAccessible(true);
                    return null;
                });
            }
            return mtd.invoke(activity,objects);
        }
        return method.invoke(proxy,objects);
    }

}

3肉渴、最后就是核心實(shí)現(xiàn)了公荧,處理注解的類、變量同规、函數(shù)

public class InjectUtils {

    public static void inject(Activity activity) {
        // 注入布局
        injectLayout(activity);
        // 注入視圖
        injectViews(activity);
        // 注入事件
        injectEvents(activity);
    }

    /**
     * 注入布局
     *
     * @param activity
     */
    private static void injectLayout(Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        ContentView content = clazz.getAnnotation(ContentView.class);
        activity.setContentView(content.value());
    }

    /**
     * 注入布局
     *
     * @param activity
     */
    private static void injectViews(Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field item : fields) {
            ViewInject viewInject = item.getAnnotation(ViewInject.class);
            if (viewInject == null) {
                continue;
            }
            int id = viewInject.value();
            View view = activity.findViewById(id);
            item.setAccessible(true); // 設(shè)置訪問權(quán)限
            try {
                item.set(activity, view);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    private static void injectEvents(Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            // 獲取方法上的所有注解
            Annotation[] annotation = method.getAnnotations();
            for (Annotation ann : annotation) {
                Class<? extends Annotation> annotationType = ann.annotationType();
                EventBase eventBase = annotationType.getAnnotation(EventBase.class);
                if (eventBase == null) {
                    continue;
                }
                // 事件要素
                Class<?> listenerType = eventBase.listenerType();
                String listenerSetter = eventBase.listenerSetter();
                String callbackMethod = eventBase.callbackMethod();

                // 方法攔截的對(duì)應(yīng)關(guān)系
                Map<String,Method> methodMap = new HashMap<>();
                methodMap.put(callbackMethod,method);

                // 事件源
                // 通過方法拿到事件源
                try {
                    Method valueMethod = annotationType.getDeclaredMethod("value");
                    int[] viewIds = (int[]) valueMethod.invoke(ann);
                    if (viewIds == null) {
                        continue;
                    }
                    for (int id : viewIds) {
                        View view = activity.findViewById(id);
                        if (view == null) {
                            continue;
                        }
                        Method setListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
                        // 如何得到View.OnClickListener監(jiān)聽器的代理對(duì)象
                        EventInvocationHandler invocationHandler = new EventInvocationHandler(activity,methodMap);
                        Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[]{listenerType}, invocationHandler);
                        setListenerMethod.invoke(view,proxy);
                    }
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }


}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末循狰,一起剝皮案震驚了整個(gè)濱河市庸诱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晤揣,老刑警劉巖桥爽,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異昧识,居然都是意外死亡钠四,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門跪楞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缀去,“玉大人,你說我怎么就攤上這事甸祭÷扑椋” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵池户,是天一觀的道長(zhǎng)咏雌。 經(jīng)常有香客問我,道長(zhǎng)校焦,這世上最難降的妖魔是什么赊抖? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮寨典,結(jié)果婚禮上氛雪,老公的妹妹穿的比我還像新娘。我一直安慰自己耸成,他們只是感情好报亩,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著井氢,像睡著了一般弦追。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上毙沾,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天骗卜,我揣著相機(jī)與錄音,去河邊找鬼左胞。 笑死寇仓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烤宙。 我是一名探鬼主播遍烦,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼躺枕!你這毒婦竟也來了服猪?” 一聲冷哼從身側(cè)響起供填,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎罢猪,沒想到半個(gè)月后近她,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡膳帕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年粘捎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片危彩。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡攒磨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汤徽,到底是詐尸還是另有隱情娩缰,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布谒府,位于F島的核電站拼坎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏狱掂。R本人自食惡果不足惜演痒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望趋惨。 院中可真熱鬧,春花似錦惦蚊、人聲如沸器虾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兆沙。三九已至,卻和暖如春莉掂,著一層夾襖步出監(jiān)牢的瞬間葛圃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工憎妙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留库正,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓厘唾,卻偏偏與公主長(zhǎng)得像褥符,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抚垃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容