Introduction to ButterKnife and its principles (6) butterknife
I analyzed the source code of ButterKnife and understood its implementation principle. Then I will apply the principle to practice.
I. Custom annotations
Only BindView annotations are provided for ease of understanding.
@Target(ElementType.FIELD)@Retention(RetentionPolicy.CLASS)public @interface BindView { int value();}2. Add annotation Processors
Add the ViewInjectProcessor annotation processor and read the code,
public class ViewInjectProcessor extends AbstractProcessor { // Storing all the annotation information under the same Class Map
classMap = new HashMap<>(); private Filer mFiler; Elements elementUtils; private Messager mMessager; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); mFiler = processingEnv.getFiler(); elementUtils = processingEnv.getElementUtils(); mMessager = processingEnv.getMessager(); } @Override public Set
getSupportedAnnotationTypes() { return super.getSupportedAnnotationTypes(); } @Override public SourceVersion getSupportedSourceVersion() { return super.getSupportedSourceVersion(); } @Override public boolean process(Set
annotations, RoundEnvironment roundEnv) { // collect information gatherInformation(roundEnv); // generate java code try { for (BindingClass bindingClass : classMap.values()) { info("Generating file for %s", bindingClass.getFullClassName()); bindingClass.brewJava().writeTo(mFiler); } } catch (IOException e) { e.printStackTrace(); error("Generate file failed, reason: %s", e.getMessage()); } return true; } ......}
The init, getSupportedAnnotationTypes, getSupportedSourceVersion, and process methods are implemented respectively.
The process method mainly implements the gatherInformation method for collecting information and the brewJava method for generating Java code.
For details, refer to the gatherInformation method;
private void gatherInformation(RoundEnvironment roundEnv) { classMap.clear(); gatherBindView(roundEnv); } private void gatherBindView(RoundEnvironment roundEnv) { Set
elements = roundEnv.getElementsAnnotatedWith(BindView.class); for (Element element : elements) { BindingClass bindingClass = getBindingClass(element); BindViewField field = new BindViewField(element); bindingClass.addField(field); } }
A BindingClass class is defined here. Let's take a look at its implementation;
Public class BindingClass {public TypeElement mClassElement; // class name public List
MFields; // member variable public Elements mElementUtils; public BindingClass (TypeElement classElement, Elements elementUtils) {this. mClassElement = classElement; this. mElementUtils = elementUtils; mFields = new ArrayList <> ();} public String getFullClassName () {return mClassElement. getQualifiedName (). toString ();} public void addField (BindViewField field) {mFields. add (field);} private String getPackageName (TypeElement type) {return mElementUtils. getPackageOf (type ). getQualifiedName (). toString ();} private static String getClassName (TypeElement type, String packageName) {int packageLen = packageName. length () + 1; return type. getQualifiedName (). toString (). substring (packageLen ). replace ('. ',' $ ');} public JavaFile brewJava () {// method inject (final T host, Object source, Provider provider) ClassName FINDER = ClassName. get ("com. muse. api. finder "," Finder "); MethodSpec. builder injectMethodBuilder = MethodSpec. methodBuilder ("inject "). addModifiers (Modifier. PUBLIC ). addAnnotation (Override. class ). addParameter (TypeName. get (mClassElement. asType (), "target", Modifier. FINAL ). addParameter (TypeName. OBJECT, "source "). addParameter (FINDER, "finder"); // field for (BindViewField field: mFields) {injectMethodBuilder. addStatement ("target. $ N = ($ T) (finder. findView (source, $ L) ", field. getFieldName (), ClassName. get (field. getFieldType (), field. getResId ();} String packageName = getPackageName (mClassElement); String className = getClassName (mClassElement, packageName); ClassName bindingClassName = ClassName. get (packageName, className); ClassName INJECTOR = ClassName. get ("com. muse. api "," Injector "); // generate whole class TypeSpec finderClass = TypeSpec. classBuilder (bindingClassName. simpleName () + "$ ViewInjector "). addModifiers (Modifier. PUBLIC ). addSuperinterface (ParameterizedTypeName. get (INJECTOR, TypeName. get (mClassElement. asType ()))). addMethod (injectMethodBuilder. build ()). build (); return JavaFile. builder (packageName, finderClass ). build ();}}
Obviously, the design of this class refers to the BindingSet design in the source code, but the design mode is not used.
3. Provide APIs
Provide InjectHelper externally. The Code is as follows:
public class InjectHelper { private static final String SUFFIX = "$$ViewInjector"; private static final ActivityFinder ACTIVITY_FINDER = new ActivityFinder(); private static final Map
FINDER_MAP = new HashMap<>(); public static void inject(Activity host) { inject(host, host, ACTIVITY_FINDER); } public static void inject(Object host, Object source, Finder finder) { String className = host.getClass().getName(); try { Injector injector = FINDER_MAP.get(className); if (injector == null) { String classFullName = host.getClass().getName() + SUFFIX; Class
finderClass = Class.forName(classFullName); injector = (Injector) finderClass.newInstance(); FINDER_MAP.put(className, injector); } injector.inject(host, source, finder); } catch (Exception e) { throw new RuntimeException("Unable to inject for " + className, e); } }}
For other classes involved in the API, refer to the source code.
Iv. Framework
Dependencies are required when applications are used.
implementation project(':ioc-api') annotationProcessor project(':ioc-compiler') implementation project(':ioc-annotation')
The Code is as follows:
public class MainActivity extends AppCompatActivity { @BindView(R.id.test_btn) Button testBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); InjectHelper.inject(this); testBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, "Button is bind", Toast.LENGTH_SHORT).show(); } }); }}
The Demo program runs as follows:
The use and source of ButterKnife is now complete.