1. 程式人生 > >Android面試題-註解框架實現原理

Android面試題-註解框架實現原理

本文配套視訊

原始碼分析相關面試題

與XMPP相關面試題

與效能優化相關面試題

與登入相關面試題

註解框架實現原理,手寫ButterKnife實現自己的註解框架

初級程式設計師使用別人的框架,中級程式設計師不僅會使用別人的框架還知道內部的實現原理,高階程式設計師則按需編寫自己的框架。新增該模組的目的就是想提交大家的逼格,讓大家養成一個動手編寫“自主智慧財產權”框架的意識。

一. 編寫 ButterKnife框架

業界比較出名的基於完全註解方式就可以進行 UI 繫結和事件繫結,無需 findViewById 和 setClickListener 等的IOC(Inverse Of Control 控制反轉,就是將 UI 的初始化和事件繫結的“權利”交給框架來完成)框架有:

ButterKnife使用如下:

會出現如下程式碼:

 @BindView(R.id.button01)
    Button mButton01;
    @BindView(R.id.button02)
    Button mButton02;
    @BindView(R.id.button03)
    Button mButton03;

@butterknife.OnClick({R.id.button01, R.id.button02, R.id.button03})
    public void onClick(View view) {
        switch (view.getId
()) { case R.id.button01: Toast.makeText(this, "butterknife-button01", Toast.LENGTH_SHORT).show(); break; case R.id.button02: Toast.makeText(this, "butterknife-button02", Toast.LENGTH_SHORT).show(); break; case R.id.button03: Toast.makeText
(this, "butterknife-button03", Toast.LENGTH_SHORT).show(); break; } }

點選每個按鈕會彈出響應的Toast,如下:

這個就是ButterKnife的用法。

接下來,我們開始編寫自己的框架實現 findViewById 和 setOnClickListener 功能。

  1. 編寫自定義註解類 ViewInject 和 Click;

ViewInject 註解類用於新增在 Filed 上。Click 註解類用於新增到 Method 上。

【檔案】ViewInject.java

/**
* @Retention 用於宣告該註解生效的生命週期,有三個列舉值可以選擇<br>
  * 1. RetentionPolicy.SOURCE 註釋只保留在原始碼上面,編譯成class的時候自動被編譯器抹除
 * 2. RetentionPolicy.CLASS 註釋只保留到位元組碼上面,VM載入位元組碼時自動抹除
 * 3. RetentionPolicy.RUNTIME 註釋永久保留,可以被VM載入時載入到記憶體中
 * 注意:由於我們的目的是想在VM執行時對Filed上的該註解進行反射操作,因此Retention值必須設定為RUNTIME
 *
 * @Target 用於指定該註解可以宣告在哪些成員上面,常見的值有FIELD和Method,
      由於我們的當前註解類是想宣告在Filed上面
 * 因此這裡設定為ElementType.FIELD。
 * 注意:如果@Target值不設定,則預設可以新增到任何元素上,不推薦這麼寫。
 *
 * @interface 是宣告註解類的組合關鍵字。
 */

@Target({java.lang.annotation.ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
    public abstract int value();
}

【檔案】Click.java

@Target({java.lang.annotation.ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick
{
    public abstract int[] value();
}

編寫核心方法 ViewUtilsTest.inject(Ativity);

public class ViewUtilsTest {
    public static void inject(final Activity activity)
    {
        /**
         * 通過位元組碼獲取activity類中所有的欄位,在獲取Field的時候一定要使用
         * getDeclaredFields(),
         * 因為只有該方法才能獲取到任何許可權修飾的Filed,包括私有的。
         */

        Class clazz = activity.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        //一個Activity中可能有多個Field,因此遍歷。
        for (int i = 0; i < declaredFields.length; i++) {
            Field field = declaredFields[i];
            //設定為可訪問,暴力反射,就算是私有的也能訪問到
            field.setAccessible(true);
         //獲取到欄位上面的註解物件
         ViewInject annotation = (ViewInject)field.getAnnotation(ViewInject.class);
            //一定對annotation是否等於null進行判斷,因為並不是所有Filed上都有我們想要的註解
            if (annotation == null)
            {
                continue;
            }
            //獲取註解中的值
            int id = annotation.value();
            //獲取控制元件
            View view = activity.findViewById(id);

            try
            {
                //將該控制元件設定給field物件
                field.set(activity, view);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }
        //獲取所有的方法(私有方法也可以獲取到)
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (int i = 0; i < declaredMethods.length; i++) {
            final Method method = declaredMethods[i];
            //獲取方法上面的註解
            OnClick annotation = (OnClick)method.getAnnotation(OnClick.class);
            if (annotation == null) {
                //如果該方法上沒有註解,迴圈下一個
                continue;
            }
            //獲取註解中的資料,因為可以給多個button繫結點選事件,因此定義註解類時使用的是int[]作為資料型別。
            int[] value = annotation.value();
            for (int j = 0; j < value.length; j++) {
                int id = value[j];

                final View button = activity.findViewById(id);

                button.setOnClickListener(new View.OnClickListener()
                {
                    public void onClick(View v)
                    {
                        try
                        {
                            //反射呼叫使用者指定的方法
                           method.invoke(activity,button);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    }
}

咱們自己的註解框架就實現好了,效果如下:

  • 歡迎關注微信公眾號,長期推薦技術文章和技術視訊