通过反射机制来实现findViewById

来源:互联网 发布:天干地支日期互算法 编辑:程序博客网 时间:2024/05/01 00:44

在Android的开发中,我们通常会写好多的findViewById,写的太多了容易腻,这时我们可以换个方式,来通过反射和自定义注解来实现findViewById的操作


自定义注解


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没法对你写的代码做优化,所以你的代码才会很慢,所以我们不需要看见反射就担心的~




4 0
原创粉丝点击