红橙Darren视频笔记 IOC注解框架 了解xUtils3与ButterKnife的原理

本文主要是介绍红橙Darren视频笔记 IOC注解框架 了解xUtils3与ButterKnife的原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.什么是IOC

IOC是Inversion of Control的缩写,直接翻译过来就叫依赖反转,看起来感觉不明觉厉,我觉得IOC就是一种解耦方式。比如原本我们在Activity中findviewbyId或者setOnClickListener时比较麻烦,需要写很多代码,比如findviewbyId需要让Activity中的view和布局文件的对应的view形成映射;setOnClickListener更复杂,除了前一步,还要创建Listener并将之绑定到view上, 有了IOC之后,关系被解耦。布局和view不再像之前紧密联系,中间靠IOC维持关系,这样是的代码结构变得清晰。(实际关系还是紧密的,只不过如果忽略中间的IOC层,结构变得清晰)
不管IOC是多么高大上的名字 我们只要清楚他可以减少我们代码的冗余程度,降低耦合性即可
关于IOC的解释 这里有一篇文章讲的不错
https://www.cnblogs.com/DebugLZQ/archive/2013/06/05/3107957.html

2.xUtils3使用

1 gradle引入

 implementation 'org.xutils:xutils:3.9.0'

2 代码修改

//https://github.com/wyouflf/xUtils3
@ContentView(R.layout.activity_main)//注解布局
public class MainActivity extends AppCompatActivity {//声明各种变量(id 来自布局xml)@ViewInject(R.id.btn)private ImageButton imageButton;@ViewInject(R.id.tv)private TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);x.view().inject(this);//将Activity注入xUtilstextView.setText("Changed Text");}/*** 1. 方法必须私有限定,* 2. 方法参数形式必须和type对应的Listener接口一致.* 3. 注解参数value支持数组: value={id1, id2, id3}* 4. 其它参数说明见{@link org.xutils.event.annotation.Event}类的说明.**///声明和使用点击事件@Event(value = R.id.btn,type = View.OnClickListener.class/*可选参数, 默认是View.OnClickListener.class*/)private void onButtonClick(View view) {Toast.makeText(this,"Image Click",Toast.LENGTH_SHORT).show();}@Event(value = R.id.tv,type = View.OnClickListener.class/*可选参数, 默认是View.OnClickListener.class*/)private void onTvClick(View view) {Toast.makeText(this,"Text Click",Toast.LENGTH_SHORT).show();}
}

3.xUtils3源码分析

3.1 field属性绑定

从x.view().inject入手

    @Overridepublic void inject(Activity activity) {//获取Activity的ContentView的注解Class<?> handlerType = activity.getClass();try {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));//跟下去}
        // inject viewField[] fields = handlerType.getDeclaredFields();//获取handlerType的所有属性filed handlerType是Activity类型if (fields != null && fields.length > 0) {for (Field field : fields) {Class<?> fieldType = field.getType();//跳过一些属性if (/* 不注入静态字段 */     Modifier.isStatic(field.getModifiers()) ||/* 不注入final字段 */    Modifier.isFinal(field.getModifiers()) ||/* 不注入基本类型字段 */  fieldType.isPrimitive() ||/* 不注入数组类型字段 */  fieldType.isArray()) {continue;}ViewInject viewInject = field.getAnnotation(ViewInject.class);//获取声明为ViewInject注解的属性if (viewInject != null) {try {View view = finder.findViewById(viewInject.value(), viewInject.parentId());//实际调用的就是view.findViewById或者activity.findViewByIdif (view != null) {field.setAccessible(true);field.set(handler, view);//利用反射 将handler(activity)中的声明了viewInject注解的地方 替换成刚刚find的view} else {throw new RuntimeException("Invalid @ViewInject for "+ handlerType.getSimpleName() + "." + field.getName());}} catch (Throwable ex) {LogUtil.e(ex.getMessage(), ex);}}}} // end inject view

3.2 event事件绑定

事件绑定就在属性绑定下面一点点

        // inject eventMethod[] methods = handlerType.getDeclaredMethods();//获取所有本类声明的方法if (methods != null && methods.length > 0) {for (Method method : methods) {if (Modifier.isStatic(method.getModifiers())|| !Modifier.isPrivate(method.getModifiers())) {//跳过静态方法和私有方法continue;}//检查当前方法是否是event注解的方法Event event = method.getAnnotation(Event.class);if (event != null) {try {// id参数int[] values = event.value();//获取event注解内部的id值valueint[] 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.addEventMethod(finder, info, event, handler, method);//跟进去}}} catch (Throwable ex) {LogUtil.e(ex.getMessage(), ex);}}}} // end inject eventpublic static void addEventMethod(//根据页面或view holder生成的ViewFinderViewFinder finder,//根据当前注解ID生成的ViewInfoViewInfo info,//注解对象Event event,//页面或view holder对象Object handler,//当前注解方法Method method) {try {View view = finder.findViewByInfo(info);if (view != null) {// 注解中定义的接口,比如Event注解默认的接口为View.OnClickListenerClass<?> listenerType = event.type();// 默认为空,注解接口对应的Set方法,比如setOnClickListener方法String listenerSetter = event.setter();if (TextUtils.isEmpty(listenerSetter)) {//取巧 加个set就变成了setOnClickListener setOnLongClickListenerlistenerSetter = "set" + listenerType.getSimpleName();}String methodName = event.method();boolean addNewMethod = false;/*根据View的ID和当前的接口类型获取已经缓存的接口实例对象,比如根据View.id和View.OnClickListener.class两个键获取这个View的OnClickListener对象*/Object listener = listenerCache.get(info, listenerType);DynamicHandler dynamicHandler = null;/*如果接口实例对象不为空获取接口对象对应的动态代理对象如果动态代理对象的handler和当前handler相同则为动态代理对象添加代理方法*/if (listener != null) {dynamicHandler = (DynamicHandler) Proxy.getInvocationHandler(listener);addNewMethod = handler.equals(dynamicHandler.getHandler());if (addNewMethod) {dynamicHandler.addMethod(methodName, method);}}// 如果还没有注册此代理if (!addNewMethod) {dynamicHandler = new DynamicHandler(handler);dynamicHandler.addMethod(methodName, method);// 生成的代理对象实例,比如View.OnClickListener的实例对象listener = Proxy.newProxyInstance(listenerType.getClassLoader(),new Class<?>[]{listenerType},dynamicHandler);listenerCache.put(info, listenerType, listener);}Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);setEventListenerMethod.invoke(view, listener);//核心 利用反射调用方法}} catch (Throwable ex) {LogUtil.e(ex.getMessage(), ex);}}

小结
属性注入
遍历属性->获取特定注解的Annotation的属性->获取其值->findviewbyid(value)->反射注入属性field.set(activity, view)
事件注入
遍历方法->获取特定注解的Annotation的方法->获取其值->findviewbyid(value)得到添加属性的view->反射调用方法method.invoke(view, listener)

4.ButterKnife使用步骤

参考
https://www.jianshu.com/p/39fc66aa3297
第一步 在moudle的gradle配置butterknife

    // 1 引入Butter knife到moduleimplementation 'com.jakewharton:butterknife:10.2.3'annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'

第二步 初始化ButterKnife

		//2 初始化ButterKnifeButterKnife.bind(this);

第三步 ButterKnife属性初始化 注意View必须有id

    //3 ButterKnife属性初始化 注意View必须有id@BindView(R.id.tv)TextView mTextView;@BindView(R.id.btn)Button mButton;

第四步

        //4 ButterKnife属性使用mTextView.setText("ABC");mButton.setText("My Button");

第五步 ButterKnife Event使用

    //5 ButterKnife Event使用@OnClick({R.id.tv,R.id.btn})void onItemClick(View view){switch (view.getId()){case R.id.tv:Toast.makeText(this,"text clicked",Toast.LENGTH_SHORT).show();break;case R.id.btn:Toast.makeText(this,"button clicked",Toast.LENGTH_SHORT).show();break;}}

完整Activity的代码

public class MainActivity extends AppCompatActivity {//3 ButterKnife属性初始化 注意View必须有id@BindView(R.id.tv)TextView mTextView;@BindView(R.id.btn)Button mButton;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//2 初始化ButterKnifeButterKnife.bind(this);//4 ButterKnife属性使用mTextView.setText("ABC");mButton.setText("My Button");}//5 ButterKnife Event使用@OnClick({R.id.tv,R.id.btn})void onItemClick(View view){switch (view.getId()){case R.id.tv:Toast.makeText(this,"text clicked",Toast.LENGTH_SHORT).show();break;case R.id.btn:Toast.makeText(this,"button clicked",Toast.LENGTH_SHORT).show();break;}}
}

5.ButterKnife的filed与事件绑定原理

因为我创建的项目支持AndroidX 而网上的资料的会ButterKnife基本都是8.8.1或以下的版本,我只能用新的ButterKnife版本,否则会报一些support v4和Androidx的冲突错误。而ButterKnife在更新之后逻辑变化了很多,无奈太菜看不太懂ButterKnife的源码,只能大致的结合网上的信息猜测流程
我们肯定从初始化开始入手

  @NonNull @UiThreadpublic static Unbinder bind(@NonNull Activity target) {View sourceView = target.getWindow().getDecorView();//target是activity sourceView是activity的根view DecorView 是一个FrameLayoutreturn bind(target, sourceView);}@NonNull @UiThreadpublic static Unbinder bind(@NonNull Object target, @NonNull View source) {Class<?> targetClass = target.getClass();if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());//获取activity的构造函数Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);if (constructor == null) {return Unbinder.EMPTY;}//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.try {//利用反射创建Activityreturn constructor.newInstance(target, source);} catch (IllegalAccessException e) {throw new RuntimeException("Unable to invoke " + constructor, e);} catch (InstantiationException e) {throw new RuntimeException("Unable to invoke " + constructor, e);} catch (InvocationTargetException e) {Throwable cause = e.getCause();if (cause instanceof RuntimeException) {throw (RuntimeException) cause;}if (cause instanceof Error) {throw (Error) cause;}throw new RuntimeException("Unable to create binding instance.", cause);}}

关注findBindingConstructorForClass方法

  //利用递归找到Activity的构造函数@Nullable @CheckResult @UiThreadprivate static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {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();//递归到了Android内部的类 还是没有找到构造方法 返回nullif (clsName.startsWith("android.") || clsName.startsWith("java.")|| clsName.startsWith("androidx.")) {if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");return null;}try {//加载全类名+_ViewBindingClass<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");//noinspection unchecked//找到全类名+_ViewBinding的构造方法bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");} catch (ClassNotFoundException e) {if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());bindingCtor = findBindingConstructorForClass(cls.getSuperclass());} catch (NoSuchMethodException e) {throw new RuntimeException("Unable to find binding constructor for " + clsName, e);}//缓存 备用BINDINGS.put(cls, bindingCtor);return bindingCtor;}

这里我们知道会有MainActivity_ViewBinding的一个类 我们可以在如下路径找到它
项目路径\moudle名称\build\generated\ap_generated_sources\debug\out\包名\

public class MainActivity_ViewBinding implements Unbinder {private MainActivity target;private View view7f080168;private View view7f080057;@UiThreadpublic MainActivity_ViewBinding(MainActivity target) {this(target, target.getWindow().getDecorView());}@UiThreadpublic MainActivity_ViewBinding(final MainActivity target, View source) {this.target = target;View view;//findRequiredView是重点view = Utils.findRequiredView(source, R.id.tv, "field 'mTextView' and method 'onItemClick'");target.mTextView = Utils.castView(view, R.id.tv, "field 'mTextView'", TextView.class);view7f080168 = view;view.setOnClickListener(new DebouncingOnClickListener() {@Overridepublic void doClick(View p0) {target.onItemClick(p0);}});view = Utils.findRequiredView(source, R.id.btn, "field 'mButton' and method 'onItemClick'");target.mButton = Utils.castView(view, R.id.btn, "field 'mButton'", Button.class);view7f080057 = view;view.setOnClickListener(new DebouncingOnClickListener() {@Overridepublic void doClick(View p0) {target.onItemClick(p0);}});}@Override@CallSuperpublic void unbind() {//可以在onDestroy调用 释放资源MainActivity target = this.target;if (target == null) throw new IllegalStateException("Bindings already cleared.");this.target = null;target.mTextView = null;target.mButton = null;view7f080168.setOnClickListener(null);view7f080168 = null;view7f080057.setOnClickListener(null);view7f080057 = null;}
}

可以猜到MainActivity_ViewBinding明显是根据MainActivity生成的类 那这个类作用是什么呢?
我们看一下findRequiredView方法

  public static View findRequiredView(View source, @IdRes int id, String who) {View view = source.findViewById(id);if (view != null) {return view;}String name = getResourceEntryName(source, id);throw new IllegalStateException("Required view '"+ name+ "' with ID "+ id+ " for "+ who+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"+ " (methods) annotation.");}

source是根View DecorView 自然可以通过findViewById找到指定的view 这里就完成了View与事件的绑定。当然如果view==null 说明我们绑定了一个不在当前加载xml中指定id的view,会抛出异常
那么filed的绑定何时进行的呢 其实就是在castView 这个相对简单多了,就是利用强制转换,如果类型不对还是会报错。不过这里也利用了上一步的findRequiredView来确保绑定的View在xml中定义过。

  public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {try {return cls.cast(view);} catch (ClassCastException e) {String name = getResourceEntryName(view, id);throw new IllegalStateException("View '"+ name+ "' with ID "+ id+ " for "+ who+ " was of the wrong type. See cause for more info.", e);}}

这个文件是如何产生的呢?
因为ButterKnife版本更新到10.2.3之后 我无法直接看到butterknife-compiler的相关代码了 主要是ButterKnifeProcessor这个类,我想会不会是因为现在用的运行时生成文件 所以我看不到该文件了?
不管怎么样 我相信他的大致原理还是和以前一样 即像网上所说的用ButterKnifeProcessor解析注解,包(PackageElement)、类(TypeElement)、成员变量(VariableElement)、方法(ExecutableElement) 等信息,当然中间需要利用Filer Trees 等工具类,最后使用JavaPoet来生成Java文件。
小结
ButterKnife利用annotationProcessor 和 JavaPoet 技术根据MainActivity生成一个类似MainActivity_ViewBinding的类,该类内部通过findViewById找到View然后给他注册各种事件,实现事件绑定

后记

对比xUtil和ButterKnife 他们的基本原理有一定的区别
xUtil使用反射+注解
ButterKnife 使用注解+annotationProcessor +JavaPoet
另外ButterKnife更新的变动感觉挺大,除了源码的一些改动,支持了AndroidX,在ButterKnife7.0.1还是在编译时@Retention(CLASS)生成MainActivity_ViewBinding,10.2.3的版本却变成了运行时@Retention(RUNTIME)了。不过不明白为什么这样改,不会影响运行速度么?还是因为现在机器已经很强大了,不需要在乎这点性能消耗呢。

这篇关于红橙Darren视频笔记 IOC注解框架 了解xUtils3与ButterKnife的原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1016221

相关文章

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

hdu4407容斥原理

题意: 有一个元素为 1~n 的数列{An},有2种操作(1000次): 1、求某段区间 [a,b] 中与 p 互质的数的和。 2、将数列中某个位置元素的值改变。 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOException;import java.io.Inpu