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

2017-05-08  本文已影响82人  初见破晓

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

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

@Target

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

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 + ".", "");
                }
            }
        }
    }
}
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

上一篇 下一篇

猜你喜欢

热点阅读