探索Android开源框架 - 6. ButterKnife使用及源码解析

探索Android开源框架 - 6. ButterKnife使用及源码解析,第1张

探索Android开源框架 - 6. ButterKnife使用及源码解析 相关知识
  • ButterKnife中最重要的就是先自定义注解,再通过APT在编译期解析注解,解析器中又会用到反射,然后通过javapoet库来生成模板代码,
    要想学习其源码,首先要基本的了解注解和反射的知识;
注解
  • Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制;
  • Java中的类、方法、变量、参数和包等都可以被标注,Java 标注可以通过反射获取标注内容;
    在编译器生成类文件时,标注可以被嵌入到字节码中,Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容;
  • 注解以@注解名的形式存在于代码中,也支持自定义 Java 注解;
  • 注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记。程序可以利用ava的反射机制来了解你的类及各种元素上有无何种标记,针对不同的标记,就去做相应的事件。
内置注解 普通注解
  • 作用在代码的注解,在java.lang中
@Override
  • 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated
  • 标记表示过时的,不推荐使用。可以用于修饰方法,属性,类。如果使用被此注解修饰的方法,属性或类,会报编译警告。
@SuppressWarnings
  • 指示编译器去忽略注解中声明的警告
新增的普通注解 @SafeVarargs
  • Java7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告;
@FunctionalInterface
  • Java8开始支持,标识一个匿名函数或函数式接口;
@Repeatable
  • Java8开始支持,标识某注解可以在同一个声明上使用多次;
元注解
  • 作用在其他注解的注解,在java.lang.annotation中
@Retention
  • 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
  • 其可选策略如下:
  1. RetentionPolicy.SOURCE: 注解仅存在于源码中,在class字节码文件中不包含;
  2. RetentionPolicy.CLASS: 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得;
  3. RetentionPolicy.RUNTIME: 注解会在class字节码文件中存在,在运行时可以通过反射获取到;
  • 如果自定义注解,只存在于源码或字节码中无法发挥作用,在运行期间能获取到注解才能实现我们目的,所以自定义注解中是使用 @Retention(RetentionPolicy.RUNTIME);
@Target
  • 标记这个注解作用的范围
  • 其可选类型如下
- ElementType.TYPE: 接口、类、枚举、注解
- ElementType.FIELD:属性字段、枚举的常量
- ElementType.METHOD:方法
- ElementType.PARAMETER: 方法参数
- ElementType.CONSTRUCTOR: 构造函数
- ElementType.LOCAL_VARIABLE: 局部变量
- ElementType.ANNOTATION_TYPE: 注解(@Retention注解中就使用该属性)
- ElementType.PACKAGE: 包
- ElementType.TYPE_PARAMETER:类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
- ElementType.TYPE_USE:类型使用.可以用于标注任意类型除了 class (jdk1.8加入)
@documented
  • 标记这些注解是否包含在用户文档中
@Inherited
  • 如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解
自定义注解
  • 创建自定义注解和创建一个接口相似,但是注解的interface关键字需要以@符号开头,可以为注解声明方法,示例如下
//创建:
@documented // 此注解包含在用户文档中
@Target(ElementType.METHOD)//此注解只能用在方法上
@Inherited//子类可以继承父类中的此注解
@Retention(RetentionPolicy.RUNTIME)// 此注解保存在运行时,可以通过反射访问
public @interface MyAnnotation {
    //注解方法不能带有参数,可以有默认值,
    //返回值类型限定为:基本类型、String、Enums、Annotation或者是这些类型的数组
    String name() ;//没有默认值,使用时需要显示赋值
    int age() default  -1;//有默认值,使用时可以不赋值
}

//使用:
@MyAnnotation(name = "ljy",age = 18)
private fun initView() {

}
实战:使用自定义注解+AOP实现打印方法耗时
  • 上面说了自定义注解如何定义,那么我们来结合实际业务需求写一个实战的例子:打印方法耗时,当然也可以用来做埋点统计
  1. 创建自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GetTime {
    String tag() default "";
}
  1. 通过AOP解析注解,我这里用的aspectj框架,关于AOP并不是本篇相关的技术就不做展开说明了;
@Around("execution(@GetTime * *(..))")
public void getTime(ProceedingJoinPoint joinPoint) throws Throwable {
    MethodSignature joinPointObject = (MethodSignature) joinPoint.getSignature();
    Method method = joinPointObject.getMethod();

    boolean flag = method.isAnnotationPresent(GetTime.class);
    LjyLogUtil.d("flag:"+flag);
    String tag = null;
    if (flag) {
        GetTime getTime = method.getAnnotation(GetTime.class);
        tag = getTime.tag();
    }
    if (TextUtils.isEmpty(tag)) {
        Signature signature = joinPoint.getSignature();
        tag = signature.toShortString();
    }
    long time = System.currentTimeMillis();
    try {
        joinPoint.proceed();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
    LjyLogUtil.d( tag+" get time: " + (System.currentTimeMillis() - time));
}
  1. 使用注解:给需要统计的方法添加注解,例如Application.onCreate方法里面经常会做许多初始化 *** 作,我们可能很关系它的耗时过长,影响应用的启动时长
@GetTime(tag = "MyApplication.onCreate() 耗时")
@Override
public void onCreate() {
    super.onCreate();
    ProcessLifecycleOwner.get().getLifecycle().addObserver(new ApplicationLifecycleObserver());
    MMKV.initialize(this);
    GreenDaoHelper.getInstance().init(getApplicationContext());
    ARouter.init(MyApplication.this);
    // Application的onCreate方法中开启卡顿监控
    BlockCanary.install(this, new AppBlockCanaryContext()).start();
    //Application的onCreate方法中初始化ANR-WatchDog
    new ANRWatchDog().start();
    NetworkListener.getInstance().init(this);
}

反射
  • 在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;
  • 反射就是把java类中的各种成分映射成一个个的Java对象;
  • 反射有两个作用:1.反编译:.class->.java; 2.通过反射机制访问java对象中的属性,方法,构造器等;
  • 实现反射,实际上是得到Class对象
常用类
 java.lang.Class //类的创建
 java.lang.reflect.Constructor //反射构造方法
 java.lang.reflect.Field //反射属性
 java.lang.reflect.Method //反射方法
 java.lang.reflect.Modifier //访问修饰符的信息
获取类(Class)对象
  • 三种获取Class对象的方法
//1.会让ClassLoader装载类,并进行类的初始化
val c1 = Class.forName("com.ljy.demo.Person")
//2.会让ClassLoader装载类,不进行类的初始化 *** 作
val c2 = Person::class.java
//3.在实例中获取,返回类对象运行时真正所指的对象
val c3 = Person("bob").javaClass
反射构造方法
  • 无参数创建对象
val person = c1.newInstance() as Person
LjyLogUtil.d(person.toString())
//new Person()是直接创建一个实列,同时完成类的装载和连接
//newInstance是使用类加载机制,可以灵活的创建类的实例,更换类的时候无需修改之前的代码
  • 有参数的创建对象: 使用Constructor
val constructor = c1.getConstructor(String::class.java)
val person2 = constructor.newInstance("Emily") as Person
LjyLogUtil.d(person2.toString())
  • 遍历构造器
for (it in c1.declaredConstructors) {
    LjyLogUtil.d("declaredConstructors.it:")
    for (i in it.parameterTypes) {
        LjyLogUtil.d("it.parameterTypes.i:${i.name}")
    }
}
反射属性
  • 获取属性(Field)
val fieldAge = c1.getDeclaredField("age")
val fieldName = c1.getDeclaredField("name")
  • 遍历属性
for (it in c1.declaredFields) {
    LjyLogUtil.d("declaredFields.it:${it.name}")
}
  • 修改属性
//取消封装,特别是取消私有字段的访问限制,并不是将方法的权限设置为public,而是取消java的权限控制检查,
// 所以即使是public方法,其isAccessible默认也是false
fieldAge.isAccessible = true
fieldName.isAccessible = true
fieldAge.set(person2, 18)
LjyLogUtil.d("fieldAge.name=${fieldAge.name}")
反射方法
  • 获取方法
val method = c1.getDeclaredMethod("setAge", Int::class.java)//获取类中的方法
method.invoke(person2, 22)//通过反射调用方法
LjyLogUtil.d(person2.toString())
  • 遍历方法
for (it in c1.declaredMethods) {
    LjyLogUtil.d("declaredMethods.it:${it.name}")
}
反射静态方法
val clz = Class.forName("com.ljy.publicdemo.util.LjyLogUtil")
val m = clz.getDeclaredMethod("d", CharSequence::class.java)
m.invoke(LjyLogUtil::class.java, "log.d")
反射泛型参数方法
//class Test {
//    public void test(T t){
//        LjyLogUtil.d("Test.test(),t:"+t);
//    }
//}
val clz2 = Class.forName("com.ljy.publicdemo.activity.Test")
//注意这里有个泛型的基础--泛型擦除,编译器会自动类型向上转型,T向上转型是Object,所以下面第二个参数是Object.class
val tm = clz2.getDeclaredMethod("test", Object::class.java)
tm.isAccessible = true
tm.invoke(Test(), 666)
访问修饰符的信息
val modifierField = Modifier.toString(fieldName.modifiers)
val modifierMethod = Modifier.toString(method.modifiers)
LjyLogUtil.d("modifierField=$modifierField, modifierMethod=$modifierMethod")
实践:通过反射获取运行时注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GetTime {
    String tag() default "";
}

public class Test {
    @GetTime(tag = "做啥子")
    private static void doSomething(String info) {
        //todo...
    }

    public static void main(String[] args) {
        Test test = new Test();
        GetTime getTime ;
        Class clazz = test.getClass();
        try {
            Method method = clazz.getMethod("doSomething", String.class);
            getTime = method.getAnnotation(GetTime.class);
            System.out.print("needLogin:"+getTime.tag());

        } catch (NoSuchMethodException e) {
            System.out.print("NoSuchMethod");

        }
    }
}
实践:通过反射越过泛型检查
  • 泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
public static void main(String[] args) throws Exception{
    ArrayList strList = new ArrayList<>();
    strList.add("aaa");
    strList.add("bbb");

//	strList.add(100);
    //获取ArrayList的Class对象,反向的调用add()方法,添加数据
    Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
    //获取add()方法
    Method m = listClass.getMethod("add", Object.class);
    //调用add()方法
    m.invoke(strList, 100);

    //遍历集合
    for(Object obj : strList){
        System.out.println(obj);
    }
}

实践:通过反射运行配置文件内容
  • 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改,我们只需要将新类发送给客户端,并修改配置文件即可
//student类:
public class Student {
	public void show(){
		System.out.println("is show()");
	}
}
// 配置文件以txt文件为例子(pro.txt):
className = cm.ljy.Student
methodName = show
//测试类
public class Demo {
	public static void main(String[] args) throws Exception {
		//通过反射获取Class对象
		Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"
		//2获取show()方法
		Method m = stuClass.getMethod(getValue("methodName"));//show
		//3.调用show()方法
		m.invoke(stuClass.getConstructor().newInstance());

	}

	//此方法接收一个key,在配置文件中获取相应的value
	public static String getValue(String key) throws IOException{
		Properties pro = new Properties();//获取配置文件的对象
		FileReader in = new FileReader("pro.txt");//获取输入流
		pro.load(in);//将流加载到配置文件对象中
		in.close();
		return pro.getProperty(key);//返回根据key获取的value值
	}
}
实践:动态代理
  • java的反射机制提供了动态代理模式实现,代理模式的作用是为其他对象提供一种代理,以控制对这个对象的访问
interface Subject {
    fun doSomething()
}

class Test : Subject {
    override fun doSomething() {
        LjyLogUtil.d("Test.doSomething")
    }
}

class DynamicProxy(private val target: Subject) : InvocationHandler {
    override fun invoke(proxy: Any?, method: Method?, args: Array?): Any? {
        LjyLogUtil.d("Proxy:${proxy?.javaClass?.name}")
        LjyLogUtil.d("before target")
        //Kotlin中数组转为可变长参数,通过前面加*符号
        val invoke = method!!.invoke(target, *(args ?: emptyArray()))
        LjyLogUtil.d("after target")
        return invoke
    }
}

//使用:
val test = Test()
val myProxy = DynamicProxy(test)
val subject: Subject =
    Proxy.newProxyInstance(
        test.javaClass.classLoader,
        test.javaClass.interfaces,
        myProxy
    ) as Subject
subject.doSomething()
LjyLogUtil.d("subject.className:" + subject.javaClass.name)
实践:动态创建fragment
//管理fragment标题及路径的类
public class PageConfig {
    public static List pageTitles = new ArrayList();

    public static List getPageTitles(Context context) {
        pageTitles.clear();
        pageTitles.add("Fragment1");
        pageTitles.add("Fragment2");
        pageTitles.add("Fragment3");
        return pageTitles;
    }

    private static final String PATH_FRAGMENT1 = "com.ljy.publicdemo.activity.fragment.Fragment1";
    private static final String PATH_FRAGMENT2 = "com.ljy.publicdemo.activity.fragment.Fragment2";
    private static final String PATH_FRAGMENT3 = "com.ljy.publicdemo.activity.fragment.Fragment3";


    public static String[] fragmentNames = {
            PATH_FRAGMENT1,
            PATH_FRAGMENT2,
            PATH_FRAGMENT3,
    };
}

//通过反射遍历添加
class FragmentActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_frag)
        try {
            //遍历Fragment地址
            for (address in PageConfig.fragmentNames) {
                //反射获得Class
                val clazz = Class.forName(address)
                //创建类
                val fragment = clazz.newInstance() as Fragment
                //添加到viewPagerAdapter的资源
                supportFragmentManager
                        .beginTransaction()
                        .add(R.id.frame_layout, fragment)
                        .commit()
            }
        } catch (e: ClassNotFoundException) {
        } catch (e: IllegalAccessException) {
        } catch (e: InstantiationException) {
        }
    }
}
反射简化: jOOR
  • 当预期工程非常多的使用到反射时,我们需要更加简化的工具来优化开发流程,一个很棒的反射框架jOOR,非常轻量,让代码更加优雅
  • 项目地址 https://github.com/jOOQ/jOOR
//1. 添加依赖
implementation 'org.jooq:joor:0.9.13'
//2. 使用
val helloStr: String? = Reflect.onClass("java.lang.String")//类似于Class.forName
        .create("Hello jOOR")//调用类中构造方法
        .call("substring", 8)//调用类中方法
        .call("toString")
        .get()//获取包装好的对象
LjyLogUtil.d("helloStr=$helloStr")
//3. 也支持动态代理
Reflect.onClass(RealSubject::class.java).create().`as`(Subject::class.java).doSomething()
注意
  • 由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射;
  • jvm无法对反射部分的代码进行优化,反射生成大量的临时对象,造成大量GC,导致UI卡顿;
  • 另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题;
ButterKnife使用
  • ButterKnife使用还是非常简单的,示例如下
class ButterKnifeActivity : AppCompatActivity() {

    @BindView(R.id.et_name)
    lateinit var etName: EditText

    @BindView(R.id.et_pwd)
    lateinit var etPwd: EditText

    @BindString(R.string.login_error)
    lateinit var loginErrorMessage: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_butter_knife)
        ButterKnife.bind(this)
    }

    @onClick(R.id.btn_submit)
    fun submit() {
        LjyLogUtil.d("etName=${etName.text}")
        LjyLogUtil.d("etPwd=${etPwd.text}")
        if ("ljy".equals(etName.text) && "123".equals(etPwd.text)) {
            LjyLogUtil.d("登录成功")
        } else {
            LjyLogUtil.d(loginErrorMessage)
        }
    }
}
ButterKnife源码解析
  • 主要有下面两步:
  1. 编译的时候通过APT扫描注解,并进行相应处理,通过 javapoet库生成Java代码;
  2. 调研ButterKnife.bind(this)方法时通过反射将id与相应的上下文绑定;
ButterKnifeProcessor类:通过APT扫描解析注解并生成代码
  • ButterKnife的APT注解解析类是ButterKnifeProcessor,继承了AbstractProcessor,我们先来看看他是如何实现的
init方法
  • 重写init方法初始化工具类, 注意该方法添加了synchronized关键字
 @Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

    String sdk = env.getOptions().get(OPTION_SDK_INT);
    。。。
    debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
    。。。
  }
getSupportedAnnotationTypes方法
  • 该方法返回支持的注解类型,用于指定定义的注解
@Override public Set getSupportedAnnotationTypes() {
    Set types = new linkedHashSet<>();
    for (Class annotation : getSupportedAnnotations()) {
      types.add(annotation.getCanonicalName());
    }
    return types;
  }

  //指定定义的注解,注册了一系列的Bindxxx注解类和监听列表LISTENERS
  private Set> getSupportedAnnotations() {
    Set> annotations = new linkedHashSet<>();

    annotations.add(BindAnim.class);
    annotations.add(BindArray.class);
    annotations.add(BindBitmap.class);
    annotations.add(BindBool.class);
    annotations.add(BindColor.class);
    annotations.add(BindDimen.class);
    annotations.add(BindDrawable.class);
    annotations.add(BindFloat.class);
    annotations.add(BindFont.class);
    annotations.add(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);

    return annotations;
  }

  private static final List> LISTENERS = Arrays.asList(
        OnCheckedChanged.class, //
        OnClick.class, //
        OnEditorAction.class, //
        OnFocusChange.class, //
        OnItemClick.class, //
        OnItemLongClick.class, //
        OnItemSelected.class, //
        OnLongClick.class, //
        OnPageChange.class, //
        OnTextChanged.class, //
        OnTouch.class //
    );
process方法
  • 最核心的方法, 注解的处理和生成代码都是在这个方法中完成
@Override public boolean process(Set elements, RoundEnvironment env) {
    //查询并解析注解,保存到bindingMap中
    Map bindingMap = findAndParseTargets(env);

    for (Map.Entry entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        //通过javaFile.writeTo(filer)生成java源文件
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
}
findAndParseTargets方法
  • 上面的process第一行就调用了findAndParseTargets方法,他主要是用来扫描注解,并将注解信息保存到map
private Map findAndParseTargets(RoundEnvironment env) {
    Map builderMap = new linkedHashMap<>();
    Set erasedTargetNames = new linkedHashSet<>();

    // 扫描@BindAnim注解并调用parseResourceAnimation方法解析处理,保存到上面两个集合中
    for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceAnimation(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindAnim.class, e);
      }
    }
    。。。//省略了其他的一系列注解扫描
    //同样的扫描并解析监听注解,保存到集合中
    for (Class listener : LISTENERS) {
        findAndParseListener(env, listener, builderMap, erasedTargetNames);
    }

    Map classpathBindings =
        findAllSupertypeBindings(builderMap, erasedTargetNames);

    // 创建一个双向队列entries,放入builderMap.entrySet()
    Deque> entries =
        new ArrayDeque<>(builderMap.entrySet());
    //
    Map bindingMap = new linkedHashMap<>();
    //遍历entries中每个
    while (!entries.isEmpty()) {
      Map.Entry entry = entries.removeFirst();

      TypeElement type = entry.getKey();
      BindingSet.Builder builder = entry.getValue();

      TypeElement parentType = findParentType(type, erasedTargetNames, classpathBindings.keySet());
      //判断是否有父类
      if (parentType == null) {
        bindingMap.put(type, builder.build());
      } else {
        BindingInformationProvider parentBinding = bindingMap.get(parentType);
        if (parentBinding == null) {
          parentBinding = classpathBindings.get(parentType);
        }
        if (parentBinding != null) {
          builder.setParent(parentBinding);
          bindingMap.put(type, builder.build());
        } else {
          // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
          entries.addLast(entry);
        }
      }
    }
    return bindingMap;
}
BindingSet.brewJava方法
  • 再回到process方法,接下来的循环中,通过brewJava返回javapoet的JavaFile对象,用来生成java文件,brewJava代码如下
JavaFile brewJava(int sdk, boolean debuggable) {
    TypeSpec bindingConfiguration = createType(sdk, debuggable);
    //通过javapoet的builder构造器将上面得到的bindingConfiguration对象构建生成一个JavaFile对象
    return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
}
BindingSet.createType方法
  • createType中使用javapoet库生成TypeSpec对象,里面保存各种的绑定配置信息
private TypeSpec createType(int sdk, boolean debuggable) {
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC)
        .addOriginatingElement(enclosingElement);
    if (isFinal) {
      result.addModifiers(FINAL);
    }

    if (parentBinding != null) {
      result.superclass(parentBinding.getBindingClassName());
    } else {
      result.addSuperinterface(UNBINDER);
    }

    if (hasTargetField()) {
      result.addField(targetTypeName, "target", PRIVATE);
    }

    if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) {
      result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {
      result.addMethod(createBindingConstructorForDialog());
    }
    if (!constructorNeedsView()) {
      result.addMethod(createBindingViewDelegateConstructor());
    }
    result.addMethod(createBindingConstructor(sdk, debuggable));

    if (hasViewBindings() || parentBinding == null) {
      result.addMethod(createBindingUnbindMethod(result));
    }

    return result.build();
}
ButterKnife类:通过反射调用APT生成的java文件的方法,完成绑定 1. bind
  • 代码如下:
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
    //sourceView:当前界面的顶级父View
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
}

@NonNull @UiThread
public static Unbinder bind(@NonNull View target) {
    return bind(target, target);
}

@NonNull @UiThread
public static Unbinder bind(@NonNull Dialog target) {
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
}
2. createBinding
  • 可以看到单参的bind有几个重载方法,但最终都是调用了两个参数的bind方法,其实这个方法早期版本的名字可能更容易理解:createBinding,代码如下:
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    Class targetClass = target.getClass();
    //这里调用findBindingConstructorForClass获取APT生成文件的构造器
    Constructor constructor = findBindingConstructorForClass(targetClass);
    //判空
    if (constructor == null) {
      return Unbinder.EMPTY;
    }
    //通过反射创建其对象,也就是createBinding
    try {
      return constructor.newInstance(target, source);
    } catch 。。。//省略异常处理代码
}
3. findBindingConstructorForClass
  • 再看一下上面代码中调用的findBindingConstructorForClass方法,代码如下:
static final Map, Constructor> BINDINGS = new linkedHashMap<>();

@Nullable @CheckResult @UiThread
private static Constructor findBindingConstructorForClass(Class cls) {
    //1. 先判断binding map中有没有缓存过cls,有则直接取用
    Constructor bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      return bindingCtor;
    }
    //2. 判断类路径是否以android.或androidx.或java.开头,是则返回null
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      return null;
    }
    try {
      //使用classLoader加载cls对应的绑定类(APT生成的),获取其class对象
      Class bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //通过反射调用其构造器创建绑定类的对象
      bindingCtor = (Constructor) bindingClass.getConstructor(cls, View.class);
    } catch (ClassNotFoundException e) {
     //没找到则递归调用找其父类的
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      //没有找到构造函数则抛出异常
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
}
番外:使用APT自定义注解处理器
  • 即Annotation Processing Tool,编译时注解处理器, APT可以用来在编译时扫描和处理注解
  • APT技术被广泛的运用在Java框架中,ButterKnife,EventBus,Dagger2以及ARouter等都运用到APT;
  • 新版的ButterKnife是使用APT编译时注解处理器在编译时扫描注解,并做相应处理,生成java代码,生成Java代码是调用javapoet库生成的;
  • 那么让我们尝试使用APT实现一个最简版的ButterKnife吧
使用流程 1. 新建Java Module(我的命名是"annotation")用于存放自定义注解,gradle配置文件如下:
apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

sourceCompatibility = "1.8"
targetCompatibility = "1.8"
  • 如果创建Android Library模块会发现不能找到AbstractProcessor这个类,这是因为Android平台是基于OpenJDK的,
    而OpenJDK中不包含APT的相关代码。因此,在使用APT时,必须在Java Library中进行,如果不知道如何创建,直接修改为上面的配置即可;
2. 新建Java Module(我的命名是"processor")用于存放自定义apt注解处理器,并依赖annotation module, gradle配置文件如下:
apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //AutoService是Google开发的一个库,用来生成meta-INF/services/javax.annotation.processing.Processor文件
    implementation 'com.google.auto.service:auto-service:1.0-rc6'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    //square的自动生成代码库
    implementation 'com.squareup:javapoet:1.11.1'
    implementation project(path: ':annotation', configuration:'default')
}

sourceCompatibility = "1.8"
targetCompatibility = "1.8"
3. 将前两部创建的module添加到主工程app module下
//导入自定义注解
implementation project(path: ':annotation')
// 指定自定义注释处理器 ,如果使用kotlin, replace annotationProcessor with kapt
kapt project(path: ':processor')
4. 自定义注解
@Target(ElementType.FIELD) // 作用于成员属性上
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}
5. Element元素
  • Java中Element是一个接口,表示一个程序元素,它可以指代包、类、方法或者一个变量
PackageElement: 表示一个包程序元素。提供对有关包及其成员的信息的访问。
ExecutableElement: 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。
TypeElement: 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。
VariableElement: 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。
  • 自定义一个ElementInfo类,用于存储解析Element元素的信息
internal class ElementInfo(
    
    var packageName: String,
    
    var className: String,
    
    var typeName: String,
    
    var nodeName: String,
    
    var value: Int
)
6. 自定义注解处理器,需继承AbstractProcessor
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
    private Types mTypeUtils;
    private Messager mMessagerUtils;
    private Filer mFilerUtils;
    private Elements mElementUtils;
    
    private Map> mCache = new HashMap<>();

    
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        //类信息工具类
        mTypeUtils = processingEnv.getTypeUtils();
        // Messager可以用来打印错误信息;
        mMessagerUtils = processingEnv.getMessager();
        // Filer可以用来编写新文件;
        mFilerUtils = processingEnv.getFiler();
        // Elements是一个可以处理Element的工具类。
        mElementUtils = processingEnv.getElementUtils();
    }

    
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
//        return super.getSupportedSourceVersion();
    }

    
    @Override
    public Set getSupportedAnnotationTypes() {
        Set types = new linkedHashSet<>();
        for (Class annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
//        return super.getSupportedAnnotationTypes();
    }

    private Set> getSupportedAnnotations() {
        Set> annotations = new linkedHashSet<>();
        annotations.add(LjyBindView.class);
        annotations.add(ClickGap.class);
        annotations.add(GetTime.class);
        annotations.add(NeedLogin.class);
        return annotations;
    }


    
    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        if (annotations == null || annotations.isEmpty()) {
            return false;
        }
        //扫描所有被@GetTime注解的元素
        Set elements = roundEnv.getElementsAnnotatedWith(LjyBindView.class);
        if (elements == null || elements.isEmpty()) {
            return false;
        }
        //遍历被@GetTime注解的元素
        for (Element element : elements) {
            if (element.getKind() != ElementKind.FIELD) {
                throw new RuntimeException("only classes can be annotated with " + element.getSimpleName());
            }
            // 获取节点包信息
            String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString();
            // 获取节点类信息,由于 @NeedLogin 作用方法上,所以这里使用 getEnclosingElement() 获取父节点信息
            String className = element.getEnclosingElement().getSimpleName().toString();
            // 获取节点类型
            String typeName = element.asType().toString();
            // 获取节点标记的属性名称
            String nodeName = element.getSimpleName().toString();
            // 获取注解的值
            int value = element.getAnnotation(LjyBindView.class).value();

            // 打印
            mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "packageName:" + packageName);
            mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "className:" + className);
            mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "typeName:" + typeName);
            mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "nodeName:" + nodeName);
            mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "value:" + value);

            // 缓存KEY
            String key = packageName + "." + className;
            // 缓存节点信息
            List nodeInfos = mCache.get(key);
            if (nodeInfos == null) {
                nodeInfos = new ArrayList<>();
                nodeInfos.add(new ElementInfo(packageName, className, typeName, nodeName, value));
                // 缓存
                mCache.put(key, nodeInfos);
            } else {
                nodeInfos.add(new ElementInfo(packageName, className, typeName, nodeName, value));
            }

        }

        // 判断临时缓存是否不为空
        if (!mCache.isEmpty()) {
            // 遍历临时缓存文件
            for (Map.Entry> stringListEntry : mCache.entrySet()) {
                try {
                    // 创建文件
                    createFile(stringListEntry.getValue());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    
    private void createFile(List infos) throws IOException {
        ElementInfo info = infos.get(0);
        // 生成的文件名(类名)
        String className = info.getClassName() + "__ViewBinding";
        // 方法参数
        ParameterSpec parameterSpec = ParameterSpec.builder(
                ClassName.get(info.getPackageName(), info.getClassName()), "target")
                .build();
        // 方法
        MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addParameter(parameterSpec)
                .returns(void.class);
        // 给方法添加代码块
        for (ElementInfo nodeInfo : infos) {
            // target.textView = (TextView) target.findViewByID(R.id.text_view);
            methodSpecBuilder.addStatement("target.$L = ($L)target.findViewById($L)",
                    nodeInfo.getNodeName(),
                    nodeInfo.getTypeName(),
                    nodeInfo.getValue());
        }
        // 类
        TypeSpec typeSpec = TypeSpec.classBuilder(className)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(methodSpecBuilder.build())
                .build();
        // 生成文件
        JavaFile.builder(info.getPackageName(), typeSpec)
                .build()
                .writeTo(mFilerUtils);
    }
}
  • reBuild项目之后,就可以|在项目的appbuildgeneratedsourcekaptdebug包名 路径下找到自动生成的文件ButterKnifeActivity__ViewBinding,其内容如下:
public class ButterKnifeActivity__ViewBinding {
  public static void bind(ButterKnifeActivity target) {
    target.etName = (android.widget.EditText)target.findViewById(2131296530);
  }
}
7. 自定义LjyButterKnife类
  • 然后就可以仿照ButterKnife在App模块下新建一个LjyButterKnife类,利用反射来调用上面方法
public class LjyButterKnife {
    public static void bind(Activity target) {
        try {
            Class clazz = target.getClass();
            // 反射获取apt生成的指定类
            Class bindViewClass = Class.forName(clazz.getName() + "__ViewBinding");
            // 获取它的方法
            Method method = bindViewClass.getMethod("bind", clazz);
            // 执行方法
            method.invoke(bindViewClass.newInstance(), target);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
8. 在Activity中使用
  • 可以看到我是和ButterKnife同时使用的,也不会有任何冲突问题
class ButterKnifeActivity : AppCompatActivity() {

    @LjyBindView(R.id.et_name)
    lateinit var etName: EditText

    @BindView(R.id.et_pwd)
    lateinit var etPwd: EditText

    @BindString(R.string.login_error)
    lateinit var loginErrorMessage: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_butter_knife)
        ButterKnife.bind(this)
        LjyButterKnife.bind(this)
    }

    @onClick(R.id.btn_submit)
    fun submit() {
        LjyLogUtil.d("etName=${etName.text}")
        LjyLogUtil.d("etPwd=${etPwd.text}")
        if ("ljy".equals(etName.text) && "123".equals(etPwd.text)) {
            LjyLogUtil.d("登录成功")
        } else {
            LjyLogUtil.d(loginErrorMessage)
        }
    }
}
参考
  • JakeWharton/ButterKnife源码
  • ButterKnife源码全面解析
  • ButterKnife原理解析
  • ButterKnife源码分析
  • Java编译期注解处理器APT
我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章

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

原文地址: http://outofmemory.cn/zaji/5438292.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-11
下一篇 2022-12-11

发表评论

登录后才能评论

评论列表(0条)

保存