手写IOC注解,解决findViewById和点击事件

在项目开发中findViewById以及控件的点击事件的场景是最多的,大量的findViewById并强转会浪费不少的开发时间,那么是否能解决掉这个问题呢?答案肯定是能解决;像xutils、butterKnidnife等主流第三方框架就很好的解决了这些问题,那么这些第三方的已经帮助开发者解决掉这些问题了,是否还要自己学着去写一套手写的IOC注解呢,肯定是需要的,在学习写的过程中能更好的理解这些第三方的实现原理,同时也能也符合项目的需要。下面这个是在学习过程中写的一个IOC注解:

涉及知识点:
1、注解
2、反射
3、动态代理(点击事件注入的时候会涉及到)

实现:
1、activity布局注入,不需要调用setContentView
2、activity、fragment等 findViewById、setOnClickListener、setOnLongClickListener等事件注入,点击和长按事件可轻松切换

代码实现:
ContentView 是用来activity布局注入:

/**
 * Created by Administrator on 2017/12/13.
 * activity 布局注入
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
    int value();
}

activity布局注入逻辑:

/**
 * Activity加载布局
 *
 * @param context
 */
private static void injectLayout(Object context) {
    int layoutId = 0;
    Class clazz = context.getClass();
    //拿到activity类上面的注解
    ContentView contentView = clazz.getAnnotation(ContentView.class);
    if (contentView != null) {
        layoutId = contentView.value();
        try {
            Method method = clazz.getMethod("setContentView", int.class);
            method.setAccessible(true);
            try {
                //反射执行
                method.invoke(context, layoutId);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

ViewInject是findViewById注入:

/**
 * Created by Administrator on 2017/12/13.
 * findViewById注入
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
    int value();
}

findViewById注入逻辑:

/**
  * Activity加载view
  *
  * @param context
  */
private static void injectView(ViewFinder finder, Object context) {
    Class clazz = context.getClass();
    //获取activity类中所有的成员变量
    Field[] fields = clazz.getDeclaredFields();
    //遍历
    for (Field field : fields) {
        field.setAccessible(true);
        //得到成员变量的注解
        ViewInject viewInject = field.getAnnotation(ViewInject.class);
        if (viewInject != null) {
            //获取对应的id
            int valueId = viewInject.value();
            //反射获取控件
            View viewById = finder.findViewById(valueId);
            if (viewById == null) {
                continue;
            }
            //反射调用方法
            try {
                field.set(context, viewById);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

下面这些是点击事件、长按事件、条目点击事件等事件注入:

/**
 * Created by Administrator on 2017/12/15.
 * 对所有的点击事件扩展
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {
    /**
     * 设置监听的方法
     * @return
     */
    String listenerSetter();

    /**
     * 事件类型
     * @return
     */
    Class listenerType();

    /**
     * 回调方法
     * 事件被触发后,执行回调方法名称
     * @return
     */
    String callBackMethod();
}
/**
 * Created by Administrator on 2017/12/15.
 * 点击事件
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener",
        listenerType = View.OnClickListener.class,
        callBackMethod = "onClick")
public @interface OnClick {
    /**
     * 点击事件控件数组
     * @return
     */
    int [] value()default -1;;
}
/**
 * Created by Administrator on 2017/12/15.
 * 长按事件
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnLongClickListener",
        listenerType = View.OnLongClickListener.class,
        callBackMethod = "onLongClick")
public @interface OnLongClick {
    int [] value() default -1;
}
/**
 * Created by Administrator on 2017/12/15.
 * 条目点击事件
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnItemClickListener",
        listenerType = AdapterView.OnItemClickListener.class,
        callBackMethod = "onItemClick")
public @interface OnItemClick {
    int [] value();
}

在注入事件时涉及到动态代理的使用;

/**
 * Created by Administrator on 2017/12/15.
 * 动态代理
 */

public class ListenerInvocationHandler implements InvocationHandler{
    //代理的真实对象
    private Object context;
    private Map methodMap;

    public ListenerInvocationHandler(Object context, Map methodMap) {
        this.context = context;
        this.methodMap = methodMap;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        //决定是否需要进行代理
        Method metf = methodMap.get(name);
        if(metf!=null){
            return metf.invoke(context,args);
        }else{
            return metf.invoke(proxy,args);
        }
    }
}

事件注入逻辑:

/**
 * 事件注入
 *
 * @param context
 */
private static void injectEvents(ViewFinder finder, Object context) {
    Class clazz = context.getClass();
    //获取activity里面所有的方法
    Method[] methods = clazz.getDeclaredMethods();
    //循环遍历
    for (Method method : methods) {
        //获取方法上面所有的注解
        Annotation[] annotations = method.getAnnotations();
        //循环遍历所有的注解
        for (Annotation annotation : annotations) {
            //获取注解类型
            Class anntionType = annotation.annotationType();
            //获取注解上面的注解
            EventBase eventBase = anntionType.getAnnotation(EventBase.class);
            if (eventBase == null) {
                continue;
            }
            //开始获取事件三要素  通过反射注入进去
            String listenerSetter = eventBase.listenerSetter();
            Class listenerType = eventBase.listenerType();
            String callMethod = eventBase.callBackMethod();
            //方法名与方法method进行对应关系
            Map methodMap = new HashMap<>();
            methodMap.put(callMethod, method);

            try {
                Method valueMethod = anntionType.getDeclaredMethod("value");
                int[] viewIds = (int[]) valueMethod.invoke(annotation);
                //遍历获取到的id
                for (int viewId : viewIds) {
                    //通过反射获取相应的view控件
                    View view = finder.findViewById(viewId);
                    if (view == null) {
                        continue;
                    }
                    Method setListener = view.getClass().getMethod(listenerSetter, listenerType);
                    ListenerInvocationHandler handle = new ListenerInvocationHandler(context, methodMap);
                    Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),
                            new Class[]{listenerType},
                            handle);

                    setListener.invoke(view, proxy);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

activity中使用:

@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
    @ViewInject(R.id.tv_ioc)
    private TextView tvIOC;
    @ViewInject(R.id.tv_ioc1)
    private TextView tvIOC1;
    @ViewInject(R.id.tv_ioc2)
    private TextView tvIoc2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtils.inject(this);

        tvIOC.setText("IOC注解  单击事件");
        tvIOC1.setText("IOC注解  长按事件");
        tvIoc2.setText("fragment IOC注解");
    }
    @OnClick(R.id.tv_ioc)
    public void click(View view){
        Toast.makeText(this,tvIOC.getText().toString(),Toast.LENGTH_LONG).show();
    }
    @OnLongClick(R.id.tv_ioc1)
    public boolean longClick(View view){
        Toast.makeText(this,tvIOC1.getText().toString(),Toast.LENGTH_LONG).show();
        return true;
    }
    @OnClick(R.id.tv_ioc2)
    public void toFragment(View view){
        startActivity(new Intent(this,SecondActivity.class));
    }
}

fragment中使用:

public class IOCFragment extends Fragment{
    @ViewInject(R.id.tv_ioc)
    private TextView tvIOC;
    @ViewInject(R.id.tv_ioc1)
    private TextView tvIOC1;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_ioc, container, false);
        InjectUtils.inject(view,this);

        tvIOC.setText("IOC注解  fragment单击事件");
        tvIOC1.setText("IOC注解  fragment长按事件");
        return view;
    }
    @OnClick(R.id.tv_ioc)
    public void click(View view){
        Toast.makeText(getActivity(),tvIOC.getText().toString(),Toast.LENGTH_LONG).show();
    }
    @OnLongClick(R.id.tv_ioc1)
    public boolean longClick(View view){
        Toast.makeText(getActivity(),tvIOC1.getText().toString(),Toast.LENGTH_LONG).show();
        return true;
    }
}

效果如下:
手写IOC注解,解决findViewById和点击事件_第1张图片

源码地址:
https://pan.baidu.com/s/1pL5gK4b

你可能感兴趣的:(android)