有了它,再也不用写setContentView了

2,858 阅读5分钟

前言

大家好,我是猪猪侠。大家多多少少都用过或者看过注解(Annotation),比如最常见@Override、@Deprecated等。近年来一些比较流行的三方框架都使用的注解,像ButterKnife(渐渐被Databinding、ViewBinding取代,已经停止维护)、Dagger、Room等等。那为什么这些大牛都这么热衷于使用注解呢?原因肯定是注解的好处多多了。

注解的优点

  • 代码的检查,将一些错误暴露在运行期之前。
  • 减少重复的工作,提高工作效率。例如ButterKnife,可以减少我们的findViewById,设置点击事件等。
  • 降低代码的耦合。典型的就是Dagger。
  • 信息配置,运行时利用反射动态处理。

元注解(meta-annotation)

什么叫元注解,意思就是在JDK中定义好的注解。在JDK1.5中提供了4个元注解

@Target

表示该注解用于哪个位置,例如作用在类、方法等上面,详细的作用范围如下:

  • ElementType.TYPE 类、接口(包括注解类型)或枚举声明
  • ElementType.FIELD 字段声明
  • ElementType.METHOD 方法声明
  • ElementType.PARAMETER 方法的参数声明
  • ElementType.CONSTRUCTOR 类的构造法声明
  • ElementType.LOCAL_VARIABLE 局部变量声明
  • ElementType.ANNOTATION_TYPE 注解声明
  • ElementType.PACKAGE 包声明
  • ElementType.TYPE_PARAMETER JDK1.8新加的,类型参数声明
  • ElementType.TYPE_USE JDK1.8新加的,类型使用声明

@Retention

表示注解的生命周期

  • RetentionPolicy.SOURCE 源码阶段,在编译时会去除
  • RetentionPolicy.CLASS 注解会保存在class文件中,运行时会去除
  • RetentionPolicy.RUNTIME 注解会一直存在,在运行时可以利用反射去获取注解上面对应的值

@Inherited

表示允许子类继承父类的注解类型

@Documented

表示文档注解

今天我们就是利用运行时注解的特性,来做一些骚操作。

自定义注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {
	int value();
}

上面的代码自定义了一个AnnotationTest注解,@Target是ElementType.TYPE,表示是作用在类、接口或者枚举上面的。@Retention是RetentionPolicy.RUNTIME,表示在运行时期。内部定义了一个返回值为int的方法,表示使用注解时要传递一个int型的参数。那下面我们建个类来看看如何使用。

@AnnotationTest(1)
public class Test {
}
Test test = new Test();
//获取Test的Class对象
Class<? extends Test> clazz = test.getClass();
//使用Class对象获取对应的注解
AnnotationTest annotation = clazz.getAnnotation(AnnotationTest.class);
if (annotation != null) {
       //获取注解传递的值
       int value = annotation.value();
       Log.d(TAG, "initView: >>>>>>>>>>>" + value);
}

上面代码首先定义了一个Test类,然后使用@AnnotationTest注解,传递数值1.然后使用反射获取该类的注解,接着获取注解传递的值,如上图打印出来的是1。

那下面开始进入我们今天的主题,使用注解去设置Activity的页面布局,不在用setContentView了。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    int value();
}

定义了一个ContentView注解,返回值是int型的方法。

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtils.injectLayout(this);
        initView();
    }

    protected void initView() {

    }
}

非常简单的一个BaseActivity

@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
}

MainActivity,使用ContentView注解,然后注解传递布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

MainActivity的布局

还有一个InjectUtils类,获取MainActivity的注解和布局,然后在设置给MainActivity,主要的操作就是放在InjectUtils类中。

public class InjectUtils {
	public static void injectLayout(Object context) {
    	//获取Class对象,这里context就是传递过来的MainActivity
        Class<?> clazz = context.getClass();
        //获取@ContentView注解
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        if (contentView != null) {
        	//获取注解中的布局文件
            int layoutId = contentView.value();
            try {
            	//利用反射调用Activity中的setContentView方法,将布局设置给MainActivity
                Method setContentViewMethod = clazz.getMethod("setContentView", int.class);
                setContentViewMethod.invoke(context, layoutId);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

如上图所以,已经成功的将布局文件设置给了MainActivity。上面的injectLayout方法中主要的操作已经加了注释,这里我就不一一解释了。其实除了使用注解设置Activity的布局,还是使用注解替代fingViewById,设置点击等等。有需要看使用注解去设置控件和点击事件的,可以戳这里

这里我要说的一点就是通过反射获取方法对于有些同学可能会存在的坑(准备的说应该是自己不经常用反射的原因吧),我之前就被这个坑了好久,不过踩过这种坑会让我更加记忆深刻,也让我对反射有了新的认识。

getMethod和getDeclaredMethod的区别

  • getMethod 可以获取自身以及父类的被public修饰的方法
  • getDeclaredMethod 可以获取自身任何的方法,父类的方法是获取不到,哪怕父类的方法是用public修饰的也无法获取。

IOC

IOC,Inversion of Control的缩写,是控制反转。 什么意思呢?IOC是原来由程序代码中主动获取的资源,转变由第三方获取并使原来的代码被动接收的方式,以达到解耦的效果,称为控制反转。最常见的方式就是依赖注入(Dependency Injection),简称DI。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。

我个人的理解,就是把事情交给别人做,做好了之后把成果交给我就行了。 举个简单的例子,老板招了个年轻又漂亮的秘书。老板要和另一家公司合作要吃饭是吧,那秘书负责定酒店,定好之后告诉老板。那合作伙伴喝多了,老板叫秘书定房间,然后秘书定好房间了之后告诉老板。这就是传中的有事秘书干,没事。。。

依赖注入它是一种思想,没有什么标准。就像面向对象思想一样,说简单也简单。说难它就太难了。能领略到思想真谛了,那绝非一朝一夕的时间能做到的。只有通过我们不断积累,不断的实战才有可能做到。

总结

今天主要说的是注解,然后使用运行时注解去设置Acivity的布局,控件等。后面又简单的谈了一下依赖注入,不是很详细。那今天就到这里,大家加油。