1 前言
前面的文章注解框架源码分析(XUtils、ButterKnife)和ButterKnife编译时生成代码原理:butterknife-compiler源码分析,按照源码的思路我们自己打造一款IOC注解框架,因为ButterKnife的源码实现难度过大,我先仿照xUtils的源码方式,反射注解实现。
虽说反射注解对性能有影响,但是影响是极小,相比I渲染和Bitmap以及Service和Handler上的内存泄露不是一个量级的,编程一开始不纠结完美化,实现这个IOC框架是为了提升自己的编码能力,也是提高自己对项目整体代码提高可控性。
2 控件属性注入
Annotation注解需要了解Java中Annotation用法、Java Annotation 总结
属性注入代码:
/*
* ElementType.FIELD 代表annotation的位置
* FIELD:属性注解
* CONSTRUCTOR:构造器注解
* METHOD:方法注解
* TYPE:类上注解
*/
@Target(ElementType.FIELD)
/*
@Retention(RetentionPolicy.CLASS)什么时候生效
CLASS 编译时
RUNTIME 运行时
SOURCR 源码资源
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface FindView {
//@FindView(R.id.xxxx)
@IdRes int value();
}
public class SteadyoungIOC {
//activity使用
public static void inject(Activity activity){
inject(new ViewFinder(activity),activity);
}
//View使用
public static void inject(View view){
inject(new ViewFinder(view),view);
}
//Fragment使用
public static void inject(View view,Object object){
inject(new ViewFinder(view),object);
}
//兼容上述三种方式
private static void inject(ViewFinder finder,Object object){
injectField(finder,object);
injectEvent(finder,object);
}
/**
* 注入属性
* @param finder
* @param object
*/
private static void injectField(ViewFinder finder, Object object) {
//1.获取类里面的所有属性
Class<?> clazz = object.getClass();
//获取所有属性包括私有和公有
Field[] fields = clazz.getDeclaredFields();
//2.获取ViewById的里面的value值
for(Field field : fields){
FindView viewById = field.getAnnotation(FindView.class);
if(viewById != null){
int viewId = viewById.value();
//3.findViewById找到View
View view = finder.findViewById(viewId);
if (view != null) {
// 4. 反射注入View属性
// 设置所有属性都能注入包括私有和公有
field.setAccessible(true);
try {
field.set(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
throw new RuntimeException("Invalid @ViewInject for "
+ clazz.getSimpleName() + "." + field.getName());
}
}
}
}
/**
* 注入事件
* @param finder
* @param object
*/
private static void injectEvent(ViewFinder finder, Object object) {
//TODO
}
public class ViewFinder {
private Activity mActivity;
private View mView;
public ViewFinder(Activity activity) {
this.mActivity = activity;
}
public ViewFinder(View view) {
this.mView = view;
}
/**
* 判断容器然后 根据容器和控件ID获取控件View
* @param viewId
* @return
*/
public View findViewById(@IdRes int viewId){
return mActivity!= null ? mActivity.findViewById(viewId) : mView.findViewById(viewId);
}
}
3 点击事件注入
我先实现setOnclickListener点击事件,其他事件后期实现。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
@IdRes int[] value();
}
/**
* 注入事件
* @param finder
* @param object
*/
private static void injectEvent(ViewFinder finder, Object object) {
//TODO
//1.获取类里面的所有方法
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
//2.获取OnClick的里面的Value值
for ( Method method: methods ) {
OnClick onClick = method.getAnnotation(OnClick.class);
if(onClick != null){
int[] viewIds = onClick.value();
for( int viewId : viewIds){
//3.findViewById找到View
View view = finder.findViewById(viewId);
//4.view.setOnClickListener
view.setOnClickListener(new DeclaredOnClickListener(method,object));
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener {
private Method mMethod;
private Object mObject;
public DeclaredOnClickListener(Method mMethod, Object mObject) {
this.mMethod = mMethod;
this.mObject = mObject;
}
@Override
public void onClick(View v) {
try {
//打开权限
mMethod.setAccessible(true);
mMethod.invoke(mObject,v);
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject,null);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
4 扩展动态检测网络注解
最后扩展动态检测网络注解,可以在点击某些按钮或者图片是需要访问网络前检测网络状态,避免打不开网页或者获取不到数据等网络问题的情况!
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckNet {
String value();
}
/**
* 注入事件
* @param finder
* @param object
*/
private static void injectEvent(ViewFinder finder, Object object) {
//TODO
//1.获取类里面的所有方法
Class<?> clazz = object.getClass();
Method[] methods = clazz.getDeclaredMethods();
//2.获取OnClick的里面的Value值
for ( Method method: methods ) {
OnClick onClick = method.getAnnotation(OnClick.class);
if(onClick != null){
int[] viewIds = onClick.value();
for( int viewId : viewIds){
//3.findViewById找到View
View view = finder.findViewById(viewId);
//判断是否有检测网络需求
boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;
String message = null;
if(isCheckNet){
//获取无网络提示信息
message = method.getAnnotation(CheckNet.class).value();
}
//4.view.setOnClickListener
view.setOnClickListener(new DeclaredOnClickListener(method,object,isCheckNet,message));
}
}
}
}
private static class DeclaredOnClickListener implements View.OnClickListener {
private Method mMethod;
private Object mObject;
private boolean isCheckNet;
private String message;
public DeclaredOnClickListener(Method mMethod, Object mObject, boolean isCheckNet, String message) {
this.mMethod = mMethod;
this.mObject = mObject;
this.isCheckNet = isCheckNet;
this.message = message;
}
@Override
public void onClick(View v) {
if(isCheckNet){
Log.d("isCheckNet", "onClick:111111111111111111 ");
if(!isNetworkConnected(v.getContext())){
Toast.makeText(v.getContext(), TextUtils.isEmpty(message) ? "网络不给力,请检查网络连接!" : message ,Toast.LENGTH_SHORT).show();
return;
}
}
try {
//打开权限
mMethod.setAccessible(true);
mMethod.invoke(mObject,v);
} catch (Exception e) {
e.printStackTrace();
try {
mMethod.invoke(mObject,null);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
/**
*获取网络连接状态
* @param context
* @return true网络连接正常 false无网络
*/
private static boolean isNetworkConnected(Context context){
if(context != null){
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if(networkInfo != null){
return networkInfo.isAvailable();
}
}
return false;
}
下面放上测试代码和测试结果:
public class MainActivity extends AppCompatActivity {
/****Hello World!****/
@FindView(R.id.test_tv)
private TextView mTestTv;
@FindView(R.id.test_iv)
private ImageView mTestIv;
/****TestButton****/
@FindView(R.id.test_btn)
private Button mTestBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SteadyoungIOC.inject(this);
mTestTv.setText("测试文本");
mTestIv.setBackgroundColor(Color.RED);
mTestBtn.setText("测试按钮");
}
@OnClick(R.id.test_tv)
private void testTvClick(TextView testTv) {
Toast.makeText(this,"点击了文字",Toast.LENGTH_SHORT).show();
}
@OnClick(R.id.test_iv)
private void testIvClick(ImageView testIv) {
mTestIv.setBackgroundColor(Color.BLUE);
Toast.makeText(this,"点击了图片",Toast.LENGTH_SHORT).show();
}
@CheckNet("亲!网络不给力哦!")
@OnClick(R.id.test_btn)
private void testBtnClick(Button testBtn) {
Toast.makeText(this,"点击了按钮",Toast.LENGTH_SHORT).show();
}
}
steadyoungioctest.gif
steadyoungioc.gif
上图中生成代码的插件是我自己编写的,后面的文章将讲解插件代码。
附上源码:https://github.com/Steadyoung/SteadyoungIOC
插件下载地址:SteadyoungIOC-CodePlug.jar
网友评论