Android 自定义一个ioc框架,快速完成开发
ioc
框架现在成熟的已经有很多了,xutils
,butterknife
等等都是ioc
框架,利用这些框架我们可以快速的进行开发而不必重复写一些代码。但是在实际开发中我们往往有自己的需求,这时候我们就需要自定义一个ioc
框架,根据需求我们可以在里面加入不同的注解简化我们的操作。下面我们自己仿照butterknife的功能来自定义一个findViewById
和onClick()
的ioc框架。
首先我们定义一个注解,使用@interface
表示一个注解,代码如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewById {
int value();
}
其中value()
是我们使用注解的时候的参数值:
@ViewById(R.id.test_tv)
private TextView mTestTv;
就是上述代码中的R.id.test_tv
@Target(ElementType.XXX)
代表的是放在那个位置:
@Target(ElementType.TYPE)
接口、类、枚举、注解
@Target(ElementType.FIELD)
字段、枚举的常量
@Target(ElementType.METHOD)
方法
@Target(ElementType.PARAMETER)
方法参数
@Target(ElementType.CONSTRUCTOR)
构造函数
@Target(ElementType.LOCAL_VARIABLE)
局部变量
@Target(ElementType.ANNOTATION_TYPE)
注解
@Target(ElementType.PACKAGE)
包
@Retention(RetentionPolicy.XXX)
代表什么时候检测:
RetentionPolicy.RUNTIME
代表运行时检测,class文件中存在
RetentionPolicy.CLASS
代表编译时检测,存在于class文件中,运行时无法获取
RetentionPolicy.SOURCE
代表在源文件中有效(在.java文件中有效)
接下来我们需要通过一个类来获取这个注解,并且通过反射
来拿到相应的View
,最后在赋值给该Filed
:
代码如下:
package ioc.zzw.com.ioclibrary;
import android.app.Activity;
import android.view.View;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Created by zzw on 2017/5/4.
* ioc注入工具类
*/
public class ViewUtils {
public static void inject(Activity activity) {
inject(new ViewHelper(activity), activity);
}
//为了兼容View
public static void inject(View v) {
inject(new ViewHelper(v), v);
}
//为了兼容Fragment
public static void inject(View v, Object o) {
inject(new ViewHelper(v), o);
}
/**
* 最终都调用这个方法
*
* @param helper View的帮助类 通过这个类根据id找到相应View
* @param o 相关对象 Activity view 等 从那个类传进来的
*/
private static void inject(ViewHelper helper, Object o) {
injectField(helper, o);
injectMethod(helper, o);
}
/**
* 通过@ViewById得到id注入相应的View
*
* @param helper View帮助类
* @param o 相关对象 Activity view 等 从那个类传进来的
*/
private static void injectField(ViewHelper helper, Object o) {
//1.获取到Object中所有的带有@ViewById的字段
Class<?> clazz = o.getClass();
//获取所有的字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//2.获取到相应的value值,也就是id值得到相应的View
ViewById viewById = field.getAnnotation(ViewById.class);
if (viewById != null) {
int viewId = viewById.value();
View view = helper.findViewById(viewId);
if (view != null) {
try {
//3.设置字段值,也就是给字段赋值
field.setAccessible(true);//为了使不被修饰符梭影响
field.set(o, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 设置点击时间
*
* @param helper View帮助类
* @param o 相关对象 Activity view 等 从那个类传进来的
*/
private static void injectMethod(ViewHelper helper, Object o) {
//1.获取到所有带有@OnClick的方法
Class<?> clazz = o.getClass();
Method[] methods = clazz.getDeclaredMethods();//获取所有方法
for (Method method : methods) {
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
//2.获取到相应的value值,也就是要设置点击时间的id数组
int[] values = onClick.value();
for (int viewId : values) {
//3.通过id获取到相应的Vie,然后设置点击事件
View view = helper.findViewById(viewId);
if (view != null) {
view.setOnClickListener(new DeclaredOnClickListener(method, o));
}
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener {
//设置点击事件的方法
private Method mMethod;
//在那个类中
private Object mObject;
public DeclaredOnClickListener(Method method, Object o) {
this.mMethod = method;
this.mObject = o;
}
@Override
public void onClick(View v) {
try {
mMethod.setAccessible(true);//所有修饰符都可以搞事
mMethod.invoke(mObject, v);//可以避免点击闪退
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject, (Object[]) null);//当方法体里面没有参数时候调用改方法,执行没有方法体的修饰的函数
} catch (IllegalAccessException e1) {
e1.printStackTrace();
} catch (InvocationTargetException e1) {
e1.printStackTrace();
}
}
}
}
}
package ioc.zzw.com.ioclibrary;
import android.app.Activity;
import android.support.annotation.IdRes;
import android.view.View;
/**
* Created by zzw on 2017/5/4.
* View帮助类
*/
public class ViewHelper {
private Activity mActivity;
private View mView;
public ViewHelper(Activity activity) {
this.mActivity = activity;
}
public ViewHelper(View v) {
this.mView = v;
}
public View findViewById(@IdRes int id) {
return mActivity == null ? mView.findViewById(id) : mActivity.findViewById(id);
}
}
package ioc.zzw.com.ioclibrary;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by zzw on 2017/5/4.
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewById {
int value();
}
package ioc.zzw.com.ioclibrary;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by zzw on 2017/5/4.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnClick {
int []value();
}
package com.zzw.ioc;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import ioc.zzw.com.ioclibrary.OnClick;
import ioc.zzw.com.ioclibrary.ViewById;
import ioc.zzw.com.ioclibrary.ViewUtils;
public class MainActivity extends AppCompatActivity {
@ViewById(R.id.test_tv)
private TextView mTestTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
mTestTv.setText("ioc_");
}
@OnClick({R.id.test_tv, R.id.test_button})
private void onClick(View view) {
switch (view.getId()) {
case R.id.test_tv:
Toast.makeText(this, "点击了TextView", Toast.LENGTH_SHORT).show();
break;
case R.id.test_button:
Toast.makeText(this, "点击了Button", Toast.LENGTH_SHORT).show();
break;
}
}
}
我们可以根据需求做一些自定义需要的注解,这里用网络检测来做个例子,当点击了检测一下网络,如果没有网络的话就显示网络的提示,有的话就继续往下执行:
我们加上CheckNet
package ioc.zzw.com.ioclibrary;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by zzw on 2017/5/4.
* 点击之后是否要执行网络检测
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckNet {
String value() default "亲,您的网络链接有问题哦!";
}
ViewUtils
类中点击事件注入的时候加上判断:
/**
* 设置点击事件
*
* @param helper View帮助类
* @param o 相关对象 Activity view 等 从那个类传进来的
*/
private static void injectMethod(ViewHelper helper, Object o) {
//1.获取到所有带有@OnClick的方法
Class<?> clazz = o.getClass();
Method[] methods = clazz.getDeclaredMethods();//获取所有方法
for (Method method : methods) {
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null) {
//网络检测
CheckNet checkNet = method.getAnnotation(CheckNet.class);
String hint = null;
if (checkNet != null) {
hint = checkNet.value();
}
//2.获取到相应的value值,也就是要设置点击时间的id数组
int[] values = onClick.value();
for (int viewId : values) {
//3.通过id获取到相应的Vie,然后设置点击事件
View view = helper.findViewById(viewId);
if (view != null) {
view.setOnClickListener(new DeclaredOnClickListener(method, o, hint));
}
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener {
//设置点击事件的方法
private Method mMethod;
//在那个类中
private Object mObject;
//是否检查网络
private String mNoNetHint;
public DeclaredOnClickListener(Method method, Object o, String hint) {
this.mMethod = method;
this.mObject = o;
this.mNoNetHint = hint;
}
@Override
public void onClick(View v) {
try {
mMethod.setAccessible(true);//所有修饰符都可以搞事
if (mNoNetHint != null && !isNetConnected(v.getContext())) {
Toast.makeText(v.getContext(), mNoNetHint, Toast.LENGTH_SHORT).show();
return;
}
mMethod.invoke(mObject, v);//可以避免点击闪退
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject, (Object[]) null);//当方法体里面没有参数时候调用改方法,执行没有方法体的修饰的函数
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
/**
* 检测网络是否连接
*
* @return
*/
private static boolean isNetConnected(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm != null) {
NetworkInfo[] infos = cm.getAllNetworkInfo();
if (infos != null) {
for (NetworkInfo ni : infos) {
if (ni.isConnected()) {
return true;
}
}
}
}
return false;
}
这样,我们在需要判断的时候加上这个注解:
@OnClick({R.id.test_tv, R.id.test_button})
@CheckNet("您网络有问题哦!")
private void onClick(View view) {
switch (view.getId()) {
case R.id.test_tv:
Toast.makeText(this, "点击了TextView", Toast.LENGTH_SHORT).show();
break;
case R.id.test_button:
Toast.makeText(this, "点击了Button", Toast.LENGTH_SHORT).show();
break;
}
}
这样的话就能够提示你需要的文字
如果你直接这样:
@OnClick({R.id.test_tv, R.id.test_button})
@CheckNet
private void onClick(View view) {
switch (view.getId()) {
case R.id.test_tv:
Toast.makeText(this, "点击了TextView", Toast.LENGTH_SHORT).show();
break;
case R.id.test_button:
Toast.makeText(this, "点击了Button", Toast.LENGTH_SHORT).show();
break;
}
}
这样的话就会提示默认的文字。
最后加上一个小技巧:
在我们利用反射来赋值或者执行方法的时候一般会try catch
,这个时候我们把异常改为Exception
,这样的话就能避免闪退,这样用户体验增加了,但是会有一些小问题,比如点击的时候出了异常,这时候点击就没反应,所以我们要细心查看logcat
打印信息找到问题所在,从而解决问题。就是想下面这几段代码一样:
try {
//3.设置字段值,也就是给字段赋值
field.setAccessible(true);//为了使不被修饰符梭影响
field.set(o, view);
} catch (Exception e) {
e.printStackTrace();
}
try {
mMethod.setAccessible(true);//所有修饰符都可以搞事
if (mNoNetHint != null && !isNetConnected(v.getContext())) {
Toast.makeText(v.getContext(), mNoNetHint, Toast.LENGTH_SHORT).show();
return;
}
mMethod.invoke(mObject, v);//可以避免点击闪退
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject, (Object[]) null);//当方法体里面没有参数时候调用改方法,执行没有方法体的修饰的函数
} catch (Exception e1) {
e1.printStackTrace();
}
}