1 概念
控制反转(Inversion of Control,缩写为IoC),是指原来由程序代码中主动获取的资源,转变为由第三方获取并使原来的代码被动接收的方式,以达到解耦的效果。
IoC是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。
通过IoC,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它,即依赖被注入到对象中。
IoC中最基本的Java技术就是“反射”编程。
IoC可以认为是一种全新的设计模式,但是理论和时间成熟相对较晚,并没有包含在GoF中。
给出一个较为通俗的解释图:
2 应用
2.1 布局注入
布局注入即通过IoC的方式替换Android中传统的setContentView(R.layout.activity_main)的方式,示例程序见代码。
项目结构如图所示:
先自定义一个注解接口ILayoutInject.java,注意@interface与interface的区别哦。
package com.example.myioc.layout;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 布局注入接口,作用域为TYPE
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ILayoutInject {
int value();
}
自定义一个注解工具类InjectUtils.java:
package com.example.myioc.utils;
import com.example.myioc.layout.ILayoutInject;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class InjectUtils {
public static void inject(Object context) {
injectLayout(context);
}
/**
* 布局注入
*
* @param context 上下文参数,定义为Object是为了方便
*/
private static void injectLayout(Object context) {
int layoutId = 0;
Class<?> clazz = context.getClass();
ILayoutInject iLayoutInject = clazz.getAnnotation(ILayoutInject.class);
if (iLayoutInject != null) {
layoutId = iLayoutInject.value();
try {
// 替换setContentView()
Method method = context.getClass().getMethod("setContentView", int.class);
method.invoke(context, layoutId);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
自定义BaseActivity基类:
package com.example.myioc.base;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.example.myioc.utils.InjectUtils;
public class BaseActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.inject(this);
}
}
直接使用,替换setContentView():
package com.example.myioc;
import android.os.Bundle;
import com.example.myioc.base.BaseActivity;
import com.example.myioc.layout.ILayoutInject;
@ILayoutInject(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/bt1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="50dp"
android:text="Hello World!"
android:textAllCaps="false"
android:textSize="22sp" />
<Button
android:id="@+id/bt2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="50dp"
android:text="Every Time"
android:textAllCaps="false"
android:textSize="22sp" />
</LinearLayout>
运行效果(能看到布局文件):
2.2 View控件注入
项目结构图:
添加自定义控件注入接口IViewInject.java,代码如下:
package com.example.myioc.view;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* View控件注入接口,作用域为FIELD
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface IViewInject {
int value();
}
在工具类InjectUtils中添加控件注入方法injectView():
package com.example.myioc.utils;
import android.view.View;
import com.example.myioc.layout.ILayoutInject;
import com.example.myioc.view.IViewInject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class InjectUtils {
public static void inject(Object context) {
injectLayout(context);
injectView(context);
}
/**
* View控件注入
*
* @param context 上下文参数,定义为Object是为了方便
*/
private static void injectView(Object context) {
Class<?> aClass = context.getClass();
Field[] fields = aClass.getDeclaredFields();
//MainActivity mainActivity = (MainActivity) context;
for (Field field : fields) {
IViewInject iViewInject = field.getAnnotation(IViewInject.class);
if (iViewInject != null) {
int viewId = iViewInject.value();
try {
// 替换findViewById()方法
Method method = aClass.getMethod("findViewById", int.class);
//View views = mainActivity.findViewById(viewId);
View view = (View) method.invoke(context, viewId);
field.setAccessible(true);
field.set(context, view);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
/**
* 布局注入
*
* @param context 上下文参数,定义为Object是为了方便
*/
private static void injectLayout(Object context) {
int layoutId = 0;
Class<?> clazz = context.getClass();
ILayoutInject iLayoutInject = clazz.getAnnotation(ILayoutInject.class);
if (iLayoutInject != null) {
layoutId = iLayoutInject.value();
try {
// 替换setContentView()
Method method = context.getClass().getMethod("setContentView", int.class);
method.invoke(context, layoutId);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
基类BaseActivity不需要做任何变动,直接在MainActivity中应用:
package com.example.myioc;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.example.myioc.base.BaseActivity;
import com.example.myioc.layout.ILayoutInject;
import com.example.myioc.view.IViewInject;
@ILayoutInject(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@IViewInject(R.id.bt1)
private Button bt1;
@IViewInject(R.id.bt2)
private Button bt2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
bt1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "我是btOne", Toast.LENGTH_SHORT).show();
}
});
bt2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "我是btTwo", Toast.LENGTH_SHORT).show();
}
});
}
}
效果图(点击生效):
2.3 事件注入
布局注入、控件注入都较简单,事件注入略微复杂。
首先,了解下事件三要素,以给Button设置点击事件为例:
(1)事件源,如button
(2)事件,如View的点击事件onClick()
(3)事件处理,即订阅事件,如给button设置点击监听setOnClickListener()
事件注入需要完成以上三要素的相应步骤才可生效,详见代码。
项目结构:
事件注入接口IEventInject:
package com.example.myioc.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 事件注入接口,作用域为ANNOTATION_TYPE,即在另一个注解上使用
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface IEventInject {
/**
* 事件订阅
*/
String listenerSet();
/**
* 事件监听类型
*/
Class<?> listenerType();
/**
* 事件处理
*/
String callbackMethod();
}
添加IOnClick注解接口:
package com.example.myioc.event;
import android.view.View;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义点击事件接口,绑定为View的onClick点击事件
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@IEventInject(listenerSet = "setOnClickListener",
listenerType = View.OnClickListener.class,
callbackMethod = "onClick")
public @interface IOnClick {
int[] value() default -1;
}
事件处理过程中需要使用动态代理:
package com.example.myioc;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 使用动态代理
*/
public class ListenerInvocationHandler implements InvocationHandler {
private Object activityObj;
private Method activityMethod;
public ListenerInvocationHandler(Object activityObj, Method activityMethod) {
this.activityObj = activityObj;
this.activityMethod = activityMethod;
}
/**
* 点击按钮就执行该方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 此处调用被注解了的click()
return activityMethod.invoke(activityObj, args);
}
}
工具类InjectUtils中添加injectEvent()方法:
package com.example.myioc.utils;
import android.view.View;
import com.example.myioc.ListenerInvocationHandler;
import com.example.myioc.event.IEventInject;
import com.example.myioc.event.IOnClick;
import com.example.myioc.layout.ILayoutInject;
import com.example.myioc.view.IViewInject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class InjectUtils {
public static void inject(Object context) {
injectLayout(context);
injectView(context);
injectEvent(context);
}
/**
* 事件注入
*
* @param context 上下文参数,定义为Object是为了方便
*/
private static void injectEvent(Object context) {
Class<?> clazz = context.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//IOnClick iOnClick = method.getAnnotation(IOnClick.class);
// 得到方法上的所有注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
Class<?> annotationClass = annotation.annotationType();
IEventInject iEventInject = annotationClass.getAnnotation(IEventInject.class);
// 如果没有iEventInject,则表示当前方法不是一个处理事件的方法
if (iEventInject == null) {
continue;
}
/*
开始获取事件处理的相关信息,用于确定是哪种事件(如,是onClick还是onLongClick)以及由谁来处理
*/
// 订阅
String listenerSetStr = iEventInject.listenerSet();
// 事件监听类型
Class<?> listenerTypeClass = iEventInject.listenerType();
// 事件处理(事件被触发之后,执行的回调方法的名称)
String callbackMethodStr = iEventInject.callbackMethod();
Method valueMethod = null;
try {
// 反射得到ID,再根据ID得到对应的View
valueMethod = annotationClass.getDeclaredMethod("value");
int[] viewIds = (int[]) valueMethod.invoke(annotation);
for (int viewId : viewIds) {
Method findViewById = clazz.getMethod("findViewById", int.class);
View view = (View) findViewById.invoke(context, viewId);
if (view == null) {
continue;
}
/*
得到ID对应的View后,在该View上执行监听(使用动态代理)
需要执行activity上的onClick方法,
activity==context click==method
*/
ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(context, method);
// proxy -> View.OnClickListener()对象
Object proxyObj = Proxy.newProxyInstance(
listenerTypeClass.getClassLoader(),
new Class[]{
listenerTypeClass},
listenerInvocationHandler
);
//执行方法,如setOnClickListener,new View.OnClickListener()
Method onClickMethod = view.getClass().getMethod(listenerSetStr, listenerTypeClass);
onClickMethod.invoke(view, proxyObj);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
/**
* View控件注入
*
* @param context 上下文参数,定义为Object是为了方便
*/
private static void injectView(Object context) {
Class<?> aClass = context.getClass();
Field[] fields = aClass.getDeclaredFields();
//MainActivity mainActivity = (MainActivity) context;
for (Field field : fields) {
IViewInject iViewInject = field.getAnnotation(IViewInject.class);
if (iViewInject != null) {
int viewId = iViewInject.value();
try {
// 替换findViewById()方法
Method method = aClass.getMethod("findViewById", int.class);
//View views = mainActivity.findViewById(viewId);
View view = (View) method.invoke(context, viewId);
field.setAccessible(true);
field.set(context, view);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
/**
* 布局注入
*
* @param context 上下文参数,定义为Object是为了方便
*/
private static void injectLayout(Object context) {
int layoutId = 0;
Class<?> clazz = context.getClass();
ILayoutInject iLayoutInject = clazz.getAnnotation(ILayoutInject.class);
if (iLayoutInject != null) {
layoutId = iLayoutInject.value();
try {
// 替换setContentView()
Method method = context.getClass().getMethod("setContentView", int.class);
method.invoke(context, layoutId);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
直接使用:
package com.example.myioc;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.example.myioc.base.BaseActivity;
import com.example.myioc.event.IOnClick;
import com.example.myioc.layout.ILayoutInject;
import com.example.myioc.view.IViewInject;
@ILayoutInject(R.layout.activity_main)
public class MainActivity extends BaseActivity {
/* @IViewInject(R.id.bt1)
private Button bt1;
@IViewInject(R.id.bt2)
private Button bt2;*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
/*bt1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "我是btOne", Toast.LENGTH_SHORT).show();
}
});
bt2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "我是btTwo", Toast.LENGTH_SHORT).show();
}
});*/
}
@IOnClick({
R.id.bt1, R.id.bt2})
public void myOnClick(View v) {
switch (v.getId()) {
case R.id.bt1:
Toast.makeText(getApplicationContext(), "我是myOnClick -> btOne", Toast.LENGTH_SHORT).show();
break;
case R.id.bt2:
Toast.makeText(getApplicationContext(), "我是myOnClick -> btTwo", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
}
效果图(点击生效):
2.4 长点击与Dialog应用
为项目添加长点击效果,并添加Dialog看能否成功应用。
项目结构图:
添加长点击IOnLongClick:
package com.example.myioc.event;
import android.view.View;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义点击事件接口,绑定为View的onLongClick点击事件
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@IEventInject(
listenerSet = "setOnLongClickListener",
listenerType = View.OnLongClickListener.class,
callbackMethod = "onLongClick"
)
public @interface IOnLongClick {
int[] value() default -1;
}
直接应用即可,代码在添加Dialog后一起给出。
以上测试了布局注入、View控件注入、事件注入,现添加一个Dialog并测试是否可以将这三种注入成功应用。
先定义一个Dialog基类:
package com.example.myioc.base;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import com.example.myioc.utils.InjectUtils;
public class BaseDialog extends Dialog {
public BaseDialog(@NonNull Context context) {
super(context);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.inject(this);
}
}
定义Dialog类:
package com.example.myioc;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.example.myioc.base.BaseDialog;
import com.example.myioc.event.IOnClick;
import com.example.myioc.layout.ILayoutInject;
import com.example.myioc.view.IViewInject;
@ILayoutInject(R.layout.dialog_news)
public class NewsDialog extends BaseDialog {
@IViewInject(R.id.dialogBtn)
private Button dialogBtn;
public NewsDialog(@NonNull Context context) {
super(context);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Toast.makeText(getContext(), "dialog控件注入:" + dialogBtn, Toast.LENGTH_SHORT).show();
}
@IOnClick(R.id.dialogBtn)
public void myOnClick(View v) {
Toast.makeText(getContext(), "dialog事件注入", Toast.LENGTH_SHORT).show();
}
@Override
public void show() {
super.show();
// 设置宽度全屏,要设置在show的后面
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
layoutParams.gravity = Gravity.BOTTOM;
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
getWindow().getDecorView().setPadding(0, 0, 0, 0);
getWindow().setAttributes(layoutParams);
}
}
Dialog类的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<Button
android:id="@+id/dialogBtn"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:layout_gravity="center"
android:gravity="center"
android:text="我是一个Dialog" />
</LinearLayout>
应用:
package com.example.myioc;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.example.myioc.base.BaseActivity;
import com.example.myioc.event.IOnClick;
import com.example.myioc.event.IOnLongClick;
import com.example.myioc.layout.ILayoutInject;
import com.example.myioc.view.IViewInject;
@ILayoutInject(R.layout.activity_main)
public class MainActivity extends BaseActivity {
/*@IViewInject(R.id.bt1)
private Button bt1;
@IViewInject(R.id.bt2)
private Button bt2;*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
/*bt1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "我是btOne", Toast.LENGTH_SHORT).show();
}
});
bt2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "我是btTwo", Toast.LENGTH_SHORT).show();
}
});*/
}
@IOnClick({
R.id.bt1, R.id.bt2})
public void myOnClick(View v) {
switch (v.getId()) {
case R.id.bt1:
Toast.makeText(getApplicationContext(), "我是myOnClick -> btOne", Toast.LENGTH_SHORT).show();
NewsDialog newsDialog = new NewsDialog(this);
newsDialog.show();
break;
case R.id.bt2:
Toast.makeText(getApplicationContext(), "我是myOnClick -> btTwo", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
@IOnLongClick({
R.id.bt1, R.id.bt2})
public boolean myOnLongClick(View v) {
switch (v.getId()) {
case R.id.bt1:
Toast.makeText(getApplicationContext(), "myOnLongClick -> btOne", Toast.LENGTH_SHORT).show();
break;
case R.id.bt2:
Toast.makeText(getApplicationContext(), "myOnLongClick -> btTwo", Toast.LENGTH_SHORT).show();
NewsDialog newsDialog = new NewsDialog(this);
newsDialog.show();
break;
default:
break;
}
/*
需要给一个返回值,否则报错:
java.lang.NullPointerException: Expected to unbox a 'boolean' primitive type but was returned null
*/
return true;
}
}
效果图(长点击和Dialog):
微信公众号: TechU