前言
在平时写代码的过程中都会出现很多方法中出现@Override
@hide
这样的注解,还有在比如我们经常用到的EventBus、ButterKnife、Retrofit、Dagger等都会用到。它们有什么作用?以及怎么使用的?改文章也会对上一篇反射的技术进行进一步的加深使用和理解。如果没有使用反射技术童鞋请先阅读上一篇 [Android进阶篇-反射机制ReFlect]
IOC
-
初始 DIP、Ioc、DI、Ioc容器
依赖倒置原则 (DIP, Dependency Inverse Principle)
强调系统的“高层组件”不应当依赖于“底层组件”, 并且不论是“高层组件”还是“底层组件”都应当依赖于抽象。抽象不应当依赖于实现,实现应该依赖于抽象(软件设计原则)
控制反转(Ioc,Inverse of Control)
一种反转、依赖和接口的方式。就是将控制权“往高处/上层”转移,控制反转是实现依赖倒置的一种方法(DIP的具体实现方式)
依赖注入 (DI,Dependency Injection)
组件通过构造函数或者setter方法,将其依赖暴露给上层,上层要设法取得组件的依赖,并将其传递给组件。依赖注入是实现控制反转的一种手段(Ioc的具体实现方式)
Ioc容器
依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)
-
作用以及优缺点
Ioc的核心是解耦,简化我们的工作量。
而解耦的目的:修改耦合对象时不影响另外一个对象,降低模块之间的关联。
在Spring中IOC更多是依靠xml的配置,而在Android中的IOC框架可以不使用xml配置。
优点:代码量减少,代码阅读性好
缺点:会产生一定的性能消耗 (现今的手机上是可以忽略的)
-
元注解
定义定义注解时,会需要一些元注解(meta-annotation)
,如@Target
和@Retention
。@Target
用来定义你的注解将应用于什么地方(可能是一个类或者是一个方法),@Retention
用来定义注解在哪一个级别可用(是在源码中SOURCE,还是在类文件中CLASS,又或者是在运行时RUNTIME)
定义注解时很像是在定义一个接口,所以一般在注解中都会包含一些元素以表示某些值,当处理注解时,就可以根据这个值来做一些判断。
@Target
Type:类/接口
FIELD:属性
METHOD:方法
PARAMETER:参数
CONSTRUCTOR:构造方法
LOCAL_VARIABLE:局部变量
ANNOTATION_TYPE:该注解使用在另一个注解上
PACKAGE:包
@Retention
注解会在class字节码文件中存在, JWM加载时可以通过反射获取到该注解的内容
SOURCE:源码级操作(检查、检测)
CLASS:在编译时 进行一些预操作
RUNTIME:运行时编译
生命周期:SOURCE < CLASS < RUNTIME
- 一般如果需要在运行时去动态获取注解信息,用RUNTIME注解
- 要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。注解会在class文件中存在,但是在运行时会被丢弃
- 做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解
-
注解元素
在定义注解时,我们同样可以为注解定义元素,例如:
@Target(ElementType.TYPE) //作用在类之上
@Retention(RetentionPolicy.RUNTIME) //运行时编译
public @interface User {
int age(); //返回int类型数据
String name() default "daxu"; //返回String类型数据
}
标签@User是由User.class定义,其中包含了int元素的age,以及一个String元素的name,如果name不传值则默认取值为“daxu”。在注解元素中,可用的类型如下:
所有的基本类型(int, float, boolean 等)
String
Class
enum
Annotaion
以上类型的数组
如果使用了其他类型则会编译报错。注意,也不允许使用任何包装类型。
-
使用
在Andorid开发中我们经常在Activity的onCraete中写setContentView(R.layout.xxx),最烦的是每个view的申明都需要些TextView tv = findviewById(R.id.tv), 所以有大神推出了ButterKnife使用注解来解决。今天我们就来自己手动写一下。
打开AS创建一个工程,并且创建一个Java的Library,在该lib中创建一个Kind为Annotation,Name为ContentView的注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
使用该注解时在Activity之上申明该注解,并传入该Activity的layout
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
}
到此,注解已经定义完成。那么怎么去完成把layout和Activity进行绑定呢?下面我们在lib中创建一个InjectManager的类。
public class InjectManager {
public static void inject(Activity activity) {
// 布局的注入
injectLayout(activity);
}
private static void injectLayout(Activity activity) {
//根据传递的Activity对象获取类
Class extends Activity> clazz = activity.getClass();
//获取类之上的注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView != null) {
//获取注解的值,也就是传递的R.layout.activity_main
int layoutId = contentView.value();
//第一种方式 (这种方式比较low)
//activity. setContentView(layoutId);
//第二种方式:反射方法
try {
Method method = clazz.getMethod("setContentView", int.class);
//执行方法
method.invoke(activity, layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
并且在BaseActivity的onCreate()方法中进行注册
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//注册
InjectManager.inject(this);
}
}
开始run一下项目,发现该MainActivity正常打开。
按照此种方式我们继续完成View的申明。比较findviewbyid才是最让人写吐的...
在lib中创建一个Kind为Annotation,Name为BindView的注解类
@Target(ElementType.FIELD) //作用在属性之上
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value();
}
@BindView和@ContentView基本一致,其差别就是这次作用在属性之上,而ContentView则作用在类之上。
定义好注解后,我们继续在InjectManager中开始编写代码, 在InjectManager中定义一个方法injectView(Activity activity); 在inject()进行调用
private static void injectView(Activity activity) {
//获取类
Class extends Activity> clazz = activity.getClass();
//拿到每个属性 (因为不确定是什么修饰 所以使用getDeclaredFields)
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//获取每个属性上的注解
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null) {
//拿到控件ID
int viewId = bindView.value();
try {
Method method = clazz.getMethod("findViewById", int.class);
Object view = method.invoke(activity, viewId);
//设置private的访问权限
field.setAccessible(true);
//将方法执行的返回值赋值给全局的某属性
field.set(activity, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
完成后在MainActivity中测试.. 为了测试view被正常申明,我们在onResume()方法中弹出吐司,吐司内容为该Button按钮的内容来测试一下。
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@BindView(R.id.btn)
public Button btn;
@Override
protected void onResume() {
super.onResume();
Toast.makeText(this, btn.getText().toString(), Toast.LENGTH_LONG).show();
}
}
run项目发现项目运行正常, 并且成功弹出Button内容的吐司~
目前呢, MainActivity中的setContentView和findViewById已经使用了注解的方式进行了简化,还能有其他的地方可以使用注解来进行快速开发吗?答案是当然有!在Android开发中还有很多地方都可以,比如:OnClickListener,onLongClickListener等点击事件,像这种点击事件它又和setContentView和findViewById完全不一样,因为这种事件都带有各自回调方法.. 而我们所有的操作也都写在了onClick的回调方法里面
这种情况下要怎么处理呢?
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
btn.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
看到上面的两个点击事件,我们发现了三个共同点(事件三部曲)
① 都有监听的方法名
setxxxListener
② 都有监听的对象
new View.OnxxxListener
③ 都有回调方法名
onxxx(View v)
找到了规律,我们是否可以想一个办法,把这三个规律整合在一起并且“委托一个人”帮忙把回调里的事情给办了呢? 委托这两个字在我们开发人员眼中很容易就会想到一个词“代理”。代理模式不就是这样的吗? 简单点来讲,就是能不能把事件三部曲
打包成一个对象
,用代理去完成这件事..
现在点击事件的规律找到了,怎么去委托也知道了,但是怎么让代理完成的事情和我们定义的方法绑定到一起呢? 这时就得需要用到另一个知识点 AOP面向切面编程。
AOP面向切面的知识点下次再来讲吧..