Android利用注解自定义一个超级简单的ButterKnife
一、概述
自从Java1.5后,其提供了一个非常强大的功能那就是注解。在普通的开发中可能不会自定义注解,甚至有些时候一个app开发下来完全不需要注解。但是想要在技术方面做一个纵深,自己封装框架,成为高级工程师,专家或者架构师,注解这块的知识是绕不开的。所以学习一下java中的注解对自己的内功修炼是非常有必要的。
现在主流的开源框架大多数都会用到注解,不同的框架注解的参与程度也不一样。像:EventBus、ButterKnife、Retrofit2等框架其注解的参与程度是比较深的,如果想要在注解方面有更快的提升,建议各位看官去看这些框架的源代码。
本节会先讲一下java&android中常用的元注解。然后通过两个小例子来系统的说一下注解的具体用法:自定义ButterKnife和获取类似retrofit中interfaceservice中的方法信息,来加强各位看官在注解方面上的理解。
二、元注解
网上将Java元注解的文章非常的多,本节只将在作者看来最重要的两个,@target和@retention。其他的大家自行学习。本节的重点在于实践,其理论只是相对较少。
1.@Target,其定义了注解可以在什么地方使用:如:类、属性、方法、参数、构造方法、局部变量、包声明等。
@Target注解有以下几种值可以使用,当然,多种值可以组合使用
a.TYPE:类、接口、枚举声明
b.FIELD:在属性上声明
c.METHOD:在方法上声明
d.PARAMETER:参数参数声明
e.CONSTRUCTOR:构造方法声明
f.ANNOTATION_TYPE:注解类型声明
g.LOCAL_VARIABLE:局部变量声明
h.PACKAGE:包声明
2@Retention,其定义了注解在那些阶段是可以用的,如:源码阶段、Class字节码阶段、运行时阶段(虚拟机)
@Retention有三种阶段可以选择:
a.RetentionPolicy.SOURCE,注解只存在源码中
b.RetentionPolicy.CLASS,注解可以一直存活到字节码阶段
c.RetentionPolicy.RUNTIME,注解可以一直存活,即使程序已经运行起来了
三、创建一个超级简单的ButterKnife
本例我们将创建一个非常简单的ButterKnife。利用bind方法进行绑定view并给view赋值,而且还可以选择性的设置view的点击事件。
类介绍:
1.BindView.java绑定view的注解
2.OnClick.java绑定点击事件的注解
3.ButterKnife.java注解工具
4.BindViewTestActivity.java测试类
BindView:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package com.yw.annotationlib; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 模仿ButterKnife实现View的绑定 * create by yangwei * on 2020-02-24 13:09 */ @Target (ElementType.FIELD) //标记该注解运行在字段上 @Retention (RetentionPolicy.RUNTIME) //标记该字段一直到程序运行时都有效 public @interface BindView { int value() default - 1 ; //View的Id,如:R.id.btn } |
OnClick.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package com.yw.annotationlib; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * create by yangwei * on 2020-02-24 13:38 */ @Target (ElementType.METHOD) //标记注解应用在方法上 @Retention (RetentionPolicy.RUNTIME) //标记注解直到运行时都可以存活 public @interface OnClick { int value() default - 1 ; //比较view的id值 } |
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 | package com.yw.annotationlib; import android.app.Activity; import android.os.Build; import android.view.View; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import androidx.annotation.RequiresApi; /** * create by yangwei * on 2020-02-24 13:12 */ public class ButterKnife { /** * 绑定对象用注解标注的所有方法和属性 * * @param activity */ @RequiresApi (api = Build.VERSION_CODES.N) public static void bind( final Activity activity) { try { Class clazz = activity.getClass(); //获取class中的所有的注解 Method[] methods = clazz.getDeclaredMethods(); for ( final Method method : methods) { final OnClick onClick = method.getDeclaredAnnotation(OnClick. class ); if (onClick != null ) { final View view = activity.findViewById(onClick.value()); method.setAccessible( true ); //设置view的点击事件 view.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { try { method.invoke(activity, view); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }); } } Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { BindView bindView = field.getDeclaredAnnotation(BindView. class ); if (bindView != null ) { View view = activity.findViewById(bindView.value()); field.setAccessible( true ); field.set(activity, view); } } } catch (Exception e) { e.printStackTrace(); } } } |
BindViewTestActivity.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 | package com.yw.rxjava3demo; import android.app.Activity; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.yw.annotationlib.BindView; import com.yw.annotationlib.ButterKnife; import com.yw.annotationlib.OnClick; import com.yw.annotationlib.retrofit.FieldMap; import com.yw.annotationlib.retrofit.POST; import com.yw.annotationlib.retrofit.RetrofitAnnotationBind; import java.util.Arrays; import java.util.Map; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; /** * create by yangwei * on 2020-02-24 13:21 */ public class BindViewTestActivity extends Activity { @BindView (R.id.tv_name) TextView tv_name; @BindView (R.id.btn_click) Button btn_click; @RequiresApi (api = Build.VERSION_CODES.N) @Override protected void onCreate( @Nullable Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.bindviewtest_layout); ButterKnife.bind( this ); tv_name.setText( "我是杨洛峋,是一个小宝宝" ); //测试参数注解和方法注解 RetrofitAnnotationBind.ServiceMethod serviceMethod = RetrofitAnnotationBind.bind( this ); Log.e( "获取注解中的请求方式:" , serviceMethod.getMethod()); Log.e( "获取请求方法中的值:" , serviceMethod.getParams()); } @POST ( "POST" ) public void retrofitTest( @FieldMap Map<String,String> map){ } @OnClick (R.id.btn_click) public void onClick(View view) { switch (view.getId()) { case R.id.btn_click: Toast.makeText(BindViewTestActivity. this , "点击事件执行了" , Toast.LENGTH_LONG).show(); break ; } } } |
xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?xml version= "1.0" encoding= "utf-8" ?> <LinearLayout xmlns:android= "http://schemas.android.com/apk/res/android" android:layout_width= "match_parent" android:layout_height= "match_parent" android:orientation= "vertical" > <TextView android:id= "@+id/tv_name" android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:text= "我是杨洛峋" /> <Button android:id= "@+id/btn_click" android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:text= "点击我试试" /> </LinearLayout> |
其最终的结果是TextView和Button按钮成功绑定上了findViewById。且Button按钮可以执行其注解标注的点击事件。
四、模仿retrofit获取注解方法参数以及方法注解参数上的值
本小节的主要内容是操作activty中的一个方法retrofitTest并的到这个方法注解的值和参数注解中的值并返回
FieldMap.java参数注解
POST.java方法注解
RetrofitAnnotationBind.java注解解析器
FieldMap.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package com.yw.annotationlib.retrofit; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 模仿Retrofit的FieldMap参数注解 * create by yangwei * on 2020-02-24 14:03 */ @Target (ElementType.PARAMETER) //标记注解附着到参数上 @Retention (RetentionPolicy.RUNTIME) //标记注解一直到运行时都有效果 public @interface FieldMap { boolean encoded() default false ; } |
POST.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package com.yw.annotationlib.retrofit; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * create by yangwei * on 2020-02-24 14:01 */ @Target (ElementType.METHOD) //在方法上运行 @Retention (RetentionPolicy.RUNTIME) //标记注解一直能存活到运行时 public @interface POST { String value() default "post_hello" ; } |
RetrofitAnnotationBind.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 68 69 70 71 72 73 74 75 76 77 78 79 | package com.yw.annotationlib.retrofit; import android.os.Build; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import androidx.annotation.RequiresApi; /** * create by yangwei * on 2020-02-24 14:06 */ public class RetrofitAnnotationBind { /** * 拿到方法注解和参数注解 * * @param obj */ @RequiresApi (api = Build.VERSION_CODES.N) public static ServiceMethod bind(Object obj) { ServiceMethod serviceMethod = new ServiceMethod(); Class clazz = obj.getClass(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { POST post = method.getDeclaredAnnotation(POST. class ); //说明此方法上有POST注解,拿到post上的值 if (post != null ) { String psotValue = post.value(); serviceMethod.setMethod(psotValue); Annotation[][] annotations = method.getParameterAnnotations(); if (annotations != null ) { StringBuffer sb = new StringBuffer(); for (Annotation[] paramsAnnotations : annotations) { for (Annotation annotation : paramsAnnotations) { if (annotation instanceof FieldMap) { FieldMap fieldMap = (FieldMap) annotation; sb.append(fieldMap.encoded()).append( "," ); } } } serviceMethod.setParams(sb.toString()); } } } return serviceMethod; } public static class ServiceMethod { private String method; private String params; public ServiceMethod() { } public ServiceMethod(String method, String params) { this .method = method; this .params = params; } public String getMethod() { return method; } public void setMethod(String method) { this .method = method; } public String getParams() { return params; } public void setParams(String params) { this .params = params; } } } |
测试类中的测试方法,解析的就是这个方法
1 2 3 4 | @POST ( "POST" ) public void retrofitTest( @FieldMap Map<String,String> map){ } |
本例的最终结果会的到一个ServiceMethod类。此类中存储了POST注解的值和FieldMap注解中的值。
其打印结果为POST,false
1 2 3 4 | //测试参数注解和方法注解 RetrofitAnnotationBind.ServiceMethod serviceMethod = RetrofitAnnotationBind.bind( this ); Log.e( "获取注解中的请求方式:" , serviceMethod.getMethod()); Log.e( "获取请求方法中的值:" , serviceMethod.getParams()); |
总结:本节的内容讲到这里就结束了,希望大家能够对注解的使用方式有一点自己的体会。如果想要更深入的了解注解,建议大家去看下相关框架的源代码。
ps:一旦你深入了,你会发现注解其实法力还是很大的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
2014-02-24 WebView之加载网页时增加进度提示
2014-02-24 WebView之禁止调用第三方浏览器