Android 自定義注解

??我們在項(xiàng)目中經(jīng)常用到注解,比如原生自帶的@Override、@NonNull等 ,等三方框架ButterKnife中@BindView、@OnClick等。用的久了,就想著去看看它是怎么弄出來的,因自定義注解的使用場景比較少,故此次咱先做下簡單了解。

注解的作用:
1.和編譯器一起給你一些提示警告信息。
2.配合一些ide 可以更加方便快捷 安全有效的編寫java代碼。谷歌出的support-annotations這個(gè)庫 就是主要干這個(gè)的。
3.和反射一起 提供一些類似于spring 可配置的功能,方便簡潔。

運(yùn)行時(shí)注解與編譯時(shí)注解的區(qū)別是什么呢?
a)保留階段不同。運(yùn)行時(shí)注解保留到運(yùn)行時(shí),可在運(yùn)行時(shí)訪問。而編譯時(shí)注解保留到編譯時(shí),運(yùn)行時(shí)無法訪問。
b)原理不同。運(yùn)行時(shí)注解是Java反射機(jī)制,而編譯時(shí)注解通過APT、AbstractProcessor。
c)性能不同。運(yùn)行時(shí)注解由于使用Java反射,因此對(duì)性能上有影響。編譯時(shí)注解對(duì)性能沒影響。這也是為什么ButterKnife從運(yùn)行時(shí)切換到了編譯時(shí)的原因。
d)產(chǎn)物不同。運(yùn)行時(shí)注解只需自定義注解處理器即可,不會(huì)產(chǎn)生其他文件。而編譯時(shí)注解通常會(huì)產(chǎn)生新的Java源文件。

??下面來試試實(shí)現(xiàn)ButterKnife中的BindView和OnClick。
先定義下BindView(ViewInject)和OnClick的注解,寫的是運(yùn)行時(shí)注解:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Retention 用于聲明該注解生效的生命周期,有三個(gè)枚舉值可以選擇<br>
 * 1. RetentionPolicy.SOURCE 注釋只保留在源碼上面,編譯成class的時(shí)候自動(dòng)被編譯器抹除
 * 2. RetentionPolicy.CLASS 注釋只保留到字節(jié)碼上面,VM加載字節(jié)碼時(shí)自動(dòng)抹除
 * 3. RetentionPolicy.RUNTIME 注釋永久保留,可以被VM加載時(shí)加載到內(nèi)存中
 * 注意:由于我們的目的是想在VM運(yùn)行時(shí)對(duì)Filed上的該注解進(jìn)行反射操作,因此Retention值必須設(shè)置為RUNTIME
 * @Target 用于指定該注解可以聲明在哪些成員上面,常見的值有FIELD和Method,
 * 由于我們的當(dāng)前注解類是想聲明在Filed上面
 * 因此這里設(shè)置為ElementType.FIELD。
 * 注意:如果@Target值不設(shè)置,則默認(rèn)可以添加到任何元素上,不推薦這么寫。
 * @interface 是聲明注解類的組合關(guān)鍵字。
 */

@Target({java.lang.annotation.ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {//通過@interface 來定義注解
    public abstract int value();
}
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

定義好注解后,再寫個(gè)的類去處理findViewById和OnClick的操作:

import android.app.Activity;
import android.view.View;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ViewUtilsTest {
    public static void inject(final Activity activity) {
        /**
         * 通過字節(jié)碼獲取activity類中所有的字段,在獲取Field的時(shí)候一定要使用
         * getDeclaredFields(),
         * 因?yàn)橹挥性摲椒ú拍塬@取到任何權(quán)限修飾的Filed,包括私有的。
         */
        Class clazz = activity.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        //一個(gè)Activity中可能有多個(gè)Field,因此遍歷。
        for (int i = 0; i < declaredFields.length; i++) {
            Field field = declaredFields[i];
            //設(shè)置為可訪問,暴力反射,就算是私有的也能訪問到
            field.setAccessible(true);
            //獲取到字段上面的注解對(duì)象
            ViewInject annotation = (ViewInject) field.getAnnotation(ViewInject.class);
            //一定對(duì)annotation是否等于null進(jìn)行判斷,因?yàn)椴⒉皇撬蠪iled上都有我們想要的注解
            if (annotation == null) {
                continue;
            }
            //獲取注解中的值
            int id = annotation.value();
            //獲取控件
            View view = activity.findViewById(id);
            try {
                //將該控件設(shè)置給field對(duì)象
                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) {
                //如果該方法上沒有注解,循環(huán)下一個(gè)
                continue;
            }
            //獲取注解中的數(shù)據(jù),因?yàn)榭梢越o多個(gè)button綁定點(diǎn)擊事件,因此定義注解類時(shí)使用的是int[]作為數(shù)據(jù)類型。
            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 {
                            //反射調(diào)用用戶指定的方法
                            method.invoke(activity, button);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    }
}

代碼中的意思很容易看懂,就是把界面activity傳過來,再去遍歷定義好的字段,取出注入的id,最后仍然是咱們經(jīng)常用的findViewById。點(diǎn)擊監(jiān)聽也是如此。然后,就結(jié)束了。。就可以用了,像ButterKnife一樣用就行:

public class TestActivity extends AppCompatActivity {
    @ViewInject(R.id.btn_test)
    Button button;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test1);
        ViewUtilsTest.inject(this);
    }

    @OnClick(R.id.btn_test)
    public void onClick(View v) {
        if (v.getId() == R.id.btn_test) {
            Toast.makeText(TestActivity.this,"test111111",Toast.LENGTH_SHORT).show();
        }
    }    
}

貌似沒問題了,可以成功的toast出來。但這只是最簡單的了解,想再多了解點(diǎn)的,可以看看這個(gè):http://www.itdecent.cn/p/806e3500fec4

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

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

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