移动架构-IOC架构设计

作者: 06fd4cf1f427 | 来源:发表于2019-04-25 21:42 被阅读46次

    控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中

    注入布局

    首先是注入布局,这也是注入中相对简单的注入
    在没有使用注入的时候,会使用setContentView(R.layout.activity_main);去加载布局,而使用注入以后就可以直接注入布局了
    首先需要一个注入注解

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

    然后编写一个BaseActivity,让MainActivity继承BaseActivity,在BaseActivity中完成注入说明

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

    去掉MainActivity中的setContentView()方法,使用注解

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

    编写注入方法类InjectUtils

    public class InjectUtils {
        public static void injectLayout(Context context) {
            int layoutId = 0;
            Class<?> clazz = context.getClass();
            //拿到类上面的注解
            ContentView contentView = clazz.getAnnotation(ContentView.class);
            if (contentView != null) {
                layoutId = contentView.value();
                try {
                    Method method = clazz.getMethod("setContentView", int.class);
                    method.invoke(context,layoutId);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    之后再运行就可以不使用setContentView完成布局注入了

    控件注入

    控件注入如同布局注入一样,不使用findViewById()方法,再要注入的控件加入id即可
    控件注入注解

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

    再InjectUtils中加入控件注入方法

    public static void injectView(Context context) {
        Class<?> aClass = context.getClass();
        //获取Activity里面的所有成员变量
        Field[] fields=aClass.getDeclaredFields();
        for (Field field : fields) {
            //得到需要注入的View
            ViewInject viewInject = field.getAnnotation(ViewInject.class);
            if (viewInject != null) {
                //获取ID
                int valueId = viewInject.value();
                try {
                    Method method = aClass.getMethod("findViewById", int.class);
                    //反射调用方法
                    View view = (View) method.invoke(context, valueId);
                    field.setAccessible(true);
                    field.set(context, view);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    最后再调用即可

    @ContentView(R.layout.activity_main)
    public class MainActivity extends BaseActivity {
    
        @ViewInject(R.id.text_view)
        private TextView textView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //InjectUtils.injectLayout(this); //BaseActivity中已经执行过
            InjectUtils.injectView(this);
            textView.setText("IOC View Inject");
        }
    }
    

    此处必须要有一个id,在注入完成以后,界面正常显示,并且TextView的显示文字变为IOC View Inject,完成布局和控件的注入
    效果如下

    事件注入

    在之前,监听一个事件,其操作还是相对来说比较繁琐的,比如Button按键的监听就使用如下方法

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

    这里使用注入,其过程比布局和控件注入稍微复杂一点,这是考虑到了扩展性,不能单一使用id完成注解
    事件的监听包含事件源,事件和回调,因此其注解就稍微复杂一点,这里设计一个监听各种点击事件的监听,包括普通单击,长按,itemclick等
    首先依旧是需要一个注解

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface EventInject {
        //设置监听
        String listenerSetter();
        //事件类型
        Class<?> listenerType();
        //回调方法
        String callBackMethod();
    }
    

    这是为了拓展方便的一个接口,用在注解上,接下来是单击和长按的注解

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

    这里还需要一个动态代理

    public class ListenerInvocationHandler implements InvocationHandler {
        //真实对象引用
        private Context context;
        //可能存在多个方法
        private Map<String, Method> methodMap;
    
        public ListenerInvocationHandler(Context context, Map<String, Method> methodMap) {
            this.context = context;
            this.methodMap = methodMap;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String name = method.getName();
            //决定是否需要代理
            Method method1 = methodMap.get(name);
            if (method1 != null) {
                return method1.invoke(context, args);
            } else {
                return method.invoke(proxy,args);
            }
        }
    }
    

    具体的注入方法

    //注入事件
    public static void injectEvent(Context context) {
        Class<?> aClass = context.getClass();
        //获取所有方法
        Method[] methods = aClass.getDeclaredMethods();
        for (Method method : methods) {
            //获取方法上所有注解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                //获取annotationType注解
                Class<?> annotationType = annotation.annotationType();
                EventInject eventInject = annotationType.getAnnotation(EventInject.class);
                if (eventInject == null) {
                    continue;
                }
                //获取事件三要素,进行注入
                String listenerSetter = eventInject.listenerSetter();
                Class<?> listenerType = eventInject.listenerType();
                String callBackMethod = eventInject.callBackMethod();
                Map<String, Method> methodMap = new HashMap<>();
                methodMap.put(callBackMethod, method);
                try {
                    Method valueMethod = annotationType.getDeclaredMethod("value");
                    int[] valueIds = (int[]) valueMethod.invoke(annotation);
                    for (int valueId : valueIds) {
                        Method findViewById = aClass.getMethod("findViewById", int.class);
                        View view = (View) findViewById.invoke(context, valueId);
                        if (view == null) {
                            continue;
                        }
                        Method setListener = view.getClass().getMethod(listenerSetter, listenerType);
                        //使用动态代理
                        ListenerInvocationHandler handler = new ListenerInvocationHandler(context, methodMap);
                        Object proxy = Proxy.newProxyInstance(listenerType.getClassLoader(),
                                new Class[]{listenerType}, handler);
                        setListener.invoke(view, proxy);
                    }
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    由于三个注入都使用了,所以在InjectUtils添加静态方法,在BaseActivity中调用

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

    在Activity中调用

    @ContentView(R.layout.activity_main)
    public class MainActivity extends BaseActivity {
    
        @ViewInject(R.id.text_view)
        private TextView textView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            textView.setText("IOC View Inject");
        }
    
        @OnClick({R.id.onclick_button_one, R.id.onclick_button_two})
        public void click(View view) {
            switch (view.getId()) {
                case R.id.onclick_button_one:
                    Toast.makeText(this, "单击按钮 1 被点击了", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.onclick_button_two:
                    Toast.makeText(this, "单击按钮 2 被点击了", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    
        @OnLongClick(R.id.onlongclick_button)
        public boolean longClick(View view) {
            Toast.makeText(this, "长按被点击了", Toast.LENGTH_SHORT).show();
            return false;
        }
    }
    

    布局如下,就是一个文本,三个按钮

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center">
    
        <TextView
            android:id="@+id/text_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="Hello World!"
            android:textSize="18sp" />
    
        <Button
            android:id="@+id/onclick_button_one"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="单击按钮 1" />
    
        <Button
            android:id="@+id/onclick_button_two"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="单击按钮 2" />
    
        <Button
            android:id="@+id/onlongclick_button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="长按按钮" />
    
    </LinearLayout>
    

    其实现的效果如下


    针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
    资料领取:点赞+加群免费获取 Android IOC架构设计

    加群 Android IOC架构设计领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

    相关文章

      网友评论

        本文标题:移动架构-IOC架构设计

        本文链接:https://www.haomeiwen.com/subject/bkvtnqtx.html