Android开发学习之路--Annotation注解简化view控件之初体验
一般我们在写android Activity的时候总是会在onCreate方法中加上setContentView方法来加载layout,通过findViewById来实现控件的绑定,每次写这么多代码总觉得很烦躁。近来看了下android中有Annotation来实现这方面的简化,对于java不是很了解,就简单的看了下。上次玩web的时候,springmvc也有很多的注解,不知道怎么实现的,这里其实基本上类似。
Annotation注解这里主要还是讲讲怎么使用吧,单纯的原理会把人绕进去的,没办法,java基础只能后面再补了,c搞久了,很多面向对象的思想只停留在大学的时候,除了linux内核的一些面向对象的思想。说了那么多的废话,接着继续我们的Annotation的学习吧,先新建工程emAnnotationStudy,新建EMLayoutBinder.java,代码如下:
package com.jared.emannotationstudy; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by jared on 16/3/10. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface EMLayoutBinder { int value(); }这里的@Target,@Retention和@interface,先来简单的介绍下这几个内容吧。
@Target:说明了Annotation修饰的对象范围,Annotation可被用于packages、types等类,接口,枚举,Annotation类型;还可以是类成员方法,构造方法,成员变量,枚举值;方法参数和本地变量等。其一般有如下几种类型:
ElementType.CONSTRUCTOR: 构造器声明;
ElementType.FIELD: 成员变量、对象、属性;
ElementType.LOCAL_VARIABLE: 局部变量声明;
ElementType.METHOD: 方法声明;
ElementType.PACKAGE: 包声明;
ElementType.PARAMETER: 参数声明;
ElementType.TYPE: 类、接口(包括注解类型)或enum声明;
这用到了TYPE。
@Retention:表示在什么级别保存该注解信息。其一般级别如下:
RetentionPolicy.SOURCE: 停留在java源文件,编译器被丢掉。
RetentionPolicy.CLASS: 停留在class文件中,但会被VM丢弃。
RetentionPolicy.RUNTIME:内存中的字节码,VM将在运行时也保留注解,因此可以通过反射机制读取注解的信息。
这里给了最高级别RUNTIME。
@interface:这个就表示注解了,和interface很像,不过多了一个@符号。
int value():表示传入的参数是int类型的。
好了,既然定义好了那么怎么使用呢?单单一个注解怎么个搞搞?其实注解一般都是和java的反射原理一起使用的。还是简单学习下java的反射吧,网上资料很多,这里就简单理解理解了。在java中的反射机制,被称为Reflection,它允许运行中的java程序对自身进行检查,并能直接操作程序的内部属性或方法。
利用Reflection APIs可以获取任何已知名称的类的内部信息,包括package、type parameters、superclass、implemented interfaces、inner classes、outer classes、fields constructors、methods、modifiers等。
Class: 表示某个具体的类或接口
Object: 每个类都使用Object 做为超类,所有对象都实现这个类的方法
Constructor:封装了Class的构造方法
Field: 提供有关类或接口的属性信息,以及对它的动态访问权限
Method: 提供类或者接口上的方法的信息
Modifier: 封装了Class(method、fields)的修饰域。
简单了解下java的发射机制,其实反射就是通过反向调用类的一些功能,可能会觉得很难理解,还是继续我们的学习吧,新建EMAnnotationParser类:
package com.jared.emannotationstudy; import android.app.Activity; import android.view.View; import java.lang.reflect.Field; /** * Created by jared on 16/3/10. */ public class EMAnnotationParser { public static void injectActivity(Activity activity) { if (null == activity) { return; } Class activityClass = (Class) activity.getClass(); if (isEMLayoutBinder(activityClass)) { EMLayoutBinder layout = activityClass.getAnnotation(EMLayoutBinder.class); activity.setContentView(layout.value()); } View decorView = activity.getWindow().getDecorView(); } private static boolean isEMLayoutBinder(Class c) { return c.isAnnotationPresent(EMLayoutBinder.class); }
这里实现了injectActivity的方法,通过getClass获取当前的Activity的class,然后通过isAnnotationPresent查看该Annotation,再通过getAnnotation获取该注解,接着就是把注解传入的那个layout通过activity的setContentView方法来加载到activity 中了。好了,那么我们来实现下MainActivity吧:
package com.jared.emannotationstudy; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; @EMLayout(R.layout.activity_main) public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EMAnnotationParser.injectActivity(this); //setContentView(R.layout.activity_main); } }去掉了setContentView,直接一个@EMLayout就搞定了,是不是很方便,这里通过EMAnnotationParser的injectActivity方法。其实一般项目中会定义一个BaseActivity,MainActivity通过继承BaseActivity来实现,那样看上去会更加的清晰,那就实现下BaseActivity吧:
package com.jared.emannotationstudy; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; /** * Created by jared on 16/3/10. */ public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EMAnnotationParser.injectActivity(this); } }然后实现MainActivity继承BaseActivity:
package com.jared.emannotationstudy; import android.os.Bundle; import android.widget.Button; import android.widget.TextView; @EMLayoutBinder(R.layout.activity_main) public class MainActivity extends BaseActivity { @EMViewBinder(R.id.hello) private TextView mHello; @EMViewBinder(R.id.test1) private Button mTest1; @EMViewBinder(R.id.test2) private Button mTest2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHello.setText("Hello Annotation!"); } }
运行后依然没有任何问题。既然layout通过注解了,那么控件也是可以通过注解的,接下去就去实现下了。首先新建Annotation为EMViewBinder:
package com.jared.emannotationstudy; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by jared on 16/3/10. */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface EMViewBinder { int value(); }
这里就很好理解了,和LayoutBinder一样,只是target是FIFLD,因为是成员的变量。接着简单实现下反射:
package com.jared.emannotationstudy; import android.app.Activity; import android.view.View; import java.lang.reflect.Field; /** * Created by jared on 16/3/10. */ public class EMAnnotationParser { public static void injectActivity(Activity activity) { if (null == activity) { return; } Class activityClass = (Class) activity.getClass(); if (isEMLayoutBinder(activityClass)) { EMLayoutBinder layout = activityClass.getAnnotation(EMLayoutBinder.class); activity.setContentView(layout.value()); } View decorView = activity.getWindow().getDecorView(); initViews(activityClass.getDeclaredFields(), decorView, activity); } private static boolean isEMLayoutBinder(Class c) { return c.isAnnotationPresent(EMLayoutBinder.class); } private static boolean isEMViewBinder(Field filed) { return filed.isAnnotationPresent(EMViewBinder.class); } private static void initViews(Field[] fields, View view, Object object) { View view1; for (Field field : fields) { if(isEMViewBinder(field)) { EMViewBinder emView = field.getAnnotation(EMViewBinder.class); view1 = view.findViewById(emView.value()); if(null != view1) { try { field.setAccessible