android ioc框架笔记。
最近在学习ioc框架,学习之后对自己的学习做一个简单的笔记。方便自己日后复习,以及再次使用。
概要:
通过反射机制,代理模式。去动态加载我们的布局,控件,以及事件。
源码实现:
首先我们需要一个注入帮助类InjectUtils。
首先我们提供一个可供外界访问public的方法,里面去实现我们的布局,控件,以及事件的注入方法。
public static void inject(Context context) {
//动态加载布局
injectLayout(context);
//加载控件
Map<Integer, Object> injectView = injectView(context);
//view 添加监听
injectEvent(context, injectView);
}
下面实现我们的布局加载
实现之前需要去定义我们的注解类@ContentView
新建一个注解类
package com.example.lq.test0420.frameWork.layout;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by Administrator on 2016-4-23.
*/
//指定作用范围 类上面
@Target(ElementType.TYPE)
//指定生命周期 当程序运行的时候 通过反射机制加载
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
有了这个@ContentView注解类以后我们就可以实现我们的动态加载布局了。
private static void injectLayout(Context context) {
//首先 定义注解类 就是上面的@ContentView
//通过反射机制注入布局
Class<? extends Context> clazz = context.getClass();
//获取contentView 注解类
ContentView contentView = clazz.getAnnotation(ContentView.class);
try {
if (contentView != null) {//判断有没有配置注解
//绑定布局
//加载布局 ---activity里面用 LayoutInfalter or setContentView去加载
//获取布局ID
int layoutID = contentView.value();
//获取加载布局的setContentView方法
Method setContentViewMethod = clazz.getMethod("setContentView", int.class);
setContentViewMethod.invoke(context, layoutID);//执行context 上的方法setcontentView
}
} catch (Exception e) {
e.printStackTrace();
}
}
这一步做完之后,再把我们的控件也注入进去,和上面一样。首先需要一个控件的注入类,新建一个@ViewInject类
package com.example.lq.test0420.frameWork.view;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by Administrator on 2016-4-23.
*/
//范围
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
}
有了注入类就可以去写我们注入控件的方法了。
private static Map<Integer, Object> injectView(Context context) {
//创建ViewInject注解类
//通过反射机制绑定View
Class<? extends Context> clazz = context.getClass();
Field[] fields = clazz.getDeclaredFields();//拿到所有属性
Map<Integer, Object> viewMap = new HashMap<Integer, Object>();
try {
for (Field field : fields) {
//获取属性注解
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
//获取控件ID
int viewID = viewInject.value();
//加载控件 activity findViewByid
Method findViewByidMethod = clazz.getMethod("findViewById", int.class);
//绑定 加载VIEW
Object view = findViewByidMethod.invoke(context, viewID);
field.setAccessible(true);//设置访问权限
//给属性赋值
field.set(context, view);//给Actiovity 的view 赋值
viewMap.put(viewID, view);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return viewMap;
}
现在我们的布局以及控件的注入就Ok了。我们可以在我们的activity里面测试一下。
package com.example.lq.test0420;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.example.lq.test0420.frameWork.InjectUtils;
import com.example.lq.test0420.frameWork.event.OnClick;
import com.example.lq.test0420.frameWork.layout.ContentView;
import com.example.lq.test0420.frameWork.view.ViewInject;
@ContentView(R.layout.activity_main)//注入布局
public class MainActivity extends Activity {
@ViewInject(R.id.btn_title)//注入控件
private Button btn_titile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.inject(this);//这一步一定要有,否则没法注入的
if (btn_titile != null) {
Toast.makeText(this, "哈哈", Toast.LENGTH_LONG).show();
}
}
}
到这一步就可以看到我们的布局控件已经显示在我们的手机上啦。有点小激动呢。
到这一歩还没完,难点是在我们的事件的动态加载。下面我们实现一下我们事件的动态加载方法。
跟上面一样哈,也是需要我们的注解类.首先我们需要思考一下。事件很多哎,那么我们就需要找出他的共性。叫做xxx三要素。
frist 事件源也就是我们的view second回调方法 third 监听器。
有事件就会有这三个玩意哈。
所以我们需要来一个基类把这三个都放进去。
package com.example.lq.test0420.frameWork.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by Administrator on 2016-4-23.
*/
//作用在注解上
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BaseEvent {
//事件三要素
// 事件源--view
String listenerSetter();
// 回调方法 --xxclick
String callBackMethod();
// 监听器----xxlistener
Class<?> listenerType();
}
然后我们就去搞我们的事件注解类
package com.example.lq.test0420.frameWork.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;
/**
* Created by Administrator on 2016-4-23.
*/
@Target(ElementType.METHOD)//指定方法上
@Retention(RetentionPolicy.RUNTIME)
//指定事件三要素
@BaseEvent(listenerSetter = "setOnClickListener",callBackMethod = "onClick",listenerType = View.OnClickListener.class)
public @interface OnClick {
int[] value();
}
在这里我们的注解类就搞定了,下面就是我们动态加载事件方法的实现了。这里学的很蒙,如果有啥不对的地方,欢迎斧正。
/**
* 事件三要素 事件源--view 回调方法 --xxclick 监听器----xxlistener
*
* @param context
* @param injectView
*/
private static void injectEvent(Context context, Map<Integer, Object> injectView) {
//创建注解文件
//绑定监听
Class<? extends Context> clazz = context.getClass();
//获取所有的方法
Method[] methods = clazz.getMethods();
try {
for (Method method : methods) {//遍历所有的方法去拿注解
//获取方法上面的所有注解
Annotation[] annotations = method.getAnnotations();
if (annotations != null) {
for (Annotation annotation : annotations) {
//拿到BaseEvent注解 获取事件三要素
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType != null) {
BaseEvent baseEvent = annotationType.getAnnotation(BaseEvent.class);
if (baseEvent != null) {
//说明注解是onclick注解
//获取事件三要素
String listenerSetter = baseEvent.listenerSetter();
String callBackMethod = baseEvent.callBackMethod();
Class<?> listenerType = baseEvent.listenerType();
//获取控件ID
Method valueMethod = annotationType.getDeclaredMethod("value");
int[] viewsID = (int[]) valueMethod.invoke(annotation);//执行annotaion方法
//动态代理模式(java 自带动态代理类)
ListenerInvocationHandler listenerInvocationHandler =
new ListenerInvocationHandler(context);
listenerInvocationHandler.addMehtod(callBackMethod, method);
//执行代理
//返回对象为 on--xxxlistener
Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(),//类加载器
new Class[]{listenerType},//代理的类
listenerInvocationHandler) //绑定监听
for (int i : viewsID) {
Object view = injectView.get(i);
//获取setOnxxlistener()
Method setOnXXXlistenerMethod =
view.getClass().getMethod(listenerSetter, listenerType);
setOnXXXlistenerMethod.invoke(view, listener);//执行view上的监听方法
}
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
到此我们的动态加载事件就搞定。下面去act里面实现一下。
@OnClick({R.id.btn_title})
public void show(View v) {
Toast.makeText(this, "你好啊", Toast.LENGTH_LONG).show();
}
写完这一句以后,你就可以点击下试试有没有点击事件的出现咯。如果出现了,说明成功了。
学了这个才知道移动构架师能赚那么多钱不是白赚的哈,努力奋斗吧。
——-致和我一样的码农