android 自定義annotation - 手?jǐn)]依賴注入

參考文章
http://blog.csdn.net/johnny901114/article/details/52662376
http://blog.csdn.net/johnny901114/article/details/52664112
http://blog.csdn.net/johnny901114/article/details/52672188
http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html

這個(gè)demo沒有在項(xiàng)目中涉及废亭,只是用來理解java的注解及使用,并不是一個(gè)完整的框架。通過這個(gè)demo,能掌握注解的相關(guān)知識(shí)趁桃,并且提高了自己的逼格雁歌,O__O "…主要是提高了逼格呻引。


一颜及、

在項(xiàng)目中用到了mvp,封裝了一下

public class EditorActivity extends BindingActivity<ActivityEditorBinding, EditorPresenter> implements EditorContract.View{

    @Override
    protected EditorPresenter createPresenter() {
        return new EditorPresenter(this);
    }

    @Override
    protected int createLayoutId() {
        return R.layout.activity_editor;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void showMessage(String message) {
        GlobalToast.show(message);
    }
}

上面創(chuàng)建了一個(gè)簡單的activity仆葡, 再createPresenter方法中創(chuàng)建了Presenter在createLayoutId方法中傳入了布局的id赏参。每個(gè)activity都要有這兩個(gè)方法,寫起來還挺繁瑣的沿盅。有沒有更好的方法呢把篓?

要是像butterknife和dragger一樣通過注解注入該多好

二、

一個(gè)叫刀一個(gè)劍的腰涧,這個(gè)demo的名字就叫fork把韧掩,和butterknife一樣,都和吃有關(guān)系

先看一下完成之后的activity

@ForkLayoutId(R.layout.activity_main)
@ForkPresenter(MainPresenter.class)
public class MainActivity extends ForkActivity<ActivityMainBinding, MainPresenter> implements MainContract.View {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Fork.bind(this);

        binding.rvText.setText("haha");
        mvpPresenter.run();
    }

    @Override
    public void showMessage(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }
}

費(fèi)了大半天的勁窖铡,少了倆方法疗锐,呵呵

三坊谁、

說了一堆廢話,記錄一下實(shí)現(xiàn)吧滑臊!

1口芍、首先再android studio 中創(chuàng)建一個(gè)java library(一定要是java 項(xiàng)目,不然android項(xiàng)目可找不到項(xiàng)目需要的包)
module 名字就叫 fork-annotations 吧雇卷,這里準(zhǔn)備主要放用到的注解

首先鬓椭,創(chuàng)建今天的第一個(gè)注解 名字叫ForkLayoutId

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface ForkLayoutId {
    int value();
}

注解跟普通的java接口的定義很像,但接口是給程序員看的关划,而注解是給計(jì)算機(jī)看的小染,所以這里的interface前面加上了一個(gè)@

@Retention

是用來標(biāo)記這個(gè)注解的生命周期,有以下幾種

  • RetentionPolicy.SOURCE : 注解只保留在源文件中
  • RetentionPolicy.CLASS : 注解保留在class文件中祭玉,在加載到JVM虛擬機(jī)時(shí)丟棄
  • RetentionPolicy.RUNTIME : 注解保留在程序運(yùn)行期間氧映,此時(shí)可以通過反射獲得定義在某個(gè)類上的所有注解春畔。

@Target

是用來標(biāo)記注解所修飾的屬性

  • ElementType.TYPE:說明該注解只能被聲明在一個(gè)類前脱货。
  • ElementType.FIELD:說明該注解只能被聲明在一個(gè)類的字段前。
  • ElementType.METHOD:說明該注解只能被聲明在一個(gè)類的方法前律姨。
  • ElementType.PARAMETER:說明該注解只能被聲明在一個(gè)方法參數(shù)前振峻。
  • ElementType.CONSTRUCTOR:說明該注解只能聲明在一個(gè)類的構(gòu)造方法前。
  • ElementType.LOCAL_VARIABLE:說明該注解只能聲明在一個(gè)局部變量前择份。
  • ElementType.ANNOTATION_TYPE:說明該注解只能聲明在一個(gè)注解類型前扣孟。
  • ElementType.PACKAGE:說明該注解只能聲明在一個(gè)包名前。

int value();

這個(gè)就是接口的參數(shù)了

再定義另一個(gè)接口荣赶,不凤价, 注解!

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface ForkPresenter {
    Class value();
}

2拔创、
兩個(gè)注解定義完之后利诺,接下來就是最重要的類 AbstractProcessor
在程序編譯時(shí),就通過 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
這個(gè)方法剩燥,處理我們的注解

處理的過程看似復(fù)雜慢逾,其實(shí)很簡單。獲得我們需要的值灭红,動(dòng)態(tài)生成java代碼侣滩,生成代碼也是一個(gè)庫javapoet,就直接貼代碼吧变擒,更直觀些君珠。(javapoet的用法這里就不說了)

@SupportedAnnotationTypes({"org.fork.annotation.ForkLayoutId", "org.fork.annotation.ForkPresenter"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SuppressWarnings("All")
public class ForkProcessor extends AbstractProcessor {
    private String packageName;
    private String activityName;
    private TypeMirror activityClass;

    private int layoutId;
    private String presenterName;

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (annotations.size() > 0) {
            parseBindViews(annotations, roundEnv);
            javaPoet();
        }
        return true;
    }

    private void javaPoet() {
        MethodSpec getPresenter = MethodSpec.methodBuilder("getPresenter")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .returns(Object.class)
                .addParameter(TypeName.OBJECT, "activity")
                .addStatement("return new " + presenterName + "((" + activityName + ")activity)")
                .build();

        MethodSpec getLayoutId = MethodSpec.methodBuilder("getLayoutId")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .returns(int.class)
                .addStatement("return " + layoutId)
                .build();

        TypeSpec clazz = TypeSpec.classBuilder(activityName + "$$Provider")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addSuperinterface(Provider.class)
                .addMethod(getPresenter)
                .addMethod(getLayoutId)
                .build();

        JavaFile.Builder builder = JavaFile
                .builder(packageName, clazz);
        JavaFile javaFile = builder.build();

        try {
            javaFile.writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void parseBindViews(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(ForkLayoutId.class)) {
            if (element.getKind() == ElementKind.CLASS) {
                layoutId = element.getAnnotation(ForkLayoutId.class).value();
                activityName = element.getSimpleName().toString();
                activityClass = element.asType();
                packageName = element.toString().replace("." + activityName, "");
            }
        }

        for (Element element : roundEnv.getElementsAnnotatedWith(ForkPresenter.class)) {
            if (element.getKind() == ElementKind.CLASS) {
                try {
                    presenterName = element.getAnnotation(ForkPresenter.class).value().getSimpleName().toString();
                } catch (MirroredTypeException mte) {
                    presenterName = mte.getTypeMirror().toString().replace(packageName + ".", "");
                }
            }
        }
    }
}
  • @SupportedAnnotationTypes 用來指定這里會(huì)處理的注解
  • @SupportedSourceVersion(SourceVersion.RELEASE_7) 指定java版本,網(wǎng)上貼子說娇斑,這個(gè)寫成注解兼容性更好策添,當(dāng)然澈段,class開頭的兩句,也可以使用java代碼來聲明
  • 注意舰攒,再這個(gè)java庫的build.gradle中败富,我們還要配置一遍java環(huán)境
sourceCompatibility = "1.7"
targetCompatibility = "1.7"

3、這些都寫完了摩窃,還要加上一個(gè)配置文件
再與java 同級(jí)兽叮,添加 resources / META_INF / services / javax.annotation.processing.Processor 這樣一個(gè)文件

填寫里面的內(nèi)容

org.fork.annotation.ForkProcessor

這是完整的目錄結(jié)構(gòu)

Paste_Image.png

ForkProcessor這個(gè)文件會(huì)報(bào)錯(cuò),但是沒什么影響猾愿○写希可能是android studio支持不夠好吧,再IntelliJ中不會(huì)報(bào)錯(cuò)

通過javapoet蒂秘,編譯之后會(huì)生成如下代碼泽本,當(dāng)然生成什么都是自己控制的。下面就詳細(xì)的說一下生成的這個(gè)類

package org.demo.tiny;

import java.lang.Object;
import org.fork.annotation.Provider;

public final class MainActivity$$Provider implements Provider {
  public final Object getPresenter(Object activity) {
    return new MainPersenter((MainActivity)activity);
  }

  public final int getLayoutId() {
    return 2130968603;
  }
}

我們?cè)賏ctivity使用注解姻僧,將pressenter的字節(jié)碼文件和layout的id傳入规丽。

我們通過注解能拿到這個(gè)activity的名字,也就是上面的MainActivity
加上final 防止復(fù)寫方法撇贺。getLayoutId沒什么說的赌莺,getPresenter的強(qiáng)轉(zhuǎn)有些蛋疼,一會(huì)兒再說松嘶。

provider 接口提供了兩個(gè)方法

public interface Provider {
    Object getPresenter(Object obj);
    int getLayoutId();
}

費(fèi)了九牛二虎之力艘狭,通過注解,拿到了layout的id并創(chuàng)建了presenter翠订。
下面巢音,我們就要使用他們了。 fork類上場(chǎng)尽超。為了將這個(gè)demo封裝起來官撼,作為一個(gè)三方框架,我新建了一個(gè)android library

public final class Fork {

    public static void bind(ForkActivity activity) {
        Provider provider = null;
        try {
            try {
                provider = (Provider) Class.forName(activity.getClass().getName() + "$$Provider").newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if (provider != null) {
            activity.binding = DataBindingUtil.setContentView(activity, provider.getLayoutId());
            activity.mvpPresenter = provider.getPresenter(activity);
        }

    }
}

通過加載器橙弱,創(chuàng)建了Provider的一個(gè)實(shí)例歧寺。這樣我們就可以得到layout id 和presenter了

為了實(shí)現(xiàn)封裝,我創(chuàng)建了一個(gè)ForkActivity

public class ForkActivity<B extends ViewDataBinding, P> extends Activity {
    protected B binding;
    protected P mvpPresenter;
}

這里就是為什么要進(jìn)行強(qiáng)轉(zhuǎn)了棘脐,因?yàn)橄氚裝inding和mvpPresenter這兩個(gè)屬性封裝起來斜筐,放進(jìn)父類。但我們自動(dòng)生成的代碼的報(bào)名卻和ForkActivity 不在同一個(gè)包下蛀缝∏炅矗總不能把兩個(gè)屬性全公有吧。

此外屈梁,還有一個(gè)坑嗤练,Provider 并不是我們自己生成的榛了,所以不可能知道Activity的名字,這里也就只有寫Object 了煞抬。會(huì)涉及幾處的強(qiáng)轉(zhuǎn)霜大。

再Fork.java中傳遞的是MainActivity,再注解創(chuàng)建presenter是革答,我們知道這是MainActivity战坤,所以將其強(qiáng)轉(zhuǎn)創(chuàng)建一個(gè)presenter,但是mvpPresenter又被抽取再ForkActivity中残拐,我們并不知道實(shí)際的activity是Main途茫,所以又強(qiáng)轉(zhuǎn)成ForkActivity,并賦值mvpPresenter溪食。我們?cè)費(fèi)ainActivity中使用mvpPresenter囊卜,通過泛型,聲明了他的類型是MainActivity

項(xiàng)目地址

https://github.com/LavenderStream/fork

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末错沃,一起剝皮案震驚了整個(gè)濱河市栅组,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捎废,老刑警劉巖笑窜,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件致燥,死亡現(xiàn)場(chǎng)離奇詭異登疗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嫌蚤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門辐益,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脱吱,你說我怎么就攤上這事智政。” “怎么了箱蝠?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵续捂,是天一觀的道長。 經(jīng)常有香客問我宦搬,道長牙瓢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任间校,我火速辦了婚禮矾克,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘憔足。我一直安慰自己胁附,他們只是感情好酒繁,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著控妻,像睡著了一般州袒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弓候,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天稳析,我揣著相機(jī)與錄音,去河邊找鬼弓叛。 笑死彰居,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撰筷。 我是一名探鬼主播陈惰,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼毕籽!你這毒婦竟也來了抬闯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤关筒,失蹤者是張志新(化名)和其女友劉穎溶握,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蒸播,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡睡榆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了袍榆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胀屿。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖包雀,靈堂內(nèi)的尸體忽然破棺而出宿崭,到底是詐尸還是另有隱情,我是刑警寧澤才写,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布葡兑,位于F島的核電站,受9級(jí)特大地震影響赞草,放射性物質(zhì)發(fā)生泄漏讹堤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一房资、第九天 我趴在偏房一處隱蔽的房頂上張望蜕劝。 院中可真熱鬧,春花似錦、人聲如沸岖沛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽婴削。三九已至廊镜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唉俗,已是汗流浹背嗤朴。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虫溜,地道東北人雹姊。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像衡楞,于是被迫代替她去往敵國和親吱雏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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