android 自定义annotation - 手撸依赖注入

参考文章
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

这个demo没有在项目中涉及,只是用来理解java的注解及使用,并不是一个完整的框架。通过这个demo,能掌握注解的相关知识,并且提高了自己的逼格,O__O "…主要是提高了逼格。


一、

在项目中用到了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);
    }
}

上面创建了一个简单的activity, 再createPresenter方法中创建了Presenter在createLayoutId方法中传入了布局的id。每个activity都要有这两个方法,写起来还挺繁琐的。有没有更好的方法呢?

要是像butterknife和dragger一样通过注解注入该多好

二、

一个叫刀一个剑的,这个demo的名字就叫fork把,和butterknife一样,都和吃有关系

先看一下完成之后的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();
    }
}

费了大半天的劲,少了俩方法,呵呵

三、

说了一堆废话,记录一下实现吧!

1、首先再android studio 中创建一个java library(一定要是java 项目,不然android项目可找不到项目需要的包)
module 名字就叫 fork-annotations 吧,这里准备主要放用到的注解

首先,创建今天的第一个注解 名字叫ForkLayoutId

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

注解跟普通的java接口的定义很像,但接口是给程序员看的,而注解是给计算机看的,所以这里的interface前面加上了一个@

@Retention

是用来标记这个注解的生命周期,有以下几种

  • RetentionPolicy.SOURCE : 注解只保留在源文件中
  • RetentionPolicy.CLASS : 注解保留在class文件中,在加载到JVM虚拟机时丢弃
  • RetentionPolicy.RUNTIME : 注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解。

@Target

是用来标记注解所修饰的属性

  • ElementType.TYPE:说明该注解只能被声明在一个类前。
  • ElementType.FIELD:说明该注解只能被声明在一个类的字段前。
  • ElementType.METHOD:说明该注解只能被声明在一个类的方法前。
  • ElementType.PARAMETER:说明该注解只能被声明在一个方法参数前。
  • ElementType.CONSTRUCTOR:说明该注解只能声明在一个类的构造方法前。
  • ElementType.LOCAL_VARIABLE:说明该注解只能声明在一个局部变量前。
  • ElementType.ANNOTATION_TYPE:说明该注解只能声明在一个注解类型前。
  • ElementType.PACKAGE:说明该注解只能声明在一个包名前。

int value();

这个就是接口的参数了

再定义另一个接口,不, 注解!

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

2、
两个注解定义完之后,接下来就是最重要的类 AbstractProcessor
在程序编译时,就通过 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
这个方法,处理我们的注解

处理的过程看似复杂,其实很简单。获得我们需要的值,动态生成java代码,生成代码也是一个库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 用来指定这里会处理的注解
  • @SupportedSourceVersion(SourceVersion.RELEASE_7) 指定java版本,网上贴子说,这个写成注解兼容性更好,当然,class开头的两句,也可以使用java代码来声明
  • 注意,再这个java库的build.gradle中,我们还要配置一遍java环境
sourceCompatibility = "1.7"
targetCompatibility = "1.7"

3、这些都写完了,还要加上一个配置文件
再与java 同级,添加 resources / META_INF / services / javax.annotation.processing.Processor 这样一个文件

填写里面的内容

org.fork.annotation.ForkProcessor

这是完整的目录结构

Paste_Image.png

ForkProcessor这个文件会报错,但是没什么影响。可能是android studio支持不够好吧,再IntelliJ中不会报错

通过javapoet,编译之后会生成如下代码,当然生成什么都是自己控制的。下面就详细的说一下生成的这个类

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;
  }
}

我们再activity使用注解,将pressenter的字节码文件和layout的id传入。

我们通过注解能拿到这个activity的名字,也就是上面的MainActivity
加上final 防止复写方法。getLayoutId没什么说的,getPresenter的强转有些蛋疼,一会儿再说。

provider 接口提供了两个方法

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

费了九牛二虎之力,通过注解,拿到了layout的id并创建了presenter。
下面,我们就要使用他们了。 fork类上场。为了将这个demo封装起来,作为一个三方框架,我新建了一个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);
        }

    }
}

通过加载器,创建了Provider的一个实例。这样我们就可以得到layout id 和presenter了

为了实现封装,我创建了一个ForkActivity

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

这里就是为什么要进行强转了,因为想把binding和mvpPresenter这两个属性封装起来,放进父类。但我们自动生成的代码的报名却和ForkActivity 不在同一个包下。总不能把两个属性全公有吧。

此外,还有一个坑,Provider 并不是我们自己生成的,所以不可能知道Activity的名字,这里也就只有写Object 了。会涉及几处的强转。

再Fork.java中传递的是MainActivity,再注解创建presenter是,我们知道这是MainActivity,所以将其强转创建一个presenter,但是mvpPresenter又被抽取再ForkActivity中,我们并不知道实际的activity是Main,所以又强转成ForkActivity,并赋值mvpPresenter。我们再MainActivity中使用mvpPresenter,通过泛型,声明了他的类型是MainActivity

项目地址

https://github.com/LavenderStream/fork

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,471评论 25 707
  • 一只鹅 伸长脖子几乎贴地 奔我而来。 一只狗 露出长牙欲扑状 朝我狂吠。 爱上的那个人 把我拉黑将我 驱逐出她的世界
    南冠阅读 156评论 0 1
  • 站占有话;如果在前期你实在搜罗不到客户的资料,那么在与客户谈话的过程中,你可以不断地询问,从询问中,你能够很快地发...
    时代小强阅读 495评论 0 0