/MyViewBind

绑定控件(分析butterknife源码仿写出来的例子。只实现了最最基础的功能)

Primary LanguageJava

# MyViewBind 手把手教你写自己的butterknife 项目主要是为了让大家了解 写一个动态注入的框架 是怎么一个流程,都需要什么,怎么做。 ## 大体思路 #### 思路 编译期间,通过自定义处理器,动态生成代码 程序运行时,通过反射调用 动态生成的代码中的方法 最后达到绑定控件的目的 #### 整体结构 整个项目需要三个model * app * java lib * android lib java lib 中主要写处理器和注解,androdi lib 中写反射。处理器必须继承 **_AbstractProcessor_** ,而 **_AbstractProcessor_** 属于 javax , 所以需要建立两种项目 ## 步骤 ### **_java lib_**(注解和控制器) #### 结构 ![image](https://github.com/LongMaoC/MyViewBind/blob/master/gif/javaLib_structure.png) * MyProcessor 核心处理类 * ClassEntey 类的抽象类 (包括 路径、类名、属性集合) * AttrEntey 属性抽象类 (包括 属性名、属性值、属性类型) #### 处理器 ##### 注册 处理器的作用就是在编译期间执行我们想生成的代码,javac会自动寻找继承了 **_AbstractProcessor_** 的类 注册处理器(两种方式) * 方式一 在main文件下创建(不推荐) resources/META-INF/javax.annotation.processing.Processor在文件中写 格式:包名+“.”+类名 ``` com.example.MyProcessor ``` * 方式二 依赖 ``` dependencies { compile 'com.google.auto.service:auto-service:1.0-rc2' } ``` 在继承了 **_AbstractProcessor_** 的类上面写 ``` @AutoService(Processor.class) public class MyProcessor extends AbstractProcessor ``` ##### 实现 创建注解 ``` @Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) public @interface BindView { int value(); } ``` 在继承 **_AbstractProcessor_** 的类中,实现如下三个方法 | 方法名 |作用| |:--:|:--:| |getSupportedAnnotationTypes()|需要处理的那些注解| |getSupportedSourceVersion()|限制版本| |process()|核心函数,在这个里面做处理| 主要介绍process()方法 **_当javac 查询到所有在getSupportedAnnotationTypes()里面注册过的注解后,才会调用process()方法_** process()方法有两个参数 ``` public boolean process(Set annotations, RoundEnvironment roundEnv) ``` 着重介绍第二个,第二个参数代表所有和这个注解有关信息的集合,如下 例子 所在路径 ``` cxy.com.myviewbind.Main2Activity ``` ``` @BindView(R.id.button) Button button ; ``` | 方法名 |含义| |:--:|:--:| |roundEnv.getSimpleName()|button| |roundEnv.getEnclosingElement()|cxy.com.myviewbind.Main2Activity| |roundEnv.getAnnotation()|R.id.button 所对应的整数值| |roundEnv.asType()|android.widget.Button| 有这些就可以生成我们需要的类了 我们看一下process()中都做了什么 ``` @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { // 以activity名为key,类的抽象类为value Map 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 entry : map.entrySet()) { try { List arrts = entry.getValue().getArrts(); Set 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 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方法中获取 ### **_android lib_**(反射生成的文件) * 依赖刚才的java lib * 没什么难度,不说了 ### **_android app_**(项目文件) 使用方式和butterknife 一样 ``` 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- 哪里写的不清晰可以给我留言,我会第一时间修改 * 邮件(xingyu1112@foxmail.com) * QQ: 1209101049 ## 参考文档 [1]: http://www.cnblogs.com/tianzhijiexian/p/4807813.html "Java 注解" [2]: http://www.jianshu.com/p/8dc7a49d86be "自己动手写注解框架"