Butterknife之從反射到注解罢维,再到實現(xiàn)簡單的butterknife效果。了解一下丙挽!

butterknife是現(xiàn)在使用的較多的一個庫了肺孵,它讓我們的工作減少了相當(dāng)一部分的量,今天就讓我們一起來走近它 颜阐,實現(xiàn)我們自己的一個簡單的類似效果平窘,讓它不再顯得那么神秘吧!

談及butterknife的原理凳怨,粗略的說瑰艘,它用到了反射和注解這兩個方面的知識是鬼。那么我們想實現(xiàn)簡單的butterknife效果,我們就得首先知道反射和注解是怎么回事紫新,ok均蜜,接下來我們先來看看反射吧。
JAVA反射機(jī)制是在運行狀態(tài)中芒率,對于任意一個類囤耳,都能夠知道這個類的所有屬性和方法;對于任意一個對象敲董,都能夠調(diào)用它的任意一個方法和屬性;這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機(jī)制慰安。
要想解剖一個類,必須先要獲取到該類的字節(jié)碼文件對象腋寨。而解剖使用的就是Class類中的方法.所以先要獲取到每一個字節(jié)碼文件對應(yīng)的Class類型的對象.
上述就是對反射的理解,有了文字含義化焕,我們再來寫一寫小案例具體看看吧:

比如我們這有個User類萄窜,想要獲取到這個類中的成員變量

public class User {
    private String name;
    public int age;
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name == null ? "" : name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void like(String type){
        Log.e("aaa","喜歡的東西是:----"+type);
    }
}

那么我們想要通過反射來獲得該類中的屬性和方法:該怎么做呢?
從上述文字中撒桨,我們可以看到查刻,想要解剖一個類:
第一步:就是先要獲取到每一個字節(jié)碼文件對應(yīng)的Class類型的對象.
這個對象有三種方式可以獲取:

//第一種  類名.class
Class class1= User.class;
//第二種  通過forName(全類名)
Class clazz2 = Class.forName("com.example.yinl.simplebutterknife.User");
// 第三種 通過對象的getClass()
User user = new User("zhangsan", 10);
Class clazz = user.getClass();
 
第二步:通過獲得的對象來拿到成員屬性
 //這里注意  當(dāng)屬性為private的時候  需要使用getDeclaredField()
            //如果是public  則也可使用getField()
            Field name=clazz.getDeclaredField("name");
            Field age=clazz.getField("age");
            //private屬性值 還需設(shè)置 允許暴力反射
            name.setAccessible(true);
            //獲得屬性值  從user對象中獲取
            String uName= (String) name.get(user);
            int uAge=age.getInt(user);

            //修改屬性值  修改的對象  修改值
            name.set(user,"yinll");
            age.set(user,12);

//獲得類中的方法
        //一:獲得所有方法
        Method[] methods=clazz.getMethods();
        for(Method method:methods){
           //打印一下方法  做個測試
            Log.e("aaa","方法-----"+method.getName());
        }

        //二:獲得某一個方法  方法名  方法類型
            Method method=clazz.getMethod("like",String.class);
            //調(diào)用該方法(在user對象中調(diào)用該方法凤类,傳入?yún)?shù))
            method.invoke(user, "水果茶");

通過以上幾個步驟穗泵,我們就能成功的獲取到我們想要知道的類中的屬性和方法,并對其進(jìn)行修改和調(diào)用了谜疤。有木有很簡單佃延?

學(xué)會了反射之后,我們接下來看看 注解:
注解(Annotation)夷磕,也叫元數(shù)據(jù)履肃。一種代碼級別的說明。它是JDK1.5及以后版本引入的一個特性坐桩,與類尺棋、接口、枚舉是在同一個層次绵跷。它可以聲明在包膘螟、類、字段碾局、方法萍鲸、局部變量、方法參數(shù)等的前面擦俐,用來對這些元素進(jìn)行說明脊阴,注釋。它可以用于創(chuàng)建文檔,跟蹤代碼中的依賴性嘿期,甚至執(zhí)行基本編譯時檢查品擎。注解是以‘@注解名’在代碼中存在的,根據(jù)注解參數(shù)的個數(shù)备徐,我們可以將注解分為:標(biāo)記注解萄传、單值注解、完整注解三類蜜猾。它們都不會直接影響到程序的語義秀菱,只是作為注解(標(biāo)識)存在,我們可以通過[反射機(jī)制]編程實現(xiàn)對這些元數(shù)據(jù)(用來描述數(shù)據(jù)的數(shù)據(jù))的訪問蹭睡。另外衍菱,你可以在編譯時選擇代碼里的注解是否只存在于源代碼級,或者它也能在class文件肩豁、或者運行時中出現(xiàn)(SOURCE/CLASS/RUNTIME)脊串。

首先如何創(chuàng)建注解:定義一個類 將其設(shè)置為@interface。下方是我定義的一個注解:

  • 注解:我們需要注意的是清钥,定義注解時我們需要給其設(shè)置 @Retention
    和 @Target , 具體使用情況詳細(xì)見下方代碼
//表示當(dāng)前注解存在于字節(jié)碼中琼锋,當(dāng)源碼被編譯成字節(jié)碼時,注解不會被清除
//在類加載的時候丟棄祟昭。在字節(jié)碼文件的處理中有用缕坎。注解默認(rèn)使用這種方式。
//@Retention(RetentionPolicy.CLASS)
//表示當(dāng)前注解存在于源碼中篡悟,當(dāng)源碼別編譯成字節(jié)碼時念赶,注解會被清除
//在編譯階段丟棄。這些注解在編譯結(jié)束之后就不再有任何意義恰力,所以它們不會寫入字節(jié)碼
//@Retention(RetentionPolicy.SOURCE)
//表示當(dāng)前注解使用在方法上
//@Target(ElementType.METHOD)
//用于描述類叉谜、接口或enum聲明
//@Target(ElementType.TYPE)

//表示當(dāng)前注解存在于虛擬機(jī)
//始終不會丟棄,運行期也保留該注解踩萎,因此可以使用反射機(jī)制讀取該注解的信息停局。我們自定義的注解通常使用這種方式。
@Retention(RetentionPolicy.RUNTIME)
//表示當(dāng)前注解使用在屬性上
@Target(ElementType.FIELD)
public @interface BindView {

    // 如果注解中只有一個屬性香府,可以直接命名為“value”董栽,使用時無需再標(biāo)明屬性名。
    //@BindView(R.id.y)
//    int value();
    //如果是其他值 比如
    int age();
    String name();
    //則在使用時 需要表明屬性名 如 @BindView(age=18,name="yinl")
}

定義好注解之后 我們就可以在類中來使用了:比如我們新建一個Test類企孩,看看如何在這個類中來使用我們的注解

public class Test {

    /**
     * 使用注解  age和name在注解中有定義
     * 如果注解中只有一個屬性锭碳,可以直接命名為“value”  
     * 此時 使用@BindView時不需要像下方一樣知名age或者name屬性
     * 直接@BindView(20)即可,@BindView(R.id.xx)就是這樣勿璃,熟悉吧
     */
   
    @BindView(age=18,name="yinl")
    private String name ;
    private int age ;

    @Override
    public String toString(){
        return "name:"+name+",age:"+age;
    }
}

那到了這里擒抛,我們又如何去獲取注解上的值呢推汽?這時候我們上面學(xué)到的反射就有用啦!這里我們再新建一個類歧沪,就取名叫做Butterknife吧歹撒,我們來模仿一下:在類中我們也來一個bind方法,我們將我們需要解剖的類傳入到這個類中進(jìn)行解析诊胞,獲取到我們想要的數(shù)據(jù)(中間除了上述說道的反射只是外暖夭,需要注意的就是我們怎么樣來拿到注解?)

public class Butterknife {
   //這里傳入需要解析的對象
    public static void bind(Test test) throws NoSuchFieldException, 
    IllegalAccessException {

        //獲得字節(jié)碼對象
        Class c= test.getClass();
        //獲得屬性
        Field name=c.getDeclaredField("name");
        Field age=c.getDeclaredField("age");
        //允許暴力反射
        name.setAccessible(true);
        age.setAccessible(true);

        /**
         * 獲取注解  這里對應(yīng)的是我們自定義的注解類撵孤,一定不要搞錯了  一一對應(yīng)的哦
         *  比如我們這里用到的是上方定義的BindView 注解迈着,那我們需要獲取到的注解對象 
          * 就是BindView ,getAnnotation(BindView.class)中傳入的參數(shù)自然也就是 
          * BindView 
         */
        BindView bindView=name.getAnnotation(BindView.class);
        if(bindView != null){
          //如果注解不是空 則可以獲取注解值
            String uName=bindView.name();
            int uAge=bindView.age();

          //然后將值設(shè)置到我們的對象上
            name.set(test,uName);
            age.set(test,uAge);
        }else{
            //注解為空
        }
    }
}

//接下來我們在activity中來調(diào)用看看:

//先定義對象
 Test t=new Test();
        try {
            //我們的Butterknife用起來  有木有很順手邪码,很hi裕菠。寫起來都是一個感覺
            Butterknife.bind(t);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        //這時候我們可以來看看注解上的值是否成功的傳遞到了對象中
        Toast.makeText(this,t.toString(),Toast.LENGTH_SHORT).show();

上方的結(jié)果經(jīng)測試,是ok的霞扬,那么到這里位置糕韧,反射和注解我們就都了解啦枫振,簡單中藏著奧妙喻圃,舒服。

接下來就是重頭戲啦粪滤,自己實現(xiàn)一個簡單的類似于Butterknife的功能,廢話不多說斧拍,動起來:
首先我們實現(xiàn)通過@BindView(R.id.xx),實現(xiàn)獲取控件的實例,并得到控件的值(省去findviewbyid)
第一步:先定義BindView注解類

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
    // 如果注解中只有一個屬性杖小,可以直接命名為“value”肆汹,使用時無需再標(biāo)明屬性名。
    //@BindView(R.id.y)
    int value();
}

//這里可以看看跟上方BindView類中的區(qū)別予权,加深理解

第二步:自定義Butterknife類昂勉,在類中來獲取我們需要的值,這里與上方不同的一點就是我們傳入的對象應(yīng)該是activity.另外就是大家可以多注意這里面是如何來找到我們的控件扫腺,做到不需要我們在activity中findviewbyid的岗照。

public class Butterknife {
    public static void bind(Activity activity) {
       try {
            bindView(activity);       
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 綁定視圖view
     */
    private static void bindView(Activity activity) throws IllegalAccessException {
        //獲得字節(jié)碼
        Class clazz=activity.getClass();
        //獲得activity中的所有變量
        Field[] fields=clazz.getDeclaredFields();
        //循環(huán)獲取
        for(Field field:fields){
          //允許暴力反射
            field.setAccessible(true);
            //獲取變量上加的注解
            BindView bindView=field.getAnnotation(BindView.class);
            if(bindView != null){
                //注解不為空的情況下  獲取注解的值  這里實際上是找到當(dāng)前控件對應(yīng)的ID
                int id=bindView.value();
                //通過id來獲取控件
                View view=activity.findViewById(id);
                //將控件的值賦值給變量
                field.set(activity,view);
            }else{
            }
        }
    }
}

//到了這里我們就可以在activity中調(diào)用了:

    Butterknife.bind(this);
   //輸出下是否獲得到了控件的值
   Toast.makeText(this,textView1.getText().toString(),Toast.LENGTH_SHORT).show();

那么這里也可以成功得到textView1這個控件的值,這樣我們就成功了一 半笆环,實現(xiàn)了這種簡單的控件綁定效果攒至,而不需要通過findviewbyid來獲取控件實例了;

下面我們再來實現(xiàn) butterknife綁定點擊事件的效果(就是butterknife中的@OnClick(R.id.xx)):
首先同樣的我們這里 新建一個針對點擊事件的注解類

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyClick {
    int value();
}

接下來我們就該思考怎么通過這個注解來設(shè)置它的點擊事件:我們想想上方的思路躁劣,肯定也是通過反射來得到當(dāng)前控件的id迫吐,然后通過在我們自定義的Butterknfe封裝類中獲得該id的控件實例,有了控件實例账忘,這時候我們是不是就可以給這個獲得的實例對象添加點擊事件監(jiān)聽啦志膀?

所以我們回到Butterknfe類中熙宇,加入一個bindClick方法(記得bind()方法中加入調(diào)用):綁定我們的監(jiān)聽事件:

/**
     * 點擊事件
     */
    private static void bindClick(final Activity activity){
        Class<? extends Activity> c=activity.getClass();
        //獲得方法
        Method[] methods=c.getDeclaredMethods();
        for(final Method method:methods){
            //允許暴力反射
            method.setAccessible(true);
            //獲得注解
            MyClick view=  method.getAnnotation(MyClick.class);
            if(view != null){
                //獲得控件id
                int id=view.value();
                View v=activity.findViewById(id);
                //點擊事件
                v.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        try {

                            //調(diào)用方法  這里方法沒帶參數(shù) 所以不用傳遞
                            method.invoke(activity);

                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }else{
            }
        }
    }

這樣就成功的設(shè)置了監(jiān)聽事件啦,我們看看activity中的使用:同樣是跟我們熟知的Butterknife是同樣的使用方式:

 /**
     * 自定義 點擊事件注解
     */
    @MyClick(R.id.button)
    public void onClick(){
        Log.e("aaa","onClick");
        Toast.makeText(ButterknifeActivity.this,"點擊了按 
                                     鈕",Toast.LENGTH_SHORT).show();
    }

經(jīng)測試一切正常梧却,大功告成奇颠。

一路走下來是不是發(fā)現(xiàn)其實特別的簡單,并不復(fù)雜(當(dāng)然我們工作中所使用到的Butterknife庫遠(yuǎn)遠(yuǎn)不止這么多的邏輯)放航,開不開心烈拒?希望看到這,能夠讓你基本的懂得了反射广鳍,注解荆几,以及結(jié)合這兩者如何實現(xiàn)類似于butterknife的功能。感謝您的觀看赊时,有問題歡迎留言指教吨铸!

最后附上傳送門,需要的朋友可以參考下:不過建議自己寫一波祖秒,這樣比較好诞吱;
源碼傳送門.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市竭缝,隨后出現(xiàn)的幾起案子房维,更是在濱河造成了極大的恐慌,老刑警劉巖抬纸,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咙俩,死亡現(xiàn)場離奇詭異,居然都是意外死亡湿故,警方通過查閱死者的電腦和手機(jī)阿趁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坛猪,“玉大人脖阵,你說我怎么就攤上這事∈裕” “怎么了命黔?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長躁锁。 經(jīng)常有香客問我纷铣,道長,這世上最難降的妖魔是什么战转? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任搜立,我火速辦了婚禮,結(jié)果婚禮上槐秧,老公的妹妹穿的比我還像新娘啄踊。我一直安慰自己忧设,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布颠通。 她就那樣靜靜地躺著址晕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪顿锰。 梳的紋絲不亂的頭發(fā)上谨垃,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機(jī)與錄音硼控,去河邊找鬼刘陶。 笑死,一個胖子當(dāng)著我的面吹牛牢撼,可吹牛的內(nèi)容都是我干的匙隔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼熏版,長吁一口氣:“原來是場噩夢啊……” “哼纷责!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起撼短,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤再膳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后阔加,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饵史,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡满钟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年胜榔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湃番。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡夭织,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吠撮,到底是詐尸還是另有隱情尊惰,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布泥兰,位于F島的核電站弄屡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鞋诗。R本人自食惡果不足惜膀捷,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望削彬。 院中可真熱鬧全庸,春花似錦秀仲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至覆劈,卻和暖如春保礼,著一層夾襖步出監(jiān)牢的瞬間释簿,已是汗流浹背蕊玷。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留捶闸,地道東北人鹦筹。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓铝阐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親铐拐。 傳聞我的和親對象是個殘疾皇子徘键,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,070評論 25 707
  • 前言## 最近一段時間在研究EventBus和Retrofit 的過程中,都遇到了注解這個概念遍蟋。由于在學(xué)習(xí)Java...
    IAM四十二閱讀 9,691評論 18 86
  • 籬落疏疏一徑深吹害,樹頭花落未成陰。 兒童急走追黃蝶虚青,飛入菜花無處尋它呀。
    詩沐陽閱讀 496評論 0 0
  • 今天來蘇州第二天。 今天瓊杰給介紹了一個食品廠棒厘,做銷售纵穿,剛起步的銷售部,工資不高奢人,但是覺得還算靠譜谓媒,約好的面前全部...
    大白比小白白閱讀 269評論 0 0
  • 我在熬一鍋湯。湯里放多了鹽何乎,鹽融進(jìn)了湯里句惯,湯太咸了。是否要加一些水沖淡支救?沖淡了抢野,湯的鮮味就沒了。這是一個值得思考的...
    胡清隱閱讀 455評論 1 2