編輯:關於Android編程
上兩篇我們分析完了處理器的process方法的findAndParseTargets方法來獲取了一個集合,該集合包含了你使用注解的類的TypeElement和這個類中的注解的實例BindingClass。
我們再看下處理器的核心方法 process方法
@Override public boolean process(Set elements, RoundEnvironment env) { //這個方法查找所有的注解信息,並形成BindingClass集合 MaptargetClassMap = findAndParseTargets(env); //遍歷生存生成java 文件 for (Map.Entry entry : targetClassMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingClass bindingClass = entry.getValue(); JavaFile javaFile = bindingClass.brewJava(); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } //表示處理器正確處理了 return true; }
我們先看一下BindingClass的一些成員變量。這裡我們主要是看關鍵的一些集合
//這個是BindView 注解在findAndParseTargets 方法解析後 形成ViewBindings實體通過該類的addxx方法添加到viewIdMap集合的對象 private final MapviewIdMap = new LinkedHashMap<>(); //這個是BindViews 注解 在findAndParseTargets 方法解析後 形成的. BindViews用法: @BindViews({ R2.id.title, R2.id.subtitle, R2.id.hello }) List headerViews; //key:FieldCollectionViewBinding 為實體,包含了名字,類型等信息,大家可以看下源碼,這裡就不說了就是一個java-bean ,你可以這麼理解。 //value: 就是這些id: R2.id.title, R2.id.subtitle, R2.id.hello private final Map > collectionBindings = new LinkedHashMap<>(); //該集合是BindBitmap注解在findAndParseTargets 注解在findAndParseTargets 方法解析後 形成FieldBitmapBinding實體通過該類的addxx方法添加到bitmapBindings集合的對象 private final List bitmapBindings = new ArrayList<>(); //這個就是BindDrawable注解.... private final List drawableBindings = new ArrayList<>(); //這個就是BindFloat,BindInt,BindString... 這些注解.... private final List resourceBindings = new ArrayList<>();
上面的addxxx方法類似下面的,當然還有其它的就不貼出了
void addFieldCollection(Listids, FieldCollectionViewBinding binding) { collectionBindings.put(binding, ids); } boolean addMethod( Id id, ListenerClass listener, ListenerMethod method, MethodViewBinding binding) { ViewBindings viewBindings = getOrCreateViewBindings(id); if (viewBindings.hasMethodBinding(listener, method) && !"void".equals(method.returnType())) { return false; } viewBindings.addMethodBinding(listener, method, binding); return true; } void addResource(FieldResourceBinding binding) { resourceBindings.add(binding); } //......
至此我們算是完成了一個大步走,我們把所有的注解 方法等放倒了一個集合裡,生成java文件的時候就可以遍歷這些集合了,從而形成有一定規律的java源文件。
形成的文件就是:
這裡我們拿官方的demo-SimpleActivity
編譯完後最後生成的文件為:SimpleActivity_ViewBinding
離目標越來越近了。哈哈
生成.java文件你可以用字符串拼接的笨方法,但是我們的jake大神怎麼可能那麼弱。人家自己又寫了一個庫。javapoet ,66666
compile 'com.squareup:javapoet:1.7.0'
這個庫可以很方便的生成我們想要的java文件。
在findAndParseTargets方法
//... //遍歷生存生成java 文件 for (Map.Entryentry : targetClassMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingClass bindingClass = entry.getValue(); JavaFile javaFile = bindingClass.brewJava(); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } }
遍歷targetClassMap集合,調用每一個類的brewJava()方法,最後返回JavaFile對象,再通過writeTo方法生成java文件。
接下來我們就看下bindingClass.brewJava()方法。
//我們可以看到javapoet這個開源庫使用了設計模式-builder模式。 // 我們看源碼就是看這些模式啦,思想啦,才能提高自己的代碼編寫能力 //bug少了才有時間干別的呀,哈哈 。。。你懂我的 JavaFile brewJava() { //參數1:包名 //參數2:TypeSpec,這個可以生成class ,interface 等java文件 return JavaFile.builder(bindingClassName.packageName(), createBindingClass()) //注釋 .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); }
主要步驟
1. 生成類名
2. 生成構造函數
3. 生成unbind方法。
private TypeSpec createBindingClass() { 1. 生成類名 //bindingClassName.simpleName() 就是我們在findAndParseTargets方法形成的類的名字:xxx_ViewBinding名字 xxx也就是使用butterknife的類名 TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) //增加類修飾符public .addModifiers(PUBLIC); //使用butterknife的類是 final類 TypeName targetType; if (isFinal) { //增加類修飾符FINAL result.addModifiers(FINAL); targetType = targetTypeName; } else { //不是final類,增加泛型參數T targetType = TypeVariableName.get("T"); result.addTypeVariable(TypeVariableName.get("T", targetTypeName)); } //如果有父類直接繼承 if (hasParentBinding()) { result.superclass(ParameterizedTypeName.get(getParentBinding(), targetType)); } else { //增加實現接口 result.addSuperinterface(UNBINDER); //增加成員變量 result.addField(targetType, "target", isFinal ? PRIVATE : PROTECTED); } //到這裡生成的代碼如下: //比如在activity public class SimpleActivity_ViewBindingimplements Unbinder { protected T target; } //比如在adapter的viewholder中 public final class SimpleAdapter$ViewHolder_ViewBinding implements Unbinder { private SimpleAdapter.ViewHolder target; } 2. 生成構造函數 if (!bindNeedsView()) { // Add a delegating constructor with a target type + view signature for reflective use. result.addMethod(createBindingViewDelegateConstructor(targetType)); } result.addMethod(createBindingConstructor(targetType)); 3. 生成unbind方法。 if (hasViewBindings() || !hasParentBinding()) { result.addMethod(createBindingUnbindMethod(result, targetType)); } return result.build(); }
代碼裡注釋的比較詳細了
主要是createBindingConstructor 方法,主要是對成員變量賦值,以及設置監聽事件。先看下javapoet提供的幾個方法或類:
這裡主要是生成構造函數,當然也可以生成其它的普通方法,構造函數也是方法的一種嗎。
通過constructorBuilder構造出一個方法,通過addAnnotation添加注解,通過addModifiers添加修飾符。
MethodSpec.Builder constructor = MethodSpec.constructorBuilder() .addAnnotation(UI_THREAD) .addModifiers(PUBLIC);
通過如下方法添加參數targetType為參數類型,”target”為參數的變量名
constructor.addParameter(targetType, "target");
通過如下方法添加代碼語句
第一參數是String類型,可以有占位符
第二個參數Object… args,類型,多參數不固定。
就像你平時使用String.format()方法一樣的意思.
constructor.addStatement("this.target = target");
這裡主要是對使用了如下的代碼進行一個賦值操作。
@BindView(R2.id.title) TextView title; @OnClick(R2.id.hello) void sayHello() {}
這裡我們主要看一下關鍵方法,因為都是類似的拼接代碼字符串。
我們看一下變量的賦值:
private void addViewBindings(MethodSpec.Builder result, ViewBindings bindings) { if (bindings.isSingleFieldBinding()) { // Optimize the common case where there's a single binding directly to a field. FieldViewBinding fieldBinding = bindings.getFieldBinding(); CodeBlock.Builder builder = CodeBlock.builder() .add("target.$L = ", fieldBinding.getName()); boolean requiresCast = requiresCast(fieldBinding.getType()); if (!requiresCast && !fieldBinding.isRequired()) { builder.add("source.findViewById($L)", bindings.getId().code); } else { builder.add("$T.find", UTILS); builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView"); if (requiresCast) { builder.add("AsType"); } builder.add("(source, $L", bindings.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; } ListrequiredViewBindings = bindings.getRequiredBindings(); if (requiredViewBindings.isEmpty()) { result.addStatement("view = source.findViewById($L)", bindings.getId().code); } else if (!bindings.isBoundToRoot()) { result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS, bindings.getId().code, asHumanDescription(requiredViewBindings)); } addFieldBindings(result, bindings); addMethodBindings(result, bindings); }
主要是調用系統的findViewById 方法,但是你看到了findRequiredViewAsType,findRequiredView方法和castView方法,findRequiredView,findRequiredViewAsType是作者為樂代碼的書寫方便對findViewById的一層封裝,你可以看一下源碼,最後都會調用的findRequiredView方法的findViewById方法。
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."); }
這個castView是什麼方法呢?是Class類的方法,直接轉換為指定的類型
public staticT castView(View view, @IdRes int id, String who, Class 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); } }
說白了都是調用系統的方法。
好了到這裡成員變量的賦值算是完了。
注意一點target.title target就是我們的activity或者view ;也驗證了為什麼是使用了類似BindView注解不能是private修飾符的另一個原因了。
接下來是方法的監聽 private void addMethodBindings(MethodSpec.Builder result, ViewBindings bindings) {}方法,李 main 也是通過循環添加方法借助我們上文提到的
MethodSpec.methodBuilder構造器
for (ListenerMethod method : getListenerMethods(listener)) { MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder(method.name()) .addAnnotation(Override.class) .addModifiers(PUBLIC) .returns(bestGuess(method.returnType())); String[] parameterTypes = method.parameters(); for (int i = 0, count = parameterTypes.length; i < count; i++) { callbackMethod.addParameter(bestGuess(parameterTypes[i]), "p" + i); //... }
感興趣的可以根據生成的代碼來對照這查看,這裡就不多說了。
最後生成的如下所示的代碼。
@UiThread public SimpleActivity_ViewBinding(final T target, View source) { this.target = target; View view; target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class); target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class); view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'"); target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class); view2130968578 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.sayHello(); } }); view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View p0) { return target.sayGetOffMe(); } }); view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'"); target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class); view2130968579 = view; ((AdapterView) view).setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView p0, View p1, int p2, long p3) { target.onItemClick(p2); } }); target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class); target.headerViews = Utils.listOf( Utils.findRequiredView(source, R.id.title, "field 'headerViews'"), Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"), Utils.findRequiredView(source, R.id.hello, "field 'headerViews'")); }
createBindingUnbindMethod方法主要是把的成員變量啦,Listener等 置為空,比如setOnClickListener(null)。
private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass, TypeName targetType) { MethodSpec.Builder result = MethodSpec.methodBuilder("unbind") .addAnnotation(Override.class) .addModifiers(PUBLIC); if (!isFinal && !hasParentBinding()) { result.addAnnotation(CALL_SUPER); } boolean rootBindingWithFields = !hasParentBinding() && hasFieldBindings(); if (hasFieldBindings() || rootBindingWithFields) { result.addStatement("$T target = this.target", targetType); } if (!hasParentBinding()) { String target = rootBindingWithFields ? "target" : "this.target"; result.addStatement("if ($N == null) throw new $T($S)", target, IllegalStateException.class, "Bindings already cleared."); } else { result.addStatement("super.unbind()"); } if (hasFieldBindings()) { result.addCode("\n"); for (ViewBindings bindings : viewIdMap.values()) { if (bindings.getFieldBinding() != null) { result.addStatement("target.$L = null", bindings.getFieldBinding().getName()); } } for (FieldCollectionViewBinding fieldCollectionBinding : collectionBindings.keySet()) { result.addStatement("target.$L = null", fieldCollectionBinding.getName()); } } if (hasMethodBindings()) { result.addCode("\n"); for (ViewBindings bindings : viewIdMap.values()) { addFieldAndUnbindStatement(bindingClass, result, bindings); } } if (!hasParentBinding()) { result.addCode("\n"); result.addStatement("this.target = null"); } return result.build(); }
主要就是addStatement方法,上文已經說了,該方法的意思就是生成一句代碼,
第一參數是String類型,可以有占位符
第二個參數Object… args,類型,多參數不固定。
就像你平時使用String.format()方法一樣的意思.
比較簡單,最後生成的方法類似:
@Override public void unbind() { SimpleAdapter.ViewHolder target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); target.word = null; target.length = null; target.position = null; this.target = null; }
之前寫過一些關於TCP和UDP數據傳輸的代碼,比如使用TCP傳輸音視頻數據包,P2P打洞中使用UDP等。寫好之後就直接丟下了,沒有總結下都。最近准備找工作,再拿來溫習下。
解決方案:當前是根據當前問題場景即豎屏強制更改為橫屏的需求而做的改動,基本是hardcode定義的狀態,總共修改有效代碼行數5行,如果後續有其他需求或者需要更靈活的配置橫
安裝做 Android 安全測試之前你應該知道的工具 (一)分析./androlyze.py -s進入分析的交互界面然後執行apk,d,dx=AnalyzeAPK(&qu
Service既不是進程也不是線程,它們之間的關系如下:可能有的朋友會問了,既然是長耗時的操作,那麼Thread也可以完成啊。沒錯,在程序裡面很多耗時工作我們也可以通過T