經過自定義Annotation簡單實現ButterKnife註解功能

前言

經過對《Java Annotation簡介》的簡單介紹,咱們應該瞭解到了Java 中自定義註解的使用方式。實際上現有市場上已經出現了不少利用註解來完成依賴注入工做的庫,例如ButterKnife。java

ButterKnife 簡單來說就是經過註解的方式,簡化代碼中View變量與XML資源綁定的流程的工具。 
前面提到過,元註解@Retention有三種取值:ide

  • RetentionPoicy.SOURCE:在源文件中有效,在class文件中失效(即只在源文件保留);
  • RetentionPoicy.CLASS:在class文件中有效(即class保留);
  • RetentionPoicy.RUNTIME:在運行時有效(即運行時保留)。

ButterKnife使用的是RetentionPoicy.CLASS級別的註解,爲了簡單直觀,咱們這裏使用RUNTIME註解來模仿,固然由於須要經過反射得到元素對象,會損失運行時性能。工具

過程

首先咱們來看一個簡單的使用場景:性能

...

@BindView(R.id.title_text)
TextView mTitleTextView;

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

    ButterKnife.inject(this);
    mTitleTextView.setText("test");

}
...

在onCreate方法中使用如下方法後,咱們已經得到了該控件的引用,能夠實現控件方法的各類調用,前提只須要在聲明控件變量時添加BindView註解,並設置對應的資源變量。this

ButterKnife.inject(this)

咱們模仿這樣一個流程,首先定義一個名爲BindView的註解:spa

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface BindView {
    // 惟一的value是要指定的R.id變量
    int value();
}

在這裏咱們定義註解保留在運行時環境中,而且這個註解是應用在類中的FIELD上。.net

下面咱們就能在Activity中使用這個註解了(暫時不起做用):code

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.textview_a)
    TextView textViewA;

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

如今咱們來實現註解的功能。起一個名爲PoorKnife的類,聲明一個static方法inject,而且接收一個Activity類型的參數:對象

public class PoorKnife {
    public static void inject(Activity activity) {
        try {
         // 獲取類變量
        Class contextClass = Class.forName(activity.getClass().getCanonicalName());
        // 遍歷類中全部Field
        for (Field field : contextClass.getDeclaredFields()) {
            // 若是包含註解
            if (field.isAnnotationPresent(BindView.class)) {
                Log.d(TAG, field.getName() + " has annotation");

                // 獲得註解值
                int rId = field.getAnnotation(BindView.class).value();
                String type = field.getType().toString();
                if (type.endsWith("TextView")) {
                    // 設置field可訪問,並將經過set方法賦值view
                    field.setAccessible(true);
                    field.set(activity, activity.findViewById(rId));
                }
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
        }
     }
}

inject方法中的操做主要用到反射機制: blog

  1. 經過參數activity得到類變量 
  2. 遍歷類中全部FIELD(成員變量),並檢查是否被標註BindView註解 
  3. 如有註解,則獲取註解給定的int值,並經過反射將findViewById獲得的view賦值給這個變量 

接下來咱們再次重寫onCreate方法,此時才完成了控件對象的初始化工做:

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

    PoorKnife.inject(this);

    textViewA.setText("PoorKnife succeed!!!");
}

因爲是運行時經過反射調用,所以效率相對較低,同理註解 onClick 按照這個流程也能夠很方便的實現。

下一步咱們講講《註解處理器Annotation Processor 的概念及使用》

相關文章
相關標籤/搜索