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

被折叠的 条评论
为什么被折叠?



