如何写一个编译时注解框架

一、注解

咱们平常使用的不少开源库都有注解的实现,主要有运行时注解和编译时注解两种。html

运行时注解:主要做用就是获得注解的信息java

Retrofit:android

调用
segmentfault

@GET("/users/{username}")
User getUser(@Path("username") String username);

定义api

@Documented
@Target(METHOD)
@Retention(RUNTIME)
@RestMethod("GET")
public @interface GET {
  String value();
}

编译时注解:主要做用动态生成代码
框架

Butter Knife
eclipse

调用ide

@InjectView(R.id.user) 
EditText username;

定义ui

@Retention(CLASS) 
@Target(FIELD)
public @interface InjectView {
  int value();
}


二、编译时注解

要实现编译时注解须要3步:
this

一、定义注解(关于注解的定义能够参考下面引用的博客)

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.ElementType;

@Target({ ElementType.FIELD, ElementType.TYPE })  
@Retention(RetentionPolicy.CLASS)  
public @interface Seriable  
{  
      
}

二、编写注解解析器

@SupportedAnnotationTypes("annotation.Seriable")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ViewInjectProcessor extends AbstractProcessor {

	@Override
	public boolean process(Set<? extends TypeElement> annotations,
			RoundEnvironment roundEnv) {

		for (Element ele : roundEnv.getElementsAnnotatedWith(InjectView.class)) {
			if(ele.getKind() == ElementKind.FIELD){
			//todo
		}

		return true;
	}

@SupportedAnnotationTypes,定义要支持注解的完整路径,也能够经过getSupportedAnnotationTypes方法来定义

@Override
	public Set<String> getSupportedAnnotationTypes() {
		Set<String> types = new LinkedHashSet<>();
		types.add(InjectView.class.getCanonicalName());

		return types;
	}

@SupportedSourceVersion(SourceVersion.RELEASE_6)表示支持的jdk的版本

三、建立制定文件resources/META-INF/services/javax.annotation.processing.Processor,并填写注解解析器的类路径,这样在编译的时候就能自动找到解析器

看上去实现编译时注解仍是很容易的,可是真要完整的实现一个相似Butter Knife的框架,这还只是开始。

Butter Knife是专一View的注入,在使用注解的类编译后,查看编译后的class文件,会发现多出了文件,如:

SimpleActivity$$ViewInjector.java

SimpleActivity$$ViewInjector.java,就是经过编译时注解动态建立出来的,查看SimpleActivity$$ViewInjector.java的内容

// Generated code from Butter Knife. Do not modify!  
package com.example.butterknife;  
  
import android.view.View;  
  
import butterknife.ButterKnife.Finder;  
  
public class SimpleActivity$$ViewInjector {  
      
    public static void inject(Finder finder, final com.example.butterknife.SimpleActivity target, Object source) {  
        View view;  
        view = finder.findRequiredView(source, 2131230759, "field 'title'");  
        target.title = (android.widget.TextView) view;  
        view = finder.findRequiredView(source, 2131230783, "field 'subtitle'");  
        target.subtitle = (android.widget.TextView) view;  
        view = finder.findRequiredView(source, 2131230784, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");  
        target.hello = (android.widget.Button) view;  
        view.setOnClickListener(  
                new butterknife.internal.DebouncingOnClickListener() {  
                    @Override  
                    public void doClick(  
                            android.view.View p0  
                    ) {  
                        target.sayHello();  
                    }  
                });  
        view.setOnLongClickListener(  
                new android.view.View.OnLongClickListener() {  
                    @Override  
                    public boolean onLongClick(  
                            android.view.View p0  
                    ) {  
                        return target.sayGetOffMe();  
                    }  
                });  
        view = finder.findRequiredView(source, 2131230785, "field 'listOfThings' and method 'onItemClick'");  
        target.listOfThings = (android.widget.ListView) view;  
        ((android.widget.AdapterView<?>) view).setOnItemClickListener(  
                new android.widget.AdapterView.OnItemClickListener() {  
                    @Override  
                    public void onItemClick(  
                            android.widget.AdapterView<?> p0,  
                            android.view.View p1,  
                            int p2,  
                            long p3  
                    ) {  
                        target.onItemClick(p2);  
                    }  
                });  
        view = finder.findRequiredView(source, 2131230786, "field 'footer'");  
        target.footer = (android.widget.TextView) view;  
    }  
  
    public static void reset(com.example.butterknife.SimpleActivity target) {  
        target.title = null;  
        target.subtitle = null;  
        target.hello = null;  
        target.listOfThings = null;  
        target.footer = null;  
    }  
}

inject方法进行初始化,reset进行释放。inject都是调用Finder的方法与android系统的findViewById等方法很像,再来看Finder类,只截取部分。

public enum Finder { 
   public <T> T findRequiredView(Object source, int id, String who) {
    T view = findOptionalView(source, id, who);
    if (view == null) {
      String name = getResourceEntryName(source, id);
      throw new IllegalStateException("Required view '"
          + name
          + "' with ID "
          + id
          + " for "
          + who
          + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
          + " (methods) annotation.");
    }
    return view;
  }
  
   public <T> T findOptionalView(Object source, int id, String who) {
    View view = findView(source, id);
    return castView(view, id, who);
  }
  
   @Override protected View findView(Object source, int id) {
      return ((View) source).findViewById(id);
    }
  
   @SuppressWarnings("unchecked") // That's the point.
  public <T> T castView(View view, int id, String who) {
    try {
      return (T) view;
    } catch (ClassCastException e) {
      if (who == null) {
        throw new AssertionError();
      }
      String name = getResourceEntryName(view, id);
      throw new IllegalStateException("View '"
          + name
          + "' with ID "
          + id
          + " for "
          + who
          + " was of the wrong type. See cause for more info.", e);
    }
  }

findRequiredView方法实际上就是咱们经常使用findViewById的实现,其动态帮咱们添加了这些实现。view注入的原理咱们就清楚了。

虽然编译时建立了这个类,运行的时候如何使用这个类呢,这里我用本身实现的一个类来描述

public class XlViewInjector {

	static final Map<Class<?>, AbstractInjector<Object>> INJECTORS = new LinkedHashMap<Class<?>, AbstractInjector<Object>>();
	
	public static void inject(Activity activity){
		AbstractInjector<Object> injector = findInjector(activity);
		injector.inject(Finder.ACTIVITY, activity, activity);
	}
	
	public static void inject(Object target, View view){
		AbstractInjector<Object> injector = findInjector(target);
		injector.inject(Finder.VIEW, target, view);
	}
	
	private static AbstractInjector<Object> findInjector(Object target){
		Class<?> clazz = target.getClass();
		AbstractInjector<Object> injector = INJECTORS.get(clazz);
		if(injector == null){
			try{
				Class injectorClazz = Class.forName(clazz.getName()+"$$"+ProxyInfo.PROXY);
				injector = (AbstractInjector<Object>) injectorClazz.newInstance();
				INJECTORS.put(clazz, injector);
			}catch(Exception e){
				e.printStackTrace();
			}
		}
		
		return injector;
	}
}

XlViewInjector与ButterKnife,好比调用时咱们都会执行XlViewInjector.inject方法,经过传入目标类的名称得到封装后的类实例就是SimpleActivity$$ViewInjector.java,再调用它的inject,来初始化各个view。

总结一下整个实现的流程:

一、经过编译时注解动态建立了一个包装类,在这个类中已解析了注解,实现了获取view、设置监听等代码。

二、执行时调用XlViewInjector.inject(object)方法,实例化object类对应的包装类,并执行他的初始化方法inject;

所以咱们也能明白为何XlViewInjector.inject(object)方法必定要在setContentView以后执行。


三、实现注解框架时的坑

解析有用到android api,所以须要建立Android工程,可是android library并无javax的一些功能,

在eclipse环境下,右键build-path add library把jdk加进来

在android studio下,须要先建立java Library功能,实现与view无关的解析,再建立一个android library功能引用这个工程并实现余下的解析。


四、参考

详细的实现过程能够参考这些博客,描述的都很详细

http://www.trinea.cn/android/java-annotation-android-open-source-analysis/

http://blog.csdn.net/lmj623565791/article/details/43452969

https://segmentfault.com/a/1190000002785541

http://blog.zenfery.cc/archives/78.html

http://www.cnblogs.com/avenwu/p/4173899.html

相关文章
相关标签/搜索