预备知识:
Java注解基础
Java反射原理
Java动态代理 一、布局文件的注解
我们在Android开发的时候,总是会写到setContentView方法,为了避免每次都写重复的代码,我们需要使用注解来代替我们做这个事情,只需要在类Activity上声明一个ContentView注解和对应的布局文件就可以了。 @ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUtils.injectContentView(this);
}
} 从上面可以看到,上面代码在MainActivity上面使用到了ContentView注解,下面我们来看看ContentView注解。 @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
} 这个注解很简单,它有一个int的value,用来存放布局文件的id,另外它注解的对象为一个类型,需要说明的是,注解的生命周期会一直到运行时,这个很重要,因为程序是在运行时进行反射的,我们来看看ViewUtils.injectContentView(this)方法,它进行的就是注解的处理,就是进行反射调用setContentView()方法。 public static void injectContentView(Activity activity) {
Class a = activity.getClass();
if (a.isAnnotationPresent(ContentView.class)) {
// 得到activity这个类的ContentView注解
ContentView contentView = (ContentView) a.getAnnotation(ContentView.class);
// 得到注解的值
int layoutId = contentView.value();
// 使用反射调用setContentView
try {
Method method = a.getMethod("setContentView", int.class);
method.setAccessible(true);
method.invoke(activity, layoutId);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
} 如果对Java注解比较熟悉的话,上面代码应该很容易看懂。 二、字段的注解
除了setContentView之外,还有一个也是我们在开发中必须写的代码,就是findViewById,同样,它也属于简单但没有价值的编码,我们也应该使用注解来代替我们做这个事情,就是对字段进行注解。 @ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.btn1)
private Button mButton1;
@ViewInject(R.id.btn2)
private Button mButton2; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUtils.injectContentView(this);
ViewUtils.injectViews(this);
}
} 上面我们看到,使用ViewInject对两个Button进行了注解,这样我们就是不用写findViewById方法,看上去很神奇,但其实原理很简单。我们先来看看ViewInject注解。 @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
} 这个注解也很简单,就不说了,重点就是注解的处理了。 public static void injectViews(Activity activity) {
Class a = activity.getClass();
// 得到activity所有字段
Field[] fields = a.getDeclaredFields();
// 得到被ViewInject注解的字段
for (Field field : fields) {
if (field.isAnnotationPresent(ViewInject.class)) {
// 得到字段的ViewInject注解
ViewInject viewInject = field.getAnnotation(ViewInject.class);
// 得到注解的值
int viewId = viewInject.value();
// 使用反射调用findViewById,并为字段设置值
try {
Method method = a.getMethod("findViewById", int.class);
method.setAccessible(true);
Object resView = method.invoke(activity, viewId);
field.setAccessible(true);
field.set(activity, resView);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} }
}
} 上面的注释很清楚,使用的也是反射调用findViewById函数。 三、事件的注解
在Android开发中,我们也经常遇到setOnClickListener这样的事件方法。同样我们可以使用注解来减少我们的代码量。 @ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUtils.injectContentView(this);
ViewUtils.injectEvents(this);
} @OnClick({R.id.btn1, R.id.btn2})
public void clickBtnInvoked(View view) {
switch (view.getId()) {
case R.id.btn1:
Toast.makeText(this, "Button1 OnClick", Toast.LENGTH_SHORT).show();
break;
case R.id.btn2:
Toast.makeText(this, "Button2 OnClick", Toast.LENGTH_SHORT).show();
break;
}
}
} 布局文件如下: <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="#70DBDB"
android:orientation="vertical"
tools:context="statusbartest.hpp.cn.statusbartest.MainActivity">
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test1"/> <Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test2"/>
</LinearLayout> 可以看到,上面我们没有对Button调用setOnClickListener,但是当我们点击按钮的时候,就会回调clickBtnInvoked方法,这里我们使用的就是注解来处理的。下面先来看看OnClick注解。 @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", methodName = "onClick")
public @interface OnClick {
int[] value();
} 可以看到这个注解使用了一个自定义的注解。 @Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
Class listenerType();
String listenerSetter();
String methodName();
} 下面来看看注解的处理。 public static void injectEvents(Activity activity) {
Class a = activity.getClass();
// 得到Activity的所有方法
Method[] methods = a.getDeclaredMethods();
for (Method method : methods) {
// 得到被OnClick注解的方法
if (method.isAnnotationPresent(OnClick.class)) {
// 得到该方法的OnClick注解
OnClick onClick = method.getAnnotation(OnClick.class);
// 得到OnClick注解的值
int[] viewIds = onClick.value();
// 得到OnClick注解上的EventBase注解
EventBase eventBase = onClick.annotationType().getAnnotation(EventBase.class);
// 得到EventBase注解的值
String listenerSetter = eventBase.listenerSetter();
Class<?> listenerType = eventBase.listenerType();
String methodName = eventBase.methodName();
// 使用动态代理
DynamicHandler handler = new DynamicHandler(activity);
Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[] { listenerType }, handler);
handler.addMethod(methodName, method);
// 为每个view设置点击事件
for (int viewId : viewIds) {
try {
Method findViewByIdMethod = a.getMethod("findViewById", int.class);
findViewByIdMethod.setAccessible(true);
View view = (View) findViewByIdMethod.invoke(activity, viewId);
Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
setEventListenerMethod.setAccessible(true);
setEventListenerMethod.invoke(view, listener);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} } }
} 这个代码相对上面的要复杂一些,它使用到了动态代理,关于动态代理的基本用法可以看看前面我提到的预备知识。 public class DynamicHandler implements InvocationHandler {
private final HashMap<String, Method> methodMap = new HashMap<String, Method>(
1);
// 因为传进来的为activity,使用弱引用主要是为了防止内存泄漏
private WeakReference<Object> handlerRef;
public DynamicHandler(Object object) {
this.handlerRef = new WeakReference<Object>(object);
} public void addMethod(String name, Method method) {
methodMap.put(name, method);
}
// 当回到OnClickListener的OnClick方法的时候,它会调用这里的invoke方法
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// 得到activity实例
Object handler = handlerRef.get();
if (handler != null) {
// method对应的就是回调方法OnClick,得到方法名
String methodName = method.getName();
// 得到activtiy里面的clickBtnInvoked方法
method = methodMap.get(methodName);
if (method != null) {
// 回调clickBtnInvoked方法
return method.invoke(handler, objects);
}
}
return null;
}
}

Android中的自定义注解(反射实现-运行时注解)的更多相关文章

  1. Java 进阶巩固:什么是注解以及运行时注解的使用

    这篇文章 2016年12月13日星期二 就写完了,当时想着等写完另外一篇关于自定义注解的一起发.结果没想到这一等就是半年多 - -. 有时候的确是这样啊,总想着等条件更好了再干,等准备完全了再开始,结 ...

  2. Android运行时注解

    Android的注解有编译时注解和运行时注解,本文就介绍下运行时注解. 其实非常简单,直接上代码:本文主要是替代传统的findViewById()的功能,就是在我们Activity中不需要再使用fin ...

  3. 自定义注解之运行时注解(RetentionPolicy.RUNTIME)

    对注解概念不了解的可以先看这个:Java注解基础概念总结 前面有提到注解按生命周期来划分可分为3类: 1.RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成clas ...

  4. Android中制作自定义dialog对话框的实例

    http://www.jb51.net/article/83319.htm   这篇文章主要介绍了Android中制作自定义dialog对话框的实例分享,安卓自带的Dialog显然不够用,因而我们要继 ...

  5. Android中的自定义Adapter(继承自BaseAdapter)——与系统Adapter的调用方法一致——含ViewHolder显示效率的优化(转)

    Android中很多地方使用的是适配器(Adapter)机制,那我们就要好好把这个Adapter利用起来,并且用出自己的特色,来符合我们自行设计的需要喽~~~ 下面先上一个例子,是使用ViewHold ...

  6. Android 中使用自定义字体的方法

    1.Android系统默认支持三种字体,分别为:“sans”, “serif”, “monospace 2.在Android中可以引入其他字体 . <?xml version="1.0 ...

  7. Android开发学习之路-Android6.0运行时权限

    在Android6.0以后开始,对于部分敏感的“危险”权限,需要在应用运行时向用户申请,只有用户允许的情况下这个权限才会被授予给应用.这对于用户来说,无疑是一个提升安全性的做法.那么对于开发者,应该怎 ...

  8. 自定义 ThreadPoolExecutor 处理线程运行时异常

    自定义 ThreadPoolExecutor 处理线程运行时异常 最近看完了ElasticSearch线程池模块的源码,感触颇深,然后也自不量力地借鉴ES的 EsThreadPoolExecutor ...

  9. Java——反射:运行时的类信息

    RTTI的使用 如果不知道某个对象的确切类型,RTTI会告诉我们,但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息做一些有用的事情.  2.什么情况下需要反射 假设 ...

随机推荐

  1. 03.JMS深入

    1.JMS API     JMS API可以分为3个主要部分:公共API.点对点API和发布/订阅API.在JMS1.1中,公共API可被用于向一个队列或主题发送消息,或从其中接收消息.点对点API ...

  2. Windows安装使用git

    下载安装Windows安装文档Git-2.16.2-64-bit双击安装(安装过程不详述) 打开git客户端 新建代码命令 mkdir /c/code 进入该目录(对应windows的c盘下面的目录) ...

  3. oracle的认证方式

    使用as sysdba是使用操作系统验证方式,不需要输入密码

  4. python 对shell 命令的 执行 逻辑 在一台机器上执行另一台机器的命令; 跨节点 执行命令

    import os l = ['ssh a;scp /data/visitlog/*11* root@d:/data/mapReduceVisitorLog/'] # b c for i in l: ...

  5. mac终端显示日历信息命令

    cal 命令: 用法: usage: cal [-jy] [[month] year] cal [-j] [-m month] [year] ncal [-Jjpwy] [-s country_cod ...

  6. mongodb安装及副本集搭建

    mongodb下载地址:https://www.mongodb.com/dr/fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel62-3.2.7.tg ...

  7. Debian更新软件源提示There is no public key available for the following key IDs的解决方法

    今天装了的debian7.0 但是更新软件源的时候出错 提示 W: There is no public key available for the following key IDs: 9D6D8F ...

  8. enum 枚举类型默认值

    enum value { one, two, three, four }; 默认值

  9. centos7 lua安装

    yum -y install gcc automake autoconf libtool makeyum install readline-develcurl -R -O http://www.lua ...

  10. 002-mybatis主配置文件

    <?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC & ...