Android 使用注解和反射自制簡單版的butternife

一.Java反射機制。

1.反射機制的定義。

Java反射機制是指在運行狀態(tài)中,
對于任意一個類,都能知道這個類的所有屬性和方法;
對于任何一個對象,都能夠調(diào)用它的任何一個方法和屬性;
這樣動態(tài)獲取新的以及動態(tài)調(diào)用對象方法的功能就叫做反射。

2.反射機制的作用。

  • 在運行時判斷任意一個對象所屬的類;

  • 在運行時構(gòu)造任意一個類的對象;

  • 在運行時判斷任意一個類所具有的成員變量和方法;

  • 在運行時調(diào)用任意一個對象的方法;

3.獲取字節(jié)碼。

1. 獲取字節(jié)碼

方式 解析
Class clazz1 = Class.forName("全限定類名"); 通過Class類中的靜態(tài)方法forName,直接獲取到一個類的字節(jié)碼文件對象,此時該類還是源文件階段,并沒有變?yōu)樽止?jié)碼文件。
Class clazz2 = Person.class; 當類被加載成.class文件時,此時Person類變成了.class,在獲取該字節(jié)碼文件對象,也就是獲取自己, 該類處于字節(jié)碼階段。
Class clazz3 = p.getClass(); 通過類的實例獲取該類的字節(jié)碼文件對象,該類處于創(chuàng)建對象階段

2. 實例化字節(jié)碼
(1.) 無參構(gòu)造。

 Class<?> aClass = Class.forName("com.demo.myapplication.Person");
  Person person = (Person) aClass.newInstance();//無參構(gòu)造

(2.) 有參構(gòu)造。

   Class<?> aClass = Class.forName("com.demo.myapplication.Person");
   Constructor<?> constructor = aClass.getConstructor(int.class, String.class);
   Person xiaoming = (Person) constructor.newInstance(10, "小明");

4.反射的實現(xiàn)。

例如有一個類:

package com.yousheng.demo;

public class Person {
    //私有屬性
    private int age = 19;
    //公共屬性
    public String name = "小明";

    //私有方法
    public String getName() {
        return name;
    }

    //私有方法
    private String getNameAndAge(String name, int age) {
        this.name = name;
        this.age = age;
        return "姓名為:" + name + "年齡為" + age;
    }
}

測試調(diào)用私有屬性和私有方法。

  try {
            Class<?> aClass = Class.forName("com.yousheng.myapplication.Person");
            Person person = (Person) aClass.newInstance();
            //訪問公共屬性
            System.out.println("訪問公共屬性-->"+person.name);

            //訪問私有屬性
            Field ageField = aClass.getDeclaredField("age");
            //允許訪問私有字段
            ageField.setAccessible(true);
            //獲得私有字段值
            int age= (int) (ageField).get(person);
            System.out.println("訪問私有屬性-->"+age);

            //訪問私有方法.
            //name為方法名
            //String.class 是第一個參數(shù)的類型
            //int.class 是第二個參數(shù)的類型
            Method getNameAndAge = aClass.getDeclaredMethod("getNameAndAge",String.class,int.class);
            //允許公共訪問
            getNameAndAge.setAccessible(true);
            //調(diào)用方法
            String result = (String) getNameAndAge.invoke(person, "比爾蓋茨", 52);
            System.out.println("訪問私有方法-->"+result);

        } catch (Exception e) {
            e.printStackTrace();
        }

結(jié)果為:

    訪問公共屬性-->小明
    訪問私有屬性-->19
    訪問私有方法-->姓名為:比爾蓋茨年齡為52

二.java注解。

1.系統(tǒng)自帶的注解。

例如:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

2.元注解。

給自定義注解使用的注解就是元注解。
java中元注解有四個: @Retention @Target @Document @Inherited;
1. @Target
表示該注解可以用于什么地方

  • ElementType.TYPE:作用于接口、類、枚舉、注解,可以給一個類型進行注解,比如類、接口、枚舉;
  • ElementType.FIELD:作用于成員變量(字段、枚舉的常量),可以給屬性進行注解;
  • ElementType.METHOD:作用于方法,可以給方法進行注解;
  • ElementType.PARAMETER:作用于方法的參數(shù),可以給一個方法內(nèi)的參數(shù)進行注解;
  • ElementType.CONSTRUCTOR:作用于構(gòu)造函數(shù),可以給構(gòu)造方法進行注解;
  • ElementType.LOCAL_VARIABLE:作用于局部變量,可以給局部變量進行注解;
  • ElementType.ANNOTATION_TYPE:作用于Annotation,可以給一個注解進行注解。
  • ElementType.PACKAGE:作用于包名,可以給一個包進行注解;
  • ElementType.TYPE_PARAMETER:java8新增,但無法訪問到;
  • ElementType.TYPE_USE:java8新增,但無法訪問到;

2. @Retention

表示需要在什么級別保存該注解信息??蛇x的RetentionPolicy參數(shù)包括:

  • SOURCE:只在*.java源文件的時候有效,編譯成class就沒用了;

  • CLASS:只在.java或者.class中的文件有效,但是在運行時無效;

  • RUNTIME:VM將在運行期間保留注解,因此可以通過反射機制讀取注解的信息。包含以上兩種,并且運行時也會有效果,一般我們都會選用該參數(shù)。

3. @Document

將注解包含在Javadoc中

4. @Inherited

允許子類繼承父類中的注解

3. 注解的屬性

注解的屬性也叫做成員變量。注解只有成員變量,沒有方法。
注解的成員變量在注解的定義中以“無形參的方法”形式來聲明,其方法名定義了該成員變量的名字,其返回值定義了該成員變量的類型。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    int id();
    String name();
}

上面代碼定義了 Test 這個注解中擁有 id 和 name兩個屬性。在使用的時候,我們應該給它們進行賦值。

@TestAnnotation(id=3,name="hello annotation")
public class Test {
}

賦值的方式是在注解的括號內(nèi)以 value=”” 形式,多個屬性之前用 ,隔開。
在注解中一般會有一些元素以表示某些值。注解的元素看起來就像接口的方法,唯一的區(qū)別在于可以為其制定默認值。沒有元素的注解稱為標記注解。
注解中屬性可以有默認值,默認值需要用 default 關(guān)鍵值指定。比如:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    public int id() default -1;
    public String name() default "Hello";
}

有默認值的話就可以不用進行賦值了。

@Test ()
public class Test {}

元素不能有不確定的值,即要么有默認值,要么在使用注解的時候提供元素的值。而且元素不能使用null作為默認值

另外,還有一種情況。如果一個注解內(nèi)僅僅只有一個名字為 value 的屬性時,應用這個注解時可以直接接屬性值填寫到括號內(nèi)。

public @interface Test {
    String value();
}

上面代碼中,Test 這個注解只有 value 這個屬性。所以可以這樣應用。

@Test ("hello")
int a;

這和下面的效果是一樣的

@Test (value="hello")
int a;

最后,還需要注意的一種情況是一個注解沒有任何屬性。比如

public @interface Test {}

那么在應用這個注解的時候,括號都可以省略。

4、解析提取注解參數(shù)

Java通過反射機制獲取類、方法、屬性上的注解,因此java.lang.reflect提供AnnotationElement支持注解,主要方法如下:

方法 說明
boolean is AnnotationPresent(Class<?extends Annotation> annotationClass) 判斷該元素是否被annotationClass注解修飾
<T extends Annotation> T getAnnotation(Class<T> annotationClass) 獲取 該元素上annotationClass類型的注解,如果沒有返回null
Annotation[] getAnnotations() 返回該元素上所有的注解
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) 返回該元素上指定類型所有的注解
Annotation[] getDeclaredAnnotations() 返回直接修飾該元素的所有注解
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) 返回直接修飾該元素的所有注解

5.結(jié)合注解和反射寫一個簡單的版本的butternife。

運行時注解是通過反射來實現(xiàn)的,這種方式的效率會受到一定的影響,因此現(xiàn)在大多數(shù)的開源注解框架都是采用編譯時注解的方式實現(xiàn)的,這種方式是在編譯的時候生成所需的代碼,不會影響運行的效率

方式一(運行時(Runtime)通過反射機制運行處理的注解):
  1. 先自定義兩個注解。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)//運行時注解
public @interface FindViewById {
    //使用value命名,則使用的時候可以忽略,否則使用時就得把參數(shù)名加上
    int value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SetOnClickListener {
    int id();
    String methodName();
}
  1. 創(chuàng)建注解解析器。
public class AnnotationParse {
    public static void parse(final Activity activity) {
        try {
            //反射獲取Class對象
            Class<? extends Activity> aClass = activity.getClass();
            //獲取所有的字段
            Field[] declaredFields = aClass.getDeclaredFields();
            //遍歷字段
            for (Field declaredField : declaredFields) {
                //獲取字段的所有的注解信息
                Annotation[] annotations = declaredField.getAnnotations();
                //遍歷注解信息
                for (Annotation annotation : annotations) {
                    if (annotation instanceof FindViewById) {
                        //注解類型為 FindViewById
                        FindViewById findViewByIdAnnotation = declaredField.getAnnotation(FindViewById.class);
                        //設置可修改該字段
                        declaredField.setAccessible(true);
                        //獲取該注解value所對的值
                        int viewID = findViewByIdAnnotation.value();

                        View view = activity.findViewById(viewID);

                        if (view != null)
                            declaredField.set(activity, view);
                        else
                            throw new Exception("不能找到該View" + declaredField.getName() + ".");

                    } else if (annotation instanceof SetOnClickListener) {
                        //注解類型為 SetOnClickListener
                        SetOnClickListener clickAnnotation = declaredField.getAnnotation(SetOnClickListener.class);
                        //設置可修改該字段
                        declaredField.setAccessible(true);
                        //獲取該注解id所對的值
                        int viewId = clickAnnotation.id();
                        //獲取該注解的methodName所對應的值
                        String methodName = clickAnnotation.methodName();
                        //獲取view
                        View view = (View) declaredField.get(activity);
                        if (view == null) {
                            // 如果對象為空,則重新查找對象
                            view = activity.findViewById(viewId);
                            if (view != null)
                                declaredField.set(activity, view);
                            else
                                throw new Exception("");
                        }
                        //獲取目標對象的所有方法集
                        Method[] declaredMethods = aClass.getDeclaredMethods();
                        //遍歷方法集
                        for (final Method declaredMethod : declaredMethods) {
                            //判斷方法名是否和注解的methodName相同
                            if (declaredMethod.getName().equals(methodName)) {
                                //設置點擊事件
                                view.setOnClickListener(new View.OnClickListener() {
                                    @Override
                                    public void onClick(View v) {
                                        try {
                                            //調(diào)用方法
                                            declaredMethod.invoke(activity);
                                        } catch (IllegalAccessException e) {
                                            e.printStackTrace();
                                        } catch (InvocationTargetException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                });
                                break;
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
  1. 調(diào)用
    mian_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/txt_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/colorPrimary"/>

    <Button
        android:id="@+id/btn_main"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="click me"/>

</LinearLayout>

MainActivity.java

 @FindViewById(R.id.txt_main)
    private TextView mTxtMain;
    @SetOnClickListener(id = R.id.btn_main, methodName = "onClick")
    private Button mBtnMain;

    /**
     * 點擊事件
     */
    public void onClick() {
        Toast.makeText(this, "Hello world", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AnnotationParse.parse(this);
        mTxtMain.setText("測試代碼");
}
方式二(運行時(Runtime)通過反射機制運行處理的注解):

部分內(nèi)容節(jié)選自:
java注解-最通俗易懂的講解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容