通过反射机制来实现findViewById

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




猜你喜欢

转载自blog.csdn.net/cfy137000/article/details/50917587