MyViewBind 手把手教你写自己的butterknife
2016-11-08 15:50 阅读(234)

项目地址:<a href="https://github.com/LongMaoC/MyViewBind" _src="https://github.com/LongMaoC/MyViewBind">https://github.com/LongMaoC/MyViewBind


项目主要是为了让大家了解 写一个动态注入的框架 是怎么一个流程,都需要什么,怎么做。</p>

大体思路

思路

编译期间,通过自定义处理器,动态生成代码</p>

程序运行时,通过反射调用 动态生成的代码中的方法

最后达到绑定控件的目的

整体结构

整个项目需要三个model

java lib 中主要写处理器和注解,androdi lib 中写反射。处理器必须继承 AbstractProcessor ,而&nbsp;AbstractProcessor 属于 javax , 所以需要建立两种项目</p>

步骤

java lib(注解和控制器)

结构

image

处理器</h4>

注册

处理器的作用就是在编译期间执行我们想生成的代码,javac会自动寻找继承了 AbstractProcessor 的类

注册处理器(两种方式)

在main文件下创建(不推荐)

resources/META-INF/javax.annotation.processing.Processor在文件中写</p>

格式:包名+“.”+类名

com.example.MyProcessor

依赖

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();
}

在继承&nbsp;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(反射生成的文件)

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-

哪里写的不清晰可以给我留言,我会第一时间修改