继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

自定义Android IOC框架

猴子搬来的救兵Castiel
关注TA
已关注
手记 77
粉丝 40
获赞 184

IOC-控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心。 控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。这段百度对IOC框架的解释,对于Java开发者来讲最著名的IOC框架莫过于Spring,而在我们的Android开发中,IOC的使用更为常见,比如大家经常使用的XUtil、butterKnife、EventBus、dagger、dagger2、otto等等,这些第三方库几乎都使用了IOC思想,举个例子给大家: 
    通常我们在Activity中获取一个图片组件采用如下方法:

ImageView img;
    img = findViewById(R.id.img);12

    而使用IOC框架给我提供了一种基于注解的实现方式:

@ViewInject(R.id.img)
 ImageView img;12

    可以看出这种方式似乎更加简洁 
    其实这正是我本篇博文想给大家介绍的,IOC框架可以:

1.让代码更加简洁
2.让模板式的代码更少,减少重复工作
3.把更多的精力放到业务逻辑上
4.解耦合1234

    下面我给大家详细讲解下如何自定义IOC框架,在Android中我们使用IOC框架更多是为了方便注入所有的控件,比如说布局文件。

自定义注解工具库

整体库结构

这里写图片描述

定义注解工具类

布局注解

/**
 * 
 * 功能:自定义ContentView注解
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016年10月14日
 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)// 使用在类上面public @interface CastielContentViewInject {
    int value();// 定义一个方法去拿注解里面的参数}123456789101112

组件注解

/**
 * 
 * 功能:自定义View注解
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016年10月14日
 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)// 使用在属性字段上public @interface CastielViewInject {
    int value();1234567891011

事件注解

/**
 * 
 * 功能:自定义OnClick注解
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016年10月14日
 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)// 使用在方法上面@CastielEventBase(listenerSetter="setOnClickListener",listenerType=View.OnClickListener.class,callbackMethod="onClick")public @interface CastielOnClickInject {

    // 由于有很多个点击事件,所以要搞个数组
    int[] value();
}123456789101112131415

在定义事件注解类时,我们需要在这个注解的基础上再定义一个注解,用于传递事件调用所需的三个重要元素setOnClickListener;传接口类型;回调方法名字

事件注解基类

public @interface CastielEventBase {    // 1.设置事件监听的方法,配置方法的名字
    String listenerSetter();    // 2.事件监听的类型
    Class<?> listenerType();    // 3.回调方法的名字
    String callbackMethod();
}123456789

定义注入工具类

/**
 * 功能:InjectUtils注入工具类
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016/10/13
 */public class InjectUtils {

    public static void inject(Activity activity) {        // 注入布局
        injectLayout(activity);        // 注入视图
        injectViews(activity);        // 注入事件
        injectEvents(activity);

    }    private static void injectEvents(Activity activity) {        // 获取方法上面的注解
        Class<? extends Activity> myClass = activity.getClass();        Method myMethod[] = myClass.getDeclaredMethods();// 先拿到全部方法 
        for (Method method : myMethod) {
            Annotation[] annotations = method.getAnnotations();            for (Annotation annotation : annotations) {                Class<? extends Annotation> annotationType = annotation.annotationType();                CastielEventBase ceb = annotationType.getAnnotation(CastielEventBase.class);// 拿到注解里面的注解
                // 得到事件的三要素                String listenerSetter = ceb.listenerSetter();                Class<?> listenerType = ceb.listenerType();
                String callbackMethod = ceb.callbackMethod();
                // 获取注解事件的控件对象Button
                try {
                    Method valueMethod = annotationType.getDeclaredMethod("value");
                    try {
                        int[] viewIds = (int[])valueMethod.invoke(annotation);
                        for (int viewId : viewIds) {
                            View view = activity.findViewById(viewId);
                            // 反射setOnClickListener方法,这里要用到代理
                            Method setListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
                            Map<String, Method> methodMap = new HashMap<String, Method>();
                            methodMap.put(callbackMethod, method);
                            InvocationHandler invocationHandler = new ListenerInvocationHandler(activity, methodMap);
                            Object newProxyInstance = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[]{listenerType}, invocationHandler);
                            setListenerMethod.invoke(view , newProxyInstance);
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (IllegalArgumentException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
        }
    }    private static void injectViews(Activity activity) {        // 获取每一个属性上的注解
        Class<? extends Activity> myClass = activity.getClass();        Field[] myFields = myClass.getDeclaredFields();// 先拿到里面所有的成员变量        for (Field field : myFields) {
            CastielViewInject myView = field.getAnnotation(CastielViewInject.class);            if (myView != null) {
                int value = myView.value();// 拿到属性id
                View view = activity.findViewById(value);                // 将view赋值给类里面的属性
                try {
                    field.setAccessible(true);// 为了防止其实私有的的,需要设置允许访问
                    field.set(activity,view);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                }
            }
        }
    }    private static void injectLayout(Activity activity) {        // 获取我们自定义类CastielContentViewInject上面的注解
        Class<? extends Activity> myClass = activity.getClass();        CastielContentViewInject myContentView = myClass.getAnnotation(CastielContentViewInject.class);        int myLayoutResId = myContentView.value();        activity.setContentView(myLayoutResId);
    }

}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091

使用代理模式

为了将我们的方法点击方法代替系统的点击方法,我们使用动态代理的方法去更替系统的点击事件

public class ListenerInvocationHandler implements InvocationHandler {

    Activity activity;
    Map<String,Method> methodMap;    public ListenerInvocationHandler(Activity activity,Map<String,Method> methodMap) {        this.activity = activity;        this.methodMap = methodMap;
        Log.i("castiel", "打印方法Map:" + methodMap.toString());
    }    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        // method就是我们的CastielOnClickInject
        String name = method.getName();
        Log.i("castiel", "打印方法name:" + name);
        Method mtd = methodMap.get(name);        if (mtd != null) {            return mtd.invoke(activity, args);
        }        return method.invoke(activity, args);
    }

}123456789101112131415161718192021222324

最后封装Activity基类

/**
 * 
 * 功能:封装的Activity基类,引入我们自定义的注入工具类
 * 作者:猴子搬来的救兵
 * 博客地址:http://blog.csdn.net/mynameishuangshuai
 * 日期:2016年10月14日
 */public class BaseActivity extends Activity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        InjectUtils.inject(this);
    }
}12345678910111213141516

使用自定义的注解工具库

MainActivity.java

import com.castiel.ioc.activity.BaseActivity;import com.castiel.ioc.annotation.CastielContentViewInject;import com.castiel.ioc.annotation.CastielOnClickInject;import com.castiel.ioc.annotation.CastielViewInject;import android.os.Bundle;import android.widget.Button;import android.widget.TextView;@CastielContentViewInject(R.layout.activity_main)public class MainActivity extends BaseActivity {

    @CastielViewInject(R.id.tv)    private TextView tv;    @CastielViewInject(R.id.btn)    private Button btn;    @CastielOnClickInject({R.id.btn})    public void changText(){
        tv.setText("猴子搬来的救兵 http://blog.csdn.net/mynameishuangshuai");
    }    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
    }
}123456789101112131415161718192021222324252627

布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_margin="20dp"
        android:text="Hello world!"
        android:textSize="18sp" />

    <Button        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv"
        android:layout_margin="20dp"
        android:text="按钮" /></RelativeLayout>1234567891011121314151617181920212223

当我点击”按钮”,就可以实现TextView文字的切换。

这里写图片描述

这里写图片描述


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP