什么是IoC(控制反转)?
- 控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找
- 通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
bufferknife通过给布局、视图、事件添加注解省去繁琐的代码 如:
@BindViews({ R.id.button})
privite Button button ;
@OnClick(R.id.button ) //给 button1 设置一个点击事件
public void showToast(){
Toast.makeText(this, "is a click", Toast.LENGTH_SHORT).show();
}
接下来我们通过注解实现类似bufferknife的功能
主要原理:
通过在activity执行onCreate()方法时进行依赖注入,对当前actvity的注解进行解析、判断,再通过反射获取对象和方法分别进行 setContentView()设置布局、findViewById()绑定控件、设置监听setOnXXXClick()等操作。
主要流程:
-
观察我们的Actvity中需要通过这样几个注解来实现setContentView()设置布局、findViewById()绑定控件、设置监听setOnXXXClick()等功能。按照我们的思路我们需要对这些注解进行解析,那么在哪里解析呢,注解又该传什么参数呢?
@SetContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@BindView(R.id.btn_b1)
private Button btn;
@BindView(R.id.tv_t1)
private TextView tv;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@OnClick({R.id.btn_b1,R.id.tv_t1})
public void onClick(View view){
switch (view.getId()){
case R.id.btn_b1:
Toast.makeText(MainActivity.this,"点击了按钮",Toast.LENGTH_SHORT).show();
break;
case R.id.tv_t1:
Toast.makeText(MainActivity.this,"点击了tv",Toast.LENGTH_SHORT).show();
break;
}
}
@OnLongClick({R.id.btn_b1,R.id.tv_t1})
public boolean onLongClick(View view){
switch (view.getId()){
case R.id.btn_b1:
Toast.makeText(MainActivity.this,"长按了按钮",Toast.LENGTH_SHORT).show();
break;
case R.id.tv_t1:
Toast.makeText(MainActivity.this,"长按了tv",Toast.LENGTH_SHORT).show();
break;
}
return false;
}
}
2.我们选择继承一个BaseActivity,在onCreate()方法里实现解析操作
public class BaseActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectManager.inject(this);
}
}
4.我们从最简单的SetContentView方法开始,首先需要定一个SetContentView注解
@Target(ElementType.TYPE)//注解作用的范围 1.TYPE:类 2.METHOD:方法
//3.FIELD:字段 不符合的编译器将报错 4.ANNOTATION_TYPE: 元注解:作用与注解之上
@Retention(RetentionPolicy.CLASS)//注解生效的时期 1.RUNTIME 程序运行的时候
//2.SOURCE 源码阶段当你打出代码的时候 3.CLASS 预编译阶段
public @interface SetContentView {
int value();
}
3.那么 InjectManager.inject(this) 方法又需要实现什么呢
public class InjectManager {
public static void inject(Activity activity){
//注入布局
injectLayout(activity);
//注入控件
injectView(activity);
//注入事件
injectEvent(activity);
}
private static void injectLayout(Activity activity) {
try {
//获取当前Activity类
Class<? extends Activity> clazz = activity.getClass();
//获取SetContentView类型的注解对象
SetContentView setContentView=clazz.getAnnotation(SetContentView.class);
//判空
if (setContentView!=null) {
//拿到传入int类型的R.id.xxx
int layoutId = setContentView.value();
//找到Activity的setContentView(int layoutId)方法
Method method = clazz.getMethod("setContentView", int.class);
//执行该方法
method.invoke(activity, layoutId);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void injectView(Activity activity) {
//todo
}
private static void injectEvent(Activity activity) {
//todo
}
}
到此我们已经实现类布局的设置
4.接下来我们需要实现控件的绑定,定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value();
}
5.InjectManager类中injectView(activity)方法 实现控件绑定
private static void injectLayout(Activity activity) {
try {
//获取当前Activity类
Class<? extends Activity> clazz = activity.getClass();
//获取SetContentView类型的注解对象
SetContentView setContentView=clazz.getAnnotation(SetContentView.class);
//判空
if (setContentView!=null) {
//拿到传入int类型的R.id.xxx
int layoutId = setContentView.value();
//找到Activity的setContentView(int layoutId)方法
Method method = clazz.getMethod("setContentView", int.class);
//执行该方法
method.invoke(activity, layoutId);
}
} catch (Exception e) {
e.printStackTrace();
}
}
6.事件监听的注解 事件可以又很多种 onClick() onLongClick()等 所以我们需要先通过元注解获取不同监听的不同监听方法和对象
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
//1.setOnXXXListener 方法名
String listenerSetter();
//2.View.OnXXXClickListener 监听对象
Class<?> listenerType();
}
OnClick.class
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnClickListener",listenerType = View.OnClickListener.class)
public @interface OnClick {
int[] value();
}
OnLongClick.class
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
//向EventBase注解传入当前需要的监听方法和对象
@EventBase(listenerSetter = "setOnLongClickListener",listenerType = View.OnLongClickListener.class)
public @interface OnLongClick {
int[] value();
}
7.注入事件 injectEvent(activity)
private static void injectEvent(Activity activity) {
try {
Class<? extends Activity> clazz = activity.getClass();
//获取当前Activity的所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method:methods){
//得到这些方法的注解
Annotation[] annotations=method.getAnnotations();
for (Annotation annotation:annotations){
Class<? extends Annotation> annotationType= annotation.annotationType();
//过滤没有带注解的方法
if (annotationType!=null){
//获取EventBase注解
EventBase eventBase=annotationType.getAnnotation(EventBase.class);
if (eventBase.annotationType()!=null) {
//得到监听需要的方法和对象
String listenerSetter = eventBase.listenerSetter();
Class<?> listenerType = eventBase.listenerType();
//得到传入的所有的R.id.xxx
Method valueMethod = annotationType.getDeclaredMethod("value");
int[] ids = (int[]) valueMethod.invoke(annotation);
//设置代理
Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),//类加载器
new Class[]{listenerType}, new ClickInvocationHandler(activity,method));//类接口
//遍历所有控件
for (int id : ids) {
//设置监听前还是需要绑定控件的
Object view = activity.findViewById(id);
Method setListener = view.getClass().getMethod(listenerSetter, listenerType);
//ClickInvocationHandler的invoke方法 会对proxy中传入的接口所有方法进行拦截
setListener.invoke(view, proxy);
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
8.InvocationHandler.class
public class ClickInvocationHandler implements InvocationHandler {
Object target;
Method method;
// String methodName;
/**
*
* @param target 目标类
* @param method 要执行的方法
*/
public ClickInvocationHandler(Object target,Method method) {//String methodName,
this.target = target;
this.method = method;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//对原来接口中的onXXclick方法进行拦截 换成执行我们传入自己的方法 参数不变
return this.method.invoke(target,objects);
}
}
~~~最后附上github地址:demo