IOC注入技术之运行时注入-xUtils3 IOC注入式框架手写实现


参考:

wyouflf/xUtils3
XUtils框架
Android Xutils 框架的介绍
xUtils使用详细介绍
维基百科-控制反转IOC
Java之注释(Annotation)

示例代码Github:https://github.com/345166018/AndroidIOC/tree/master/HxIOC

IOC 是原来由程序代码中主动获取的资源,转变由第三方获取并使原来的代码被动接收的方式,以达到解耦的效果,称为控制反转。

xUtils3使用

import org.xutils.x;
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        x.Ext.init(this);
    }
}
import org.xutils.x;
public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        x.view().inject(this);
    }
}
mport org.xutils.view.annotation.ContentView;
import org.xutils.view.annotation.Event;
import org.xutils.view.annotation.ViewInject;

@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
    @ViewInject(R.id.tv_text)
    TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        textView.setText("修改了TextView的内容");
    }
    /*可选参数, 默认是View.OnClickListener.class*/
    @Event(value = R.id.btn_click1, type = View.OnClickListener.class)
    private void onClick1(View view) {
        Toast.makeText(this, "点击按钮1", Toast.LENGTH_SHORT).show();
    }
    @Event(value = {R.id.btn_click2, R.id.btn_click3})
    private void onClick23(View view) {
        switch (view.getId()) {
            case R.id.btn_click2:
                Toast.makeText(this, "点击按钮2", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn_click3:
                Toast.makeText(this, "点击按钮3", Toast.LENGTH_SHORT).show();
                break;
        }
    }
    @Event(value = R.id.btn_click4, type = View.OnLongClickListener.class)
    private boolean onLongClick4(View view) {
        Toast.makeText(this, "长按按钮4", Toast.LENGTH_SHORT).show();
        return false;
    }
}

接下来手写实现上面的功能。

1 布局注入

1.1 定义注解ContentView

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
    int value();
}

1.2 将注解添加到MainActivity上

@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
    }
}

1.3 反射

通过反射获取到注解上设置的值,并获取到setContentView,然后将值传递到setContentView方法中。

public class InjectUtils {
    public static void inject(Object context) {
        injectLayout(context);
    }
    private static void injectLayout(Object context) {
        int layouId = 0;
        Class<?> clazz = context.getClass();
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        if (contentView != null) {
            layouId = contentView.value();
        }
        try {
            Method method = context.getClass().getMethod("setContentView", int.class);
            method.invoke(context, layouId);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

1.4 在BaseActivity中对注入工具进行初始化

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtils.inject(this);
    }
}

这样就完成了一个最简单的布局注入,在MainActivity我们没有使用setContentView方法去设置布局文件,而是通过注解的形式,运行程序后app运行正常,说明达到了布局注入的目的。


2 控件注入

2.1 定义注解ViewInject

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ViewInject {
    int value();
}

2.2 添加注解

在button上添加注解ViewInject,这里没有使用findViewById方法去获取Button控件,并给button一个点击事件,查看是否真正获取到Button控件。

@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {

    @ViewInject(R.id.btn_click)
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        Log.i("hongxue button string",button.toString());
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this,"点击按钮",Toast.LENGTH_SHORT).show();
            }
        });
    }
}

2.3 反射

通过反射执行findViewById

public class InjectUtils {
    public static void inject(Object context) {
        injectLayout(context);
        injectView(context);
    }
    private static void injectView(Object context) {
        Class<?> clazz = context.getClass();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields){
            ViewInject viewInject = field.getAnnotation(ViewInject.class);
            if(viewInject != null){
                int valueId = viewInject.value();
                try {
                    //反射执行findViewById
                    Method method = clazz.getMethod("findViewById",int.class);
                    View view = (View) method.invoke(context,valueId);
                    field.setAccessible(true);//1
                    field.set(context,view);//2
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }
	
	...

}

注释1:
在java的反射使用中,如果字段是私有的,那么必须要对这个字段设置 。
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。

注释2:将反射findViewById获取到的控件对象添加到对应的成员变量(field)中。


初始化在布局注入中已经说到,在BaseActivity中添加InjectUtils.inject(this);


3 事件注入

3.1 事件三要素

android 所有的23事件
OnClickListener、OnLongClickListener …

点击事件的三要素

  1. button 事件源
  2. new View.OnClickListener() 事件
  3. onClick() 事件处理

进行订阅 setOnClickListener

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        
    }
}); 

button.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        return false;
    }
});

3.2 实现onClick事件

3.2.1 定义注解@OnClick

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

3.2.2 添加注解到方法上

在布局文件中添加了两个按钮,id分别为btn_click2和btn_click3。点击不同的按钮显示不同的信息。

    @OnClick({R.id.btn_click2, R.id.btn_click3})
    public boolean click(View view) {
        if (view.getId() == R.id.btn_click2) {
            Log.i("hongxue", " MainActivity btn 2 click");
            Toast.makeText(this, "点击了button2", Toast.LENGTH_SHORT).show();
        }
        else if (view.getId() == R.id.btn_click3) {
            Log.i("hongxue", " MainActivity btn 3 click");
            Toast.makeText(this, "点击了button3", Toast.LENGTH_SHORT).show();
        }
        return false;
    }

3.2.3 实现事件注入

在InjectUtils添加injectEvent方法。

  /**
     * 事件注入
     */
    private static void injectEvent(Object context) {
        Class<?> clazz = context.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            OnClick onClick = method.getAnnotation(OnClick.class);
            if (onClick == null) {
                continue;
            }
            String listenerSetter = "setOnClickListener";
            Class<?> listenerType = View.OnClickListener.class;
            //String callBackMethod = "onClick";
            try {
                int[] viewId = onClick.value();
                for (int id : viewId) {
                    Method findViewById = clazz.getMethod("findViewById", int.class);
                    View view = (View) findViewById.invoke(context, id);
                    if (view == null) {
                        continue;
                    }
                    ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(context, method);
                    Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, listenerInvocationHandler);
                    Method onClickMethod = view.getClass().getMethod(listenerSetter, listenerType);
                    onClickMethod.invoke(view, proxy);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

将injectEvent方法添加到inject方法中。

    public static void inject(Object context) {
        injectLayout(context);
        injectView(context);
        injectEvent(context);
    }

3.2.3 动态代理

public class ListenerInvocationHandler implements InvocationHandler {

    private Object activity;
    private  Method activityMethod;

    public ListenerInvocationHandler(Object activity, Method activityMethod) {
        this.activity = activity;
        this.activityMethod = activityMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在这里去调用被注解了的click()
        return activityMethod.invoke(activity,args);
    }
}

4 改造

前面已经提到Android有23种事件,所以可以再创建一个注解,用于事件注解上,这样我们就不需要在injectEvent中去添加事件注解的三要素,而是可以在创建事件注解的时候再去设置三要素。

4.1 定义注解@EventBase

@Retention(RetentionPolicy.RUNTIME)
//该注解在另外一个注解上使用
@Target(ElementType.ANNOTATION_TYPE)
public @interface EventBase {
    //  setOnClickListener  订阅
    String  listenerSetter();
	//    事件以及他的类型
    /**
     * 事件监听的类型

     */
    Class<?> listenerType();

    /**
     * 事件处理
     */
    String callbackMethod();

}

4.2 改造注解@OnClick

在@OnClick注解上使用使用@EventBase注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnClickListener"
        , listenerType = View.OnClickListener.class
        , callbackMethod = "onClick")
public @interface OnClick {
    int[] value() default  -1;
}

4.3 改造事件注入方法

    /**
     * 事件注入
     */
    private static void injectEvent(Object context) {
        Class<?> clazz = context.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
//            OnClick onClick = method.getAnnotation(OnClick.class);
            //得到方法上的所有注解
            Annotation[] annotations = method.getAnnotations();//1

            for (Annotation annotation : annotations) {
//                annotation  ===OnClick  OnClick.class
                Class<?> annotionClass = annotation.annotationType();//2
                EventBase eventBase = annotionClass.getAnnotation(EventBase.class);//3
                //如果没有eventBase,则表示当前方法不是一个处理事件的方法
                if (eventBase == null) {
                    continue;
                }
                //开始获取事件处理的相关信息,
                // 用于确定是哪种事件(onClick还是onLongClick)以及由谁来处理
                //订阅
                String listenerSetter = eventBase.listenerSetter();
                //事件(事件监听的类型)
                Class<?> listenerType = eventBase.listenerType();
                //事件处理   事件被触发之后,执行的回调方法的名称
                String callBackMethod = eventBase.callbackMethod();

//                         textView.setOnClickListener(new View.OnClickListener() {
//                              @Override
//                              public void onClick(View v) {
//
//                              }
//                          });

//                int[] value1=OnClick.value();//这就写死了

                Method valueMethod = null;
                try {
                    //反射得到ID,再根据ID号得到对应的VIEW
                    valueMethod = annotionClass.getDeclaredMethod("value");
                    int[] viewId = (int[]) valueMethod.invoke(annotation);
                    for (int id : viewId) {
                        Method findViewById = clazz.getMethod("findViewById", int.class);
                        View view = (View) findViewById.invoke(context, id);
                        if (view == null) {
                            continue;
                        }
                        //得到ID对应的VIEW以后
                        //开始在这个VIEW上执行监听  (使用动态代理)
                        //需要执行activity上的onClick方法
                        //activity==context       click==method
                        ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(context, method);
                        //proxy======View.OnClickListener()对象
                        Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[]{listenerType}, listenerInvocationHandler);

                        //执行方法                                   setOnClickListener,new View.OnClickListener()
                        Method onClickMethod = view.getClass().getMethod(listenerSetter, listenerType);
                        onClickMethod.invoke(view, proxy);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

4.4 定义@OnLongClick注解

再定义一个长按事件的注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventBase(listenerSetter = "setOnLongClickListener", 
        listenerType = View.OnLongClickListener.class, 
        callbackMethod = "onLongClick")
public @interface OnLongClick {
    int[] value() default -1;
}
    @OnLongClick({R.id.btn_click2,R.id.btn_click3})
    public boolean longClick(View view) {
        if (view.getId() == R.id.btn_click2) {
            Log.i("hongxue", " MainActivity btn 2 longlick");
            Toast.makeText(this, "长按了button2", Toast.LENGTH_SHORT).show();
        }
        else if (view.getId() == R.id.btn_click3) {
            Log.i("hongxue", " MainActivity btn 3 longClick");
            Toast.makeText(this, "长按了button3", Toast.LENGTH_SHORT).show();
        }
        return true;//1
    }

注释1:return true 可以拦截点击事件


示例代码Github:https://github.com/345166018/AndroidIOC/tree/master/HxIOC

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值