5. Android 框架ButterKnife源代码分析

简介:

一. ButterKnife介绍

在Android编程过程中,我们会写大量的布局和点击事件,像初始view、设置view监听这样简单而重复的操作,这些代码繁琐而又不雅观,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TextView tvSetName = findViewById(R.id.xxx);
tvSetName.setOnClickListener( new  View.OnClickListener() {
     @Override
     public  void  onClick(View v) {
         //xxxx
     }
});
TextView tvSetAge = findViewById(R.id.xxx);
tvSetAge.setOnClickListener( new  View.OnClickListener() {
     @Override
     public  void  onClick(View v) {
         //xxxx
     }
});
TextView tvSetArea = findViewById(R.id.xxx);
tvSetArea.setOnClickListener( new  View.OnClickListener() {
     @Override
     public  void  onClick(View v) {
         //xxxx
     }
});



Activity中这种代码多了之后,很不雅观。


二. 使用简介

ButterKnife使用方法比较简单,主要包括以下步骤:


  1. 引用ButterKnife包

  2. 在onCreate里面bind(setContentView之后)

  3. 绑定各种事件

  4. onDestroy里面解绑释放资源


ButterKnife使用方法


三. ButterKnife源代码下载


ButterKnife github源代码地址


直接git clone或者下载zip即可。


四. 编译

  1. Android studio打开ButterKnife源代码

    AndroidStudio->File->open->ButterKnife源代码路径->确认

  2. Build->Rebuild Project



五. 生成的aar和jar包

生成的包主要有两个

  1. butterknife-annotations-8.5.2-SNAPSHOT.jar

    路径:butterknife-annotations->build->libs


  2. butterknife-release.aar

    路径: butterknife->build->outputs->aar



六. 其他应用引用自定义ButterKnife包 

  1. 删除原来ButterKnife包引用,因为要使用自己编译的包

  2. //    compile 'com.jakewharton:butterknife:8.4.0'




  3. 拷贝文件

    拷贝上面两个文件到自己项目app模块的libs 目录


  4. 添加aar的关联

    打开app模块的build.gradle文件,添加:


  5. 1
    2
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile(name: 'butterknife-release', ext: 'aar')
  6. Build->Rebuild Project



七. 源代码分析


1. ButterKnife.bind(Activity target)过程


文件名:ButterKnife.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
static  final  Map<Class<?>, Constructor<?  extends  Unbinder>> BINDINGS =  new  LinkedHashMap<>();
 
public  static  Unbinder bind( @NonNull  Activity target) {
     Log.d( "Sandy" "ButterKnife bind.. target: "  + target);
     View sourceView = target.getWindow().getDecorView();
     return  createBinding(target, sourceView);
}
 
 
 
 
private  static  Unbinder createBinding( @NonNull  Object target,  @NonNull  View source) {
     Class<?> targetClass = target.getClass();
     if  (debug) Log.d(TAG,  "Looking up binding for "  + targetClass.getName());
     Constructor<?  extends  Unbinder> constructor = findBindingConstructorForClass(targetClass);
 
     if  (constructor ==  null ) {
       return  Unbinder.EMPTY;
     }
 
     //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
     try  {
       return  constructor.newInstance(target, source);
     catch  (IllegalAccessException e) {
       throw  new  RuntimeException( "Unable to invoke "  + constructor, e);
     catch  (InstantiationException e) {
       throw  new  RuntimeException( "Unable to invoke "  + constructor, e);
     catch  (InvocationTargetException e) {
       Throwable cause = e.getCause();
       if  (cause  instanceof  RuntimeException) {
         throw  (RuntimeException) cause;
       }
       if  (cause  instanceof  Error) {
         throw  (Error) cause;
       }
       throw  new  RuntimeException( "Unable to create binding instance." , cause);
     }
}
 
 
private  static  Constructor<?  extends  Unbinder> findBindingConstructorForClass(Class<?> cls) {
     Constructor<?  extends  Unbinder> bindingCtor = BINDINGS.get(cls);
     if  (bindingCtor !=  null ) {
       if  (debug) Log.d(TAG,  "HIT: Cached in binding map." );
       return  bindingCtor;
     }
     String clsName = cls.getName();
     if  (clsName.startsWith( "android." ) || clsName.startsWith( "java." )) {
       if  (debug) Log.d(TAG,  "MISS: Reached framework class. Abandoning search." );
       return  null ;
     }
     try  {
         Log.d( "Sandy" "findBindingConsForClass: "  + clsName +  " vindBinding name: "  +
                 clsName +  "_ViewBinding" );
       Class<?> bindingClass = cls.getClassLoader().loadClass(clsName +  "_ViewBinding" );
       //noinspection unchecked
       bindingCtor = (Constructor<?  extends  Unbinder>) bindingClass.getConstructor(cls, View. class );
       if  (debug) Log.d(TAG,  "HIT: Loaded binding class and constructor." );
     catch  (ClassNotFoundException e) {
       if  (debug) Log.d(TAG,  "Not found. Trying superclass "  + cls.getSuperclass().getName());
       bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
     catch  (NoSuchMethodException e) {
       throw  new  RuntimeException( "Unable to find binding constructor for "  + clsName, e);
     }
     BINDINGS.put(cls, bindingCtor);
     return  bindingCtor;
}




上面这段代码有几个注意点:

a. sourceView代表是DecorView,也就是我们窗口的顶级View。


b. findBindingConstructorForClass有个BINDINGS缓存,key是class,value是缓存的Unbinder对象,这样做可以加快bind速度。

因为每个类的ButterKnife注解在运行期间是不会变的,比如MainActivity有3个ButterKnife注解,那么它就是3个。除非有新的apk安装。

所以适合用缓存来实现。


c. findBindingConstructorForClass使用了递归的方法

这个方法使用了递归,不断调用父类,也就是


1
2
3
4
catch  (ClassNotFoundException e) {
       if  (debug) Log.d(TAG,  "Not found. Trying superclass "  + cls.getSuperclass().getName());
       bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
}



那为什么要这么处理呢?


因为有些Activity没有ButterKnife的注解,但是它的父类可能有,比如BaseActivity。所以需要往上递归。那什么时候递归结束呢?


1
2
3
4
5
6
7
8
9
if  (bindingCtor !=  null ) {
       if  (debug) Log.d(TAG,  "HIT: Cached in binding map." );
       return  bindingCtor;
     }
     String clsName = cls.getName();
     if  (clsName.startsWith( "android." ) || clsName.startsWith( "java." )) {
       if  (debug) Log.d(TAG,  "MISS: Reached framework class. Abandoning search." );
       return  null ;
}



如果缓存里面找到了结果,那么结束,同时返回结果;


或者类名以"android."或者"java."开头,也结束,返回null;

以Activity为例,Activity的类名是android.app.Activity,所以你的MainActivity如果递归到Activity还没有找到ButterKnife注解,那就说明你的MainActivity是没有包含ButterKnife注解的。


d. 如果子Activity和父Activity都有ButterKnife注解怎么办?

答案是返回子Activity以及其对应的 Constructor<? extends Unbinder> bindingCtor对象


那它的父Activity如果也有ButterKnife注解怎么办?怎么解析父Activity的ButterKnife注解呢? 这个问题我们待会再讲。

记为问题1


e. Constructor<? extends Unbinder> 是个什么东西?

调用ButterKnife.bind(Activity target)方法后会返回一个Unbinder对象,可以在onDestroy中调用unbind()方法,那个Unbinder是什么东西呢?这个问题待会再讲。

记为问题2.


f. clsName + "_ViewBinding"是什么类?

Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");

这个问题记为问题3.


2. ButterKnifeProcessor.java

这个类是ButterKnife里面很重要的一个类了,它继承自AbstractProcessor。


看来不懂的问题越来越多,那么有必要来学习下Java注解的知识。



八. Java注解


在分析ButterKnife代码前,需要了解Java的注解,需要了解Annotation Processor,因为ButterKnifer用到这个知识。


这里面很重要的一个知识点就是你可以编写一定的规则,让它在应用程序编译时执行你的规则,然后生成Java代码;并且生成的Java还可以参与编译。


Java注解



九. 自己定义的注解框架


1. Eclipse实现


主要是参考这篇帖子完成的,大家可以参考这篇帖子:

Eclipse中使用Java注解Processor


主要说下不同的地方:

a. source folder的创建,直接File->New->source folder一直创建不成功,后面用另外一种方法创建成功了。

项目->右击->Properties->Java Build Path->Source->Add Folder->Create New Folder->输入resources/META-INF/services->finish->ok->ok

wKiom1kUG_CSP3OvAACTzaqjj0w123.png-wh_50



2. Android studio实现


AndroidStudio下面使用Java注解Processor




十. 调试自己的自定义框架

有个时候需要调试自己写的框架是否正常运行,下面介绍下调试:


1. 在项目gradle.properties里面添加

1
2
org.gradle.daemon=true
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8011


2.Edit Configureations

wKioL1kUUgXzetkqAADJBDdHWVg272.png-wh_50


3. 增加远程调试

wKiom1kUUlWiJR1iAADIV1Xx2G0912.png-wh_50


4. 启动远程调试

wKioL1kUUqSykGMqAAAtwfDm2S0203.png-wh_50



下面的控制台会出现下面的提示:

Connected to the target VM, address: 'localhost:8011', transport: 'socket'


5. 打断点

在Processor里面打上断点,比如init, process


6. 连上手机,项目根目录命令行下执行

gradle clean connectedCheck



十一. ButterKnife使用Java注解

理解了Java注解Processor之后,ButterKnife就比较好理解了。


首先它的ButterKnifeProcessor.java继承自AbstractProcessor,重写了init和process之类的方法。

也就是说它在编译的时候会被执行,生成辅助代码。


它的辅助代码生成到哪里了呢?


在我们自己的应用程序里面搜索_ViewBinding,就可以找到已经生成好的辅助类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public  class  xxxx_ViewBinding<T  extends  LoginCloudActivity>  extends  BaseActivity_ViewBinding<T> {
   private  View view2131689593;
 
   @UiThread
   public  xxxx_ViewBinding( final  T target, View source) {
     super (target, source);
 
     View view;
     target.mEtUser = Utils.findRequiredViewAsType(source, R.id.et_user,  "field 'mEtUser'" , EditText. class );
     target.mEtPwd = Utils.findRequiredViewAsType(source, R.id.et_pwd,  "field 'mEtPwd'" , EditText. class );
     view = Utils.findRequiredView(source, R.id.btn_login,  "method 'btn_login' and method 'btn_login_long'" );
     view2131689593 = view;
     view.setOnClickListener( new  DebouncingOnClickListener() {
       @Override
       public  void  doClick(View p0) {
         target.btn_login();
       }
     });
     view.setOnLongClickListener( new  View.OnLongClickListener() {
       @Override
       public  boolean  onLongClick(View p0) {
         return  target.btn_login_long();
       }
     });
   }
 
   @Override
   public  void  unbind() {
     T target =  this .target;
     super .unbind();
 
     target.mEtUser =  null ;
     target.mEtPwd =  null ;
 
     view2131689593.setOnClickListener( null );
     view2131689593.setOnLongClickListener( null );
     view2131689593 =  null ;
   }
}


这个类在编译的时候会被自动生成,那么在运行的时候,它会被调用。

这个类的构造函数会去初始化那些控件,设置监听。


回到第七步 ButterKnife.bind()的过程


在createBinding的时候,它会初始化这个xxx_ViewBinding类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private  static  Unbinder createBinding( @NonNull  Object target,  @NonNull  View source) {
     Class<?> targetClass = target.getClass();
     if  (debug) Log.d(TAG,  "Looking up binding for "  + targetClass.getName());
     Constructor<?  extends  Unbinder> constructor = findBindingConstructorForClass(targetClass);
 
     if  (constructor ==  null ) {
       return  Unbinder.EMPTY;
     }
 
     //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
     try  {
       return  constructor.newInstance(target, source);
       ...
       
   }


那么就会走到xxx_ViewBinding的构造函数,那么就会初始化控件,同时也会设置监听。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
target.mEtUser = Utils.findRequiredViewAsType(source, R.id.et_user,  "field 'mEtUser'" , EditText. class );
     target.mEtPwd = Utils.findRequiredViewAsType(source, R.id.et_pwd,  "field 'mEtPwd'" , EditText. class );
     view = Utils.findRequiredView(source, R.id.btn_login,  "method 'btn_login' and method 'btn_login_long'" );
     view2131689593 = view;
     view.setOnClickListener( new  DebouncingOnClickListener() {
       @Override
       public  void  doClick(View p0) {
         target.btn_login();
       }
     });
     view.setOnLongClickListener( new  View.OnLongClickListener() {
       @Override
       public  boolean  onLongClick(View p0) {
         return  target.btn_login_long();
       }
     });



它的调用方式直接是target.mEtpwd,所以也就是说Activity的mEtpwd控件不能是private的,否则会引用不到。



参考网址:

butterknife github源代码下载


ButterKnife源代码解析


Java注解处理器分析


Eclipse中使用Java注解处理器


Android studio使用java注解处理器


JavaPoet介绍


调试Java注解处理器出错





     本文转自rongwei84n 51CTO博客,原文链接: http://blog.51cto.com/483181/1924089 ,如需转载请自行联系原作者


相关文章
|
1月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
166 4
|
1月前
|
安全 Android开发 数据安全/隐私保护
深入探讨iOS与Android系统安全性对比分析
在移动操作系统领域,iOS和Android无疑是两大巨头。本文从技术角度出发,对这两个系统的架构、安全机制以及用户隐私保护等方面进行了详细的比较分析。通过深入探讨,我们旨在揭示两个系统在安全性方面的差异,并为用户提供一些实用的安全建议。
|
26天前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文深入探讨了这两个平台的开发环境,从编程语言、开发工具到用户界面设计等多个角度进行比较。通过实际案例分析和代码示例,我们旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和个人偏好做出明智的选择。无论你是初涉移动开发领域的新手,还是寻求跨平台解决方案的资深开发者,这篇文章都将为你提供宝贵的信息和启示。
30 8
|
2月前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
84 15
Android 系统缓存扫描与清理方法分析
|
1月前
|
安全 Android开发 数据安全/隐私保护
深入探索Android与iOS系统安全性的对比分析
在当今数字化时代,移动操作系统的安全已成为用户和开发者共同关注的重点。本文旨在通过比较Android与iOS两大主流操作系统在安全性方面的差异,揭示两者在设计理念、权限管理、应用审核机制等方面的不同之处。我们将探讨这些差异如何影响用户的安全体验以及可能带来的风险。
38 1
|
1月前
|
算法 JavaScript Android开发
|
1月前
|
开发框架 Dart Android开发
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。
|
2月前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
139 1
|
2月前
|
存储 Linux Android开发
Android底层:通熟易懂分析binder:1.binder准备工作
本文详细介绍了Android Binder机制的准备工作,包括打开Binder驱动、内存映射(mmap)、启动Binder主线程等内容。通过分析系统调用和进程与驱动层的通信,解释了Binder如何实现进程间通信。文章还探讨了Binder主线程的启动流程及其在进程通信中的作用,最后总结了Binder准备工作的调用时机和重要性。
Android底层:通熟易懂分析binder:1.binder准备工作
|
3月前
|
安全 Android开发 数据安全/隐私保护
探索安卓与iOS的安全性差异:技术深度分析与实践建议
本文旨在深入探讨并比较Android和iOS两大移动操作系统在安全性方面的不同之处。通过详细的技术分析,揭示两者在架构设计、权限管理、应用生态及更新机制等方面的安全特性。同时,针对这些差异提出针对性的实践建议,旨在为开发者和用户提供增强移动设备安全性的参考。
156 3