在项目开发中findViewById以及控件的点击事件的场景是最多的,大量的findViewById并强转会浪费不少的开发时间,那么是否能解决掉这个问题呢?答案肯定是能解决;像xutils、butterKnidnife等主流第三方框架就很好的解决了这些问题,那么这些第三方的已经帮助开发者解决掉这些问题了,是否还要自己学着去写一套手写的IOC注解呢,肯定是需要的,在学习写的过程中能更好的理解这些第三方的实现原理,同时也能也符合项目的需要。下面这个是在学习过程中写的一个IOC注解:
涉及知识点:
1、注解
2、反射
3、动态代理(点击事件注入的时候会涉及到)
实现:
1、activity布局注入,不需要调用setContentView
2、activity、fragment等 findViewById、setOnClickListener、setOnLongClickListener等事件注入,点击和长按事件可轻松切换
代码实现:
ContentView 是用来activity布局注入:
/**
* Created by Administrator on 2017/12/13.
* activity 布局注入
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
int value();
}
activity布局注入逻辑:
/**
* Activity加载布局
*
* @param context
*/
private static void injectLayout(Object context) {
int layoutId = 0;
Class> clazz = context.getClass();
//拿到activity类上面的注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView != null) {
layoutId = contentView.value();
try {
Method method = clazz.getMethod("setContentView", int.class);
method.setAccessible(true);
try {
//反射执行
method.invoke(context, layoutId);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
ViewInject是findViewById注入:
/**
* Created by Administrator on 2017/12/13.
* findViewById注入
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
int value();
}
findViewById注入逻辑:
/**
* Activity加载view
*
* @param context
*/
private static void injectView(ViewFinder finder, Object context) {
Class> clazz = context.getClass();
//获取activity类中所有的成员变量
Field[] fields = clazz.getDeclaredFields();
//遍历
for (Field field : fields) {
field.setAccessible(true);
//得到成员变量的注解
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
//获取对应的id
int valueId = viewInject.value();
//反射获取控件
View viewById = finder.findViewById(valueId);
if (viewById == null) {
continue;
}
//反射调用方法
try {
field.set(context, viewById);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
下面这些是点击事件、长按事件、条目点击事件等事件注入:
/**
* Created by Administrator on 2017/12/15.
* 对所有的点击事件扩展
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {
/**
* 设置监听的方法
* @return
*/
String listenerSetter();
/**
* 事件类型
* @return
*/
Class> listenerType();
/**
* 回调方法
* 事件被触发后,执行回调方法名称
* @return
*/
String callBackMethod();
}
/**
* Created by Administrator on 2017/12/15.
* 点击事件
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener",
listenerType = View.OnClickListener.class,
callBackMethod = "onClick")
public @interface OnClick {
/**
* 点击事件控件数组
* @return
*/
int [] value()default -1;;
}
/**
* Created by Administrator on 2017/12/15.
* 长按事件
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnLongClickListener",
listenerType = View.OnLongClickListener.class,
callBackMethod = "onLongClick")
public @interface OnLongClick {
int [] value() default -1;
}
/**
* Created by Administrator on 2017/12/15.
* 条目点击事件
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnItemClickListener",
listenerType = AdapterView.OnItemClickListener.class,
callBackMethod = "onItemClick")
public @interface OnItemClick {
int [] value();
}
在注入事件时涉及到动态代理的使用;
/**
* Created by Administrator on 2017/12/15.
* 动态代理
*/
public class ListenerInvocationHandler implements InvocationHandler{
//代理的真实对象
private Object context;
private Map methodMap;
public ListenerInvocationHandler(Object context, Map methodMap) {
this.context = context;
this.methodMap = methodMap;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
//决定是否需要进行代理
Method metf = methodMap.get(name);
if(metf!=null){
return metf.invoke(context,args);
}else{
return metf.invoke(proxy,args);
}
}
}
事件注入逻辑:
/**
* 事件注入
*
* @param context
*/
private static void injectEvents(ViewFinder finder, Object context) {
Class> clazz = context.getClass();
//获取activity里面所有的方法
Method[] methods = clazz.getDeclaredMethods();
//循环遍历
for (Method method : methods) {
//获取方法上面所有的注解
Annotation[] annotations = method.getAnnotations();
//循环遍历所有的注解
for (Annotation annotation : annotations) {
//获取注解类型
Class> anntionType = annotation.annotationType();
//获取注解上面的注解
EventBase eventBase = anntionType.getAnnotation(EventBase.class);
if (eventBase == null) {
continue;
}
//开始获取事件三要素 通过反射注入进去
String listenerSetter = eventBase.listenerSetter();
Class> listenerType = eventBase.listenerType();
String callMethod = eventBase.callBackMethod();
//方法名与方法method进行对应关系
Map methodMap = new HashMap<>();
methodMap.put(callMethod, method);
try {
Method valueMethod = anntionType.getDeclaredMethod("value");
int[] viewIds = (int[]) valueMethod.invoke(annotation);
//遍历获取到的id
for (int viewId : viewIds) {
//通过反射获取相应的view控件
View view = finder.findViewById(viewId);
if (view == null) {
continue;
}
Method setListener = view.getClass().getMethod(listenerSetter, listenerType);
ListenerInvocationHandler handle = new ListenerInvocationHandler(context, methodMap);
Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),
new Class[]{listenerType},
handle);
setListener.invoke(view, proxy);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
activity中使用:
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.tv_ioc)
private TextView tvIOC;
@ViewInject(R.id.tv_ioc1)
private TextView tvIOC1;
@ViewInject(R.id.tv_ioc2)
private TextView tvIoc2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.inject(this);
tvIOC.setText("IOC注解 单击事件");
tvIOC1.setText("IOC注解 长按事件");
tvIoc2.setText("fragment IOC注解");
}
@OnClick(R.id.tv_ioc)
public void click(View view){
Toast.makeText(this,tvIOC.getText().toString(),Toast.LENGTH_LONG).show();
}
@OnLongClick(R.id.tv_ioc1)
public boolean longClick(View view){
Toast.makeText(this,tvIOC1.getText().toString(),Toast.LENGTH_LONG).show();
return true;
}
@OnClick(R.id.tv_ioc2)
public void toFragment(View view){
startActivity(new Intent(this,SecondActivity.class));
}
}
fragment中使用:
public class IOCFragment extends Fragment{
@ViewInject(R.id.tv_ioc)
private TextView tvIOC;
@ViewInject(R.id.tv_ioc1)
private TextView tvIOC1;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_ioc, container, false);
InjectUtils.inject(view,this);
tvIOC.setText("IOC注解 fragment单击事件");
tvIOC1.setText("IOC注解 fragment长按事件");
return view;
}
@OnClick(R.id.tv_ioc)
public void click(View view){
Toast.makeText(getActivity(),tvIOC.getText().toString(),Toast.LENGTH_LONG).show();
}
@OnLongClick(R.id.tv_ioc1)
public boolean longClick(View view){
Toast.makeText(getActivity(),tvIOC1.getText().toString(),Toast.LENGTH_LONG).show();
return true;
}
}
源码地址:
https://pan.baidu.com/s/1pL5gK4b