Android自定義注解與反射

反射

??JAVA反射機制是在運行狀態(tài)中,對于任意一個類助隧,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意一個方法和屬性并村;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機制巍实。

1.獲取Class對象

舉個例子,我們要獲取MainActivity的Class對象哩牍,有以下幾種方式

1.Class<MainActivity> clazz = MainActivity.class;
2.Class<? extends MainActivity> clazz = this.getClass();
3.Class<?> clazz = Class.forName("utils.utilcode.blankj.com.dongtai.MainActivity");

2.通過Class對象獲取構(gòu)造方法棚潦,成員變量,方法等

構(gòu)造函數(shù)

getConstructor();//獲取本類或父類中public修飾的無參構(gòu)造函數(shù)
getDeclaredConstructor();//獲取本類無參構(gòu)造函數(shù)
getDeclaredConstructor(Class<?>... parameterTypes): 獲取本類中指定參數(shù)的構(gòu)造器

成員變量

getFields(): 獲取本類或父類中所有public屬性
getField(String name): 獲取本類或父類中特定名字的public屬性
getDeclaredFields(): 獲取本類中聲明的所有屬性

獲取方法

getMethods(): 獲取本類或父類中所有public方法(包括構(gòu)造器方法)
getMethod(String name, Class<?>... parameterTypes): 獲取本類或父類中特定名字和參數(shù)的public方法
getDeclaredMethods(): 獲取本類中聲明的所有方法(包括非public但不包括繼承來的)
getDeclaredMethod(String name, Class<?>... parameterTypes): 獲取本類中聲明的特定名字和參數(shù)的方法(最常用)

獲取注解

getAnnotation(Class<A> annotationClass): 獲取這個元素上指定類型的注解(常用)
getDeclaredAnnotations(): 獲取直接標注在這個元素上的注解

父類子類(接口)相關(guān)

getSuperclass(): 返回本類的父類
getGenericSuperclass(): 以Type的形式返回本類的父類, 帶有范型信息
getInterfaces(): 返回本類直接實現(xiàn)的接口
getGenericInterfaces(): 以Type的形式返回本類直接實現(xiàn)的接口, 帶有范型信息

自定義注解

新建一個InjectView類
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
    int value();
}

Target表示作用范圍膝昆,這里表示作用于成員變量

/** Targe表示作用范圍
 * TYPE 作用對象類/接口/枚舉
 * FIELD 成員變量
 * METHOD 成員方法
 * PARAMETER 方法參數(shù)
 * ANNOTATION_TYPE 注解的注解
 */

Retention指定了注解有效期直到運行時時期
value就是用來指定id丸边,也就是findViewById的參數(shù)

在MainActivity中添加注解
    @InjectView(R.id.bind_view_btn)
    Button mBindView;
    @InjectView(R.id.textView)
    TextView textView;
在MainActivity的OnCreate方法中進行注入
  super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Utils.injectView(this);
新建Utils這個類,創(chuàng)建injectView方法

代碼中的每一行都有詳細的注釋

    public static void injectView(Activity activity) {
        if (null == activity) return;
        //獲取activity的.class對象
        Class<? extends Activity> activityClass = activity.getClass();
//        獲取所有成員變量集合
        Field[] declaredFields = activityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //找到有@InjectView注解的成員變量
            if (field.isAnnotationPresent(InjectView.class)) {
                //得到注解類的對象
                InjectView annotation = field.getAnnotation(InjectView.class);
                //找到VIew的id
                int value = annotation.value();
                try {
                    //找到findViewById方法
                    Method findViewByIdMethod = activityClass.getMethod("findViewById", int.class);
                   //暴力訪問荚孵,可獲取私有方法
                    findViewByIdMethod.setAccessible(true);
                    View view = (View) findViewByIdMethod.invoke(activity, value);
                    //將結(jié)果賦值給成員變量
                    field.set(activity, view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

首先是獲取activity的.class對象妹窖,通過Class對象找到該類中所有的成員變量。繼而找到有InjectView注解的成員變量收叶,然后得到注解類的對象骄呼,找到VIew的id,通過反射獲取Activity的findViewById方法判没,然后將結(jié)果傳給成員變量蜓萄。

注解onClick點擊事件

新建一個onClick類

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventType(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", methodName = "onClick")
public @interface onClick {
    int[] value();
}
   Target指定了onClick注解作用對象是成員方法
   Retention指定了onClick注解有效期直到運行時時期
   value就是用來指定id,也就是findViewById的參數(shù)
新建一個EventType類
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventType {
    Class listenerType();
    String listenerSetter();
    String methodName();
}
Target指定了EventType注解作用對象是注解澄峰,也就是注解的注解
Retention指定了EventType注解有效期直到運行時時期
listenerType用來指定點擊監(jiān)聽類型嫉沽,比如OnClickListener
listenerSetter用來指定設(shè)置點擊事件方法,比如setOnClickListener
methodName用來指定點擊事件發(fā)生后會回調(diào)的方法摊阀,比如onClick

MainActivity中添加注解

    @onClick({R.id.button})
    public void onclick(View view) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        switch (view.getId()) {
            case R.id.button:
                Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
                break;
        }
    }

記得注入onClick事件

  setContentView(R.layout.activity_main);
  Utils.injectView(this);
  Utils.injectEvent(this);

創(chuàng)建injectEvent方法

    public static void injectEvent(Activity activity) {
        if (null == activity) {
            return;
        }
        //獲取activity的.class對象
        Class<? extends Activity> activityClass = activity.getClass();
        // 獲取所有方法
        Method[] declaredMethods = activityClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
       //找到有@onClick注解的方法
            if (method.isAnnotationPresent(onClick.class)) {
                //得到注解類的對象
                onClick annotation = method.getAnnotation(onClick.class);
                //獲取控件id的集合
                int[] value = annotation.value();
                //獲取注解類中的注解
                EventType eventType = annotation.annotationType().getAnnotation(EventType.class);
               //得到注解類中的注解的屬性
                Class listenerType = eventType.listenerType();
                String listenerSetter = eventType.listenerSetter();
                String methodName = eventType.methodName();
                //創(chuàng)建InvocationHandler和動態(tài)代理(代理要實現(xiàn)listenerType耻蛇,這個例子就是處理onClick點擊事件)
                ProxyHandler proxyHandler = new ProxyHandler(activity);
                //得到實現(xiàn)類的接口對象(1:實現(xiàn)類的類加載器2:實現(xiàn)類的接口數(shù)組3:proxyHandler對象(當執(zhí)行接口的方法時會先執(zhí)行里面的invoke,再執(zhí)行實際方法胞此,實現(xiàn)動態(tài)代理)
                Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, proxyHandler);
                // 將Activity中被注解的方法傳至ProxyHandler 中
                proxyHandler.mapMethod(methodName, method);
                try {
                    for (int id : value) {
                        //找到Button
                        Method findViewByIdMethod = activityClass.getMethod("findViewById", int.class);
                        findViewByIdMethod.setAccessible(true);
                        View btn = (View) findViewByIdMethod.invoke(activity, id);
                        //根據(jù)listenerSetter方法名和listenerType方法參數(shù)找到method
                        Method listenerSetMethod = btn.getClass().getMethod(listenerSetter, listenerType);
                        listenerSetMethod.setAccessible(true);
                        //反射執(zhí)行setOnclickListener
                        listenerSetMethod.invoke(btn, listener);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

ProxyHandler代碼如下

public class ProxyHandler implements InvocationHandler {

    private WeakReference<Activity> mHandlerRef;

    private HashMap<String, Method> mMethodHashMap;

    public ProxyHandler(Activity activity) {
        mHandlerRef = new WeakReference<>(activity);
        mMethodHashMap = new HashMap<>();
    }

    public void mapMethod(String name, Method method) {
        mMethodHashMap.put(name, method);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Log.i("TAG", "method name = " + method.getName() + " and args = " + Arrays.toString(args));

        Object handler = mHandlerRef.get();

        if (null == handler) return null;

        String name = method.getName();

        //得到injectEvent中傳遞過來的被@onClick注解的方法
        Method realMethod = mMethodHashMap.get(name);
        if (null != realMethod){
       //反射執(zhí)行Activity中onClick方法
            return realMethod.invoke(handler, args);
        }

        return null;
    }
}

重點是動態(tài)代理。這里簡單分析一下ProxyHandler 類跃捣,它主要是用于動態(tài)代理漱牵。這里為什么要用動態(tài)代理呢!是因為當我們點擊按鈕時疚漆,會觸發(fā)onClick方法酣胀,而我們需要在onClick里面執(zhí)行我們注解的方法,所以必須對Activity動態(tài)代理娶聘。

Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, proxyHandler);

第一個參數(shù)為接口的加載器(本例中為Activity的接口View.OnclickListener)闻镶,第二個參數(shù)為Activity的接口數(shù)組(View.OnclickListener),第三個參數(shù)為ProxyHandler 對象丸升。返回一個實現(xiàn)類(View.OnclickListener)的對象铆农。由于在injectEvent中我們反射執(zhí)行了setOnClickListener,當用戶觸發(fā)點擊事件時狡耻,會執(zhí)行
接口中的方法(setOnClickListener的onClick方法)然后會執(zhí)行ProxyHandler 類中的invoke方法墩剖,然后反射得到Activity中被@onClick注解的方法即onClick方法猴凹,然后反射調(diào)用它,執(zhí)行注解中的方法岭皂。因此

    @onClick({R.id.button})
    public void onclick(View view) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        switch (view.getId()) {
            case R.id.button:
                Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
                break;
        }
    }

不管方法名是什么郊霎,當用戶點擊后,都會反射執(zhí)行對應(yīng)方法體

注解類對象

這次我們通過注解得到Animal 的對象


public class Animal implements Fly, Run {

    public static final String TAG = "ProxyTest";

    public Animal() {
    }

    @Override
    public void fly() {
        System.out.println("Animal fly");
    }

    @Override
    public void run() {
        System.out.println("Animal run");

    }
}

同理新建一個自定義注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
}

在MainActivity中進行注解

   @Inject
    Animal mAnimal;

然后注入

  setContentView(R.layout.activity_main);
  Utils.injectView(this);
  Utils.injectEvent(this);
  InjectUtils.inject(this);

InjectUtil

public class InjectUtils {
    public static void inject(Activity activity) {
        if (null == activity) {
            return;
        }
        //獲取activity的.class對象
        Class<? extends Activity> activityClass = activity.getClass();
        //        獲取所有成員變量集合
        Field[] declaredFields = activityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //找到有Inject注解的成員變量
            if (field.isAnnotationPresent(Inject.class)) {
                //得到成員變量的類型的字節(jié)碼文件
                Class<?> clazz = field.getType();
                Constructor con = null;
               try {
                    //得到無參構(gòu)造
                   con = clazz.getConstructor();
                    //獲取Animal其對象
                   Object instance = con.newInstance();
                    //賦值給field
                  field.set(activity,instance);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

本文中所有Demo的GitHub地址為https://github.com/zhonghongwen/Custom-Annotation

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末爷绘,一起剝皮案震驚了整個濱河市书劝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌土至,老刑警劉巖庄撮,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異毙籽,居然都是意外死亡洞斯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門坑赡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烙如,“玉大人,你說我怎么就攤上這事毅否⊙翘” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵螟加,是天一觀的道長徘溢。 經(jīng)常有香客問我,道長捆探,這世上最難降的妖魔是什么然爆? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮黍图,結(jié)果婚禮上曾雕,老公的妹妹穿的比我還像新娘。我一直安慰自己助被,他們只是感情好剖张,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著揩环,像睡著了一般搔弄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丰滑,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天顾犹,我揣著相機與錄音,去河邊找鬼。 笑死蹦渣,一個胖子當著我的面吹牛哄芜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播柬唯,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼认臊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锄奢?” 一聲冷哼從身側(cè)響起失晴,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拘央,沒想到半個月后涂屁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡灰伟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年拆又,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栏账。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡帖族,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挡爵,到底是詐尸還是另有隱情竖般,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布茶鹃,位于F島的核電站涣雕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闭翩。R本人自食惡果不足惜挣郭,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望男杈。 院中可真熱鬧丈屹,春花似錦、人聲如沸伶棒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肤无。三九已至,卻和暖如春骇钦,著一層夾襖步出監(jiān)牢的瞬間宛渐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留窥翩,地道東北人业岁。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像寇蚊,于是被迫代替她去往敵國和親笔时。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

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