Android框架系列-搞定APT技术

458 阅读4分钟

1、基本介绍:【在程序编译阶段工作】

  • 按照处理时期,注解分为两种类型,一种是运行时注解,另一种是编译时注解。
  • 运行时注解:
    • 运行时注解的实质是,在代码中通过注解进行标记,运行时通过反射寻找标记进行某种处理。而运行时注解一直以来被呕病的原因便是反射的低效。
    • Retrofit运用了运行时注解。
  • 编译时注解:
    • 核心依赖APT(Annotation Processing Tools)实现,原理是在某些代码元素上(如类型、函数、字段等)添加注解,在编译时编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员可以在编译器进行相应的处理,例如,根据注解生成新的Java类,这也就是EventBus,Retrofit,Dragger等开源库的基本原理。
    • APT是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。 简单来说就是在编译期,通过注解生成.java文件。
    • APT生成的源代码在build/generated/source/apt下可以看到。
    • APT和代码插入是有本质区别的,APT是生成代码;代码插入是修改已有代码。
    • APT注解处理器可以生成Java代码,这些生成的Java代码会组成 .java 文件,但不能修改已经存在的Java类(即不能向已有的类中添加方法)。而这些生成的Java文件,会同时与其他普通的手写Java源代码一起被javac编译。

2、APT优缺点:

2.1、难点:

  • 在于设计模式和解耦思想的灵活应用。
  • 在于代码生成的繁琐,你可以手动进行字符串拼接,当然有更高级的玩法用squareup的javapoet库,用建造者的模式构建出任何你想要的源代码。

2.2、优点:

  • 它可以做任何你不想做的繁杂的工作,它可以帮你写任何你不想重复的模板代码。
  • 它可以生成任何源代码供你在任何地方使用。

3、使用流程:

3.1、编写compiler java-library模块:(生成代理模板类)

3.1.1、编写业务Processor类:
  • 继承AbstractProcessor抽象类,然后在process( )方法里做生成模板代码类的逻辑。
  • 生成模板代码类的方法:可以手动进行字符串拼接书写,也可以用javapoet第三方库进行书写。
3.1.2、将编写好的业务Processor类注册到到javac中:
  • 首先我们需要将我们的注解处理器打包到一个jar文件中。
  • 其次在这个jar中,需要打包一个特定的文件javax.annotation.processing.ProcessorMETA-INF/services路径下。
  • 打包进jar文件中的javax.annotation.processing.Processor的内容是:所有自定义注解处理器的合法全名列表,每一个自定义注解处理器全名用换行分割。
  • javac会自动检查和读取javax.annotation.processing.Processor中的内容,并且注册自定义注解处理器作为注解处理器。
  • 这一步可以用auto-service库简化处理逻辑:
  • 在具体的业务注解处理器类上用注解@AutoService(Processor.class)修饰这个处理器类即可。
       @AutoService(Processor.class)
		public class MyProcessor extends AbstractProcessor {
		    ...
		}

3.2、编写API模块去供用户访问生成的代理模板类:(实现一个ButterKnife的功能)

3.2.1、接口A:APT编译生成的所有业务代理类都会去实现这个接口。
		public interface Finder<T> {
		    /**
		     *
		     * @param host 表示注解所在的业务类。
		     *             {比如:MainActivity}
		     *
		     * @param source 表示调用具体业务模板的对象。
		     *               {比如:实际调用findViewById的Activity对象、View对象}
		     *
		     * @param provider 是一个接口,定义了不同的{@code source}对象如何去调用具体的业务模板。
		     *                 {@linkplain ActivityProvider Activity如何去实例化控件}
		     *                 {@linkplain ViewProvider View如何去实例化控件}
		     *
		     */
		    void inject(T host, Object source, Provider provider);
		}
3.2.2、接口B:给接口A调用的,用于抽取具体的业务模块逻辑。比如抽取findViewById()实例化控件实例逻辑。
		public interface Provider {
		
		    /**
		     * 获取{@code source}的上下文Context
		     * @param source 表示调用具体业务模板的对象。 {比如:实际调用findViewById的Activity对象、View对象}
		     * @return 
		     */
		    Context getContext(Object source);
		
		    /**
		     * 抽取出来的具体模板逻辑 {比如:findViewById()实例化控件逻辑}
		     * @param source  表示调用具体业务模板的对象。
		     * @param id 资源id
		     * @return 
		     */
		    View findView(Object source, int id);
		}
3.2.3、接口B的实现类:
		public class ActivityProvider implements Provider {
		    @Override
		    public Context getContext(Object source) {
		        return ((Activity) source);
		    }
		
		    @Override
		    public View findView(Object source, int id) {
		        return ((Activity) source).findViewById(id);
		    }
		}
3.2.4、接口A的实现类:【自动生成的业务代理类】
		public class MainActivity$$Finder implements Finder<MainActivity> {
		  @Override
		  public void inject(final MainActivity host, Object source, Provider provider) {
		    host.mTextView = (TextView)(provider.findView(source, 2131427414));
		    host.mButton = (Button)(provider.findView(source, 2131427413));
		    host.mEditText = (EditText)(provider.findView(source, 2131427412));
		    View.OnClickListener listener;
		    listener = new View.OnClickListener() {
		      @Override
		      public void onClick(View view) {
		        host.onButtonClick();
		      }
		    } ;
		    provider.findView(source, 2131427413).setOnClickListener(listener);
		    listener = new View.OnClickListener() {
		      @Override
		      public void onClick(View view) {
		        host.onTextClick();
		      }
		    } ;
		    provider.findView(source, 2131427414).setOnClickListener(listener);
		  }
		}
3.2.5、API模块的入口类:
		public class ViewRegister {
		
		    private static final ActivityProvider PROVIDER_ACTIVITY = new ActivityProvider();
		    private static final ViewProvider PROVIDER_VIEW = new ViewProvider();
		
		    /**
		     * key:宿主host类的类名
		     * value:自动生成的每个host类所对应的代理类实例
		     */
		    private static final Map<String, Finder> FINDER_MAP = new HashMap<>();
		
		    /**
		      * Activity注入
		     * @param activity
		     */
		    public static void inject(Activity activity) {
		        inject(activity, activity, PROVIDER_ACTIVITY);
		    }
		
		    /**
		     * View注入
		     * @param view
		     */
		    public static void inject(View view) {
		        inject(view, view);
		    }
		
		    /**
		     * Fragment注入
		     * @param host
		     * @param view
		     */
		    public static void inject(Object host, View view) {
		        inject(host, view, PROVIDER_VIEW);
		    }
		
		    /**
		     * 1、根据传入的host类(注解变量所在的类,比如MainActivity)去寻找我们生成的代理类。
		     * 2、将代理类强转为统一的接口类(这里所有代理类都实现了这个接口类)。
		     * 3、然后通过调用接口类提供的方法进而去调用具体代理类中的方法。
		     *
		     * @param host
		     * @param source
		     * @param provider
		     */
		    public static void inject(Object host, Object source, Provider provider) {
		        String className = host.getClass().getName();
		        try {
		            Finder finder = FINDER_MAP.get(className);
		            if (finder == null) {
		                Class<?> finderClass = Class.forName(className + "$$Finder");
		                finder = (Finder) finderClass.newInstance();
		                FINDER_MAP.put(className, finder);
		            }
		            finder.inject(host, source, provider);  //调用具体的代理类中的方法
		        } catch (Exception e) {
		            throw new RuntimeException("Unable to inject for " + className, e);
		        }
		    }
		}
3.2.6、使用:
		public class MainActivity extends AppCompatActivity {
		
		    @BindView(R.id.tv)
		    TextView mTextView;
		    @BindView(R.id.btn)
		    Button mButton;
		    @BindView(R.id.et)
		    EditText mEditText;
		
		    @OnClick(R.id.btn)
		    public void onButtonClick() {
		        Toast.makeText(this, "onButtonClick", Toast.LENGTH_SHORT).show();
		    }
		
		    @OnClick(R.id.tv)
		    public void onTextClick() {
		        Toast.makeText(this, "onTextClick", Toast.LENGTH_SHORT).show();
		    }
		
		    @Override
		    protected void onCreate(Bundle savedInstanceState) {
		        super.onCreate(savedInstanceState);
		        setContentView(R.layout.activity_main);
		        ViewRegister.inject(this);
		    }
		}

参考:blog.csdn.net/l540675759/…


了解更多,欢迎关注: