美文网首页
Android进阶篇- IOC注入架构

Android进阶篇- IOC注入架构

作者: 不睡太晚不说太满 | 来源:发表于2019-09-17 11:13 被阅读0次

    前言

    在平时写代码的过程中都会出现很多方法中出现@Override @hide这样的注解,还有在比如我们经常用到的EventBus、ButterKnife、Retrofit、Dagger等都会用到。它们有什么作用?以及怎么使用的?改文章也会对上一篇反射的技术进行进一步的加深使用和理解。如果没有使用反射技术童鞋请先阅读上一篇 [Android进阶篇-反射机制ReFlect]

    IOC

    • 初始 DIP、Ioc、DI、Ioc容器

    依赖倒置原则 (DIP, Dependency Inverse Principle)
    强调系统的“高层组件”不应当依赖于“底层组件”, 并且不论是“高层组件”还是“底层组件”都应当依赖于抽象。抽象不应当依赖于实现,实现应该依赖于抽象(软件设计原则)

    控制反转(Ioc,Inverse of Control)
    一种反转、依赖和接口的方式。就是将控制权“往高处/上层”转移,控制反转是实现依赖倒置的一种方法(DIP的具体实现方式)

    依赖注入 (DI,Dependency Injection)
    组件通过构造函数或者setter方法,将其依赖暴露给上层,上层要设法取得组件的依赖,并将其传递给组件。依赖注入是实现控制反转的一种手段(Ioc的具体实现方式)

    Ioc容器
    依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)

    • 作用以及优缺点

    Ioc的核心是解耦,简化我们的工作量。
    而解耦的目的:修改耦合对象时不影响另外一个对象,降低模块之间的关联。
    在Spring中IOC更多是依靠xml的配置,而在Android中的IOC框架可以不使用xml配置。

    优点:代码量减少,代码阅读性好
    缺点:会产生一定的性能消耗 (现今的手机上是可以忽略的)

    • 元注解

    定义定义注解时,会需要一些元注解(meta-annotation),如@Target@Retention@Target用来定义你的注解将应用于什么地方(可能是一个类或者是一个方法),@Retention用来定义注解在哪一个级别可用(是在源码中SOURCE,还是在类文件中CLASS,又或者是在运行时RUNTIME)
    定义注解时很像是在定义一个接口,所以一般在注解中都会包含一些元素以表示某些值,当处理注解时,就可以根据这个值来做一些判断。

    @Target
    Type:类/接口
    FIELD:属性
    METHOD:方法
    PARAMETER:参数
    CONSTRUCTOR:构造方法
    LOCAL_VARIABLE:局部变量
    ANNOTATION_TYPE:该注解使用在另一个注解上
    PACKAGE:包

    @Retention
    注解会在class字节码文件中存在, JWM加载时可以通过反射获取到该注解的内容
    SOURCE:源码级操作(检查、检测)
    CLASS:在编译时 进行一些预操作
    RUNTIME:运行时编译
    生命周期:SOURCE < CLASS < RUNTIME

    1. 一般如果需要在运行时去动态获取注解信息,用RUNTIME注解
    2. 要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。注解会在class文件中存在,但是在运行时会被丢弃
    3. 做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解
    • 注解元素

    在定义注解时,我们同样可以为注解定义元素,例如:

    @Target(ElementType.TYPE) //作用在类之上
    @Retention(RetentionPolicy.RUNTIME) //运行时编译
    public @interface User {
          int age(); //返回int类型数据
          String name() default "daxu"; //返回String类型数据
    }
    

    标签@User是由User.class定义,其中包含了int元素的age,以及一个String元素的name,如果name不传值则默认取值为“daxu”。在注解元素中,可用的类型如下:

    所有的基本类型(int, float, boolean 等)
    String
    Class
    enum
    Annotaion
    以上类型的数组

    如果使用了其他类型则会编译报错。注意,也不允许使用任何包装类型。

    • 使用

    在Andorid开发中我们经常在Activity的onCraete中写setContentView(R.layout.xxx),最烦的是每个view的申明都需要些TextView tv = findviewById(R.id.tv), 所以有大神推出了ButterKnife使用注解来解决。今天我们就来自己手动写一下。

    打开AS创建一个工程,并且创建一个Java的Library,在该lib中创建一个Kind为Annotation,Name为ContentView的注解类

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

    使用该注解时在Activity之上申明该注解,并传入该Activity的layout

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

    到此,注解已经定义完成。那么怎么去完成把layout和Activity进行绑定呢?下面我们在lib中创建一个InjectManager的类。

    public class InjectManager {
      public static void inject(Activity activity) {
        // 布局的注入
        injectLayout(activity);
      }
      
      private static void injectLayout(Activity activity) {
        //根据传递的Activity对象获取类
        Class<? extends Activity> clazz = activity.getClass();
        //获取类之上的注解
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        if (contentView != null) {
          //获取注解的值,也就是传递的R.layout.activity_main
          int layoutId = contentView.value();
          //第一种方式 (这种方式比较low)
          //activity. setContentView(layoutId);
          //第二种方式:反射方法
          try {
              Method method = clazz.getMethod("setContentView", int.class);
              //执行方法
              method.invoke(activity, layoutId);
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    }
    

    并且在BaseActivity的onCreate()方法中进行注册

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

    开始run一下项目,发现该MainActivity正常打开。
    按照此种方式我们继续完成View的申明。比较findviewbyid才是最让人写吐的...
    在lib中创建一个Kind为Annotation,Name为BindView的注解类

    @Target(ElementType.FIELD) //作用在属性之上
    @Retention(RetentionPolicy.RUNTIME)
    public @interface BindView {
        int value();
    }
    

    @BindView和@ContentView基本一致,其差别就是这次作用在属性之上,而ContentView则作用在类之上。
    定义好注解后,我们继续在InjectManager中开始编写代码, 在InjectManager中定义一个方法injectView(Activity activity); 在inject()进行调用

    private static void injectView(Activity activity) {
      //获取类
      Class<? extends Activity> clazz = activity.getClass();
      //拿到每个属性 (因为不确定是什么修饰 所以使用getDeclaredFields)
      Field[] fields = clazz.getDeclaredFields();
      for (Field field : fields) {
        //获取每个属性上的注解
        BindView bindView = field.getAnnotation(BindView.class);
         if (bindView != null) {
          //拿到控件ID
          int viewId = bindView.value();
          try {
              Method method = clazz.getMethod("findViewById", int.class);
              Object view = method.invoke(activity, viewId);
              //设置private的访问权限
              field.setAccessible(true);
              //将方法执行的返回值赋值给全局的某属性
              field.set(activity, view);
            } catch (Exception e) {
               e.printStackTrace();
            }
        }
      }
    }
    

    完成后在MainActivity中测试.. 为了测试view被正常申明,我们在onResume()方法中弹出吐司,吐司内容为该Button按钮的内容来测试一下。

    @ContentView(R.layout.activity_main)
    public class MainActivity extends BaseActivity {
        @BindView(R.id.btn)
        public Button btn;
    
        @Override
        protected void onResume() {
            super.onResume();
            Toast.makeText(this, btn.getText().toString(), Toast.LENGTH_LONG).show();
        }
    }
    

    run项目发现项目运行正常, 并且成功弹出Button内容的吐司~


    image.png

    目前呢, MainActivity中的setContentView和findViewById已经使用了注解的方式进行了简化,还能有其他的地方可以使用注解来进行快速开发吗?答案是当然有!在Android开发中还有很多地方都可以,比如:OnClickListener,onLongClickListener等点击事件,像这种点击事件它又和setContentView和findViewById完全不一样,因为这种事件都带有各自回调方法.. 而我们所有的操作也都写在了onClick的回调方法里面 这种情况下要怎么处理呢?

    btn.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
      }
    });
    
    btn.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View v) {
         return false;
      }
     });
    

    看到上面的两个点击事件,我们发现了三个共同点(事件三部曲)
    ① 都有监听的方法名 setxxxListener
    ② 都有监听的对象 new View.OnxxxListener
    ③ 都有回调方法名 onxxx(View v)
    找到了规律,我们是否可以想一个办法,把这三个规律整合在一起并且“委托一个人”帮忙把回调里的事情给办了呢? 委托这两个字在我们开发人员眼中很容易就会想到一个词“代理”。代理模式不就是这样的吗? 简单点来讲,就是能不能把事件三部曲打包成一个对象,用代理去完成这件事..
    现在点击事件的规律找到了,怎么去委托也知道了,但是怎么让代理完成的事情和我们定义的方法绑定到一起呢? 这时就得需要用到另一个知识点 AOP面向切面编程。

    AOP面向切面的知识点下次再来讲吧..

    相关文章

      网友评论

          本文标题:Android进阶篇- IOC注入架构

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