Android Butterknife浅分析
来源:互联网 发布:阳春网络歌手思维 编辑:程序博客网 时间:2024/05/30 23:14
今天很顺利的完成了公司的任务,干嘛呢,当然是写写代码看看书了。
发现个问题,一个APP中很多次的使用了一段代码,而且这行代码还非常不好省略,这个就是findViewId()和onClick,一个app肯定有界面和按钮,有见面就有控件,有控件就有点击需求,而有这些需求和控件就必须要在Activity或者Fragment中使用这段findViewId()和onClick(),真是太烦了,现在github上大神那么多,去看看有什么好的解决办法呗。
首先我发现了thinkAndroid这个框架,这是个很好的框架,集成了很多的模块:
- MVC模块:实现了视图和模型的分离,当然不用说
- IOC模块:这个就是我们所需要的,下面我们会认真的去看下这个模块,他们的github上的介绍是说:完全注解方式就可以进行UI的绑定,res中的资源的读取,以及对象的初始化。
- http模块:通过httpclient进行封装http数据请求,支持异步和同步方式加载。呃呃呃,这个什么情况,现在的Android studio上已经不支持httpclient了,要与时俱进啊大神...
- 缓存模块:通过简单的配置及设计可以很好的实现缓存,对缓存可以随意的配置
- 图片缓存模块:imageview加载图片的时候无需考虑图片加载过程中出现的oom和android容器快速滑动时候出现的图片错位等现象。
- 配置器模块:可以对简易的实现配对配置的操作,目前配置文件可以支持Preference、Properties对配置进行存取。
- 日志打印模块:可以较快的轻易的是实现日志打印,支持日志打印的扩展,目前支持对sdcard写入本地打印、以及控制台打印
- 下载器模块:可以简单的实现多线程下载、后台下载、断点续传、对下载进行控制、如开始、暂停、删除等等。
- 网络状态检测模块:当网络状态改变时,对其进行检测。
模块很多,上面这个东西是从他们的github上复制过来的,其他的我们不说,先看看所谓了ioc模块:
又给出例子:
@TAInject Entity entity; //目前只能对无参构造函数进行初始化 @@TAInject(id=R.string.app_name) String appNameString; @TAInjectResource(id=R.attr.test) int[] test; @TAInjectView(id=R.id.add); Button addButton;我们分析代码可以看到这个框架是通过@TAInjectView来快速初始化控件的。
看下@TAInjectView的代码:
package com.ta.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface TAInjectView{/** View的ID */public int id() default -1;/** View的单击事件 */public String click() default "";/** View的长按键事件 */public String longClick() default "";/** View的焦点改变事件 */public String focuschange() default "";/** View的手机键盘事件 */public String key() default "";/** View的触摸事件 */public String Touch() default "";}
是一个注解类用RetentionPloicy.RUNTIME做修饰,而且制定了修饰的类别Field,在这里看到大神很贴心的贴上了注释,几乎包含了控件的所有操作,很是方便,看下下面这行代码:
package com.ta.util;import java.lang.reflect.Field;import com.ta.annotation.TAInject;import com.ta.annotation.TAInjectResource;import com.ta.annotation.TAInjectView;import android.app.Activity;import android.content.res.Resources;public class TAInjector{private static TAInjector instance;private TAInjector(){}public static TAInjector getInstance(){if (instance == null){instance = new TAInjector();}return instance;}public void inJectAll(Activity activity){// TODO Auto-generated method stubField[] fields = activity.getClass().getDeclaredFields();if (fields != null && fields.length > 0){for (Field field : fields){if (field.isAnnotationPresent(TAInjectView.class)){injectView(activity, field);} else if (field.isAnnotationPresent(TAInjectResource.class)){injectResource(activity, field);} else if (field.isAnnotationPresent(TAInject.class)){inject(activity, field);}}}}private void inject(Activity activity, Field field){// TODO Auto-generated method stubtry{field.setAccessible(true);field.set(activity, field.getType().newInstance());} catch (Exception e){e.printStackTrace();}}private void injectView(Activity activity, Field field){// TODO Auto-generated method stubif (field.isAnnotationPresent(TAInjectView.class)){TAInjectView viewInject = field.getAnnotation(TAInjectView.class);int viewId = viewInject.id();try{field.setAccessible(true);field.set(activity, activity.findViewById(viewId));} catch (Exception e){e.printStackTrace();}}}private void injectResource(Activity activity, Field field){// TODO Auto-generated method stubif (field.isAnnotationPresent(TAInjectResource.class)){TAInjectResource resourceJect = field.getAnnotation(TAInjectResource.class);int resourceID = resourceJect.id();try{field.setAccessible(true);Resources resources = activity.getResources();String type = resources.getResourceTypeName(resourceID);if (type.equalsIgnoreCase("string")){field.set(activity,activity.getResources().getString(resourceID));} else if (type.equalsIgnoreCase("drawable")){field.set(activity,activity.getResources().getDrawable(resourceID));} else if (type.equalsIgnoreCase("layout")){field.set(activity,activity.getResources().getLayout(resourceID));} else if (type.equalsIgnoreCase("array")){if (field.getType().equals(int[].class)){field.set(activity, activity.getResources().getIntArray(resourceID));} else if (field.getType().equals(String[].class)){field.set(activity, activity.getResources().getStringArray(resourceID));} else{field.set(activity, activity.getResources().getStringArray(resourceID));}} else if (type.equalsIgnoreCase("color")){if (field.getType().equals(Integer.TYPE)){field.set(activity,activity.getResources().getColor(resourceID));} else{field.set(activity, activity.getResources().getColorStateList(resourceID));}}} catch (Exception e){e.printStackTrace();}}}public void inject(Activity activity){// TODO Auto-generated method stubField[] fields = activity.getClass().getDeclaredFields();if (fields != null && fields.length > 0){for (Field field : fields){if (field.isAnnotationPresent(TAInject.class)){inject(activity, field);}}}}public void injectView(Activity activity){// TODO Auto-generated method stubField[] fields = activity.getClass().getDeclaredFields();if (fields != null && fields.length > 0){for (Field field : fields){if (field.isAnnotationPresent(TAInjectView.class)){injectView(activity, field);}}}}public void injectResource(Activity activity){// TODO Auto-generated method stubField[] fields = activity.getClass().getDeclaredFields();if (fields != null && fields.length > 0){for (Field field : fields){if (field.isAnnotationPresent(TAInjectResource.class)){injectResource(activity, field);}}}}}这才是具体让findViewId()消失的类,一模了然的类,是一个单例模式,里面injectView,injectResource来实现具体的方法,代码不是很难理解,我就不对说了。
这个框架其实很不错的,但是看情况大神好像已经不更新这个框架了,只能放弃,看下代码好了,学学大神思路也是进步。
上面已经把大神的github给出了,有兴趣的可以点击进去看看。
看完这个之后我又开始找已经在Android studio上更新了的框架,发现了这个:Butterknife来自JakeWharton大神。
看下这个框架的实现代码:
class ExampleActivity extends Activity { @BindView(R.id.user) EditText username; @BindView(R.id.pass) EditText password; @BindString(R.string.login_error) String loginErrorMessage; @OnClick(R.id.submit) void submit() { // TODO call server... } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // TODO Use fields... }}
看到这个我就想,这就是我要找的框架。
我们接下来去分析这个的实现方法看下这个框架的代码结构:
额,很复杂的样子,看下onBindView之类的类在哪,去看下,
我们在butterknife-annotations模块找到了他们,分的非常仔细,看到名字就可以知道这个类是做什么的。我们首先找个典型的例子看下,BindView,上面的代码中表示这个类主要的是初始化控件的,我们看下这个代码:
package butterknife;import android.support.annotation.IdRes;import java.lang.annotation.Retention;import java.lang.annotation.Target;import static java.lang.annotation.ElementType.FIELD;import static java.lang.annotation.RetentionPolicy.CLASS;/** * Bind a field to the view for the specified ID. The view will automatically be cast to the field * type. * <pre><code> * {@literal @}BindView(R.id.title) TextView title; * </code></pre> */@Retention(CLASS) @Target(FIELD)public @interface BindView { /** View ID to which the field will be bound. */ @IdRes int value();}
不是很难哦,设置的保留策略为Class,注解用于Field上。传入一个IdRes,并且直接以value的形式进行设置。看下注解处理器的实现:
<pre name="code" class="java">package butterknife.compiler;@AutoService(Processor.class)public final class ButterKnifeProcessor extends AbstractProcessor { static final Id NO_ID = new Id(-1); static final String VIEW_TYPE = "android.view.View"; private static final String COLOR_STATE_LIST_TYPE = "android.content.res.ColorStateList"; private static final String BITMAP_TYPE = "android.graphics.Bitmap"; private static final String DRAWABLE_TYPE = "android.graphics.drawable.Drawable"; private static final String TYPED_ARRAY_TYPE = "android.content.res.TypedArray"; private static final String NULLABLE_ANNOTATION_NAME = "Nullable"; private static final String STRING_TYPE = "java.lang.String"; private static final String LIST_TYPE = List.class.getCanonicalName(); private static final String R = "R"; private static final List<Class<? extends Annotation>> LISTENERS = Arrays.asList(// OnCheckedChanged.class, // OnClick.class, // OnEditorAction.class, // OnFocusChange.class, // OnItemClick.class, // OnItemLongClick.class, // OnItemSelected.class, // OnLongClick.class, // OnPageChange.class, // OnTextChanged.class, // OnTouch.class // ); private static final List<String> SUPPORTED_TYPES = Arrays.asList( "array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "string" ); private Elements elementUtils; private Types typeUtils; private Filer filer; private Trees trees; private final Map<Integer, Id> symbols = new LinkedHashMap<>(); @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); trees = Trees.instance(processingEnv); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); for (Class<? extends Annotation> annotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; } private Set<Class<? extends Annotation>> getSupportedAnnotations() { Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>(); annotations.add(BindArray.class); annotations.add(BindBitmap.class); annotations.add(BindBool.class); annotations.add(BindColor.class); annotations.add(BindDimen.class); annotations.add(BindDrawable.class); annotations.add(BindInt.class); annotations.add(BindString.class); annotations.add(BindView.class); annotations.add(BindViews.class); annotations.addAll(LISTENERS); return annotations; } @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { //BindingClass类对象,代表生成代理类的信息 //Map<>集合代理类对象集合 Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env); //生成代理类 for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingClass bindingClass = entry.getValue(); for (JavaFile javaFile : bindingClass.brewJava()) { try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write view binder for type %s: %s", typeElement, e.getMessage()); } } } return true; } private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); scanForRClasses(env); //通过getElementsAnnotatedWith拿到我们注解的每个元素,返回值为Map<>集合 for (Element element : env.getElementsAnnotatedWith(BindArray.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceArray(element, targetClassMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindArray.class, e); } } //代码省略 return targetClassMap; } //代码省略}
代码很长只留核心代码,其他省略
嗯哼,一般的写法,继承了AbstractProcessor,并实现了public synchronized void init(ProcessingEnvironment env)
,@Override public Set<String> getSupportedAnnotationTypes() 和private Set<Class<? extends Annotation>> getSupportedAnnotations()函数,在这些函数里,主要是将BindView之类的注解和注解处理器连接,一般情况下,这些函数的实现是通用的,主要是返回注解类型,返回源码版本,和初始化辅助类。处理器中还有一个非常重要的函数需要我们的实现:process(),这是核心,在我们的知道的process()函数的实现复杂有简单,其实就实现了两个功能而已:
- 收集代理类信息使用的是getElementsAnnotatedWith函数 。返回代理类集合。
- 生成咱们在使用BindView之后编译生成的代理类,想上面的例子,在MainActivity中的,我们会生成一个MainActivity_ViewBinder和MainActivity_ViewBining代理类,在Android studio的build文件夹下可以很清楚的看到这个类的存在.
上面的BindingClass类为生成java类的方法,通过收集到的信息,拼接完成代理类对象。
完成代理类对象之后,提供一个api供用户调用,ButterKnife的实现API为ButterKnife类,看下代码:
package butterknife;public final class ButterKnife { //代码省略 //传入当前对象,不管是Activity,fragment或者dialog全都行,寻找代理类 @NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { return getViewBinder(target).bind(Finder.ACTIVITY, target, target); } //代码省略 //强制转换接口为统一接口,并调用接口提供的方法。 @NonNull @CheckResult @UiThread static ViewBinder<Object> getViewBinder(@NonNull Object target) { Class<?> targetClass = target.getClass(); if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName()); return findViewBinderForClass(targetClass); } @NonNull @CheckResult @UiThread private static ViewBinder<Object> findViewBinderForClass(Class<?> cls) { ViewBinder<Object> viewBinder = BINDERS.get(cls); if (viewBinder != null) { if (debug) Log.d(TAG, "HIT: Cached in view binder map."); return viewBinder; } String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.")) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return NOP_VIEW_BINDER; } //noinspection TryWithIdenticalCatches Resolves to API 19+ only type. try { Class<?> viewBindingClass = Class.forName(clsName + "_ViewBinder"); //noinspection unchecked viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance(); if (debug) Log.d(TAG, "HIT: Loaded view binder class."); } catch (ClassNotFoundException e) { if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName()); viewBinder = findViewBinderForClass(cls.getSuperclass()); } catch (InstantiationException e) { throw new RuntimeException("Unable to create view binder for " + clsName, e); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to create view binder for " + clsName, e); } BINDERS.put(cls, viewBinder); return viewBinder; } //代码省略 /** Simpler version of {@link View#findViewById(int)} which infers the target type. */ @SuppressWarnings({ "unchecked", "UnusedDeclaration" }) // Checked by runtime cast. Public API. @CheckResult public static <T extends View> T findById(@NonNull View view, @IdRes int id) { return (T) view.findViewById(id); } /** Simpler version of {@link Activity#findViewById(int)} which infers the target type. */ @SuppressWarnings({ "unchecked", "UnusedDeclaration" }) // Checked by runtime cast. Public API. @CheckResult public static <T extends View> T findById(@NonNull Activity activity, @IdRes int id) { return (T) activity.findViewById(id); } /** Simpler version of {@link Dialog#findViewById(int)} which infers the target type. */ @SuppressWarnings({ "unchecked", "UnusedDeclaration" }) // Checked by runtime cast. Public API. @CheckResult public static <T extends View> T findById(@NonNull Dialog dialog, @IdRes int id) { return (T) dialog.findViewById(id); }}
这个类中主要做了两件事情:
- 传入当前对象,不管是Activity,fragment或者dialog全都行,寻找我们刚才生成的代理类
- 强制转换接口为统一接口,并调用接口提供的方法。
至此,一个简化版的findViewById就算是完成了,可以省略代码写控件初始化了。。。。
- Android Butterknife浅分析
- Android 注解工具ButterKnife源码分析
- Android 源码分析 ButterKnife框架原理
- android ButterKnife
- Android ButterKnife
- Android ButterKnife
- Android Butterknife
- Android Butterknife
- Android Butterknife 框架源码解析(3)——Butterknife 8.7.0源码分析
- ButterKnife + ButterKnife Zelezny(Android Studio)
- Android 注解框架 Butterknife的核心代码分析笔记
- Android ButterKnife 注解框架的使用详解和原理分析
- android注解Butterknife的使用及代码分析
- Android UI注解框架 ButterKnife源码及原理分析
- ButterKnife源码分析
- ButterKnife源码分析
- ButterKnife源码分析
- ButterKnife源码分析
- 一些超级基础&常用的opencv功能
- ceil() 与 floor() 与 round()
- intellij 报inspects a maven model for resolution problems
- Django信号系统简介
- java.lang.IllegalArgumentException: Control character in cookie value or attribute.
- Android Butterknife浅分析
- Zedboard上运行Linaro系统(三):编译内核和设备树
- GDB十分钟教程
- spring-boot-mybatis 学习资料整理
- mysql压缩包的实际安装记录
- php环境变量:$ENV
- POJ-1608-Parencodings
- Java设计模式—— 观察者模式
- EasyUI数据表格之二