Xutils功能的仿写通过注解和反射。

190 阅读3分钟

一、实现的功能介绍

通过注解手写Xutils的功能:

1.通过注解注入SetContentView方法。

2.通过注解注入成员变量的findViewById().

3.通过注解注入点击事件。

二、实现原理

1.通过反射获取到各个位置的注解。

2.根据注解的参数在注入类里通过this注入到相应的位置上。

三、MainActivity的调用

@SetContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {


    @BindView(R.id.tv)
    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        //注入注解
        InjectTool.inject(this);
        Toast.makeText(this,tv.getText(),Toast.LENGTH_SHORT).show();


      
    }

    @Click(R.id.tv)
    public void show(){
        Toast.makeText(MainActivity.this,"我是点击事件",Toast.LENGTH_SHORT).show();
    }
}

四、注入函数和注解的编写。

BindView注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    int value() default  -1;
}

Click注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Click {
    int value() default -1;
}

SetContentView注解

public @interface SetContentView {
    int value() default -1;
}

注入类InjectTool.java

public class InjectTool {

    private static final String TAG = InjectTool.class.getSimpleName();
    public static void inject(Object mainActivity) {
        injectSetContentView(mainActivity);
        injectBindView(mainActivity);
        injectMethord(mainActivity);
    }

    /**
     * 点击事件的注入
     * @param mainActivity
     */
    private static void injectMethord(Object mainActivity) {
        Class mClass = mainActivity.getClass();
        Method[] methods = mClass.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            methods[i].setAccessible(true);
            Log.e("lzl",methods[i].getName());
            Click click = methods[i].getAnnotation(Click.class);
            if(click ==null){
                continue;
            }

            int value = click.value();

            //反射获取到view
            try {
                //1.先获取到得到view的方法
                Method method = mClass.getMethod("findViewById",int.class);
                //2.调用方法,去获取到VIEW控件
                Object object = method.invoke(mainActivity,value);
                //3.获取view,直接设置setOnclickListener.
                int finalI = i;
                Log.e("lzl","设置了监听");
                ((View)object).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        try {
                            methods[finalI].invoke(mainActivity);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                });

                //4.
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }


        }
    }

    /**
     * 绑定控件
     * @param mainActivity
     */
    private static void injectBindView(Object mainActivity) {

        Class<?> aClass =  mainActivity.getClass();
        //因为控件是对象的成员变量,所以先获取到所有的成员变量,
        //又因为是成员变量可以是private所以需要用declared方法。
        Field[] files = aClass.getDeclaredFields();
        for (int i = 0; i < files.length; i++) {
            files[i].setAccessible(true);//让jvm不要去管private 修饰的

            BindView annotation = files[i].getAnnotation(BindView.class);
            //获取我们需要的注解内容
            if(annotation==null){
               continue;
            }

            int viewId = annotation.value();

            try {
                //获取方法
                Method findview = aClass.getMethod("findViewById",int.class);
                //调用方法。
                Object object = findview.invoke(mainActivity,viewId);
                //给变量设置值。
                files[i].set(mainActivity,object);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }


        }
    }

    /**
     * 把布局绑定到this中
     * @param mainActivity
     */
    private static void injectSetContentView(Object mainActivity) {

        Class<?> aClass =  mainActivity.getClass();
        //拿到Class上的注解
        SetContentView setContentView = aClass.getAnnotation(SetContentView.class);

        if(setContentView == null){
            Log.d(TAG,"ContentView 为null");
            return;
        }

        //拿到布局ID
        int id = setContentView.value();

        //反射设置id
        try {
            Method method = aClass.getMethod("setContentView",int.class);
            method.invoke(mainActivity,id);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

}

五、兼容版本的代码

通过注解的注解来区分不同的点击事件,扩展性强。

调用代码

@OnClickCommon(R.id.bt1)
public void bt1(){
    Toast.makeText(MainActivity.this,"点击事件运行",Toast.LENGTH_SHORT).show();
}
@OnLongClickCommon(R.id.bt2)
public Boolean bt2(){
    Toast.makeText(MainActivity.this,"长按事件运行",Toast.LENGTH_SHORT).show();
    return false;
}

注解的注解

/**
 * 注解的注解
 *
 * 事件的三要素:1.订阅方式:是setOnClickListener() 、 setOnLongClickListener()
 *              2. 事件原对象:view.OnClickListener()、 View.OnLongClickListener()
 *              3. 事件执行方法:onClick、OnLongClick 最终的事件消费。
 */

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BaseCommon {

    String setCommonListener();

    Class setCommonObject();

    String CallBackMethord();
}

点击事件的注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@BaseCommon(setCommonListener = "setOnClickListener",
setCommonObject = View.OnClickListener.class,
CallBackMethord = "onClick")
public @interface OnClickCommon {
    int value() default -1;
}

长按事件的注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@BaseCommon(setCommonListener = "setOnLongClickListener",
        setCommonObject = View.OnLongClickListener.class,
CallBackMethord = "onLongClick")
public @interface OnLongClickCommon {
    int value() default -1;
}

注入的方式

通过反射和动态代理实现。

  private static void injectCommon(Object mainActivity) {
        //1.获取Activity的class对象
        Class<?> mMainActivityClass = mainActivity.getClass();
        //2.获取Class对象里的所有方法。
        Method[] declaredMethods = mMainActivityClass.getDeclaredMethods();
        //3.遍历所有方法
        for (Method declaredMethod : declaredMethods) {
            //4.设置该方法不进行java访问检查。
            declaredMethod.setAccessible(true);
            //5.获取方法上的注解。
            Annotation[] annotations = declaredMethod.getAnnotations();
            //6.遍历方法上的注解,因为可能有多个。
            for (Annotation annotation : annotations) { // 2

                // 7.获取当前注解的父注解 是否有 OnBaseCommon
                Class<? extends Annotation> annotationType = annotation.annotationType();
                BaseCommon onBaseCommon = annotationType.getAnnotation(BaseCommon.class);
                
                if (null == onBaseCommon) {
                    Log.d(TAG, "OnBaseCommon is null");
                    continue; // 结束本次,继续下一次
                }

                // 终于找到了 此注解包含 OnBaseCommon
                //8. 获取事件三要数
                String setCommonListener = onBaseCommon.setCommonListener();
                Class setCommonObjectListener = onBaseCommon.setCommonObject();
                String callbackMethod= onBaseCommon.CallBackMethord(); // 感觉3在,多个方法下才有用

                // 9.动态代理完成,
                try {

                    // 由于上面无法明确 子注解   annotationType获取到子注解

                    // 获取 @OnClickCommon(R.id.bt_t1) value == R.id.bt_t1
                    
                    //获取子注解的值。通过反射
                    Method valueMethod =  annotationType.getDeclaredMethod("value");
                    valueMethod.setAccessible(true);
                    int value = (int) valueMethod.invoke(annotation);

                    // 就是执行 findViewById 拿到 button1 == resultView
                    Method findViewByIdMethod = mMainActivityClass.getMethod("findViewById", int.class);
                    Object resultView = findViewByIdMethod.invoke(mainActivity, value);

                  

                    // button1.setOnClickListener(new OnClickList)
                    // button1.要素1(要素2);

                    // 再次启用反射  button1 class    setOnClickListener(事件三要数之一)   setCommonObjectListener(事件三要数之一)
                    Method mViewMethod = resultView.getClass().getMethod(setCommonListener, setCommonObjectListener);

                    // 动态代理 监听 第三个要素
                    Object proxy = Proxy.newProxyInstance(
                            setCommonObjectListener.getClassLoader(), // 动态代理内部需要的 getClassLoader
                            new Class[]{setCommonObjectListener}, // 动态代理只监听 第二要素 的 接口
                            new InvocationHandler() {
                                @Override
                                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                    // 代理里边可以区分方法名字的
                                    return declaredMethod.invoke(mainActivity, null);
                                }
                            }
                    );

                    // 事件三要素的 第三个 最终的事件消费
                    mViewMethod.invoke(resultView, proxy);
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }


    }