注解(Annotation)自定义注解(三)--自己动手写注解框架(仿xutils)

前面两篇文章讲解了注解的基础知识和运行时注解的解析方法。下面我们用学到的知识来手动写一个属于自己的注解框架。


在写自己的注解框架前,让我们先来看下一些好的框架,相信大家对xutils应该不会太陌生。xutils主要包括网络,数据库,IOC注入,网络图片使用,那么我们这里主要看看xutils3.0的IOC注解:https://github.com/wyouflf/xUtils3


xutils注解使用:

public class MainActivity extends AppCompatActivity {

    @ViewInject(R.id.tv)
    private TextView mTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        x.view().inject(this);
        mTv.setText("MMMMMM");
    }
 
   
 
   
 
   

 /** * 1. 方法必须私有限定, * 2. 方法参数形式必须和type对应的Listener接口一致. * 3. 注解参数value支持数组: value={id1, id2, id3} * 4. 其它参数说明见{@link org.xutils.view.annotation.Event}类的说明. **/ @Event(value = R.id.tv, 
   
 
   
            type = View.OnClickListener.class/*可选参数, 默认是View.OnClickListener.class*/)
 
   
 private void tvClick(View view) { Toast.makeText(this, "我被点击了", Toast.LENGTH_LONG).show(); }} 
   
 
   
 
   
上面的列子很简单,就是通过注解实例化了TextView控件,并且给它添加了点击事件。那xutls实现原理是什么呢?

可能很多人都已经猜到了,不错就是上一篇中我们讲到的注解和发射来实现的。让我们一起来看下源码吧!看一下x.view().inject(this);到底做了什么?


@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));
    }

xutils相对来说应该不难理解吧,其实就是我们利用类的反射循环获取属性的注解值然后通过findViewById之后,动态的注入到控件属性里面;事件注入也是类似首先findViewById然后利用动态代理去反射执行方法。


知道了原理,现在我们就开始手动打造我们自己的框架.


1.控件注入:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
    /**
     * @ViewInject(R.id.xxx)
     * @return
     */
    int value();
}


public class ViewUtils {


    public static void inject(Activity activity){
        inject(new ViewFinder(activity),activity);
    }

    public static void inject(View view){
        inject(new ViewFinder(view),view);
    }


    public static void inject(View view,Object object){
        inject(new ViewFinder(view),object);
    }

    /**
     * 兼容
     * @param viewFinder
     * @param object
     */
    public static void inject(ViewFinder viewFinder,Object object){
        injectField(viewFinder,object);
        injectEvent(viewFinder,object);
    }

    private static void injectField(ViewFinder viewFinder,Object object) {
        Class clazz = object.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field:fields) {
            ViewInject annotation = field.getAnnotation(ViewInject.class);
            if(annotation!=null) {
                int viewId = annotation.value();
                View view = viewFinder.findViewById(viewId);
                if (view!=null) {
                    try {
                        //private
                        field.setAccessible(true);
                        field.set(object, view);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    
}



2.点击事件:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnClick {
    /**
     *
     * @return
     */
    int[] value();
}

    private static void injectEvent(ViewFinder viewFinder, Object object) {
        Class clazz = object.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method:methods) {
            OnClick annotation = method.getAnnotation(OnClick.class);
            if(annotation!=null){
                int[] value = annotation.value();
                for (int val: value) {
                    View view = viewFinder.findViewById(val);
                    boolean isCheckNet = method.getAnnotation(CheckNet.class)!=null;
                    if(view!=null){
                        view.setOnClickListener(new DeclaredOnClickListener(method,object,isCheckNet));
                    }
                }
            }
        }

    }
    private static class DeclaredOnClickListener implements View.OnClickListener{
        private Method mMethod;
        private Object mObject;
        private boolean isCheckNet;
        public DeclaredOnClickListener(Method method, Object object,boolean isCheckNet) {
            this.mMethod=method;
            this.mObject=object;
            this.isCheckNet=isCheckNet;
        }

        @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();
                }
            }
        }
    }


3.扩展其他功能

   比如:检测网络是否正常:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckNet {
}



private static class DeclaredOnClickListener implements View.OnClickListener{
        private Method mMethod;
        private Object mObject;
        private boolean isCheckNet;
        public DeclaredOnClickListener(Method method, Object object,boolean isCheckNet) {
            this.mMethod=method;
            this.mObject=object;
            this.isCheckNet=isCheckNet;
        }

        @Override
        public void onClick(View v) {
            //检查网络
            if(isCheckNet){
                if(networkAvaliable(v.getContext())){
                    Toast.makeText(v.getContext(),"",Toast.LENGTH_LONG).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();
                }
            }
        }
    }

有兴趣可以扩展其他功能。

下面两篇将讲解,在编译期使用注解,和仿ButterKnife基本功能






 
   
 
  

你可能感兴趣的:(android进阶)