
ButterKniffe项目地址:https://github.com/JakeWharton/butterknife.git 项目基本分为两个最大的模块组成 * butterkniffe:用于项目运行时调用,为参数进行赋值操作 * butterkniffe-annotations、butterknife-annotations:用于执行代码注解,在编译时调用,java版本的butterkniffe将在build的apt目录生成相应的注解文件***_ViewBinder.java 注解部分 注解部分,入口为:butterknife.compiler.ButterKnifeProcessor.java文件 @AutoService(Processor.class) public final class ButterKnifeProcessor extends AbstractProcessor { public synchronized void init(ProcessingEnvironment env){ } public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { } } 注解入口,告诉编译器这个类为注解类,将在编译时执行。AbstractProcessor为虚处理器,所有编译时注解处理器都必须继承自AbstactProcessor。在它被注解工具调用时,首先执行的是init函数,传入处理环境相关的参数。process函数为当前注解处理器的主函数,在这里可以处理所有注解代码。 @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); String sdk = env.getOptions().get(OPTION_SDK_INT); if (sdk != null) { try { this.sdk = Integer.parseInt(sdk); } catch (NumberFormatException e) { env.getMessager() .printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '" + sdk + "'. Falling back to API 1 support."); } } debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE)); elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); try { trees = Trees.instance(processingEnv); } catch (IllegalArgumentException ignored) { } } 在init函数中,butterkniffe做了环境检查和参数初始化的工作 接下来,进入了主处理函数process中,butterkniffe的process代码如下: @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk, debuggable); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; } 从代码中可以提现,主处理函数一共做了两个事情:扫描所有文件中的注解使用情况,将这些事情情况保存在一个Map中,然后统一使用JavaFile生成注解类。 扫描所有文件中的注解使用 首先看butterkniffe是如何扫描所有文件中的注解使用情况的,以下是process函数的第一行,findAndParseTargets函数 private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); scanForRClasses(env); // Process each @BindAnim element. for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) { //parse BindAnim to builderMap and erasedTargetNames } // Process each @BindArray element. for (Element element : env.getElementsAnnotatedWith(BindArray.class)) { //parse BindArray to builderMap and erasedTargetNames } // Process each @BindBitmap element. for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) { //parse BindBitmap to builderMap and erasedTargetNames } // Process each @BindBool element. for (Element element : env.getElementsAnnotatedWith(BindBool.class)) { //parse BindBool to builderMap and erasedTargetNames } // Process each @BindColor element. for (Element element : env.getElementsAnnotatedWith(BindColor.class)) { //parse BindColor to builderMap and erasedTargetNames } // Process each @BindDimen element. for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) { //parse BindDimen to builderMap and erasedTargetNames } // Process each @BindDrawable element. for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) { //parse BindDrawable to builderMap and erasedTargetNames } // Process each @BindFloat element. for (Element element : env.getElementsAnnotatedWith(BindFloat.class)) { //parse BindFloat to builderMap and erasedTargetNames } // Process each @BindFont element. for (Element element : env.getElementsAnnotatedWith(BindFont.class)) { //parse BindFont to builderMap and erasedTargetNames } // Process each @BindInt element. for (Element element : env.getElementsAnnotatedWith(BindInt.class)) { //parse BindInt to builderMap and erasedTargetNames } // Process each @BindString element. for (Element element : env.getElementsAnnotatedWith(BindString.class)) { //parse BindString to builderMap and erasedTargetNames } // Process each @BindView element. for (Element element : env.getElementsAnnotatedWith(BindView.class)) { //parse BindView to builderMap and erasedTargetNames } // Process each @BindViews element. for (Element element : env.getElementsAnnotatedWith(BindViews.class)) { //parse BindViews to builderMap and erasedTargetNames } // Process each annotation that corresponds to a listener. for (Class<? extends Annotation> listener : LISTENERS) { findAndParseListener(env, listener, builderMap, erasedTargetNames); } // Associate superclass binders with their subclass binders. This is a queue-based tree walk // which starts at the roots (superclasses) and walks to the leafs (subclasses). Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet()); Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>(); while (!entries.isEmpty()) { Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst(); TypeElement type = entry.getKey(); BindingSet.Builder builder = entry.getValue(); TypeElement parentType = findParentType(type, erasedTargetNames); if (parentType == null) { bindingMap.put(type, builder.build()); } else { BindingSet parentBinding = bindingMap.get(parentType); if (parentBinding != null) { builder.setParent(parentBinding); bindingMap.put(type, builder.build()); } else { // Has a superclass binding but we haven't built it yet. Re-enqueue for later. entries.addLast(entry); } } } return bindingMap; } 这段代码主要做了三件事情: 1. 通过scanForRClass搜索资源文件,通过扫描资源文件,将View和id绑定,在避免控件重复绑定时被使用 2. 对支持的注解进行循环解析,如“Element element : env.getElementsAnnotatedWith(BindView.class)”,就是获取所有BindView类型的注解 3. 检测是否包含父类的注解,如果存在,将父类和子类的注解合并,代码从Deque处起始,先通过findParentType来判断是否存在父类,如果有,使用setParent设置,findParentType通过TypeMirror类获取父类信息 以parseBindView来看一下具体绑定操作是如何进行的 private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,Set<TypeElement> erasedTargetNames) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Start by verifying common generated code restrictions. boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element); // Verify that the target type extends from View. TypeMirror elementType = element.asType(); if (elementType.getKind() == TypeKind.TYPEVAR) { TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound(); } Name qualifiedName = enclosingElement.getQualifiedName(); Name simpleName = element.getSimpleName(); if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) { if (elementType.getKind() == TypeKind.ERROR) { note(element, "@%s field with unresolved type (%s) " + "must elsewhere be generated as a View or interface. (%s.%s)", BindView.class.getSimpleName(), elementType, qualifiedName, simpleName); } else { error(element, "@%s fields must extend from View or be an interface. (%s.%s)", BindView.class.getSimpleName(), qualifiedName, simpleName); hasError = true; } } if (hasError) { return; } // Assemble information on the field. int id = element.getAnnotation(BindView.class).value(); BindingSet.Builder builder = builderMap.get(enclosingElement); QualifiedId qualifiedId = elementToQualifiedId(element, id); if (builder != null) { String existingBindingName = builder.findExistingBindingName(getId(qualifiedId)); if (existingBindingName != null) { error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)", BindView.class.getSimpleName(), id, existingBindingName, enclosingElement.getQualifiedName(), element.getSimpleName()); return; } } else { builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString(); TypeName type = TypeName.get(elementType); boolean required = isFieldRequired(element); builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required)); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement); } 首先,在isInaccessibleViaGengerateCode中检测变量有操作权限,不允许为private或static,不允许非类成员变量,不允许使用private class。 然后在isBindingInWrongPackage检测包头,不允许以android.或java.起始的包出现 接下来,检测变量类型是否正确,是否为view类型 继续,将变量名,类名,是否包含其它注解保存到map中 生成注解文件 for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk, debuggable); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } 遍历解析注解生成的map,使用JavaFile类进行文件生成。生成代码位于brewJava中 // brewJava JavaFile brewJava(int sdk, boolean debuggable) { return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable)) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); } brewJava继续调用createType函数,位于同类中,返回TypeSpec private TypeSpec createType(int sdk, boolean debuggable) { TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC); if (isFinal) { result.addModifiers(FINAL); } if (parentBinding != null) { result.superclass(parentBinding.bindingClassName); } else { result.addSuperinterface(UNBINDER); } if (hasTargetField()) { result.addField(targetTypeName, "target", PRIVATE); } if (isView) { result.addMethod(createBindingConstructorForView()); } else if (isActivity) { result.addMethod(createBindingConstructorForActivity()); } else if (isDialog) { result.addMethod(createBindingConstructorForDialog()); } if (!constructorNeedsView()) { // Add a delegating constructor with a target type + view signature for reflective use. result.addMethod(createBindingViewDelegateConstructor()); } result.addMethod(createBindingConstructor(sdk, debuggable)); if (hasViewBindings() || parentBinding == null) { result.addMethod(createBindingUnbindMethod(result)); } return result.build(); } 处理流程大致为,创建注解类,检测是否存在父类,如果有,设置之。以及添加设置相应的全局变量和成员函数(主要是构造函数和unbind) 参考一个生成构造函数的函数createBindingConstructor private MethodSpec createBindingConstructor(int sdk, boolean debuggable) { MethodSpec.Builder constructor = MethodSpec.constructorBuilder() .addAnnotation(UI_THREAD) .addModifiers(PUBLIC); if (hasMethodBindings()) { constructor.addParameter(targetTypeName, "target", FINAL); } else { constructor.addParameter(targetTypeName, "target"); } if (constructorNeedsView()) { constructor.addParameter(VIEW, "source"); } else { constructor.addParameter(CONTEXT, "context"); } if (hasUnqualifiedResourceBindings()) { // Aapt can change IDs out from underneath us, just suppress since all will work at runtime. constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType") .build()); } if (hasOnTouchMethodBindings()) { constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT) .addMember("value", "$S", "ClickableViewAccessibility") .build()); } if (parentBinding != null) { if (parentBinding.constructorNeedsView()) { constructor.addStatement("super(target, source)"); } else if (constructorNeedsView()) { constructor.addStatement("super(target, source.getContext())"); } else { constructor.addStatement("super(target, context)"); } constructor.addCode("\n"); } if (hasTargetField()) { constructor.addStatement("this.target = target"); constructor.addCode("\n"); } if (hasViewBindings()) { if (hasViewLocal()) { // Local variable in which all views will be temporarily stored. constructor.addStatement("$T view", VIEW); } for (ViewBinding binding : viewBindings) { addViewBinding(constructor, binding, debuggable); } for (FieldCollectionViewBinding binding : collectionBindings) { constructor.addStatement("$L", binding.render(debuggable)); } if (!resourceBindings.isEmpty()) { constructor.addCode("\n"); } } if (!resourceBindings.isEmpty()) { if (constructorNeedsView()) { constructor.addStatement("$T context = source.getContext()", CONTEXT); } if (hasResourceBindingsNeedingResource(sdk)) { constructor.addStatement("$T res = context.getResources()", RESOURCES); } for (ResourceBinding binding : resourceBindings) { constructor.addStatement("$L", binding.render(sdk)); } } return constructor.build(); } 真正执行对资源的绑定,如果是view类型的注解,继续调用addViewBinding依次对资源绑定 private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) { if (binding.isSingleFieldBinding()) { // Optimize the common case where there's a single binding directly to a field. FieldViewBinding fieldBinding = binding.getFieldBinding(); CodeBlock.Builder builder = CodeBlock.builder() .add("target.$L = ", fieldBinding.getName()); boolean requiresCast = requiresCast(fieldBinding.getType()); if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) { if (requiresCast) { builder.add("($T) ", fieldBinding.getType()); } builder.add("source.findViewById($L)", binding.getId().code); } else { builder.add("$T.find", UTILS); builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView"); if (requiresCast) { builder.add("AsType"); } builder.add("(source, $L", binding.getId().code); if (fieldBinding.isRequired() || requiresCast) { builder.add(", $S", asHumanDescription(singletonList(fieldBinding))); } if (requiresCast) { builder.add(", $T.class", fieldBinding.getRawType()); } builder.add(")"); } result.addStatement("$L", builder.build()); return; } List<MemberViewBinding> requiredBindings = binding.getRequiredBindings(); if (!debuggable || requiredBindings.isEmpty()) { result.addStatement("view = source.findViewById($L)", binding.getId().code); } else if (!binding.isBoundToRoot()) { result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS, binding.getId().code, asHumanDescription(requiredBindings)); } // 执行对变量赋值 addFieldBinding(result, binding, debuggable); // 执行对事件绑定方法的注解 addMethodBindings(result, binding, debuggable); } 调用部分 代码位于butterKniffe包下的ButterKnife.java文件中,通过查看源码得知,所有bind函数的调用,统一归纳到createBinding(context, view)调用中。 private static Unbinder createBinding(@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); } 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); } } createBinding继续递归调用findBindingConstructorForClass @Nullable @CheckResult @UiThread private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls); if (bindingCtor != null) { if (debug) Log.d(TAG, "HIT: Cached in binding map."); return bindingCtor; } String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.")) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return null; } try { 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."); } 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; } 在此函数中,以类名+”_ViewBinding”的方式找到注解类,并调用它的构造函数,如果注解类存在父类,依次向上调用。 尾 只是粗浅的看懂了代码而已,butterkniffe代码难懂之处在于对AbstractProcessor和JavaFile不清楚,对于整体架构和代码结构来说,复杂程度不算很高,理解难度也在可接受的范围之内
kotlin调用ButterKnife (gradle更新后,ButterKnife不再支持,Kotlin使用的库KotterKnife等待发布) (KotterKnife:https://github.com/JakeWharton/kotterknife) // app/build.gradle中添加相关配置引入ButterKnifer dependencies { implementation 'com.jakewharton:butterknife:8.8.1' kapt 'com.jakewharton:butterknife-compiler:8.8.1' } // kotlin中添加相关注解方式如下 @BindView(R.id.xxxxxx) lateinit var view:View // 同java,使用注解 ButterKnife.bind(this) // activity为例,其它类比java kotlin多重嵌套向上调用 class A { private var m:Int = 0 inner class B { private var m:Int = 1 inner class C { private var m:Int = 2 fun callFunc{ this.m // 2 this@B.m // 1 this@A.m // 0 } } } } // 关键字inner,内部类没有加inner前缀,多重内部类无法向上调用 // 适用于隐形内部类 kotlin隐形创建接口对象时,提示没有构造函数的问题。no construct // 错误示例 interface iFace{ ..... } var fc = iFace{....} // interface iFace does not have constructors // 使用方法 interface iFace{ ..... } var fc = object : iFace{....} // interface iFace does not have constructors kotlin使用mutablelist可写列表 与java不同,kotlin分为只读列表List和可读写列表MutableList var readOnlyList : List<T> = emptyList() var readOnlyList : List<T> = listOf(t1, t2, t3, t4) // 只读列表 var readWriteList : MutableList<T> = mutableListof() // 可读写列表 var readWriteList : MutableList<T> = mutableListOf<T>() 坑列表会持续更新。。。。
CentOS 7.0防火墙配置参数说明 启动停止 获取firewall状态 systemctl status firewalld.service firewall-cmd --state 开启停止防火墙 开机启动:systemctl enable firewalld.service 启动:systemctl start firewalld.service 停止:systemctl stop firewalld.service 禁止开机启动:systemctl disable firewalld.service 开放端口 firewall-cmd --zone=public --add-port=80/tcp --permanent –zone #作用域 –add-port=80/tcp #添加端口,格式为:端口号/协议 –permanent #永久生效,没有此参数重启后失效 禁用端口 firewall-cmd --zone=public --remove-port=80/tcp --permanent 应用修改 firewall-cmd --reload 查看所有开放的端口 firewall-cmd --zone=dmz --list-ports 其它参数说明 1. firewall-cmd --state ##查看防火墙状态,是否是running 2. firewall-cmd --reload ##重新载入配置,比如添加规则之后,需要执行此命令 3. firewall-cmd --get-zones ##列出支持的zone 4. firewall-cmd --get-services ##列出支持的服务,在列表中的服务是放行的 5. firewall-cmd --query-service ftp ##查看ftp服务是否支持,返回yes或者no 6. firewall-cmd --add-service=ftp ##临时开放ftp服务 7. firewall-cmd --add-service=ftp --permanent ##永久开放ftp服务 8. firewall-cmd --remove-service=ftp --permanent ##永久移除ftp服务 9. iptables -L -n ##查看规则,这个命令是和iptables的相同的
mLocalFrameLayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLocalX = mLocalFrameLayout.getX() - event.getRawX(); mLocalY = mLocalFrameLayout.getY() - event.getRawY(); break; case MotionEvent.ACTION_MOVE: mLocalFrameLayout.animate() .x(event.getRawX() + mLocalX) .y(event.getRawY() + mLocalY) .setDuration(0) .start(); break; default: return false; } return true; } });
RGB 转换成 YUV Y = (0.257 * R) + (0.504 * G) + (0.098 * B) + 16 Cr = V = (0.439 * R) - (0.368 * G) - (0.071 * B) + 128 Cb = U = -( 0.148 * R) - (0.291 * G) + (0.439 * B) + 128 YUV 转换成 RGB B = 1.164(Y - 16) + 2.018(U - 128) G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128) R = 1.164(Y - 16) + 1.596(V - 128) 上面公式引用自 arm-linux 的博客,但是经过测试,RGB转YUV时,UV顺序应该对调,正确公式为: RGB 转换成 YUV Y = (0.257 * R) + (0.504 * G) + (0.098 * B) + 16 U = (0.439 * R) - (0.368 * G) - (0.071 * B) + 128 V = -( 0.148 * R) - (0.291 * G) + (0.439 * B) + 128