相信大家对findViewById()都不陌生,有人对这个很是苦恼,有时候往往要在一个类中写特别多的findViewById。好在后面有了xUtils,butterknife等框架让我们轻松很多。大家有没有看过这类第三方开源框架的源码。
我们在这里就看一下xUtils的源码,看看是怎么实现的?
这里给一下xUtils3 github地址点击打开链接
先看一下使用方法
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.icon)
private ImageView mImageView ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
x.view().inject(this);
mImageView .setImageResource(R.drawable.icon);
}
/**
* 1. 方法必须私有限定,
* 2. 方法参数形式必须和type对应的Listener接口一致.
* 3. 注解参数value支持数组: value={id1, id2, id3}
* 4. 其它参数说明见{@link org.xutils.view.annotation.Event}类的说明.
**/
@Event(value = R.id.icon,
type = View.OnClickListener.class/*可选参数, 默认是View.OnClickListener.class*/)
private void iconIvClick(View view) {
Toast.makeText(this, "被点击了", Toast.LENGTH_LONG).show();
}
}
我这里就是设置一张图片和一个点击事件,其实主要解决的就是我们不再需要findViewById()和setOnClickListener(),我们来看一下源码到底是怎么实现的:
这里只是部分源码 如果想看详细的源码请下载xUtils3 源码
@Override
public void inject(Activity activity) {
//获取Activity的ContentView的注解
Class<?> handlerType = activity.getClass();
try {
// 找到ContentView这个注解,在activity类上面获取
ContentView contentView = findContentView(handlerType);
if (contentView != null) {
int viewId = contentView.value();
if (viewId > 0) {
// 如果有注解获取layoutId的值,利用反射调用activity的setContentView方法注入视图
Method setContentViewMethod =
handlerType.getMethod("setContentView", int.class);
setContentViewMethod.invoke(activity, viewId);
}
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
// 处理 findViewById和setOnclickListener的注解
injectObject(activity, handlerType, new ViewFinder(activity));
}
private static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {
// .......
// 从父类到子类递归
injectObject(handler, handlerType.getSuperclass(), finder);
// inject view 注入控件View
Field[] fields = handlerType.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
// ......
// 获取viewInject 注解
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
try {
// 其实最终还是调用findViewById的方法
View view = finder.findViewById(viewInject.value(),
viewInject.parentId());
if (view != null) {
// 利用反射 把View注入到该属性中
field.setAccessible(true);
field.set(handler, view);
} else {
// ......
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
} // end inject view
// inject event 注入事件
Method[] methods = handlerType.getDeclaredMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
// ......
//检查当前方法是否是event注解的方法
Event event = method.getAnnotation(Event.class);
if (event != null) {
try {
// id参数
int[] values = event.value();
int[] parentIds = event.parentId();
int parentIdsLen = parentIds == null ? 0 : parentIds.length;
//循环所有id,生成ViewInfo并添加代理反射 主要使用了动态代理的设计模式
for (int i = 0; i < values.length; i++) {
int value = values[i];
if (value > 0) {
ViewInfo info = new ViewInfo();
info.value = value;
info.parentId = parentIdsLen > i ? parentIds[i] : 0;
method.setAccessible(true);
// EventListenerManager 动态代理执行相应的方法
EventListenerManager.addEventMethod(
finder, info, event, handler, method);
}
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
} // end inject event
}
这里其实就是我们利用类的反射循环获取属性的注解值然后通过findViewById之后,动态的注入到控件属性里面;事件注入也是类似首先findViewById然后利用动态代理去反射执行方法。
接下来我们自己来实现一套IOC注解框架吧,采用的方式反射加注解和Xutils类似
/**
* Created by Cwm on 2018/1/17.
* <p>
* View注解的Annotation
*
* @Target({ElementType.FIELD})代表注解的位置 FIELD为属性 TYPE为类上使用 CONSTRUCTOR构造函数使用
* @Retention(RetentionPolicy.CLASS)什么时候生效 CLASS编译时 RUNTIME运行时 SOURCE源码资源
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ViewById {
int value();
}
/**
* Created by Cwm on 2018/1/17.
* <p>
* 监测网络Annotation
*
* @Target({ElementType.FIELD})代表注解的位置 FIELD为属性 TYPE为类上使用 CONSTRUCTOR构造函数使用
* @Retention(RetentionPolicy.CLASS)什么时候生效 CLASS编译时 RUNTIME运行时 SOURCE源码资源
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface CheckNet {
String value() default "亲 您的网络不太给力哦...";
}
IOC 注入 ViewUtils
public class ViewUtils {
//activity注解
public static void inject(Activity activity) {
inject(new ViewFinder(activity), activity);
}
//fragment注解
public static void inject(View view, Object object) {
inject(new ViewFinder(view), object);
}
//自定义view注解
public static void inject(View view) {
inject(new ViewFinder(view), view);
}
//兼容上面三个方法 object-->反射需要执行的类
private static void inject(ViewFinder finder, Object object) {
injectFiled(finder, object);//注入属性
injectEvent(finder, object);//注入事件
}
/**
* 注入属性
*
* @param finder
* @param object
*/
private static void injectFiled(ViewFinder finder, Object object) {
//1、获取类里面的所有属性
Class<?> aClass = object.getClass();//获取class
Field[] fields = aClass.getDeclaredFields();//获取所有的属性
//2、获取ViewById里面的value值
if (fields != null && fields.length > 0) {
for (Field field : fields) {
//获取该属性中ViewById的注解对象
ViewById viewById = field.getAnnotation(ViewById.class);
if (viewById != null) {
int value = viewById.value();//获取ViewId
//3、findById找到View
View view = finder.findViewById(value);
field.setAccessible(true);//不管什么修饰符都可以操作
if (view != null) {
try {
//4、动态注入View
field.set(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* 注入事件
*
* @param finder
* @param object
*/
private static void injectEvent(ViewFinder finder, Object object) {
//1、获取类里面的所有方法
Class<?> aClass = object.getClass();//获取class
Method[] methods = aClass.getDeclaredMethods();//获取所有的方法
if (methods != null && methods.length > 0) {
for (Method method : methods) {
Onclick onclick = method.getAnnotation(Onclick.class);//获取该注解对象
CheckNet checkNet = method.getAnnotation(CheckNet.class);
boolean isCheckNet = checkNet != null ? true : false;
String isCheckNetvalue = null;
if (isCheckNet) {
isCheckNetvalue = checkNet.value();//自定义吐司字符串/取默认的
}
if (onclick != null) {
int[] values = onclick.value();//获取onclick的value值
if (values != null && values.length > 0) {
for (int value : values) {
View view = finder.findViewById(value);//绑定控件
if (view != null) {
view.setOnClickListener(new DeclaredOnClickListener(object, method, isCheckNet, isCheckNetvalue));//view点击事件
}
}
}
}
}
}
}
//继承点击事件
public static class DeclaredOnClickListener implements View.OnClickListener {
private Object mObject;//操作类
private Method mMethod;//操作方法
private boolean mIsCheckNet;
private String isCheckString;
public DeclaredOnClickListener(Object object, Method method, boolean checkNet, String CheckString) {
mObject = object;
mMethod = method;
mIsCheckNet = checkNet;
isCheckString = CheckString;
}
@Override
public void onClick(View view) {
//判断是否执行网络检测
if (mIsCheckNet) {
if (!isNetworkAvailable(view.getContext())) {
Toast.makeText(view.getContext(), isCheckString, Toast.LENGTH_SHORT).show();
return;
}
}
//这样好处:即使点击事件里面逻辑操作有错误也不会崩溃,只会打印错误,去LogCat找错误
mMethod.setAccessible(true);//可以越过所有修饰符
try {
mMethod.invoke(mObject, view);//方法反射执行
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject, null);//如果view为空调用该方法
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
//检测网络
private static NetworkInfo getNetworkInfo(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo();
}
/**
* 检查是否有网络
*/
public static boolean isNetworkAvailable(Context context) {
NetworkInfo info = getNetworkInfo(context);
return info != null && info.isAvailable();
}
/**
* Created by Cwm on 2018/1/17.
* View findViewById辅助类
*/
public class ViewFinder {
private Activity mActivity;
private View mView;
public ViewFinder(Activity activity) {
mActivity = activity;
}
public ViewFinder(View view) {
mView = view;
}
/**
* 最终执行绑定控件方法
* @param viewId
* @return
*/
public View findViewById(int viewId) {
return mActivity != null ? mActivity.findViewById(viewId) : mView.findViewById(viewId);
}
使用方法:(这里没有上传检测网络的使用方法,可以自己使用下)
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.icon)
private ImageView mIcon;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
mIconIv.setImageResource(R.drawable.icon);
}
@OnClick(R.id.icon)
private void onClick(View view) {
Toast.makeText(this, "图片点击了"+i, Toast.LENGTH_LONG).show();
}
}
以上就是自定义简易的IOC注解框架