# # # 1. The overview


This is the first phase of our content section sub-system architecture to share, I hope you can first go to understand the content of this phase: 2017Android advanced road with you. In the introduction of the connotation of the whole project when we also agreed to analyze the system source code design mode, the third party framework source code analysis, and then do their own bit by bit to create a set of connotation of the sub-framework. The content of this issue may be a bit troublesome for some of the buddies. If you feel abstract, please see the video explanation. If you’ve studied the Java background, this will be used a lot in the three major frameworks. Too much to explain is actually not interesting, we mainly see what is useful. Attached video interpretation address: http://pan.baidu.com/s/1kVFMRQJ # # # 2. Third party IOC framework source code analysis


Today’s main talk is Android IOC framework is to inject controls and layout or Settings click listening, if you have used xUtils, Afinal, Butterknife class framework, you are certainly not unfamiliar ~ we pick two do a comparison and source analysis, Let’s pick xUtils and Butterknife:

2.1 IOC annotations for xUtils

Xutils if we used it in the content of the will is more, including network, database, IOC injection, network using images, so we here mainly to see xutils3.0 IOC comments: https://github.com/wyouflf/xUtils3

public class MainActivity extends AppCompatActivity {

    @ViewInject(R.id.icon)
    private ImageView mIconIv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); x.view().inject(this); mIconIv.setImageResource(R.drawable.icon); } /** * 1. The method must be private qualified, * 2typeValue ={id1, ID2, ID3} * 4. Other parameters are explained {@ link org. Xutils. View. The annotation. The Event} class. * * / @ Event (value = R.i d.i con,type= the View. An OnClickListener. The class / * optional parameters, The default is View. An OnClickListener. Class * /) private void iconIvClick (View View) {Toast. MakeText (this,"Picture was clicked.", Toast.LENGTH_LONG).show(); }}Copy the code

2.2 IOC annotation source code analysis of xUtils

@override public void inject(Activity Activity) {// Get the Activity ContentView annotation Class<? > handlerType = activity.getClass(); ContentView = findContentView(handlerType); // Find the ContentView annotation in the Activity class.if(contentView ! = null) { int viewId = contentView.value();if(viewId > 0) {// If there is an annotation to get the value of layoutId, use reflection to call the activitysetThe ContentView Method injects the Method viewsetContentViewMethod = 
	                    handlerType.getMethod("setContentView", int.class);
                    setContentViewMethod.invoke(activity, viewId); } } } catch (Throwable ex) { LogUtil.e(ex.getMessage(), ex); } // Handle findViewById andsetOnclickListener annotation injectObject(Activity, handlerType, New ViewFinder(Activity)); }Copy the code
private static void injectObject(Object handler, Class<? > handlerType, ViewFinder finder) {//....... / / from the parent class to subclass recursive injectObject (handler, handlerType getSuperclass (), the finder); / / inject view injection control view Field [] fields. = handlerType getDeclaredFields ();if(fields ! = null && fields.length > 0) {for(Field Field: fields) {//...... ViewInject viewInject = field.getannotation (viewinject.class);if(viewInject ! View View = finder.findViewById(vieWinject.value (), View View = finder.findViewById(viewinject.value ()), viewInject.parentId());if(view ! = null) {// Use reflection to inject the View into this property.true);
                            field.set(handler, view);
                        } else{/ /... } } catch (Throwable ex) { LogUtil.e(ex.getMessage(), ex); }}}} / / end inject the view / / inject event into the event Method. [] the methods = handlerType getDeclaredMethods ();if(methods ! = null && methods.length > 0) {for(Method: methods) {//...... // Check if the current method is the method for the Event annotation. Event Event = method.getannotation (event.class);if(event ! = null) {try {// id parameter int[] values = event.value(); int[] parentIds = event.parentId(); int parentIdsLen = parentIds == null ? 0 : parentIds.length; // Loop through all ids, generate ViewInfo and add proxy reflection mainly using the dynamic proxy design patternfor (int i = 0; i < values.length; i++) {
                            int value = values[i];
                            if (value > 0) {
                                ViewInfo info = new ViewInfo();
                                info.value = value;
                                info.parentId = parentIdsLen > i ? parentIds[i] : 0;
                                method.setAccessible(true); / / EventListenerManager executes EventListenerManager method. The dynamic proxy addEventMethod (finder, info, the event handler, method); } } } catch (Throwable ex) { LogUtil.e(ex.getMessage(), ex); } } } } // end inject event }Copy the code

The key source code is probably so much, the dynamic agent part is not posted, this write may write unclear you can go to see the source code or go to the web search dynamic agent design pattern analysis, the video will tell you clearly. Dynamic proxy I remember when I just taught myself was really a hurdle, but this hurdle we have to overcome, later we will talk about Android Hook technology and plug-in development will be repeated. Xutils is a class whose reflection loop is used to retrieve the annotation value of the property and then dynamically inject it into the control property through findViewById. Event injection is similar to starting with findViewById and then using dynamic proxies to reflect execution methods.

2.3 Butterknife may be more popular than Xutils. Firstly, in terms of performance, Xutils is completely reflective, while Butterknife is a lightweight reflection with compile-time annotations. And it also provides an Android Studio plugin does not require us to write any code, the author JakeWharton famously wrote many large third-party frameworks, https://github.com/JakeWharton/butterknife

public class MainActivity extends AppCompatActivity {

    @Bind(R.id.icon)
    ImageView icon;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }

    @OnClick(R.id.icon)
    public void onClick() {
        Toast.makeText(this, "Picture clicked.", Toast.LENGTH_LONG).show(); }}Copy the code

The code above is generated automatically for us by the plug-in, so we don’t have to write it manually anymore. Properties can’t be private, and the onClick() method can’t be private, or you’ll get an error if you look at the source code implementation later. The names and methods automatically generated by the plugin feel weird and don’t conform to our Android source code specification. Of course, later we will write an Android Studio plug-in to work with our own IOC annotation framework.

2.4 Butterknife source code analysis

Butterknife.bind (this); butterknife.bind (this); You’ll find nothing in it but this:

static void bind(Object target, Object source, Finder finder) { Class<? > targetClass = target.getClass(); try {if (debug) Log.d(TAG, "Looking up view binder for "+ targetClass.getName()); ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);if(viewBinder ! = null) {// Directly execute method viewbinder.bind (finder, target,source);
      }
    } catch (Exception e) {
      throw new RuntimeException("Unable to bind views for "+ targetClass.getName(), e); }}Copy the code

If it looks like we can’t see anything from here, how does the workflow work? We can get a clue by looking at the Bind Annotation Annotation class and the ButterKnifeProcessor class:

@Retention(CLASS) @Target(FIELD)
public @interface Bind {
  /** View ID to which the field will be bound. */
  int[] value();
}
Copy the code

@retention is the Annotation of CLASS, which is resolved automatically by the Annotation Processing Tool (APT). ButterKnife uses Java Annotation Processing, which handles @bind, @onclick (ButterKnife supports many other annotations) when Java code is compiled into Java bytecode.

You can define annotations and define your own parser to handle them. Annotation Processing is performed at compile time by reading in Java source code, parsing annotations, and generating new Java code. The newly generated Java code is eventually compiled into Java bytecode, and the Annotation Processor cannot change the Java classes read in, such as adding or removing Java methods. I think we have a general idea of how it works, right?

2.5 ButterKnife Workflow

When you compile your Android project, the process() method of the ButterKnifeProcessor class in the ButterKnife project does the following:

  • It starts by scanning all the ButterKnife annotations @bind, @onclick, @onItemclicked, and so on in Java code.

  • When it finds any annotations in a class, ButterKnifeProcessor generates a Java class for you with a similar name, okay? ViewBinder, this newly generated class implements the ViewBinder interface.

  • The ViewBinder class contains all the corresponding code, such as @bind for findViewById(), @onclick for View.setonClickListener (), and so on.

  • Finally, when the Activity starts butterknip.bind (this), the ButterKnife loads the corresponding ViewBinder classes and calls their bind() method.

Now do we understand why our generated properties and methods can’t be private? Let’s take a final look at the class class generated at compile time

public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {
  @Override public void bind(final Finder finder, final T target, Object source) {
    View view;
    view = finder.findRequiredView(source, 2131427372, "field 'icon' and method 'onClick'");
    target.icon = finder.castView(view, 2131427372, "field 'icon'");
    view.setOnClickListener(
      new butterknife.internal.DebouncingOnClickListener() {
        @Override public void doClick(View p0) { target.onClick(); }}); } @Override public void unbind(T target) { target.icon = null; }}Copy the code

If now want us to choose, I would definitely choose butterknife this mainly because it has a plugin I completely don’t have to write any code, and there is no use is not a total reflection in terms of performance also has a little improvement, but sometimes I want to add a network of detection of annotations, this would be a little trouble, plug-in to generate the attribute name and method name can be a little hurt, Let’s try to write it ourselves. ###3. Feed yourself


Next we own annotation framework to implement a set of the IOC, the reflex annotations and Xutils similar way, but we try not to write so trouble, also don’t plan to use dynamic proxy, we extend a testing network annotations, such as no net when we don’t go to prompt execution method but giving no network at the same time, it does not allow users to click again and again. At this point, someone started spraying, knowing that reflection would affect performance why would they use it? I’m just going to say it off the top of my head, I’ll admit that reflection affects performance but it’s not a big deal and we can test it ourselves and see what happens if we reflect 10,000 times, and if you have to, I can’t help it, but let’s spend more time on UI rendering and bitmaps and services and handlers, I have never encountered a situation where reflection calls GC or runs out of memory, and I will use reflection later in plug-in development. Let’s get started. 3.1 Control Property Injection I will not introduce the use of annotations here. If you are not familiar with this, you can look it up or watch the video I recorded. First, we will deal with the injection of control properties, but we need to consider various situations:

/** * Created by Darren on 2017/2/4. * Email: [email protected] * Description: */ / RUNTIME RUNTIME test, The @retention (retentionPolicy.runtime) // FIELD annotation can only be placed on the property METHOD and TYPE CLASS when butterKnife is compiled using the SOURCE resource @target (elementtype. FIELD) public @interface ViewById {// ViewById(R.id.xxx) int value(); }Copy the code
/** * Created by Darren on 2017/2/4. * Email: [email protected] * Description: IOC inject ViewUtils */ public class ViewUtils {public static void inject(Activity Activity) {inject(new ViewFinder(activity), activity); } public static void inject(View View) {inject(new ViewFinder(View), View); } // Compatible Fragment public static void inject(View View, Object Object) {inject(new ViewFinder(View), Object); } private static void inject(ViewFinder viewFinder, Object object) { injectFiled(viewFinder, object); injectEvent(viewFinder, object); } private static void injectEvent(ViewFinder ViewFinder, Object Object) {} private static void injectFiled(ViewFinder ViewFinder, Object Object) {// Object --> Activity or fragment or view is a reflection class // viewFinder --> just a view findViewById helper class // 1. Get all attributes Class<? > clazz = object.getClass(); [] fields = clazz.getDeclaredFields();for(Field field : fields) { // 2. ViewById ViewById = field.getannotation (viewByID.class);if(viewById ! Int viewId = viewByid.value (); = null) {// Get viewId on ViewById int viewId = viewByid.value (); View View = viewFinder.findViewById(viewId);if(view ! = null) {// 4. Reflection infuses View attributes // Set all attributes to infuse including private and public field.setaccessible (true); try { field.set(object, view); } catch (IllegalAccessException e) { e.printStackTrace(); }}else {
                    throw new RuntimeException("Invalid @ViewInject for "
                            + clazz.getSimpleName() + "." + field.getName());
                }
            }
        }
    }
}

Copy the code

3.2 Click Event Injection We are only going to setOnclickListener for the injection of events and we will not use dynamic proxy design mode for the other uncommon ones.

Private static void injectEvent(ViewFinder ViewFinder, Object Object) {// 1. Get all methods Class<? > clazz = object.getClass(); Method[] methods = clazz.getDeclaredMethods(); // 2. Get all the ids above the methodfor (Method method : methods) {
            OnClick onClick = method.getAnnotation(OnClick.class);
            if(onClick ! = null) { int[] viewIds = onClick.value();if (viewIds.length > 0) {
                    for(int viewId : viewIds) { // 3. I'm going to go through all the ids findViewById and thensetOnClickListener
                        View view = viewFinder.findViewById(viewId);
                        if(view ! = null) { view.setOnClickListener(new DeclaredOnClickListener(method, object)); } } } } } } private static class DeclaredOnClickListener implements View.OnClickListener { private Method mMethod; private Object mHandlerType; public DeclaredOnClickListener(Method method, Object handlerType) { mMethod = method; mHandlerType = handlerType; } @Override public void onClick(View v) { // 4. Reflection implementation method mmethod.setaccessible (true); try { mMethod.invoke(mHandlerType, v); } catch (Exception e) { e.printStackTrace(); try { mMethod.invoke(mHandlerType, null); } catch (Exception e1) { e1.printStackTrace(); }}}}Copy the code
public class MainActivity extends AppCompatActivity {

    @ViewById(R.id.icon)
    private ImageView mIconIv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewUtils.inject(this);
        mIconIv.setImageResource(R.drawable.icon);
    }

    @OnClick(R.id.icon)
    private void onClick(View view) {
        int i = 2 / 0;
        Toast.makeText(this, "Picture clicked."+i, Toast.LENGTH_LONG).show(); }}Copy the code

Use and xutils similar, methods and attributes can be private, but there is a point we in the Onclick event method no matter what operation is not error, so if you find a bug need to pay attention to the warning log, this is not pit whit? In fact, in our development process to the user or boss when we play the most afraid is flash back, now we will not flash back even if there are bugs, but we need to pay attention to the warning log is quite good. 3.3 Extend dynamic detection of network annotations

We finally expand to add a note to detect the network, sometimes we need to detect the network in the click method, such as login and registration, if we have no network, there is no need to call the interface to start the thread, just need to remind the user that there is no network. Of course this is just an extension.

public class MainActivity extends AppCompatActivity {

    @ViewById(R.id.icon)
    private ImageView mIconIv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); ViewUtils.inject(this); mIconIv.setImageResource(R.drawable.icon); } @onclick (r.i.con) @checknet private void OnClick(View View) {toast.maketext (this,"Picture clicked.", Toast.LENGTH_LONG).show(); }}Copy the code

Extension we write such an example, this is the connotation of the sub-framework to build the first phase of sharing, I hope you can first go to understand all our sharing content 2017Android advanced road with you,

Attached video interpretation address: http://pan.baidu.com/s/1kVFMRQJ