美文网首页
android 自定义annotation - 手撸依赖注入

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

作者: 初见破晓 | 来源:发表于2017-05-08 19:27 被阅读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

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

    • 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

    相关文章

      网友评论

          本文标题:android 自定义annotation - 手撸依赖注入

          本文链接:https://www.haomeiwen.com/subject/arkvtxtx.html