Android编译时技术,仿照ButterKnife,实现自己的自动注入框架

学习目标:

Android编译时技术,仿照ButterKnife,实现自己的自动注入框架

学习内容:

提示:这里可以添加要学的内容
1、 首先我们肯定要知道怎么使用ButterKnife
2、 了解ButterKnife
3、 打造自己的ButterKnife


学习开始:

ps:注释记得看

1.ButterKnife使用
想要了解ButterKinfe的详细使用方法,请绕道别的博客,嘿嘿。

我们这里只是简单介绍一下,我们既然想要写一个差不多的框架,那肯定要先来使用一下它,然后我们也要实现它的这种功能。
1.只需在app的 build.gradle 中添加如下代码:

dependencies {
  //依赖核心库
  implementation 'com.jakewharton:butterknife:8.8.1'
  //注解处理器
  annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}

然后在Activity中

@BindView(R.id.tv)
TextView tv;

@Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     ButterKnife.bind(this);
 }


 @OnClick(R.id.tv)
 public void onViewClicked() {
 }

到这里,我们就简单的了解了ButterKnife的使用,感觉是不是非常方便,这是因为ButterKnife采用了编译时技术来实现的。
我们的代码是有生命周期的分别为源码期,编译期,运行期,下面看一张图
在这里插入图片描述
所以ButterKinfe就是在编译期通过编译期自动生成一些代码,这样就等于我们写了,所以这是关键。
我们在通过看类,我们使用ButterKnife在编译的时候会生成一些java文件,而这些文件就是实现它的功能主要java文件。我们来看一下生成的文件长啥样。
我的在这个文件目录下,你的也许跟我不一样。
在这里插入图片描述
我们来看一下这个ButterKnife帮我们生成的类(MainActivity$$ViewBinder)到底干了什么,代码如下

public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {
  //target就是我们MainActivity	
  @Override
  public Unbinder bind(final Finder finder, final T target, Object source) {
    InnerUnbinder unbinder = createUnbinder(target);
    View view;
    view = finder.findRequiredView(source, 2131165325, "field 'tv' and method 'onViewClicked'");
    //这里就是把找到的textView 赋值给target的tv,这里的tv就是MainActivity的变量名称
    target.tv = finder.castView(view, 2131165325, "field 'tv'");
    unbinder.view2131165325 = view;
    //这里就是给这个view设置了监听事件,回调的就是我们MainActivity里的onViewClicked方法
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onViewClicked();
      }
    });
    return unbinder;
  }

  protected InnerUnbinder<T> createUnbinder(T target) {
    return new InnerUnbinder(target);
  }

  protected static class InnerUnbinder<T extends MainActivity> implements Unbinder {
    private T target;

    View view2131165325;

    protected InnerUnbinder(T target) {
      this.target = target;
    }

    @Override
    public final void unbind() {
      if (target == null) throw new IllegalStateException("Bindings already cleared.");
      unbind(target);
      target = null;
    }

    protected void unbind(T target) {
      view2131165325.setOnClickListener(null);
      target.tv = null;
    }
  }
}

而上面的这个给我们自动生成的类,我们是通过ButterKnife.bind(this);给MainActivity变量注册了事件,为什么这么说,我们再来看这个bind方法到底干了啥?

........
//看这个target就是MainActivity
 public static Unbinder bind(@NonNull Activity target) {
    return bind(target, target, Finder.ACTIVITY);
  }
 
static Unbinder bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
    Class<?> targetClass = target.getClass();
    try {
      if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
      //这个是去找相对应生成的类
      ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
      //看这个,就是给我们绑定了事件
      return viewBinder.bind(finder, target, source);
    } catch (Exception e) {
      throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
    }
  }

//这个方法就是去找ButterKnife给我们生成的类
 private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
      throws IllegalAccessException, InstantiationException {
    ViewBinder<Object> viewBinder = BINDERS.get(cls);
    if (viewBinder != null) {
      if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
      return viewBinder;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return NOP_VIEW_BINDER;
    }
    try {
      //看到这$$ViewBinder后缀没,就是通过这个加载了生成的类。
      Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");
      //noinspection unchecked
      //生成类对象
      viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
      if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      viewBinder = findViewBinderForClass(cls.getSuperclass());
    }
    BINDERS.put(cls, viewBinder);
    return viewBinder;
  }
.........

所以大概流程就是这样,我们接下就来实现一个我们自己的简易ButterKnife,开干。
首先建两个java Library.
MyAnnotation

package com.suyong.myannotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 这是注解类
**/
//申明注解修饰的类型
//这是规定了修饰成员变量
@Target(ElementType.FIELD)
//声明注解的生命周期
@Retention(RetentionPolicy.CLASS)
public @interface MyBindView {
    int value();
}

MyAnnotation-Complier

package com.suyong.myannotation_complier;

import com.google.auto.service.AutoService;
import com.suyong.myannotation.MyBindView;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.JavaFileObject;

/**
* 注解解释器类
**/
@AutoService(Processor.class)
public class AnnotationComplier extends AbstractProcessor {

    //处理文件
    Filer filer;
    //处理log日志
    Messager messager;

    //初始化
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
    }

    //声明这个注解处理器要处理的注解有哪些
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(MyBindView.class.getCanonicalName());
        return types;
    }

    //申明注解处理器支持的java版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

    //查找被我们注解标记的内容
	//VariableElement 变量
	//ExecutableElement 方法
	//TypeElement 类
	//PackageElement 包
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //查找所有被MyBindView注解标记的内容
        Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(MyBindView.class);
        // 类名 这个类里的所有用MyBindView标记的变量
        Map<String, List<VariableElement>> map = new HashMap<>();
        for (Element element : elementsAnnotatedWith) {
            VariableElement variableElement = (VariableElement) element;
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
            String activityName = typeElement.getSimpleName().toString();
            List<VariableElement> variableElements = map.get(activityName);
            if (variableElements == null) {
                variableElements = new ArrayList<>();
                map.put(activityName, variableElements);
            }
            variableElements.add(variableElement);
        }
        //自动生成代码
        if (map.size() > 0) {
            Writer writer = null;
            Iterator<String> iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
                String activityName = iterator.next();
                List<VariableElement> variableElements = map.get(activityName);
                String packageName = getPackageName(variableElements.get(0));
                //生成类的名字
                String newActivityName = activityName + "$$MyViewBinder";
                try {
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "."+newActivityName);
                    writer = sourceFile.openWriter();
                    StringBuffer buffer = new StringBuffer();
                    buffer.append("package " + packageName + ";\n");
                    buffer.append("import android.view.View;\n");
                    buffer.append("public class " + newActivityName +"{\n");
                    buffer.append("public void bind("+packageName+"."+activityName+" target){\n");
                    for (VariableElement variableElement : variableElements) {
                        //获取成员变量的名字
                        String filedName = variableElement.getSimpleName().toString();
                        //获取成员变量上面的注解
                        int resId = variableElement.getAnnotation(MyBindView.class).value();
                        buffer.append("target."+variableElement+"=target.findViewById("+resId+");\n");
                    }
                    buffer.append("}\n");
                    buffer.append("}\n");
                    writer.write(buffer.toString());
                    } catch(IOException e){
                        e.printStackTrace();
                    }finally{
                    if(writer!=null){
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                }
            }

            return false;
        }

        //获取包名
        private String getPackageName (VariableElement variableElement){
            Element enclosingElement = variableElement.getEnclosingElement();
            PackageElement packageOf = processingEnv.getElementUtils().getPackageOf(enclosingElement);
            return packageOf.getQualifiedName().toString();
        }
    }

然后我们需要在app build.gradle下添加如下两句

	//依赖注解类
    implementation project(path: ':MyAnnotation')
    //依赖注解解释器类
    annotationProcessor  project(path: ':MyAnnotation-Complier')

还要在app模块下建一个MyButterKinfe类

package com.suyong.mybutterkinfe;

import java.lang.reflect.Method;
//这个是为了执行我们上面代码生成的类
public class MyButterKnife {
    public static void bind(Object activity){
        String name = activity.getClass().getName();
        String binderClassName = name + "$$MyViewBinder";
        try {
        	//利用反射
            Class<?> aClass = Class.forName(binderClassName);
            Object binder = aClass.newInstance();
            Method bindMethod = aClass.getDeclaredMethod("bind", activity.getClass());
            bindMethod.invoke(binder,activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

最后我们可以在我的MainActivity里优雅的使用了,如下:

package com.suyong.mybutterkinfe;

import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.suyong.myannotation.MyBindView;

public class MainActivity extends AppCompatActivity {

    @MyBindView(R.id.tv)
    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyButterKnife.bind(this);
    }

}

这里的话呢,我只实现了成员变量的初始化,并没有写那些绑定事件那些东东,其实BindString,Onclick都跟我这个原理、逻辑是一致的,相信聪明的你一定能悟出来。
代码链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本项目是一个基于安卓的框架项目源码 Loonandroid是一个注解框架,不涉及任何UI效果,目的是一个功能一个方法,以方法为最小颗粒度对功能进行拆解。把功能傻瓜化,简单化,去掉重复性的代码,隐藏复杂的实现。以便团队合作或者后期修改变得简单。说框架是夸大了,主要是因为我比较喜欢偷懒,对于一个码农来说,能够偷懒,并且在不影响项目质量的情况下,是不容易的。 很多朋友看到注解就就要吐槽,会影响性能什么的。注解,确实会影响性能。通过注解自动注入,反射会让程序变慢50~100毫秒左右,从体验感基本感觉不出来.硬件性能好的手机可以忽略,经过测试无需太大的担心。我是做外包的,初衷是在不影响项目质量的前提下减少我的工作量,而且BUG其他人改起来相对比较容易,本工具专属外包码农,如果你想做精细,很在意性能数据,请看看就好。 1、基本功能 InLayer注解 InPlayer 注解 Activity生命周期注解 InView注解 InSource注解 InAll注解 后台进程注解 方法点击事件注解 基类注解 自动Fragment注解 手动Fragment注解 2、适配器功能 无适配器 无参baseAdapter 自定义一adapter 自定义二adapter 自动绑定一adapter 自动绑定二adapter 通用适配器 3、综合功能集合 网络请求模块 输入验证 跨进程通讯 Json格式化类 倒计类 4、傻瓜式下拉刷新 Listview Grid 横向Scrollview 纵向Scrollview 横向ViewPage 纵向ViewPage WebView 5、自定义模块类 自定义模块XML中使用 自定义模块变量使用 6、傻瓜式组件类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值