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

如何写一个编译时注解框架,第1张

1、注解

我们日常使用的很多开源库都有注解的实现,主要有运行时注解和编译时注解两种。

运行时注解:主要作用就是得到注解的信息

Retrofit:

调用

@GET("/users/{username}")

User getUser(@Path("username") String username)

定义

@Documented

@Target(METHOD)

@Retention(RUNTIME)

@RestMethod("GET")

public @interface GET {

String value()

}

编译时注解:主要作用动态生成代码

Butter Knife

调用

@InjectView(R.id.user)

EditText username

定义

@Retention(CLASS)

@Target(FIELD)

public @interface InjectView {

int value()

}

2、编译时注解

要实现编译时注解需要3步:

1、定义注解(关于注解的定义可以参考下面引用的博客)

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

{

}

2、编写注解解析器

@SupportedAnnotationTypes("annotation.Seriable")

@SupportedSourceVersion(SourceVersion.RELEASE_6)

public class ViewInjectProcessor extends AbstractProcessor {

@Override

public boolean process(Set annotations,

RoundEnvironment roundEnv) {

for (Element ele : roundEnv.getElementsAnnotatedWith(InjectView.class)) {

if(ele.getKind() == ElementKind.FIELD){

//todo

}

return true

}

@SupportedAnnotationTypes,定义要燃并支持注解的完整路径,也可以通过getSupportedAnnotationTypes方法来定义

@Override

public Set getSupportedAnnotationTypes() {

Set types = new LinkedHashSet<>()

types.add(InjectView.class.getCanonicalName())

return types

}

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

3、创建制定文猜段液件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 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 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 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, AbstractInjector>INJECTORS = new LinkedHashMap, AbstractInjector>()

public static void inject(Activity activity){

AbstractInjector injector = findInjector(activity)

injector.inject(Finder.ACTIVITY, activity, activity)

}

public static void inject(Object target, View view){

AbstractInjector injector = findInjector(target)

injector.inject(Finder.VIEW, target, view)

}

private static AbstractInjector findInjector(Object target){

Class clazz = target.getClass()

AbstractInjector injector = INJECTORS.get(clazz)

if(injector == null){

try{

Class injectorClazz = Class.forName(clazz.getName()+"$$"+ProxyInfo.PROXY)

injector = (AbstractInjector) injectorClazz.newInstance()

INJECTORS.put(clazz, injector)

}catch(Exception e){

e.printStackTrace()

}

}

return injector

}

}

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

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

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

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

因此我们也能明白为什么XlViewInjector.inject(object)方法一定要在setContentView之后执行。

3、实现注解框架时的坑

解析有用到android api,因此需要创建Android工程,但是android library并没有javax的一些功能,

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

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

返回上一个activity只需要调用 finish() 方法(前歼闷提是你跳转的时候没有关闭上个activity)

当然,这洞唤也必须要通过点击事件来实现

1.写个BaseActivity实现 OnClickListener

public abstract class BaseActivity extends AppCompatActivity implements OnClickListener{} 这样每个继承它的activity都会自动重写

@Override

public void onClick(View view) {

switch(view.getid){

    }

}

给每一个需要点击事件的view 氏颤弯写个监听 view.setOnClickListener(this)

2.使用框架 比如xUtil等 不推荐

3.使用butterknife

4.使用rxjava实现的rxbus

5.使用EventBus 不推荐

ADB Idea ADB *** 作快捷菜单,快速清除数据没旁,重启应用,卸载应用等 *** 作

Android

Annotations 配合Annotation框架快速生成控件绑定代码,同Android ButterKnife Zeleny

,本来都是用ButterKnife,生成快速绑定代码,后面开发拍察禅的都是Lib库没办法使用ButterKnife只好转Annotations库

Android Drawable Importer 做些小应用时妈妈再也不用担心我找不到图标了~需要配合google/material-design-icons · GitHub、Android Icons使用,安装后在设置菜单多出来的选项中设置

Android Parcelable Code Generator 如标题。快速生成Parcelable代码

Android

Accessors

按照Google官方的开发规范,类的成员变量以m开头,此插件可快速生成成员变量的set/get方法但是不带m,评论中有人指出可以通过再

Settings-CodeStyle-Java-CodeGeneration中修改前缀,经查验却是更加方便。

SelectorChapek

for Android

插件库好多生成Selector的插件,还是这个用的最顺,按照不同状态(normal、pressed)的标准命名后,右键文件树Generate

Android Selectors见inmite/android-selector-chapek · GitHub

IntelliJ IDea通用

AceJump 快速跳转到屏幕某个位置(Ctrl+)

FindBugs-IDEA、CheckStyle-IDEA 用自动化代替双眼

JavaDoc 添加注释,可自定义模板

Eclipse Code Formmater 项目之前袭尘未全部转向Studio,该插件兼容Eclipse的代码格式化风格


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/12237155.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-22
下一篇 2023-05-22

发表评论

登录后才能评论

评论列表(0条)

保存