美文网首页
IOC注解及相应源码分析

IOC注解及相应源码分析

作者: isLJli | 来源:发表于2020-06-15 22:32 被阅读0次

IOC的定义

英文全称为:Inversion of Control 控制和反转。翻译到代码就是反射加注解。

第三方IOC框架源码解析

IOC框架主要是注入控件和布局、设置点击事件。接下来分析一下,第三方IOC框架:xUtils和BufferKnife的源码。

xUtils

  • 基本使用
@ContentView(R.layout.activity_main)
public class MainActivityTest extends AppCompatActivity {

  @ViewInject(R.id.text)
  private TextView mTextView;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      x.view().inject(this);
      mTextView.setText("你好");
  }

/**
   * 1. 方法必须私有限定,
   * 2. 方法参数形式必须和type对应的Listener接口一致.
   * 3. 注解参数value支持数组: value={id1, id2, id3}
   * 4. 其它参数说明见{@link org.xutils.view.annotation.Event}类的说明.
   **/
  @Event(value = R.id.text,
  type = View.OnClickListener.class /*可选参数, 默认是View.OnClickListener.class*/)
  private void onClick(View view){
      Toast.makeText(this,"Toast",Toast.LENGTH_SHORT).show();
  }
}

x.view().inject(this)
首先来分析这行注册代码。点击inject之后,发现跳转到了接口方法。

public interface ViewInjector {

  /**
   * 注入view
   */
  void inject(View view);

  /**
   * 注入activity
   */
  void inject(Activity activity);

  /**
   * 注入view holder
   *
   * @param handler view holder
   */
  void inject(Object handler, View view);

  /**
   * 注入fragment
   */
  View inject(Object fragment, LayoutInflater inflater, ViewGroup container);
}

然后发现这个接口的实现类是ViewInjectorImpl,那在这个实现类中查看inject(activity)方法做了什么

  • 首先查看是否使用了ContentView注解来设置setContentView()
  • 设置控件和点击事件
@Override
  public void inject(Activity activity) {
      //获取Activity的ContentView的注解
      Class<?> handlerType = activity.getClass();
      try {
           //利用注解设置setContentView
          ContentView contentView = findContentView(handlerType);
          if (contentView != null) {
              int viewId = contentView.value();
              if (viewId > 0) {
                  activity.setContentView(viewId);
              }
          }
      } catch (Throwable ex) {
          LogUtil.e(ex.getMessage(), ex);
      }
       // 利用反射注解设置控件和点击事件
      injectObject(activity, handlerType, new ViewFinder(activity));
  }

   /**
   * 从父类获取注解View
   */
  private static ContentView findContentView(Class<?> thisCls) {
      if (thisCls == null || IGNORED.contains(thisCls) || thisCls.getName().startsWith("androidx.")) {
          return null;
      }
      ContentView contentView = thisCls.getAnnotation(ContentView.class);
      if (contentView == null) {
          return findContentView(thisCls.getSuperclass());
      }
      return contentView;
  }

属性设置:利用反射去获取 Annotaion --> value --> findViewById --> 反射注入
事件设置: 利用反射去获取Annotaion-->value-->findViewById --> setOnClickListener -->动态代理执行方法

rivate static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {
     // .......

      // 从父类到子类递归
      injectObject(handler, handlerType.getSuperclass(), finder);

      // inject view  注入控件View
      Field[] fields = handlerType.getDeclaredFields();
      if (fields != null && fields.length > 0) {
          for (Field field : fields) {
            // ......
             // 获取viewInject 注解
              ViewInject viewInject = field.getAnnotation(ViewInject.class);
              if (viewInject != null) {
                  try {
                     // 其实最终还是调用findViewById的方法
                      View view = finder.findViewById(viewInject.value(),
                           viewInject.parentId());
                      if (view != null) {
                          // 利用反射 把View注入到该属性中
                          field.setAccessible(true);
                          field.set(handler, view);
                      } else {
                         // ......
                      }
                  } catch (Throwable ex) {
                      LogUtil.e(ex.getMessage(), ex);
                  }
              }
          }
      } // end inject view

      // inject event  注入事件
      Method[] methods = handlerType.getDeclaredMethods();
      if (methods != null && methods.length > 0) {
          for (Method method : methods) {
          // ......
              //检查当前方法是否是event注解的方法
              Event event = method.getAnnotation(Event.class);
              if (event != null) {
                  try {
                      // id参数
                      int[] values = event.value();
                      int[] parentIds = event.parentId();
                      int parentIdsLen = parentIds == null ? 0 : parentIds.length;
                      //循环所有id,生成ViewInfo并添加代理反射   主要使用了动态代理的设计模式
                      for (int i = 0; i < values.length; i++) {
                          int value = values[i];
                          if (value > 0) {
                              ViewInfo info = new ViewInfo();
                              info.value = value;
                              info.parentId = parentIdsLen > i ? parentIds[i] : 0;
                              method.setAccessible(true);
                              // EventListenerManager 动态代理执行相应的方法
                              EventListenerManager.addEventMethod(
                                  finder, info, event, handler, method);
                          }
                      }
                  } catch (Throwable ex) {
                      LogUtil.e(ex.getMessage(), ex);
                  }
              }
          }
      } // end inject event

  }

ButterKnife

butterknife是一个安卓大神写的,https://github.com/JakeWharton/butterknife
其效率比xUtils更高,但在组件化开发中也挺多坑的,一般不在组件化开发中使用。
对于其源码的分析,不能像xUtils一样通过注册代码就可以找到整个线索。

  • 基本使用
public class MainActivity extends AppCompatActivity {

  @Bind(R.id.icon)
  ImageView icon;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      ButterKnife.bind(this);
  }

  @OnClick(R.id.icon)
  public void onClick() {
      Toast.makeText(this, "toast", Toast.LENGTH_LONG).show();
  }
}

ButterKnife.bind(this)

@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
  Class<?> targetClass = target.getClass();
  if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
   //重点在这个方法
  Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

  if (constructor == null) {
    return Unbinder.EMPTY;
  }

  //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
  try {
    return constructor.newInstance(target, source);
  } 
  ...
}

private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
   //查看map中是否有这个类,有则直接返回
  Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
  if (bindingCtor != null || BINDINGS.containsKey(cls)) {
    if (debug) Log.d(TAG, "HIT: Cached in binding map.");
    return bindingCtor;
  }
  //得到名字
  String clsName = cls.getName();
  if (clsName.startsWith("android.") || clsName.startsWith("java.")
      || clsName.startsWith("androidx.")) {
    if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
    return null;
  }
  try {
      //找到类名+_ViewBinding的类名
    Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
    //noinspection unchecked
    bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
    if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
  } 
  ...
  BINDINGS.put(cls, bindingCtor);
  return bindingCtor;
}

可以看到ButterKnife并不像xUtils那样在注册时就通过反射找到注解然后设置相关代码。
我们来看一下注解:

@Retention(RUNTIME) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}

Retention为RUNTIME 的 Annotation,由apt(Annotation Processing Tool) 解析自动解析。就是在Java代码运行的时候就处理@BindView、@OnClick(ButterKnife还支持很多其他的注解)这些注解了。

Annotation processing是在运行阶段执行的,它的原理就是读入Java源代码,解析注解,然后成新的Java代码生。新生成的Java代码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法。其他同理。

  • 开始它会扫描Java代码中所有的ButterKnife注解@BindView、@OnClick、@OnItemClicked等。

  • 当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似_ViewBinder,这个新生成的类实现了ViewBinder接口。

  • 这个ViewBinder类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等。

  • 最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法。
    看看生成的类:

public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {
@Override public void bind(final Finder finder, final T target, Object source) {
  View view;
  view = finder.findRequiredView(source, 2131427372, "field 'icon' and method 'onClick'");
  //为原来的类属性findViewById
  target.icon = finder.castView(view, 2131427372, "field 'icon'");
  view.setOnClickListener(
    new butterknife.internal.DebouncingOnClickListener() {
      @Override public void doClick(View p0) {
        //调用原来的类的方法
         target.onClick();
      }
    });
}

@Override public void unbind(T target) {
  target.icon = null;
}
}

用反射自定义IOC注解

属性

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

点击

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

网络

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckNet {

}
/**
* 根据id查询属性
*/
public class ViewFinder {

  private Activity mActivity;
  private View mView;

  public ViewFinder(Activity activity) {
      this.mActivity = activity;
  }

  public ViewFinder(View view) {
      this.mView = view;
  }

  public View findViewById(int viewId) {
      return mActivity != null ? mActivity.findViewById(viewId) : mView.findViewById(viewId);
  }

}
public class ViewUtils {

  public static void inject(Activity activity) {
      inject(new ViewFinder(activity), activity);
  }

  public static void inject(View view) {
      inject(new ViewFinder(view), view);
  }

  public static void inject(View view, Object object) {
      inject(new ViewFinder(view), object);
  }

  //兼容给上面三个方法
  public static void inject(ViewFinder finder, Object object) {
      //注入属性
      injectFiled(finder, object);
      //注入事件
      injectEvent(finder, object);
  }

  //属性
  private static void injectFiled(ViewFinder finder, Object object) {
      //1.获取类里面所有的属性
      Class<?> clazz = object.getClass();
      //获取所有的属性包括私有和公有
      Field[] fields = clazz.getDeclaredFields();

      //2.获取viewById的里面的value值
      for (Field field : fields) {
          ViewById viewById = field.getAnnotation(ViewById.class);
          if (viewById != null) {
              // 获取注解里面的id值 --》 R.id.test_tv
              int viewId = viewById.value();
              //3.findViewById 找到View
              View view = finder.findViewById(viewId);
              if (view != null) {
                  //能够注入所有修饰符
                  field.setAccessible(true);
                  //4.动态注入找到view
                  try {
                      field.set(object, view);
                  } catch (IllegalAccessException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
  }

  //点击和检查网络
  private static void injectEvent(ViewFinder finder, Object object) {
      //1.获取类里面的所有方法
      Class<?> clazz = object.getClass();
      Method[] methods = clazz.getDeclaredMethods();

      //2.获取onclick里面的value值
      for (Method method : methods) {
          OnClick onClick = method.getAnnotation(OnClick.class);
          if (onClick != null) {
              int[] viewIds = onClick.value();
              for (int viewId : viewIds) {
                  //3.findViewById 找到View
                  View view = finder.findViewById(viewId);

                  //扩展功能 检测网络
                  boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;

                  if (view != null) {
                      //4.setOnClickListener()
                      view.setOnClickListener(new DeclareOnClickListener(method, object, isCheckNet));
                  }
              }
          }
      }

  }

  private static class DeclareOnClickListener implements View.OnClickListener {
      private Object object;
      private Method method;
      private boolean isCheckNet;

      public DeclareOnClickListener(Method method, Object object, boolean isCheckNet) {
          this.method = method;
          this.object = object;
          this.isCheckNet = isCheckNet;
      }

      @Override
      public void onClick(View v) {
          //是否需要检测网络
          if (isCheckNet) {
              //没网则不往下执行
              if (isNetworkConnected(v.getContext())) {
                  Toast.makeText(v.getContext(), "网络没有连接", Toast.LENGTH_SHORT).show();
                  return;
              }
          }
          //点击会调用该方法
          try {
              //所有方法都可以,包括私有共有
              method.setAccessible(true);
              //5.反射执行方法
              method.invoke(object, v);
          } catch (IllegalAccessException e) {
              e.printStackTrace();
          } catch (InvocationTargetException e) {
              e.printStackTrace();
              try {
                  method.invoke(object, v);
              } catch (Exception ex) {
                  ex.printStackTrace();
              }
          }
      }
  }

  public static boolean isNetworkConnected(Context context) {
      if (context != null) {
          ConnectivityManager mConnectivityManager = (ConnectivityManager) context
                  .getSystemService(Context.CONNECTIVITY_SERVICE);
          NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
          if (mNetworkInfo != null) {
              return mNetworkInfo.isAvailable();
          }
      }
      return false;
  }
}

使用

public class MainActivityTest extends AppCompatActivity {

  @ViewById(R.id.text)
  public TextView mTtext;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      ViewUtils.inject(this);
      mTtext.setText("IOCTV");
  }

  @Override
  protected void onResume() {
      super.onResume();
  }

  @OnClick({R.id.text})
  @CheckNet  //没网不执行
  public void login(View view) {
      Toast.makeText(this, "有网,被点击了", Toast.LENGTH_SHORT).show();
  }

  @OnClick(R.id.two)
  public void click(View view) {
      Toast.makeText(this, "被点击了", Toast.LENGTH_SHORT).show();
  }
}

相关文章

网友评论

      本文标题:IOC注解及相应源码分析

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