项目地址:<a href="https://github.com/LongMaoC/MyViewBind" _src="https://github.com/LongMaoC/MyViewBind">https://github.com/LongMaoC/MyViewBind
项目主要是为了让大家了解 写一个动态注入的框架 是怎么一个流程,都需要什么,怎么做。</p>
大体思路
思路
编译期间,通过自定义处理器,动态生成代码</p>
程序运行时,通过反射调用 动态生成的代码中的方法
最后达到绑定控件的目的
整体结构
整个项目需要三个model
app
java lib
android lib
java lib 中主要写处理器和注解,androdi lib 中写反射。处理器必须继承 AbstractProcessor ,而 AbstractProcessor 属于 javax , 所以需要建立两种项目</p>
步骤
java lib(注解和控制器)
结构
MyProcessor 核心处理类</p>
ClassEntey 类的抽象类 (包括 路径、类名、属性集合)
AttrEntey 属性抽象类 (包括 属性名、属性值、属性类型)
处理器</h4>
注册
处理器的作用就是在编译期间执行我们想生成的代码,javac会自动寻找继承了 AbstractProcessor 的类
注册处理器(两种方式)
方式一
在main文件下创建(不推荐)
resources/META-INF/javax.annotation.processing.Processor在文件中写</p>
格式:包名+“.”+类名
com.example.MyProcessor
方式二</p>
依赖
dependencies { compile 'com.google.auto.service:auto-service:1.0-rc2' }
在继承了 AbstractProcessor 的类上面写</p>
@AutoService(Processor.class) public class MyProcessor extends AbstractProcessor
实现
创建注解
@Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) public @interface BindView { int value(); }
在继承 AbstractProcessor 的类中,实现如下三个方法
方法名</th> | 作用 |
---|---|
getSupportedAnnotationTypes() | 需要处理的那些注解 |
getSupportedSourceVersion() | 限制版本 |
process() | 核心函数,在这个里面做处理</td> |
主要介绍process()方法
当javac 查询到所有在getSupportedAnnotationTypes()里面注册过的注解后,才会调用process()方法
process()方法有两个参数</p>
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
着重介绍第二个,第二个参数代表所有和这个注解有关信息的集合,如下
例子
所在路径</p>
cxy.com.myviewbind.Main2Activity
@BindView(R.id.button) Button button ;
方法名</th> | 含义 |
---|---|
roundEnv.getSimpleName() | button |
roundEnv.getEnclosingElement() | cxy.com.myviewbind.Main2Activity |
roundEnv.getAnnotation() | R.id.button 所对应的整数值</td> |
roundEnv.asType() | android.widget.Button |
有这些就可以生成我们需要的类了
我们看一下process()中都做了什么</p>
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // 以activity名为key,类的抽象类为value Map<String, ClassEntey> map = new LinkedHashMap<>(); //判断是否为BindView 注解 for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) { //判断是否为变量 if (element.getKind() == ElementKind.FIELD) { String className = element.getEnclosingElement().toString(); //判断当前界面在map结合中是否存在,不存在则添加 ClassEntey classEntey = map.get(className); if (classEntey == null) { String parkageNmae = className.substring(0, className.toString().lastIndexOf(".")); String classSimpleName = className.substring( className.lastIndexOf(".") + 1, className.length() ) + "_BindView"; classEntey = ClassEntey.create(classSimpleName, parkageNmae); map.put(element.getEnclosingElement().toString(), classEntey); } //activity可能存在多个控件属性,创建属性抽象类并赋值,最后添加进当前界面为key的抽象类中 AttrEntey attrEntey = AttrEntey.create( element.getSimpleName().toString(), element.asType().toString().substring( element.asType().toString().lastIndexOf(".") + 1, element.asType().toString().length() ), element.getAnnotation(BindView.class).value() ); classEntey.addAttr(attrEntey, processingEnv); } } //以上,所有的属性已经获取获取 //以下,把所有的数据写成文件 // for (Map.Entry<String, ClassEntey> entry : map.entrySet()) { try { List<AttrEntey> arrts = entry.getValue().getArrts(); Set<String> setImp = new HashSet<>(); //循环获取出所有需要导入的包 for (AttrEntey entey : arrts) { setImp.add(entey.getType()); } //写文件 createJava(entry.getValue(), setImp); } catch (Exception e) { e.printStackTrace(); } } return false; }
createJava为写文件的方法,为了更容易读懂,没有用javapoet,使用拼接字符串的方式完成写文件操作 createJava
private void createJava(ClassEntey entry, Set<String> setImp) throws Exception { JavaFileObject filerClassFile = filer.createSourceFile(entry.getParkageName() + "." + entry.getClassSimpleName(), new Element[]{}); Writer writer = filerClassFile.openWriter(); PrintWriter pw = new PrintWriter(writer); ··· pw.flush(); writer.close(); }
简单说一下 filer :是写文件用的,在init方法中获取</p>
android lib(反射生成的文件)
依赖刚才的java lib
没什么难度,不说了</p>
android app(项目文件)
使用方式和butterknife 一样</p>
public class MainActivity extends AppCompatActivity { @BindView(R.id.edittext) EditText edittext; @BindView(R.id.button) Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyBindView.bind(this); edittext.setText("界面1"); button.setText("跳转至第二个界面"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { startActivity(new Intent(MainActivity.this, Main2Activity.class)); } }); } }
-END-
哪里写的不清晰可以给我留言,我会第一时间修改
QQ: 1209101049