Android AOP三劍客之APT
AOP概念
AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
Android AOP三劍客: APT, AspectJ, Javassist

這是一張用爛了的圖.jpg
專案地址: ofollow,noindex">android-aop-samples
工程目錄結構

.
- annotation 定義註解
- apt-library apt輔助工具類
- apt-processor 自定義註解解析器,生成輔助程式碼
- apt-plugin 自定義Gradle外掛,實現切面和操作位元組碼的外掛
- app 主目錄
Android APT
APT(Annotation Processing Tool 的簡稱),可以在程式碼編譯期解析註解,並且生成新的 Java 檔案,減少手動的程式碼輸入。現在有很多主流庫都用上了 APT,比如 Dagger2, ButterKnife, EventBus3 等
定義註解
@Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface BindView { int value(); }
建立一個Module,自定義AbstractProcessor,並且用@AutoService標記
-
AutoService會自動在META-INF資料夾下生成Processor配置資訊檔案,該檔案裡就是實現該服務介面的具體實現類。而當外部程式裝配這個模組的時候,
就能通過該jar包META-INF/services/裡的配置檔案找到具體的實現類名,並裝載例項化,完成模組的注入
@AutoService(Processor.class) public class BindViewProcessor extends AbstractProcessor { private Messager mMessager; private Elements mElementUtils; private Map<String, ClassFactory> mProxyMap = new HashMap<>(); @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); mMessager = processingEnv.getMessager(); mElementUtils = processingEnv.getElementUtils(); } @Override public Set<String> getSupportedAnnotationTypes() { HashSet<String> supportTypes = new LinkedHashSet<>(); supportTypes.add(BindView.class.getCanonicalName()); return supportTypes; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { mMessager.printMessage(Diagnostic.Kind.NOTE, "processing..."); mProxyMap.clear(); //得到所有的註解 Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class); for (Element element : elements) { VariableElement variableElement = (VariableElement) element; TypeElement classElement = (TypeElement) variableElement.getEnclosingElement(); String fullClassName = classElement.getQualifiedName().toString(); //elements的資訊儲存到mProxyMap中 ClassFactory proxy = mProxyMap.get(fullClassName); if (proxy == null) { proxy = new ClassFactory(mElementUtils, classElement); mProxyMap.put(fullClassName, proxy); } BindView bindAnnotation = variableElement.getAnnotation(BindView.class); int id = bindAnnotation.value(); proxy.putElement(id, variableElement); } //通過javapoet生成 for (String key : mProxyMap.keySet()) { ClassFactory proxyInfo = mProxyMap.get(key); JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode()).build(); try { // 生成檔案 javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } } mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ..."); return true; }
}
process方法,處理我們自定義的註解,生成程式碼,這裡使用的squareup公司的javapoet庫輔助生成程式碼
public class ClassFactory { private String mBindingClassName; private String mPackageName; private TypeElement mTypeElement; private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>(); public ClassFactory(Elements elementUtils, TypeElement classElement) { this.mTypeElement = classElement; PackageElement packageElement = elementUtils.getPackageOf(mTypeElement); String packageName = packageElement.getQualifiedName().toString(); String className = mTypeElement.getSimpleName().toString(); this.mPackageName = packageName; this.mBindingClassName = className + "_ViewBinding"; } public void putElement(int id, VariableElement element) { mVariableElementMap.put(id, element); } public TypeSpec generateJavaCode() { TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName) .addModifiers(Modifier.PUBLIC) .addMethod(generateMethods()) .build(); return bindingClass; } private MethodSpec generateMethods() { ClassName activity = ClassName.bestGuess(mTypeElement.getQualifiedName().toString()); MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind") .addModifiers(Modifier.PUBLIC) .returns(void.class) .addParameter(activity, "activity"); for (int id : mVariableElementMap.keySet()) { VariableElement element = mVariableElementMap.get(id); String name = element.getSimpleName().toString(); String type = element.asType().toString(); methodBuilder.addCode("activity." + name + " = " + "(" + type + ")(((android.app.Activity)activity).findViewById( " + id + "));\n\n"); } return methodBuilder.build(); } public String getPackageName() { return mPackageName; } }
generateMethods方法通過for迴圈構建程式碼,findViewById繫結view
我們先看下原本的MainActivity有什麼東東
public class MainActivity extends AppCompatActivity { @BindView(R.id.button) Button button; @BindView(R.id.tv) TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); BindViewTools.bind(this); textView.setText("bind Button success"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { doMarkDown(); } }); } }
這個BindViewTools.bind(this)傳送門進去看看
public class BindViewTools { public static void bind(Activity activity) { Class clazz = activity.getClass(); try { Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding"); Method method = bindViewClass.getMethod("bind", activity.getClass()); method.invoke(bindViewClass.newInstance(), activity); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
}
我們可以看到他是通過反射尋找的是MainActivity_ViewBinding的類,並進行Id繫結的
可以在build/intermediates/classes/debug/包名/MainActivity_ViewBinding.class看到生成的程式碼

這下就很清晰了
1.首先定義註解
2.BindViewProcessor裡的process 解析註解,生成輔助類MainActivity_ViewBinding
3.在入口BindViewTools.bind(this),反射找到生成的MainActivity_ViewBinding類並進行findViewById。
總結
本章節主要說了APT的簡單使用,從使用角度來說,APT技術並沒有難度,重點是怎麼設計,在實際專案中可以把很多繁瑣重複性的工作,通過APT來生成各種程式碼,作為老司機,這是彎道超車的必備祕籍,天下武功、唯快不破!