通过反射机制来实现findViewById
2016-04-22 19:47
337 查看
在Android的开发中,我们通常会写好多的findViewById,写的太多了容易腻,这时我们可以换个方式,来通过反射和自定义注解来实现findViewById的操作
首先我们来创建我们的自定义注解
新建一个Java的类
接下来再来看看简化setContentView的注解
首先来看看contentInject方法
而组件的findViewById就要稍微复杂一点,我们来看一下代码:
方法让这个成员变量的赋值是可以被修改的,那么修改成什么值呢?自然是调用findViewById的返回值了,于是调用
首先是使用反射的方式,我们在onCreate方法的开始和结束分别记录一下当前的系统时间,并输出时间的差值
04-22 07:07:10.923 8850-8850/com.lanou.chenfengyao.temp D/MainActivity: end - start:177
可以看到消耗的时间是177毫秒
在看一下使用正常的加载方式所消耗的时间,我们写一个测试用的Activity实现的功能和利用反射来实现是一样的来看一下
04-22 07:12:36.575 12461-12461/com.lanou.chenfengyao.temp D/BaseActivity: end - start:214
可以看到,实际上消耗的时间并没有差多少,也就是说,使用这种方式,并不会成为制约你性能的瓶颈,关于反射为什么慢,在stackoverflow看到这样一段话
Because reflection involves types that are dynamically resolved, certain Java virtual machine
optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
大概意思是说 当你使用反射的时候,JVM没法对你写的代码做优化,所以你的代码才会很慢,所以我们不需要看见反射就担心的~
自定义注解
Java中有很多的注解(Annotation)例如最常见的@Override就是注解,利用注解我们可以在程序运行的时候将组件的id和组件联系起来,将布局文件和Activity绑定在一起.首先我们来创建我们的自定义注解
新建一个Java的类
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by ChenFengYao on 16/1/21. */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface BindView { int value() default 0; }来介绍一下该注解,首先它与interface类似,但是类型是@interface 类名是BindView,@Target(ElementType.FIELD)的意思是标明该注解是作用于成员变量的;而@Rentention(RententionPolicy.RUNTIME)表示当jJVM运行时,此注解可以被读出,里面的内容代表注解接收一个int类型的value并且当没有指定值得时候,默认值是0,该注解的意义就是为了在定义组件的时候省略findViewById的操作,直接将id信息写在成员变量上就好了
接下来再来看看简化setContentView的注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by ChenFengYao on 16/1/21. * 为Activity绑定布局的 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface BindContent { int value() default 0; }发现@Target(ElementType.TYPE)是与上面不同的,表明该注解是作用于类的,而内部的代码是一样的,都是接收int类型的值,到此2个基本的注解就完成了
基类
我们想让所有的Activity都能实现类似的功能,我们可以将通过反射读取注解内容的这部分代码写在Activity的基类中,先看代码import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import com.lanou.chenfengyao.gobangdemo.utils.BindContent; import com.lanou.chenfengyao.gobangdemo.utils.BindView; import java.lang.reflect.Field; /** * Created by ChenFengYao on 16/1/21. * 所有Activity的基类 */ public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); contentInject();//绑定布局 viewInject();//绑定组件 initData(); } public abstract void initData(); private void contentInject(){ Class clazz = this.getClass(); if(clazz.isAnnotationPresent(BindContent.class)){ BindContent bindContent = (BindContent) clazz.getAnnotation(BindContent.class); int id = bindContent.value(); if(id > 0){ this.setContentView(id); } } } //遍历注释,去执行findViewById的方法 private void viewInject() { Class clazz = this.getClass();//将当期对象转化成类对象 Field[] fields = clazz.getDeclaredFields();//获得所有的成员变量 for (Field field : fields) { //循环遍历 if (field.isAnnotationPresent(BindView.class)) { BindView bindView = field.getAnnotation(BindView.class); int id = bindView.value(); if (id > 0) { //如果成员变量被BindView修饰 field.setAccessible(true);//让这个成员可以被修改 try { field.set(this, this.findViewById(id)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } }我们来分析下上面的代码,首先重写了onCreate方法在onCreate方法内部首先调用我们自己写的方法contentInject来加载布局,然后调用viewInject来加载组件,之后是一个抽象方法,用来为其他组件设置数据,添加监听什么的,需要BaseActivity的子类自己实现.
首先来看看contentInject方法
Class clazz = this.getClass();将当前Activity转换成了类类型,然后判断当前类是否加上了BindContent的注释,如果有该注释并且它的id不为0则证明在注释里设置了布局,拿到该布局,并调用setContentView方法将此id传进去,即完成了布局的绑定
而组件的findViewById就要稍微复杂一点,我们来看一下代码:
//遍历注释,去执行findViewById的方法 private void viewInject() { Class clazz = this.getClass();//将当期对象转化成类对象 Field[] fields = clazz.getDeclaredFields();//获得所有的成员变量 for (Field field : fields) { //循环遍历 if (field.isAnnotationPresent(BindView.class)) { BindView bindView = field.getAnnotation(BindView.class); int id = bindView.value(); if (id > 0) { //如果成员变量被BindView修饰 field.setAccessible(true);//让这个成员可以被修改 try { field.set(this, this.findViewById(id)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } }首先,还是将本类通过this.gitClass()方法将本类对象转换成类类型,然后调用
Field[] fields = clazz.getDeclaredFields();来获得本类所有的成员变量,循环遍历他们,如果被我们自定义的注解BindView注解过的话,就证明它是有id的,我们还是通过getAnnotation方法来拿到该注解,让从该注解中拿到它的value,也就是我们组件的ID,拿到id之后,首先我们需要调用
field.setAccessible(true);
方法让这个成员变量的赋值是可以被修改的,那么修改成什么值呢?自然是调用findViewById的返回值了,于是调用
field.set(this, this.findViewById(id));方法来为该成员变量进行赋值,就实现了各种组件的findViewById的操作。之后来看一下我们使用该基类的效果吧
@BindContent(R.layout.activity_main) public class MainActivity extends BaseActivity { @BindView(R.id.main_tv) private TextView textView; @BindView(R.id.main_btn) private Button button; @Override public void initData() { textView.setText("Hello"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "Hello", Toast.LENGTH_SHORT).show(); } }); } }可以看到整个代码变得简洁了不少,在类之前使用BindContent来绑定布局,在组件之前使用BindView来绑定组件
关于效率
提到反射首先想到就是效率问题,我们来测试一下,使用反射和正常的加载组件各自所需要的时间,我们就看看Activity的onCreate方法所消耗的时间首先是使用反射的方式,我们在onCreate方法的开始和结束分别记录一下当前的系统时间,并输出时间的差值
@BindContent(R.layout.activity_main) public class MainActivity extends BaseActivity { @BindView(R.id.main_tv) private TextView textView; @BindView(R.id.main_btn) private Button button; @Override protected void onCreate(Bundle savedInstanceState) { Long start = System.currentTimeMillis(); super.onCreate(savedInstanceState); Long end = System.currentTimeMillis(); Log.d("MainActivity", "end - start:" + (end - start)); } @Override public void initData() { textView.setText("Hello"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "Hello", Toast.LENGTH_SHORT).show(); } }); } }然后运行 系统的日志:
04-22 07:07:10.923 8850-8850/com.lanou.chenfengyao.temp D/MainActivity: end - start:177
可以看到消耗的时间是177毫秒
在看一下使用正常的加载方式所消耗的时间,我们写一个测试用的Activity实现的功能和利用反射来实现是一样的来看一下
public class TestAty extends AppCompatActivity { private TextView textView; private Button button; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { Long start = System.currentTimeMillis(); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.main_tv); button = (Button) findViewById(R.id.main_btn); textView.setText("Hello"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(TestAty.this, "Hello", Toast.LENGTH_SHORT).show(); } }); Long end = System.currentTimeMillis(); Log.d("BaseActivity", "end - start:" + (end - start)); } }同样的记录一下onCreate的时间
04-22 07:12:36.575 12461-12461/com.lanou.chenfengyao.temp D/BaseActivity: end - start:214
可以看到,实际上消耗的时间并没有差多少,也就是说,使用这种方式,并不会成为制约你性能的瓶颈,关于反射为什么慢,在stackoverflow看到这样一段话
Because reflection involves types that are dynamically resolved, certain Java virtual machine
optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
大概意思是说 当你使用反射的时候,JVM没法对你写的代码做优化,所以你的代码才会很慢,所以我们不需要看见反射就担心的~
相关文章推荐
- LA4794 分享巧克力
- 回发或回调参数无效(译)
- Spark官方文档: Spark Configuration(Spark配置)
- [快速因数分解]Pollard's Rho 算法
- 对string的排序
- codeforces 633A
- "数学口袋精灵"bug的发现
- maven打包的时候 ,将依赖包 打进同一个jar包命令 mvn assembly:assembly -DskipTests
- 数字签名是什么?
- 16年4月21号:Git版本控制工具的使用
- 连连看核心算法代码
- 百万级PV高可用网站架构设计
- python中类的总结
- Sublime text快捷键汇总
- bzoj4542 大数 莫队算法
- python中类的总结
- Java IO:PipedOutputStream和PipedInputStream使用详解及源码分析
- 挂起是什么操作
- python中类的总结
- jQuery UI Autocomplete Combobox 配 ASP.NET DropDownList